Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.

Commit 2d36ee7

Browse files
test: warning-call helper named by behavior; roundtrip typecheck gains its negative control
exactNameWarningCalls replaces the spec-ID helper name, and the rendered- migration typecheck test gains a sibling that deletes the policy literal required name key and asserts tsc fails with TS2741 — proving the green run is a real compile, not a vacuous pass. The typecheck fixture/tsconfig setup is shared by both tests via writeTypecheckDir. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
1 parent 7bdc642 commit 2d36ee7

2 files changed

Lines changed: 102 additions & 53 deletions

File tree

packages/3-targets/3-targets/postgres/test/psl-policy-map-authoring.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ describe('exact-name body-comparison warning for @@map policies — shared per-b
290290
emitWarning.mockClear();
291291
});
292292

293-
function d9Calls() {
293+
function exactNameWarningCalls() {
294294
return emitWarning.mock.calls.filter(
295295
([, options]) =>
296296
(options as { code?: string } | undefined)?.code === 'PN_EXACT_NAME_BODY_COMPARISON',
@@ -308,7 +308,7 @@ describe('exact-name body-comparison warning for @@map policies — shared per-b
308308
}
309309
`),
310310
);
311-
const calls = d9Calls();
311+
const calls = exactNameWarningCalls();
312312
expect(calls).toHaveLength(1);
313313
expect(calls[0]?.[0]).toContain('policy "Tenant members can read" uses map: with a SQL body.');
314314
});
@@ -337,7 +337,7 @@ describe('exact-name body-comparison warning for @@map policies — shared per-b
337337
indexAttributes,
338338
),
339339
);
340-
const calls = d9Calls();
340+
const calls = exactNameWarningCalls();
341341
expect(calls).toHaveLength(1);
342342
const message = String(calls[0]?.[0]);
343343
expect(message).toContain('6 objects use map: with a SQL body.');

packages/3-targets/6-adapters/postgres/test/migrations/render-typescript.roundtrip.test.ts

Lines changed: 99 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,60 @@ const META = {
145145
to: '1'.repeat(64),
146146
} as const;
147147

148+
const tscPath = join(repoRoot, 'node_modules/.bin/tsc');
149+
150+
/**
151+
* Writes a rendered migration plus the tsconfig and contract-type fixtures
152+
* the typecheck tests need. The facade import is pointed at the live
153+
* workspace source (an absolute path specifier; bare workspace imports
154+
* inside the sources then resolve from their own package directories), so
155+
* tsc checks the rendered text against the real `createRlsPolicy`
156+
* signature. The execution fixtures' minimal `Contract` type does not
157+
* satisfy the `Migration` base's `Contract<SqlStorage>` constraint, so the
158+
* snapshot contract types come from the same dist types the migration
159+
* source graph resolves its own bare imports to.
160+
*/
161+
async function writeTypecheckDir(dir: string, renderedSource: string): Promise<void> {
162+
const tsSource = renderedSource.replace(
163+
"'@prisma-next/postgres/migration'",
164+
`'${resolve(targetPostgresRoot, 'src/exports/migration.ts')}'`,
165+
);
166+
await writeFile(join(dir, 'migration.ts'), tsSource);
167+
const contractDistTypes = resolve(
168+
repoRoot,
169+
'packages/1-framework/0-foundation/contract/dist/types.mjs',
170+
);
171+
const sqlContractDistTypes = resolve(repoRoot, 'packages/2-sql/1-core/contract/dist/types.mjs');
172+
const realContractType = `export type Contract = import('${contractDistTypes}').Contract<\n import('${sqlContractDistTypes}').SqlStorage\n>;\n`;
173+
for (const hash of [META.to, META.from]) {
174+
await writeFile(
175+
join(dir, SNAPSHOTS_IMPORT_PATH, storageHashHex(hash), 'contract.ts'),
176+
realContractType,
177+
);
178+
}
179+
await writeFile(
180+
join(dir, 'tsconfig.json'),
181+
JSON.stringify({
182+
compilerOptions: {
183+
target: 'ES2022',
184+
module: 'preserve',
185+
moduleResolution: 'bundler',
186+
lib: ['ES2022'],
187+
strict: true,
188+
exactOptionalPropertyTypes: true,
189+
noUncheckedIndexedAccess: true,
190+
skipLibCheck: true,
191+
noEmit: true,
192+
allowImportingTsExtensions: true,
193+
resolveJsonModule: true,
194+
typeRoots: [join(repoRoot, 'node_modules/@types')],
195+
types: ['node'],
196+
},
197+
include: ['migration.ts'],
198+
}),
199+
);
200+
}
201+
148202
describe('TypeScriptRenderablePostgresMigration round-trip', () => {
149203
let tmpDir: string;
150204

@@ -302,60 +356,55 @@ describe('TypeScriptRenderablePostgresMigration round-trip', () => {
302356
testAdapter,
303357
);
304358

305-
// Point the facade import at the live workspace source (an absolute path
306-
// specifier; bare workspace imports inside the sources then resolve from
307-
// their own package directories) so tsc checks the rendered text against
308-
// the real `createRlsPolicy` signature.
309-
const tsSource = migration
310-
.renderTypeScript()
311-
.replace(
312-
"'@prisma-next/postgres/migration'",
313-
`'${resolve(targetPostgresRoot, 'src/exports/migration.ts')}'`,
314-
);
315-
await writeFile(join(tmpDir, 'migration.ts'), tsSource);
316-
// The execution fixtures' minimal `Contract` type does not satisfy the
317-
// `Migration` base's `Contract<SqlStorage>` constraint; the typecheck
318-
// needs the real one, taken from the same dist types the migration
319-
// source graph resolves its own bare imports to.
320-
const contractDistTypes = resolve(
321-
repoRoot,
322-
'packages/1-framework/0-foundation/contract/dist/types.mjs',
323-
);
324-
const sqlContractDistTypes = resolve(repoRoot, 'packages/2-sql/1-core/contract/dist/types.mjs');
325-
const realContractType = `export type Contract = import('${contractDistTypes}').Contract<\n import('${sqlContractDistTypes}').SqlStorage\n>;\n`;
326-
for (const hash of [META.to, META.from]) {
327-
await writeFile(
328-
join(tmpDir, SNAPSHOTS_IMPORT_PATH, storageHashHex(hash), 'contract.ts'),
329-
realContractType,
330-
);
331-
}
332-
await writeFile(
333-
join(tmpDir, 'tsconfig.json'),
334-
JSON.stringify({
335-
compilerOptions: {
336-
target: 'ES2022',
337-
module: 'preserve',
338-
moduleResolution: 'bundler',
339-
lib: ['ES2022'],
340-
strict: true,
341-
exactOptionalPropertyTypes: true,
342-
noUncheckedIndexedAccess: true,
343-
skipLibCheck: true,
344-
noEmit: true,
345-
allowImportingTsExtensions: true,
346-
resolveJsonModule: true,
347-
typeRoots: [join(repoRoot, 'node_modules/@types')],
348-
types: ['node'],
349-
},
350-
include: ['migration.ts'],
351-
}),
352-
);
353-
354-
const tscPath = join(repoRoot, 'node_modules/.bin/tsc');
359+
await writeTypecheckDir(tmpDir, migration.renderTypeScript());
355360
// Non-zero exit (a type error in the rendered source) rejects.
356361
await execFileAsync(tscPath, ['--project', tmpDir]);
357362
});
358363

364+
it('the typecheck catches a rendered literal missing a required key (negative control)', {
365+
timeout: timeouts.typeScriptCompilation,
366+
}, async () => {
367+
const migration = new TypeScriptRenderablePostgresMigration(
368+
[
369+
new CreatePostgresRlsPolicyCall(
370+
'public',
371+
'user',
372+
new PostgresRlsPolicy({
373+
name: 'Tenant members can read',
374+
prefix: undefined,
375+
tableName: 'user',
376+
namespaceId: 'public',
377+
operation: 'select',
378+
roles: ['app_user'],
379+
using: '(tenant_id = 1)',
380+
withCheck: undefined,
381+
permissive: true,
382+
}),
383+
),
384+
],
385+
META,
386+
APP_SPACE_ID,
387+
SNAPSHOTS_IMPORT_PATH,
388+
testAdapter,
389+
);
390+
391+
// Delete the policy's required `name` key from the rendered source — the
392+
// compile must fail (TS2741 missing-property), proving the green run of
393+
// the sibling test is a real typecheck and not a vacuous pass.
394+
const brokenSource = migration
395+
.renderTypeScript()
396+
.replace(' name: "Tenant members can read",\n', '');
397+
expect(brokenSource).not.toContain('Tenant members can read');
398+
await writeTypecheckDir(tmpDir, brokenSource);
399+
400+
const failure = await execFileAsync(tscPath, ['--project', tmpDir]).then(
401+
() => undefined,
402+
(error: unknown) => error,
403+
);
404+
expect(failure).toBeDefined();
405+
expect(String((failure as { stdout?: string }).stdout)).toContain('TS2741');
406+
});
407+
359408
it('preserves RawSqlCall ops byte-for-byte through the render → execute round-trip', {
360409
timeout: timeouts.typeScriptCompilation,
361410
}, async () => {

0 commit comments

Comments
 (0)