1package sessionsecurity
2
3import (
4 "errors"
5 "math"
6 "sync"
7 "testing"
8
9 "github.com/bnema/neferwl/internal/ports"
10)
11
12func TestGateTransitionOwnership(t *testing.T) {
13 var g Gate
14 if got := g.Snapshot(); got != (ports.SecurityState{}) {
15 t.Fatalf("zero state %v", got)
16 }
17 first, err := g.Engage()
18 if err != nil || !first.Protected || first.Generation != 1 {
19 t.Fatalf("engage %v %v", first, err)
20 }
21 second, err := g.Engage()
22 if err != nil || !second.Protected || second.Generation != 2 {
23 t.Fatalf("takeover %v %v", second, err)
24 }
25 if _, err := g.Release(first.Generation); !errors.Is(err, ErrStaleRelease) {
26 t.Fatalf("old owner released: %v", err)
27 }
28 if got := g.Snapshot(); got != second {
29 t.Fatalf("stale release changed gate: %v", got)
30 }
31 released, err := g.Release(second.Generation)
32 if err != nil || released.Protected || released.Generation != 3 {
33 t.Fatalf("release %v %v", released, err)
34 }
35 if _, err := g.Release(second.Generation); !errors.Is(err, ErrStaleRelease) {
36 t.Fatalf("duplicate release: %v", err)
37 }
38}
39
40func TestGateExhaustionStaysProtected(t *testing.T) {
41 var g Gate
42 g.state.Store(math.MaxUint64)
43 before := g.Snapshot()
44 if _, err := g.Engage(); !errors.Is(err, ErrGenerationExhausted) {
45 t.Fatalf("engage: %v", err)
46 }
47 if _, err := g.Release(before.Generation); !errors.Is(err, ErrGenerationExhausted) {
48 t.Fatalf("release: %v", err)
49 }
50 if g.Snapshot() != before {
51 t.Fatal("exhaustion changed protection")
52 }
53}
54
55func TestGatePenultimateReleaseStaysProtected(t *testing.T) {
56 var g Gate
57 g.state.Store(math.MaxUint64 - 2)
58 before := g.Snapshot()
59 if _, err := g.Release(before.Generation); !errors.Is(err, ErrGenerationExhausted) {
60 t.Fatalf("penultimate release: %v", err)
61 }
62 if g.Snapshot() != before || !g.Snapshot().Protected {
63 t.Fatal("release exhausted generation while unprotected")
64 }
65 next, err := g.Engage()
66 if err != nil || !next.Protected || next.Generation != before.Generation+1 {
67 t.Fatalf("final engagement: %v %v", next, err)
68 }
69 if _, err := g.Release(next.Generation); !errors.Is(err, ErrGenerationExhausted) {
70 t.Fatalf("final release: %v", err)
71 }
72}
73
74func TestGateSnapshotCoherence(t *testing.T) {
75 var g Gate
76 var readers sync.WaitGroup
77 stop := make(chan struct{})
78 for range 4 {
79 readers.Go(func() {
80 for {
81 select {
82 case <-stop:
83 return
84 default:
85 }
86 s := g.Snapshot()
87 // This test alternates engage/release without takeovers:
88 // odd generations protected, even generations unlocked.
89 if s.Protected != (s.Generation&1 != 0) {
90 t.Errorf("torn state %v", s)
91 return
92 }
93 }
94 })
95 }
96 for range 1000 {
97 s, err := g.Engage()
98 if err != nil {
99 t.Fatal(err)
100 }
101 if _, err := g.Release(s.Generation); err != nil {
102 t.Fatal(err)
103 }
104 }
105 close(stop)
106 readers.Wait()
107}