/** * scaffold-migration — CLI entry contract. `index.ts` used to hand the RAW * spec to execute(): `context` (default ExtensionsDbContext, documented as * NON-NEGOTIABLE on a dual-context app) was `undefined` for every caller that * omitted it, so `dotnet ef migrations add … --context undefined` failed on * every run. `--dry_run` reports the command without touching dotnet. * * QC 2026-09-24 (lot 13): the parity block holds two REAL properties * (lib/__tests__/helpers/spec-parity) on validate()'s output: no-strip * against the RAW spec, defaults-only against the schema's DECLARED defaults * — each proven to fire. No generate() here: execute() spawns dotnet. */ import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { z } from 'zod' import { runCli } from '../../../../../../lib/__tests__/helpers/run-cli.js' import { specParity } from '../../../../../../lib/__tests__/helpers/spec-parity.js' import { validate } from '../validate.js' import { ScaffoldMigrationInputSchema } from '../types.js' const INDEX = resolve(dirname(fileURLToPath(import.meta.url)), '../index.ts') let tmp: string beforeAll(() => { tmp = mkdtempSync(join(tmpdir(), 'ss-migration-entry-')) }) afterAll(() => { rmSync(tmp, { recursive: true, force: true }) }) describe('scaffold-migration — the CLI entry runs the PARSED spec', () => { it('--dry_run reports the command with the DEFAULT context when the spec omits it', () => { const spec = { name: 'AddEmployee', module: 'hrm', appCode: 'MyApp', projectPath: tmp } const r = runCli(INDEX, ['--spec', JSON.stringify(spec), '--dry_run']) expect(r.envelope, r.stderr).not.toBeNull() expect(r.envelope.success, JSON.stringify(r.envelope.errors)).toBe(true) expect(r.status).toBe(0) expect(r.envelope.data.dryRun).toBe(true) expect(r.envelope.data.command).toContain('dotnet ef migrations add AddEmployee') expect(r.envelope.data.command).toContain('--context ExtensionsDbContext') expect(r.envelope.data.command).toContain('--project src/MyApp.Infrastructure') expect(r.envelope.data.command).not.toContain('undefined') }) it('validate() returns the parsed spec with `context` defaulted', () => { const v = validate({ name: 'AddEmployee', module: 'hrm', appCode: 'MyApp', projectPath: tmp }) expect(v.valid).toBe(true) expect(v.data?.context).toBe('ExtensionsDbContext') }) it('validate() carries no data when the project path does not exist', () => { const v = validate({ name: 'AddEmployee', module: 'hrm', appCode: 'MyApp', projectPath: join(tmp, 'missing') }) expect(v.valid).toBe(false) expect(v.data).toBeUndefined() }) }) describe('scaffold-migration — parity RAW ↔ PARSED (real properties, not schema-vs-schema)', () => { const raw = () => ({ name: 'AddEmployee', module: 'hrm', appCode: 'MyApp', projectPath: tmp }) const parsed = () => validate(raw()).data! it('(a) no-strip: every key path of the spec survives the parse with its value', () => { const report = specParity(raw(), parsed(), ScaffoldMigrationInputSchema) expect(report.rawPaths).toEqual(['name', 'module', 'appCode', 'projectPath']) expect(report.stripped).toEqual([]) expect(report.changed).toEqual([]) }) it('(b) defaults-only: the only key the parse adds is the DECLARED `context` default', () => { const report = specParity(raw(), parsed(), ScaffoldMigrationInputSchema) expect(report.added.map(a => a.path)).toEqual(['context']) expect(report.undeclared).toEqual([]) }) it('FIRES (a): a strip-mode copy of the schema without `module` is reported at that path', () => { const stripping = z.object(ScaffoldMigrationInputSchema.shape).omit({ module: true }) const report = specParity(raw(), stripping.parse(raw()), stripping) expect(report.stripped).toEqual(['module']) expect(report.changed).toEqual([]) }) it('FIRES (b): a key injected after the parse, or a default applied with another value, is reported', () => { expect(specParity(raw(), { ...parsed(), infrastructureProject: 'x' }, ScaffoldMigrationInputSchema).undeclared).toEqual(['infrastructureProject']) expect(specParity(raw(), { ...parsed(), context: 'CoreDbContext' }, ScaffoldMigrationInputSchema).undeclared).toEqual(['context']) }) })