refactor: remove depth/generationProfile tiering, use explicit runs
- Replace TestDepth ('quick'|'standard'|'thorough') with explicit runs:number
- Remove GenerationProfile type and all generationProfile parameters
- schema-to-arbitrary.ts now uses fixed defaults (string≤128, array≤10, props≤6)
- Delete src/cli/core/generation-profile.ts
- Remove --generation-profile CLI flag from verify and qualify
- Remove depth field from PresetDefinition and all preset scaffolds
- Remove generationProfiles from Config interface
- Update all test files: depth:'quick' → runs:10
- Remove depth from all fixture configs
- Update builders.ts, mutation.ts, outbound-mock-runtime.ts
- Build: clean | Tests: 849 pass, 0 fail
This commit is contained in:
@@ -23,12 +23,10 @@
|
||||
* - Property and state model-based testing focused on confidence
|
||||
* - Iterative small steps with rapid feedback loops
|
||||
*/
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import { writeFileSync, readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
import {
|
||||
migrateCommand,
|
||||
detectAllLegacyPatterns,
|
||||
@@ -36,31 +34,25 @@ import {
|
||||
type MigrateOptions,
|
||||
type MigrationItem,
|
||||
} from '../../cli/commands/migrate/index.js';
|
||||
|
||||
import {
|
||||
rewriteConfigFile,
|
||||
detectLegacyConfigFields,
|
||||
detectLegacyFieldsNoEquivalent,
|
||||
detectMixedLegacyModernFields,
|
||||
} from '../../cli/commands/migrate/rewriters/config-rewriter.js';
|
||||
|
||||
import {
|
||||
rewriteRouteAnnotations,
|
||||
detectLegacyRouteAnnotations,
|
||||
detectAmbiguousRoutePatterns,
|
||||
} from '../../cli/commands/migrate/rewriters/route-rewriter.js';
|
||||
|
||||
import {
|
||||
rewriteCodePatterns,
|
||||
detectLegacyCodePatterns,
|
||||
detectAmbiguousCodePatterns,
|
||||
} from '../../cli/commands/migrate/rewriters/code-rewriter.js';
|
||||
|
||||
import { createTempDir, cleanup, makeCtx } from './helpers.js';
|
||||
|
||||
test('migrate --check detects broad legacy config field set', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const legacyConfig = `export default {
|
||||
testMode: "verify",
|
||||
@@ -82,12 +74,9 @@ test('migrate --check detects broad legacy config field set', async () => {
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ check: true }, ctx);
|
||||
|
||||
assert.strictEqual(result.exitCode, 1, 'Should exit 1 when legacy patterns are found');
|
||||
const legacyNames = result.items.map((item) => item.legacy);
|
||||
assert.ok(legacyNames.includes('testMode'), 'Should detect testMode');
|
||||
@@ -103,29 +92,23 @@ test('migrate --check detects broad legacy config field set', async () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 1: Mixed legacy and modern config detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate detects mixed legacy and modern config fields', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
// Config with both legacy and modern fields present
|
||||
const mixedConfig = `export default {
|
||||
// Legacy field
|
||||
testMode: "verify",
|
||||
|
||||
// Modern field (conflicts with legacy)
|
||||
mode: "observe",
|
||||
|
||||
profiles: {
|
||||
quick: {
|
||||
preset: "safe-ci",
|
||||
},
|
||||
},
|
||||
|
||||
// Legacy container
|
||||
testProfiles: {
|
||||
old: {
|
||||
@@ -133,22 +116,17 @@ test('migrate detects mixed legacy and modern config fields', async () => {
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), mixedConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ check: true }, ctx);
|
||||
|
||||
// Should detect legacy patterns
|
||||
assert.strictEqual(result.exitCode, 1, 'Should exit 1 when legacy patterns found');
|
||||
assert.ok(result.items.length > 0, 'Should detect legacy items');
|
||||
|
||||
// Check that mixed fields are reported
|
||||
const legacyNames = result.items.map((item) => item.legacy);
|
||||
assert.ok(legacyNames.includes('testMode'), 'Should detect testMode');
|
||||
assert.ok(legacyNames.includes('testProfiles'), 'Should detect testProfiles');
|
||||
assert.ok(legacyNames.includes('usesPreset'), 'Should detect usesPreset');
|
||||
|
||||
// Verify guidance mentions the conflict
|
||||
const testModeItem = result.items.find((item) => item.legacy === 'testMode');
|
||||
assert.ok(testModeItem, 'Should have testMode item');
|
||||
@@ -157,19 +135,15 @@ test('migrate detects mixed legacy and modern config fields', async () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 2: Dry-run shows exact rewrites
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate dry-run shows exact file path, line number, legacy text, replacement text', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const legacyConfig = `export default {
|
||||
// Line 2
|
||||
testMode: "verify",
|
||||
|
||||
profiles: {
|
||||
quick: {
|
||||
// Line 7
|
||||
@@ -177,36 +151,27 @@ test('migrate dry-run shows exact file path, line number, legacy text, replaceme
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ dryRun: true }, ctx);
|
||||
|
||||
assert.strictEqual(result.exitCode, 1, 'Should exit 1 when legacy patterns found');
|
||||
assert.ok(result.message, 'Should have output message');
|
||||
|
||||
// Verify dry-run output contains exact details
|
||||
assert.ok(result.message.includes('Dry run'), 'Should indicate dry run');
|
||||
assert.ok(result.message.includes('testMode'), 'Should show legacy text');
|
||||
assert.ok(result.message.includes('mode'), 'Should show replacement text');
|
||||
assert.ok(result.message.includes('usesPreset'), 'Should show usesPreset');
|
||||
assert.ok(result.message.includes('preset'), 'Should show preset replacement');
|
||||
|
||||
// Verify file path is shown
|
||||
assert.ok(result.message.includes('apophis.config.js'), 'Should show file path');
|
||||
|
||||
// Verify line numbers are shown
|
||||
assert.ok(result.message.includes(':2') || result.message.includes(': 2'), 'Should show line number');
|
||||
|
||||
// Verify total count
|
||||
assert.ok(result.message.includes('Total:'), 'Should show total count');
|
||||
assert.ok(result.message.includes('3'), 'Should show correct total (3 items)');
|
||||
|
||||
// Verify files would be modified
|
||||
assert.ok(result.filesWouldBeModified, 'Should list files that would be modified');
|
||||
assert.strictEqual(result.filesWouldBeModified.length, 1, 'Should show 1 file would be modified');
|
||||
|
||||
// Verify file was NOT modified
|
||||
const content = readFileSync(resolve(dir, 'apophis.config.js'), 'utf-8');
|
||||
assert.ok(content.includes('testMode'), 'File should still have testMode');
|
||||
@@ -215,14 +180,11 @@ test('migrate dry-run shows exact file path, line number, legacy text, replaceme
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 3: Write performs rewrites correctly
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate write performs rewrites correctly', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const legacyConfig = `export default {
|
||||
testMode: "verify",
|
||||
@@ -232,16 +194,12 @@ test('migrate write performs rewrites correctly', async () => {
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ write: true }, ctx);
|
||||
|
||||
assert.strictEqual(result.exitCode, 1, 'Should exit 1 when rewrites performed');
|
||||
assert.ok(result.completed.length > 0, 'Should have completed items');
|
||||
assert.ok(result.filesModified && result.filesModified.length > 0, 'Should list modified files');
|
||||
|
||||
// Verify file WAS modified
|
||||
const content = readFileSync(resolve(dir, 'apophis.config.js'), 'utf-8');
|
||||
assert.ok(!content.includes('testMode'), 'File should not have testMode');
|
||||
@@ -254,35 +212,26 @@ test('migrate write performs rewrites correctly', async () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 4: Ambiguous rewrite stops and shows context
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate ambiguous rewrite stops and shows surrounding context', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
// Create a file with an ambiguous code pattern
|
||||
const ambiguousCode = `import Fastify from 'fastify';
|
||||
const app = Fastify();
|
||||
|
||||
// This is ambiguous: what does oldApi() mean here?
|
||||
app.register(oldApi());
|
||||
|
||||
export default app;`;
|
||||
|
||||
writeFileSync(resolve(dir, 'app.js'), ambiguousCode);
|
||||
|
||||
// Also create a config file so migration has something to work with
|
||||
const config = `export default {
|
||||
mode: "verify",
|
||||
};`;
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), config);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ write: true }, ctx);
|
||||
|
||||
// Should stop with exit code 2 (USAGE_ERROR) because ambiguous patterns found
|
||||
assert.strictEqual(result.exitCode, 2, 'Should exit 2 when ambiguous patterns found in write mode');
|
||||
assert.ok(result.remaining.length > 0, 'Should have remaining items');
|
||||
@@ -290,21 +239,17 @@ export default app;`;
|
||||
assert.ok(result.message.includes('Ambiguous'), 'Should mention ambiguous patterns');
|
||||
assert.ok(result.message.includes('oldApi()'), 'Should show the ambiguous pattern');
|
||||
assert.ok(result.message.includes('manual choice'), 'Should mention manual choice');
|
||||
|
||||
// Verify context is shown (surrounding lines)
|
||||
assert.ok(result.message.includes('app.register'), 'Should show surrounding context');
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 5: Legacy field with no equivalent emits guidance
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate legacy field with no direct equivalent emits human guidance', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
// Config with a legacy field that has no direct equivalent
|
||||
const legacyConfig = `export default {
|
||||
@@ -317,16 +262,12 @@ test('migrate legacy field with no direct equivalent emits human guidance', asyn
|
||||
// This field is deprecated with no direct equivalent
|
||||
legacyField: true,
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ check: true }, ctx);
|
||||
|
||||
// Should detect the legacy field with no equivalent
|
||||
assert.strictEqual(result.exitCode, 1, 'Should exit 1 when legacy patterns found');
|
||||
assert.ok(result.items.length > 0, 'Should detect legacy items');
|
||||
|
||||
const legacyFieldItem = result.items.find((item) => item.legacy === 'legacyField');
|
||||
assert.ok(legacyFieldItem, 'Should detect legacyField');
|
||||
assert.ok(legacyFieldItem.guidance, 'Should have guidance for legacyField');
|
||||
@@ -343,14 +284,11 @@ test('migrate legacy field with no direct equivalent emits human guidance', asyn
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 6: Partial migration reports completed and remaining
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate partial migration reports completed and remaining items', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const legacyConfig = `export default {
|
||||
testMode: "verify",
|
||||
@@ -360,12 +298,9 @@ test('migrate partial migration reports completed and remaining items', async ()
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ write: true }, ctx);
|
||||
|
||||
assert.ok(result.completed.length > 0, 'Should have completed items');
|
||||
assert.ok(result.message, 'Should have output message');
|
||||
assert.ok(result.message.includes('Completed'), 'Should mention completed');
|
||||
@@ -374,20 +309,16 @@ test('migrate partial migration reports completed and remaining items', async ()
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 7: Preserves comments/formatting where feasible
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate preserves comments and formatting where feasible', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
// Config with specific formatting (comments, indentation)
|
||||
const legacyConfig = `export default {
|
||||
// This is a comment about testMode
|
||||
testMode: "verify",
|
||||
|
||||
/*
|
||||
* Block comment about testProfiles
|
||||
*/
|
||||
@@ -398,19 +329,14 @@ test('migrate preserves comments and formatting where feasible', async () => {
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ write: true }, ctx);
|
||||
|
||||
const content = readFileSync(resolve(dir, 'apophis.config.js'), 'utf-8');
|
||||
|
||||
// Verify comments are preserved
|
||||
assert.ok(content.includes('// This is a comment about testMode'), 'Should preserve line comment');
|
||||
assert.ok(content.includes('Block comment about testProfiles'), 'Should preserve block comment');
|
||||
assert.ok(content.includes('// Inline comment'), 'Should preserve inline comment');
|
||||
|
||||
// Verify replacements were made
|
||||
assert.ok(content.includes('mode:'), 'Should have mode');
|
||||
assert.ok(content.includes('profiles:'), 'Should have profiles');
|
||||
@@ -419,14 +345,11 @@ test('migrate preserves comments and formatting where feasible', async () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 8: Migrate exits 0 when config is already modern
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate exits 0 when config is already modern', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const modernConfig = `export default {
|
||||
mode: "verify",
|
||||
@@ -438,7 +361,7 @@ test('migrate exits 0 when config is already modern', async () => {
|
||||
},
|
||||
presets: {
|
||||
"safe-ci": {
|
||||
depth: "quick",
|
||||
,
|
||||
timeout: 5000,
|
||||
},
|
||||
},
|
||||
@@ -448,12 +371,9 @@ test('migrate exits 0 when config is already modern', async () => {
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), modernConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ check: true }, ctx);
|
||||
|
||||
assert.strictEqual(result.exitCode, 0, 'Should exit 0 for modern config');
|
||||
assert.strictEqual(result.items.length, 0, 'Should have no items');
|
||||
assert.ok(result.message, 'Should have message');
|
||||
@@ -462,35 +382,25 @@ test('migrate exits 0 when config is already modern', async () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 9: Migrate exits 2 when ambiguous in write mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate exits 2 when ambiguous patterns found in write mode', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const config = `export default {
|
||||
mode: "verify",
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), config);
|
||||
|
||||
// Create app with an ambiguous pattern
|
||||
const code = `import Fastify from 'fastify';
|
||||
const app = Fastify();
|
||||
|
||||
// Ambiguous pattern
|
||||
app.register(oldApi());
|
||||
|
||||
export default app;`;
|
||||
|
||||
writeFileSync(resolve(dir, 'app.js'), code);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ write: true }, ctx);
|
||||
|
||||
// Should exit 2 because ambiguous patterns found
|
||||
assert.strictEqual(result.exitCode, 2, 'Should exit 2 when ambiguous patterns found in write mode');
|
||||
assert.ok(result.remaining.length > 0, 'Should have remaining ambiguous items');
|
||||
@@ -499,14 +409,11 @@ export default app;`;
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 10: Migrate emits guidance for each legacy field
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate emits guidance for each legacy field', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const legacyConfig = `export default {
|
||||
testMode: "verify",
|
||||
@@ -516,14 +423,10 @@ test('migrate emits guidance for each legacy field', async () => {
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ check: true }, ctx);
|
||||
|
||||
assert.ok(result.items.length > 0, 'Should have items');
|
||||
|
||||
for (const item of result.items) {
|
||||
assert.ok(item.guidance, `Item ${item.legacy} should have guidance`);
|
||||
assert.ok(
|
||||
@@ -535,14 +438,11 @@ test('migrate emits guidance for each legacy field', async () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 11: Config rewriter replaces legacy fields
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('config rewriter replaces legacy fields', () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const content = `export default {
|
||||
testMode: "verify",
|
||||
@@ -552,17 +452,13 @@ test('config rewriter replaces legacy fields', () => {
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'test.config.js'), content);
|
||||
|
||||
const items = detectLegacyConfigFields(content, 'test.config.js');
|
||||
assert.strictEqual(items.length, 3, 'Should detect 3 legacy fields');
|
||||
|
||||
const result = rewriteConfigFile(
|
||||
resolve(dir, 'test.config.js'),
|
||||
items,
|
||||
);
|
||||
|
||||
assert.strictEqual(result.modified, true, 'Should modify content');
|
||||
assert.ok(result.content.includes('mode:'), 'Should have mode');
|
||||
assert.ok(result.content.includes('profiles:'), 'Should have profiles');
|
||||
@@ -572,35 +468,28 @@ test('config rewriter replaces legacy fields', () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 12: Route rewriter detects x-validate-runtime annotation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('route rewriter detects x-validate-runtime annotation', () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const content = `export default {
|
||||
schema: {
|
||||
'x-validate-runtime': true,
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'test.routes.js'), content);
|
||||
|
||||
const items = detectLegacyRouteAnnotations(content, 'test.routes.js');
|
||||
assert.strictEqual(items.length, 1, 'Should detect 1 legacy annotation');
|
||||
const firstItem = items[0];
|
||||
assert.ok(firstItem, 'Expected one migration item');
|
||||
assert.strictEqual(firstItem.legacy, 'x-validate-runtime');
|
||||
assert.strictEqual(firstItem.replacement, 'runtime');
|
||||
|
||||
const result = rewriteRouteAnnotations(
|
||||
resolve(dir, 'test.routes.js'),
|
||||
items,
|
||||
);
|
||||
|
||||
assert.strictEqual(result.modified, true, 'Should modify content');
|
||||
assert.ok(result.content.includes("'runtime'"), 'Should have runtime');
|
||||
assert.ok(!result.content.includes('x-validate-runtime'), 'Should not have legacy annotation');
|
||||
@@ -608,34 +497,25 @@ test('route rewriter detects x-validate-runtime annotation', () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 13: Code rewriter detects legacy patterns
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('code rewriter detects legacy patterns', () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const content = `import Fastify from 'fastify';
|
||||
const app = Fastify();
|
||||
|
||||
app.register(contract());
|
||||
app.register(stateful());
|
||||
app.register(scenario());
|
||||
|
||||
export default app;`;
|
||||
|
||||
writeFileSync(resolve(dir, 'test.app.js'), content);
|
||||
|
||||
const items = detectLegacyCodePatterns(content, 'test.app.js');
|
||||
assert.strictEqual(items.length, 3, 'Should detect 3 legacy patterns');
|
||||
|
||||
const result = rewriteCodePatterns(
|
||||
resolve(dir, 'test.app.js'),
|
||||
items,
|
||||
);
|
||||
|
||||
assert.strictEqual(result.modified, true, 'Should modify content');
|
||||
assert.ok(result.content.includes("verify({ kind: 'contract' })"), 'Should have verify');
|
||||
assert.ok(result.content.includes("qualify({ kind: 'stateful' })"), 'Should have qualify stateful');
|
||||
@@ -644,28 +524,21 @@ export default app;`;
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 14: Dry-run default mode (safe by default)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate defaults to dry-run mode (safe by default)', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const legacyConfig = `export default {
|
||||
testMode: "verify",
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
// No mode specified — should default to dry-run
|
||||
const result = await migrateCommand({}, ctx);
|
||||
|
||||
assert.strictEqual(result.exitCode, 1, 'Should exit 1 in dry-run mode');
|
||||
assert.ok(result.message?.includes('Dry run'), 'Should indicate dry run');
|
||||
|
||||
// Verify file was NOT modified
|
||||
const content = readFileSync(resolve(dir, 'apophis.config.js'), 'utf-8');
|
||||
assert.ok(content.includes('testMode'), 'File should still have testMode');
|
||||
@@ -673,38 +546,30 @@ test('migrate defaults to dry-run mode (safe by default)', async () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 15: Mixed legacy/modern field detection at rewriter level
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('config rewriter detects mixed legacy and modern fields', () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const content = `export default {
|
||||
// Both legacy and modern present
|
||||
testMode: "verify",
|
||||
mode: "observe",
|
||||
|
||||
testProfiles: {
|
||||
quick: {
|
||||
usesPreset: "safe-ci",
|
||||
},
|
||||
},
|
||||
|
||||
profiles: {
|
||||
modern: {
|
||||
preset: "safe-ci",
|
||||
},
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'test.config.js'), content);
|
||||
|
||||
const mixedReports = detectMixedLegacyModernFields(content, 'test.config.js');
|
||||
assert.ok(mixedReports.length > 0, 'Should detect mixed fields');
|
||||
|
||||
const testModeReport = mixedReports.find((r) => r.legacy === 'testMode');
|
||||
assert.ok(testModeReport, 'Should report testMode as mixed');
|
||||
assert.ok(testModeReport.guidance.includes('testMode'), 'Guidance should mention testMode');
|
||||
@@ -713,14 +578,11 @@ test('config rewriter detects mixed legacy and modern fields', () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 16: Ambiguous route pattern detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('route rewriter detects ambiguous route patterns with context', () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const content = `export default {
|
||||
schema: {
|
||||
@@ -728,12 +590,9 @@ test('route rewriter detects ambiguous route patterns with context', () => {
|
||||
'x-validate': true,
|
||||
},
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'test.routes.js'), content);
|
||||
|
||||
const items = detectAmbiguousRoutePatterns(content, 'test.routes.js');
|
||||
assert.strictEqual(items.length, 1, 'Should detect 1 ambiguous pattern');
|
||||
|
||||
const firstItem = items[0];
|
||||
assert.ok(firstItem, 'Expected one migration item');
|
||||
assert.strictEqual(firstItem.legacy, 'x-validate');
|
||||
@@ -744,28 +603,20 @@ test('route rewriter detects ambiguous route patterns with context', () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 17: Ambiguous code pattern detection with context
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('code rewriter detects ambiguous code patterns with surrounding context', () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const content = `import Fastify from 'fastify';
|
||||
const app = Fastify();
|
||||
|
||||
// Ambiguous pattern
|
||||
app.register(oldApi());
|
||||
|
||||
export default app;`;
|
||||
|
||||
writeFileSync(resolve(dir, 'test.app.js'), content);
|
||||
|
||||
const items = detectAmbiguousCodePatterns(content, 'test.app.js');
|
||||
assert.strictEqual(items.length, 1, 'Should detect 1 ambiguous pattern');
|
||||
|
||||
const firstItem = items[0];
|
||||
assert.ok(firstItem, 'Expected one migration item');
|
||||
assert.strictEqual(firstItem.legacy, 'oldApi()');
|
||||
@@ -777,42 +628,32 @@ export default app;`;
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 18: Legacy fixture detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate detects legacy patterns in fixture config', async () => {
|
||||
const ctx = makeCtx({ cwd: 'src/cli/__fixtures__/legacy-config' });
|
||||
const result = await migrateCommand({ check: true }, ctx);
|
||||
|
||||
assert.strictEqual(result.exitCode, 1, 'Should detect legacy patterns in fixture');
|
||||
assert.ok(result.items.length > 0, 'Should find legacy items');
|
||||
|
||||
const legacyNames = result.items.map((item) => item.legacy);
|
||||
assert.ok(legacyNames.includes('testMode'), 'Should detect testMode in fixture');
|
||||
assert.ok(legacyNames.includes('testProfiles'), 'Should detect testProfiles in fixture');
|
||||
assert.ok(legacyNames.includes('testPresets'), 'Should detect testPresets in fixture');
|
||||
assert.ok(legacyNames.includes('envPolicies'), 'Should detect envPolicies in fixture');
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 19: JSON output format
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate outputs JSON format with all fields', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const legacyConfig = `export default {
|
||||
testMode: "verify",
|
||||
};`;
|
||||
|
||||
writeFileSync(resolve(dir, 'apophis.config.js'), legacyConfig);
|
||||
|
||||
const ctx = makeCtx({ cwd: dir, options: { ...makeCtx().options, format: 'json' } });
|
||||
const result = await migrateCommand({ check: true }, ctx);
|
||||
|
||||
assert.strictEqual(result.exitCode, 1, 'Should exit 1');
|
||||
assert.ok(result.items.length > 0, 'Should have items');
|
||||
assert.ok(result.totalRewrites, 'Should have totalRewrites');
|
||||
@@ -821,18 +662,14 @@ test('migrate outputs JSON format with all fields', async () => {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Test 20: No files found returns usage error
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('migrate returns usage error when no files found', async () => {
|
||||
const dir = createTempDir();
|
||||
|
||||
try {
|
||||
const ctx = makeCtx({ cwd: dir });
|
||||
const result = await migrateCommand({ check: true }, ctx);
|
||||
|
||||
assert.strictEqual(result.exitCode, 2, 'Should exit 2 when no files found');
|
||||
assert.ok(result.message?.includes('No config or app files found'), 'Should mention no files found');
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user