'use strict';
/**
* Unit tests for `msd-tools phase tdd-applicable` (#4273, Phase 1 of epic #4272).
*
* Matrix rows referenced below are from
* .msd/phase/feat-4273-tdd-applicable-query-verb/50-test-matrix.md
*/
const path = require('node:path');
const fs = require('node:fs');
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const { createTempProject, cleanup, runMsdTools } = require('./helpers.cjs');
function writePlan(dir, relPath, content) {
const full = path.join(dir, relPath);
fs.mkdirSync(path.dirname(full), { recursive: true });
fs.writeFileSync(full, content, 'utf8');
return full;
}
describe('phase.tdd-applicable', () => {
test('row 9 — --cli-flag wins regardless of plan content', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', `---
type: standard
---
a
`);
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath, '--cli-flag'], tmpDir);
assert.equal(result.success, true, result.error);
const parsed = JSON.parse(result.output);
assert.equal(parsed.applicable, true);
assert.equal(parsed.source, 'cli_flag');
});
test('row 10 — plan frontmatter type: tdd, no flag', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', `---
type: tdd
---
a
`);
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath], tmpDir);
assert.equal(result.success, true, result.error);
const parsed = JSON.parse(result.output);
assert.equal(parsed.applicable, true);
assert.equal(parsed.source, 'plan_frontmatter');
});
test('row 11 — mixed-mode plan: standard plan, one tdd="true" task (the #4265 shape)', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', `---
type: standard
---
a
b
`);
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath], tmpDir);
assert.equal(result.success, true, result.error);
const parsed = JSON.parse(result.output);
assert.equal(parsed.applicable, true);
assert.equal(parsed.source, 'task_attribute');
});
test('row 12 — config.workflow.tdd_mode true, nothing else set', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', `a\n`);
const configPath = path.join(tmpDir, '.planning', 'config.json');
fs.writeFileSync(configPath, JSON.stringify({ workflow: { tdd_mode: true } }), 'utf8');
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath], tmpDir);
assert.equal(result.success, true, result.error);
const parsed = JSON.parse(result.output);
assert.equal(parsed.applicable, true);
assert.equal(parsed.source, 'config');
});
test('row 13 — nothing set: applicable false, source none', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', `a\n`);
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath], tmpDir);
assert.equal(result.success, true, result.error);
const parsed = JSON.parse(result.output);
assert.equal(parsed.applicable, false);
assert.equal(parsed.source, 'none');
});
test('row 14 — missing plan file: non-zero exit, no silent false', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const missingPath = path.join(tmpDir, '.planning', 'phases', '01-x', 'does-not-exist-PLAN.md');
const result = runMsdTools(['query', 'phase.tdd-applicable', missingPath], tmpDir);
assert.equal(result.success, false);
});
test('row 15 — missing plan-id argument: USAGE error, non-zero exit', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const result = runMsdTools(['query', 'phase.tdd-applicable'], tmpDir);
assert.equal(result.success, false);
});
test('row 16 — independence: --cli-flag wins over type: standard', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', `---
type: standard
---
a
`);
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath, '--cli-flag'], tmpDir);
const parsed = JSON.parse(result.output);
assert.equal(parsed.source, 'cli_flag');
});
test('row 17 — independence: type: tdd wins over a task with no tdd attribute (task tier never consulted)', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', `---
type: tdd
---
a
`);
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath], tmpDir);
const parsed = JSON.parse(result.output);
assert.equal(parsed.source, 'plan_frontmatter');
});
test('row 18 — JSON output shape names every source field', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', `a\n`);
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath], tmpDir);
assert.equal(result.success, true, result.error);
const parsed = JSON.parse(result.output);
assert.ok('applicable' in parsed);
assert.ok('source' in parsed);
assert.ok('plan_type' in parsed);
assert.ok('config_tdd_mode' in parsed);
assert.ok('cli_flag_present' in parsed);
});
test('row 19 — empty plan file (no frontmatter, no tasks) falls through, not an error', (t) => {
const tmpDir = createTempProject();
t.after(() => cleanup(tmpDir));
const planPath = writePlan(tmpDir, '.planning/phases/01-x/01-PLAN.md', '');
const result = runMsdTools(['query', 'phase.tdd-applicable', planPath], tmpDir);
assert.equal(result.success, true, result.error);
const parsed = JSON.parse(result.output);
assert.equal(parsed.applicable, false);
assert.equal(parsed.source, 'none');
});
});