package web import ( "net/http" "net/http/httptest" "strings" "testing" "maisym.com/hsa/internal/auth" "maisym.com/hsa/internal/config" ) func testServer() *Server { var key [32]byte copy(key[:], "web-test-key-32-bytes-padded!!!!") return &Server{cfg: config.Config{ SessionKey: key, RequiredGroup: "hsa-users", SecureCookies: false, }} } func TestStaticCSS(t *testing.T) { s := testServer() req := httptest.NewRequest(http.MethodGet, "/static/style.css", nil) rec := httptest.NewRecorder() s.Routes().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } if !strings.Contains(rec.Body.String(), "font-family") { t.Errorf("CSS not served: %q", rec.Body.String()) } } func TestHealthz(t *testing.T) { s := testServer() req := httptest.NewRequest(http.MethodGet, "/healthz", nil) rec := httptest.NewRecorder() s.Routes().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } if !strings.Contains(rec.Body.String(), "ok") { t.Errorf("body = %q, want to contain ok", rec.Body.String()) } } func TestHome_NoSession_RedirectsToLogin(t *testing.T) { s := testServer() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() s.Routes().ServeHTTP(rec, req) if rec.Code != http.StatusFound { t.Fatalf("status = %d, want 302", rec.Code) } if loc := rec.Header().Get("Location"); loc != "/login" { t.Errorf("Location = %q, want /login", loc) } } func TestHome_ValidSession_ShowsUploadForm(t *testing.T) { s := testServerWithStore(t) sess := auth.Session{Subject: "jm@example.com", Groups: []string{"hsa-users"}} cookie, err := auth.EncodeSession(sess, s.cfg.SessionKey) if err != nil { t.Fatal(err) } req := httptest.NewRequest(http.MethodGet, "/", nil) req.AddCookie(&http.Cookie{Name: sessionCookie, Value: cookie}) rec := httptest.NewRecorder() s.Routes().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200", rec.Code) } body := rec.Body.String() if !strings.Contains(body, "jm@example.com") { t.Errorf("body missing subject: %q", body) } if !strings.Contains(body, "Add receipt") { t.Errorf("body missing upload form: %q", body) } } func TestHome_TamperedSession_RedirectsToLogin(t *testing.T) { s := testServer() sess := auth.Session{Subject: "jm@example.com", Groups: []string{"hsa-users"}} cookie, _ := auth.EncodeSession(sess, s.cfg.SessionKey) tampered := cookie[:len(cookie)-3] + "AAA" req := httptest.NewRequest(http.MethodGet, "/", nil) req.AddCookie(&http.Cookie{Name: sessionCookie, Value: tampered}) rec := httptest.NewRecorder() s.Routes().ServeHTTP(rec, req) if rec.Code != http.StatusFound { t.Fatalf("status = %d, want 302", rec.Code) } if loc := rec.Header().Get("Location"); loc != "/login" { t.Errorf("Location = %q, want /login", loc) } } func TestLogout_ClearsCookieAndRedirects(t *testing.T) { s := testServer() req := httptest.NewRequest(http.MethodGet, "/logout", nil) rec := httptest.NewRecorder() s.Routes().ServeHTTP(rec, req) if rec.Code != http.StatusFound { t.Fatalf("status = %d, want 302", rec.Code) } // The session cookie should be expired (MaxAge < 0). var cleared bool for _, c := range rec.Result().Cookies() { if c.Name == sessionCookie && c.MaxAge < 0 { cleared = true } } if !cleared { t.Error("logout did not clear the session cookie") } }