@flow-scanner/lightning-flow-scanner-core 6.6.2 → 6.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +1 -0
- package/.husky/pre-push +1 -0
- package/.prettierignore +5 -0
- package/.swcrc +26 -0
- package/eslint.config.mjs +36 -0
- package/jest.config.cjs +32 -0
- package/jest.env-setup.js +101 -0
- package/lint-staged.config.mjs +8 -0
- package/out/assets/media/banner.png +0 -0
- package/{main → out/main}/interfaces/IRuleDefinition.d.ts +0 -1
- package/{main → out/main}/internals/internals.d.ts +2 -2
- package/{main → out/main}/internals/internals.js +0 -4
- package/{main → out/main}/models/RuleCommon.d.ts +0 -1
- package/{main → out/main}/models/RuleCommon.js +8 -4
- package/{main → out/main}/models/RuleInfo.d.ts +0 -15
- package/{main → out/main}/models/RuleInfo.js +0 -13
- package/{main → out/main}/rules/APIVersion.js +1 -3
- package/{main → out/main}/rules/ActionCallsInLoop.js +0 -2
- package/{main → out/main}/rules/AutoLayout.js +1 -3
- package/{main → out/main}/rules/CopyAPIName.js +1 -3
- package/{main → out/main}/rules/CyclomaticComplexity.js +1 -3
- package/{main → out/main}/rules/DMLStatementInLoop.js +0 -2
- package/{main → out/main}/rules/DuplicateDMLOperation.js +1 -3
- package/{main → out/main}/rules/FlowDescription.js +0 -2
- package/{main → out/main}/rules/FlowName.js +0 -2
- package/{main → out/main}/rules/GetRecordAllFields.js +0 -2
- package/{main → out/main}/rules/HardcodedId.js +1 -3
- package/{main → out/main}/rules/HardcodedUrl.js +0 -2
- package/{main → out/main}/rules/InactiveFlow.js +1 -3
- package/{main → out/main}/rules/MissingFaultPath.js +0 -2
- package/{main → out/main}/rules/MissingMetadataDescription.js +0 -2
- package/{main → out/main}/rules/MissingNullHandler.js +0 -2
- package/{main → out/main}/rules/ProcessBuilder.js +1 -3
- package/{main → out/main}/rules/RecursiveAfterUpdate.js +0 -2
- package/{main → out/main}/rules/SOQLQueryInLoop.js +0 -2
- package/{main → out/main}/rules/SameRecordFieldUpdates.js +1 -3
- package/{main → out/main}/rules/TriggerOrder.js +1 -3
- package/{main → out/main}/rules/UnconnectedElement.js +0 -2
- package/{main → out/main}/rules/UnsafeRunningContext.js +1 -3
- package/{main → out/main}/rules/UnusedVariable.js +1 -3
- package/package.json +58 -7
- package/prettier.config.mjs +5 -0
- package/src/index.ts +44 -0
- package/src/main/interfaces/IExceptions.ts +6 -0
- package/src/main/interfaces/IRuleConfig.ts +3 -0
- package/src/main/interfaces/IRuleDefinition.ts +12 -0
- package/src/main/interfaces/IRuleOptions.ts +5 -0
- package/src/main/interfaces/IRulesConfig.ts +15 -0
- package/src/main/internals/internals.ts +35 -0
- package/src/main/libs/BuildFlow.ts +11 -0
- package/src/main/libs/Compiler.ts +69 -0
- package/src/main/libs/ConvertFlowNodes.ts +4 -0
- package/src/main/libs/DynamicRule.ts +11 -0
- package/src/main/libs/FixFlows.ts +61 -0
- package/src/main/libs/GetRuleDefinitions.ts +65 -0
- package/src/main/libs/ParseFlows.ts +34 -0
- package/src/main/libs/ScanFlows.ts +112 -0
- package/src/main/libs/exportAsDetails.ts +26 -0
- package/src/main/libs/exportAsSarif.ts +88 -0
- package/src/main/models/FlatViolation.ts +8 -0
- package/src/main/models/Flow.ts +214 -0
- package/src/main/models/FlowAttribute.ts +12 -0
- package/src/main/models/FlowElement.ts +15 -0
- package/src/main/models/FlowElementConnector.ts +28 -0
- package/src/main/models/FlowMetadata.ts +7 -0
- package/src/main/models/FlowNode.ts +171 -0
- package/src/main/models/FlowResource.ts +10 -0
- package/src/main/models/FlowType.ts +52 -0
- package/src/main/models/FlowVariable.ts +12 -0
- package/src/main/models/LoopRuleCommon.ts +55 -0
- package/src/main/models/ParsedFlow.ts +15 -0
- package/src/main/models/RuleCommon.ts +68 -0
- package/src/main/models/RuleInfo.ts +43 -0
- package/src/main/models/RuleResult.ts +25 -0
- package/src/main/models/ScanResult.ts +12 -0
- package/src/main/models/Violation.ts +78 -0
- package/src/main/rules/APIVersion.ts +59 -0
- package/src/main/rules/ActionCallsInLoop.ts +24 -0
- package/src/main/rules/AutoLayout.ts +44 -0
- package/src/main/rules/CopyAPIName.ts +29 -0
- package/src/main/rules/CyclomaticComplexity.ts +67 -0
- package/src/main/rules/DMLStatementInLoop.ts +24 -0
- package/src/main/rules/DuplicateDMLOperation.ts +114 -0
- package/src/main/rules/FlowDescription.ts +32 -0
- package/src/main/rules/FlowName.ts +40 -0
- package/src/main/rules/GetRecordAllFields.ts +59 -0
- package/src/main/rules/HardcodedId.ts +37 -0
- package/src/main/rules/HardcodedUrl.ts +42 -0
- package/src/main/rules/InactiveFlow.ts +31 -0
- package/src/main/rules/MissingFaultPath.ts +89 -0
- package/src/main/rules/MissingMetadataDescription.ts +39 -0
- package/src/main/rules/MissingNullHandler.ts +95 -0
- package/src/main/rules/ProcessBuilder.ts +33 -0
- package/src/main/rules/RecursiveAfterUpdate.ts +88 -0
- package/src/main/rules/SOQLQueryInLoop.ts +24 -0
- package/src/main/rules/SameRecordFieldUpdates.ts +64 -0
- package/src/main/rules/TriggerOrder.ts +44 -0
- package/src/main/rules/UnconnectedElement.ts +48 -0
- package/src/main/rules/UnsafeRunningContext.ts +44 -0
- package/src/main/rules/UnusedVariable.ts +64 -0
- package/src/main/store/DefaultRuleStore.ts +54 -0
- package/stryker.config.mjs +23 -0
- package/tests/APIVersion.test.ts +83 -0
- package/tests/AutoLayout.test.ts +39 -0
- package/tests/Config.test.ts +119 -0
- package/tests/ConfigBetaMode.test.ts +26 -0
- package/tests/CopyAPIName.test.ts +43 -0
- package/tests/CyclomaticComplexity.test.ts +123 -0
- package/tests/DMLStatementInLoop.test.ts +31 -0
- package/tests/DuplicateDMLOperation.test.ts +41 -0
- package/tests/Exceptions.test.ts +813 -0
- package/tests/ExportSarif.test.ts +61 -0
- package/tests/FlowDescription.test.ts +42 -0
- package/tests/FlowName.test.ts +62 -0
- package/tests/GetRecordElementAllFields.test.ts +180 -0
- package/tests/HardcodedId.test.ts +16 -0
- package/tests/HardcodedUrl.test.ts +252 -0
- package/tests/InactiveFlow.test.ts +99 -0
- package/tests/MissingFaultPath.test.ts +50 -0
- package/tests/MissingMetadataDescription.test.ts +24 -0
- package/tests/MissingNullHandler.test.ts +43 -0
- package/tests/No_Missing_Null_Handler.test.ts +30 -0
- package/tests/RecursiveAfterUpdate.test.ts +160 -0
- package/tests/SOQLQueryInLoop.test.ts +32 -0
- package/tests/SameRecordFieldUpdates.test.ts +241 -0
- package/tests/SanityTest.test.ts +15 -0
- package/tests/TriggerOrder.test.ts +92 -0
- package/tests/UnconnectedElement.test.ts +74 -0
- package/tests/UnsafeRunningContext.test.ts +46 -0
- package/tests/UnusedVariable.test.ts +56 -0
- package/tests/jsonfiles/MissingFaultPath_BeforeSave_Bypass.json +128 -0
- package/tests/jsonfiles/MissingFaultPath_WaitConditions.json +102 -0
- package/tests/jsonfiles/MissingFaultPath_WaitDate.json +88 -0
- package/tests/jsonfiles/MissingFaultPath_WaitDuration.json +90 -0
- package/tests/models/Flow.test.ts +107 -0
- package/tests/models/LoopRuleCommon.test.ts +253 -0
- package/tests/models/RuleCommon.test.ts +47 -0
- package/tsconfig.json +19 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.types.json +25 -0
- package/vite.config.ts +28 -0
- package/CONTRIBUTING.md +0 -31
- package/README.md +0 -443
- package/SECURITY.md +0 -47
- /package/{index.d.ts → out/index.d.ts} +0 -0
- /package/{index.js → out/index.js} +0 -0
- /package/{main → out/main}/interfaces/IExceptions.d.ts +0 -0
- /package/{main → out/main}/interfaces/IExceptions.js +0 -0
- /package/{main → out/main}/interfaces/IRuleConfig.d.ts +0 -0
- /package/{main → out/main}/interfaces/IRuleConfig.js +0 -0
- /package/{main → out/main}/interfaces/IRuleDefinition.js +0 -0
- /package/{main → out/main}/interfaces/IRuleOptions.d.ts +0 -0
- /package/{main → out/main}/interfaces/IRuleOptions.js +0 -0
- /package/{main → out/main}/interfaces/IRulesConfig.d.ts +0 -0
- /package/{main → out/main}/interfaces/IRulesConfig.js +0 -0
- /package/{main → out/main}/libs/BuildFlow.d.ts +0 -0
- /package/{main → out/main}/libs/BuildFlow.js +0 -0
- /package/{main → out/main}/libs/Compiler.d.ts +0 -0
- /package/{main → out/main}/libs/Compiler.js +0 -0
- /package/{main → out/main}/libs/ConvertFlowNodes.d.ts +0 -0
- /package/{main → out/main}/libs/ConvertFlowNodes.js +0 -0
- /package/{main → out/main}/libs/DynamicRule.d.ts +0 -0
- /package/{main → out/main}/libs/DynamicRule.js +0 -0
- /package/{main → out/main}/libs/FixFlows.d.ts +0 -0
- /package/{main → out/main}/libs/FixFlows.js +0 -0
- /package/{main → out/main}/libs/GetRuleDefinitions.d.ts +0 -0
- /package/{main → out/main}/libs/GetRuleDefinitions.js +0 -0
- /package/{main → out/main}/libs/ParseFlows.d.ts +0 -0
- /package/{main → out/main}/libs/ParseFlows.js +0 -0
- /package/{main → out/main}/libs/ScanFlows.d.ts +0 -0
- /package/{main → out/main}/libs/ScanFlows.js +0 -0
- /package/{main → out/main}/libs/exportAsDetails.d.ts +0 -0
- /package/{main → out/main}/libs/exportAsDetails.js +0 -0
- /package/{main → out/main}/libs/exportAsSarif.d.ts +0 -0
- /package/{main → out/main}/libs/exportAsSarif.js +0 -0
- /package/{main → out/main}/models/FlatViolation.d.ts +0 -0
- /package/{main → out/main}/models/FlatViolation.js +0 -0
- /package/{main → out/main}/models/Flow.d.ts +0 -0
- /package/{main → out/main}/models/Flow.js +0 -0
- /package/{main → out/main}/models/FlowAttribute.d.ts +0 -0
- /package/{main → out/main}/models/FlowAttribute.js +0 -0
- /package/{main → out/main}/models/FlowElement.d.ts +0 -0
- /package/{main → out/main}/models/FlowElement.js +0 -0
- /package/{main → out/main}/models/FlowElementConnector.d.ts +0 -0
- /package/{main → out/main}/models/FlowElementConnector.js +0 -0
- /package/{main → out/main}/models/FlowMetadata.d.ts +0 -0
- /package/{main → out/main}/models/FlowMetadata.js +0 -0
- /package/{main → out/main}/models/FlowNode.d.ts +0 -0
- /package/{main → out/main}/models/FlowNode.js +0 -0
- /package/{main → out/main}/models/FlowResource.d.ts +0 -0
- /package/{main → out/main}/models/FlowResource.js +0 -0
- /package/{main → out/main}/models/FlowType.d.ts +0 -0
- /package/{main → out/main}/models/FlowType.js +0 -0
- /package/{main → out/main}/models/FlowVariable.d.ts +0 -0
- /package/{main → out/main}/models/FlowVariable.js +0 -0
- /package/{main → out/main}/models/LoopRuleCommon.d.ts +0 -0
- /package/{main → out/main}/models/LoopRuleCommon.js +0 -0
- /package/{main → out/main}/models/ParsedFlow.d.ts +0 -0
- /package/{main → out/main}/models/ParsedFlow.js +0 -0
- /package/{main → out/main}/models/RuleResult.d.ts +0 -0
- /package/{main → out/main}/models/RuleResult.js +0 -0
- /package/{main → out/main}/models/ScanResult.d.ts +0 -0
- /package/{main → out/main}/models/ScanResult.js +0 -0
- /package/{main → out/main}/models/Violation.d.ts +0 -0
- /package/{main → out/main}/models/Violation.js +0 -0
- /package/{main → out/main}/rules/APIVersion.d.ts +0 -0
- /package/{main → out/main}/rules/ActionCallsInLoop.d.ts +0 -0
- /package/{main → out/main}/rules/AutoLayout.d.ts +0 -0
- /package/{main → out/main}/rules/CopyAPIName.d.ts +0 -0
- /package/{main → out/main}/rules/CyclomaticComplexity.d.ts +0 -0
- /package/{main → out/main}/rules/DMLStatementInLoop.d.ts +0 -0
- /package/{main → out/main}/rules/DuplicateDMLOperation.d.ts +0 -0
- /package/{main → out/main}/rules/FlowDescription.d.ts +0 -0
- /package/{main → out/main}/rules/FlowName.d.ts +0 -0
- /package/{main → out/main}/rules/GetRecordAllFields.d.ts +0 -0
- /package/{main → out/main}/rules/HardcodedId.d.ts +0 -0
- /package/{main → out/main}/rules/HardcodedUrl.d.ts +0 -0
- /package/{main → out/main}/rules/InactiveFlow.d.ts +0 -0
- /package/{main → out/main}/rules/MissingFaultPath.d.ts +0 -0
- /package/{main → out/main}/rules/MissingMetadataDescription.d.ts +0 -0
- /package/{main → out/main}/rules/MissingNullHandler.d.ts +0 -0
- /package/{main → out/main}/rules/ProcessBuilder.d.ts +0 -0
- /package/{main → out/main}/rules/RecursiveAfterUpdate.d.ts +0 -0
- /package/{main → out/main}/rules/SOQLQueryInLoop.d.ts +0 -0
- /package/{main → out/main}/rules/SameRecordFieldUpdates.d.ts +0 -0
- /package/{main → out/main}/rules/TriggerOrder.d.ts +0 -0
- /package/{main → out/main}/rules/UnconnectedElement.d.ts +0 -0
- /package/{main → out/main}/rules/UnsafeRunningContext.d.ts +0 -0
- /package/{main → out/main}/rules/UnusedVariable.d.ts +0 -0
- /package/{main → out/main}/store/DefaultRuleStore.d.ts +0 -0
- /package/{main → out/main}/store/DefaultRuleStore.js +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as core from "../src";
|
|
4
|
+
|
|
5
|
+
describe("exportSarif()", () => {
|
|
6
|
+
const badFlowPath = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/demo/DML_Statement_In_A_Loop.flow-meta.xml");
|
|
7
|
+
const goodFlowPath = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/testing/Duplicate_DML_Operation_Fixed.flow-meta.xml");
|
|
8
|
+
const config = {
|
|
9
|
+
rules: {
|
|
10
|
+
DMLStatementInLoop: { severity: "error" },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
it("generates valid SARIF with real file path and line numbers", async () => {
|
|
14
|
+
const flows = await core.parse([badFlowPath]);
|
|
15
|
+
const results = core.scan(flows, config);
|
|
16
|
+
const sarif = core.exportSarif(results);
|
|
17
|
+
|
|
18
|
+
const json = JSON.parse(sarif);
|
|
19
|
+
// SARIF structure
|
|
20
|
+
expect(json.version).toBe("2.1.0");
|
|
21
|
+
expect(json.runs).toHaveLength(1);
|
|
22
|
+
expect(json.runs[0].tool.driver.name).toBe("Lightning Flow Scanner");
|
|
23
|
+
// Artifacts: real path (relative or absolute containing the substring)
|
|
24
|
+
const artifactUri = json.runs[0].artifacts[0].location.uri;
|
|
25
|
+
expect(artifactUri).toContain("force-app/main/default/flows/demo/DML_Statement_In_A_Loop.flow-meta.xml");
|
|
26
|
+
// Results: one issue
|
|
27
|
+
const resultsArray = json.runs[0].results;
|
|
28
|
+
expect(resultsArray).toHaveLength(1);
|
|
29
|
+
expect(resultsArray[0].ruleId).toBe("DMLStatementInLoop");
|
|
30
|
+
expect(resultsArray[0].level).toBe("error");
|
|
31
|
+
// Location: has region
|
|
32
|
+
const region = resultsArray[0].locations[0].physicalLocation.region;
|
|
33
|
+
expect(region).toBeDefined();
|
|
34
|
+
expect(typeof region.startLine).toBe("number");
|
|
35
|
+
expect(typeof region.startColumn).toBe("number");
|
|
36
|
+
expect(region.startLine).toBeGreaterThanOrEqual(1);
|
|
37
|
+
expect(region.startColumn).toBeGreaterThanOrEqual(1);
|
|
38
|
+
// Message
|
|
39
|
+
expect(resultsArray[0].message.text).toContain("createNewCase");
|
|
40
|
+
});
|
|
41
|
+
it("generates empty results for fixed flow", async () => {
|
|
42
|
+
const flows = await core.parse([goodFlowPath]);
|
|
43
|
+
const results = core.scan(flows, config);
|
|
44
|
+
const sarif = core.exportSarif(results);
|
|
45
|
+
|
|
46
|
+
const json = JSON.parse(sarif);
|
|
47
|
+
expect(json.runs[0].results).toHaveLength(0);
|
|
48
|
+
});
|
|
49
|
+
it("falls back to virtual URI when no fsPath", async () => {
|
|
50
|
+
const flows = await core.parse([badFlowPath]);
|
|
51
|
+
// Simulate browser: remove fsPath and set virtual uri with subdir structure
|
|
52
|
+
flows[0].flow.fsPath = undefined;
|
|
53
|
+
flows[0].flow.uri = "flows/demo/DML_Statement_In_A_Loop.flow-meta.xml";
|
|
54
|
+
const results = core.scan(flows, config);
|
|
55
|
+
const sarif = core.exportSarif(results);
|
|
56
|
+
const json = JSON.parse(sarif);
|
|
57
|
+
|
|
58
|
+
const uri = json.runs[0].artifacts[0].location.uri;
|
|
59
|
+
expect(uri).toBe("flows/demo/DML_Statement_In_A_Loop.flow-meta.xml");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as core from "../src";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect } from "@jest/globals";
|
|
5
|
+
|
|
6
|
+
describe("FlowDescription", () => {
|
|
7
|
+
const example_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/demo/Missing_Flow_Description.flow-meta.xml");
|
|
8
|
+
const fixed_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/testing/Missing_Flow_Description_Fixed.flow-meta.xml");
|
|
9
|
+
|
|
10
|
+
it("should return a result when missing a description", async () => {
|
|
11
|
+
const flows = await core.parse([example_uri]);
|
|
12
|
+
const ruleConfig = {
|
|
13
|
+
rules: {
|
|
14
|
+
FlowDescription: {
|
|
15
|
+
severity: "error",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
21
|
+
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
|
|
22
|
+
expect(occurringResults).toHaveLength(1);
|
|
23
|
+
expect(occurringResults[0].ruleName).toBe("FlowDescription");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should have no result when provided a description", async () => {
|
|
27
|
+
const flows = await core.parse([fixed_uri]);
|
|
28
|
+
const ruleConfig = {
|
|
29
|
+
rules: {
|
|
30
|
+
FlowDescription: {
|
|
31
|
+
severity: "error",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
37
|
+
|
|
38
|
+
expect(results[0].ruleResults).toHaveLength(1);
|
|
39
|
+
expect(results[0].ruleResults[0].ruleName).toBe("FlowDescription");
|
|
40
|
+
expect(results[0].ruleResults[0].occurs).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as core from "../src";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect } from "@jest/globals";
|
|
5
|
+
|
|
6
|
+
describe("FlowName", () => {
|
|
7
|
+
const example_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/demo/FlowNamingConvention.flow-meta.xml");
|
|
8
|
+
const fixed_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/testing/Flow_Naming_Convention_Fixed.flow-meta.xml");
|
|
9
|
+
|
|
10
|
+
it("should have a result when not in line with conventions", async () => {
|
|
11
|
+
const flows = await core.parse([example_uri]);
|
|
12
|
+
const ruleConfig = {
|
|
13
|
+
rules: {
|
|
14
|
+
FlowName: {
|
|
15
|
+
severity: "error",
|
|
16
|
+
expression: "[A-Za-z0-9]+_[A-Za-z0-9]+",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
22
|
+
expect(results[0].ruleResults).toHaveLength(1);
|
|
23
|
+
expect(results[0].ruleResults[0].ruleName).toBe("FlowName");
|
|
24
|
+
expect(results[0].ruleResults[0].occurs).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should have no result when defined as exception", async () => {
|
|
28
|
+
const flows = await core.parse([example_uri]);
|
|
29
|
+
const ruleConfig = {
|
|
30
|
+
rules: {
|
|
31
|
+
FlowName: {
|
|
32
|
+
severity: "error",
|
|
33
|
+
expression: "[0-9]",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
exceptions: {
|
|
37
|
+
FlowNamingConvention: { FlowName: ["FlowNamingConvention"] },
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
42
|
+
expect(results[0].ruleResults).toHaveLength(1);
|
|
43
|
+
expect(results[0].ruleResults[0].ruleName).toBe("FlowName");
|
|
44
|
+
expect(results[0].ruleResults[0].occurs).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should not have a result when in line with conventions", async () => {
|
|
48
|
+
const flows = await core.parse([fixed_uri]);
|
|
49
|
+
const ruleConfig = {
|
|
50
|
+
rules: {
|
|
51
|
+
FlowName: {
|
|
52
|
+
severity: "error",
|
|
53
|
+
expression: "[A-Za-z0-9]+_[A-Za-z0-9]+",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const results: core.ScanResult[] = core.scan(flows, ruleConfig);
|
|
59
|
+
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
|
|
60
|
+
expect(occurringResults).toHaveLength(0);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { ParsedFlow } from "../src/main/models/ParsedFlow";
|
|
2
|
+
import { RuleResult, Flow, scan, ScanResult } from "../src";
|
|
3
|
+
import { GetRecordAllFields } from "../src/main/rules/GetRecordAllFields";
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "@jest/globals";
|
|
6
|
+
|
|
7
|
+
describe("GetRecordAllFields", () => {
|
|
8
|
+
it("should be defined", () => {
|
|
9
|
+
expect(GetRecordAllFields).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
let rule: GetRecordAllFields;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
rule = new GetRecordAllFields();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("e2e", () => {
|
|
18
|
+
it("should be empty when no Get Record elements are present", () => {
|
|
19
|
+
const config = {
|
|
20
|
+
rules: {
|
|
21
|
+
GetRecordAllFields: {
|
|
22
|
+
severity: "error",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const flows: ParsedFlow[] = [
|
|
28
|
+
{
|
|
29
|
+
flow: {
|
|
30
|
+
type: "AutoLaunchedFlow",
|
|
31
|
+
},
|
|
32
|
+
} as Partial<ParsedFlow> as ParsedFlow,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const results: ScanResult[] = scan(flows, config);
|
|
36
|
+
const scanResults = results.pop();
|
|
37
|
+
const ruleResults = scanResults?.ruleResults.filter((rule) => {
|
|
38
|
+
return rule.ruleDefinition.name === "GetRecordAllFields" && rule.occurs;
|
|
39
|
+
});
|
|
40
|
+
expect(ruleResults).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should error when getRecord element has storeOutputAutomatically", () => {
|
|
44
|
+
const config = {
|
|
45
|
+
rules: {
|
|
46
|
+
GetRecordAllFields: {
|
|
47
|
+
severity: "error",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const flows: ParsedFlow[] = [
|
|
53
|
+
{
|
|
54
|
+
flow: {
|
|
55
|
+
type: "AutoLaunchedFlow",
|
|
56
|
+
elements: [
|
|
57
|
+
{
|
|
58
|
+
name: "GetRecord",
|
|
59
|
+
subtype: "recordLookups",
|
|
60
|
+
metaType: "node",
|
|
61
|
+
element: {
|
|
62
|
+
storeOutputAutomatically: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
} as Partial<ParsedFlow> as ParsedFlow,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const results: ScanResult[] = scan(flows, config);
|
|
71
|
+
const scanResults = results.pop();
|
|
72
|
+
const ruleResults = scanResults?.ruleResults.filter((rule) => {
|
|
73
|
+
return rule.ruleDefinition.name === "GetRecordAllFields" && rule.occurs;
|
|
74
|
+
});
|
|
75
|
+
expect(ruleResults).toHaveLength(1);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("empty unit", () => {
|
|
80
|
+
it("should be empty results when no Get Record elements are present", () => {
|
|
81
|
+
const flow: Flow = {
|
|
82
|
+
type: "AutoLaunchedFlow",
|
|
83
|
+
} as Partial<Flow> as Flow;
|
|
84
|
+
const result: RuleResult = rule.execute(flow);
|
|
85
|
+
expect(result.occurs).toBeFalsy();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should be empty when no qualified node", () => {
|
|
89
|
+
const flow: Flow = {
|
|
90
|
+
type: "AutoLaunchedFlow",
|
|
91
|
+
elements: [
|
|
92
|
+
{
|
|
93
|
+
name: "GetRecord",
|
|
94
|
+
subtype: "recordLookups",
|
|
95
|
+
metaType: "node",
|
|
96
|
+
element: "attribute",
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
} as Partial<Flow> as Flow;
|
|
100
|
+
const result: RuleResult = rule.execute(flow);
|
|
101
|
+
expect(result.occurs).toBeFalsy();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should be empty when outputReference and queriedFields are present", () => {
|
|
105
|
+
const flow: Flow = {
|
|
106
|
+
type: "AutoLaunchedFlow",
|
|
107
|
+
elements: [
|
|
108
|
+
{
|
|
109
|
+
name: "GetRecord",
|
|
110
|
+
subtype: "recordLookups",
|
|
111
|
+
metaType: "node",
|
|
112
|
+
element: {
|
|
113
|
+
queriedFields: ["Id", "AccountId"],
|
|
114
|
+
outputReference: "outputReference",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
} as Partial<Flow> as Flow;
|
|
119
|
+
const result: RuleResult = rule.execute(flow);
|
|
120
|
+
expect(result.occurs).toBeFalsy();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should be empty when outputAssignments are present", () => {
|
|
124
|
+
const flow: Flow = {
|
|
125
|
+
type: "AutoLaunchedFlow",
|
|
126
|
+
elements: [
|
|
127
|
+
{
|
|
128
|
+
name: "GetRecord",
|
|
129
|
+
subtype: "recordLookups",
|
|
130
|
+
metaType: "node",
|
|
131
|
+
element: {
|
|
132
|
+
outputAssignments: [{ assignToReference: "testVar", field: "AccountId" }],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
} as Partial<Flow> as Flow;
|
|
137
|
+
const result: RuleResult = rule.execute(flow);
|
|
138
|
+
expect(result.occurs).toBeFalsy();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should be empty when storeOutputAutomatically and queriedFields", () => {
|
|
142
|
+
const flow: Flow = {
|
|
143
|
+
type: "AutoLaunchedFlow",
|
|
144
|
+
elements: [
|
|
145
|
+
{
|
|
146
|
+
name: "GetRecord",
|
|
147
|
+
subtype: "recordLookups",
|
|
148
|
+
metaType: "node",
|
|
149
|
+
element: {
|
|
150
|
+
storeOutputAutomatically: true,
|
|
151
|
+
queriedFields: ["Id", "AccountId"],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
} as Partial<Flow> as Flow;
|
|
156
|
+
const result: RuleResult = rule.execute(flow);
|
|
157
|
+
expect(result.occurs).toBeFalsy();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("error unit", () => {
|
|
162
|
+
it("should error when Get Record element has storeOutputAutomatically and no queriedFields", () => {
|
|
163
|
+
const flow: Flow = {
|
|
164
|
+
type: "AutoLaunchedFlow",
|
|
165
|
+
elements: [
|
|
166
|
+
{
|
|
167
|
+
name: "GetRecord",
|
|
168
|
+
subtype: "recordLookups",
|
|
169
|
+
metaType: "node",
|
|
170
|
+
element: {
|
|
171
|
+
storeOutputAutomatically: true,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
} as Partial<Flow> as Flow;
|
|
176
|
+
const result: RuleResult = rule.execute(flow);
|
|
177
|
+
expect(result.occurs).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as core from "../src";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect } from "@jest/globals";
|
|
5
|
+
|
|
6
|
+
describe("HardcodedId", () => {
|
|
7
|
+
const example_uri = path.join(__dirname, "../../../assets/example-flows/force-app/main/default/flows/demo/Hardcoded_Id.flow-meta.xml");
|
|
8
|
+
|
|
9
|
+
it("there should be one result for the rule HardcodedIds", async () => {
|
|
10
|
+
const flows = await core.parse([example_uri]);
|
|
11
|
+
const results: core.ScanResult[] = core.scan(flows);
|
|
12
|
+
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
|
|
13
|
+
expect(occurringResults).toHaveLength(1);
|
|
14
|
+
expect(occurringResults[0].ruleName).toBe("HardcodedId");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { describe, it, expect } from "@jest/globals";
|
|
2
|
+
import { HardcodedUrl } from "../src/main/rules/HardcodedUrl";
|
|
3
|
+
import { Flow, ParsedFlow, ScanResult } from "../src/main/internals/internals";
|
|
4
|
+
import { scan } from "../src";
|
|
5
|
+
|
|
6
|
+
describe("HardcodedUrl", () => {
|
|
7
|
+
it("should be defined", () => {
|
|
8
|
+
expect(HardcodedUrl).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
let rule: HardcodedUrl;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
rule = new HardcodedUrl();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("e2e", () => {
|
|
18
|
+
it("should scan for hardcoded urls", () => {
|
|
19
|
+
const config = {
|
|
20
|
+
rules: {
|
|
21
|
+
HardcodedUrl: {
|
|
22
|
+
severity: "error",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const parsedFlows: ParsedFlow[] = [
|
|
28
|
+
{
|
|
29
|
+
flow: {
|
|
30
|
+
type: "AutoLaunchedFlow",
|
|
31
|
+
elements: [
|
|
32
|
+
{
|
|
33
|
+
name: "hardcodedUrl",
|
|
34
|
+
subtype: "formulas",
|
|
35
|
+
metaType: "variable",
|
|
36
|
+
element: {
|
|
37
|
+
name: "hardcodedUrl",
|
|
38
|
+
dataType: "String",
|
|
39
|
+
expression:
|
|
40
|
+
""https://mydomain.sandbox.my.salesforce.com/"&{!$User.Id}",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
} as Partial<ParsedFlow> as ParsedFlow,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const results: ScanResult[] = scan(parsedFlows, config);
|
|
49
|
+
const scanResults = results.pop();
|
|
50
|
+
const ruleResults = scanResults?.ruleResults.filter((rule) => {
|
|
51
|
+
return rule.ruleDefinition.name === "HardcodedUrl" && rule.occurs;
|
|
52
|
+
});
|
|
53
|
+
expect(ruleResults).toBeTruthy();
|
|
54
|
+
expect(ruleResults).toHaveLength(1);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("empty unit", () => {
|
|
59
|
+
it("should return empty results when flow has no elements", () => {
|
|
60
|
+
const flow = {
|
|
61
|
+
type: "AutoLaunchedFlow",
|
|
62
|
+
elements: [],
|
|
63
|
+
} as Partial<Flow> as Flow;
|
|
64
|
+
|
|
65
|
+
const result = rule.execute(flow);
|
|
66
|
+
expect(result).toBeDefined();
|
|
67
|
+
expect(result.occurs).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should return empty results when flow has no element property", () => {
|
|
71
|
+
const flow = {
|
|
72
|
+
type: "AutoLaunchedFlow",
|
|
73
|
+
} as Partial<Flow> as Flow;
|
|
74
|
+
|
|
75
|
+
const result = rule.execute(flow);
|
|
76
|
+
expect(result).toBeDefined();
|
|
77
|
+
expect(result.occurs).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should return empty results when url is generic", () => {
|
|
81
|
+
const flow = {
|
|
82
|
+
type: "AutoLaunchedFlow",
|
|
83
|
+
elements: [
|
|
84
|
+
{
|
|
85
|
+
name: "genericUrl",
|
|
86
|
+
subtype: "formulas",
|
|
87
|
+
metaType: "variable",
|
|
88
|
+
element: {
|
|
89
|
+
name: "genericUrl",
|
|
90
|
+
dataType: "String",
|
|
91
|
+
expression: ""https://www.google.com/"&{!$User.Id}",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
} as Partial<Flow> as Flow;
|
|
96
|
+
|
|
97
|
+
const result = rule.execute(flow);
|
|
98
|
+
expect(result).toBeDefined();
|
|
99
|
+
expect(result.occurs).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should return empty results when url is like jira", () => {
|
|
103
|
+
const flow = {
|
|
104
|
+
type: "AutoLaunchedFlow",
|
|
105
|
+
elements: [
|
|
106
|
+
{
|
|
107
|
+
name: "jiraUrl",
|
|
108
|
+
subtype: "formulas",
|
|
109
|
+
metaType: "variable",
|
|
110
|
+
element: {
|
|
111
|
+
name: "jiraUrl",
|
|
112
|
+
dataType: "String",
|
|
113
|
+
expression: ""https://mydomain.atlassian.net/browse/TEST-123",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
} as Partial<Flow> as Flow;
|
|
118
|
+
|
|
119
|
+
const result = rule.execute(flow);
|
|
120
|
+
expect(result).toBeDefined();
|
|
121
|
+
expect(result.occurs).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should return empty results when url is like confluence", () => {
|
|
125
|
+
const flow = {
|
|
126
|
+
type: "AutoLaunchedFlow",
|
|
127
|
+
elements: [
|
|
128
|
+
{
|
|
129
|
+
name: "confluenceUrl",
|
|
130
|
+
subtype: "formulas",
|
|
131
|
+
metaType: "variable",
|
|
132
|
+
element: {
|
|
133
|
+
name: "confluenceUrl",
|
|
134
|
+
dataType: "String",
|
|
135
|
+
expression:
|
|
136
|
+
""https://mydomain.atlassian.net/wiki/spaces/TEST/pages/123456789/Test+Page"",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
} as Partial<Flow> as Flow;
|
|
141
|
+
|
|
142
|
+
const result = rule.execute(flow);
|
|
143
|
+
expect(result).toBeDefined();
|
|
144
|
+
expect(result.occurs).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("error unit", () => {
|
|
149
|
+
describe("flow formula", () => {
|
|
150
|
+
it("should return results when hardcoding sandbox org url", () => {
|
|
151
|
+
const flow = {
|
|
152
|
+
type: "AutoLaunchedFlow",
|
|
153
|
+
elements: [
|
|
154
|
+
{
|
|
155
|
+
name: "hardcodedUrl",
|
|
156
|
+
subtype: "formulas",
|
|
157
|
+
metaType: "variable",
|
|
158
|
+
element: {
|
|
159
|
+
name: "hardcodedUrl",
|
|
160
|
+
dataType: "String",
|
|
161
|
+
expression:
|
|
162
|
+
""https://mydomain.sandbox.my.salesforce.com/"&{!$User.Id}",
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
} as Partial<Flow> as Flow;
|
|
167
|
+
|
|
168
|
+
const result = rule.execute(flow);
|
|
169
|
+
expect(result).toBeDefined();
|
|
170
|
+
expect(result.occurs).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should return results when hardcoding prod org url", () => {
|
|
174
|
+
const flow = {
|
|
175
|
+
type: "AutoLaunchedFlow",
|
|
176
|
+
elements: [
|
|
177
|
+
{
|
|
178
|
+
name: "hardcodedUrl",
|
|
179
|
+
subtype: "formulas",
|
|
180
|
+
metaType: "variable",
|
|
181
|
+
element: {
|
|
182
|
+
name: "hardcodedUrl",
|
|
183
|
+
dataType: "String",
|
|
184
|
+
expression: ""https://mydomain.my.salesforce.com/"&{!$User.Id}",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
} as Partial<Flow> as Flow;
|
|
189
|
+
|
|
190
|
+
const result = rule.execute(flow);
|
|
191
|
+
expect(result).toBeDefined();
|
|
192
|
+
expect(result.occurs).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("flow variable", () => {
|
|
197
|
+
it("should return results when hardcoding sandbox org url", () => {
|
|
198
|
+
const flow = {
|
|
199
|
+
type: "AutoLaunchedFlow",
|
|
200
|
+
elements: [
|
|
201
|
+
{
|
|
202
|
+
name: "hardcodedFlowVariable",
|
|
203
|
+
subtype: "variables",
|
|
204
|
+
metaType: "variable",
|
|
205
|
+
element: {
|
|
206
|
+
name: "hardcodedFlowVariable",
|
|
207
|
+
dataType: "String",
|
|
208
|
+
isCollection: false,
|
|
209
|
+
isInput: false,
|
|
210
|
+
isOutput: false,
|
|
211
|
+
value: {
|
|
212
|
+
stringValue: "https://mydomain.sandbox.my.salesforce.com/",
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
} as Partial<Flow> as Flow;
|
|
218
|
+
|
|
219
|
+
const result = rule.execute(flow);
|
|
220
|
+
expect(result).toBeDefined();
|
|
221
|
+
expect(result.occurs).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should return results when hardcoding prod org url", () => {
|
|
225
|
+
const flow = {
|
|
226
|
+
type: "AutoLaunchedFlow",
|
|
227
|
+
elements: [
|
|
228
|
+
{
|
|
229
|
+
name: "hardcodedFlowVariable",
|
|
230
|
+
subtype: "variables",
|
|
231
|
+
metaType: "variable",
|
|
232
|
+
element: {
|
|
233
|
+
name: "hardcodedFlowVariable",
|
|
234
|
+
dataType: "String",
|
|
235
|
+
isCollection: false,
|
|
236
|
+
isInput: false,
|
|
237
|
+
isOutput: false,
|
|
238
|
+
value: {
|
|
239
|
+
stringValue: "https://mydomain.my.salesforce.com/",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
} as Partial<Flow> as Flow;
|
|
245
|
+
|
|
246
|
+
const result = rule.execute(flow);
|
|
247
|
+
expect(result).toBeDefined();
|
|
248
|
+
expect(result.occurs).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|