mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
chore(db): entity --check reports stray files; class-field census is a ratchet; migrate-encryption typechecked
This commit is contained in:
@@ -181,10 +181,10 @@ jobs:
|
||||
run: cd server && npm run typecheck:tests
|
||||
|
||||
- name: Typecheck entity generator
|
||||
# tsconfig.scripts.json only covers scripts/generate-entities.ts —
|
||||
# gen-plugin-facts.ts/migrate-encryption.ts don't compile under this
|
||||
# config (import.meta needs a newer --module target), so the gate is
|
||||
# named for what it actually covers rather than the whole directory.
|
||||
# tsconfig.scripts.json covers generate-entities.ts and migrate-encryption.ts;
|
||||
# gen-plugin-facts.ts uses import.meta and does not compile under this
|
||||
# config (needs a newer --module target), so the gate is named for what
|
||||
# it actually covers rather than the whole directory.
|
||||
run: cd server && npm run typecheck:scripts
|
||||
|
||||
- name: Entity files match the generator
|
||||
|
||||
@@ -1363,6 +1363,17 @@ export function checkEntities(entitiesDir: string, repositoriesDir: string, file
|
||||
const onDiskIndex = fs.existsSync(indexPath) ? fs.readFileSync(indexPath, 'utf8') : undefined;
|
||||
if (onDiskIndex !== expectedIndex) differingFiles.push('index.ts');
|
||||
|
||||
// The comparison must also run the other way: a hand-added `*.entity.ts`
|
||||
// that the generator does not produce would be folded into index.ts by the
|
||||
// next --write, so it is drift too.
|
||||
const generated = new Set(files.keys());
|
||||
const onDiskEntityFiles = fs.existsSync(entitiesDir)
|
||||
? fs.readdirSync(entitiesDir).filter((f) => f.endsWith('.entity.ts')).sort()
|
||||
: [];
|
||||
for (const fileName of onDiskEntityFiles) {
|
||||
if (!generated.has(fileName)) differingFiles.push(`${fileName} (not produced by the generator)`);
|
||||
}
|
||||
|
||||
const missingRepositories: string[] = [];
|
||||
for (const fileName of files.keys()) {
|
||||
const className = fileName.replace(/\.entity\.ts$/, '');
|
||||
|
||||
@@ -19,6 +19,9 @@ import { createTestOrm, type TestOrm } from '../../helpers/test-orm';
|
||||
* reintroduces a `NaN` initialiser fails loudly here, not silently at a
|
||||
* caller's first `em.create()`.
|
||||
*/
|
||||
/** NOT NULL scalars with neither a column default nor a class-field initialiser (legitimate: the caller must supply them). */
|
||||
const KNOWN_UNINITIALISED_NOT_NULL_SCALARS = 423;
|
||||
|
||||
describe('entity class fields never carry a NaN initialiser (task-4-review-shape.md, Important 1)', () => {
|
||||
it('CLASSFIELD-001: new X() has no own property whose value is NaN, across all 125 entities', () => {
|
||||
const failures: string[] = [];
|
||||
@@ -44,7 +47,7 @@ describe('entity class fields never carry a NaN initialiser (task-4-review-shape
|
||||
* suddenly losing its default) shows up in test output without failing
|
||||
* the suite on a false positive.
|
||||
*/
|
||||
it('CLASSFIELD-002 (report-only): NOT NULL scalar properties with no default and no class-field initialiser', async () => {
|
||||
it('CLASSFIELD-002: exactly the known NOT NULL, no-default scalar properties have no class-field initialiser', async () => {
|
||||
const testDb = createSnapshotTestDb();
|
||||
try {
|
||||
const t = await createTestOrm(testDb);
|
||||
@@ -66,8 +69,11 @@ describe('entity class fields never carry a NaN initialiser (task-4-review-shape
|
||||
}
|
||||
// Informational only — see the doc comment above. `console.info` (not
|
||||
// a `console.log` vitest might buffer away) so it survives a run.
|
||||
console.info(`[CLASSFIELD-002] ${report.length} NOT NULL, no-default scalar propert${report.length === 1 ? 'y' : 'ies'} with no initialiser (expected, not a failure)`);
|
||||
expect(report).toEqual(report); // always true — this test only ever reports, never fails
|
||||
// A ratchet, not a census: the failure mode this file guards (a regeneration
|
||||
// handing every numeric property a bogus default) makes this number DROP;
|
||||
// a new NOT NULL column without a default bumps it by one — update the
|
||||
// constant with the migration that adds the column.
|
||||
expect(report.length, report.join(', ')).toBe(KNOWN_UNINITIALISED_NOT_NULL_SCALARS);
|
||||
} finally {
|
||||
await t.close();
|
||||
}
|
||||
|
||||
@@ -759,6 +759,21 @@ describe('checkEntities', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('CHECK-005: a stray entity file the generator does not produce is reported as drift', async () => {
|
||||
const files = new Map([['X.entity.ts', 'content']]);
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gen-entities-check-'));
|
||||
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gen-entities-check-repos-'));
|
||||
try {
|
||||
fs.writeFileSync(path.join(dir, 'X.entity.ts'), 'content');
|
||||
fs.writeFileSync(path.join(dir, 'Stray.entity.ts'), 'export class Stray {}');
|
||||
const report = checkEntities(dir, repoDir, files);
|
||||
expect(report.differingFiles).toEqual(['index.ts', 'Stray.entity.ts (not produced by the generator)']);
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
fs.rmSync(repoDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('CHECK-004: a missing repository file is reported without being created (never writes)', async () => {
|
||||
const files = new Map([['X.entity.ts', 'content']]);
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gen-entities-check-'));
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
"rootDir": ".",
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["scripts/generate-entities.ts"]
|
||||
"include": ["scripts/generate-entities.ts", "scripts/migrate-encryption.ts"]
|
||||
}
|
||||
|
||||
@@ -172,6 +172,7 @@ export default defineConfig({
|
||||
// src/demo/** is deliberately absent: it measures 0%, and a floor of
|
||||
// zero asserts nothing. It needs tests before it needs a threshold.
|
||||
'src/app-config/**/*.ts': { statements: 99, branches: 95, functions: 99, lines: 99 },
|
||||
// Hand-maintained (scripts/coverage-thresholds.mjs only emits src/nest/* lines): floor(measured) - 1 after Plan 2.
|
||||
'src/db/**/*.ts': { statements: 84, branches: 42, functions: 95, lines: 87 },
|
||||
'src/db/dialect/**/*.ts': { statements: 99, branches: 99, functions: 99, lines: 99 },
|
||||
'src/db/repositories/**/*.ts': { statements: 99, branches: 99, functions: 99, lines: 99 },
|
||||
|
||||
Reference in New Issue
Block a user