test(ext/sw): cover handleUnlock keyfile error paths (forward-compat guard, invalid_key_file, key-file-not-set)
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user