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();
|
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