-
Notifications
You must be signed in to change notification settings - Fork 451
Expand file tree
/
Copy pathoauthNegativeCache.test.ts
More file actions
147 lines (119 loc) · 5.07 KB
/
oauthNegativeCache.test.ts
File metadata and controls
147 lines (119 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { MachineTokenVerificationError, MachineTokenVerificationErrorCode } from '../../errors';
import {
isOAuthTokenCachedAsInvalid,
makeCachedInvalidOAuthTokenError,
maybeCacheOAuthTokenAsInvalid,
resetOAuthNegativeCache,
} from '../oauthNegativeCache';
const TOKEN = 'oat_abc123';
const ANOTHER_TOKEN = 'oat_xyz789';
function makeTokenInvalidError() {
return new MachineTokenVerificationError({
message: 'OAuth token not found',
code: MachineTokenVerificationErrorCode.TokenInvalid,
status: 404,
});
}
function makeOtherError() {
return new MachineTokenVerificationError({
message: 'Invalid secret key',
code: MachineTokenVerificationErrorCode.InvalidSecretKey,
status: 401,
});
}
describe('oauthNegativeCache', () => {
beforeEach(() => {
resetOAuthNegativeCache();
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
describe('isOAuthTokenCachedAsInvalid', () => {
it('returns false for a token that has never been cached', () => {
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(false);
});
it('returns true for a token cached as invalid', () => {
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), TOKEN);
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(true);
});
it('returns false for a different token not in the cache', () => {
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), TOKEN);
expect(isOAuthTokenCachedAsInvalid(ANOTHER_TOKEN)).toBe(false);
});
it('returns false and evicts the entry after TTL expires', () => {
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), TOKEN);
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(true);
vi.advanceTimersByTime(30_001);
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(false);
});
it('returns true just before TTL expires', () => {
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), TOKEN);
vi.advanceTimersByTime(29_999);
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(true);
});
});
describe('maybeCacheOAuthTokenAsInvalid', () => {
it('caches when error is TokenInvalid', () => {
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), TOKEN);
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(true);
});
it('does not cache when error is a different MachineTokenVerificationError code', () => {
maybeCacheOAuthTokenAsInvalid(makeOtherError(), TOKEN);
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(false);
});
it('does not cache when error is not a MachineTokenVerificationError', () => {
maybeCacheOAuthTokenAsInvalid(new Error('network failure'), TOKEN);
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(false);
});
it('does not cache when error is null', () => {
maybeCacheOAuthTokenAsInvalid(null, TOKEN);
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(false);
});
it('updates the expiry when caching the same token again', () => {
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), TOKEN);
vi.advanceTimersByTime(20_000);
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), TOKEN);
vi.advanceTimersByTime(20_000);
// 40s total since first cache, but only 20s since re-cache; should still be valid
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(true);
vi.advanceTimersByTime(10_001);
// 30s since re-cache; should now expire
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(false);
});
});
describe('makeCachedInvalidOAuthTokenError', () => {
it('returns a MachineTokenVerificationError with TokenInvalid code', () => {
const err = makeCachedInvalidOAuthTokenError();
expect(err).toBeInstanceOf(MachineTokenVerificationError);
expect(err.code).toBe(MachineTokenVerificationErrorCode.TokenInvalid);
});
it('returns an error with status 404', () => {
const err = makeCachedInvalidOAuthTokenError();
expect(err.status).toBe(404);
});
});
describe('resetOAuthNegativeCache', () => {
it('clears all cached entries', () => {
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), TOKEN);
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), ANOTHER_TOKEN);
resetOAuthNegativeCache();
expect(isOAuthTokenCachedAsInvalid(TOKEN)).toBe(false);
expect(isOAuthTokenCachedAsInvalid(ANOTHER_TOKEN)).toBe(false);
});
});
describe('capacity eviction', () => {
it('evicts the oldest entry when the cache reaches MAX_ENTRIES (10,000)', () => {
// Fill the cache to max capacity
for (let i = 0; i < 10_000; i++) {
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), `oat_token_${i}`);
}
expect(isOAuthTokenCachedAsInvalid('oat_token_0')).toBe(true);
// Adding one more should evict oat_token_0 (the oldest)
maybeCacheOAuthTokenAsInvalid(makeTokenInvalidError(), 'oat_overflow');
expect(isOAuthTokenCachedAsInvalid('oat_token_0')).toBe(false);
expect(isOAuthTokenCachedAsInvalid('oat_overflow')).toBe(true);
});
});
});