Skip to content

Commit 7c352d9

Browse files
committed
Delineate between FlowRecordCreate and FlowRecordUpdate
- Refactor isRecordCreate and isRecordUpdate functions to include additional validation for required properties.
1 parent 3dd759c commit 7c352d9

7 files changed

Lines changed: 322 additions & 41 deletions

src/main/flow_parser.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,16 @@ function isLoop(node: flowTypes.FlowNode): node is flowTypes.FlowLoop {
687687
function isRecordCreate(
688688
node: flowTypes.FlowNode,
689689
): node is flowTypes.FlowRecordCreate {
690-
return (node as flowTypes.FlowRecordCreate).inputReference !== undefined;
690+
const recordCreate = node as flowTypes.FlowRecordCreate;
691+
// Check for required FlowRecordCreate properties
692+
const hasInputAssignments = recordCreate.inputAssignments !== undefined &&
693+
Array.isArray(recordCreate.inputAssignments);
694+
const hasObject = recordCreate.object !== undefined;
695+
// FlowRecordUpdate has filters, FlowRecordCreate does not
696+
const recordUpdate = node as flowTypes.FlowRecordUpdate;
697+
const hasNoFilters = recordUpdate.filters === undefined;
698+
699+
return hasInputAssignments && hasObject && hasNoFilters;
691700
}
692701

693702
function isRecordDelete(
@@ -705,7 +714,12 @@ function isRecordLookup(
705714
function isRecordUpdate(
706715
node: flowTypes.FlowNode,
707716
): node is flowTypes.FlowRecordUpdate {
708-
return (node as flowTypes.FlowRecordUpdate).inputReference !== undefined;
717+
const recordUpdate = node as flowTypes.FlowRecordUpdate;
718+
return (
719+
recordUpdate.inputAssignments !== undefined &&
720+
recordUpdate.filters !== undefined &&
721+
recordUpdate.object !== undefined
722+
);
709723
}
710724

711725
function isRecordRollback(

src/main/flow_types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ export interface FlowRecordCreate extends FlowNode {
554554
connector?: FlowConnector;
555555
faultConnector?: FlowConnector;
556556
inputAssignments: FlowInputFieldAssignment[];
557-
inputReference: string;
557+
inputReference?: string;
558558
object: string;
559559
storeOutputAutomatically?: boolean; // default: false, Available in API version 48.0 and later
560560
}
@@ -598,7 +598,7 @@ export interface FlowRecordUpdate extends FlowNode {
598598
faultConnector?: FlowConnector;
599599
filters: FlowRecordFilter[];
600600
inputAssignments: FlowInputFieldAssignment[]; // Assuming FlowInputFieldAssignment is a pre-existing interface
601-
inputReference: string;
601+
inputReference?: string;
602602
object: string;
603603
}
604604

src/test/flow_parser_test.ts

Lines changed: 164 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ const TEST_FILES = {
5656
GOLDENS_PATH,
5757
"non_async_scheduled_path.flow-meta.xml",
5858
),
59+
recordCreateWithoutInputReference: join(
60+
GOLDENS_PATH,
61+
"record_create_without_input_reference.flow-meta.xml",
62+
),
63+
recordCreateVsRecordUpdate: join(
64+
GOLDENS_PATH,
65+
"record_create_vs_record_update.flow-meta.xml",
66+
),
5967
};
6068

6169
const NODE_NAMES = {
@@ -534,11 +542,73 @@ Deno.test("FlowParser", async (t) => {
534542
},
535543
);
536544

545+
await t.step("should handle single async path correctly", async () => {
546+
systemUnderTest = new FlowParser(
547+
Deno.readTextFileSync(TEST_FILES.asyncPathTest),
548+
);
549+
550+
parsedFlow = await systemUnderTest.generateFlowDefinition();
551+
552+
assert(parsedFlow.transitions);
553+
assertEquals(parsedFlow.transitions.length, 2);
554+
555+
// Main flow transition (no label)
556+
assertEquals(parsedFlow.transitions[0], {
557+
from: START_NODE_NAME,
558+
to: "main_update",
559+
fault: false,
560+
label: undefined,
561+
});
562+
563+
// Async path transition (with path type as label)
564+
assertEquals(parsedFlow.transitions[1], {
565+
from: START_NODE_NAME,
566+
to: "async_update",
567+
fault: false,
568+
label: "AsyncAfterCommit",
569+
});
570+
});
571+
572+
await t.step("should handle multiple async paths correctly", async () => {
573+
systemUnderTest = new FlowParser(
574+
Deno.readTextFileSync(TEST_FILES.multipleAsyncPaths),
575+
);
576+
577+
parsedFlow = await systemUnderTest.generateFlowDefinition();
578+
579+
assert(parsedFlow.transitions);
580+
assertEquals(parsedFlow.transitions.length, 3);
581+
582+
// Main flow transition (no label)
583+
assertEquals(parsedFlow.transitions[0], {
584+
from: START_NODE_NAME,
585+
to: "main_update",
586+
fault: false,
587+
label: undefined,
588+
});
589+
590+
// First async path transition
591+
assertEquals(parsedFlow.transitions[1], {
592+
from: START_NODE_NAME,
593+
to: "async_update_1",
594+
fault: false,
595+
label: "AsyncAfterCommit",
596+
});
597+
598+
// Second async path transition
599+
assertEquals(parsedFlow.transitions[2], {
600+
from: START_NODE_NAME,
601+
to: "async_update_2",
602+
fault: false,
603+
label: "AsyncAfterCommit",
604+
});
605+
});
606+
537607
await t.step(
538-
"should handle single async path correctly",
608+
"should not label non-async scheduled paths as asynchronous",
539609
async () => {
540610
systemUnderTest = new FlowParser(
541-
Deno.readTextFileSync(TEST_FILES.asyncPathTest),
611+
Deno.readTextFileSync(TEST_FILES.nonAsyncScheduledPath),
542612
);
543613

544614
parsedFlow = await systemUnderTest.generateFlowDefinition();
@@ -554,81 +624,139 @@ Deno.test("FlowParser", async (t) => {
554624
label: undefined,
555625
});
556626

557-
// Async path transition (with path type as label)
627+
// Non-async scheduled path transition (with path type as label)
558628
assertEquals(parsedFlow.transitions[1], {
559629
from: START_NODE_NAME,
560-
to: "async_update",
630+
to: "scheduled_update",
561631
fault: false,
562-
label: "AsyncAfterCommit",
632+
label: "RecordField",
563633
});
564634
},
565635
);
566636

567637
await t.step(
568-
"should handle multiple async paths correctly",
638+
"should include fault connectors for recordCreates nodes without inputReference",
569639
async () => {
570640
systemUnderTest = new FlowParser(
571-
Deno.readTextFileSync(TEST_FILES.multipleAsyncPaths),
641+
Deno.readTextFileSync(TEST_FILES.recordCreateWithoutInputReference),
572642
);
573643

574644
parsedFlow = await systemUnderTest.generateFlowDefinition();
575645

646+
assert(parsedFlow);
647+
assert(parsedFlow.recordCreates);
648+
assertEquals(parsedFlow.recordCreates.length, 1);
649+
650+
const recordCreate = parsedFlow.recordCreates[0];
651+
assertEquals(recordCreate.name, "create_property");
652+
assertEquals(recordCreate.object, "Property__c");
653+
// Verify that inputAssignments is present (required for identification)
654+
assert(recordCreate.inputAssignments);
655+
assertEquals(recordCreate.inputAssignments.length, 2);
656+
// Verify that inputReference is optional and can be undefined
657+
assertEquals(recordCreate.inputReference, undefined);
658+
// Verify that fault connector is present and parsed correctly
659+
assert(recordCreate.faultConnector);
660+
assertEquals(
661+
recordCreate.faultConnector.targetReference,
662+
"error_creating_records",
663+
);
664+
// Verify that normal connector is also present
665+
assert(recordCreate.connector);
666+
assertEquals(recordCreate.connector.targetReference, "success_screen");
667+
668+
// Verify that transitions include both normal and fault paths
576669
assert(parsedFlow.transitions);
577670
assertEquals(parsedFlow.transitions.length, 3);
578671

579-
// Main flow transition (no label)
672+
// Start node to create_property
580673
assertEquals(parsedFlow.transitions[0], {
581674
from: START_NODE_NAME,
582-
to: "main_update",
675+
to: "create_property",
583676
fault: false,
584677
label: undefined,
585678
});
586679

587-
// First async path transition
680+
// Normal path: create_property to success_screen
588681
assertEquals(parsedFlow.transitions[1], {
589-
from: START_NODE_NAME,
590-
to: "async_update_1",
682+
from: "create_property",
683+
to: "success_screen",
591684
fault: false,
592-
label: "AsyncAfterCommit",
685+
label: undefined,
593686
});
594687

595-
// Second async path transition
688+
// Fault path: create_property to error_creating_records
596689
assertEquals(parsedFlow.transitions[2], {
597-
from: START_NODE_NAME,
598-
to: "async_update_2",
599-
fault: false,
600-
label: "AsyncAfterCommit",
690+
from: "create_property",
691+
to: "error_creating_records",
692+
fault: true,
693+
label: "Fault",
601694
});
602695
},
603696
);
604697

605698
await t.step(
606-
"should not label non-async scheduled paths as asynchronous",
699+
"should still work correctly for recordCreates nodes with inputReference (backward compatibility)",
607700
async () => {
608701
systemUnderTest = new FlowParser(
609-
Deno.readTextFileSync(TEST_FILES.nonAsyncScheduledPath),
702+
Deno.readTextFileSync(TEST_FILES.sample),
610703
);
611704

612705
parsedFlow = await systemUnderTest.generateFlowDefinition();
613706

614-
assert(parsedFlow.transitions);
615-
assertEquals(parsedFlow.transitions.length, 2);
707+
assert(parsedFlow);
708+
assert(parsedFlow.recordCreates);
709+
assertEquals(parsedFlow.recordCreates.length, 1);
616710

617-
// Main flow transition (no label)
618-
assertEquals(parsedFlow.transitions[0], {
619-
from: START_NODE_NAME,
620-
to: "main_update",
621-
fault: false,
622-
label: undefined,
623-
});
711+
const recordCreate = parsedFlow.recordCreates[0];
712+
assertEquals(recordCreate.name, "Insert_Tag");
713+
// Verify that inputReference is present (traditional approach)
714+
assertEquals(recordCreate.inputReference, "tagToInsert");
715+
// Verify that fault connector is still correctly parsed and included
716+
assert(recordCreate.faultConnector);
717+
assertEquals(
718+
recordCreate.faultConnector.targetReference,
719+
"Add_Issue_Inserting_Tag_Record_Error",
720+
);
624721

625-
// Non-async scheduled path transition (with path type as label)
626-
assertEquals(parsedFlow.transitions[1], {
627-
from: START_NODE_NAME,
628-
to: "scheduled_update",
629-
fault: false,
630-
label: "RecordField",
631-
});
722+
// Verify that the fault transition is included in the transitions array
723+
const faultTransition = parsedFlow.transitions?.find(
724+
(t) => t.from === "Insert_Tag" && t.fault === true,
725+
);
726+
assert(faultTransition);
727+
assertEquals(faultTransition.to, "Add_Issue_Inserting_Tag_Record_Error");
728+
assertEquals(faultTransition.label, "Fault");
729+
},
730+
);
731+
732+
await t.step(
733+
"should correctly distinguish recordCreates from recordUpdates",
734+
async () => {
735+
systemUnderTest = new FlowParser(
736+
Deno.readTextFileSync(TEST_FILES.recordCreateVsRecordUpdate),
737+
);
738+
parsedFlow = await systemUnderTest.generateFlowDefinition();
739+
740+
assert(parsedFlow);
741+
742+
// Verify recordCreates is identified correctly (has inputAssignments but no filters)
743+
assert(parsedFlow.recordCreates);
744+
assertEquals(parsedFlow.recordCreates.length, 1);
745+
assertEquals(parsedFlow.recordCreates[0].name, "create_record");
746+
assert(parsedFlow.recordCreates[0].inputAssignments);
747+
// recordCreates should not have filters
748+
assertEquals(
749+
(parsedFlow.recordCreates[0] as flowTypes.FlowRecordUpdate).filters,
750+
undefined,
751+
);
752+
753+
// Verify recordUpdates is identified correctly (has both inputAssignments and filters)
754+
assert(parsedFlow.recordUpdates);
755+
assertEquals(parsedFlow.recordUpdates.length, 1);
756+
assertEquals(parsedFlow.recordUpdates[0].name, "update_record");
757+
assert(parsedFlow.recordUpdates[0].inputAssignments);
758+
assert(parsedFlow.recordUpdates[0].filters);
759+
assertEquals(parsedFlow.recordUpdates[0].filters.length, 1);
632760
},
633761
);
634762
});

src/test/goldens/multiple_elements.flow-meta.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
3-
Copyright 2024 Google LLC
3+
Copyright 2026 Google LLC
44
55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
@@ -61,9 +61,13 @@
6161
</orchestratedStages>
6262
<recordCreates>
6363
<name>myRecordCreate</name>
64+
<object>Account</object>
65+
<inputAssignments></inputAssignments>
6466
</recordCreates>
6567
<recordCreates>
6668
<name>myRecordCreate2</name>
69+
<object>Account</object>
70+
<inputAssignments></inputAssignments>
6771
</recordCreates>
6872
<recordDeletes>
6973
<name>myRecordDelete</name>

0 commit comments

Comments
 (0)