package web_test import ( "context" "fmt" "io" "net/http" "net/http/httptest" "os" "strings" "testing" "time" embeddedpostgres "github.com/fergusstrange/embedded-postgres" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/require" "gitea.d-ma.be/mathias/tapir/internal/adapters/store" "gitea.d-ma.be/mathias/tapir/internal/domain" "gitea.d-ma.be/mathias/tapir/internal/web" ) // dsn points at the in-process Postgres started in TestMain. Handler tests run // against the real store (real SQL, the lane-A actions) behind StubAuth — the // full read/write path minus live Dex. var dsn string func TestMain(m *testing.M) { const port = 54330 // distinct from the store package's embedded PG (54329) dsn = fmt.Sprintf("postgres://postgres:postgres@localhost:%d/postgres?sslmode=disable", port) pg := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Port(port)) if err := pg.Start(); err != nil { fmt.Fprintf(os.Stderr, "embedded-postgres start: %v\n", err) os.Exit(1) } code := m.Run() if err := pg.Stop(); err != nil { fmt.Fprintf(os.Stderr, "embedded-postgres stop: %v\n", err) } os.Exit(code) } const ( userID = "11111111-1111-1111-1111-111111111111" videoX = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" videoY = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" ) func newStore(t *testing.T) *store.Store { t.Helper() s, err := store.New(context.Background(), dsn) require.NoError(t, err) t.Cleanup(s.Close) return s } func rawPool(t *testing.T) *pgxpool.Pool { t.Helper() p, err := pgxpool.New(context.Background(), dsn) require.NoError(t, err) t.Cleanup(p.Close) return p } func resetDB(t *testing.T, p *pgxpool.Pool) { t.Helper() _, err := p.Exec(context.Background(), `TRUNCATE summary_actions, sink_deliveries, summaries, transcripts, videos, users CASCADE`) require.NoError(t, err) } // newApp builds the App under test: the real store, StubAuth (allow-all) keyed to // the configured user. This is exactly cmd/tapir's serve wiring minus Dex. func newApp(t *testing.T) *web.App { t.Helper() return &web.App{ Store: newStore(t), Auth: web.StubAuth{U: web.User{Subject: userID}}, UserID: userID, } } func summary(videoID, text string) domain.Summary { return domain.Summary{ UserID: userID, VideoID: videoID, Summary: text, Highlights: []string{"highlight one", "highlight two"}, Takeaways: []string{"takeaway one"}, AIProvider: "local", AIModel: "phi4-mini", } } func seedVideo(t *testing.T, p *pgxpool.Pool, videoID, title, url string, published time.Time) { t.Helper() var pub any if !published.IsZero() { pub = published } _, err := p.Exec(context.Background(), `INSERT INTO videos (id, user_id, provider, provider_video_id, title, url, published_at) VALUES ($1, $2, 'youtube', $3, $4, $5, $6)`, videoID, userID, "pv-"+videoID[:8], title, url, pub) require.NoError(t, err) } func do(t *testing.T, app *web.App, req *http.Request) *httptest.ResponseRecorder { t.Helper() rec := httptest.NewRecorder() app.Router().ServeHTTP(rec, req) return rec } func body(t *testing.T, rec *httptest.ResponseRecorder) string { t.Helper() b, err := io.ReadAll(rec.Body) require.NoError(t, err) return string(b) } func TestHealthzNoAuth(t *testing.T) { app := newApp(t) rec := do(t, app, httptest.NewRequest(http.MethodGet, "/healthz", nil)) require.Equal(t, http.StatusOK, rec.Code) require.Equal(t, "ok", body(t, rec)) } func TestListRendersRowsAndActionState(t *testing.T) { ctx := context.Background() app := newApp(t) p := rawPool(t) resetDB(t, p) require.NoError(t, app.Store.SetAction(ctx, userID, videoX, "watched")) // action exists pre-summary require.NoError(t, deliver(ctx, app, videoX, "body x")) require.NoError(t, deliver(ctx, app, videoY, "body y")) seedVideo(t, p, videoX, "X Title", "https://x", time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)) seedVideo(t, p, videoY, "Y Title", "https://y", time.Time{}) rec := do(t, app, httptest.NewRequest(http.MethodGet, "/", nil)) require.Equal(t, http.StatusOK, rec.Code) html := body(t, rec) require.Contains(t, html, "