Files
openchamber/packages/ui/src/apps/mobileConnections.test.ts
T

171 lines
6.3 KiB
TypeScript
Raw Normal View History

import { describe, expect, mock, test } from 'bun:test';
import { loadMobileConnections, upsertMobileConnection, validateMobileConnectionSession, type MobileRelayConfig } from './mobileConnections';
const originalFetch = globalThis.fetch;
const originalWindow = globalThis.window;
const createLocalStorageStub = () => {
const store = new Map<string, string>();
return {
getItem: (key: string) => store.get(key) ?? null,
setItem: (key: string, value: string) => { store.set(key, value); },
removeItem: (key: string) => { store.delete(key); },
};
};
const installTestWindow = () => {
Object.defineProperty(globalThis, 'window', {
configurable: true,
value: {
setTimeout: globalThis.setTimeout.bind(globalThis),
clearTimeout: globalThis.clearTimeout.bind(globalThis),
location: { protocol: 'https:' },
localStorage: createLocalStorageStub(),
},
});
};
const restoreGlobals = () => {
globalThis.fetch = originalFetch;
Object.defineProperty(globalThis, 'window', { configurable: true, value: originalWindow });
};
const STORAGE_KEY = 'openchamber.mobile.connections.v1';
const testRelay: MobileRelayConfig = {
relayUrl: 'wss://relay.example/tunnel',
serverId: 'srv_test123',
hostEncPubJwk: { kty: 'EC', crv: 'P-256', x: 'eHhY', y: 'eVlZ' },
};
describe('mobile connection storage', () => {
test('entries persisted before relay support normalize to direct mode on read', async () => {
try {
installTestWindow();
window.localStorage.setItem(STORAGE_KEY, JSON.stringify([
{ id: 'a', label: 'Home', url: 'http://192.168.1.10:2606', lastUsedAt: 10, clientToken: 'tok-a' },
{ id: 'b', label: 'Work', url: 'http://work.example', lastUsedAt: 5 },
]));
const connections = await loadMobileConnections();
expect(connections).toHaveLength(2);
expect(connections.every((connection) => connection.mode === 'direct')).toBe(true);
expect(connections[0]?.relay).toBe(undefined);
expect(connections[0]?.clientToken).toBe('tok-a');
} finally {
restoreGlobals();
}
});
test('relay connections round-trip mode and transport config', async () => {
try {
installTestWindow();
await upsertMobileConnection({
label: 'My Desktop',
url: 'openchamber://connect?v=1&mode=relay',
clientToken: 'oc_client_secret',
relay: testRelay,
});
const connections = await loadMobileConnections();
expect(connections).toHaveLength(1);
const saved = connections[0]!;
expect(saved.mode).toBe('relay');
expect(saved.relay).toEqual(testRelay);
// Web surface: token stays inline like direct connections.
expect(saved.clientToken).toBe('oc_client_secret');
// Persisted metadata carries only the three transport fields — no grant.
const raw = JSON.parse(window.localStorage.getItem(STORAGE_KEY) || '[]') as Array<Record<string, unknown>>;
expect(raw[0]?.mode).toBe('relay');
expect(Object.keys(raw[0]?.relay as object).sort()).toEqual(['hostEncPubJwk', 'relayUrl', 'serverId']);
} finally {
restoreGlobals();
}
});
test('relay entries with malformed transport config are dropped, direct entries survive', async () => {
try {
installTestWindow();
window.localStorage.setItem(STORAGE_KEY, JSON.stringify([
{ id: 'bad', label: 'Broken', url: 'openchamber://connect', lastUsedAt: 20, mode: 'relay', relay: { relayUrl: 'wss://relay.example' } },
{ id: 'ok', label: 'Home', url: 'http://192.168.1.10:2606', lastUsedAt: 10 },
]));
const connections = await loadMobileConnections();
expect(connections).toHaveLength(1);
expect(connections[0]?.id).toBe('ok');
expect(connections[0]?.mode).toBe('direct');
} finally {
restoreGlobals();
}
});
test('relay and direct connections dedupe independently', async () => {
try {
installTestWindow();
await upsertMobileConnection({ label: 'Direct', url: 'http://host.example' });
await upsertMobileConnection({ label: 'Relay', url: 'openchamber://connect?v=1&mode=relay', relay: testRelay });
await upsertMobileConnection({ label: 'Relay renamed', url: 'openchamber://connect?v=1&mode=relay', relay: testRelay });
const connections = await loadMobileConnections();
expect(connections).toHaveLength(2);
expect(connections.filter((connection) => connection.mode === 'relay')).toHaveLength(1);
expect(connections.find((connection) => connection.mode === 'relay')?.label).toBe('Relay renamed');
} finally {
restoreGlobals();
}
});
});
describe('validateMobileConnectionSession', () => {
test('accepts a reachable authenticated runtime', async () => {
const fetchMock = mock(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.endsWith('/health')) return Response.json({ ok: true });
if (url.endsWith('/auth/session')) return Response.json({ authenticated: true, scope: 'client' });
return new Response(null, { status: 404 });
});
try {
installTestWindow();
globalThis.fetch = fetchMock as typeof fetch;
const result = await validateMobileConnectionSession({ url: 'https://runtime.example', clientToken: 'token' });
expect(result).toBe(true);
} finally {
restoreGlobals();
}
});
test('rejects unreachable runtimes', async () => {
try {
installTestWindow();
globalThis.fetch = mock(async () => new Response(null, { status: 503 })) as typeof fetch;
const result = await validateMobileConnectionSession({ url: 'https://runtime.example', clientToken: 'token' });
expect(result).toBe(false);
} finally {
restoreGlobals();
}
});
test('rejects invalid or unauthenticated sessions', async () => {
const fetchMock = mock(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.endsWith('/health')) return Response.json({ ok: true });
return Response.json({ authenticated: false }, { status: 401 });
});
try {
installTestWindow();
globalThis.fetch = fetchMock as typeof fetch;
const result = await validateMobileConnectionSession({ url: 'https://runtime.example', clientToken: 'expired' });
expect(result).toBe(false);
} finally {
restoreGlobals();
}
});
});