git-vault.test.js
3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* gitVault 单测
*
* 需要系统装了 git。如果没装则 skip 所有用例。
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const pathResolver = require('../../services/devloop/path-resolver');
const gitVault = require('../../services/devloop/git-vault');
const docStore = require('../../services/devloop/doc-store');
let gitAvailable = false;
try { execSync('git --version', { stdio: 'ignore' }); gitAvailable = true; } catch { /* skip */ }
(gitAvailable ? describe : describe.skip)('gitVault', () => {
let pid, root;
beforeEach(() => {
pathResolver._resetForTesting();
gitVault._resetMutexesForTesting();
const r = pathResolver.allocateProjectRoot({ company_id: 'c-test', mode: 'sandbox' });
pid = r.pid;
root = r.root_path;
});
afterEach(() => {
fs.rmSync(root, { recursive: true, force: true });
});
it('init creates .git + initial commit', async () => {
const r = await gitVault.init(pid);
expect(r.ok).toBe(true);
expect(fs.existsSync(path.join(root, '.git'))).toBe(true);
expect(fs.existsSync(path.join(root, '.gitignore'))).toBe(true);
const logs = await gitVault.log(pid);
expect(logs.length).toBeGreaterThanOrEqual(1);
expect(logs[0].summary).toMatch(/^init: project p-/);
});
it('init is idempotent', async () => {
await gitVault.init(pid);
const r = await gitVault.init(pid);
expect(r.already_exists).toBe(true);
});
it('commit with content creates new sha', async () => {
await gitVault.init(pid);
// 写一个真实的文件再 commit (PRD/HTML 校验严格,这里直接 raw)
fs.writeFileSync(path.join(root, 'README.md'), 'hello\n');
const r = await gitVault.commit(pid, {
kind: 'chore', summary: 'add README',
author: { name: 'PM Agent', email: 'pm@devloop.local' },
});
expect(r.ok).toBe(true);
expect(r.skipped).toBeFalsy();
expect(r.sha).toMatch(/^[a-f0-9]{40}$/);
// author 被记录
const logs = await gitVault.log(pid, { limit: 1 });
expect(logs[0].author_name).toBe('PM Agent');
expect(logs[0].author_email).toBe('pm@devloop.local');
});
it('commit with nothing-to-commit returns skipped', async () => {
await gitVault.init(pid);
const r = await gitVault.commit(pid, {
kind: 'chore', summary: 'empty commit attempt',
});
expect(r.ok).toBe(true);
expect(r.skipped).toBe(true);
});
it('tag + listTags', async () => {
await gitVault.init(pid);
await gitVault.tag(pid, 'shipped-v1');
const tags = await gitVault.listTags(pid);
expect(tags).toContain('shipped-v1');
});
it('status detects dirty tree', async () => {
await gitVault.init(pid);
let s = await gitVault.status(pid);
expect(s.dirty).toBe(false);
fs.writeFileSync(path.join(root, 'untracked.txt'), 'x');
s = await gitVault.status(pid);
expect(s.dirty).toBe(true);
});
it('resetWorkingTree clears dirty', async () => {
await gitVault.init(pid);
fs.writeFileSync(path.join(root, 'dirt.txt'), 'x');
expect((await gitVault.status(pid)).dirty).toBe(true);
await gitVault.resetWorkingTree(pid);
expect((await gitVault.status(pid)).dirty).toBe(false);
});
it('archive produces zip', async () => {
await gitVault.init(pid);
fs.writeFileSync(path.join(root, 'kept.txt'), 'x');
await gitVault.commit(pid, { kind: 'chore', summary: 'add file' });
const r = await gitVault.archive(pid);
expect(fs.existsSync(r.path)).toBe(true);
fs.rmSync(r.path);
});
});