1+ package cache
2+
3+ import (
4+ "testing"
5+ "time"
6+
7+ "authguard/internal/auth"
8+ "github.com/alicebob/miniredis/v2"
9+ "github.com/stretchr/testify/assert"
10+ "github.com/stretchr/testify/mock"
11+ )
12+
13+ // MockLogger implements the Logger interface for testing
14+ type MockLogger struct {
15+ mock.Mock
16+ }
17+
18+ func (m * MockLogger ) Info (msg string , keysAndValues ... any ) {
19+ args := []any {msg }
20+ args = append (args , keysAndValues ... )
21+ m .Called (args ... )
22+ }
23+
24+ func (m * MockLogger ) Debug (msg string , keysAndValues ... any ) {
25+ args := []any {msg }
26+ args = append (args , keysAndValues ... )
27+ m .Called (args ... )
28+ }
29+
30+ func (m * MockLogger ) Error (msg string , keysAndValues ... any ) {
31+ args := []any {msg }
32+ args = append (args , keysAndValues ... )
33+ m .Called (args ... )
34+ }
35+
36+ func (m * MockLogger ) Warn (msg string , keysAndValues ... any ) {
37+ args := []any {msg }
38+ args = append (args , keysAndValues ... )
39+ m .Called (args ... )
40+ }
41+
42+ func (m * MockLogger ) With (keysAndValues ... any ) auth.Logger {
43+ args := m .Called (keysAndValues )
44+ return args .Get (0 ).(auth.Logger )
45+ }
46+
47+ func TestNewCache (t * testing.T ) {
48+ t .Run ("Memory cache type" , func (t * testing.T ) {
49+ mockLogger := & MockLogger {}
50+ mockLogger .On ("Info" , "initializing memory cache" , "max_keys" , 1000 , "cleanup_interval" , 10 * time .Minute )
51+ mockLogger .On ("Info" , "memory cache initialized successfully" )
52+
53+ config := auth.CacheConfig {
54+ Type : auth .CacheTypeMemory ,
55+ MaxKeys : 1000 ,
56+ CleanupInterval : 10 * time .Minute ,
57+ }
58+
59+ cache , err := NewCache (config , mockLogger )
60+
61+ assert .NoError (t , err )
62+ assert .NotNil (t , cache )
63+
64+ // Verify it's a memory cache
65+ memCache , ok := cache .(* MemoryCache )
66+ assert .True (t , ok )
67+ assert .Equal (t , 1000 , memCache .maxKeys )
68+
69+ _ = cache .Close ()
70+ mockLogger .AssertExpectations (t )
71+ })
72+
73+ t .Run ("Redis cache type with empty URL" , func (t * testing.T ) {
74+ mockLogger := & MockLogger {}
75+ mockLogger .On ("Info" , "Redis URL not configured, falling back to memory cache" )
76+ mockLogger .On ("Info" , "initializing memory cache" , "max_keys" , 500 , "cleanup_interval" , 5 * time .Minute )
77+ mockLogger .On ("Info" , "memory cache initialized successfully" )
78+
79+ config := auth.CacheConfig {
80+ Type : auth .CacheTypeRedis ,
81+ RedisURL : "" , // Empty URL should fallback to memory
82+ MaxKeys : 500 ,
83+ CleanupInterval : 5 * time .Minute ,
84+ }
85+
86+ cache , err := NewCache (config , mockLogger )
87+
88+ assert .NoError (t , err )
89+ assert .NotNil (t , cache )
90+
91+ // Should fallback to memory cache
92+ _ , ok := cache .(* MemoryCache )
93+ assert .True (t , ok )
94+
95+ _ = cache .Close ()
96+ mockLogger .AssertExpectations (t )
97+ })
98+
99+ t .Run ("Redis cache type with invalid URL" , func (t * testing.T ) {
100+ mockLogger := & MockLogger {}
101+ mockLogger .On ("Info" , "attempting to connect to Redis" , "url" , "invalid-redis-url" , "db" , 0 )
102+ mockLogger .On ("Warn" , "failed to connect to Redis, falling back to memory cache" , "error" , mock .AnythingOfType ("*fmt.wrapError" ))
103+ mockLogger .On ("Info" , "initializing memory cache" , "max_keys" , 1000 , "cleanup_interval" , 10 * time .Minute )
104+ mockLogger .On ("Info" , "memory cache initialized successfully" )
105+
106+ config := auth.CacheConfig {
107+ Type : auth .CacheTypeRedis ,
108+ RedisURL : "invalid-redis-url" ,
109+ RedisPassword : "password" ,
110+ RedisDB : 0 ,
111+ MaxKeys : 1000 ,
112+ CleanupInterval : 10 * time .Minute ,
113+ }
114+
115+ cache , err := NewCache (config , mockLogger )
116+
117+ assert .NoError (t , err )
118+ assert .NotNil (t , cache )
119+
120+ // Should fallback to memory cache due to connection failure
121+ _ , ok := cache .(* MemoryCache )
122+ assert .True (t , ok )
123+
124+ _ = cache .Close ()
125+ mockLogger .AssertExpectations (t )
126+ })
127+
128+ t .Run ("Unknown cache type" , func (t * testing.T ) {
129+ mockLogger := & MockLogger {}
130+ mockLogger .On ("Warn" , "unknown cache type, defaulting to memory" , "type" , auth .CacheType (999 ))
131+ mockLogger .On ("Info" , "initializing memory cache" , "max_keys" , 1000 , "cleanup_interval" , 10 * time .Minute )
132+ mockLogger .On ("Info" , "memory cache initialized successfully" )
133+
134+ config := auth.CacheConfig {
135+ Type : auth .CacheType (999 ), // Invalid cache type
136+ MaxKeys : 1000 ,
137+ CleanupInterval : 10 * time .Minute ,
138+ }
139+
140+ cache , err := NewCache (config , mockLogger )
141+
142+ assert .NoError (t , err )
143+ assert .NotNil (t , cache )
144+
145+ // Should default to memory cache
146+ _ , ok := cache .(* MemoryCache )
147+ assert .True (t , ok )
148+
149+ _ = cache .Close ()
150+ mockLogger .AssertExpectations (t )
151+ })
152+ }
153+
154+ func TestCreateRedisCache (t * testing.T ) {
155+ t .Run ("Empty Redis URL" , func (t * testing.T ) {
156+ mockLogger := & MockLogger {}
157+ mockLogger .On ("Info" , "Redis URL not configured, falling back to memory cache" )
158+ mockLogger .On ("Info" , "initializing memory cache" , "max_keys" , 1000 , "cleanup_interval" , 10 * time .Minute )
159+ mockLogger .On ("Info" , "memory cache initialized successfully" )
160+
161+ config := auth.CacheConfig {
162+ RedisURL : "" ,
163+ MaxKeys : 1000 ,
164+ CleanupInterval : 10 * time .Minute ,
165+ }
166+
167+ cache , err := createRedisCache (config , mockLogger )
168+
169+ assert .NoError (t , err )
170+ assert .NotNil (t , cache )
171+
172+ // Should be memory cache
173+ _ , ok := cache .(* MemoryCache )
174+ assert .True (t , ok )
175+
176+ _ = cache .Close ()
177+ mockLogger .AssertExpectations (t )
178+ })
179+
180+ t .Run ("Invalid Redis URL" , func (t * testing.T ) {
181+ mockLogger := & MockLogger {}
182+ mockLogger .On ("Info" , "attempting to connect to Redis" , "url" , "invalid-url" , "db" , 0 )
183+ mockLogger .On ("Warn" , "failed to connect to Redis, falling back to memory cache" , "error" , mock .AnythingOfType ("*fmt.wrapError" ))
184+ mockLogger .On ("Info" , "initializing memory cache" , "max_keys" , 1000 , "cleanup_interval" , 10 * time .Minute )
185+ mockLogger .On ("Info" , "memory cache initialized successfully" )
186+
187+ config := auth.CacheConfig {
188+ RedisURL : "invalid-url" ,
189+ RedisPassword : "test" ,
190+ RedisDB : 0 ,
191+ MaxKeys : 1000 ,
192+ CleanupInterval : 10 * time .Minute ,
193+ }
194+
195+ cache , err := createRedisCache (config , mockLogger )
196+
197+ assert .NoError (t , err )
198+ assert .NotNil (t , cache )
199+
200+ // Should fallback to memory cache
201+ _ , ok := cache .(* MemoryCache )
202+ assert .True (t , ok )
203+
204+ _ = cache .Close ()
205+ mockLogger .AssertExpectations (t )
206+ })
207+
208+ t .Run ("Successful Redis connection" , func (t * testing.T ) {
209+ // Use miniredis for real Redis connection test
210+ s := miniredis .RunT (t )
211+ defer s .Close ()
212+
213+ mockLogger := & MockLogger {}
214+ mockLogger .On ("Info" , "attempting to connect to Redis" , "url" , "redis://" + s .Addr (), "db" , 0 )
215+ mockLogger .On ("Info" , "Redis cache initialized successfully" )
216+
217+ config := auth.CacheConfig {
218+ RedisURL : "redis://" + s .Addr (),
219+ RedisPassword : "" ,
220+ RedisDB : 0 ,
221+ MaxKeys : 1000 ,
222+ CleanupInterval : 10 * time .Minute ,
223+ }
224+
225+ cache , err := createRedisCache (config , mockLogger )
226+
227+ assert .NoError (t , err )
228+ assert .NotNil (t , cache )
229+
230+ // Should be Redis cache, not memory fallback
231+ _ , ok := cache .(* RedisCache )
232+ assert .True (t , ok )
233+
234+ _ = cache .Close ()
235+ mockLogger .AssertExpectations (t )
236+ })
237+ }
238+
239+ func TestCreateMemoryCache (t * testing.T ) {
240+ t .Run ("Valid configuration" , func (t * testing.T ) {
241+ mockLogger := & MockLogger {}
242+ mockLogger .On ("Info" , "initializing memory cache" , "max_keys" , 500 , "cleanup_interval" , 5 * time .Minute )
243+ mockLogger .On ("Info" , "memory cache initialized successfully" )
244+
245+ config := auth.CacheConfig {
246+ MaxKeys : 500 ,
247+ CleanupInterval : 5 * time .Minute ,
248+ }
249+
250+ cache , err := createMemoryCache (config , mockLogger )
251+
252+ assert .NoError (t , err )
253+ assert .NotNil (t , cache )
254+
255+ memCache , ok := cache .(* MemoryCache )
256+ assert .True (t , ok )
257+ assert .Equal (t , 500 , memCache .maxKeys )
258+
259+ _ = cache .Close ()
260+ mockLogger .AssertExpectations (t )
261+ })
262+
263+ t .Run ("Invalid memory cache config" , func (t * testing.T ) {
264+ mockLogger := & MockLogger {}
265+ mockLogger .On ("Info" , "initializing memory cache" , "max_keys" , - 100 , "cleanup_interval" , time .Duration (- 1 ))
266+ mockLogger .On ("Info" , "memory cache initialized successfully" )
267+
268+ config := auth.CacheConfig {
269+ MaxKeys : - 100 , // Invalid
270+ CleanupInterval : time .Duration (- 1 ), // Invalid
271+ }
272+
273+ cache , err := createMemoryCache (config , mockLogger )
274+
275+ // Should still work due to defaults in NewMemoryCache
276+ assert .NoError (t , err )
277+ assert .NotNil (t , cache )
278+
279+ memCache , ok := cache .(* MemoryCache )
280+ assert .True (t , ok )
281+ // Should use defaults
282+ assert .Equal (t , 1000 , memCache .maxKeys )
283+ assert .Equal (t , 10 * time .Minute , memCache .janitor .interval )
284+
285+ _ = cache .Close ()
286+ mockLogger .AssertExpectations (t )
287+ })
288+ }
0 commit comments