import { describe, it, expect, afterEach, vi } from 'vitest'; import fs from 'fs'; import os from 'os'; import path from 'path'; import { ComposeCommand } from '../composeCommand'; let tmp = ''; afterEach(() => { if (tmp && fs.existsSync(tmp)) fs.rmSync(tmp, { recursive: true, force: true }); tmp = ''; vi.restoreAllMocks(); }); function write(p: string, content: string) { fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, content); } describe('compose validate subcommand', () => { it('is registered as a subcommand of compose (alongside description)', () => { const names = ComposeCommand() .commands.map((c) => c.name()) .sort(); expect(names).toContain('validate'); expect(names).toContain('description'); }); it('composes the folder and writes output.yaml + the validation report; exits 0 on findings', async () => { tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'val-cmd-')); write(path.join(tmp, 'proj/partials/main.yaml'), `externals: []\n`); // a webhook with a dangling objectId → the report has a CRITICAL, but validate is advisory write( path.join(tmp, 'proj/partials/W.yaml'), `connectors:\n - id: C\nwebhooks:\n - id: W\n connectionId: C\n targets:\n t:\n objectIds:\n - GHOST\n`, ); const logs: string[] = []; vi.spyOn(console, 'log').mockImplementation((...a) => { logs.push(a.join(' ')); }); await ComposeCommand().parseAsync(['validate', path.join(tmp, 'proj')], { from: 'user', }); expect(fs.existsSync(path.join(tmp, 'proj/output.yaml'))).toBe(true); expect( fs.existsSync(path.join(tmp, 'proj/output.validation.report.md')), ).toBe(true); // the summary line was surfaced for CI expect(logs.some((l) => l.startsWith('Validation:'))).toBe(true); }); });