Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/core/src/main/rules/DuplicateDMLOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export class DuplicateDMLOperation extends RuleCommon implements IRuleDefinition
if (
nextSeenDML &&
node.subtype === "screens" &&
node.element["allowBack"] === "true" &&
node.element["showFooter"] === "true" &&
this.isTrue(node.element["allowBack"]) &&
this.isTrue(node.element["showFooter"]) &&
!suppressions.has(node.name)
) {
violations.push(new core.Violation(node));
Expand All @@ -60,7 +60,7 @@ export class DuplicateDMLOperation extends RuleCommon implements IRuleDefinition
if (
nextSeenDML &&
node.subtype === "screens" &&
node.element["allowBack"] !== "true"
!this.isTrue(node.element["allowBack"])
) {
nextSeenDML = false;
}
Expand All @@ -80,4 +80,10 @@ export class DuplicateDMLOperation extends RuleCommon implements IRuleDefinition
node.subtype === "recordDeletes"
);
}

// XML-parsed flows carry the string "true"; flows built from Tooling API
// JSON (e.g. the Salesforce app) carry a real boolean.
private isTrue(value: unknown): boolean {
return value === true || String(value).toLowerCase() === "true";
}
}
73 changes: 73 additions & 0 deletions packages/core/tests/DuplicateDMLOperation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,39 @@ import * as path from "path";

import * as core from "../src";

const jsonRuleConfig = {
ruleMode: "isolated",
rules: {
DuplicateDMLOperation: {
severity: "error",
},
},
};

// Mirrors the shape produced by the Salesforce app when it fetches
// Flow.Metadata via the Tooling API - booleans are real booleans, not the
// "true"/"false" strings XML parsing yields.
function buildJsonFlow(screens: Record<string, unknown>[]): core.Flow {
const flowObj = {
label: "Duplicate DML Operation JSON",
processType: "Flow",
status: "Active",
recordCreates: {
name: "createAccount",
label: "createAccount",
connector: { targetReference: screens[0].name },
inputAssignments: { field: "Name", value: { elementReference: "account_name" } },
object: "Account",
storeOutputAutomatically: true,
},
screens,
start: {
connector: { targetReference: "createAccount" },
},
};
return new core.Flow("json-test-flow", flowObj);
}

describe("DuplicateDMLOperation ", () => {
const example_uri = path.join(__dirname, "../../../example-flows/force-app/demo/Demo_Duplicate_DML.flow-meta.xml");
const fixed_uri = path.join(__dirname, "../../../example-flows/force-app/testing/Duplicate_DML_Operation_Fixed.flow-meta.xml");
Expand Down Expand Up @@ -40,4 +73,44 @@ describe("DuplicateDMLOperation ", () => {
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
expect(occurringResults).toHaveLength(0);
});

it("should report a violation for a JSON-sourced flow with real boolean allowBack/showFooter", async () => {
const flow = buildJsonFlow([
{
name: "mock_screen_1",
label: "mock screen 1",
allowBack: true,
showFooter: true,
},
]);
const parsedFlow = new core.ParsedFlow("json-test-flow", flow);

const results: core.ScanResult[] = core.scan([parsedFlow], jsonRuleConfig);
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
expect(occurringResults).toHaveLength(1);
expect(occurringResults[0].ruleName).toBe("DuplicateDMLOperation");
});

it("should reset the seen-DML flag on a JSON-sourced back-disabled screen (real boolean false)", async () => {
const flow = buildJsonFlow([
{
name: "mock_screen_1",
label: "mock screen 1",
allowBack: false,
showFooter: true,
connector: { targetReference: "mock_screen_2" },
},
{
name: "mock_screen_2",
label: "mock screen 2",
allowBack: true,
showFooter: true,
},
]);
const parsedFlow = new core.ParsedFlow("json-test-flow", flow);

const results: core.ScanResult[] = core.scan([parsedFlow], jsonRuleConfig);
const occurringResults = results[0].ruleResults.filter((rule) => rule.occurs);
expect(occurringResults).toHaveLength(0);
});
});
Loading