|
1 | 1 | package session
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "sync" |
4 | 5 | "testing"
|
5 | 6 | "time"
|
6 | 7 |
|
7 | 8 | "github.com/stretchr/testify/assert"
|
8 | 9 | "github.com/stretchr/testify/require"
|
9 | 10 | )
|
10 | 11 |
|
11 |
| -func TestAddAndGetWithStubSession(t *testing.T) { |
12 |
| - t.Parallel() |
| 12 | +// stubFactory returns ProxySessions with fixed timestamps and records IDs. |
| 13 | +type stubFactory struct { |
| 14 | + mu sync.Mutex |
| 15 | + createdIDs []string |
| 16 | + fixedTime time.Time |
| 17 | +} |
13 | 18 |
|
14 |
| - orig := NewProxySession |
15 |
| - NewProxySession = func(id string) *ProxySession { |
16 |
| - ts := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) |
17 |
| - return &ProxySession{id: id, created: ts, updated: ts} |
| 19 | +func (f *stubFactory) New(id string) *ProxySession { |
| 20 | + f.mu.Lock() |
| 21 | + defer f.mu.Unlock() |
| 22 | + f.createdIDs = append(f.createdIDs, id) |
| 23 | + return &ProxySession{ |
| 24 | + id: id, |
| 25 | + created: f.fixedTime, |
| 26 | + updated: f.fixedTime, |
18 | 27 | }
|
19 |
| - defer func() { NewProxySession = orig }() |
| 28 | +} |
20 | 29 |
|
21 |
| - m := NewManager(1 * time.Hour) |
| 30 | +func TestAddAndGetWithStubSession(t *testing.T) { |
| 31 | + t.Parallel() |
| 32 | + now := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) |
| 33 | + factory := &stubFactory{fixedTime: now} |
| 34 | + |
| 35 | + m := NewManager(time.Hour, factory.New) |
22 | 36 | defer m.Stop()
|
23 | 37 |
|
24 | 38 | require.NoError(t, m.AddWithID("foo"))
|
25 | 39 |
|
26 | 40 | sess, ok := m.Get("foo")
|
27 |
| - require.True(t, ok) |
| 41 | + require.True(t, ok, "session foo should exist") |
28 | 42 | assert.Equal(t, "foo", sess.ID())
|
| 43 | + assert.Contains(t, factory.createdIDs, "foo") |
29 | 44 | }
|
30 | 45 |
|
31 | 46 | func TestAddDuplicate(t *testing.T) {
|
32 | 47 | t.Parallel()
|
| 48 | + factory := &stubFactory{fixedTime: time.Now()} |
33 | 49 |
|
34 |
| - m := NewManager(time.Hour) |
| 50 | + m := NewManager(time.Hour, factory.New) |
35 | 51 | defer m.Stop()
|
36 | 52 |
|
37 |
| - err := m.AddWithID("dup") |
38 |
| - assert.NoError(t, err) |
| 53 | + require.NoError(t, m.AddWithID("dup")) |
39 | 54 |
|
40 |
| - err2 := m.AddWithID("dup") |
41 |
| - assert.Error(t, err2) |
42 |
| - assert.Contains(t, err2.Error(), "already exists") |
| 55 | + err := m.AddWithID("dup") |
| 56 | + assert.Error(t, err) |
| 57 | + assert.Contains(t, err.Error(), "already exists") |
43 | 58 | }
|
44 | 59 |
|
45 | 60 | func TestDeleteSession(t *testing.T) {
|
46 | 61 | t.Parallel()
|
| 62 | + factory := &stubFactory{fixedTime: time.Now()} |
47 | 63 |
|
48 |
| - m := NewManager(time.Hour) |
| 64 | + m := NewManager(time.Hour, factory.New) |
49 | 65 | defer m.Stop()
|
50 | 66 |
|
51 | 67 | require.NoError(t, m.AddWithID("del"))
|
52 | 68 | m.Delete("del")
|
53 | 69 |
|
54 | 70 | _, ok := m.Get("del")
|
55 |
| - assert.False(t, ok) |
| 71 | + assert.False(t, ok, "deleted session should not be found") |
56 | 72 | }
|
57 | 73 |
|
58 | 74 | func TestGetUpdatesTimestamp(t *testing.T) {
|
59 | 75 | t.Parallel()
|
| 76 | + oldTime := time.Now().Add(-1 * time.Minute) |
| 77 | + factory := &stubFactory{fixedTime: oldTime} |
60 | 78 |
|
61 |
| - orig := NewProxySession |
62 |
| - NewProxySession = func(id string) *ProxySession { |
63 |
| - ts := time.Now().Add(-1 * time.Minute) |
64 |
| - return &ProxySession{id: id, created: ts, updated: ts} |
65 |
| - } |
66 |
| - defer func() { NewProxySession = orig }() |
67 |
| - |
68 |
| - m := NewManager(1 * time.Hour) |
| 79 | + m := NewManager(time.Hour, factory.New) |
69 | 80 | defer m.Stop()
|
70 | 81 |
|
71 | 82 | require.NoError(t, m.AddWithID("touchme"))
|
72 |
| - s1, _ := m.Get("touchme") |
| 83 | + s1, ok := m.Get("touchme") |
| 84 | + require.True(t, ok) |
73 | 85 | t0 := s1.UpdatedAt()
|
74 | 86 |
|
75 |
| - time.Sleep(5 * time.Millisecond) |
76 |
| - s2, _ := m.Get("touchme") |
| 87 | + time.Sleep(10 * time.Millisecond) |
| 88 | + s2, ok2 := m.Get("touchme") |
| 89 | + require.True(t, ok2) |
77 | 90 | t1 := s2.UpdatedAt()
|
78 | 91 |
|
79 |
| - assert.True(t, t1.After(t0), "UpdatedAt should update on Get()") |
| 92 | + assert.True(t, t1.After(t0), "UpdatedAt should update on repeated Get()") |
80 | 93 | }
|
81 |
| - |
82 |
| -func TestCleanupExpired(t *testing.T) { |
| 94 | +func TestCleanupExpired_ManualTrigger(t *testing.T) { |
83 | 95 | t.Parallel()
|
84 | 96 |
|
| 97 | + // Stub factory: all sessions start with UpdatedAt = `now` |
| 98 | + now := time.Now() |
| 99 | + factory := &stubFactory{fixedTime: now} |
85 | 100 | ttl := 50 * time.Millisecond
|
86 |
| - orig := NewProxySession |
87 |
| - NewProxySession = func(id string) *ProxySession { |
88 |
| - return &ProxySession{ |
89 |
| - id: id, |
90 |
| - created: time.Now(), |
91 |
| - updated: time.Now(), |
92 |
| - } |
93 |
| - } |
94 |
| - defer func() { NewProxySession = orig }() |
95 | 101 |
|
96 |
| - m := NewManager(ttl) |
| 102 | + m := NewManager(ttl, factory.New) |
97 | 103 | defer m.Stop()
|
98 | 104 |
|
99 | 105 | require.NoError(t, m.AddWithID("old"))
|
100 |
| - time.Sleep(ttl * 2) // allow old to expire |
101 | 106 |
|
102 |
| - require.NoError(t, m.AddWithID("new")) |
103 |
| - time.Sleep(ttl) // let cleanup execute |
| 107 | + // Retrieve and expire session manually |
| 108 | + sess, ok := m.Get("old") |
| 109 | + require.True(t, ok) |
| 110 | + ps := sess.(*ProxySession) |
| 111 | + ps.updated = now.Add(-ttl * 2) |
104 | 112 |
|
| 113 | + // Run cleanup manually |
| 114 | + m.cleanupExpiredOnce() |
| 115 | + |
| 116 | + // Now it should be gone |
105 | 117 | _, okOld := m.Get("old")
|
| 118 | + assert.False(t, okOld, "expired session should have been cleaned") |
| 119 | + |
| 120 | + // Add fresh session and assert it remains after cleanup |
| 121 | + require.NoError(t, m.AddWithID("new")) |
| 122 | + m.cleanupExpiredOnce() |
106 | 123 | _, okNew := m.Get("new")
|
107 |
| - assert.False(t, okOld, "expired session should be cleaned") |
108 |
| - assert.True(t, okNew, "recent session should remain") |
| 124 | + assert.True(t, okNew, "new session should still exist after cleanup") |
109 | 125 | }
|
110 | 126 |
|
111 | 127 | func TestStopDisablesCleanup(t *testing.T) {
|
112 | 128 | t.Parallel()
|
113 |
| - |
114 | 129 | ttl := 50 * time.Millisecond
|
115 |
| - m := NewManager(ttl) |
116 |
| - m.Stop() // stop cleanup upfront |
| 130 | + factory := &stubFactory{fixedTime: time.Now()} |
| 131 | + |
| 132 | + m := NewManager(ttl, factory.New) |
| 133 | + m.Stop() // disable cleanup before any session expires |
117 | 134 |
|
118 | 135 | require.NoError(t, m.AddWithID("stay"))
|
119 | 136 | time.Sleep(ttl * 2)
|
120 | 137 |
|
121 | 138 | _, ok := m.Get("stay")
|
122 |
| - assert.True(t, ok, "session should persist after Stop()") |
| 139 | + assert.True(t, ok, "session should still be present even after Stop() and TTL elapsed") |
123 | 140 | }
|
0 commit comments