final code review (Opus) 발견 2 important issues: 1. SyncConflict.noteId 가 실제로 export filename slug (date-id8-slug) 였음 — UUID 가 아니라 git checkout path 의 stem. 명명 혼동 → 'path' 로 rename (실제 의미와 일치). 2. ConflictModal preview 가 항상 빈 문자열이라 사용자가 비교 없이 local/remote 선택해야 했음. runSync 의 conflict 분기에서 `git show :2:<path>` (ours) + `:3:<path>` (theirs) 호출 추가하여 localText/remoteText 채움. 영향: - SyncService.SyncConflict + shared/types.ts.SyncConflict: noteId → path - SyncService.resolveConflict(path, choice) — 'notes/...md' 그대로 받음 - pathToNoteId 헬퍼 제거 (불필요) - ConflictModal: c.noteId → c.path, busy 상태 + 표시 모두 path 키 - IPC handler / preload bridge / InboxApi 시그니처 모두 path 로 통일 - SyncService.bidirectional/resolveConflict/sync-ipc/ConflictModal 4 test 갱신 regression 회귀 패턴 검사: rename 후 NoteRepository / SyncService / IPC / UI 의 모든 conflict-related path 일관 (typecheck 0).
61 lines
2.7 KiB
TypeScript
61 lines
2.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { SyncService } from '../../src/main/services/SyncService.js';
|
|
|
|
vi.mock('../../src/main/services/GitClient.js');
|
|
import { GitClient } from '../../src/main/services/GitClient.js';
|
|
|
|
describe('SyncService.resolveConflict', () => {
|
|
let svc: SyncService;
|
|
let importSvc: { applySyncFromDir: ReturnType<typeof vi.fn> };
|
|
let gitInstance: {
|
|
run: ReturnType<typeof vi.fn>;
|
|
addAll: ReturnType<typeof vi.fn>;
|
|
push: ReturnType<typeof vi.fn>;
|
|
};
|
|
|
|
beforeEach(() => {
|
|
importSvc = { applySyncFromDir: vi.fn(async () => ({ changedCount: 0 })) };
|
|
gitInstance = {
|
|
run: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
|
|
addAll: vi.fn(async () => {}),
|
|
push: vi.fn(async () => {})
|
|
};
|
|
(GitClient as unknown as ReturnType<typeof vi.fn>).mockImplementation(function () { return gitInstance; });
|
|
svc = new SyncService('/tmp', {} as never, importSvc as never);
|
|
});
|
|
|
|
it('local 선택 → checkout --ours + add + rebase --continue + push', async () => {
|
|
const r = await svc.resolveConflict('notes/note-id.md', 'local');
|
|
expect(gitInstance.run).toHaveBeenCalledWith(['checkout', '--ours', 'notes/note-id.md']);
|
|
expect(gitInstance.run).toHaveBeenCalledWith(['rebase', '--continue']);
|
|
expect(gitInstance.push).toHaveBeenCalled();
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
it('remote 선택 → checkout --theirs + add + rebase --continue + applySyncFromDir + push', async () => {
|
|
const r = await svc.resolveConflict('notes/note-id.md', 'remote');
|
|
expect(gitInstance.run).toHaveBeenCalledWith(['checkout', '--theirs', 'notes/note-id.md']);
|
|
expect(importSvc.applySyncFromDir).toHaveBeenCalled();
|
|
expect(gitInstance.push).toHaveBeenCalled();
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
it('checkout 실패 → ok:false + reason 반환', async () => {
|
|
gitInstance.run.mockResolvedValueOnce({ stdout: '', stderr: 'fail', exitCode: 1 });
|
|
const r = await svc.resolveConflict('notes/note-id.md', 'local');
|
|
expect(r.ok).toBe(false);
|
|
expect((r as { reason: string }).reason).toContain('checkout failed');
|
|
expect(gitInstance.push).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('rebase --continue 실패 (다른 파일 미해결) → ok:false', async () => {
|
|
gitInstance.run
|
|
.mockResolvedValueOnce({ stdout: '', stderr: '', exitCode: 0 }) // checkout
|
|
.mockResolvedValueOnce({ stdout: '', stderr: 'still unresolved', exitCode: 1 }); // rebase --continue
|
|
const r = await svc.resolveConflict('notes/note-id.md', 'local');
|
|
expect(r.ok).toBe(false);
|
|
expect((r as { reason: string }).reason).toContain('rebase --continue failed');
|
|
expect(gitInstance.push).not.toHaveBeenCalled();
|
|
});
|
|
});
|