package handlers import ( "context" "net/http" "net/http/httptest" "path/filepath" "strings" "testing" "veola/internal/apify" "veola/internal/auth" "veola/internal/config" "veola/internal/crypto" "veola/internal/db" "veola/internal/models" "veola/internal/ntfy" "veola/internal/scheduler" ) // newTestApp builds an App backed by a fresh sqlite db in t.TempDir(). The // scheduler, apify, and ntfy clients are wired but unused by the routes we // hit here. The returned http.Handler is App.Routes(). func newTestApp(t *testing.T) (*App, http.Handler) { t.Helper() dbPath := filepath.Join(t.TempDir(), "test.db") sqlDB, err := db.Open(dbPath) if err != nil { t.Fatalf("db.Open: %v", err) } t.Cleanup(func() { sqlDB.Close() }) key, err := crypto.DeriveKey([]byte("test-encryption-key-32-bytes-min-aaaaaa")) if err != nil { t.Fatalf("DeriveKey: %v", err) } store := db.NewStore(sqlDB, key) am, err := auth.NewManager(sqlDB, store, strings.Repeat("a", 32), false) if err != nil { t.Fatalf("auth.NewManager: %v", err) } cfg := &config.Config{} ap := apify.New("") nt := ntfy.New("") sc := scheduler.New(cfg, store, ap, nt) app := New(cfg, store, am, ap, nt, sc) return app, app.Routes() } func TestHealthz(t *testing.T) { _, h := newTestApp(t) req := httptest.NewRequest(http.MethodGet, "/healthz", nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } if got := rec.Body.String(); got != "ok" { t.Fatalf("body = %q, want %q", got, "ok") } } func TestSetupGateRedirectsWhenNoUsers(t *testing.T) { _, h := newTestApp(t) req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusSeeOther { t.Fatalf("status = %d, want 303", rec.Code) } if loc := rec.Header().Get("Location"); loc != "/setup" { t.Fatalf("Location = %q, want /setup", loc) } } func TestRequireAuthRedirectsToLogin(t *testing.T) { app, h := newTestApp(t) hash, err := auth.HashPassword("a-long-enough-password") if err != nil { t.Fatalf("HashPassword: %v", err) } if _, err := app.Store.CreateUser(context.Background(), "admin", hash, models.RoleAdmin); err != nil { t.Fatalf("CreateUser: %v", err) } req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusSeeOther { t.Fatalf("status = %d, want 303", rec.Code) } if loc := rec.Header().Get("Location"); loc != "/login" { t.Fatalf("Location = %q, want /login", loc) } } func TestLoginPageRenders(t *testing.T) { app, h := newTestApp(t) hash, _ := auth.HashPassword("a-long-enough-password") if _, err := app.Store.CreateUser(context.Background(), "admin", hash, models.RoleAdmin); err != nil { t.Fatalf("CreateUser: %v", err) } req := httptest.NewRequest(http.MethodGet, "/login", nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } if !strings.Contains(rec.Body.String(), "") } }