package api import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "github.com/go-chi/chi/v5" ) func newMeProfileRouter(h *handlers) chi.Router { r := chi.NewRouter() r.Put("/api/me/profile", h.handleUpdateMyProfile) return r } func TestUpdateProfile_DisplayNameOnly(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, "prof1", "pw", false) body := `{"display_name":"My Name"}` req := httptest.NewRequest(http.MethodPut, "/api/me/profile", bytes.NewReader([]byte(body))) req = withUser(req, user) rec := httptest.NewRecorder() newMeProfileRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String()) } var resp meProfileResp if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v", err) } if resp.DisplayName == nil || *resp.DisplayName != "My Name" { t.Errorf("display_name = %v, want 'My Name'", resp.DisplayName) } } func TestUpdateProfile_EmailOnly_Lowercased(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, "prof2", "pw", false) body := `{"email":"User@Example.COM"}` req := httptest.NewRequest(http.MethodPut, "/api/me/profile", bytes.NewReader([]byte(body))) req = withUser(req, user) rec := httptest.NewRecorder() newMeProfileRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String()) } // Verify DB row is lowercased. var email *string if err := pool.QueryRow(context.Background(), "SELECT email FROM users WHERE id = $1", user.ID).Scan(&email); err != nil { t.Fatalf("read email: %v", err) } if email == nil || *email != "user@example.com" { t.Errorf("DB email = %v, want 'user@example.com'", email) } } func TestUpdateProfile_BothFields(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, "prof3", "pw", false) body := `{"display_name":"Both Fields","email":"both@example.com"}` req := httptest.NewRequest(http.MethodPut, "/api/me/profile", bytes.NewReader([]byte(body))) req = withUser(req, user) rec := httptest.NewRecorder() newMeProfileRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String()) } var resp meProfileResp if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v", err) } if resp.DisplayName == nil || *resp.DisplayName != "Both Fields" { t.Errorf("display_name = %v, want 'Both Fields'", resp.DisplayName) } if resp.Email == nil || *resp.Email != "both@example.com" { t.Errorf("email = %v, want 'both@example.com'", resp.Email) } } func TestUpdateProfile_EmptyBody_NoOp(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, "prof4", "pw", false) body := `{}` req := httptest.NewRequest(http.MethodPut, "/api/me/profile", bytes.NewReader([]byte(body))) req = withUser(req, user) rec := httptest.NewRecorder() newMeProfileRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200 (no-op); body=%s", rec.Code, rec.Body.String()) } var resp meProfileResp if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v", err) } // Neither field should have been set (seeded without them). if resp.DisplayName != nil { t.Errorf("display_name = %v, want nil (no-op)", resp.DisplayName) } } func TestUpdateProfile_InvalidEmail_400(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, "prof5", "pw", false) body := `{"email":"notanemail"}` req := httptest.NewRequest(http.MethodPut, "/api/me/profile", bytes.NewReader([]byte(body))) req = withUser(req, user) rec := httptest.NewRecorder() newMeProfileRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Errorf("status = %d, want 400 (email_invalid)", rec.Code) } } func TestUpdateProfile_EmailTaken_409(t *testing.T) { if os.Getenv("MINSTREL_TEST_DATABASE_URL") == "" { t.Skip("MINSTREL_TEST_DATABASE_URL not set") } h, pool := testHandlers(t) other := seedUser(t, pool, "prof6other", "pw", false) user := seedUser(t, pool, "prof6user", "pw", false) // Give the other user an email directly. if _, err := pool.Exec(context.Background(), "UPDATE users SET email = 'taken@example.com' WHERE id = $1", other.ID); err != nil { t.Fatalf("setup email: %v", err) } body := `{"email":"taken@example.com"}` req := httptest.NewRequest(http.MethodPut, "/api/me/profile", bytes.NewReader([]byte(body))) req = withUser(req, user) rec := httptest.NewRecorder() newMeProfileRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusConflict { t.Errorf("status = %d, want 409 (email_taken)", rec.Code) } } func TestUpdateProfile_ClearDisplayName(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, "prof7", "pw", false) // First, set a display name. if _, err := pool.Exec(context.Background(), "UPDATE users SET display_name = 'Will Be Cleared' WHERE id = $1", user.ID); err != nil { t.Fatalf("setup display_name: %v", err) } // Clear it by sending empty string. body := `{"display_name":""}` req := httptest.NewRequest(http.MethodPut, "/api/me/profile", bytes.NewReader([]byte(body))) req = withUser(req, user) rec := httptest.NewRecorder() newMeProfileRouter(h).ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String()) } // DB should now have NULL display_name. var dn *string if err := pool.QueryRow(context.Background(), "SELECT display_name FROM users WHERE id = $1", user.ID).Scan(&dn); err != nil { t.Fatalf("read display_name: %v", err) } if dn != nil { t.Errorf("display_name = %v, want nil (cleared)", dn) } }