Skip to content

Commit a1aa485

Browse files
lishuceoclaude
andcommitted
fix: export injectLocalSettings 并重写测试调用真实实现
Review 指出测试重新实现了逻辑而非调用真实函数,导致无法检测漂移。 现在导出 injectLocalSettings 并在测试中直接 import 调用,覆盖: - 文件创建 + 白名单内容验证 - .claude 目录不存在时自动创建 - 已存在文件不被覆盖 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 17ec20d commit a1aa485

2 files changed

Lines changed: 29 additions & 61 deletions

File tree

src/__tests__/workspace-settings-injection.test.ts

Lines changed: 28 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,15 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node
33
import { resolve } from 'node:path';
44
import { tmpdir } from 'node:os';
55
import { randomBytes } from 'node:crypto';
6-
7-
/**
8-
* Test the .claude/settings.local.json injection behavior in workspace setup.
9-
* Since injectLocalSettings is not exported, we replicate its logic here to verify the contract.
10-
*/
6+
import { injectLocalSettings } from '../workspace/manager.js';
117

128
function createTempWorkspace(): string {
139
const dir = resolve(tmpdir(), `ws-test-${randomBytes(4).toString('hex')}`);
1410
mkdirSync(dir, { recursive: true });
1511
return dir;
1612
}
1713

18-
describe('workspace settings.local.json injection', () => {
14+
describe('injectLocalSettings', () => {
1915
let workspacePath: string;
2016

2117
beforeEach(() => {
@@ -26,80 +22,52 @@ describe('workspace settings.local.json injection', () => {
2622
rmSync(workspacePath, { recursive: true, force: true });
2723
});
2824

29-
it('should create .claude/settings.local.json with permission whitelist', async () => {
30-
// Simulate what injectLocalSettings does
31-
const claudeDir = resolve(workspacePath, '.claude');
32-
const settingsPath = resolve(claudeDir, 'settings.local.json');
33-
34-
// Import the manager module to call setupWorkspace indirectly won't work without git,
35-
// so we verify the contract: after injection, the file should contain expected permissions
36-
mkdirSync(claudeDir, { recursive: true });
37-
38-
const settings = {
39-
permissions: {
40-
allow: [
41-
'Bash(git *)',
42-
'Bash(npm *)',
43-
'Bash(npx *)',
44-
'Bash(node *)',
45-
'Bash(cat *)',
46-
'Bash(ls *)',
47-
'Bash(find *)',
48-
'Bash(grep *)',
49-
'Bash(echo *)',
50-
'Bash(pwd)',
51-
'Bash(which *)',
52-
'Bash(gh *)',
53-
],
54-
},
55-
};
56-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
25+
it('should create .claude/settings.local.json with permission whitelist', () => {
26+
injectLocalSettings(workspacePath);
5727

28+
const settingsPath = resolve(workspacePath, '.claude', 'settings.local.json');
5829
expect(existsSync(settingsPath)).toBe(true);
30+
5931
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
6032
expect(content.permissions.allow).toContain('Bash(git *)');
6133
expect(content.permissions.allow).toContain('Bash(npm *)');
34+
expect(content.permissions.allow).toContain('Bash(npx *)');
6235
expect(content.permissions.allow).toContain('Bash(gh *)');
6336
});
6437

38+
it('should create .claude directory if it does not exist', () => {
39+
const claudeDir = resolve(workspacePath, '.claude');
40+
expect(existsSync(claudeDir)).toBe(false);
41+
42+
injectLocalSettings(workspacePath);
43+
44+
expect(existsSync(claudeDir)).toBe(true);
45+
expect(existsSync(resolve(claudeDir, 'settings.local.json'))).toBe(true);
46+
});
47+
6548
it('should not overwrite existing settings.local.json', () => {
6649
const claudeDir = resolve(workspacePath, '.claude');
6750
const settingsPath = resolve(claudeDir, 'settings.local.json');
6851

69-
// Pre-create a custom settings file
7052
mkdirSync(claudeDir, { recursive: true });
7153
const customSettings = { permissions: { allow: ['Bash(custom *)'] } };
7254
writeFileSync(settingsPath, JSON.stringify(customSettings));
7355

74-
// Verify existing file is preserved (injectLocalSettings skips if exists)
75-
expect(existsSync(settingsPath)).toBe(true);
56+
injectLocalSettings(workspacePath);
57+
7658
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
7759
expect(content.permissions.allow).toEqual(['Bash(custom *)']);
7860
});
7961

80-
it('should include git commands critical for bot workflow', () => {
81-
const requiredPatterns = ['Bash(git *)', 'Bash(npm *)', 'Bash(npx *)'];
82-
const settings = {
83-
permissions: {
84-
allow: [
85-
'Bash(git *)',
86-
'Bash(npm *)',
87-
'Bash(npx *)',
88-
'Bash(node *)',
89-
'Bash(cat *)',
90-
'Bash(ls *)',
91-
'Bash(find *)',
92-
'Bash(grep *)',
93-
'Bash(echo *)',
94-
'Bash(pwd)',
95-
'Bash(which *)',
96-
'Bash(gh *)',
97-
],
98-
},
99-
};
100-
101-
for (const pattern of requiredPatterns) {
102-
expect(settings.permissions.allow).toContain(pattern);
62+
it('should include all critical bot workflow commands', () => {
63+
injectLocalSettings(workspacePath);
64+
65+
const settingsPath = resolve(workspacePath, '.claude', 'settings.local.json');
66+
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
67+
68+
const required = ['Bash(git *)', 'Bash(npm *)', 'Bash(npx *)', 'Bash(node *)', 'Bash(gh *)'];
69+
for (const pattern of required) {
70+
expect(content.permissions.allow).toContain(pattern);
10371
}
10472
});
10573
});

src/workspace/manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ export function parseRepoNameFromWorkspaceDir(dirName: string): string {
234234
* 注入 .claude/settings.local.json 覆盖项目级权限限制。
235235
* 确保 bot 无终端场景下 git/npm/npx 等基础命令不会触发交互式权限审批。
236236
*/
237-
function injectLocalSettings(workspacePath: string): void {
237+
export function injectLocalSettings(workspacePath: string): void {
238238
const claudeDir = resolve(workspacePath, '.claude');
239239
const settingsPath = resolve(claudeDir, 'settings.local.json');
240240

0 commit comments

Comments
 (0)