Skip to content

Commit 3b2c069

Browse files
committed
fix: new test cases for schema extractions and suggestions
1 parent f437874 commit 3b2c069

1 file changed

Lines changed: 282 additions & 1 deletion

File tree

test/schema/schema.test.ts

Lines changed: 282 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {describe, it} from "vitest";
1+
import {describe, expect, it} from "vitest";
22
import {Flow} from "@code0-tech/sagittarius-graphql-types";
33
import {getSignatureSchema, getTypeSchema} from "../../src";
44
import {DATA_TYPES, FUNCTION_SIGNATURES} from "../data";
@@ -276,4 +276,285 @@ describe("Schema", () => {
276276
//console.dir(result, {depth: null})
277277
});
278278

279+
it('merges function-typed select suggestions with concrete node value', () => {
280+
// http_method has function-declared type HTTP_METHOD ('GET' | 'POST' | ...).
281+
// Node value "GET" must not collapse the select suggestions to just ['GET'].
282+
const flow: Flow = {
283+
id: "gid://sagittarius/Flow/1",
284+
startingNodeId: "gid://sagittarius/NodeFunction/1",
285+
signature: "(): void",
286+
nodes: {
287+
nodes: [
288+
{
289+
id: "gid://sagittarius/NodeFunction/1",
290+
functionDefinition: {identifier: "http::request::send"},
291+
parameters: {
292+
nodes: [
293+
{value: {__typename: "LiteralValue", value: "GET"}},
294+
{value: {__typename: "LiteralValue", value: "/x"}},
295+
{value: null},
296+
{value: null},
297+
{value: null},
298+
{value: null},
299+
{value: null},
300+
{value: null},
301+
],
302+
},
303+
},
304+
],
305+
},
306+
};
307+
308+
const [first] = getSignatureSchema(
309+
flow,
310+
DATA_TYPES,
311+
FUNCTION_SIGNATURES,
312+
"gid://sagittarius/NodeFunction/1",
313+
);
314+
315+
expect(first.schema.input).toBe("select");
316+
const values = (first.schema.suggestions ?? []).map((s: any) => s.value);
317+
expect(values).toEqual(
318+
expect.arrayContaining(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"]),
319+
);
320+
});
321+
322+
it('return: ReferenceValue (payload) vs LiteralValue feeding a generic T parameter', () => {
323+
// Two-node flow:
324+
// node 1: http::request::send → returns HTTP_RESPONSE<any> ({ payload, headers, http_status_code }).
325+
// node 2: std::control::return<T>(value: T)
326+
// We probe node 2's parameter schema once with a ReferenceValue that pulls
327+
// the .payload of node 1, and once with a LiteralValue. Both feed the same
328+
// generic T parameter, so the schema's structural shape is driven by the
329+
// node side (function side is generic).
330+
const buildFlow = (returnValue: any): Flow => ({
331+
id: "gid://sagittarius/Flow/1",
332+
startingNodeId: "gid://sagittarius/NodeFunction/1",
333+
signature: "(): void",
334+
nodes: {
335+
nodes: [
336+
{
337+
id: "gid://sagittarius/NodeFunction/1",
338+
functionDefinition: {identifier: "http::request::send"},
339+
nextNodeId: "gid://sagittarius/NodeFunction/2",
340+
parameters: {
341+
nodes: [
342+
{value: {__typename: "LiteralValue", value: "GET"}},
343+
{value: {__typename: "LiteralValue", value: "/x"}},
344+
{value: null},
345+
{value: null},
346+
{value: null},
347+
{value: null},
348+
{value: null},
349+
{value: null},
350+
],
351+
},
352+
},
353+
{
354+
id: "gid://sagittarius/NodeFunction/2",
355+
functionDefinition: {identifier: "std::control::return"},
356+
parameters: {
357+
nodes: [{value: returnValue}],
358+
},
359+
},
360+
],
361+
},
362+
});
363+
364+
const refFlow = buildFlow({
365+
__typename: "ReferenceValue",
366+
nodeFunctionId: "gid://sagittarius/NodeFunction/1",
367+
referencePath: [{path: "payload"}],
368+
});
369+
const literalFlow = buildFlow({
370+
__typename: "LiteralValue",
371+
value: "hello",
372+
});
373+
374+
const [refResult] = getSignatureSchema(
375+
refFlow,
376+
DATA_TYPES,
377+
FUNCTION_SIGNATURES,
378+
"gid://sagittarius/NodeFunction/2",
379+
);
380+
const [literalResult] = getSignatureSchema(
381+
literalFlow,
382+
DATA_TYPES,
383+
FUNCTION_SIGNATURES,
384+
"gid://sagittarius/NodeFunction/2",
385+
);
386+
387+
// Function side is generic for both. The node side decides the structural shape.
388+
389+
// ReferenceValue → node value resolves to HTTP_RESPONSE<any>.payload = any.
390+
// `any` carries no structural info but its runtime shape is open — surface it
391+
// as a `data` schema (open object) rather than the dead-end `generic`.
392+
expect(refResult.schema.input).toBe("data");
393+
394+
// Every reference in scope (node 1 with each of its response paths, plus
395+
// top-level node/flow references and zero-arg node-function nodes) is
396+
// assignable to `any`, so all of them must be offered as suggestions.
397+
const refSuggestions = (refResult.schema.suggestions ?? []) as any[];
398+
expect(refSuggestions.length).toBeGreaterThan(0);
399+
expect(
400+
refSuggestions.some(
401+
(s) =>
402+
s.__typename === "ReferenceValue" &&
403+
s.nodeFunctionId === "gid://sagittarius/NodeFunction/1" &&
404+
Array.isArray(s.referencePath) &&
405+
s.referencePath.some((p: any) => p.path === "payload"),
406+
),
407+
).toBe(true);
408+
expect(
409+
refSuggestions.some(
410+
(s) =>
411+
s.__typename === "ReferenceValue" &&
412+
s.nodeFunctionId === "gid://sagittarius/NodeFunction/1" &&
413+
Array.isArray(s.referencePath) &&
414+
s.referencePath.some((p: any) => p.path === "headers"),
415+
),
416+
).toBe(true);
417+
expect(
418+
refSuggestions.some(
419+
(s) =>
420+
s.__typename === "ReferenceValue" &&
421+
s.nodeFunctionId === "gid://sagittarius/NodeFunction/1" &&
422+
Array.isArray(s.referencePath) &&
423+
s.referencePath.some((p: any) => p.path === "http_status_code"),
424+
),
425+
).toBe(true);
426+
427+
// LiteralValue "hello" → narrows T to the string literal "hello". The node
428+
// schema alone would be a select with one option; the merge must demote it
429+
// to free-form text because the function side is generic.
430+
expect(literalResult.schema.input).toBe("text");
431+
432+
const literalSuggestions = (literalResult.schema.suggestions ?? []) as any[];
433+
434+
// Suggestions are scoped by the *function* side (T → any), not by the narrow
435+
// "hello" literal. Every response field of node 1 is reachable, not just the
436+
// payload that happens to be `any`-typed.
437+
for (const expectedPath of ["payload", "headers", "http_status_code"]) {
438+
expect(
439+
literalSuggestions.some(
440+
(s) =>
441+
s.__typename === "ReferenceValue" &&
442+
s.nodeFunctionId === "gid://sagittarius/NodeFunction/1" &&
443+
Array.isArray(s.referencePath) &&
444+
s.referencePath.some((p: any) => p.path === expectedPath),
445+
),
446+
).toBe(true);
447+
}
448+
});
449+
450+
it('generic function parameter gives the same suggestion set regardless of the current literal kind', () => {
451+
// `std::control::return<T>(value: T)` accepts anything. The set of
452+
// suggestions the user can pick from should not shrink just because the
453+
// currently-set value happens to narrow T (e.g. to `boolean`).
454+
const buildFlow = (returnValue: any): Flow => ({
455+
id: "gid://sagittarius/Flow/1",
456+
startingNodeId: "gid://sagittarius/NodeFunction/1",
457+
signature: "(): void",
458+
nodes: {
459+
nodes: [
460+
{
461+
id: "gid://sagittarius/NodeFunction/1",
462+
functionDefinition: {identifier: "http::request::send"},
463+
nextNodeId: "gid://sagittarius/NodeFunction/2",
464+
parameters: {
465+
nodes: [
466+
{value: {__typename: "LiteralValue", value: "GET"}},
467+
{value: {__typename: "LiteralValue", value: "/x"}},
468+
{value: null},
469+
{value: null},
470+
{value: null},
471+
{value: null},
472+
{value: null},
473+
{value: null},
474+
],
475+
},
476+
},
477+
{
478+
id: "gid://sagittarius/NodeFunction/2",
479+
functionDefinition: {identifier: "std::control::return"},
480+
parameters: {
481+
nodes: [{value: returnValue}],
482+
},
483+
},
484+
],
485+
},
486+
});
487+
488+
const probe = (returnValue: any) => {
489+
const [r] = getSignatureSchema(
490+
buildFlow(returnValue),
491+
DATA_TYPES,
492+
FUNCTION_SIGNATURES,
493+
"gid://sagittarius/NodeFunction/2",
494+
);
495+
return ((r.schema.suggestions ?? []) as any[]).map((s) =>
496+
JSON.stringify(s),
497+
);
498+
};
499+
500+
// Snapshot the set of references/node-functions surfaced for each value
501+
// kind. Strip literal-value suggestions because those legitimately differ
502+
// (the empty-object case has no literal alternative; the boolean case
503+
// would surface `true`/`false` via getValues of the booleans constraint).
504+
const onlyRefsAndNodes = (sigs: string[]) =>
505+
sigs.filter((s) => !s.includes('"LiteralValue"'));
506+
507+
const objectSet = onlyRefsAndNodes(
508+
probe({__typename: "LiteralValue", value: {}}),
509+
);
510+
const boolSet = onlyRefsAndNodes(
511+
probe({__typename: "LiteralValue", value: true}),
512+
);
513+
const stringSet = onlyRefsAndNodes(
514+
probe({__typename: "LiteralValue", value: "x"}),
515+
);
516+
const numberSet = onlyRefsAndNodes(
517+
probe({__typename: "LiteralValue", value: 42}),
518+
);
519+
520+
// All sets are non-empty (the function accepts anything → references in
521+
// scope are valid candidates) and identical to each other.
522+
expect(objectSet.length).toBeGreaterThan(0);
523+
expect(new Set(boolSet)).toEqual(new Set(objectSet));
524+
expect(new Set(stringSet)).toEqual(new Set(objectSet));
525+
expect(new Set(numberSet)).toEqual(new Set(objectSet));
526+
});
527+
528+
it('demotes select to free-form when function parameter is generic', () => {
529+
// std::control::return<T>(value: T): T — function declares T, node sets a
530+
// string literal. Result must be text (free-form), NOT select with one option.
531+
const flow: Flow = {
532+
id: "gid://sagittarius/Flow/2",
533+
startingNodeId: "gid://sagittarius/NodeFunction/1",
534+
signature: "(): void",
535+
nodes: {
536+
nodes: [
537+
{
538+
id: "gid://sagittarius/NodeFunction/1",
539+
functionDefinition: {identifier: "std::control::return"},
540+
parameters: {
541+
nodes: [
542+
{value: {__typename: "LiteralValue", value: "Test"}},
543+
],
544+
},
545+
},
546+
],
547+
},
548+
};
549+
550+
const [first] = getSignatureSchema(
551+
flow,
552+
DATA_TYPES,
553+
FUNCTION_SIGNATURES,
554+
"gid://sagittarius/NodeFunction/1",
555+
);
556+
557+
expect(first.schema.input).toBe("text");
558+
});
559+
279560
})

0 commit comments

Comments
 (0)