package api import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "github.com/go-chi/chi/v5" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" ) func newAdminInvitesRouter(h *handlers) chi.Router { r := chi.NewRouter() r.Route("/api/admin", func(admin chi.Router) { admin.Use(auth.RequireAdmin()) admin.Get("/invites", h.handleListInvites) admin.Post("/invites", h.handleCreateInvite) admin.Delete("/invites/{token}", h.handleDeleteInvite) }) return r } func TestAdminInvites_CreateListDelete(t *testing.T) { if os.Getenv("MINSTREL_TEST_DATABASE_URL") == "" { t.Skip("MINSTREL_TEST_DATABASE_URL not set") } h, pool := testHandlers(t) admin := seedUser(t, pool, "invadmin", "pw", true) // Create req := httptest.NewRequest(http.MethodPost, "/api/admin/invites", nil) req = withUser(req, admin) rec := httptest.NewRecorder() newAdminInvitesRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("create status = %d, want 200; body=%s", rec.Code, rec.Body.String()) } var created struct { Token string `json:"token"` ExpiresAt string `json:"expires_at"` } if err := json.Unmarshal(rec.Body.Bytes(), &created); err != nil { t.Fatalf("decode: %v", err) } if created.Token == "" { t.Fatal("created.Token is empty") } // List req = httptest.NewRequest(http.MethodGet, "/api/admin/invites", nil) req = withUser(req, admin) rec = httptest.NewRecorder() newAdminInvitesRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("list status = %d, want 200", rec.Code) } var listed struct { Invites []struct{ Token string } `json:"invites"` } if err := json.Unmarshal(rec.Body.Bytes(), &listed); err != nil { t.Fatalf("decode: %v", err) } found := false for _, inv := range listed.Invites { if inv.Token == created.Token { found = true } } if !found { t.Errorf("created invite not in list response") } // Delete req = httptest.NewRequest(http.MethodDelete, "/api/admin/invites/"+created.Token, nil) req = withUser(req, admin) rec = httptest.NewRecorder() newAdminInvitesRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusNoContent { t.Errorf("delete status = %d, want 204", rec.Code) } // Verify gone var count int if err := pool.QueryRow(context.Background(), "SELECT count(*) FROM user_invites WHERE token = $1", created.Token).Scan(&count); err != nil { t.Fatalf("verify: %v", err) } if count != 0 { t.Errorf("invite still exists after delete (count=%d)", count) } } func TestAdminInvites_NonAdmin_403(t *testing.T) { if os.Getenv("MINSTREL_TEST_DATABASE_URL") == "" { t.Skip("MINSTREL_TEST_DATABASE_URL not set") } h, pool := testHandlers(t) user := seedUser(t, pool, "invnonadmin", "pw", false) req := httptest.NewRequest(http.MethodPost, "/api/admin/invites", nil) req = withUser(req, user) rec := httptest.NewRecorder() newAdminInvitesRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusForbidden { t.Errorf("status = %d, want 403", rec.Code) } }