217 lines
7.0 KiB
JavaScript
217 lines
7.0 KiB
JavaScript
/**
|
|
* Tests for CodexSetup.writeSkillArtifacts
|
|
*
|
|
* Validates directory creation, SKILL.md file writing, type filtering,
|
|
* and integration with transformToSkillFormat.
|
|
*
|
|
* Usage: node test/test-codex-write-skills.js
|
|
*/
|
|
|
|
const path = require('node:path');
|
|
const fs = require('fs-extra');
|
|
const os = require('node:os');
|
|
const yaml = require('yaml');
|
|
|
|
// ANSI colors
|
|
const colors = {
|
|
reset: '\u001B[0m',
|
|
green: '\u001B[32m',
|
|
red: '\u001B[31m',
|
|
cyan: '\u001B[36m',
|
|
dim: '\u001B[2m',
|
|
};
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function assert(condition, testName, detail) {
|
|
if (condition) {
|
|
console.log(` ${colors.green}PASS${colors.reset} ${testName}`);
|
|
passed++;
|
|
} else {
|
|
console.log(` ${colors.red}FAIL${colors.reset} ${testName}`);
|
|
if (detail) console.log(` ${colors.dim}${detail}${colors.reset}`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
// Import the class under test
|
|
const { CodexSetup } = require(path.join(__dirname, '..', 'tools', 'cli', 'installers', 'lib', 'ide', 'codex.js'));
|
|
|
|
const setup = new CodexSetup();
|
|
|
|
// Create a temp directory for each test run
|
|
let tmpDir;
|
|
|
|
async function createTmpDir() {
|
|
tmpDir = path.join(os.tmpdir(), `bmad-test-skills-${Date.now()}`);
|
|
await fs.ensureDir(tmpDir);
|
|
return tmpDir;
|
|
}
|
|
|
|
async function cleanTmpDir() {
|
|
if (tmpDir) {
|
|
await fs.remove(tmpDir);
|
|
}
|
|
}
|
|
|
|
async function runTests() {
|
|
console.log(`\n${colors.cyan}CodexSetup.writeSkillArtifacts tests${colors.reset}\n`);
|
|
|
|
// --- Writes a single artifact as a skill directory with SKILL.md ---
|
|
{
|
|
const destDir = await createTmpDir();
|
|
const artifacts = [
|
|
{
|
|
type: 'task',
|
|
relativePath: 'bmm/tasks/create-story.md',
|
|
content: '---\ndescription: Create a user story\n---\n\nStory creation instructions.',
|
|
},
|
|
];
|
|
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
|
|
assert(count === 1, 'single artifact returns count 1');
|
|
|
|
const skillDir = path.join(destDir, 'bmad-bmm-create-story');
|
|
assert(await fs.pathExists(skillDir), 'skill directory created');
|
|
|
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
assert(await fs.pathExists(skillFile), 'SKILL.md file created');
|
|
|
|
const content = await fs.readFile(skillFile, 'utf8');
|
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
assert(fmMatch !== null, 'SKILL.md has frontmatter');
|
|
|
|
const parsed = yaml.parse(fmMatch[1]);
|
|
assert(parsed.name === 'bmad-bmm-create-story', 'name matches skill directory name', `got: ${parsed.name}`);
|
|
assert(parsed.description === 'Create a user story', 'description preserved', `got: ${parsed.description}`);
|
|
assert(content.includes('Story creation instructions.'), 'body content preserved');
|
|
await cleanTmpDir();
|
|
}
|
|
|
|
// --- Filters artifacts by type ---
|
|
{
|
|
const destDir = await createTmpDir();
|
|
const artifacts = [
|
|
{
|
|
type: 'task',
|
|
relativePath: 'bmm/tasks/create-story.md',
|
|
content: '---\ndescription: A task\n---\n\nTask body.',
|
|
},
|
|
{
|
|
type: 'workflow-command',
|
|
relativePath: 'bmm/workflows/plan-project.md',
|
|
content: '---\ndescription: A workflow\n---\n\nWorkflow body.',
|
|
},
|
|
{
|
|
type: 'agent-launcher',
|
|
relativePath: 'bmm/agents/pm.md',
|
|
content: '---\ndescription: An agent\n---\n\nAgent body.',
|
|
},
|
|
];
|
|
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
|
|
assert(count === 1, 'only matching type is written when filtering for task');
|
|
|
|
const entries = await fs.readdir(destDir);
|
|
assert(entries.length === 1, 'only one skill directory created', `got ${entries.length}: ${entries.join(', ')}`);
|
|
assert(entries[0] === 'bmad-bmm-create-story', 'correct artifact was written', `got: ${entries[0]}`);
|
|
await cleanTmpDir();
|
|
}
|
|
|
|
// --- Writes multiple artifacts of the same type ---
|
|
{
|
|
const destDir = await createTmpDir();
|
|
const artifacts = [
|
|
{
|
|
type: 'workflow-command',
|
|
relativePath: 'bmm/workflows/plan-project.md',
|
|
content: '---\ndescription: Plan\n---\n\nPlan body.',
|
|
},
|
|
{
|
|
type: 'workflow-command',
|
|
relativePath: 'core/workflows/review.md',
|
|
content: '---\ndescription: Review\n---\n\nReview body.',
|
|
},
|
|
];
|
|
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'workflow-command');
|
|
assert(count === 2, 'two artifacts written');
|
|
|
|
const entries = new Set((await fs.readdir(destDir)).sort());
|
|
assert(entries.has('bmad-bmm-plan-project'), 'first skill directory exists');
|
|
assert(entries.has('bmad-review'), 'second skill directory exists (core module)');
|
|
await cleanTmpDir();
|
|
}
|
|
|
|
// --- Returns 0 when no artifacts match type ---
|
|
{
|
|
const destDir = await createTmpDir();
|
|
const artifacts = [
|
|
{
|
|
type: 'agent-launcher',
|
|
relativePath: 'bmm/agents/pm.md',
|
|
content: '---\ndescription: An agent\n---\n\nBody.',
|
|
},
|
|
];
|
|
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
|
|
assert(count === 0, 'returns 0 when no types match');
|
|
|
|
const entries = await fs.readdir(destDir);
|
|
assert(entries.length === 0, 'no directories created when no types match');
|
|
await cleanTmpDir();
|
|
}
|
|
|
|
// --- Handles empty artifacts array ---
|
|
{
|
|
const destDir = await createTmpDir();
|
|
const count = await setup.writeSkillArtifacts(destDir, [], 'task');
|
|
assert(count === 0, 'returns 0 for empty artifacts array');
|
|
await cleanTmpDir();
|
|
}
|
|
|
|
// --- Artifacts without type field are always written ---
|
|
{
|
|
const destDir = await createTmpDir();
|
|
const artifacts = [
|
|
{
|
|
relativePath: 'bmm/tasks/no-type.md',
|
|
content: '---\ndescription: No type field\n---\n\nBody.',
|
|
},
|
|
];
|
|
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
|
|
assert(count === 1, 'artifact without type field is written (no filtering)');
|
|
await cleanTmpDir();
|
|
}
|
|
|
|
// --- Content without frontmatter gets minimal frontmatter added ---
|
|
{
|
|
const destDir = await createTmpDir();
|
|
const artifacts = [
|
|
{
|
|
type: 'task',
|
|
relativePath: 'bmm/tasks/bare.md',
|
|
content: 'Just plain content, no frontmatter.',
|
|
},
|
|
];
|
|
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
|
|
assert(count === 1, 'bare content artifact written');
|
|
|
|
const skillFile = path.join(destDir, 'bmad-bmm-bare', 'SKILL.md');
|
|
const content = await fs.readFile(skillFile, 'utf8');
|
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
assert(fmMatch !== null, 'frontmatter added to bare content');
|
|
|
|
const parsed = yaml.parse(fmMatch[1]);
|
|
assert(parsed.name === 'bmad-bmm-bare', 'name set for bare content', `got: ${parsed.name}`);
|
|
assert(content.includes('Just plain content, no frontmatter.'), 'original content preserved');
|
|
await cleanTmpDir();
|
|
}
|
|
|
|
// --- Summary ---
|
|
console.log(`\n${passed} passed, ${failed} failed\n`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
runTests().catch((error) => {
|
|
console.error('Test runner error:', error);
|
|
process.exit(1);
|
|
});
|