package members_test import ( "context" "database/sql" "io/fs" "path/filepath" "sort" "testing" "time" assets "galo" "galo/internal/albums" "galo/internal/auth" "galo/internal/db" "galo/internal/members" "galo/internal/sse" "galo/internal/store" ) type stack struct { db *sql.DB repos store.Repos hub *sse.Hub albums *albums.Service svc *members.Service } // newStack opens a migrated SQLite database and wires the albums and members // services over the real repositories, mirroring the production wiring. func newStack(t *testing.T) *stack { t.Helper() path := filepath.Join(t.TempDir(), "meta.db") migFS, err := fs.Sub(assets.Migrations, "migrations/sqlite") if err != nil { t.Fatalf("migrations fs: %v", err) } if err := db.MigrateSQLite(path, migFS); err != nil { t.Fatalf("migrate: %v", err) } database, err := db.OpenSQLite(path) if err != nil { t.Fatalf("open: %v", err) } t.Cleanup(func() { database.Close() }) repos := store.NewSQLite(database) hub := sse.NewHub(16) albumsSvc := albums.NewService(repos.Albums, repos.Members, hub, nil) membersSvc := members.NewService(repos.Members, albumsSvc, hub, nil) return &stack{db: database, repos: repos, hub: hub, albums: albumsSvc, svc: membersSvc} } func seedUser(t *testing.T, st *stack, name string) string { t.Helper() id := auth.NewID() if _, err := st.db.Exec( `INSERT INTO users (id, email, password_hash, name) VALUES ($1, $2, 'x', $3)`, id, id[:8]+"@test.local", name); err != nil { t.Fatalf("seed user: %v", err) } return id } func nextEvent(t *testing.T, sub *sse.Subscription) sse.Event { t.Helper() select { case ev, ok := <-sub.Events(): if !ok { t.Fatal("subscription closed before an event arrived") } return ev case <-time.After(2 * time.Second): t.Fatal("timed out waiting for an event") } return sse.Event{} } func assertNoEvent(t *testing.T, sub *sse.Subscription) { t.Helper() select { case ev, ok := <-sub.Events(): if !ok { return } t.Fatalf("unexpected event %q", ev.Type) case <-time.After(50 * time.Millisecond): } } func assertAudience(t *testing.T, st *stack, albumID string, want ...string) { t.Helper() got, err := st.repos.Members.Audience(context.Background(), albumID) if err != nil { t.Fatalf("audience: %v", err) } sort.Strings(got) sorted := append([]string(nil), want...) sort.Strings(sorted) if len(got) != len(sorted) { t.Fatalf("audience = %v, want %v", got, sorted) } for i := range got { if got[i] != sorted[i] { t.Fatalf("audience = %v, want %v", got, sorted) } } }