package setup_test import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" "git.unsupervised.ca/walkies/internal/setup" ) // ---- fakes --------------------------------------------------------------- type fakeStore struct { needsSetup bool needsSetupErr error createAdminID int64 createAdminErr error } func (f *fakeStore) NeedsSetup(_ context.Context) (bool, error) { return f.needsSetup, f.needsSetupErr } func (f *fakeStore) CreateAdmin(_ context.Context, _, _, _ string) (int64, error) { return f.createAdminID, f.createAdminErr } type fakeTokenIssuer struct { token string err error } func (f *fakeTokenIssuer) IssueToken(_ int64, _ string) (string, error) { return f.token, f.err } // Compile-time interface checks. var _ setup.Storer = (*fakeStore)(nil) var _ setup.TokenIssuer = (*fakeTokenIssuer)(nil) // ---- helpers ------------------------------------------------------------- func do(t *testing.T, handler http.HandlerFunc, method, path, body string) *httptest.ResponseRecorder { t.Helper() var b *bytes.Reader if body != "" { b = bytes.NewReader([]byte(body)) } else { b = bytes.NewReader(nil) } req := httptest.NewRequest(method, path, b) if body != "" { req.Header.Set("Content-Type", "application/json") } w := httptest.NewRecorder() handler.ServeHTTP(w, req) return w } // ---- Status tests -------------------------------------------------------- func TestStatus_NeedsSetup(t *testing.T) { h := setup.NewHandlerFromInterfaces( &fakeStore{needsSetup: true}, &fakeTokenIssuer{}, ) w := do(t, h.Status, "GET", "/api/v1/setup/status", "") if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body) } var resp map[string]bool json.NewDecoder(w.Body).Decode(&resp) if !resp["needs_setup"] { t.Error("expected needs_setup=true") } } func TestStatus_SetupDone(t *testing.T) { h := setup.NewHandlerFromInterfaces( &fakeStore{needsSetup: false}, &fakeTokenIssuer{}, ) w := do(t, h.Status, "GET", "/api/v1/setup/status", "") if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body) } var resp map[string]bool json.NewDecoder(w.Body).Decode(&resp) if resp["needs_setup"] { t.Error("expected needs_setup=false") } } // ---- CreateAdmin tests --------------------------------------------------- func TestCreateAdmin_Success(t *testing.T) { h := setup.NewHandlerFromInterfaces( &fakeStore{createAdminID: 1}, &fakeTokenIssuer{token: "jwt-token"}, ) w := do(t, h.CreateAdmin, "POST", "/api/v1/setup/admin", `{"name":"Admin","email":"admin@example.com","password":"supersecret"}`) if w.Code != http.StatusCreated { t.Fatalf("expected 201, got %d: %s", w.Code, w.Body) } var resp map[string]string json.NewDecoder(w.Body).Decode(&resp) if resp["token"] != "jwt-token" { t.Errorf("expected token jwt-token, got %q", resp["token"]) } } func TestCreateAdmin_AlreadyDone(t *testing.T) { h := setup.NewHandlerFromInterfaces( &fakeStore{createAdminErr: setup.ErrSetupAlreadyDone}, &fakeTokenIssuer{}, ) w := do(t, h.CreateAdmin, "POST", "/api/v1/setup/admin", `{"name":"Admin","email":"admin@example.com","password":"supersecret"}`) if w.Code != http.StatusForbidden { t.Fatalf("expected 403, got %d: %s", w.Code, w.Body) } } func TestCreateAdmin_MissingFields(t *testing.T) { h := setup.NewHandlerFromInterfaces( &fakeStore{}, &fakeTokenIssuer{}, ) tests := []struct { name string body string }{ {"missing name", `{"email":"a@b.com","password":"supersecret"}`}, {"missing email", `{"name":"Admin","password":"supersecret"}`}, {"missing password", `{"name":"Admin","email":"a@b.com"}`}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { w := do(t, h.CreateAdmin, "POST", "/api/v1/setup/admin", tc.body) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body) } }) } } func TestCreateAdmin_PasswordTooShort(t *testing.T) { h := setup.NewHandlerFromInterfaces( &fakeStore{}, &fakeTokenIssuer{}, ) w := do(t, h.CreateAdmin, "POST", "/api/v1/setup/admin", `{"name":"Admin","email":"admin@example.com","password":"short"}`) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body) } }