Skip to content

Commit 48f1679

Browse files
committed
Add compile-safe bypass and validation methods for Flow types in TriggerActionFlow
1 parent 21833c2 commit 48f1679

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

trigger-actions-framework/main/default/classes/TriggerActionFlow.cls

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@ public virtual inherited sharing class TriggerActionFlow implements TriggerActio
2424
@TestVisible
2525
private static final String RECORD_VARIABLE_NOT_FOUND_ERROR = 'There must be a variable defined in this flow with api name of "record" and type of "record" that is marked as "available for output"';
2626
@TestVisible
27+
private static final String FLOW_INTERVIEW_PREFIX = 'Flow.Interview';
28+
@TestVisible
2729
private static Set<String> bypassedFlows = new Set<String>();
2830
@TestVisible
2931
private static InvocableAction invocableAction = new InvocableAction();
32+
@TestVisible
33+
private static NameExtractor nameExtractor = new NameExtractor();
3034

3135
public String flowName;
3236
public Boolean allowRecursion;
@@ -59,13 +63,71 @@ public virtual inherited sharing class TriggerActionFlow implements TriggerActio
5963
return TriggerActionFlow.bypassedFlows.contains(flowName);
6064
}
6165

66+
/**
67+
* @description This method bypasses the execution of the Flow for the specified list of records.
68+
*
69+
* @param flowType The Type of the Flow to bypass (e.g., Flow.Interview.MyFlow.class).
70+
* @throws IllegalArgumentException if the type does not represent a Flow.
71+
*/
72+
public static void bypass(System.Type flowType) {
73+
String flowName = getFlowNameFromType(flowType);
74+
TriggerActionFlow.bypassedFlows.add(flowName);
75+
}
76+
77+
/**
78+
* @description This method clears the bypass for the specified list of records.
79+
*
80+
* @param flowType The Type of the Flow to clear the bypass for (e.g., Flow.Interview.MyFlow.class).
81+
* @throws IllegalArgumentException if the type does not represent a Flow.
82+
*/
83+
public static void clearBypass(System.Type flowType) {
84+
String flowName = getFlowNameFromType(flowType);
85+
TriggerActionFlow.bypassedFlows.remove(flowName);
86+
}
87+
88+
/**
89+
* @description This method checks if the Flow is bypassed for the specified list of records.
90+
*
91+
* @param flowType The Type of the Flow to check the bypass for (e.g., Flow.Interview.MyFlow.class).
92+
* @return `true` if the Flow is bypassed for the specified list of records, `false` otherwise.
93+
* @throws IllegalArgumentException if the type does not represent a Flow.
94+
*/
95+
public static Boolean isBypassed(System.Type flowType) {
96+
String flowName = getFlowNameFromType(flowType);
97+
return TriggerActionFlow.bypassedFlows.contains(flowName);
98+
}
99+
62100
/**
63101
* @description This method clears all bypasses for all Flows.
64102
*/
65103
public static void clearAllBypasses() {
66104
TriggerActionFlow.bypassedFlows.clear();
67105
}
68106

107+
/**
108+
* @description This method extracts the flow name from a Type and validates that it represents a Flow.
109+
*
110+
* @param flowType The Type to extract the flow name from.
111+
* @return The flow API name.
112+
* @throws IllegalArgumentException if the type does not represent a Flow.
113+
*/
114+
private static String getFlowNameFromType(System.Type flowType) {
115+
String typeName = nameExtractor.extractName(flowType);
116+
if (!typeName.startsWith(FLOW_INTERVIEW_PREFIX)) {
117+
throw new IllegalArgumentException(
118+
'Type must represent a Flow (e.g., Flow.Interview.MyFlow.class), but got: ' +
119+
typeName
120+
);
121+
}
122+
// If the type name is exactly "Flow.Interview", it means the flow doesn't exist
123+
if (typeName.equals(FLOW_INTERVIEW_PREFIX)) {
124+
throw new IllegalArgumentException(
125+
'Flow does not exist. Please check that the flow API name is correct.'
126+
);
127+
}
128+
return typeName.replace(FLOW_INTERVIEW_PREFIX + '.', '');
129+
}
130+
69131
/**
70132
* @description This method validates the specified bypass type.
71133
*
@@ -380,4 +442,11 @@ public virtual inherited sharing class TriggerActionFlow implements TriggerActio
380442
return action.invoke();
381443
}
382444
}
445+
446+
@TestVisible
447+
private virtual class NameExtractor {
448+
public virtual String extractName(System.Type flowType) {
449+
return flowType.getName();
450+
}
451+
}
383452
}

trigger-actions-framework/main/default/classes/TriggerActionFlowTest.cls

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ private class TriggerActionFlowTest {
3434
private static final String ONE_ERROR = 'There should be exactly one error';
3535
private static final String SAMPLE_FLOW_NAME = 'TriggerActionFlowTest';
3636
private static final String TEST = 'Test!';
37+
private static final String SAMPLE_FLOW_TYPE_NAME =
38+
TriggerActionFlow.FLOW_INTERVIEW_PREFIX +
39+
'.' +
40+
SAMPLE_FLOW_NAME;
3741

3842
private static Account myAccount = new Account(
3943
Name = MY_ACCOUNT,
@@ -359,6 +363,114 @@ private class TriggerActionFlowTest {
359363
);
360364
}
361365

366+
@IsTest
367+
private static void bypassWithTypeShouldSucceed() {
368+
TriggerActionFlow.nameExtractor = new FakeNameExtractor(
369+
SAMPLE_FLOW_TYPE_NAME
370+
);
371+
TriggerActionFlow.bypass(Flow.Interview.TriggerActionFlowTest.class);
372+
373+
System.Assert.isTrue(
374+
TriggerActionFlow.bypassedFlows.contains(SAMPLE_FLOW_NAME),
375+
BYPASSES_SHOULD_BE_POPULATED_CORRECTLY
376+
);
377+
}
378+
379+
@IsTest
380+
private static void clearBypassWithTypeShouldSucceed() {
381+
TriggerActionFlow.nameExtractor = new FakeNameExtractor(
382+
SAMPLE_FLOW_TYPE_NAME
383+
);
384+
TriggerActionFlow.bypass(Flow.Interview.TriggerActionFlowTest.class);
385+
TriggerActionFlow.clearBypass(Flow.Interview.TriggerActionFlowTest.class);
386+
387+
System.Assert.isFalse(
388+
TriggerActionFlow.bypassedFlows.contains(SAMPLE_FLOW_NAME),
389+
BYPASSES_SHOULD_BE_POPULATED_CORRECTLY
390+
);
391+
}
392+
393+
@IsTest
394+
private static void isBypassedWithTypeShouldSucceed() {
395+
TriggerActionFlow.nameExtractor = new FakeNameExtractor(
396+
SAMPLE_FLOW_TYPE_NAME
397+
);
398+
Boolean isBypassed;
399+
TriggerActionFlow.bypass(Flow.Interview.TriggerActionFlowTest.class);
400+
401+
isBypassed = TriggerActionFlow.isBypassed(
402+
Flow.Interview.TriggerActionFlowTest.class
403+
);
404+
405+
System.Assert.isTrue(isBypassed, BYPASSES_SHOULD_BE_POPULATED_CORRECTLY);
406+
}
407+
408+
@IsTest
409+
private static void bypassWithInvalidTypeShouldThrowException() {
410+
try {
411+
TriggerActionFlow.bypass(String.class);
412+
} catch (Exception e) {
413+
myException = e;
414+
}
415+
416+
System.Assert.areNotEqual(null, myException, EXCEPTION_SHOULD_BE_THROWN);
417+
System.Assert.areEqual(
418+
true,
419+
myException.getMessage().contains('Type must represent a Flow'),
420+
EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE
421+
);
422+
}
423+
424+
@IsTest
425+
private static void clearBypassWithInvalidTypeShouldThrowException() {
426+
try {
427+
TriggerActionFlow.clearBypass(String.class);
428+
} catch (Exception e) {
429+
myException = e;
430+
}
431+
432+
System.Assert.areNotEqual(null, myException, EXCEPTION_SHOULD_BE_THROWN);
433+
System.Assert.areEqual(
434+
true,
435+
myException.getMessage().contains('Type must represent a Flow'),
436+
EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE
437+
);
438+
}
439+
440+
@IsTest
441+
private static void isBypassedWithInvalidTypeShouldThrowException() {
442+
try {
443+
TriggerActionFlow.isBypassed(String.class);
444+
} catch (Exception e) {
445+
myException = e;
446+
}
447+
448+
System.Assert.areNotEqual(null, myException, EXCEPTION_SHOULD_BE_THROWN);
449+
System.Assert.areEqual(
450+
true,
451+
myException.getMessage().contains('Type must represent a Flow'),
452+
EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE
453+
);
454+
}
455+
456+
@IsTest
457+
private static void bypassWithNonExistentFlowShouldThrowException() {
458+
try {
459+
TriggerActionFlow.bypass(
460+
Flow.Interview.SomeBogusClassWhichDoesNotExist.class
461+
);
462+
} catch (Exception e) {
463+
myException = e;
464+
}
465+
466+
System.Assert.areNotEqual(null, myException, EXCEPTION_SHOULD_BE_THROWN);
467+
System.Assert.areEqual(
468+
true,
469+
myException.getMessage().contains('Flow does not exist'),
470+
EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE
471+
);
472+
}
473+
362474
@IsTest
363475
private static void invokeFlowShouldReturnUnsuccessfulResponseWithBogusFlowName() {
364476
List<Invocable.Action.Result> results = new TriggerActionFlow.InvocableAction()
@@ -454,4 +566,16 @@ private class TriggerActionFlowTest {
454566
);
455567
}
456568
}
569+
570+
private class FakeNameExtractor extends TriggerActionFlow.NameExtractor {
571+
private String mockTypeName;
572+
573+
public FakeNameExtractor(String mockTypeName) {
574+
this.mockTypeName = mockTypeName;
575+
}
576+
577+
public override String extractName(System.Type flowType) {
578+
return this.mockTypeName;
579+
}
580+
}
457581
}

0 commit comments

Comments
 (0)