Files
ITWorx-Pulse-Public/internal/authapi/flowstore_test.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

155 lines
3.7 KiB
Go

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