|
| 1 | +import { describe, it, expect, beforeEach, vi } from 'vitest'; |
| 2 | +import { RefreshPolicy } from './refresh-policy'; |
| 3 | + |
| 4 | +// Mock the quota monitor to avoid interference |
| 5 | +vi.mock('./quota-monitor', () => ({ |
| 6 | + quotaMonitor: { |
| 7 | + isQuotaLow: vi.fn(() => false), |
| 8 | + incrementRefreshCount: vi.fn(), |
| 9 | + }, |
| 10 | +})); |
| 11 | + |
| 12 | +describe('RefreshPolicy - Edge Cases & Empty/Missing Inputs', () => { |
| 13 | + let policy: RefreshPolicy; |
| 14 | + |
| 15 | + beforeEach(() => { |
| 16 | + policy = RefreshPolicy.getInstance(); |
| 17 | + policy.reset(); |
| 18 | + vi.clearAllMocks(); |
| 19 | + }); |
| 20 | + |
| 21 | + it('Empty/Whitespace Usernames: handles empty and whitespace strings gracefully', () => { |
| 22 | + const emptyUser = ''; |
| 23 | + const spaceUser = ' '; |
| 24 | + |
| 25 | + // Should be allowed initially |
| 26 | + expect(policy.isRefreshAllowed(emptyUser)).toBe(true); |
| 27 | + expect(policy.isRefreshAllowed(spaceUser)).toBe(true); |
| 28 | + |
| 29 | + // Record refresh |
| 30 | + policy.recordRefresh(emptyUser); |
| 31 | + |
| 32 | + // Both should now be restricted because ' '.trim() === ''.trim() |
| 33 | + expect(policy.isRefreshAllowed(emptyUser)).toBe(false); |
| 34 | + expect(policy.isRefreshAllowed(spaceUser)).toBe(false); |
| 35 | + }); |
| 36 | + |
| 37 | + it('Negative Cooldown Boundaries: clamps negative cooldown durations to 0', () => { |
| 38 | + policy.setCooldown(-5000); // Should be clamped to 0 |
| 39 | + policy.recordRefresh('testuser'); |
| 40 | + |
| 41 | + // Since cooldown is clamped to 0, refresh is immediately allowed again |
| 42 | + expect(policy.isRefreshAllowed('testuser')).toBe(true); |
| 43 | + expect(policy.getRemainingCooldown('testuser')).toBe(0); |
| 44 | + }); |
| 45 | + |
| 46 | + it('Unrecorded Users Fallback: returns safe fallback value (0) for unrecorded users', () => { |
| 47 | + const remaining = policy.getRemainingCooldown('never-seen-before'); |
| 48 | + expect(remaining).toBe(0); // Should be exactly 0, not undefined/NaN |
| 49 | + |
| 50 | + // Ensure we aren't getting false negatives |
| 51 | + expect(policy.isRefreshAllowed('never-seen-before')).toBe(true); |
| 52 | + }); |
| 53 | + |
| 54 | + it('Missing/Undefined Parameters: throws TypeError predictably when passed undefined/null', () => { |
| 55 | + // Bypassing TS to simulate runtime missing parameters in JS environments |
| 56 | + const undefinedUser = undefined as unknown as string; |
| 57 | + const nullUser = null as unknown as string; |
| 58 | + |
| 59 | + // We expect it to throw because .trim() is called on undefined/null |
| 60 | + expect(() => policy.isRefreshAllowed(undefinedUser)).toThrowError(TypeError); |
| 61 | + expect(() => policy.isRefreshAllowed(nullUser)).toThrowError(TypeError); |
| 62 | + |
| 63 | + expect(() => policy.recordRefresh(undefinedUser)).toThrowError(TypeError); |
| 64 | + expect(() => policy.getRemainingCooldown(nullUser)).toThrowError(TypeError); |
| 65 | + }); |
| 66 | + |
| 67 | + it('Singleton Reset Stability: clears all state and restores defaults on reset', () => { |
| 68 | + // Change state |
| 69 | + policy.setCooldown(10000); |
| 70 | + policy.recordRefresh('user-a'); |
| 71 | + |
| 72 | + // Verify state was changed |
| 73 | + expect(policy.isRefreshAllowed('user-a')).toBe(false); |
| 74 | + |
| 75 | + // Reset |
| 76 | + policy.reset(); |
| 77 | + |
| 78 | + // Verify map is completely cleared |
| 79 | + expect(policy.isRefreshAllowed('user-a')).toBe(true); |
| 80 | + expect(policy.getRemainingCooldown('user-a')).toBe(0); |
| 81 | + |
| 82 | + // Verify cooldown is restored to default (5 * 60 * 1000 = 300000ms) |
| 83 | + policy.recordRefresh('user-a'); |
| 84 | + // It should be 300000 right after recording (elapsed ~ 0) |
| 85 | + expect(policy.getRemainingCooldown('user-a')).toBe(300000); |
| 86 | + }); |
| 87 | +}); |
0 commit comments