- inbox:emitRecallShown / emitRecallSnoozed: ipcMain.handle → on (fire-and-forget honest pattern, return value 의존자 0) - preload: ipcRenderer.invoke → send (matching on the main side) - shared/types: Promise<void> → void on both recall emit methods - store.ts: drop await on emitRecallSnoozed (now void) - inboxApi-*.test.ts: add ipcMain.on to electron mock (broken by above) - tests/unit/recall-ipc.test.ts: new TDD test for handle→on migration Note: #20 CaptureService telemetry .catch debug log skipped — CaptureService has no logger field; adding one would require non-trivial constructor signature change. Reported as CONCERN below. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
2.0 KiB
TypeScript
64 lines
2.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { join } from 'node:path';
|
|
|
|
const { handlers, mockOpenPath } = vi.hoisted(() => ({
|
|
handlers: {} as Record<string, (...args: unknown[]) => unknown>,
|
|
mockOpenPath: vi.fn(async () => '')
|
|
}));
|
|
|
|
vi.mock('electron', () => ({
|
|
default: {
|
|
ipcMain: {
|
|
handle: (ch: string, fn: (...args: unknown[]) => unknown) => {
|
|
handlers[ch] = fn;
|
|
},
|
|
on: (_ch: string, _fn: unknown) => {}
|
|
},
|
|
dialog: {},
|
|
shell: { openPath: mockOpenPath }
|
|
}
|
|
}));
|
|
|
|
import { registerInboxApi } from '../../src/main/ipc/inboxApi';
|
|
|
|
function makeDeps(profileDir: string): Parameters<typeof registerInboxApi>[0] {
|
|
// Minimal stub — `inbox:open-media` 핸들러는 deps.paths.profileDir 만 참조.
|
|
return {
|
|
repo: {} as never,
|
|
continuity: {} as never,
|
|
capture: {} as never,
|
|
health: {} as never,
|
|
intent: {} as never,
|
|
getInboxWindow: () => null,
|
|
settings: {} as never,
|
|
providerHolder: {} as never,
|
|
paths: { profileDir }
|
|
};
|
|
}
|
|
|
|
describe('inbox:open-media IPC', () => {
|
|
beforeEach(() => {
|
|
Object.keys(handlers).forEach((k) => delete handlers[k]);
|
|
mockOpenPath.mockClear();
|
|
});
|
|
|
|
it('opens valid relPath with shell.openPath', async () => {
|
|
registerInboxApi(makeDeps('/profile'));
|
|
const handler = handlers['inbox:open-media'];
|
|
if (handler === undefined) throw new Error('handler not registered');
|
|
const r = await handler(null, 'media/note1/img.png');
|
|
expect(r).toEqual({ ok: true });
|
|
expect(mockOpenPath).toHaveBeenCalledWith(join('/profile', 'media', 'note1', 'img.png'));
|
|
});
|
|
|
|
it('rejects path traversal with reason "invalid path"', async () => {
|
|
registerInboxApi(makeDeps('/profile'));
|
|
const handler = handlers['inbox:open-media'];
|
|
if (handler === undefined) throw new Error('handler not registered');
|
|
const r = await handler(null, '../etc/passwd') as { ok: boolean; reason?: string };
|
|
expect(r.ok).toBe(false);
|
|
expect(r.reason).toBe('invalid path');
|
|
expect(mockOpenPath).not.toHaveBeenCalled();
|
|
});
|
|
});
|