/** * scaffold-migration / execute — regression net. This was the only backend CLI * with NO colocated tests, and the only EF invocation in the repo without * `--context`: on a dual-context generated app (CoreDbContext from the * package + ExtensionsDbContext) EF aborted on EVERY run, and the 2000-char * head-slice swallowed the actual error behind the MSBuild preamble. * child_process is mocked — these tests assert the COMMAND we build and the * output-handling contract, no dotnet involved. (A plain holder, not vi.fn: * a spy whose implementation throws gets its error re-reported by the runner * as an unhandled error even though execute() catches it.) */ import { describe, it, expect, beforeEach } from 'vitest' const calls: string[] = [] let execSyncImpl: (cmd: string) => string = () => '' vi.mock('node:child_process', () => ({ execSync: (cmd: string) => { calls.push(cmd) return execSyncImpl(cmd) }, })) vi.mock('../../../../../lib/fs.js', () => ({ findFiles: async () => [] })) vi.mock('../../../../../lib/sql-objects.js', () => ({ ensureSqlObjectsInMigration: async () => ({ injected: false, inlined: [] }) })) import { vi } from 'vitest' import { execute } from '../execute.js' import { ScaffoldMigrationInputSchema } from '../types.js' const spec = (over: Record = {}) => ScaffoldMigrationInputSchema.parse({ name: 'AddParametrage', module: 'parametrage', appCode: 'Demo', projectPath: '/proj', ...over, }) beforeEach(() => { calls.length = 0 execSyncImpl = () => '' }) describe('scaffold-migration / execute', () => { it('passes --context (default ExtensionsDbContext) — dual-context apps abort without it', async () => { execSyncImpl = () => 'Done.' await execute(spec()) expect(calls[0]).toContain('--context ExtensionsDbContext') expect(calls[0]).toContain('--project src/Demo.Infrastructure') expect(calls[0]).toContain('--startup-project src/Demo.Api') }) it('honours an explicit context', async () => { execSyncImpl = () => 'Done.' await execute(spec({ context: 'CoreDbContext' })) expect(calls[0]).toContain('--context CoreDbContext') }) it('keeps the TAIL of a long success output (EF messages live after the MSBuild preamble)', async () => { const noise = 'MSBuild preamble '.repeat(300) // >> 4000 chars execSyncImpl = () => noise + 'Done. EF-FINAL-MESSAGE' const r = await execute(spec()) expect(r.success).toBe(true) expect(r.output).toContain('EF-FINAL-MESSAGE') expect(r.output.length).toBeLessThanOrEqual(4000) }) it('does NOT truncate a failure — the EF error sits after the build noise', async () => { const noise = 'Determining projects to restore... '.repeat(120) // > 2000 chars of stdout execSyncImpl = () => { const err = new Error('exit 1') as Error & { stdout?: string; stderr?: string } err.stdout = noise err.stderr = 'More than one DbContext was found. Specify which one to use.' throw err } const r = await execute(spec()) expect(r.success).toBe(false) expect(r.output).toContain('More than one DbContext was found') expect(r.output).toContain('Determining projects to restore') }) })