package oidcstub import ( "strings" "sync" "testing" "time" ) // TestCodeStore_Issue_DistinctURLSafeCodes verifies that Issue returns distinct, // URL-safe codes with sufficient entropy (32 bytes → 43-char base64url string). func TestCodeStore_Issue_DistinctURLSafeCodes(t *testing.T) { cs := newCodeStore(nil) seen := make(map[string]bool) for i := 0; i < 20; i++ { code := cs.Issue("sub", "challenge", "http://x/cb", 5*time.Minute) if code == "" { t.Fatal("Issue returned empty code") } // URL-safe: must not contain +, /, or = if strings.ContainsAny(code, "+/=") { t.Errorf("code %q is not URL-safe (contains +, /, or =)", code) } // 32 bytes base64url-encoded without padding = 43 chars if len(code) < 43 { t.Errorf("code %q too short (len=%d, want >=43)", code, len(code)) } if seen[code] { t.Errorf("duplicate code issued: %q", code) } seen[code] = true } } // TestCodeStore_Consume_SingleUse verifies IV2: code is deleted on first Consume. func TestCodeStore_Consume_SingleUse(t *testing.T) { cs := newCodeStore(nil) code := cs.Issue("alice", "challenge", "http://x/cb", 5*time.Minute) entry, ok := cs.Consume(code) if !ok { t.Fatal("Consume: expected ok=true on first call") } if entry.Sub != "alice" { t.Errorf("entry.Sub = %q; want alice", entry.Sub) } if entry.CodeChallenge != "challenge" { t.Errorf("entry.CodeChallenge = %q; want challenge", entry.CodeChallenge) } if entry.RedirectURI != "http://x/cb" { t.Errorf("entry.RedirectURI = %q; want http://x/cb", entry.RedirectURI) } // Second call must return false (IV2). _, ok2 := cs.Consume(code) if ok2 { t.Fatal("Consume: expected ok=false on second call (single-use)") } } // TestCodeStore_Consume_Expired verifies IV3: expired entries are rejected. func TestCodeStore_Consume_Expired(t *testing.T) { // Start at time zero, issue with 5m TTL. now := time.Unix(0, 0) cs := newCodeStore(func() time.Time { return now }) code := cs.Issue("bob", "challenge", "http://x/cb", 5*time.Minute) // Advance past TTL. now = now.Add(6 * time.Minute) _, ok := cs.Consume(code) if ok { t.Fatal("Consume: expected ok=false for expired code (IV3)") } } // TestCodeStore_Consume_UnknownCode verifies false on unknown code. func TestCodeStore_Consume_UnknownCode(t *testing.T) { cs := newCodeStore(nil) _, ok := cs.Consume("no-such-code") if ok { t.Fatal("Consume: expected ok=false for unknown code") } } // TestCodeStore_ConcurrentAccess verifies race-freedom (run with -race). func TestCodeStore_ConcurrentAccess(t *testing.T) { cs := newCodeStore(nil) var wg sync.WaitGroup // Writers codes := make(chan string, 100) for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 10; j++ { code := cs.Issue("sub", "challenge", "http://x/cb", 5*time.Minute) codes <- code } }() } // Readers for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 5; j++ { cs.Consume("probably-not-there") } }() } wg.Wait() close(codes) for code := range codes { cs.Consume(code) } }