mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 06:42:03 +00:00
- Skip platform-specific tests on Windows (CI runs on Linux) - Add tests for json-extractor.ts (96% coverage) - Add tests for cursor-config-manager.ts (100% coverage) - Add tests for cursor-config-service.ts (98.8% coverage) - Exclude CLI integration code from coverage (needs integration tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
360 lines
12 KiB
TypeScript
360 lines
12 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import * as fs from 'fs/promises';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import {
|
|
getGlobalConfigPath,
|
|
getProjectConfigPath,
|
|
readGlobalConfig,
|
|
writeGlobalConfig,
|
|
readProjectConfig,
|
|
writeProjectConfig,
|
|
deleteProjectConfig,
|
|
getEffectivePermissions,
|
|
applyProfileToProject,
|
|
applyProfileGlobally,
|
|
detectProfile,
|
|
generateExampleConfig,
|
|
hasProjectConfig,
|
|
getAvailableProfiles,
|
|
} from '@/services/cursor-config-service.js';
|
|
|
|
vi.mock('fs/promises');
|
|
vi.mock('os');
|
|
|
|
describe('cursor-config-service.ts', () => {
|
|
const mockHomedir = path.join(path.sep, 'home', 'user');
|
|
const testProjectPath = path.join(path.sep, 'tmp', 'test-project');
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(os.homedir).mockReturnValue(mockHomedir);
|
|
delete process.env.XDG_CONFIG_HOME;
|
|
delete process.env.CURSOR_CONFIG_DIR;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.resetAllMocks();
|
|
});
|
|
|
|
describe('getGlobalConfigPath', () => {
|
|
it('should return default path using homedir', () => {
|
|
const result = getGlobalConfigPath();
|
|
expect(result).toContain('.cursor');
|
|
expect(result).toContain('cli-config.json');
|
|
});
|
|
|
|
it('should use CURSOR_CONFIG_DIR if set', () => {
|
|
const customDir = path.join(path.sep, 'custom', 'cursor', 'config');
|
|
process.env.CURSOR_CONFIG_DIR = customDir;
|
|
|
|
const result = getGlobalConfigPath();
|
|
|
|
expect(result).toContain('custom');
|
|
expect(result).toContain('cli-config.json');
|
|
});
|
|
});
|
|
|
|
describe('getProjectConfigPath', () => {
|
|
it('should return project config path', () => {
|
|
const result = getProjectConfigPath(testProjectPath);
|
|
expect(result).toContain('.cursor');
|
|
expect(result).toContain('cli.json');
|
|
});
|
|
});
|
|
|
|
describe('readGlobalConfig', () => {
|
|
it('should read and parse global config', async () => {
|
|
const mockConfig = { version: 1, permissions: { allow: ['*'], deny: [] } };
|
|
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig));
|
|
|
|
const result = await readGlobalConfig();
|
|
|
|
expect(result).toEqual(mockConfig);
|
|
expect(fs.readFile).toHaveBeenCalledWith(expect.stringContaining('cli-config.json'), 'utf-8');
|
|
});
|
|
|
|
it('should return null if file does not exist', async () => {
|
|
const error = new Error('ENOENT') as NodeJS.ErrnoException;
|
|
error.code = 'ENOENT';
|
|
vi.mocked(fs.readFile).mockRejectedValue(error);
|
|
|
|
const result = await readGlobalConfig();
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should throw on other errors', async () => {
|
|
const error = new Error('Permission denied') as NodeJS.ErrnoException;
|
|
error.code = 'EACCES';
|
|
vi.mocked(fs.readFile).mockRejectedValue(error);
|
|
|
|
await expect(readGlobalConfig()).rejects.toThrow('Permission denied');
|
|
});
|
|
});
|
|
|
|
describe('writeGlobalConfig', () => {
|
|
it('should create directory and write config', async () => {
|
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
|
|
const config = { version: 1, permissions: { allow: ['*'], deny: [] } };
|
|
await writeGlobalConfig(config);
|
|
|
|
expect(fs.mkdir).toHaveBeenCalledWith(expect.stringContaining('.cursor'), {
|
|
recursive: true,
|
|
});
|
|
expect(fs.writeFile).toHaveBeenCalledWith(
|
|
expect.stringContaining('cli-config.json'),
|
|
expect.any(String)
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('readProjectConfig', () => {
|
|
it('should read and parse project config', async () => {
|
|
const mockConfig = { version: 1, permissions: { allow: ['read'], deny: ['write'] } };
|
|
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig));
|
|
|
|
const result = await readProjectConfig(testProjectPath);
|
|
|
|
expect(result).toEqual(mockConfig);
|
|
expect(fs.readFile).toHaveBeenCalledWith(expect.stringContaining('cli.json'), 'utf-8');
|
|
});
|
|
|
|
it('should return null if file does not exist', async () => {
|
|
const error = new Error('ENOENT') as NodeJS.ErrnoException;
|
|
error.code = 'ENOENT';
|
|
vi.mocked(fs.readFile).mockRejectedValue(error);
|
|
|
|
const result = await readProjectConfig(testProjectPath);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should throw on other errors', async () => {
|
|
const error = new Error('Read error') as NodeJS.ErrnoException;
|
|
error.code = 'EIO';
|
|
vi.mocked(fs.readFile).mockRejectedValue(error);
|
|
|
|
await expect(readProjectConfig(testProjectPath)).rejects.toThrow('Read error');
|
|
});
|
|
});
|
|
|
|
describe('writeProjectConfig', () => {
|
|
it('should write project config with only permissions', async () => {
|
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
|
|
const config = { version: 1, permissions: { allow: ['read'], deny: ['write'] } };
|
|
await writeProjectConfig(testProjectPath, config);
|
|
|
|
expect(fs.mkdir).toHaveBeenCalledWith(expect.stringContaining('.cursor'), {
|
|
recursive: true,
|
|
});
|
|
|
|
// Check that only permissions is written (no version)
|
|
const writtenContent = vi.mocked(fs.writeFile).mock.calls[0][1] as string;
|
|
const parsed = JSON.parse(writtenContent);
|
|
expect(parsed).toEqual({ permissions: { allow: ['read'], deny: ['write'] } });
|
|
expect(parsed.version).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('deleteProjectConfig', () => {
|
|
it('should delete project config', async () => {
|
|
vi.mocked(fs.unlink).mockResolvedValue(undefined);
|
|
|
|
await deleteProjectConfig(testProjectPath);
|
|
|
|
expect(fs.unlink).toHaveBeenCalledWith(expect.stringContaining('cli.json'));
|
|
});
|
|
|
|
it('should not throw if file does not exist', async () => {
|
|
const error = new Error('ENOENT') as NodeJS.ErrnoException;
|
|
error.code = 'ENOENT';
|
|
vi.mocked(fs.unlink).mockRejectedValue(error);
|
|
|
|
await expect(deleteProjectConfig(testProjectPath)).resolves.not.toThrow();
|
|
});
|
|
|
|
it('should throw on other errors', async () => {
|
|
const error = new Error('Permission denied') as NodeJS.ErrnoException;
|
|
error.code = 'EACCES';
|
|
vi.mocked(fs.unlink).mockRejectedValue(error);
|
|
|
|
await expect(deleteProjectConfig(testProjectPath)).rejects.toThrow('Permission denied');
|
|
});
|
|
});
|
|
|
|
describe('getEffectivePermissions', () => {
|
|
it('should return project permissions if available', async () => {
|
|
const projectPerms = { allow: ['read'], deny: ['write'] };
|
|
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify({ permissions: projectPerms }));
|
|
|
|
const result = await getEffectivePermissions(testProjectPath);
|
|
|
|
expect(result).toEqual(projectPerms);
|
|
});
|
|
|
|
it('should fall back to global permissions', async () => {
|
|
const globalPerms = { allow: ['*'], deny: [] };
|
|
const error = new Error('ENOENT') as NodeJS.ErrnoException;
|
|
error.code = 'ENOENT';
|
|
vi.mocked(fs.readFile)
|
|
.mockRejectedValueOnce(error) // Project config not found
|
|
.mockResolvedValueOnce(JSON.stringify({ permissions: globalPerms }));
|
|
|
|
const result = await getEffectivePermissions(testProjectPath);
|
|
|
|
expect(result).toEqual(globalPerms);
|
|
});
|
|
|
|
it('should return null if no config exists', async () => {
|
|
const error = new Error('ENOENT') as NodeJS.ErrnoException;
|
|
error.code = 'ENOENT';
|
|
vi.mocked(fs.readFile).mockRejectedValue(error);
|
|
|
|
const result = await getEffectivePermissions(testProjectPath);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return global permissions if no project path provided', async () => {
|
|
const globalPerms = { allow: ['*'], deny: [] };
|
|
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({ permissions: globalPerms }));
|
|
|
|
const result = await getEffectivePermissions();
|
|
|
|
expect(result).toEqual(globalPerms);
|
|
});
|
|
});
|
|
|
|
describe('applyProfileToProject', () => {
|
|
it('should write development profile to project', async () => {
|
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
|
|
await applyProfileToProject(testProjectPath, 'development');
|
|
|
|
expect(fs.writeFile).toHaveBeenCalled();
|
|
const writtenContent = vi.mocked(fs.writeFile).mock.calls[0][1] as string;
|
|
const parsed = JSON.parse(writtenContent);
|
|
expect(parsed.permissions).toBeDefined();
|
|
});
|
|
|
|
it('should throw on unknown profile', async () => {
|
|
await expect(applyProfileToProject(testProjectPath, 'unknown' as any)).rejects.toThrow(
|
|
'Unknown permission profile: unknown'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('applyProfileGlobally', () => {
|
|
it('should write profile to global config', async () => {
|
|
const error = new Error('ENOENT') as NodeJS.ErrnoException;
|
|
error.code = 'ENOENT';
|
|
vi.mocked(fs.readFile).mockRejectedValue(error); // No existing config
|
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
|
|
await applyProfileGlobally('strict');
|
|
|
|
expect(fs.writeFile).toHaveBeenCalled();
|
|
const writtenContent = vi.mocked(fs.writeFile).mock.calls[0][1] as string;
|
|
const parsed = JSON.parse(writtenContent);
|
|
expect(parsed.version).toBe(1);
|
|
expect(parsed.permissions).toBeDefined();
|
|
});
|
|
|
|
it('should preserve existing settings', async () => {
|
|
const existingConfig = { version: 1, someOtherSetting: 'value' };
|
|
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(existingConfig));
|
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
|
|
await applyProfileGlobally('development');
|
|
|
|
const writtenContent = vi.mocked(fs.writeFile).mock.calls[0][1] as string;
|
|
const parsed = JSON.parse(writtenContent);
|
|
expect(parsed.someOtherSetting).toBe('value');
|
|
});
|
|
|
|
it('should throw on unknown profile', async () => {
|
|
await expect(applyProfileGlobally('unknown' as any)).rejects.toThrow(
|
|
'Unknown permission profile: unknown'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('detectProfile', () => {
|
|
it('should return null for null permissions', () => {
|
|
expect(detectProfile(null)).toBeNull();
|
|
});
|
|
|
|
it('should return custom for non-matching permissions', () => {
|
|
const customPerms = { allow: ['some-custom'], deny: ['other-custom'] };
|
|
const result = detectProfile(customPerms);
|
|
expect(result).toBe('custom');
|
|
});
|
|
|
|
it('should detect matching profile', () => {
|
|
// Get a profile's permissions and verify detection works
|
|
const profiles = getAvailableProfiles();
|
|
if (profiles.length > 0) {
|
|
const profile = profiles[0];
|
|
const result = detectProfile(profile.permissions);
|
|
expect(result).toBe(profile.id);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('generateExampleConfig', () => {
|
|
it('should generate development profile config by default', () => {
|
|
const config = generateExampleConfig();
|
|
const parsed = JSON.parse(config);
|
|
|
|
expect(parsed.version).toBe(1);
|
|
expect(parsed.permissions).toBeDefined();
|
|
});
|
|
|
|
it('should generate specified profile config', () => {
|
|
const config = generateExampleConfig('strict');
|
|
const parsed = JSON.parse(config);
|
|
|
|
expect(parsed.version).toBe(1);
|
|
expect(parsed.permissions).toBeDefined();
|
|
expect(parsed.permissions.deny).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('hasProjectConfig', () => {
|
|
it('should return true if config exists', async () => {
|
|
vi.mocked(fs.access).mockResolvedValue(undefined);
|
|
|
|
const result = await hasProjectConfig(testProjectPath);
|
|
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return false if config does not exist', async () => {
|
|
vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT'));
|
|
|
|
const result = await hasProjectConfig(testProjectPath);
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getAvailableProfiles', () => {
|
|
it('should return all available profiles', () => {
|
|
const profiles = getAvailableProfiles();
|
|
|
|
expect(Array.isArray(profiles)).toBe(true);
|
|
expect(profiles.length).toBeGreaterThan(0);
|
|
expect(profiles.some((p) => p.id === 'strict')).toBe(true);
|
|
expect(profiles.some((p) => p.id === 'development')).toBe(true);
|
|
});
|
|
});
|
|
});
|