Skip to content

Commit 79e038b

Browse files
Merge pull request #38 from Patrick-Ehimen/feat/auth-tests
Add comprehensive auth module tests
2 parents 5b6036b + ca3bb3e commit 79e038b

File tree

7 files changed

+941
-0
lines changed

7 files changed

+941
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/**
2+
* Tests for AuthManager
3+
*/
4+
5+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
6+
import { AuthManager } from "../AuthManager.js";
7+
import { AuthConfig } from "../types.js";
8+
9+
describe("AuthManager", () => {
10+
let authManager: AuthManager;
11+
let config: AuthConfig;
12+
13+
beforeEach(() => {
14+
config = {
15+
defaultApiKey: "default-api-key-12345",
16+
enablePerRequestAuth: true,
17+
requireAuthentication: true,
18+
keyValidationCache: {
19+
enabled: true,
20+
maxSize: 100,
21+
ttlSeconds: 300,
22+
cleanupIntervalSeconds: 0,
23+
},
24+
rateLimiting: {
25+
enabled: true,
26+
requestsPerMinute: 60,
27+
burstLimit: 10,
28+
keyBasedLimiting: true,
29+
},
30+
};
31+
authManager = new AuthManager(config);
32+
});
33+
34+
afterEach(() => {
35+
authManager.destroy();
36+
});
37+
38+
describe("validateApiKey", () => {
39+
it("should validate API key with correct format", async () => {
40+
const apiKey = "valid-api-key-12345";
41+
const result = await authManager.validateApiKey(apiKey);
42+
43+
expect(result.isValid).toBe(true);
44+
expect(result.keyHash).toBeDefined();
45+
expect(result.errorMessage).toBeUndefined();
46+
});
47+
48+
it("should reject API key with invalid format", async () => {
49+
const apiKey = "short";
50+
const result = await authManager.validateApiKey(apiKey);
51+
52+
expect(result.isValid).toBe(false);
53+
expect(result.errorMessage).toBe("Invalid API key format");
54+
});
55+
56+
it("should cache validation results", async () => {
57+
const apiKey = "valid-api-key-12345";
58+
59+
const result1 = await authManager.validateApiKey(apiKey);
60+
const result2 = await authManager.validateApiKey(apiKey);
61+
62+
expect(result1).toEqual(result2);
63+
});
64+
65+
it("should enforce rate limiting", async () => {
66+
const limitedConfig: AuthConfig = {
67+
...config,
68+
keyValidationCache: {
69+
enabled: false, // Disable cache to test rate limiting
70+
maxSize: 100,
71+
ttlSeconds: 300,
72+
cleanupIntervalSeconds: 0,
73+
},
74+
rateLimiting: {
75+
enabled: true,
76+
requestsPerMinute: 2,
77+
burstLimit: 2,
78+
keyBasedLimiting: true,
79+
},
80+
};
81+
const limitedManager = new AuthManager(limitedConfig);
82+
83+
const apiKey = "test-api-key-12345";
84+
85+
// First two requests should succeed
86+
const result1 = await limitedManager.validateApiKey(apiKey);
87+
expect(result1.isValid).toBe(true);
88+
89+
const result2 = await limitedManager.validateApiKey(apiKey);
90+
expect(result2.isValid).toBe(true);
91+
92+
// Third request should be rate limited
93+
const result3 = await limitedManager.validateApiKey(apiKey);
94+
expect(result3.isValid).toBe(false);
95+
expect(result3.errorMessage).toBe("Rate limit exceeded");
96+
97+
limitedManager.destroy();
98+
});
99+
});
100+
101+
describe("getEffectiveApiKey", () => {
102+
it("should return request key when provided", async () => {
103+
const requestKey = "request-api-key-12345";
104+
const effectiveKey = await authManager.getEffectiveApiKey(requestKey);
105+
106+
expect(effectiveKey).toBe(requestKey);
107+
});
108+
109+
it("should return default key when no request key provided", async () => {
110+
const effectiveKey = await authManager.getEffectiveApiKey();
111+
112+
expect(effectiveKey).toBe("default-api-key-12345");
113+
});
114+
115+
it("should throw error when no key available and authentication required", async () => {
116+
const noDefaultConfig: AuthConfig = {
117+
...config,
118+
defaultApiKey: undefined,
119+
};
120+
const noDefaultManager = new AuthManager(noDefaultConfig);
121+
122+
await expect(noDefaultManager.getEffectiveApiKey()).rejects.toThrow("API key is required");
123+
124+
noDefaultManager.destroy();
125+
});
126+
});
127+
128+
describe("authenticate", () => {
129+
it("should authenticate with valid request key", async () => {
130+
const requestKey = "valid-request-key-12345";
131+
const result = await authManager.authenticate(requestKey);
132+
133+
expect(result.success).toBe(true);
134+
expect(result.usedFallback).toBe(false);
135+
expect(result.keyHash).toBeDefined();
136+
expect(result.authTime).toBeGreaterThanOrEqual(0);
137+
});
138+
139+
it("should authenticate with fallback key", async () => {
140+
const result = await authManager.authenticate();
141+
142+
expect(result.success).toBe(true);
143+
expect(result.usedFallback).toBe(true);
144+
expect(result.keyHash).toBeDefined();
145+
});
146+
147+
it("should fail authentication with invalid key", async () => {
148+
const invalidKey = "bad";
149+
const result = await authManager.authenticate(invalidKey);
150+
151+
expect(result.success).toBe(false);
152+
expect(result.errorMessage).toBeDefined();
153+
});
154+
155+
it("should track authentication time", async () => {
156+
const result = await authManager.authenticate("valid-key-12345");
157+
158+
expect(result.authTime).toBeGreaterThanOrEqual(0);
159+
expect(result.authTime).toBeLessThan(1000); // Should be fast
160+
});
161+
});
162+
163+
describe("sanitizeApiKey", () => {
164+
it("should sanitize API key for logging", () => {
165+
const apiKey = "test-api-key-12345678";
166+
const sanitized = authManager.sanitizeApiKey(apiKey);
167+
168+
expect(sanitized).toBe("test...5678");
169+
expect(sanitized).not.toContain("api-key");
170+
});
171+
});
172+
173+
describe("isRateLimited", () => {
174+
it("should check if key is rate limited", async () => {
175+
const apiKey = "test-api-key-12345";
176+
177+
const isLimited = authManager.isRateLimited(apiKey);
178+
expect(isLimited).toBe(false);
179+
});
180+
});
181+
182+
describe("invalidateKey", () => {
183+
it("should invalidate cached key", async () => {
184+
const apiKey = "valid-api-key-12345";
185+
186+
// Validate and cache
187+
await authManager.validateApiKey(apiKey);
188+
189+
// Invalidate
190+
authManager.invalidateKey(apiKey);
191+
192+
// Should validate again (not from cache)
193+
const result = await authManager.validateApiKey(apiKey);
194+
expect(result.isValid).toBe(true);
195+
});
196+
});
197+
198+
describe("getCacheStats", () => {
199+
it("should return cache statistics", async () => {
200+
await authManager.validateApiKey("key1-valid-12345");
201+
await authManager.validateApiKey("key2-valid-12345");
202+
203+
const stats = authManager.getCacheStats();
204+
205+
expect(stats.size).toBeGreaterThanOrEqual(0);
206+
expect(stats.maxSize).toBe(100);
207+
});
208+
});
209+
210+
describe("getRateLimitStatus", () => {
211+
it("should return rate limit status for key", () => {
212+
const apiKey = "test-api-key-12345";
213+
const status = authManager.getRateLimitStatus(apiKey);
214+
215+
expect(status.allowed).toBe(true);
216+
expect(status.remaining).toBeGreaterThan(0);
217+
expect(status.resetTime).toBeInstanceOf(Date);
218+
});
219+
});
220+
});
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Tests for KeyValidationCache
3+
*/
4+
5+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
6+
import { KeyValidationCache } from "../KeyValidationCache.js";
7+
import { CacheConfig, ValidationResult } from "../types.js";
8+
9+
describe("KeyValidationCache", () => {
10+
let cache: KeyValidationCache;
11+
let config: CacheConfig;
12+
13+
beforeEach(() => {
14+
config = {
15+
enabled: true,
16+
maxSize: 10,
17+
ttlSeconds: 60,
18+
cleanupIntervalSeconds: 0, // Disable auto-cleanup for tests
19+
};
20+
cache = new KeyValidationCache(config);
21+
});
22+
23+
afterEach(() => {
24+
cache.destroy();
25+
});
26+
27+
describe("get and set", () => {
28+
it("should store and retrieve validation results", () => {
29+
const keyHash = "test-hash-123";
30+
const result: ValidationResult = {
31+
isValid: true,
32+
keyHash,
33+
};
34+
35+
cache.set(keyHash, result);
36+
const retrieved = cache.get(keyHash);
37+
38+
expect(retrieved).toEqual(result);
39+
});
40+
41+
it("should return null for non-existent keys", () => {
42+
const retrieved = cache.get("non-existent");
43+
44+
expect(retrieved).toBeNull();
45+
});
46+
47+
it("should return null when cache is disabled", () => {
48+
const disabledCache = new KeyValidationCache({ ...config, enabled: false });
49+
const keyHash = "test-hash";
50+
const result: ValidationResult = { isValid: true, keyHash };
51+
52+
disabledCache.set(keyHash, result);
53+
const retrieved = disabledCache.get(keyHash);
54+
55+
expect(retrieved).toBeNull();
56+
disabledCache.destroy();
57+
});
58+
});
59+
60+
describe("TTL expiration", () => {
61+
it("should expire entries after TTL", async () => {
62+
const shortTTLConfig: CacheConfig = {
63+
...config,
64+
ttlSeconds: 0.1, // 100ms
65+
};
66+
const shortCache = new KeyValidationCache(shortTTLConfig);
67+
68+
const keyHash = "test-hash";
69+
const result: ValidationResult = { isValid: true, keyHash };
70+
71+
shortCache.set(keyHash, result);
72+
73+
// Should be available immediately
74+
expect(shortCache.get(keyHash)).toEqual(result);
75+
76+
// Wait for expiration
77+
await new Promise((resolve) => setTimeout(resolve, 150));
78+
79+
// Should be expired
80+
expect(shortCache.get(keyHash)).toBeNull();
81+
82+
shortCache.destroy();
83+
});
84+
});
85+
86+
describe("LRU eviction", () => {
87+
it("should evict least recently used entry when cache is full", () => {
88+
const smallCache = new KeyValidationCache({ ...config, maxSize: 3 });
89+
90+
// Fill cache
91+
smallCache.set("key1", { isValid: true, keyHash: "key1" });
92+
smallCache.set("key2", { isValid: true, keyHash: "key2" });
93+
smallCache.set("key3", { isValid: true, keyHash: "key3" });
94+
95+
// Access key2 to make it more recently used
96+
smallCache.get("key2");
97+
98+
// Add new entry, should evict key1 (least recently used)
99+
smallCache.set("key4", { isValid: true, keyHash: "key4" });
100+
101+
expect(smallCache.get("key1")).toBeNull();
102+
expect(smallCache.get("key2")).not.toBeNull();
103+
expect(smallCache.get("key3")).not.toBeNull();
104+
expect(smallCache.get("key4")).not.toBeNull();
105+
106+
smallCache.destroy();
107+
});
108+
});
109+
110+
describe("invalidate", () => {
111+
it("should remove specific key from cache", () => {
112+
const keyHash = "test-hash";
113+
const result: ValidationResult = { isValid: true, keyHash };
114+
115+
cache.set(keyHash, result);
116+
expect(cache.get(keyHash)).toEqual(result);
117+
118+
cache.invalidate(keyHash);
119+
expect(cache.get(keyHash)).toBeNull();
120+
});
121+
});
122+
123+
describe("clear", () => {
124+
it("should remove all entries from cache", () => {
125+
cache.set("key1", { isValid: true, keyHash: "key1" });
126+
cache.set("key2", { isValid: true, keyHash: "key2" });
127+
128+
cache.clear();
129+
130+
expect(cache.get("key1")).toBeNull();
131+
expect(cache.get("key2")).toBeNull();
132+
});
133+
});
134+
135+
describe("getStats", () => {
136+
it("should return cache statistics", () => {
137+
cache.set("key1", { isValid: true, keyHash: "key1" });
138+
cache.set("key2", { isValid: true, keyHash: "key2" });
139+
140+
const stats = cache.getStats();
141+
142+
expect(stats.size).toBe(2);
143+
expect(stats.maxSize).toBe(10);
144+
});
145+
});
146+
});

0 commit comments

Comments
 (0)