-
Notifications
You must be signed in to change notification settings - Fork 453
Expand file tree
/
Copy pathrefresh-timer-cleanup.test.ts
More file actions
79 lines (66 loc) · 3.03 KB
/
refresh-timer-cleanup.test.ts
File metadata and controls
79 lines (66 loc) · 3.03 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
import { expect, test } from '@playwright/test';
import { appConfigs } from '../../presets';
import type { FakeUser } from '../../testUtils';
import { createTestUtils, testAgainstRunningApps } from '../../testUtils';
/**
* Tests that the token cache's proactive refresh timer does not accumulate
* orphaned timers across set() calls.
*
* Background: Every API response that piggybacks client data triggers _updateClient,
* which reconstructs Session objects and calls #hydrateCache → SessionTokenCache.set().
* Without proper timer cleanup, each set() call would leave the previous refresh timer
* running, causing the effective polling rate to accelerate over time.
*/
testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })(
'Token refresh timer cleanup @generic',
({ app }) => {
test.describe.configure({ mode: 'serial' });
let fakeUser: FakeUser;
test.beforeAll(async () => {
const u = createTestUtils({ app });
fakeUser = u.services.users.createFakeUser();
await u.services.users.createBapiUser(fakeUser);
});
test.afterAll(async () => {
await fakeUser.deleteIfExists();
await app.teardown();
});
test('touch does not cause clustered token refresh requests', async ({ page, context }) => {
test.setTimeout(120_000);
const u = createTestUtils({ app, page, context });
await u.po.signIn.goTo();
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
await u.po.expect.toBeSignedIn();
// Track token fetch requests with timestamps
const tokenRequests: number[] = [];
await page.route('**/v1/client/sessions/*/tokens**', async route => {
tokenRequests.push(Date.now());
await route.continue();
});
// Trigger multiple touch() calls — each causes _updateClient → Session constructor
// → #hydrateCache → set(), which previously leaked orphaned refresh timers.
// Note: This works because the test instance is multi-session, so it doesn't
// hit the 5s single-session touch throttle.
for (let i = 0; i < 5; i++) {
await page.evaluate(async () => {
await (window as any).Clerk?.session?.touch();
});
}
// Wait 50s — enough for one refresh cycle (~43s) but not two
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(50_000);
await page.unrouteAll();
// With the fix: at most 1-2 refresh requests (one cycle at ~43s)
// Without the fix: 5+ requests from orphaned timers all firing at different offsets
expect(tokenRequests.length).toBeLessThanOrEqual(3);
// If multiple requests occurred, verify they aren't clustered together
// (clustering = orphaned timers firing near-simultaneously)
if (tokenRequests.length >= 2) {
for (let i = 1; i < tokenRequests.length; i++) {
const gap = tokenRequests[i] - tokenRequests[i - 1];
expect(gap).toBeGreaterThan(10_000);
}
}
});
},
);