Skip to content
File

Blob: worker/tests/runtime/leases.test.ts

typescript87 lines
1import { expect, test, vi } from 'vitest';
2import { runDurableObjectAlarm, runInDurableObject } from 'cloudflare:test';
3import { createHarness, device } from './harness.ts';
4 
5test('failed revocation retains the controller until a successful retry', async () => {
6 let failRevoke = false;
7 const h = await createHarness(({ path, input }) =>
8 path.endsWith('/datachannels/update') && input.dataChannels[0].canReply === false && failRevoke
9 ? Response.json({ errorCode: 'retry_later' }, { status: 503 })
10 : undefined,
11 );
12 await h.login();
13 await h.start();
14 const a = await h.viewer(),
15 b = await h.viewer();
16 expect((await h.call(`/viewers/${a.id}/claim`, {}, a.owner)).status).toBe(200);
17 failRevoke = true;
18 expect((await h.call(`/viewers/${a.id}/release`, {}, a.owner)).status).toBe(502);
19 expect((await h.status()).controller).toBe(a.id);
20 expect((await h.call(`/viewers/${b.id}/claim`, {}, b.owner)).status).toBe(409);
21 failRevoke = false;
22 expect((await h.call(`/viewers/${a.id}/release`, {}, a.owner)).status).toBe(200);
23 expect((await h.call(`/viewers/${b.id}/claim`, {}, b.owner)).status).toBe(200);
24});
25 
26test('status polling keeps the earliest alarm and an expired lease revokes SFU permission', async () => {
27 const h = createHarness();
28 await h.login();
29 await h.start();
30 const v = await h.viewer();
31 expect((await h.call(`/viewers/${v.id}/claim`, {}, v.owner)).status).toBe(200);
32 const scheduled = await runInDurableObject(h.room, (_instance, state) =>
33 state.storage.getAlarm(),
34 );
35 expect(scheduled).not.toBeNull();
36 vi.useFakeTimers({ toFake: ['Date'] });
37 const now = Date.now();
38 for (let i = 1; i <= 5; i++) {
39 vi.setSystemTime(now + i * 1000);
40 await h.status();
41 expect(await runInDurableObject(h.room, (_instance, state) => state.storage.getAlarm())).toBe(
42 scheduled,
43 );
44 }
45 vi.setSystemTime(now + 16000);
46 expect(await runDurableObjectAlarm(h.room)).toBe(true);
47 expect((await h.status()).controller).toBeNull();
48 expect(
49 h.calls.some(
50 (call) =>
51 call.path.endsWith('/datachannels/update') && call.input.dataChannels[0].canReply === false,
52 ),
53 ).toBe(true);
54});
55 
56test('publisher expiry discards its whole generation and stops cleanup alarms', async () => {
57 let expired = false;
58 const h = createHarness(() =>
59 expired ? Response.json({ errorCode: 'internal_error' }, { status: 503 }) : undefined,
60 );
61 await h.login();
62 const previous = await h.start();
63 const viewer = await h.viewer();
64 expect((await h.call(`/viewers/${viewer.id}/claim`, {}, viewer.owner)).status).toBe(200);
65 const before = h.calls.length;
66 vi.useFakeTimers({ toFake: ['Date'] });
67 vi.setSystemTime(Date.now() + 91000);
68 expired = true;
69 expect(await runDurableObjectAlarm(h.room)).toBe(true);
70 expect(h.calls).toHaveLength(before);
71 await h.evict();
72 expect(await h.status()).toMatchObject({
73 online: false,
74 generation: null,
75 viewers: 0,
76 controller: null,
77 });
78 expect(await runInDurableObject(h.room, (_instance, state) => state.storage.getAlarm())).toBe(
79 null,
80 );
81 expect((await h.call('/device/heartbeat', previous.identity, device)).status).toBe(409);
82 expect((await h.call(`/viewers/${viewer.id}/heartbeat`, {}, viewer.owner)).status).toBe(403);
83 expired = false;
84 await h.start({ bootId: 'b'.repeat(32) });
85 expect((await h.status()).online).toBe(true);
86});