|
1 | 1 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' |
2 | | -import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readdirSync, readFileSync } from 'fs' |
| 2 | +import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readdirSync, readFileSync, statSync } from 'fs' |
3 | 3 | import { join } from 'path' |
4 | 4 | import { tmpdir } from 'os' |
5 | 5 | import { randomBytes } from 'crypto' |
6 | 6 | import { spawnSync, execSync } from 'child_process' |
7 | | -import { prepareMirror, MirrorAbort, mirrorDown, mirrorUp, mirrorUpPatch, mirrorUpFast, checkPushDivergence, checkPullDivergence } from '../src/mirror' |
| 7 | +import { prepareMirror, MirrorAbort, mirrorDown, mirrorUp, mirrorUpPatch, mirrorUpFast, checkPushDivergence, checkPullDivergence, type MirrorUpFastPhase } from '../src/mirror' |
8 | 8 | import { getBranchName } from '../src/local-repo' |
9 | 9 | import { gitRemoteProjectId } from '@opencode-manager/shared/project-id' |
10 | 10 |
|
@@ -818,6 +818,73 @@ describe('mirrorUpFast targets the selected repo', () => { |
818 | 818 | expect(result.repoId).toBe(99) |
819 | 819 | }) |
820 | 820 |
|
| 821 | + it('reports bundling, uploading, and patching phases in order', async () => { |
| 822 | + const repoRoot = join(tmpDir, 'repo-phases') |
| 823 | + mkdirSync(repoRoot) |
| 824 | + spawnSync('git', ['init'], { cwd: repoRoot, stdio: 'ignore' }) |
| 825 | + spawnSync('git', ['config', 'user.email', 'test@test.com'], { cwd: repoRoot, stdio: 'ignore' }) |
| 826 | + spawnSync('git', ['config', 'user.name', 'Test'], { cwd: repoRoot, stdio: 'ignore' }) |
| 827 | + writeFileSync(join(repoRoot, 'a.txt'), 'a\n') |
| 828 | + spawnSync('git', ['add', '.'], { cwd: repoRoot, stdio: 'ignore' }) |
| 829 | + spawnSync('git', ['commit', '-m', 'init'], { cwd: repoRoot, stdio: 'ignore' }) |
| 830 | + |
| 831 | + const api = { |
| 832 | + mirrorUploadBundle: vi.fn().mockResolvedValue(undefined), |
| 833 | + mirrorPatch: vi.fn().mockResolvedValue({ repoId: 1, fullPath: '/tmp/x', branch: 'main', head: 'abc', created: false, applied: true }), |
| 834 | + } |
| 835 | + |
| 836 | + const plan = { |
| 837 | + repoRoot, |
| 838 | + localProjectId: 'proj', |
| 839 | + matched: [{ repoId: 1, name: 'repo-A', projectId: 'proj', branch: 'main' }], |
| 840 | + } |
| 841 | + |
| 842 | + const phases: MirrorUpFastPhase[] = [] |
| 843 | + await mirrorUpFast(plan, { api: api as any, force: false, onPhase: (p) => phases.push(p) }) |
| 844 | + |
| 845 | + expect(phases.map((p) => p.kind)).toEqual(['bundling', 'uploading', 'patching']) |
| 846 | + const uploading = phases[1] as Extract<MirrorUpFastPhase, { kind: 'uploading' }> |
| 847 | + expect(uploading.bytesSent).toBe(0) |
| 848 | + expect(uploading.totalBytes).toBeGreaterThan(0) |
| 849 | + }) |
| 850 | + |
| 851 | + it('emits cumulative upload progress and a processing phase when the api reports sent bytes', async () => { |
| 852 | + const repoRoot = join(tmpDir, 'repo-upload-progress') |
| 853 | + mkdirSync(repoRoot) |
| 854 | + spawnSync('git', ['init'], { cwd: repoRoot, stdio: 'ignore' }) |
| 855 | + spawnSync('git', ['config', 'user.email', 'test@test.com'], { cwd: repoRoot, stdio: 'ignore' }) |
| 856 | + spawnSync('git', ['config', 'user.name', 'Test'], { cwd: repoRoot, stdio: 'ignore' }) |
| 857 | + writeFileSync(join(repoRoot, 'a.txt'), 'a\n') |
| 858 | + spawnSync('git', ['add', '.'], { cwd: repoRoot, stdio: 'ignore' }) |
| 859 | + spawnSync('git', ['commit', '-m', 'init'], { cwd: repoRoot, stdio: 'ignore' }) |
| 860 | + |
| 861 | + const api = { |
| 862 | + mirrorUploadBundle: vi.fn().mockImplementation( |
| 863 | + async (_repoId: number, bundlePath: string, opts: { onProgress?: (bytesSent: number) => void }) => { |
| 864 | + const { size } = statSync(bundlePath) |
| 865 | + opts.onProgress?.(Math.floor(size / 2)) |
| 866 | + opts.onProgress?.(size) |
| 867 | + }, |
| 868 | + ), |
| 869 | + mirrorPatch: vi.fn().mockResolvedValue({ repoId: 1, fullPath: '/tmp/x', branch: 'main', head: 'abc', created: false, applied: true }), |
| 870 | + } |
| 871 | + |
| 872 | + const plan = { |
| 873 | + repoRoot, |
| 874 | + localProjectId: 'proj', |
| 875 | + matched: [{ repoId: 1, name: 'repo-A', projectId: 'proj', branch: 'main' }], |
| 876 | + } |
| 877 | + |
| 878 | + const phases: MirrorUpFastPhase[] = [] |
| 879 | + await mirrorUpFast(plan, { api: api as any, force: false, onPhase: (p) => phases.push(p) }) |
| 880 | + |
| 881 | + expect(phases.map((p) => p.kind)).toEqual(['bundling', 'uploading', 'uploading', 'uploading', 'processing', 'patching']) |
| 882 | + const sent = phases.filter((p): p is Extract<MirrorUpFastPhase, { kind: 'uploading' }> => p.kind === 'uploading').map((p) => p.bytesSent) |
| 883 | + expect(sent[0]).toBe(0) |
| 884 | + expect(sent[1]!).toBeGreaterThan(0) |
| 885 | + expect(sent[2]!).toBeGreaterThan(sent[1]!) |
| 886 | + }) |
| 887 | + |
821 | 888 | it('narrows a multi-match plan so bundle goes to the chosen repo', async () => { |
822 | 889 | const repoRoot = join(tmpDir, 'repo-narrow') |
823 | 890 | mkdirSync(repoRoot) |
|
0 commit comments