96 lines
3.8 KiB
TypeScript
96 lines
3.8 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.sync — 양방향', () => {
|
|
let svc: SyncService;
|
|
let exportSvc: { export: ReturnType<typeof vi.fn> };
|
|
let importSvc: { applySyncFromDir: ReturnType<typeof vi.fn> };
|
|
let gitInstance: {
|
|
isRepo: ReturnType<typeof vi.fn>;
|
|
hasRemote: ReturnType<typeof vi.fn>;
|
|
addAll: ReturnType<typeof vi.fn>;
|
|
hasUncommittedChanges: ReturnType<typeof vi.fn>;
|
|
commit: ReturnType<typeof vi.fn>;
|
|
fetch: ReturnType<typeof vi.fn>;
|
|
refExists: ReturnType<typeof vi.fn>;
|
|
rebaseOnto: ReturnType<typeof vi.fn>;
|
|
rebaseAbort: ReturnType<typeof vi.fn>;
|
|
listConflicts: ReturnType<typeof vi.fn>;
|
|
push: ReturnType<typeof vi.fn>;
|
|
};
|
|
|
|
beforeEach(() => {
|
|
exportSvc = { export: vi.fn(async () => {}) };
|
|
importSvc = { applySyncFromDir: vi.fn(async () => ({ changedCount: 0 })) };
|
|
gitInstance = {
|
|
isRepo: vi.fn(async () => true),
|
|
hasRemote: vi.fn(async () => true),
|
|
addAll: vi.fn(async () => {}),
|
|
hasUncommittedChanges: vi.fn(async () => true),
|
|
commit: vi.fn(async () => ({ changed: true, sha: 'abc' })),
|
|
fetch: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
|
|
refExists: vi.fn(async () => true),
|
|
rebaseOnto: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
|
|
rebaseAbort: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
|
|
listConflicts: vi.fn(async () => []),
|
|
push: vi.fn(async () => {})
|
|
};
|
|
(GitClient as unknown as ReturnType<typeof vi.fn>).mockImplementation(function () { return gitInstance; });
|
|
svc = new SyncService(
|
|
'/tmp/profile',
|
|
exportSvc as unknown as never,
|
|
importSvc as unknown as never
|
|
);
|
|
});
|
|
|
|
it('happy path — 6단계 모두 호출, ok:true', async () => {
|
|
const r = await svc.sync();
|
|
expect(exportSvc.export).toHaveBeenCalled();
|
|
expect(gitInstance.addAll).toHaveBeenCalled();
|
|
expect(gitInstance.commit).toHaveBeenCalled();
|
|
expect(gitInstance.fetch).toHaveBeenCalled();
|
|
expect(gitInstance.rebaseOnto).toHaveBeenCalledWith('origin/main');
|
|
expect(importSvc.applySyncFromDir).toHaveBeenCalled();
|
|
expect(gitInstance.push).toHaveBeenCalled();
|
|
expect(r.ok).toBe(true);
|
|
expect(r.pushed).toBe(true);
|
|
});
|
|
|
|
it('local 변경 없음 → commit skip + 다음 단계 진행', async () => {
|
|
gitInstance.hasUncommittedChanges.mockResolvedValueOnce(false);
|
|
const r = await svc.sync();
|
|
expect(gitInstance.commit).not.toHaveBeenCalled();
|
|
expect(gitInstance.fetch).toHaveBeenCalled();
|
|
expect(r.ok).toBe(true);
|
|
});
|
|
|
|
it('rebase 실패 → abort + reason=conflict + conflicts 포함', async () => {
|
|
gitInstance.rebaseOnto.mockResolvedValueOnce({ stdout: '', stderr: 'CONFLICT', exitCode: 1 });
|
|
gitInstance.listConflicts.mockResolvedValueOnce(['notes/aaa.md', 'notes/bbb.md']);
|
|
const r = await svc.sync();
|
|
expect(gitInstance.rebaseAbort).toHaveBeenCalled();
|
|
expect(r.ok).toBe(false);
|
|
expect(r.reason).toBe('conflict');
|
|
expect(r.conflicts?.length).toBe(2);
|
|
expect(gitInstance.push).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('fetch 실패 → reason 반환', async () => {
|
|
gitInstance.fetch.mockResolvedValueOnce({ stdout: '', stderr: 'no network', exitCode: 1 });
|
|
const r = await svc.sync();
|
|
expect(r.ok).toBe(false);
|
|
expect(r.reason).toContain('fetch failed');
|
|
expect(gitInstance.rebaseOnto).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('not configured → ok:false + reason=not_configured', async () => {
|
|
gitInstance.isRepo.mockResolvedValueOnce(false);
|
|
const r = await svc.sync();
|
|
expect(r.ok).toBe(false);
|
|
expect(r.reason).toBe('not_configured');
|
|
});
|
|
});
|