Skip to content

Commit 3b4ddd3

Browse files
committed
feat: add tests for std::list::push parameter unblocking behavior in schema
1 parent 86d15b3 commit 3b4ddd3

1 file changed

Lines changed: 224 additions & 0 deletions

File tree

test/schema/schema.test.ts

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,146 @@ describe("Schema", () => {
10501050
expect(paths).not.toContain("chain.next.next.next.next.next.next.value");
10511051
});
10521052

1053+
it('unblocks std::list::push item once the list is provided, even as an empty literal', () => {
1054+
// std::list::push → <T>(list: LIST<T>, item: T): NUMBER
1055+
// `item` shares the type parameter T with `list`, so it starts out
1056+
// blockedBy [0]. Providing the `list` argument satisfies that dependency,
1057+
// so `item` becomes unblocked — an empty list literal `[]` counts as a
1058+
// provided value just like a concrete list does.
1059+
const buildPushFlow = (listValue: any): Flow => ({
1060+
id: "gid://sagittarius/Flow/1",
1061+
startingNodeId: "gid://sagittarius/NodeFunction/1",
1062+
signature: "(): void",
1063+
nodes: {
1064+
nodes: [
1065+
{
1066+
id: "gid://sagittarius/NodeFunction/1",
1067+
functionDefinition: {identifier: "std::list::push"},
1068+
parameters: {
1069+
nodes: [
1070+
{value: listValue},
1071+
{value: null},
1072+
],
1073+
},
1074+
},
1075+
],
1076+
},
1077+
});
1078+
1079+
// With no list provided, `item` is blocked by the list parameter (index 0).
1080+
const blockedResult = getSignatureSchema(
1081+
buildPushFlow(null),
1082+
DATA_TYPES,
1083+
FUNCTION_SIGNATURES,
1084+
"gid://sagittarius/NodeFunction/1",
1085+
);
1086+
expect(blockedResult.parameters[1].blockedBy).toEqual([0]);
1087+
1088+
// Providing an empty list literal `[]` satisfies the dependency, so the
1089+
// `item` parameter is now unblocked.
1090+
const emptyResult = getSignatureSchema(
1091+
buildPushFlow({__typename: "LiteralValue", value: []}),
1092+
DATA_TYPES,
1093+
FUNCTION_SIGNATURES,
1094+
"gid://sagittarius/NodeFunction/1",
1095+
);
1096+
const [emptyList, emptyItem] = emptyResult.parameters;
1097+
1098+
expect(emptyList.schema.input).toBe("list");
1099+
expect(emptyList.blockedBy).toEqual([]);
1100+
expect(emptyItem.blockedBy).toEqual([]);
1101+
});
1102+
1103+
it('keeps std::list::push item blocked while the list has no value', () => {
1104+
// std::list::push → <T>(list: LIST<T>, item: T): NUMBER
1105+
// As long as the `list` parameter (index 0) carries no value, T cannot be
1106+
// pinned, so `item` (index 1) must stay blockedBy [0] and fall back to a
1107+
// generic input. This holds whether the list slot is `null` or an explicit
1108+
// null-typed value.
1109+
const buildPushFlow = (listValue: any): Flow => ({
1110+
id: "gid://sagittarius/Flow/1",
1111+
startingNodeId: "gid://sagittarius/NodeFunction/1",
1112+
signature: "(): void",
1113+
nodes: {
1114+
nodes: [
1115+
{
1116+
id: "gid://sagittarius/NodeFunction/1",
1117+
functionDefinition: {identifier: "std::list::push"},
1118+
parameters: {
1119+
nodes: [
1120+
{value: listValue},
1121+
{value: null},
1122+
],
1123+
},
1124+
},
1125+
],
1126+
},
1127+
});
1128+
1129+
// No parameter object at all for the list slot.
1130+
const nullResult = getSignatureSchema(
1131+
buildPushFlow(null),
1132+
DATA_TYPES,
1133+
FUNCTION_SIGNATURES,
1134+
"gid://sagittarius/NodeFunction/1",
1135+
);
1136+
const [nullList, nullItem] = nullResult.parameters;
1137+
1138+
// The list itself is never blocked — nothing feeds it.
1139+
expect(nullList.blockedBy).toEqual([]);
1140+
// item stays blocked by the list and has no concrete type yet.
1141+
expect(nullItem.blockedBy).toEqual([0]);
1142+
expect(nullItem.schema.input).toBe("generic");
1143+
});
1144+
1145+
it('keeps a later std::list::push item blocked when its list references an earlier node', () => {
1146+
// Two-node flow. Node 1 (std::list::push) returns NUMBER. Node 2 is another
1147+
// std::list::push whose `list` parameter is still empty, so its `item`
1148+
// parameter must stay blockedBy [0] — an unrelated, already-configured
1149+
// predecessor node does not pin node 2's T.
1150+
const flow: Flow = {
1151+
id: "gid://sagittarius/Flow/1",
1152+
startingNodeId: "gid://sagittarius/NodeFunction/1",
1153+
signature: "(): void",
1154+
nodes: {
1155+
nodes: [
1156+
{
1157+
id: "gid://sagittarius/NodeFunction/1",
1158+
functionDefinition: {identifier: "std::list::push"},
1159+
nextNodeId: "gid://sagittarius/NodeFunction/2",
1160+
parameters: {
1161+
nodes: [
1162+
{value: {__typename: "LiteralValue", value: [1, 2, 3]}},
1163+
{value: {__typename: "LiteralValue", value: 4}},
1164+
],
1165+
},
1166+
},
1167+
{
1168+
id: "gid://sagittarius/NodeFunction/2",
1169+
functionDefinition: {identifier: "std::list::push"},
1170+
parameters: {
1171+
nodes: [
1172+
{value: null},
1173+
{value: null},
1174+
],
1175+
},
1176+
},
1177+
],
1178+
},
1179+
};
1180+
1181+
const {parameters: [list, item]} = getSignatureSchema(
1182+
flow,
1183+
DATA_TYPES,
1184+
FUNCTION_SIGNATURES,
1185+
"gid://sagittarius/NodeFunction/2",
1186+
);
1187+
1188+
expect(list.blockedBy).toEqual([]);
1189+
expect(item.blockedBy).toEqual([0]);
1190+
expect(item.schema.input).toBe("generic");
1191+
});
1192+
10531193
describe("return schema", () => {
10541194
// Single-node flow calling `identifier` with the given parameters, probed at that node.
10551195
const singleNode = (identifier: string, params: any[]): Flow => ({
@@ -1167,6 +1307,90 @@ describe("Schema", () => {
11671307
expect(result.return).toEqual({input: "generic", type: "void"});
11681308
});
11691309

1310+
// A trigger is analyzed at the flow level (no nodeId). The flow's own
1311+
// signature carries the return type, and its `settings` supply the
1312+
// arguments the generic return is instantiated from. This mirrors the
1313+
// REST trigger: <T>(input_schema: TYPE<T>, ...): REST_ADAPTER_INPUT<T>.
1314+
const restTrigger = (inputSchema: any): Flow => ({
1315+
id: "gid://sagittarius/Flow/1",
1316+
startingNodeId: "gid://sagittarius/NodeFunction/1",
1317+
signature:
1318+
"<T>(input_schema: TYPE<T>, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT<T>",
1319+
settings: {
1320+
nodes: [
1321+
{value: inputSchema},
1322+
{value: "/users"},
1323+
{value: "GET"},
1324+
],
1325+
},
1326+
nodes: {nodes: []},
1327+
} as Flow);
1328+
1329+
it("resolves a trigger's return schema at the flow level (no nodeId) from its settings", () => {
1330+
const result = getSignatureSchema(
1331+
restTrigger({name: "text"}),
1332+
DATA_TYPES,
1333+
FUNCTION_SIGNATURES,
1334+
);
1335+
1336+
// Flow-level probe → no target node.
1337+
expect(result.nodeId).toBeUndefined();
1338+
1339+
// REST_ADAPTER_INPUT<T> is an object → data input with its four fields.
1340+
const ret = result.return as {
1341+
input: string;
1342+
properties: Record<string, any>;
1343+
required: string[];
1344+
};
1345+
expect(ret.input).toBe("data");
1346+
expect(Object.keys(ret.properties)).toEqual(
1347+
expect.arrayContaining([
1348+
"payload",
1349+
"headers",
1350+
"query_params",
1351+
"path_params",
1352+
]),
1353+
);
1354+
expect(ret.required).toEqual(
1355+
expect.arrayContaining([
1356+
"payload",
1357+
"headers",
1358+
"query_params",
1359+
"path_params",
1360+
]),
1361+
);
1362+
1363+
// T is bound from the input_schema setting ({name: TEXT}), so the
1364+
// payload keeps that concrete shape.
1365+
const payload = ret.properties.payload;
1366+
expect(payload.input).toBe("data");
1367+
expect(Object.keys(payload.properties)).toEqual(["name"]);
1368+
expect(payload.properties.name.input).toBe("text");
1369+
1370+
// The remaining REST fields are open objects.
1371+
expect(ret.properties.headers.input).toBe("data");
1372+
expect(ret.properties.query_params.input).toBe("data");
1373+
expect(ret.properties.path_params.input).toBe("data");
1374+
1375+
// A return type describes an output → no suggestions anywhere.
1376+
expectNoSuggestionsAnywhere(ret);
1377+
});
1378+
1379+
it("instantiates the trigger's generic return payload from a primitive input_schema setting", () => {
1380+
const result = getSignatureSchema(
1381+
restTrigger(42),
1382+
DATA_TYPES,
1383+
FUNCTION_SIGNATURES,
1384+
);
1385+
1386+
expect(result.nodeId).toBeUndefined();
1387+
1388+
// input_schema = 42 → T = NUMBER → payload is a number input.
1389+
const ret = result.return as {properties: Record<string, any>};
1390+
expect(ret.properties.payload).toEqual({input: "number", type: "number"});
1391+
expectNoSuggestionsAnywhere(ret);
1392+
});
1393+
11701394
it("never carries suggestions, whatever the return type", () => {
11711395
// Primitive, object, list and generic returns all stay suggestion-free.
11721396
expectNoSuggestionsAnywhere(

0 commit comments

Comments
 (0)