test(ext/sw): cover handleUnlock keyfile error paths (forward-compat guard, invalid_key_file, key-file-not-set)

This commit is contained in:
adlee-was-taken
2026-06-26 22:51:10 -04:00
parent 98861218f9
commit 7a6cfb5659

View File

@@ -339,3 +339,103 @@ describe('handleUnlock — keyfile branch (Task 4)', () => {
expect(wasm.unlock).not.toHaveBeenCalled();
});
});
// ---- Task 4 error paths: forward-compat guard, invalid_key_file, key-file-not-set ----
describe('handleUnlock — keyfile error paths', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('forward-compat guard: rejects unknown second_factor without calling unlock or unlock_with_secret', async () => {
const fakeVaultConfig = { hostType: 'gitea' as const, hostUrl: 'https://g', repoPath: 'u/v', apiToken: 't' };
const keyfileBase64 = btoa(String.fromCharCode(...new Uint8Array(32)));
mockChromeStorage({ vaultConfig: fakeVaultConfig, keyfileBase64 });
vi.mocked(gitHostMod.createGitHost).mockImplementation(() => {
const h = makeHostMock();
(h.readFile as ReturnType<typeof vi.fn>).mockImplementation(async (path: string) => {
if (path === '.relicario/salt') return new Uint8Array(32);
if (path === '.relicario/params.json') {
return new TextEncoder().encode(
'{"argon2_m":65536,"argon2_t":3,"argon2_p":4,"second_factor":"totp"}',
);
}
if (path === 'manifest.enc') return new Uint8Array([0xab, 0xcd]);
throw new Error(`404: ${path}`);
});
(globalThis as { __lastFakeGitHost?: ReturnType<typeof makeHostMock> | null }).__lastFakeGitHost = h;
return h;
});
const wasm = makeWasm({ keyfile_decode: vi.fn(), unlock_with_secret: vi.fn() });
const state = makeState(wasm);
vault.setWasm(wasm);
const resp = await handleUnlock({ type: 'unlock', passphrase: 'pw' }, state);
expect(resp).toEqual({ ok: false, error: 'unsupported second factor in vault params' });
expect(wasm.unlock).not.toHaveBeenCalled();
expect(wasm.unlock_with_secret).not.toHaveBeenCalled();
});
it('returns invalid_key_file when keyfile_decode throws (bad armor)', async () => {
const fakeVaultConfig = { hostType: 'gitea' as const, hostUrl: 'https://g', repoPath: 'u/v', apiToken: 't' };
const keyfileBase64 = btoa(String.fromCharCode(...new Uint8Array(32)));
mockChromeStorage({ vaultConfig: fakeVaultConfig, keyfileBase64 });
vi.mocked(gitHostMod.createGitHost).mockImplementation(() => {
const h = makeHostMock();
(h.readFile as ReturnType<typeof vi.fn>).mockImplementation(async (path: string) => {
if (path === '.relicario/salt') return new Uint8Array(32);
if (path === '.relicario/params.json') {
return new TextEncoder().encode(
'{"argon2_m":65536,"argon2_t":3,"argon2_p":4,"second_factor":"keyfile"}',
);
}
if (path === 'manifest.enc') return new Uint8Array([0xab, 0xcd]);
throw new Error(`404: ${path}`);
});
(globalThis as { __lastFakeGitHost?: ReturnType<typeof makeHostMock> | null }).__lastFakeGitHost = h;
return h;
});
const wasm = makeWasm({
keyfile_decode: vi.fn(() => { throw new Error('bad armor'); }),
unlock_with_secret: vi.fn(),
});
const state = makeState(wasm);
vault.setWasm(wasm);
const resp = await handleUnlock({ type: 'unlock', passphrase: 'pw' }, state);
expect(resp).toEqual({ ok: false, error: 'invalid_key_file' });
expect(wasm.unlock_with_secret).not.toHaveBeenCalled();
});
it('returns key-file-not-set error when keyfileBase64 absent from storage', async () => {
const fakeVaultConfig = { hostType: 'gitea' as const, hostUrl: 'https://g', repoPath: 'u/v', apiToken: 't' };
// Intentionally no keyfileBase64 in storage — only vaultConfig.
mockChromeStorage({ vaultConfig: fakeVaultConfig });
vi.mocked(gitHostMod.createGitHost).mockImplementation(() => {
const h = makeHostMock();
(h.readFile as ReturnType<typeof vi.fn>).mockImplementation(async (path: string) => {
if (path === '.relicario/salt') return new Uint8Array(32);
if (path === '.relicario/params.json') {
return new TextEncoder().encode(
'{"argon2_m":65536,"argon2_t":3,"argon2_p":4,"second_factor":"keyfile"}',
);
}
if (path === 'manifest.enc') return new Uint8Array([0xab, 0xcd]);
throw new Error(`404: ${path}`);
});
(globalThis as { __lastFakeGitHost?: ReturnType<typeof makeHostMock> | null }).__lastFakeGitHost = h;
return h;
});
const wasm = makeWasm({ keyfile_decode: vi.fn(), unlock_with_secret: vi.fn() });
const state = makeState(wasm);
vault.setWasm(wasm);
const resp = await handleUnlock({ type: 'unlock', passphrase: 'pw' }, state);
expect(resp).toEqual({ ok: false, error: 'Key file not set. Run setup first.' });
expect(wasm.keyfile_decode).not.toHaveBeenCalled();
expect(wasm.unlock_with_secret).not.toHaveBeenCalled();
});
});