/** * scaffold-business — the guarded legacy sweep (lib/guarded-rm) through the * REAL index.ts: the pre-classification copies (`.Application//…`, * `.Infrastructure/Services//…`) are removed on re-run and traced * in `data.filesRemoved`, an `@customised` legacy file survives and is warned, * and nothing is reported when nothing was there (audit 2026-09-24, A-WARN-11). */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { runCli } from '../../../../../../lib/__tests__/helpers/run-cli.js' import { generate, legacyPaths } from '../generate.js' import { ScaffoldBusinessInputSchema } from '../types.js' import { CUSTOMISED_HEAD, INDEX, entrySpec, norm, plant } from './fixture.js' let root: string beforeEach(() => { root = mkdtempSync(join(tmpdir(), 'ss-business-legacy-')) }) afterEach(() => rmSync(root, { recursive: true, force: true })) function legacyOf(projectPath: string) { const parsed = ScaffoldBusinessInputSchema.parse(entrySpec(projectPath)) const legacy = legacyPaths(parsed) const iface = legacy.find(p => p.endsWith('/Interfaces/IEmployeeService.cs')) const service = legacy.find(p => p.endsWith('/EmployeeService.cs') && p.includes('/Services/')) return { parsed, legacy, iface: iface!, service: service! } } function run(projectPath: string) { const r = runCli(INDEX, ['--spec', JSON.stringify(entrySpec(projectPath))]) expect(r.envelope, r.stderr).not.toBeNull() expect(r.envelope.success, JSON.stringify(r.envelope.errors)).toBe(true) return r } describe('scaffold-business / legacyPaths — the pre-classification locations', () => { it('maps the classified / folders back to the module-only ones', () => { const { iface, service } = legacyOf(root) expect(iface).toBe('src/MyApp.Application/Hrm/Interfaces/IEmployeeService.cs') expect(service).toBe('src/MyApp.Infrastructure/Services/Hrm/EmployeeService.cs') }) it('never names a file the current run is about to write (the sweep runs BEFORE the write)', () => { const { parsed, legacy } = legacyOf(root) const current = new Set(generate(parsed).map(f => f.path)) for (const p of legacy) expect(current.has(p), `legacyPaths would delete a current output: ${p}`).toBe(false) }) }) describe('scaffold-business / guarded legacy sweep', () => { it('removes an unmarked legacy file, lists it in data.filesRemoved, and keeps a @customised one with a warning', () => { const { iface, service } = legacyOf(root) const bespoke = `${CUSTOMISED_HEAD}namespace MyApp.Application.Hrm.Interfaces;\npublic interface IEmployeeService { }\n` const stale = 'namespace MyApp.Infrastructure.Services.Hrm;\npublic class EmployeeService { }\n' const ifaceAbs = plant(root, iface, bespoke) const serviceAbs = plant(root, service, stale) const r = run(root) // Unmarked → gone, traced. expect(existsSync(serviceAbs)).toBe(false) expect(r.envelope.data.filesRemoved).toEqual([service]) // @customised → preserved byte-for-byte, warned, NOT in filesRemoved. expect(existsSync(ifaceAbs)).toBe(true) expect(readFileSync(ifaceAbs, 'utf-8')).toBe(bespoke) expect(r.envelope.warnings.some((w: string) => w.includes(`legacy path ${iface} kept: marked @customised`))).toBe(true) // The classified outputs are written regardless. const created: string[] = r.envelope.filesCreated.map(norm) expect(created.some(p => p.endsWith('/src/MyApp.Application/Hr/Hrm/Interfaces/IEmployeeService.cs'))).toBe(true) expect(created.some(p => p.endsWith('/src/MyApp.Infrastructure/Services/Hr/Hrm/EmployeeService.cs'))).toBe(true) }) it('with no legacy file on disk the sweep reports nothing and warns nothing', () => { const r = run(root) expect(r.envelope.data.filesRemoved).toEqual([]) expect(r.envelope.warnings.some((w: string) => /legacy path/.test(w))).toBe(false) }) it('a @customised CURRENT output is never overwritten either (write guard, data.skipped is the warning)', () => { const { parsed } = legacyOf(root) const svcRel = generate(parsed).map(f => f.path).find(p => p.endsWith('/Services/Hr/Hrm/EmployeeService.cs'))! const bespoke = `${CUSTOMISED_HEAD}public class EmployeeService { /* mine */ }\n` const abs = plant(root, svcRel, bespoke) const r = run(root) expect(readFileSync(abs, 'utf-8')).toBe(bespoke) expect(r.envelope.filesCreated.map(norm)).not.toContain(norm(abs)) expect(r.envelope.warnings.some((w: string) => w.includes('kept: marked @customised — not overwritten'))).toBe(true) }) })