/** * 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 { buildRaw } from "../build-raw.js"; vi.mock("../../lib/session.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, createSession: vi.fn(actual.createSession) }; }); const SCHEMA_SDL = ` type Query { uiapi: UIAPI! } type UIAPI { query: RecordQuery!, aggregate: RecordQueryAggregate! } type RecordQuery { Case(first: Int, after: String, where: Case_Filter): CaseConnection! } type RecordQueryAggregate { Case(first: Int, where: Case_Filter, groupBy: Case_GroupBy): CaseAggregateConnection } input Case_Filter { Status: PicklistOperators } input Case_GroupBy { Status: GroupByClause } input GroupByClause { group: Boolean } input PicklistOperators { eq: String } type CaseConnection { edges: [CaseEdge!]!, pageInfo: PageInfo! } type CaseEdge { node: Case! } type CaseAggregateConnection { edges: [CaseAggregateEdge!]!, pageInfo: PageInfo! } type CaseAggregateEdge { node: CaseResult! } type CaseResult { aggregate: CaseAggregate } type CaseAggregate { Id: IDAggregate } type IDAggregate { count: Int } type PageInfo { hasNextPage: Boolean!, endCursor: String } type Case { Id: ID!, Subject: StringValue, Status: StringValue } type StringValue { value: String } type Mutation { uiapi: UIAPIMut! } type UIAPIMut { placeholder: Boolean } `; const ORG = "test-raw"; const ORG_URL = "https://test-raw.my.salesforce.com"; const SCHEMA = buildSchema(SCHEMA_SDL); primeSchemaCache(ORG, SCHEMA); primeSchemaCache(ORG_URL, SCHEMA); const deps = () => makeNoopPrimeDeps(ORG, ORG_URL, SCHEMA); describe("intent/build-raw", () => { it("renders a query from select + set commands", async () => { const out = await buildRaw( { org: ORG, commands: [ "select uiapi/query/Case/edges/node/Subject/value", "set uiapi/query/Case first=5", ], }, deps(), ); expect(out.query).toMatch(/Subject\s+@optional\s*\{\s*value\s*\}/); expect(out.query).toMatch(/first:\s*5/); expect(out.query).toMatch(/\bquery\b/); }); it("uses the mutation root when operation is mutation", async () => { const out = await buildRaw( { org: ORG, commands: ["select uiapi/placeholder"], operation: "mutation" }, deps(), ); expect(out.query).toMatch(/\bmutation\b/); }); it("uses the aggregate root when operation is aggregate", async () => { const out = await buildRaw( { org: ORG, commands: ["select uiapi/aggregate/Case/edges/node/aggregate/Id/count"], operation: "aggregate", }, deps(), ); // Aggregate operations render under the query keyword against uiapi.aggregate. expect(out.query).toMatch(/\bquery\s+RawAggregate\b/); expect(out.query).toMatch(/aggregate\s*\{[\s\S]*Id\s*\{[\s\S]*count/); }); it("honors typeName as the operation name", async () => { const out = await buildRaw( { org: ORG, commands: ["select uiapi/query/Case/edges/node/Id"], typeName: "MyRaw" }, deps(), ); expect(out.query).toMatch(/\bquery\s+MyRaw\b/); }); it("defaults operation name to RawQuery", async () => { const out = await buildRaw( { org: ORG, commands: ["select uiapi/query/Case/edges/node/Id"] }, deps(), ); expect(out.query).toMatch(/\bquery\s+RawQuery\b/); }); it("fails fast: a bad command throws with its index and text", async () => { await expect( buildRaw( { org: ORG, commands: ["select uiapi/query/Case/edges/node/Id", "frobnicate the widget"], }, deps(), ), ).rejects.toThrow(/command 1 \(frobnicate the widget\).*unknown command 'frobnicate'/s); }); it("rejects an empty commands array", async () => { await expect(buildRaw({ org: ORG, commands: [] }, deps())).rejects.toThrow( /at least one command/, ); }); it("returns a ToolOutput with the standard shape", async () => { const out = await buildRaw( { org: ORG, commands: ["select uiapi/query/Case/edges/node/Id"] }, deps(), ); expect(out).toHaveProperty("query"); expect(out).toHaveProperty("variables"); expect(out).toHaveProperty("types"); expect(Array.isArray(out.warnings)).toBe(true); }); it("rejects a typeName that is not a valid GraphQL Name", async () => { await expect( buildRaw( { org: ORG, commands: ["select uiapi/query/Case/edges/node/Id"], typeName: "has spaces" }, deps(), ), ).rejects.toThrow(/buildRaw: typeName '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 buildRaw({ org: ORG, commands: ["select uiapi/query/Case/edges/node/Id"] }, deps()); expect(spy).toHaveBeenCalledWith(ORG, "query", ORG_URL); }); });