2026-07-25 17:55:13 +02:00
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
2026-07-25 18:10:09 +02:00
|
|
|
"path/filepath"
|
2026-07-25 17:55:13 +02:00
|
|
|
"testing"
|
2026-07-25 18:10:09 +02:00
|
|
|
|
|
|
|
|
authmock "geniusrun/backend/internal/auth/mock"
|
|
|
|
|
"geniusrun/backend/internal/garmin"
|
|
|
|
|
"geniusrun/backend/internal/garmin/mock"
|
|
|
|
|
"geniusrun/backend/internal/store"
|
|
|
|
|
appsync "geniusrun/backend/internal/sync"
|
2026-07-25 17:55:13 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestSetup_ProvisionsNewUserWithDisplayName(t *testing.T) {
|
2026-07-25 18:10:09 +02:00
|
|
|
// This test specifically needs an *unprovisioned* session, unlike every
|
|
|
|
|
// other test in this package -- build the server without the
|
|
|
|
|
// newTestServer helper's automatic ProvisionUser call.
|
|
|
|
|
db, err := store.Open(filepath.Join(t.TempDir(), "geniusrun_test.db"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("store.Open: %v", err)
|
|
|
|
|
}
|
|
|
|
|
t.Cleanup(func() { db.Close() })
|
|
|
|
|
m := &mock.Client{}
|
|
|
|
|
s := NewServer(db, func(garmin.Config) garmin.Client { return m }, garmin.Config{}, appsync.Config{}, &authmock.Verifier{}, testSessionConfig)
|
2026-07-25 17:55:13 +02:00
|
|
|
router := s.Router()
|
|
|
|
|
|
|
|
|
|
rec := doJSON(t, router, http.MethodGet, "/api/session/me", nil)
|
|
|
|
|
var me sessionMeResponse
|
|
|
|
|
unmarshalBody(t, rec, &me)
|
|
|
|
|
if me.HasProfile {
|
|
|
|
|
t.Fatal("expected a brand-new session to have no profile yet")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rec = doJSON(t, router, http.MethodPost, "/api/setup", map[string]any{"display_name": "Lucie"})
|
|
|
|
|
if rec.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("setup status = %d, body = %s", rec.Code, rec.Body.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rec = doJSON(t, router, http.MethodGet, "/api/session/me", nil)
|
|
|
|
|
unmarshalBody(t, rec, &me)
|
|
|
|
|
if !me.HasProfile || me.DisplayName != "Lucie" {
|
|
|
|
|
t.Fatalf("session/me after setup = %+v, want HasProfile=true DisplayName=Lucie", me)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u, found, err := db.GetUserBySub(newCtx(), "test-user") // doJSON's test cookie's Sub
|
|
|
|
|
if err != nil || !found {
|
|
|
|
|
t.Fatalf("GetUserBySub: found=%v err=%v", found, err)
|
|
|
|
|
}
|
|
|
|
|
if u.DisplayName != "Lucie" {
|
|
|
|
|
t.Errorf("stored DisplayName = %q, want Lucie", u.DisplayName)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSetup_RejectsEmptyDisplayName(t *testing.T) {
|
2026-07-25 18:10:09 +02:00
|
|
|
db, err := store.Open(filepath.Join(t.TempDir(), "geniusrun_test.db"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("store.Open: %v", err)
|
|
|
|
|
}
|
|
|
|
|
t.Cleanup(func() { db.Close() })
|
|
|
|
|
m := &mock.Client{}
|
|
|
|
|
s := NewServer(db, func(garmin.Config) garmin.Client { return m }, garmin.Config{}, appsync.Config{}, &authmock.Verifier{}, testSessionConfig)
|
|
|
|
|
|
2026-07-25 17:55:13 +02:00
|
|
|
rec := doJSON(t, s.Router(), http.MethodPost, "/api/setup", map[string]any{"display_name": ""})
|
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
|
|
|
t.Fatalf("status = %d, want 400", rec.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSetup_RejectsWhenAlreadyProvisioned(t *testing.T) {
|
2026-07-25 18:28:02 +02:00
|
|
|
s, _, _ := newTestServer(t)
|
2026-07-25 17:55:13 +02:00
|
|
|
rec := doJSON(t, s.Router(), http.MethodPost, "/api/setup", map[string]any{"display_name": "Someone Else"})
|
|
|
|
|
if rec.Code != http.StatusConflict {
|
|
|
|
|
t.Fatalf("status = %d, want 409, body = %s", rec.Code, rec.Body.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func unmarshalBody(t *testing.T, rec *httptest.ResponseRecorder, v any) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
if err := json.Unmarshal(rec.Body.Bytes(), v); err != nil {
|
|
|
|
|
t.Fatalf("unmarshal response body %q: %v", rec.Body.String(), err)
|
|
|
|
|
}
|
|
|
|
|
}
|