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:
2026-09-11 12:25:42 +04:00
co-authored by Claude Sonnet 5
parent 2cb1959bff
commit 25c171f742
5 changed files with 190 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
// browser.storage.local is backed by the real add-on profile on disk, so anything
// written through transport/storage.ts is what "survives a browser restart" means in
// practice — this covers the storage half of that DoD item; websocket.test.ts's
// "survives a browser restart" case covers the transport half (a fresh transport
// instance reusing a persisted token with no re-pairing).
import { beforeEach, describe, expect, it } from 'vitest';
import * as storage from '../../src/background/transport/storage.js';
describe('transport/storage', () => {
beforeEach(async () => {
await browser.storage.local.clear();
});
it('round-trips the pairing token', async () => {
expect(await storage.getToken()).toBeNull();
await storage.setToken('tok-abc');
expect(await storage.getToken()).toBe('tok-abc');
});
it('clears the token on unpair (null)', async () => {
await storage.setToken('tok-abc');
await storage.setToken(null);
expect(await storage.getToken()).toBeNull();
});
it('a fresh read after a simulated restart still sees the persisted token', async () => {
await storage.setToken('tok-survives');
// Nothing here recreates browser.storage.local — that's the point: it is the one
// thing in the extension that outlives the background page's lifetime, restart
// included. A second, independent read call stands in for "the page reloaded".
expect(await storage.getToken()).toBe('tok-survives');
expect(await storage.getToken()).toBe('tok-survives');
});
it('round-trips the transport override, defaulting to auto', async () => {
expect(await storage.getOverride()).toBe('auto');
await storage.setOverride('uds');
expect(await storage.getOverride()).toBe('uds');
await storage.setOverride('ws');
expect(await storage.getOverride()).toBe('ws');
});
it('ignores a corrupted override value and falls back to auto', async () => {
await browser.storage.local.set({ 'velox.transportOverride': 'not-a-transport' });
expect(await storage.getOverride()).toBe('auto');
});
it('round-trips the cached WebSocket port', async () => {
expect(await storage.getCachedWsPort()).toBeNull();
await storage.setCachedWsPort(52003);
expect(await storage.getCachedWsPort()).toBe(52003);
await storage.setCachedWsPort(null);
expect(await storage.getCachedWsPort()).toBeNull();
});
});