This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user