package server_test import ( "bytes" "context" "database/sql" "encoding/json" "fmt" "io" "log/slog" "net/http" "net/http/httptest" "os" "testing" "time" "github.com/golang-migrate/migrate/v4" migpg "github.com/golang-migrate/migrate/v4/database/postgres" "github.com/golang-migrate/migrate/v4/source/iofs" _ "github.com/jackc/pgx/v5/stdlib" tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres" "gitea.meghsakha.com/platform/tenant-registry/internal/config" "gitea.meghsakha.com/platform/tenant-registry/internal/server" "gitea.meghsakha.com/platform/tenant-registry/internal/store" "gitea.meghsakha.com/platform/tenant-registry/migrations" ) // ─── harness ────────────────────────────────────────────────────────────── type testHarness struct { t *testing.T srv *httptest.Server store store.Store tenant *store.Tenant // pre-created acme tenant } func (h *testHarness) Close() { h.srv.Close() h.store.Close() } // every test runs against both stores so we know they're equivalent. func eachStore(t *testing.T, run func(*testing.T, *testHarness)) { t.Run("memory", func(t *testing.T) { h := newMemoryHarness(t) defer h.Close() run(t, h) }) t.Run("postgres", func(t *testing.T) { if testing.Short() { t.Skip("skipping postgres harness under -short") } h := newPostgresHarness(t) defer h.Close() run(t, h) }) } func newMemoryHarness(t *testing.T) *testHarness { t.Helper() mem := store.NewMemory() tenant, _ := mem.GetTenantBySlug(context.Background(), "acme") return wireHarness(t, mem, tenant) } func newPostgresHarness(t *testing.T) *testHarness { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() pgc, err := tcpostgres.Run(ctx, "postgres:16-alpine", tcpostgres.WithDatabase("tenant_registry_test"), tcpostgres.WithUsername("test"), tcpostgres.WithPassword("test"), tcpostgres.BasicWaitStrategies(), ) if err != nil { t.Skipf("skipping postgres harness: docker unreachable (%v)", err) } dsn, err := pgc.ConnectionString(ctx, "sslmode=disable") if err != nil { _ = pgc.Terminate(context.Background()) t.Fatalf("dsn: %v", err) } t.Cleanup(func() { c, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() _ = pgc.Terminate(c) }) // run migrations src, err := iofs.New(migrations.FS, ".") if err != nil { t.Fatal(err) } db, err := sql.Open("pgx", dsn) if err != nil { t.Fatal(err) } driver, err := migpg.WithInstance(db, &migpg.Config{}) if err != nil { t.Fatal(err) } m, err := migrate.NewWithInstance("iofs", src, "postgres", driver) if err != nil { t.Fatal(err) } if err := m.Up(); err != nil && err.Error() != "no change" { t.Fatalf("migrate: %v", err) } _, _ = m.Close() _ = db.Close() pg, err := store.NewPostgres(ctx, dsn) if err != nil { t.Fatalf("new postgres: %v", err) } // seed an acme tenant so the per-endpoint tests can reuse the slug. tenant, err := pg.CreateTenant(ctx, store.TenantCreate{ Slug: "acme", Name: "Acme Inc.", Plan: "professional", }) if err != nil { t.Fatalf("seed acme: %v", err) } return wireHarness(t, pg, tenant) } func wireHarness(t *testing.T, s store.Store, seed *store.Tenant) *testHarness { t.Helper() logger := slog.New(slog.NewTextHandler(io.Discard, nil)) handler := server.NewRouter(&server.Server{ Cfg: &config.Config{Env: "dev"}, Log: logger, Store: s, }) return &testHarness{ t: t, srv: httptest.NewServer(handler), store: s, tenant: seed, } } func (h *testHarness) do(method, path string, body any) (*http.Response, []byte) { h.t.Helper() var reader io.Reader if body != nil { buf, _ := json.Marshal(body) reader = bytes.NewReader(buf) } req, err := http.NewRequest(method, h.srv.URL+path, reader) if err != nil { h.t.Fatal(err) } if body != nil { req.Header.Set("Content-Type", "application/json") } resp, err := http.DefaultClient.Do(req) if err != nil { h.t.Fatal(err) } defer func() { _ = resp.Body.Close() }() raw, _ := io.ReadAll(resp.Body) return resp, raw } func decode[T any](t *testing.T, raw []byte) T { t.Helper() var v T if err := json.Unmarshal(raw, &v); err != nil { t.Fatalf("decode: %v; raw=%s", err, raw) } return v } // silence unused-import linter warnings if a test is removed temporarily. var _ = fmt.Sprintf var _ = os.Stderr