forked from colbymchenry/codegraph
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstaller.test.ts
More file actions
220 lines (186 loc) · 7.33 KB
/
Copy pathinstaller.test.ts
File metadata and controls
220 lines (186 loc) · 7.33 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/**
* Installer Tests
*
* Tests for installer config-writer fixes:
* - readJsonFile error handling
* - writeClaudeMd section replacement
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
// We test the exported functions from config-writer
import {
writeMcpConfig,
writePermissions,
writeClaudeMd,
hasMcpConfig,
hasPermissions,
hasClaudeMdSection,
} from '../src/installer/config-writer';
function createTempDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-installer-test-'));
}
function cleanupTempDir(dir: string): void {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
describe('Installer Config Writer', () => {
let origCwd: string;
let tempDir: string;
beforeEach(() => {
tempDir = createTempDir();
origCwd = process.cwd();
process.chdir(tempDir);
});
afterEach(() => {
process.chdir(origCwd);
cleanupTempDir(tempDir);
});
describe('readJsonFile error handling', () => {
it('should return empty object for non-existent file', () => {
// writeMcpConfig reads .mcp.json - if it doesn't exist, it should create it
writeMcpConfig('local');
const mcpJson = path.join(tempDir, '.mcp.json');
expect(fs.existsSync(mcpJson)).toBe(true);
const content = JSON.parse(fs.readFileSync(mcpJson, 'utf-8'));
expect(content.mcpServers).toBeDefined();
expect(content.mcpServers.codegraph).toBeDefined();
});
it('should handle corrupted JSON by creating backup', () => {
// Create a corrupted .mcp.json
const mcpJson = path.join(tempDir, '.mcp.json');
fs.writeFileSync(mcpJson, '{ this is not valid json !!!');
// Suppress console.warn during test
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
// Should not throw - gracefully handles corruption
writeMcpConfig('local');
// Should have warned
expect(warnSpy).toHaveBeenCalled();
const warnMsg = warnSpy.mock.calls[0][0];
expect(warnMsg).toContain('Warning');
// Backup should exist
expect(fs.existsSync(mcpJson + '.backup')).toBe(true);
// Original backup content should be the corrupted content
const backup = fs.readFileSync(mcpJson + '.backup', 'utf-8');
expect(backup).toContain('this is not valid json');
// New file should be valid JSON with codegraph config
const content = JSON.parse(fs.readFileSync(mcpJson, 'utf-8'));
expect(content.mcpServers.codegraph).toBeDefined();
warnSpy.mockRestore();
});
it('should preserve existing valid config when adding codegraph', () => {
const mcpJson = path.join(tempDir, '.mcp.json');
fs.writeFileSync(mcpJson, JSON.stringify({
mcpServers: { other: { command: 'other-tool' } },
customField: 'preserved',
}, null, 2));
writeMcpConfig('local');
const content = JSON.parse(fs.readFileSync(mcpJson, 'utf-8'));
expect(content.mcpServers.codegraph).toBeDefined();
expect(content.mcpServers.other).toBeDefined();
expect(content.customField).toBe('preserved');
});
});
describe('writeClaudeMd section replacement', () => {
it('should create new CLAUDE.md with markers', () => {
const result = writeClaudeMd('local');
expect(result.created).toBe(true);
const content = fs.readFileSync(path.join(tempDir, '.claude', 'CLAUDE.md'), 'utf-8');
expect(content).toContain('<!-- CODEGRAPH_START -->');
expect(content).toContain('<!-- CODEGRAPH_END -->');
expect(content).toContain('## CodeGraph');
});
it('should replace marked section on update', () => {
// First write
writeClaudeMd('local');
// Modify file to add custom content before and after
const claudeMdPath = path.join(tempDir, '.claude', 'CLAUDE.md');
const original = fs.readFileSync(claudeMdPath, 'utf-8');
const modified = '## My Custom Section\n\nCustom content\n\n' + original + '\n\n## Another Section\n\nMore content\n';
fs.writeFileSync(claudeMdPath, modified);
// Second write should leave the marked block as-is (byte-identical
// body, so result is `created:false, updated:false` — both flags
// are off but the surrounding custom content must survive).
writeClaudeMd('local');
const final = fs.readFileSync(claudeMdPath, 'utf-8');
expect(final).toContain('## My Custom Section');
expect(final).toContain('Custom content');
expect(final).toContain('## Another Section');
expect(final).toContain('More content');
expect(final).toContain('## CodeGraph');
});
it('should use atomic writes (no temp files left behind)', () => {
writeClaudeMd('local');
const claudeDir = path.join(tempDir, '.claude');
const files = fs.readdirSync(claudeDir);
const tmpFiles = files.filter(f => f.includes('.tmp.'));
expect(tmpFiles).toHaveLength(0);
});
it('should not overwrite content after unmarked section with ### subsections', () => {
// Create a CLAUDE.md with an unmarked CodeGraph section that has ### subsections
// followed by another ## section
const claudeDir = path.join(tempDir, '.claude');
fs.mkdirSync(claudeDir, { recursive: true });
const claudeMdPath = path.join(claudeDir, 'CLAUDE.md');
fs.writeFileSync(claudeMdPath, [
'## Pre-existing Section',
'',
'Some content',
'',
'## CodeGraph',
'',
'### Subsection A',
'',
'Old codegraph content',
'',
'### Subsection B',
'',
'More old content',
'',
'## Important Section After',
'',
'This content must not be overwritten!',
'',
].join('\n'));
const result = writeClaudeMd('local');
expect(result.updated).toBe(true);
const final = fs.readFileSync(claudeMdPath, 'utf-8');
// The section after CodeGraph must be preserved
expect(final).toContain('## Important Section After');
expect(final).toContain('This content must not be overwritten!');
// Pre-existing section should also be preserved
expect(final).toContain('## Pre-existing Section');
// New CodeGraph content should be present with markers
expect(final).toContain('<!-- CODEGRAPH_START -->');
expect(final).toContain('<!-- CODEGRAPH_END -->');
});
it('should replace unmarked section without subsections', () => {
const claudeDir = path.join(tempDir, '.claude');
fs.mkdirSync(claudeDir, { recursive: true });
const claudeMdPath = path.join(claudeDir, 'CLAUDE.md');
// Note: regex needs \n before ## CodeGraph, so prefix with another section
fs.writeFileSync(claudeMdPath, [
'## Intro',
'',
'Preamble',
'',
'## CodeGraph',
'',
'Old simple content',
'',
'## Next Section',
'',
'Must be preserved',
'',
].join('\n'));
writeClaudeMd('local');
const final = fs.readFileSync(claudeMdPath, 'utf-8');
expect(final).toContain('<!-- CODEGRAPH_START -->');
expect(final).toContain('## Next Section');
expect(final).toContain('Must be preserved');
expect(final).not.toContain('Old simple content');
});
});
});