fix: testing for peer

This commit is contained in:
Daniel Salazar
2026-08-25 11:22:09 -04:00
parent 3c866c645b
commit f969faa28f
2 changed files with 154 additions and 1 deletions
@@ -38,7 +38,9 @@ const makeReq = (init: {
method?: string;
}): Request => {
return {
body: init.body ?? {},
// Distinguish "no body key" from an explicit `body: undefined`, so a
// test can exercise a request that never had a parsed body at all.
body: 'body' in init ? init.body : {},
query: {},
headers: init.headers ?? {},
actor: init.actor,
@@ -912,6 +914,58 @@ describe('PeerController guest TURN', () => {
});
});
describe('default lifetimes', () => {
// The defaults are the security-relevant knob — a deployment that sets
// only the secret still gets short-lived grants and credentials, and a
// guest credential still cannot outlive the host's own.
it('falls back to an hour for grants and guest credentials', async () => {
const defaultsServer = await setupTestServer({
peers: {
turn: {
cloudflare_turn_service_id: 'svc-1',
cloudflare_turn_api_token: 'token-1',
ttl: 86_400,
},
guest_turn: { grant_secret: GRANT_SECRET },
},
} as never);
const fetchSpy = stubCloudflare();
try {
const router = new PuterRouter();
(
defaultsServer.controllers.peer as unknown as PeerController
).registerRoutes(router);
const handlerFor = (path: string) =>
router.routes.find((r) => r.path === path)!.handler;
const grantRes = makeRes();
handlerFor('/peer/turn-grant')(
makeReq({ actor: hostActor }),
grantRes.res,
);
const { grant, expiresAt } = grantRes.captured.body as {
grant: string;
expiresAt: number;
};
const grantTtl = expiresAt - Math.floor(Date.now() / 1000);
expect(grantTtl).toBeGreaterThan(3590);
expect(grantTtl).toBeLessThanOrEqual(3600);
const turnRes = makeRes();
await handlerFor('/peer/guest-turn')(
makeReq({ body: { grant } }),
turnRes.res,
);
// An hour, not the host's 24 — the guest ceiling wins here.
expect(turnRes.captured.body).toMatchObject({ ttl: 3600 });
expect(upstreamBody(fetchSpy).ttl).toBe(3600);
} finally {
fetchSpy.mockRestore();
await defaultsServer.shutdown();
}
});
});
describe('when guest access is not configured', () => {
it('refuses to issue or redeem a grant', async () => {
const noGuestServer = await setupTestServer({
@@ -264,6 +264,105 @@ class FakeRTCPeerConnection {
close () {}
}
/** Polls until `pred` holds, for handshakes that resolve across microtasks. */
const waitFor = async (pred, tries = 50) => {
for ( let i = 0; i < tries; i++ ) {
if ( pred() ) return;
await new Promise((resolve) => setTimeout(resolve, 0));
}
throw new Error('condition never became true');
};
describe('serve as a host', () => {
const origWebSocket = globalThis.WebSocket;
beforeEach(() => {
FakeWebSocket.latest = null;
globalThis.WebSocket = FakeWebSocket;
});
afterEach(() => {
globalThis.WebSocket = origWebSocket;
});
const signaller = respond({
url: 'ws://signaller.test/',
fallbackIce: [{ urls: 'stun:fallback.test' }],
});
/** Drives the signaller's create handshake and resolves the invite code. */
const startServing = async (peer, options) => {
const started = peer.serve(options);
await waitFor(() => FakeWebSocket.latest?.onmessage);
await FakeWebSocket.latest.onmessage({
data: JSON.stringify({
server: { create: { success: true, invitecode: 'HOST-1234' } },
}),
});
return await started;
};
it('mints relays against the host session', async () => {
routeFetch({
'/peer/signaller-info': signaller,
'/peer/generate-turn': respond({
iceServers: HOST_SERVERS,
ttl: 3600,
}),
});
const { peer, puter } = makePeer({ authToken: 'host-token' });
const server = await startServing(peer);
expect(server.inviteCode).toBe('HOST-1234');
expect(puter.ui.authenticateWithPuter).not.toHaveBeenCalled();
expect(callTo('/peer/generate-turn')).toBeDefined();
expect(callTo('/peer/guest-turn')).toBeUndefined();
const sent = JSON.parse(FakeWebSocket.latest.sent[0]);
expect(sent.server.create.authToken).toBe('host-token');
});
it('prompts an unauthenticated host to sign in', async () => {
routeFetch({
'/peer/signaller-info': signaller,
'/peer/generate-turn': respond({
iceServers: HOST_SERVERS,
ttl: 3600,
}),
});
const { peer, puter } = makePeer();
await startServing(peer);
expect(puter.ui.authenticateWithPuter).toHaveBeenCalledTimes(1);
});
it('hosts anonymously without relays of its own', async () => {
// An anonymous host has no account to attribute relay usage to, so it
// gets the public ICE servers and no sign-in prompt.
routeFetch({
'/peer/signaller-info': signaller,
'/peer/generate-turn': respond({}, false),
});
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const { peer, puter } = makePeer();
try {
await startServing(peer, {
anonToken: '11111111-2222-3333-4444-555555555555',
});
} finally {
warn.mockRestore();
}
expect(puter.ui.authenticateWithPuter).not.toHaveBeenCalled();
const sent = JSON.parse(FakeWebSocket.latest.sent[0]);
expect(sent.server.create.anonToken).toBe(
'11111111-2222-3333-4444-555555555555',
);
});
});
describe('connect as a guest', () => {
const origWebSocket = globalThis.WebSocket;
const origRTC = globalThis.RTCPeerConnection;