store: add User type and ProvisionUser

Lays the groundwork for per-user accounts: CreateUser/GetUserBySub/
ListUsers plus ProvisionUser, which seeds a brand-new user's profile,
default workout-kind taxonomy, and sync state in one transaction. Depends
on later tasks scoping GetProfile/ListWorkoutKinds/GetSyncState to compile
and pass -- expected or committing to a shared branch.
This commit is contained in:
2026-07-25 12:59:56 +02:00
parent 46ad8a7e08
commit 36bc651d66
2 changed files with 224 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
package store
import (
"context"
"testing"
)
func TestGetUserBySub_NotFoundReturnsFalseNotError(t *testing.T) {
db := openTestDB(t)
_, found, err := db.GetUserBySub(context.Background(), "no-such-sub")
if err != nil {
t.Fatalf("GetUserBySub: %v", err)
}
if found {
t.Fatal("expected found=false for an unknown sub")
}
}
func TestProvisionUser_SeedsProfileTaxonomyAndSyncState(t *testing.T) {
db := openTestDB(t)
ctx := context.Background()
userID, err := db.ProvisionUser(ctx, "sub-123", "Lucie")
if err != nil {
t.Fatalf("ProvisionUser: %v", err)
}
u, found, err := db.GetUserBySub(ctx, "sub-123")
if err != nil || !found {
t.Fatalf("GetUserBySub: found=%v err=%v", found, err)
}
if u.ID != userID || u.DisplayName != "Lucie" {
t.Fatalf("got %+v, want ID=%d DisplayName=Lucie", u, userID)
}
profile, err := db.GetProfile(ctx, userID)
if err != nil {
t.Fatalf("GetProfile: %v", err)
}
if profile.Name != "Lucie" {
t.Errorf("profile.Name = %q, want %q", profile.Name, "Lucie")
}
if profile.RollingWindowDays != 90 {
t.Errorf("profile.RollingWindowDays = %d, want 90 (default)", profile.RollingWindowDays)
}
kinds, err := db.ListWorkoutKinds(ctx, userID, false)
if err != nil {
t.Fatalf("ListWorkoutKinds: %v", err)
}
if len(kinds) != 8 {
t.Fatalf("expected 8 seeded workout kinds, got %d", len(kinds))
}
state, err := db.GetSyncState(ctx, userID)
if err != nil {
t.Fatalf("GetSyncState: %v", err)
}
if state.EarliestSyncedDate != nil || state.BackfillComplete {
t.Errorf("expected fresh sync state, got %+v", state)
}
}
func TestProvisionUser_TwoUsersGetIndependentTaxonomies(t *testing.T) {
db := openTestDB(t)
ctx := context.Background()
userA, err := db.ProvisionUser(ctx, "sub-a", "A")
if err != nil {
t.Fatalf("ProvisionUser(a): %v", err)
}
userB, err := db.ProvisionUser(ctx, "sub-b", "B")
if err != nil {
t.Fatalf("ProvisionUser(b): %v", err)
}
kindsA, err := db.ListWorkoutKinds(ctx, userA, false)
if err != nil {
t.Fatalf("ListWorkoutKinds(a): %v", err)
}
kindsB, err := db.ListWorkoutKinds(ctx, userB, false)
if err != nil {
t.Fatalf("ListWorkoutKinds(b): %v", err)
}
if len(kindsA) != 8 || len(kindsB) != 8 {
t.Fatalf("expected 8 kinds each, got a=%d b=%d", len(kindsA), len(kindsB))
}
if kindsA[0].ID == kindsB[0].ID {
t.Fatal("expected each user's seeded kinds to be distinct rows")
}
}