/** * 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 { buildDelete } from "../build-delete.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 { AccountDelete(input: RecordDeleteInput!): RecordDeletePayload Custom_Object__cDelete(input: RecordDeleteInput!): RecordDeletePayload } input RecordDeleteInput { Id: ID! } type RecordDeletePayload { Id: ID } type Account { Id: ID!, Name: StringValue } type Custom_Object__c { Id: ID!, Name: StringValue } type StringValue { value: String } `; const ORG = "test-delete"; const ORG_URL = "https://test-delete.my.salesforce.com"; const SCHEMA = buildSchema(SCHEMA_SDL); primeSchemaCache(ORG, SCHEMA); primeSchemaCache(ORG_URL, SCHEMA); function noopPrimeDeps() { return makeNoopPrimeDeps(ORG, ORG_URL, SCHEMA); } describe("intent/build-delete", () => { it("renders a delete mutation with RecordDeleteInput! and selects Id only", async () => { const result = await buildDelete({ org: ORG, object: "Account" }, noopPrimeDeps()); expect(result.query).toContain("mutation DeleteAccount"); expect(result.query).toContain("$input: RecordDeleteInput!"); expect(result.query).toContain("AccountDelete(input: $input)"); // Id is plain ID on the payload — selected bare, never `Id { value }`. // Id is FLS-exempt, so it carries no @optional directive (W-22818723). expect(result.query).toMatch(/AccountDelete\(input: \$input\)\s*{\s*Id\s*}/); expect(result.query).not.toMatch(/\bId\s+@optional\b/); // No `Record { ... }` sub-selection (delete payloads have no Record path). expect(result.query).not.toMatch(/\bRecord\s*{/); expect(result.query).not.toContain("Id { value }"); }); it("declares a single required input variable of type RecordDeleteInput!", async () => { const result = await buildDelete({ org: ORG, object: "Account" }, noopPrimeDeps()); expect(result.variables).toEqual([ { name: "input", type: "RecordDeleteInput!", required: true }, ]); }); it("uses RecordDeleteInput! for custom objects too (schema-wide, not -specific)", async () => { const result = await buildDelete({ org: ORG, object: "Custom_Object__c" }, noopPrimeDeps()); expect(result.query).toContain("Custom_Object__cDelete(input: $input)"); expect(result.query).toContain("$input: RecordDeleteInput!"); // Crucially NOT Custom_Object__cDeleteInput. expect(result.query).not.toContain("Custom_Object__cDeleteInput"); }); it("honors a custom operationName", async () => { const result = await buildDelete( { org: ORG, object: "Account", operationName: "RemoveAcct" }, noopPrimeDeps(), ); expect(result.query).toContain("mutation RemoveAcct"); }); it("honors a custom inputVariable and strips a leading $", async () => { const result = await buildDelete( { org: ORG, object: "Account", inputVariable: "$acctInput" }, noopPrimeDeps(), ); expect(result.query).toContain("$acctInput: RecordDeleteInput!"); expect(result.query).toContain("AccountDelete(input: $acctInput)"); }); it("produces no validation warnings for a well-formed delete", async () => { const result = await buildDelete({ org: ORG, object: "Account" }, noopPrimeDeps()); expect(result.warnings.filter((w) => w.startsWith("Validation:"))).toEqual([]); }); it("throws for an object that is not a valid GraphQL Name", async () => { await expect(buildDelete({ org: ORG, object: "my-object" }, noopPrimeDeps())).rejects.toThrow( /buildDelete: object 'my-object' is not a valid GraphQL Name/, ); }); it("throws for an inputVariable that is not a valid GraphQL Name", async () => { await expect( buildDelete({ org: ORG, object: "Account", inputVariable: "has spaces" }, noopPrimeDeps()), ).rejects.toThrow(/buildDelete: inputVariable 'has spaces' is not a valid GraphQL Name/); }); it("throws for an operationName that is not a valid GraphQL Name", async () => { await expect( buildDelete({ org: ORG, object: "Account", operationName: "has spaces" }, noopPrimeDeps()), ).rejects.toThrow(/buildDelete: operationName 'has spaces' is not a valid GraphQL Name/); }); it("threads instanceUrl as 3rd arg to createSession", async () => { const spy = vi.mocked(sessionModule.createSession); spy.mockClear(); await buildDelete({ org: ORG, object: "Account" }, noopPrimeDeps()); expect(spy).toHaveBeenCalledWith(ORG, "mutation", ORG_URL); }); });