@mbc-cqrs-serverless/cli
Version:
a CLI to get started with MBC CQRS serverless framework
352 lines (351 loc) • 17.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const install_skills_action_1 = __importStar(require("./install-skills.action"));
jest.mock('fs');
jest.mock('os');
const fs_1 = require("fs");
const os_1 = __importDefault(require("os"));
const mockExistsSync = fs_1.existsSync;
const mockMkdirSync = fs_1.mkdirSync;
const mockCpSync = fs_1.cpSync;
const mockReaddirSync = fs_1.readdirSync;
const mockReadFileSync = fs_1.readFileSync;
const mockWriteFileSync = fs_1.writeFileSync;
describe('Install Skills Action', () => {
const mockCommand = {
name: () => 'install-skills',
opts: () => ({}),
};
beforeEach(() => {
jest.clearAllMocks();
os_1.default.homedir.mockReturnValue('/home/user');
});
describe('getSkillsSourcePath', () => {
it('should return the path to mcp-server skills directory', () => {
// Use real path module, mock only existsSync to return true for skills path
mockExistsSync.mockReturnValue(true);
const sourcePath = (0, install_skills_action_1.getSkillsSourcePath)();
expect(sourcePath).toContain('skills');
});
it('should try multiple paths to find skills directory', () => {
// First call returns false, second returns true
mockExistsSync
.mockReturnValueOnce(false)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true);
const sourcePath = (0, install_skills_action_1.getSkillsSourcePath)();
expect(sourcePath).toContain('skills');
expect(mockExistsSync).toHaveBeenCalledTimes(3);
});
it('should fallback to require.resolve when file paths not found', () => {
// All file paths return false, but require.resolve finds the package
mockExistsSync.mockReturnValue(false);
// In monorepo environment, require.resolve will find mcp-server
// This tests the fallback path (lines 62-64)
const sourcePath = (0, install_skills_action_1.getSkillsSourcePath)();
expect(sourcePath).toContain('skills');
});
});
describe('getPersonalSkillsPath', () => {
it('should return ~/.claude/skills/ path', () => {
const personalPath = (0, install_skills_action_1.getPersonalSkillsPath)();
expect(personalPath).toContain('/home/user');
expect(personalPath).toContain('.claude');
expect(personalPath).toContain('skills');
});
});
describe('getProjectSkillsPath', () => {
it('should return .claude/skills/ path in current directory', () => {
const projectPath = (0, install_skills_action_1.getProjectSkillsPath)();
expect(projectPath).toContain('.claude/skills');
});
});
describe('copySkills', () => {
const mockSourcePath = '/source/skills';
const mockDestPath = '/dest/skills';
beforeEach(() => {
mockReaddirSync.mockReturnValue([
{ name: 'mbc-generate', isDirectory: () => true },
{ name: 'mbc-review', isDirectory: () => true },
{ name: 'mbc-migrate', isDirectory: () => true },
{ name: 'mbc-debug', isDirectory: () => true },
]);
});
it('should create destination directory if it does not exist', () => {
mockExistsSync.mockReturnValue(false);
(0, install_skills_action_1.copySkills)(mockSourcePath, mockDestPath);
expect(mockMkdirSync).toHaveBeenCalledWith(mockDestPath, {
recursive: true,
});
});
it('should not create destination directory if it exists', () => {
mockExistsSync.mockReturnValue(true);
(0, install_skills_action_1.copySkills)(mockSourcePath, mockDestPath);
expect(mockMkdirSync).not.toHaveBeenCalled();
});
it('should copy all skill directories', () => {
mockExistsSync.mockReturnValue(true);
(0, install_skills_action_1.copySkills)(mockSourcePath, mockDestPath);
expect(mockCpSync).toHaveBeenCalledTimes(4);
expect(mockCpSync).toHaveBeenCalledWith(expect.stringContaining('mbc-generate'), expect.stringContaining('mbc-generate'), { recursive: true });
expect(mockCpSync).toHaveBeenCalledWith(expect.stringContaining('mbc-review'), expect.stringContaining('mbc-review'), { recursive: true });
expect(mockCpSync).toHaveBeenCalledWith(expect.stringContaining('mbc-migrate'), expect.stringContaining('mbc-migrate'), { recursive: true });
expect(mockCpSync).toHaveBeenCalledWith(expect.stringContaining('mbc-debug'), expect.stringContaining('mbc-debug'), { recursive: true });
});
it('should return the list of copied skills', () => {
mockExistsSync.mockReturnValue(true);
const copiedSkills = (0, install_skills_action_1.copySkills)(mockSourcePath, mockDestPath);
expect(copiedSkills).toEqual([
'mbc-generate',
'mbc-review',
'mbc-migrate',
'mbc-debug',
]);
});
});
describe('installSkillsAction', () => {
beforeEach(() => {
mockExistsSync.mockReturnValue(true);
mockReaddirSync.mockReturnValue([
{ name: 'mbc-generate', isDirectory: () => true },
{ name: 'mbc-review', isDirectory: () => true },
{ name: 'mbc-migrate', isDirectory: () => true },
{ name: 'mbc-debug', isDirectory: () => true },
]);
});
describe('Personal installation (default)', () => {
it('should install skills to personal directory by default', async () => {
const options = {};
await (0, install_skills_action_1.default)(options, mockCommand);
expect(mockCpSync).toHaveBeenCalled();
});
it('should install to ~/.claude/skills/', async () => {
const options = {};
await (0, install_skills_action_1.default)(options, mockCommand);
expect(mockCpSync).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('/home/user/.claude/skills'), expect.any(Object));
});
});
describe('Project installation (--project flag)', () => {
it('should install skills to project directory when --project is specified', async () => {
const options = { project: true };
await (0, install_skills_action_1.default)(options, mockCommand);
expect(mockCpSync).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('.claude/skills'), expect.any(Object));
});
});
describe('Error handling', () => {
it('should handle missing source directory after finding path', async () => {
// Source path is found, but verification fails
mockExistsSync
.mockReturnValueOnce(true) // getSkillsSourcePath finds it
.mockReturnValueOnce(false); // but verification fails
await expect((0, install_skills_action_1.default)({}, mockCommand)).rejects.toThrow('Skills source directory not found');
});
it('should handle undefined command', async () => {
mockExistsSync.mockReturnValue(true);
await expect((0, install_skills_action_1.default)({}, undefined)).rejects.toThrow('Command is required');
});
});
describe('Force option', () => {
it('should overwrite existing skills when --force is specified', async () => {
const options = { force: true };
mockExistsSync.mockReturnValue(true);
await (0, install_skills_action_1.default)(options, mockCommand);
expect(mockCpSync).toHaveBeenCalled();
});
});
describe('List option', () => {
it('should list available skills when --list is specified', async () => {
const options = { list: true };
await (0, install_skills_action_1.default)(options, mockCommand);
// Should not copy when just listing
expect(mockCpSync).not.toHaveBeenCalled();
});
});
});
describe('Integration scenarios', () => {
beforeEach(() => {
mockExistsSync.mockReturnValue(true);
mockReaddirSync.mockReturnValue([
{ name: 'mbc-generate', isDirectory: () => true },
{ name: 'mbc-review', isDirectory: () => true },
{ name: 'mbc-migrate', isDirectory: () => true },
{ name: 'mbc-debug', isDirectory: () => true },
]);
});
it('should handle installation to non-existent directory', async () => {
mockExistsSync.mockImplementation((p) => {
if (p.includes('mcp-server/skills'))
return true;
return false;
});
await (0, install_skills_action_1.default)({}, mockCommand);
expect(mockMkdirSync).toHaveBeenCalled();
});
it('should handle multiple concurrent installations', async () => {
const promises = [
(0, install_skills_action_1.default)({}, mockCommand),
(0, install_skills_action_1.default)({ project: true }, mockCommand),
];
await expect(Promise.all(promises)).resolves.not.toThrow();
});
});
describe('Version management', () => {
describe('getInstalledVersion', () => {
it('should return null if version file does not exist', () => {
mockExistsSync.mockReturnValue(false);
const version = (0, install_skills_action_1.getInstalledVersion)('/dest/skills');
expect(version).toBeNull();
});
it('should return version from version file', () => {
mockExistsSync.mockReturnValue(true);
mockReadFileSync.mockReturnValue('1.0.24');
const version = (0, install_skills_action_1.getInstalledVersion)('/dest/skills');
expect(version).toBe('1.0.24');
});
it('should trim whitespace from version', () => {
mockExistsSync.mockReturnValue(true);
mockReadFileSync.mockReturnValue(' 1.0.24\n ');
const version = (0, install_skills_action_1.getInstalledVersion)('/dest/skills');
expect(version).toBe('1.0.24');
});
it('should return null on read error', () => {
mockExistsSync.mockReturnValue(true);
mockReadFileSync.mockImplementation(() => {
throw new Error('Read error');
});
const version = (0, install_skills_action_1.getInstalledVersion)('/dest/skills');
expect(version).toBeNull();
});
});
describe('getPackageVersion', () => {
it('should return version from package.json', () => {
mockExistsSync.mockReturnValue(true);
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '1.0.25' }));
const version = (0, install_skills_action_1.getPackageVersion)('/source/skills');
expect(version).toBe('1.0.25');
});
it('should return null if package.json does not exist', () => {
mockExistsSync.mockReturnValue(false);
const version = (0, install_skills_action_1.getPackageVersion)('/source/skills');
expect(version).toBeNull();
});
it('should return null on parse error', () => {
mockExistsSync.mockReturnValue(true);
mockReadFileSync.mockReturnValue('invalid json');
const version = (0, install_skills_action_1.getPackageVersion)('/source/skills');
expect(version).toBeNull();
});
});
describe('writeVersionFile', () => {
it('should write version to file', () => {
(0, install_skills_action_1.writeVersionFile)('/dest/skills', '1.0.25');
expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining(install_skills_action_1.VERSION_FILE_NAME), '1.0.25', 'utf-8');
});
});
describe('Check option', () => {
beforeEach(() => {
mockReaddirSync.mockReturnValue([
{ name: 'mbc-generate', isDirectory: () => true },
{ name: 'mbc-review', isDirectory: () => true },
]);
});
it('should check for updates when --check is specified', async () => {
const options = { check: true };
mockExistsSync.mockReturnValue(true);
mockReadFileSync
.mockReturnValueOnce('1.0.24') // installed version
.mockReturnValueOnce(JSON.stringify({ version: '1.0.25' })); // package version
await (0, install_skills_action_1.default)(options, mockCommand);
// Should not copy when just checking
expect(mockCpSync).not.toHaveBeenCalled();
});
it('should report up-to-date when versions match', async () => {
const options = { check: true };
mockExistsSync.mockReturnValue(true);
mockReadFileSync
.mockReturnValueOnce('1.0.25') // installed version
.mockReturnValueOnce(JSON.stringify({ version: '1.0.25' })); // package version
await (0, install_skills_action_1.default)(options, mockCommand);
expect(mockCpSync).not.toHaveBeenCalled();
});
it('should report not installed when version file does not exist', async () => {
const options = { check: true };
// First call for source path check, second for version file check (false)
mockExistsSync.mockImplementation((p) => {
if (p.includes('mcp-server/skills') || p.includes('mcp-server\\skills')) {
return true;
}
if (p.includes(install_skills_action_1.VERSION_FILE_NAME)) {
return false;
}
return true;
});
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '1.0.25' }));
await (0, install_skills_action_1.default)(options, mockCommand);
expect(mockCpSync).not.toHaveBeenCalled();
});
it('should handle unknown package version', async () => {
const options = { check: true };
mockExistsSync.mockImplementation((p) => {
// Package.json does not exist
if (p.includes('package.json')) {
return false;
}
return true;
});
mockReadFileSync.mockReturnValue('1.0.24'); // installed version only
await (0, install_skills_action_1.default)(options, mockCommand);
expect(mockCpSync).not.toHaveBeenCalled();
});
});
describe('Version file creation during installation', () => {
beforeEach(() => {
mockExistsSync.mockReturnValue(true);
mockReaddirSync.mockReturnValue([
{ name: 'mbc-generate', isDirectory: () => true },
]);
mockReadFileSync.mockReturnValue(JSON.stringify({ version: '1.0.25' }));
});
it('should create version file after installation', async () => {
await (0, install_skills_action_1.default)({}, mockCommand);
expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining(install_skills_action_1.VERSION_FILE_NAME), '1.0.25', 'utf-8');
});
});
});
});