import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { discoverArchitecture } from '../../lib/validators/architecture-discovery.mjs'; function setup(structure) { const root = mkdtempSync(join(tmpdir(), 'ultraplan-arch-')); for (const [path, content] of Object.entries(structure)) { const full = join(root, path); mkdirSync(join(full, '..'), { recursive: true }); writeFileSync(full, content); } return root; } test('discoverArchitecture — canonical overview.md found cleanly', () => { const root = setup({ 'architecture/overview.md': '# Overview\n' }); try { const r = discoverArchitecture(root); assert.equal(r.found, true); assert.match(r.overview, /architecture\/overview\.md$/); assert.equal(r.warnings.length, 0); assert.equal(r.firstHeading, 'Overview'); } finally { rmSync(root, { recursive: true, force: true }); } }); test('discoverArchitecture — no architecture dir = not found, no warnings', () => { const root = setup({ 'brief.md': 'b' }); try { const r = discoverArchitecture(root); assert.equal(r.found, false); assert.equal(r.warnings.length, 0); } finally { rmSync(root, { recursive: true, force: true }); } }); test('discoverArchitecture — non-canonical name discovered with warning (drift-WARN)', () => { const root = setup({ 'architecture/architecture-overview.md': '# Drifted\n' }); try { const r = discoverArchitecture(root); assert.equal(r.found, true); assert.ok(r.warnings.find(w => w.code === 'ARCH_NON_CANONICAL_OVERVIEW')); } finally { rmSync(root, { recursive: true, force: true }); } }); test('discoverArchitecture — loose unknown files surfaced as drift warning', () => { const root = setup({ 'architecture/overview.md': '# OK\n', 'architecture/random-note.md': 'x', 'architecture/another.md': 'y', }); try { const r = discoverArchitecture(root); assert.equal(r.found, true); assert.ok(r.warnings.find(w => w.code === 'ARCH_LOOSE_FILES')); assert.equal(r.looseFiles.length, 2); } finally { rmSync(root, { recursive: true, force: true }); } }); test('discoverArchitecture — gaps.md detected when present', () => { const root = setup({ 'architecture/overview.md': '# OK\n', 'architecture/gaps.md': '# Gaps\n', }); try { const r = discoverArchitecture(root); assert.match(r.gaps, /architecture\/gaps\.md$/); } finally { rmSync(root, { recursive: true, force: true }); } }); test('discoverArchitecture — never reads body beyond first heading', () => { const root = setup({ 'architecture/overview.md': '# Overview\n\n## Components\n\nlots of detail that we MUST NOT validate\n', }); try { const r = discoverArchitecture(root); assert.equal(r.firstHeading, 'Overview'); // Validator does not assert on Components section — that's the contract. } finally { rmSync(root, { recursive: true, force: true }); } });