Skip to content

Commit 9ccebe0

Browse files
TML-3008: differ pairs siblings by (nodeKind, id) — delete the role-sigil workaround (#952)
## Linked issue Refs [TML-3008](https://linear.app/prisma-company/issue/TML-3008/schema-differ-pairs-siblings-by-nodekind-id-delete-the-role-sigil) Unblocks [#950](#950) (TML-2870), which rebases onto this and deletes its `role:`-sigil workaround. ## At a glance ```ts // packages/1-framework/1-core/framework-components/src/control/schema-diff.ts export interface DiffableNode { readonly id: string; readonly nodeKind: string; isEqualTo(other: DiffableNode): boolean; children(): readonly DiffableNode[]; } ``` Before this PR the differ keyed a parent's children into one flat map on bare `id` and threw `diffSchemas: duplicate id among siblings` on any collision — so a Postgres role named `public` and a schema named `public`, which are different objects that never pair against each other, could not coexist as siblings in the diff tree. ## Decision Two things: 1. **The differ pairs siblings by `(nodeKind, id)`, not by a globally-unique `id`.** `DiffableNode` gains `readonly nodeKind: string` — the per-node discriminant every SQL schema-IR node already declares, so the SQL tree satisfies it with zero changes. An `id` now only needs to be unique among same-kind siblings, which it already is by construction (you can't declare two roles named `public`, or two columns named `id` on one table). A node never folds its kind into its id string to route around the differ. 2. **`SchemaDiffIssue.message` is deleted.** The differ reports structured data (`path`, `reason`, the nodes); turning that into a human sentence is the renderer's job. Every producer that baked prose into `message` — including Mongo's hand-rolled `diffMongoSchemas` — stops; every consumer renders from `path`/`reason` itself. ## Reviewer notes - **Byte-identity is the review question.** Sibling pairing uses a single `Map` keyed on `` `${nodeKind}\u0000${id}` `` — deliberately *not* a nested per-kind map — so insertion order, and therefore emitted-issue order, is bit-for-bit what it was. Every `nodeKind` in the repo is a code-defined literal, so the NUL delimiter cannot collide. The only input whose behavior changes is one that previously **threw** (cross-kind sibling id collision); no previously-succeeding diff changes output. Zero fixture/snapshot/golden files changed in this PR. - **Mongo `db verify` failure text becomes path-based.** `diffMongoSchemas` used to bake sentences like `Collection "users" is missing from the database` into `message`; issues now render as `users` / `orders/validator` with the `reason` label. Accepted deliberately — no committed snapshot pinned the prose, and a follow-up renderer can rebuild richer text from the structured issue when wanted. - **The `message` sweep is most of the diff.** The keying change itself is ~40 lines; the other ~30 files are consumers and test literals that carried `message`. One consumer was missed on the first pass and caught by review: the CLI error-envelope formatter printed blank issue bodies for SQLite/Mongo verify failures ([errors.ts](packages/1-framework/3-tooling/cli/src/utils/formatters/errors.ts) now renders `message ?? path.join('/')`, since PSL diagnostics share that envelope and legitimately carry `message`). - **Slice process artifacts** (`projects/postgres-rls/slices/differ-kind-keying/`) are committed for review context and are cleaned up at project close-out, per this repo's Drive convention. ## How it fits together 1. **The interface carries the discriminant.** `DiffableNode.nodeKind` is an opaque string — no target vocabulary enters the framework. SQL nodes inherit it from `SqlSchemaIRNode`'s existing abstract `nodeKind`; the five Mongo schema-IR nodes each gain a genuine per-node literal (`collection`, `index`, `validator`, `collectionOptions`, `schema`) matching their existing `kind` discriminators 1:1. 2. **The differ keys on the pair.** `insertNode`/`diffChildren` build the sibling map on the combined key and throw only on a genuine same-kind/same-id duplicate ([schema-diff.ts](packages/1-framework/1-core/framework-components/src/control/schema-diff.ts)). 3. **Issues drop the baked message.** `pathMessage` and `SchemaDiffIssue.message` are deleted; consumers that need a string build it from `path` — the CLI verify formatter, `db verify`, the sqlite/postgres issue planners' conflict summaries, and the CLI error-envelope formatter. 4. **The ADR records the model.** [adr-schema-diff-over-structured-ir.md](projects/postgres-rls/specs/adr-schema-diff-over-structured-ir.md) now states per-kind sibling-id uniqueness and explicitly rejects folding a node's kind into its `id()` (the guidance that produced #950's sigil). ## Behavior changes & evidence - Same-name, different-kind siblings diff instead of throwing; each pairs only against its own-kind counterpart. Implementation: [schema-diff.ts](packages/1-framework/1-core/framework-components/src/control/schema-diff.ts). Evidence: [schema-diff.test.ts](packages/1-framework/1-core/framework-components/test/schema-diff.test.ts) (role-`public`/namespace-`public` pair independently; same-kind/same-id still throws; issues carry no `message`). - Mongo schema-IR nodes declare `nodeKind`; the Mongo diff algorithm is otherwise unchanged apart from dropping `message`. Implementation: [schema-node.ts](packages/2-mongo-family/3-tooling/mongo-schema-ir/src/schema-node.ts), [schema-diff.ts](packages/2-mongo-family/9-family/src/core/schema-diff.ts). Evidence: [cli.control-policy.mongo.e2e.test.ts](test/integration/test/cli.control-policy.mongo.e2e.test.ts). - CLI issue rendering is path-based where prose was removed, with PSL diagnostics keeping their messages. Implementation: [verify.ts](packages/1-framework/3-tooling/cli/src/utils/formatters/verify.ts), [errors.ts](packages/1-framework/3-tooling/cli/src/utils/formatters/errors.ts). Evidence: [output.errors.test.ts](packages/1-framework/3-tooling/cli/test/output.errors.test.ts) (pins both producer shapes). ## Testing performed - `pnpm build` (68/68), forced `pnpm typecheck` (143/143), `pnpm lint` + `pnpm lint:deps` on touched packages - `pnpm test:packages` (12k+ tests), `pnpm test:integration`, `pnpm test:e2e` (109/109) - `pnpm fixtures:check` — clean; `git status` after: no fixture/snapshot/golden file changed - Reproduced-and-ruled-pre-existing flakes: config-loader `/var` symlink, sql-orm-client portal race (both known families, untouched files) ## Follow-ups - PR #950 rebases onto this and deletes its `role:` sigil + `withCleanRoleMessage` (TML-2870). - Optional richer issue rendering (human sentences rebuilt from structured issues in the CLI formatter) if path-based output proves too terse. ## Alternatives considered - **Fold the kind into the id string (the `role:` sigil).** Rejected — it re-encodes lost structural information into a name, then leaks into paths and needs laundering (`withCleanRoleMessage`). That workaround is what #950 currently ships and deletes after this merges. - **Key the differ on the contract `EntityCoordinate`.** Rejected (and the ADR's original rejection stands) — schema-IR-node identity is its own domain; mapping a schema-IR node to a contract coordinate is a separate consumer-side operation, not the diff walk's concern. - **A nested `Map<nodeKind, Map<id, node>>`.** Rejected — regrouping by kind changes iteration order and would break emitted-issue byte-identity; the single combined-key map preserves insertion order exactly. - **Keep `message` populated with the path string.** Rejected — it duplicates `path` as a pre-rendered string and invites producers (as Mongo did) to bake presentation into data the renderer should own. ## Checklist - [x] All commits DCO-signed (`Signed-off-by`) - [x] Tests written/updated for the behavior changes - [x] Title follows `TML-XXXX: <sentence case>` convention - [x] Docs/ADR updated where behavior is described <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved schema-diff matching to avoid conflating items that share an ID but differ by node type. * Schema verification and CLI/database-check output now consistently identify issues using their structured paths (with reason), including in warnings and formatted error output. * Migration/rebuild summaries now use deterministic, path-based locations for conflicts. * **Refactor** * Schema-diff issues no longer rely on a preformatted message; they use structured fields such as path, reason, expected, and actual. * Mongo schema IR nodes now expose `nodeKind` instead of `kind` for type discrimination. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
1 parent 813bc5a commit 9ccebe0

41 files changed

Lines changed: 306 additions & 153 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/1-framework/1-core/framework-components/src/control/schema-diff.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export interface SchemaDiffIssue<TNode extends DiffableNode = DiffableNode> {
55
readonly path: readonly string[];
66
/** Why the actual state fails the expectation. Consumers filter on this field. */
77
readonly reason: ExpectationFailureReason;
8-
readonly message: string;
98
/** The expected (desired-side) node, when available. Absent for `not-expected` issues. */
109
readonly expected?: TNode;
1110
/** The actual (current-side) node, when available. Absent for `not-found` issues. */
@@ -15,43 +14,45 @@ export interface SchemaDiffIssue<TNode extends DiffableNode = DiffableNode> {
1514
/**
1615
* A node in the schema tree. Every node in the tree implements this interface.
1716
*
18-
* `id` must be unique among sibling nodes at the same level — the differ keys
19-
* on it and treats a collision as the same entity (enforced by a duplicate-id
20-
* throw). The differ accumulates these ids into a path that stamps every emitted
21-
* issue.
17+
* The differ pairs siblings by the combination of `nodeKind` and `id`, not by
18+
* `id` alone: `id` needs only be unique among siblings of the same
19+
* `nodeKind` at the same level, not globally unique at that level. Two
20+
* distinct kinds of child in distinct slots (e.g. a role and a namespace) may
21+
* legitimately share a name — they are never paired against each other, so
22+
* the collision is harmless. A node never folds its kind into its id string
23+
* to route around this; `nodeKind` is the discriminant that does that job.
24+
* A same-`nodeKind`/same-`id` collision among siblings is a genuine
25+
* duplicate and is enforced by a throw. The differ accumulates ids (not
26+
* nodeKind) into a path that stamps every emitted issue.
2227
*/
2328
export interface DiffableNode {
2429
readonly id: string;
30+
readonly nodeKind: string;
2531
isEqualTo(other: DiffableNode): boolean;
2632
children(): readonly DiffableNode[];
2733
}
2834

35+
/** Delimiter joining `nodeKind` and `id` into one sibling-map key. Every `nodeKind` is a code-defined literal (kebab-case-style), so a null character can never appear in one. */
36+
const SIBLING_KEY_DELIMITER = '\u0000';
37+
38+
function siblingKey(node: DiffableNode): string {
39+
return `${node.nodeKind}${SIBLING_KEY_DELIMITER}${node.id}`;
40+
}
41+
2942
function insertNode(map: Map<string, DiffableNode>, node: DiffableNode): void {
30-
const key = node.id;
43+
const key = siblingKey(node);
3144
if (map.has(key)) {
32-
throw new Error(`diffSchemas: duplicate id among siblings: ${key}`);
45+
throw new Error(`diffSchemas: duplicate id among siblings: ${node.nodeKind}/${node.id}`);
3346
}
3447
map.set(key, node);
3548
}
3649

37-
/**
38-
* The issue's own default message: the path, nothing else. `reason` already
39-
* carries why the node is flagged as structured data; turning that into a
40-
* human label ("missing: …" / "extra: …" / "mismatch: …") is a presentation
41-
* concern for whoever renders the issue (the CLI verify formatter), not the
42-
* differ's.
43-
*/
44-
function pathMessage(path: readonly string[]): string {
45-
return path.join('/');
46-
}
47-
4850
function emitMissingSubtree(node: DiffableNode, parentPath: readonly string[]): SchemaDiffIssue[] {
4951
const path = [...parentPath, node.id];
5052
return [
5153
{
5254
path,
5355
reason: 'not-found',
54-
message: pathMessage(path),
5556
expected: node,
5657
},
5758
...node.children().flatMap((c) => emitMissingSubtree(c, path)),
@@ -64,7 +65,6 @@ function emitExtraSubtree(node: DiffableNode, parentPath: readonly string[]): Sc
6465
{
6566
path,
6667
reason: 'not-expected',
67-
message: pathMessage(path),
6868
actual: node,
6969
},
7070
...node.children().flatMap((c) => emitExtraSubtree(c, path)),
@@ -98,7 +98,6 @@ function diffPair(
9898
issues.push({
9999
path,
100100
reason: 'not-equal',
101-
message: pathMessage(path),
102101
expected,
103102
actual,
104103
});
@@ -108,7 +107,8 @@ function diffPair(
108107
}
109108

110109
/**
111-
* Align one level of nodes by id; emit issues in input order and recurse.
110+
* Align one level of nodes by `(nodeKind, id)`; emit issues in input order
111+
* and recurse.
112112
*
113113
* A missing node emits one issue for itself and one for every node in its
114114
* subtree (total descent). Same for extra nodes. A matched pair recurses via

packages/1-framework/1-core/framework-components/test/schema-diff.test.ts

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { diffSchemas, SchemaDiff } from '../src/control/schema-diff';
66
function rootOf(nodes: readonly DiffableNode[]): DiffableNode {
77
return {
88
id: 'root',
9+
nodeKind: 'root',
910
isEqualTo(): boolean {
1011
return true;
1112
},
@@ -19,14 +20,20 @@ function makeNode(
1920
nodeId: string,
2021
body = '',
2122
childNodes: readonly DiffableNode[] = [],
23+
nodeKind = 'widget',
2224
): DiffableNode {
2325
return {
2426
id: nodeId,
27+
nodeKind,
2528
children(): readonly DiffableNode[] {
2629
return childNodes;
2730
},
2831
isEqualTo(other: DiffableNode): boolean {
29-
return nodeId === other.id && body === (other as typeof this & { _body?: string })._body;
32+
return (
33+
nodeId === other.id &&
34+
nodeKind === other.nodeKind &&
35+
body === (other as typeof this & { _body?: string })._body
36+
);
3037
},
3138
_body: body,
3239
} as DiffableNode & { _body: string };
@@ -105,10 +112,9 @@ describe('diffSchemas', () => {
105112
expect(issues).toHaveLength(2);
106113
});
107114

108-
it('message field is a non-empty string', () => {
115+
it('issues do not carry a message field', () => {
109116
const issues = diffSchemas(rootOf([makeNode('ns/x/y')]), rootOf([]));
110-
expect(typeof issues[0]?.message).toBe('string');
111-
expect((issues[0]?.message.length ?? 0) > 0).toBe(true);
117+
expect(issues[0]).not.toHaveProperty('message');
112118
});
113119

114120
it('missing issue carries expected node ref but no actual', () => {
@@ -162,6 +168,47 @@ describe('diffSchemas', () => {
162168
);
163169
});
164170

171+
it('throws when two siblings share the same id AND the same nodeKind (genuine duplicate)', () => {
172+
const a = makeNode('public', 'a', [], 'role');
173+
const b = makeNode('public', 'b', [], 'role');
174+
expect(() => diffSchemas(rootOf([a, b]), rootOf([]))).toThrow(
175+
'diffSchemas: duplicate id among siblings',
176+
);
177+
});
178+
179+
it('a same-id sibling pair with different nodeKind does not throw and pairs independently (role "public" vs namespace "public")', () => {
180+
const role = makeNode('public', 'role-body', [], 'role');
181+
const namespace = makeNode('public', 'namespace-body', [], 'namespace');
182+
const expected = [role, namespace];
183+
const actual = [
184+
makeNode('public', 'role-body', [], 'role'),
185+
makeNode('public', 'namespace-body-v2', [], 'namespace'),
186+
];
187+
188+
const issues = diffSchemas(rootOf(expected), rootOf(actual));
189+
190+
// The role pairs cleanly (equal bodies); the namespace pairs and mismatches.
191+
// Neither is reported as missing/extra — proof the two same-id, different-kind
192+
// siblings were never conflated into one map slot.
193+
expect(issues).toHaveLength(1);
194+
expect(issues[0]).toMatchObject({ reason: 'not-equal', path: ['root', 'public'] });
195+
expect((issues[0]?.expected as DiffableNode | undefined)?.nodeKind).toBe('namespace');
196+
});
197+
198+
it('a nodeKind-only-different sibling pair reports missing+extra when one side lacks the counterpart kind', () => {
199+
// Expected has only a role named "public"; actual has only a namespace named
200+
// "public". Same id, different nodeKind on each side — they must not pair
201+
// against each other; each is reported independently.
202+
const expected = [makeNode('public', 'role-body', [], 'role')];
203+
const actual = [makeNode('public', 'namespace-body', [], 'namespace')];
204+
205+
const issues = diffSchemas(rootOf(expected), rootOf(actual));
206+
207+
expect(issues).toHaveLength(2);
208+
const reasons = new Set(issues.map((i) => i.reason));
209+
expect(reasons).toEqual(new Set(['not-found', 'not-expected']));
210+
});
211+
165212
it('descends into a matched pair and reports one issue at the child path (AC-2)', () => {
166213
// A parent present on both sides whose id() matches and isEqualTo is true,
167214
// but whose children differ on one child. diffSchemas descends the matched
@@ -242,7 +289,7 @@ describe('diffSchemas', () => {
242289
});
243290

244291
function makeSchemaDiffIssue(path: readonly string[]): SchemaDiffIssue {
245-
return { path, reason: 'not-expected', message: `extra: ${path.join('/')}` };
292+
return { path, reason: 'not-expected' };
246293
}
247294

248295
describe('SchemaDiff', () => {

packages/1-framework/3-tooling/cli/src/commands/db-verify.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,9 @@ async function executeDbVerifyCommand(
438438
schema: {
439439
summary: combined.result.summary,
440440
strict: combined.result.meta?.strict ?? false,
441-
warnings: (combined.result.schema.warnings?.issues ?? []).map((issue) => issue.message),
441+
warnings: (combined.result.schema.warnings?.issues ?? []).map((issue) =>
442+
issue.path.join('/'),
443+
),
442444
},
443445
unclaimed: combined.unclaimed,
444446
meta: {

packages/1-framework/3-tooling/cli/src/utils/formatters/errors.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ export function formatErrorOutput(error: CliErrorEnvelope, flags: GlobalFlags):
5050
}
5151
// Show issues list if present (always show a short list; show full list when verbose).
5252
// `issues` is a shared error-envelope field: PSL interpretation diagnostics stamp
53-
// `kind` with their diagnostic code (e.g. `PSL_ORPHANED_BACKRELATION_LIST`); schema-diff
54-
// issues carry no `kind` and stamp `reason` instead (e.g. `not-found`). Prefer whichever
55-
// the producer supplied.
53+
// `kind` and `message` (their diagnostic code and prose); schema-diff issues
54+
// (`SchemaDiffIssue`) carry no `message` and stamp `reason` and `path` instead.
5655
if (error.meta?.['issues']) {
5756
const issues = error.meta['issues'] as readonly {
5857
kind?: string;
5958
reason?: string;
6059
message?: string;
60+
path?: readonly string[];
6161
}[];
6262
if (issues.length > 0) {
6363
const maxToShow = isVerbose(flags, 1) ? issues.length : Math.min(3, issues.length);
@@ -67,7 +67,7 @@ export function formatErrorOutput(error: CliErrorEnvelope, flags: GlobalFlags):
6767
lines.push(`${formatDimText(header)}`);
6868
for (const issue of issues.slice(0, maxToShow)) {
6969
const label = issue.kind ?? issue.reason ?? 'issue';
70-
const message = issue.message ?? '';
70+
const message = issue.message ?? issue.path?.join('/') ?? '';
7171
lines.push(`${formatDimText(` - [${label}] ${message}`)}`);
7272
}
7373
if (!isVerbose(flags, 1) && issues.length > maxToShow) {

packages/1-framework/3-tooling/cli/src/utils/formatters/verify.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ const REASON_LABEL: Record<ExpectationFailureReason, string> = {
2121
};
2222

2323
/**
24-
* The issue's display text: its own message (the differ's `path`), prefixed
25-
* with a human label for why it's flagged. Turning `reason` into a label is
26-
* this formatter's job, not the differ's — the differ's issue is data
27-
* (`path` + `reason` + nodes), not prose.
24+
* The issue's display text: its own path, prefixed with a human label for
25+
* why it's flagged. Turning `reason` (and the path) into prose is this
26+
* formatter's job, not the differ's — the differ's issue is data (`path` +
27+
* `reason` + nodes), not prose.
2828
*/
2929
function formatIssueMessage(issue: SchemaDiffIssue): string {
30-
return `${REASON_LABEL[issue.reason]}: ${issue.message}`;
30+
return `${REASON_LABEL[issue.reason]}: ${issue.path.join('/')}`;
3131
}
3232

3333
// ============================================================================

packages/1-framework/3-tooling/cli/test/control-api/client.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,6 @@ describe('ControlClient progress emission', () => {
285285
{
286286
path: ['root'],
287287
reason: 'not-found',
288-
message: 'Schema mismatch',
289288
},
290289
],
291290
},

packages/1-framework/3-tooling/cli/test/output.errors.test.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,32 +89,41 @@ describe('formatErrorOutput - conflicts', () => {
8989
});
9090
});
9191

92-
describe('formatErrorOutput - issues list label fallback', () => {
93-
it('labels a PSL-diagnostic issue by its `kind` and a schema-diff issue by its `reason`', () => {
94-
// The `meta.issues` channel is shared: PSL interpretation diagnostics stamp
95-
// `kind` (their diagnostic code); schema-diff issues carry no `kind` and stamp
96-
// `reason` instead. The renderer prefers `kind`, falls back to `reason`, then
97-
// to a generic `issue` label.
92+
describe('formatErrorOutput - issues list label and body fallback', () => {
93+
it('renders a PSL-diagnostic issue as `[kind] message`', () => {
94+
// PSL interpretation diagnostics stamp `kind` (their diagnostic code) and `message` (their prose).
9895
const error: CliErrorEnvelope = {
9996
...baseError,
10097
code: 'PN-RUN-3000',
10198
domain: 'RUN',
10299
summary: 'Failed to resolve contract source',
103100
meta: {
104-
issues: [
105-
{ kind: 'PSL_ORPHANED_BACKRELATION_LIST', message: 'orphaned backrelation list' },
106-
{ reason: 'not-found', message: 'Table "post" is missing from database' },
107-
{ message: 'issue with neither kind nor reason' },
108-
],
101+
issues: [{ kind: 'PSL_ORPHANED_BACKRELATION_LIST', message: 'orphaned backrelation list' }],
109102
},
110103
};
111104

112105
const flags = parseGlobalFlags({ verbose: true, 'no-color': true });
113106
const stripped = stripAnsi(formatErrorOutput(error, flags));
114107

115108
expect(stripped).toContain('[PSL_ORPHANED_BACKRELATION_LIST] orphaned backrelation list');
116-
expect(stripped).toContain('[not-found] Table "post" is missing from database');
117-
expect(stripped).toContain('[issue] issue with neither kind nor reason');
109+
});
110+
111+
it('renders a schema-diff issue (no `message`) as `[reason] path/joined/with/slashes`', () => {
112+
// Schema-diff issues (SchemaDiffIssue) carry no `message`; they stamp `reason` and `path`.
113+
const error: CliErrorEnvelope = {
114+
...baseError,
115+
code: 'PN-RUN-3000',
116+
domain: 'RUN',
117+
summary: 'Failed to resolve contract source',
118+
meta: {
119+
issues: [{ reason: 'not-found', path: ['public', 'post'] }],
120+
},
121+
};
122+
123+
const flags = parseGlobalFlags({ verbose: true, 'no-color': true });
124+
const stripped = stripAnsi(formatErrorOutput(error, flags));
125+
126+
expect(stripped).toContain('[not-found] public/post');
118127
});
119128
});
120129

packages/1-framework/3-tooling/cli/test/output.test.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,6 @@ describe('formatSchemaVerifyOutput', () => {
335335
const missingTableIssue: SchemaDiffIssue = {
336336
path: ['post'],
337337
reason: 'not-found',
338-
message: 'post',
339338
};
340339

341340
const createResult = (): VerifyDatabaseSchemaResult => ({
@@ -377,7 +376,6 @@ describe('formatSchemaVerifyOutput', () => {
377376
const diffIssue: SchemaDiffIssue = {
378377
path: ['public', 'profiles', 'policy_abc'],
379378
reason: 'not-found',
380-
message: 'public/profiles/policy_abc',
381379
};
382380
const result: VerifyDatabaseSchemaResult = {
383381
...createResult(),
@@ -390,8 +388,10 @@ describe('formatSchemaVerifyOutput', () => {
390388
const output = formatSchemaVerifyOutput(result, flags);
391389
const lines = output.split('\n').map(stripAnsi);
392390

393-
const issueLineIndex = lines.findIndex((line) => line.includes(missingTableIssue.message));
394-
const diffLineIndex = lines.findIndex((line) => line.includes(diffIssue.message));
391+
const issueLineIndex = lines.findIndex((line) =>
392+
line.includes(missingTableIssue.path.join('/')),
393+
);
394+
const diffLineIndex = lines.findIndex((line) => line.includes(diffIssue.path.join('/')));
395395

396396
expect(issueLineIndex).toBeGreaterThanOrEqual(0);
397397
expect(diffLineIndex).toBeGreaterThan(issueLineIndex);
@@ -427,7 +427,6 @@ describe('formatSchemaVerifyOutput', () => {
427427
{
428428
path: ['database', 'public', 'legacy_jobs'],
429429
reason: 'not-found',
430-
message: 'database/public/legacy_jobs',
431430
},
432431
],
433432
},
@@ -562,7 +561,6 @@ describe('formatSchemaVerifyOutput', () => {
562561
{
563562
path: ['public', 'profiles', policyWireName],
564563
reason: 'not-found',
565-
message: `RLS policy "${policyWireName}" on table "profiles" is missing from the database`,
566564
},
567565
],
568566
},
@@ -695,12 +693,10 @@ describe('formatSchemaVerifyJson', () => {
695693
{
696694
path: ['post'],
697695
reason: 'not-found',
698-
message: 'Table "post" is missing',
699696
},
700697
{
701698
path: ['public', 'profiles', 'policy_abc'],
702699
reason: 'not-found',
703-
message: 'RLS policy "policy_abc" is missing from the database',
704700
},
705701
],
706702
},

0 commit comments

Comments
 (0)