mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-25 23:46:51 +00:00
fix: testing for peer
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user