/** * Copyright (c) 2026, Salesforce, Inc., * All rights reserved. * For full license text, see the LICENSE.txt file */ import { buildSchema } from "graphql"; import { describe, expect, it, vi } from "vitest"; import { makeNoopPrimeDeps } from "../../__tests__/helpers/prime-deps.js"; import * as sessionModule from "../../lib/session.js"; import { primeSchemaCache } from "../../lib/walker.js"; import { buildMutation } from "../build-mutation.js"; vi.mock("../../lib/session.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, createSession: vi.fn(actual.createSession) }; }); const SCHEMA_SDL = ` type Query { _placeholder: Boolean } type Mutation { uiapi(input: UIAPIMutationsInput): UIAPIMutations! } input UIAPIMutationsInput { allOrNone: Boolean } type UIAPIMutations { AccountCreate(input: AccountCreateInput!): AccountCreatePayload AccountUpdate(input: AccountUpdateInput!): AccountUpdatePayload } input AccountCreateInput { Account: AccountCreateRepresentation! } input AccountCreateRepresentation { Name: String } type AccountCreatePayload { Record: Account } input AccountUpdateInput { Account: AccountUpdateRepresentation! } input AccountUpdateRepresentation { Name: String } type AccountUpdatePayload { Record: Account } type Account { Id: ID!, Name: StringValue } type StringValue { value: String } `; const ORG = "test-build-mutation"; const ORG_URL = "https://test-build-mutation.my.salesforce.com"; const SCHEMA = buildSchema(SCHEMA_SDL); primeSchemaCache(ORG, SCHEMA); primeSchemaCache(ORG_URL, SCHEMA); const noopPrimeDeps = () => makeNoopPrimeDeps(ORG, ORG_URL, SCHEMA); describe("intent/build-mutation", () => { it("op=Create renders mutation Create with CreateInput!", async () => { const result = await buildMutation( { org: ORG, object: "Account", returnFields: ["Id", "Name"] }, "Create", noopPrimeDeps(), ); expect(result.query).toMatch(/mutation CreateAccount/); expect(result.query).toMatch(/\$input:\s*AccountCreateInput!/); expect(result.query).toMatch(/AccountCreate\(input:\s*\$input\)/); expect(result.query).toMatch(/Name\s+@optional\s*\{\s*value/s); expect(result.variables).toEqual([ { name: "input", type: "AccountCreateInput!", required: true }, ]); }); it("op=Update renders mutation Update with UpdateInput!", async () => { const result = await buildMutation( { org: ORG, object: "Account", returnFields: ["Id", "Name"] }, "Update", noopPrimeDeps(), ); expect(result.query).toMatch(/mutation UpdateAccount/); expect(result.query).toMatch(/\$input:\s*AccountUpdateInput!/); expect(result.query).toMatch(/AccountUpdate\(input:\s*\$input\)/); expect(result.query).toMatch(/Name\s+@optional\s*\{\s*value/s); expect(result.variables).toEqual([ { name: "input", type: "AccountUpdateInput!", required: true }, ]); }); it('op=Update defaults returnFields to ["Id"]', async () => { const result = await buildMutation({ org: ORG, object: "Account" }, "Update", noopPrimeDeps()); // Id is FLS-exempt: selected bare, never @optional (W-22818723). expect(result.query).toMatch(/\bId\b/); expect(result.query).not.toMatch(/\bId\s+@optional\b/); expect(result.query).not.toMatch(/Name\s*\{/); }); it("returned record fields carry @optional directive for FLS safety", async () => { const result = await buildMutation( { org: ORG, object: "Account", returnFields: ["Id", "Name"] }, "Create", noopPrimeDeps(), ); // FLS-gateable record fields get @optional; Id is exempt (selected bare) expect(result.query).toMatch(/Name\s+@optional\s*\{/); expect(result.query).not.toMatch(/\bId\s+@optional\b/); // Structural plumbing (Record, uiapi, mutation name) never marked expect(result.query).not.toMatch(/Record\s+@optional/); expect(result.query).not.toMatch(/uiapi\s+@optional/); expect(result.query).not.toMatch(/AccountCreate\s+@optional/); }); it("op=Update with custom inputVariable strips leading $ and declares it", async () => { const result = await buildMutation( { org: ORG, object: "Account", inputVariable: "$myInput" }, "Update", noopPrimeDeps(), ); expect(result.query).toMatch(/\$myInput:\s*AccountUpdateInput!/); expect(result.query).toMatch(/input:\s*\$myInput/); expect(result.query).not.toMatch(/\$\$myInput/); }); it("op=Update with custom operationName uses it", async () => { const result = await buildMutation( { org: ORG, object: "Account", operationName: "MutateAccount" }, "Update", noopPrimeDeps(), ); expect(result.query).toMatch(/mutation MutateAccount/); }); it("threads instanceUrl as 3rd arg to createSession", async () => { const spy = vi.mocked(sessionModule.createSession); spy.mockClear(); await buildMutation({ org: ORG, object: "Account" }, "Update", noopPrimeDeps()); expect(spy).toHaveBeenCalledWith(ORG, "mutation", ORG_URL); }); });