Skip to content

Commit 3e71356

Browse files
committed
test(vnext): enforce completion coverage gates
1 parent e0efb40 commit 3e71356

2 files changed

Lines changed: 1411 additions & 3 deletions

File tree

src/vnext/__tests__/relation-completion.test.ts

Lines changed: 267 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "../local-relation-site.js";
1616
import {
1717
POSTGRESQL_SQL_RELATION_DIALECT,
18+
type SqlRelationDialectRuntime,
1819
} from "../relation-dialect.js";
1920
import {
2021
createIdentitySqlSource,
@@ -28,6 +29,7 @@ import type {
2829
SqlCatalogRelationKind,
2930
SqlCatalogReadyCoverage,
3031
SqlCompletionList,
32+
SqlRelationCompletionDialectRuntime,
3133
} from "../relation-completion-types.js";
3234
import type {
3335
SqlTextRange,
@@ -112,6 +114,7 @@ function relationPath(
112114
function catalogRelation(input: {
113115
readonly completionPathStart?: number;
114116
readonly containers?: readonly string[];
117+
readonly detail?: string;
115118
readonly entityId: string;
116119
readonly kind?: SqlCatalogRelationKind;
117120
readonly match?: "equivalent" | "exact";
@@ -133,7 +136,7 @@ function catalogRelation(input: {
133136
completionPathStart === 0
134137
? path
135138
: relationPath([], input.name);
136-
return Object.freeze({
139+
const relation = {
137140
canonicalPath: path,
138141
completionPath,
139142
completionPathStart,
@@ -143,7 +146,12 @@ function catalogRelation(input: {
143146
entityId: input.entityId,
144147
matchQuality: input.match ?? "exact",
145148
relationKind: input.kind ?? "table",
146-
});
149+
};
150+
return Object.freeze(
151+
input.detail === undefined
152+
? relation
153+
: { ...relation, detail: input.detail },
154+
);
147155
}
148156

149157
function readyResponse(
@@ -363,6 +371,263 @@ describe("relation completion composition", () => {
363371
},
364372
);
365373

374+
it("composes terminal loading and failed provider evidence", () => {
375+
const loading = compose(
376+
"SELECT * FROM |",
377+
usable(
378+
Object.freeze({
379+
epoch: Object.freeze({
380+
generation: 2,
381+
token: "loading",
382+
}),
383+
status: "loading",
384+
}),
385+
),
386+
);
387+
expect(loading.issues).toEqual([
388+
{
389+
reason: "catalog-loading",
390+
remainingIntentLeaseMs: 25,
391+
},
392+
]);
393+
394+
const local = markedLocalSite("SELECT * FROM |");
395+
const failed = composeSqlRelationCompletion({
396+
catalogOutcome: usable(
397+
Object.freeze({
398+
code: "authentication",
399+
epoch: Object.freeze({
400+
generation: 2,
401+
token: "failed",
402+
}),
403+
retry: "after-invalidation",
404+
status: "failed",
405+
}),
406+
),
407+
dialect: POSTGRESQL_SQL_RELATION_DIALECT,
408+
localSite: local.localSite,
409+
providerId: "catalog",
410+
remainingIntentLeaseMs: 0,
411+
replacementRange: local.replacementRange,
412+
statementOffset: local.statementOffset,
413+
});
414+
expect(failed.value.issues).toEqual([
415+
{ reason: "catalog-failed" },
416+
]);
417+
expect(failed.sources).toEqual([
418+
{
419+
code: "authentication",
420+
feature: "relation-catalog",
421+
outcome: "failed",
422+
providerId: "catalog",
423+
retry: "after-invalidation",
424+
},
425+
]);
426+
});
427+
428+
it("fails closed for uncertain and hostile CTE prefix policy", () => {
429+
const local = markedLocalSite(
430+
"WITH local_table AS (SELECT 1) SELECT * FROM |",
431+
);
432+
const complete = (
433+
cteIdentifierMatchesPrefix:
434+
SqlRelationCompletionDialectRuntime["cteIdentifierMatchesPrefix"],
435+
): SqlCompletionList => {
436+
const completion = Object.freeze({
437+
...POSTGRESQL_SQL_RELATION_DIALECT.completion,
438+
cteIdentifierMatchesPrefix,
439+
});
440+
const dialect: SqlRelationDialectRuntime = Object.freeze({
441+
...POSTGRESQL_SQL_RELATION_DIALECT,
442+
completion,
443+
});
444+
return composeSqlRelationCompletion({
445+
catalogOutcome: null,
446+
dialect,
447+
localSite: local.localSite,
448+
providerId: null,
449+
remainingIntentLeaseMs: 0,
450+
replacementRange: local.replacementRange,
451+
statementOffset: local.statementOffset,
452+
}).value;
453+
};
454+
455+
const unknown = complete(() => "unknown");
456+
expect(unknown.items).toEqual([]);
457+
expect(unknown.issues).toContainEqual({
458+
reason: "cte-scope-uncertainty",
459+
});
460+
461+
const throwing = complete(() => {
462+
throw new Error("hostile prefix policy");
463+
});
464+
expect(throwing.items).toEqual([]);
465+
expect(throwing.issues).toContainEqual({
466+
reason: "cte-scope-uncertainty",
467+
});
468+
469+
const noMatch = complete(() => "no-match");
470+
expect(noMatch.items).toEqual([]);
471+
expect(noMatch.isIncomplete).toBe(false);
472+
});
473+
474+
it("retains catalog evidence when CTE shadow comparison is uncertain", () => {
475+
const local = markedLocalSite(
476+
"WITH users AS (SELECT 1) SELECT * FROM |",
477+
);
478+
const completion = Object.freeze({
479+
...POSTGRESQL_SQL_RELATION_DIALECT.completion,
480+
compareCteIdentifiers: () => {
481+
throw new Error("hostile comparison policy");
482+
},
483+
renderRelationPath: () =>
484+
Object.freeze({
485+
reason: "illegal-role-sequence" as const,
486+
status: "unsupported" as const,
487+
}),
488+
});
489+
const dialect: SqlRelationDialectRuntime = Object.freeze({
490+
...POSTGRESQL_SQL_RELATION_DIALECT,
491+
completion,
492+
});
493+
const result = composeSqlRelationCompletion({
494+
catalogOutcome: usable(
495+
readyResponse([
496+
catalogRelation({
497+
detail: "A catalog relation",
498+
entityId: "users",
499+
name: "users",
500+
}),
501+
]),
502+
),
503+
dialect,
504+
localSite: local.localSite,
505+
providerId: "catalog",
506+
remainingIntentLeaseMs: 0,
507+
replacementRange: local.replacementRange,
508+
statementOffset: local.statementOffset,
509+
});
510+
511+
expect(result.value.items).toHaveLength(2);
512+
expect(result.value.items[1]).toMatchObject({
513+
detail: "A catalog relation",
514+
label: "users",
515+
provenance: { entityId: "users" },
516+
});
517+
expect(result.value.issues).toContainEqual({
518+
reason: "cte-scope-uncertainty",
519+
});
520+
521+
if (local.localSite.local.kind !== "unqualified") {
522+
throw new Error("Expected an unqualified local site");
523+
}
524+
const uncertainSite: typeof local.localSite = Object.freeze({
525+
...local.localSite,
526+
local: Object.freeze({
527+
...local.localSite.local,
528+
cteVisibility: Object.freeze({
529+
...local.localSite.local.cteVisibility,
530+
quality: "recovered" as const,
531+
shadowing: Object.freeze({
532+
coverage: "unknown" as const,
533+
}),
534+
}),
535+
}),
536+
});
537+
const unknownCoverage = composeSqlRelationCompletion({
538+
catalogOutcome: usable(
539+
readyResponse([
540+
catalogRelation({
541+
entityId: "users",
542+
name: "users",
543+
}),
544+
]),
545+
),
546+
dialect: POSTGRESQL_SQL_RELATION_DIALECT,
547+
localSite: uncertainSite,
548+
providerId: "catalog",
549+
remainingIntentLeaseMs: 0,
550+
replacementRange: local.replacementRange,
551+
statementOffset: local.statementOffset,
552+
});
553+
expect(unknownCoverage.value.items).toHaveLength(2);
554+
expect(unknownCoverage.value.issues).toContainEqual({
555+
reason: "cte-scope-uncertainty",
556+
});
557+
});
558+
559+
it("surfaces recursive CTE and opaque query-site recovery", () => {
560+
const recursive = compose(
561+
"WITH RECURSIVE local_table AS (SELECT * FROM |) SELECT * FROM local_table",
562+
null,
563+
);
564+
expect(recursive.issues.map((issue) => issue.reason)).toEqual([
565+
"cte-scope-uncertainty",
566+
"recursive-cte-uncertainty",
567+
]);
568+
569+
const local = markedLocalSite("SELECT * FROM |");
570+
const opaqueSite: typeof local.localSite = Object.freeze({
571+
...local.localSite,
572+
querySite: Object.freeze({
573+
...local.localSite.querySite,
574+
recognition: Object.freeze({
575+
issues: Object.freeze([
576+
"opaque-template-context",
577+
] as const),
578+
quality: "recovered" as const,
579+
}),
580+
}),
581+
});
582+
const opaque = composeSqlRelationCompletion({
583+
catalogOutcome: null,
584+
dialect: POSTGRESQL_SQL_RELATION_DIALECT,
585+
localSite: opaqueSite,
586+
providerId: null,
587+
remainingIntentLeaseMs: 0,
588+
replacementRange: local.replacementRange,
589+
statementOffset: local.statementOffset,
590+
}).value;
591+
expect(opaque.issues).toContainEqual({
592+
reason: "opaque-template-context",
593+
});
594+
});
595+
596+
it("uses path and entity IDs as deterministic final catalog ties", () => {
597+
const relations = [
598+
catalogRelation({
599+
completionPathStart: 0,
600+
containers: ["zeta"],
601+
entityId: "z-last",
602+
name: "users",
603+
}),
604+
catalogRelation({
605+
completionPathStart: 0,
606+
containers: ["alpha"],
607+
entityId: "b",
608+
name: "users",
609+
}),
610+
catalogRelation({
611+
completionPathStart: 0,
612+
containers: ["alpha"],
613+
entityId: "a",
614+
name: "users",
615+
}),
616+
];
617+
const value = compose(
618+
"SELECT * FROM |",
619+
usable(readyResponse(relations)),
620+
);
621+
expect(
622+
value.items.map(
623+
(item) =>
624+
item.provenance.kind === "catalog"
625+
? item.provenance.entityId
626+
: "cte",
627+
),
628+
).toEqual(["a", "b", "z-last"]);
629+
});
630+
366631
it("applies the result cap after ranking and returns deeply frozen output", () => {
367632
const declarations = Array.from(
368633
{ length: MAX_RELATION_COMPLETION_RESULTS + 1 },

0 commit comments

Comments
 (0)