From a7c44e95279d22862ba7babd3b9c789122d77884 Mon Sep 17 00:00:00 2001 From: RubenHalman <25385823+RubenHalman@users.noreply.github.com> Date: Tue, 25 Aug 2026 07:34:00 +0200 Subject: [PATCH] fix(core): DuplicateDMLOperation now handles JSON-sourced boolean flow metadata allowBack/showFooter compared strictly against the string "true", so flows parsed from Tooling API JSON (real booleans) never matched: violations were silently missed and the back-disabled reset never fired. --- .../src/main/rules/DuplicateDMLOperation.ts | 12 ++- .../core/tests/DuplicateDMLOperation.test.ts | 73 +++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/packages/core/src/main/rules/DuplicateDMLOperation.ts b/packages/core/src/main/rules/DuplicateDMLOperation.ts index 6e3e7dac..c63ef80f 100644 --- a/packages/core/src/main/rules/DuplicateDMLOperation.ts +++ b/packages/core/src/main/rules/DuplicateDMLOperation.ts @@ -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)); @@ -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; } @@ -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"; + } } diff --git a/packages/core/tests/DuplicateDMLOperation.test.ts b/packages/core/tests/DuplicateDMLOperation.test.ts index 0aa1bd78..bc8b4f88 100644 --- a/packages/core/tests/DuplicateDMLOperation.test.ts +++ b/packages/core/tests/DuplicateDMLOperation.test.ts @@ -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[]): 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"); @@ -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); + }); });