import { test } from 'node:test'; import assert from 'node:assert/strict'; import { KeychainInvoke, sameTokens, syncKeychain, tokensOf } from './keychainMirror'; import type { Session } from './sessions'; const session: Session = { baseUrl: 'https://matrix.example.org', userId: '@alice:example.org', deviceId: 'DEV1', accessToken: 'syt_token', refreshToken: 'mar_refresh', }; /** An in-memory keychain behind the same commands the desktop app exposes. */ const fakeKeychain = (opts: { supported?: boolean; failSet?: boolean; corrupt?: boolean } = {}) => { let stored: unknown = null; const calls: string[] = []; const invoke: KeychainInvoke = async (cmd, args) => { calls.push(cmd); switch (cmd) { case 'secure_session_supported': return opts.supported ?? true; case 'secure_session_get': return stored; case 'secure_session_set': if (opts.failSet) throw new Error('Access is denied.'); stored = opts.corrupt ? { ...(args?.tokens as object), accessToken: 'x' } : args?.tokens; return null; case 'secure_session_clear': stored = null; return null; default: throw new Error(`unknown ${cmd}`); } }; return { invoke, calls, get: () => stored }; }; test('stores the tokens (not the whole session) and verifies them', async () => { const kc = fakeKeychain(); assert.deepEqual(await syncKeychain(kc.invoke, session), { state: 'ok' }); assert.deepEqual(kc.get(), { userId: '@alice:example.org', deviceId: 'DEV1', accessToken: 'syt_token', refreshToken: 'mar_refresh', }); assert.deepEqual(kc.calls, [ 'secure_session_supported', 'secure_session_get', 'secure_session_set', 'secure_session_get', ]); }); test('an up-to-date copy is not rewritten', async () => { const kc = fakeKeychain(); await syncKeychain(kc.invoke, session); kc.calls.length = 0; assert.deepEqual(await syncKeychain(kc.invoke, session), { state: 'ok' }); assert.deepEqual(kc.calls, ['secure_session_supported', 'secure_session_get']); }); test('a token rotation rewrites; no session clears', async () => { const kc = fakeKeychain(); await syncKeychain(kc.invoke, session); await syncKeychain(kc.invoke, { ...session, accessToken: 'syt_new', refreshToken: undefined }); assert.equal((kc.get() as { accessToken: string }).accessToken, 'syt_new'); assert.equal('refreshToken' in (kc.get() as object), false); assert.deepEqual(await syncKeychain(kc.invoke, null), { state: 'cleared' }); assert.equal(kc.get(), null); }); test('unsupported platform: nothing is touched', async () => { const kc = fakeKeychain({ supported: false }); assert.deepEqual(await syncKeychain(kc.invoke, session), { state: 'unsupported' }); assert.deepEqual(kc.calls, ['secure_session_supported']); }); test('failures become a status, never an exception', async () => { assert.deepEqual(await syncKeychain(fakeKeychain({ failSet: true }).invoke, session), { state: 'error', error: 'Access is denied.', }); assert.deepEqual(await syncKeychain(fakeKeychain({ corrupt: true }).invoke, session), { state: 'error', error: 'read-back did not match', }); // The credential store hangs (the support check itself is instant). const hung: KeychainInvoke = async (cmd) => cmd === 'secure_session_supported' ? true : new Promise(() => { /* never settles */ }); assert.deepEqual(await syncKeychain(hung, session, 50), { state: 'error', error: 'keychain timed out', }); }); test('a desktop build without the commands reads as unsupported, not an error', async () => { const missingCommand: KeychainInvoke = async (cmd) => { throw new Error(`Command ${cmd} not found`); }; assert.deepEqual(await syncKeychain(missingCommand, session), { state: 'unsupported' }); }); test('sameTokens compares only the secrets, treating a missing refresh token as absent', () => { const t = tokensOf({ ...session, refreshToken: undefined }); assert.equal(sameTokens({ ...t }, t), true); assert.equal(sameTokens({ ...t, refreshToken: undefined }, t), true); assert.equal(sameTokens({ ...t, accessToken: 'other' }, t), false); assert.equal(sameTokens({ ...t, refreshToken: 'r' }, t), false); assert.equal(sameTokens(null, t), false); assert.equal(sameTokens('string', t), false); });