ext: pairing-restart test + AMO permission justification
tests/transport/storage.test.ts covers the storage half of "the pairing token survives a browser restart" (round-trip, unpair-clears, corrupted override falls back to auto). websocket.test.ts adds the transport half: a fresh WebSocketTransport instance over the same backing store reuses the persisted token with no re-pairing, plus pairWithCode/unpair coverage. "Wrong token rejected and rate-limited" was already covered (websocket.test.ts's NotPaired/RateLimited cases). docs/amo-permissions.md is the submission-ready permission justification for AMO's Notes to Reviewer field, covering every permission in manifest.json plus what was deliberately not requested and how cookie/ header data is handled. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ed8KEmAW48v4YHdxLtqsMB
This commit is contained in:
@@ -93,6 +93,59 @@ describe('WebSocketTransport', () => {
|
||||
expect(lastHello?.token).toBe('tok-issued-1');
|
||||
});
|
||||
|
||||
it('the pairing token survives a browser restart: a fresh transport instance over the same storage reuses it, no re-pairing', async () => {
|
||||
const port = nextPort();
|
||||
daemon = await FakeDaemon.start({ port, acceptToken: null });
|
||||
const h = memDeps(); // stands in for browser.storage.local, which outlives the page
|
||||
transport = makeTransport(port, h);
|
||||
|
||||
await transport.connect();
|
||||
expect(daemon.pairCount).toBe(1);
|
||||
expect(h.store.token).toBe('tok-issued-1');
|
||||
transport.disconnect();
|
||||
|
||||
// Simulate "the browser restarted": a brand new transport instance, same backing
|
||||
// store (in reality, the same on-disk profile), no in-memory state carried over.
|
||||
const restarted = makeTransport(port, h);
|
||||
await restarted.connect();
|
||||
try {
|
||||
expect(restarted.state).toBe('connected');
|
||||
expect(daemon.pairCount).toBe(1); // still just the one pairing, ever
|
||||
const lastHello = [...daemon.seen].reverse().find((s) => s.method === 'session.hello');
|
||||
expect(lastHello?.token).toBe('tok-issued-1');
|
||||
} finally {
|
||||
restarted.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
it('pairWithCode drops any stale token and pairs fresh with the typed code', async () => {
|
||||
const port = nextPort();
|
||||
daemon = await FakeDaemon.start({ port, acceptToken: 'stale' });
|
||||
const h = memDeps({ token: 'stale' });
|
||||
transport = makeTransport(port, h);
|
||||
|
||||
await transport.pairWithCode('4821');
|
||||
|
||||
expect(transport.state).toBe('connected');
|
||||
expect(daemon.pairCount).toBe(1);
|
||||
const pairCall = daemon.seen.find((s) => s.method === 'session.pair');
|
||||
expect((pairCall?.params as { code?: string }).code).toBe('4821');
|
||||
expect(h.store.token).toBe('tok-issued-1');
|
||||
});
|
||||
|
||||
it('unpair clears the stored token and disconnects', async () => {
|
||||
const port = nextPort();
|
||||
daemon = await FakeDaemon.start({ port, acceptToken: 'good-token' });
|
||||
const h = memDeps({ token: 'good-token' });
|
||||
transport = makeTransport(port, h);
|
||||
await transport.connect();
|
||||
|
||||
await transport.unpair();
|
||||
|
||||
expect(h.store.token).toBeNull();
|
||||
expect(transport.state).toBe('disconnected');
|
||||
});
|
||||
|
||||
it('with autoPair off, a wrong token surfaces needsPairing and does NOT retry', async () => {
|
||||
const port = nextPort();
|
||||
daemon = await FakeDaemon.start({ port, acceptToken: 'the-real-one' });
|
||||
|
||||
Reference in New Issue
Block a user