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)
}
}