package authapi import ( "sync" "testing" "time" "github.com/itworx/pulse/internal/auth" ) func testFlow(state string) flow { return flow{authorization: auth.Authorization{State: state, Nonce: "nonce", CodeVerifier: "verifier"}, redirect: "/"} } func TestFlowStoreSingleUseAndExpiry(t *testing.T) { now := time.Date(2026, 8, 4, 10, 0, 0, 0, time.UTC) for _, test := range []struct { name string takeAt time.Time twice bool wantOK bool wantAll int }{ {name: "within ttl", takeAt: now.Add(time.Minute), wantOK: true}, {name: "at ttl boundary", takeAt: now.Add(defaultFlowTTL), wantOK: false}, {name: "after ttl", takeAt: now.Add(defaultFlowTTL + time.Second), wantOK: false}, {name: "replayed", takeAt: now.Add(time.Minute), twice: true, wantOK: false}, } { t.Run(test.name, func(t *testing.T) { store := newFlowStore(0, 0) id, err := store.create(testFlow("state-1"), now) if err != nil { t.Fatalf("create: %v", err) } if test.twice { if _, ok := store.take(id, test.takeAt); !ok { t.Fatal("first take failed") } } entry, ok := store.take(id, test.takeAt) if ok != test.wantOK { t.Fatalf("take ok = %v, want %v", ok, test.wantOK) } if ok && entry.authorization.State != "state-1" { t.Fatalf("state = %q", entry.authorization.State) } if store.size() != 0 { t.Fatalf("take left %d entries behind", store.size()) } }) } } func TestFlowStoreRejectsUnknownIdentifiers(t *testing.T) { store := newFlowStore(0, 0) for _, id := range []string{"", "unknown", " "} { if _, ok := store.take(id, time.Now()); ok { t.Fatalf("identifier %q was accepted", id) } } } func TestFlowStoreIsBounded(t *testing.T) { now := time.Now().UTC() store := newFlowStore(time.Minute, 4) for index := range 50 { if _, err := store.create(testFlow("state"), now.Add(time.Duration(index)*time.Second)); err != nil { t.Fatalf("create: %v", err) } } if store.size() != 4 { t.Fatalf("size = %d, want 4", store.size()) } expired, err := store.create(testFlow("expired"), now) if err != nil { t.Fatalf("create: %v", err) } if _, err := store.create(testFlow("fresh"), now.Add(2*time.Minute)); err != nil { t.Fatalf("create: %v", err) } if _, ok := store.take(expired, now.Add(2*time.Minute)); ok { t.Fatal("expired flow survived the purge") } if store.size() > 4 { t.Fatalf("size = %d, want at most 4", store.size()) } } func TestFlowStoreConcurrentAccess(t *testing.T) { const workers = 128 store := newFlowStore(time.Minute, 64) now := time.Now().UTC() identifiers := make([]string, workers) var wait sync.WaitGroup for index := range workers { wait.Add(1) go func() { defer wait.Done() id, err := store.create(testFlow("state"), now) if err != nil { t.Errorf("create: %v", err) return } identifiers[index] = id }() } wait.Wait() unique := make(map[string]struct{}, workers) for _, id := range identifiers { if id == "" { t.Fatal("empty flow identifier") } unique[id] = struct{}{} } if len(unique) != workers { t.Fatalf("unique identifiers = %d, want %d", len(unique), workers) } if store.size() > 64 { t.Fatalf("size = %d, want at most 64", store.size()) } var taken sync.WaitGroup results := make(chan bool, 2*workers) for _, id := range identifiers { taken.Add(1) go func() { defer taken.Done() _, ok := store.take(id, now) results <- ok }() taken.Add(1) go func() { defer taken.Done() _, ok := store.take(id, now) results <- ok }() } taken.Wait() close(results) accepted := 0 for ok := range results { if ok { accepted++ } } if accepted > 64 { t.Fatalf("accepted %d flows, want at most the store capacity", accepted) } if store.size() != 0 { t.Fatalf("size = %d, want 0", store.size()) } }