Step 1 of the build order in docs/decisions.md. Boots, applies migrations before serving, and serves a health check and an empty admin page. - 001_init.sql is the full schema from docs/spec.md, including the check constraints and indexes the old app lacked - The startup sweep fails submissions left mid-conversion by a restart; an in-process goroutine dies with the process and those rows would otherwise say converting forever - Admin is Basic Auth from env on its own listener, fatal at startup when ADMIN_PASSWORD is unset
76 lines
1.9 KiB
Go
76 lines
1.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func TestRequireAdmin(t *testing.T) {
|
|
a := &app{cfg: config{adminUser: "admin", adminPass: "s3cret"}}
|
|
h := a.requireAdmin(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusTeapot)
|
|
}))
|
|
|
|
for _, tc := range []struct {
|
|
name, user, pass string
|
|
auth bool
|
|
want int
|
|
}{
|
|
{name: "no credentials", want: http.StatusUnauthorized},
|
|
{name: "wrong password", user: "admin", pass: "hunter2", auth: true, want: http.StatusUnauthorized},
|
|
{name: "wrong user", user: "root", pass: "s3cret", auth: true, want: http.StatusUnauthorized},
|
|
{name: "correct", user: "admin", pass: "s3cret", auth: true, want: http.StatusTeapot},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := httptest.NewRequest("GET", "/admin", nil)
|
|
if tc.auth {
|
|
r.SetBasicAuth(tc.user, tc.pass)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
if w.Code != tc.want {
|
|
t.Fatalf("status = %d, want %d", w.Code, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Set TEST_DATABASE_URL to run this against a throwaway database.
|
|
func TestMigrateIsIdempotent(t *testing.T) {
|
|
url := os.Getenv("TEST_DATABASE_URL")
|
|
if url == "" {
|
|
t.Skip("TEST_DATABASE_URL not set")
|
|
}
|
|
ctx := context.Background()
|
|
pool, err := pgxpool.New(ctx, url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
|
|
if _, err := pool.Exec(ctx, `drop schema public cascade; create schema public`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := range 2 {
|
|
if err := migrate(ctx, pool); err != nil {
|
|
t.Fatalf("migrate run %d: %v", i+1, err)
|
|
}
|
|
}
|
|
if err := sweep(ctx, pool); err != nil {
|
|
t.Fatalf("sweep: %v", err)
|
|
}
|
|
|
|
var n int
|
|
if err := pool.QueryRow(ctx, `select count(*) from schema_migrations`).Scan(&n); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 1 {
|
|
t.Fatalf("applied migrations = %d, want 1", n)
|
|
}
|
|
}
|