/** * Copyright (c) 2026, Salesforce, Inc., * All rights reserved. * For full license text, see the LICENSE.txt file */ import { describe, expect, it } from "vitest"; import { assertDottedGraphqlName, assertGraphqlName, DOTTED_GRAPHQL_NAME_RE, GRAPHQL_NAME_RE, } from "../graphql-name.js"; /** * Single source of truth for the GraphQL Name guard shared by every intent * builder. The exhaustive valid/invalid matrix lives here; each builder spec * keeps just one representative case proving it wires the guard in. */ describe("lib/graphql-name", () => { describe("assertGraphqlName", () => { it.each([ "Account", "Custom_Object__c", "MyNS__Field__c", // namespaced custom field (PR #678 review: __c regression guard) "_Foo", "_", "a", "a1_2b", "CreateAccount", ])("accepts the valid GraphQL Name %j", (value) => { expect(() => assertGraphqlName(value, "buildX", "operationName")).not.toThrow(); }); it.each([ "", // empty "1Bad", // leading digit "my-op", // hyphen "has spaces", // internal space "$op", // leading $ (no stripping in the operation-name position) " ", // whitespace only " leading", // leading space "trailing ", // trailing space "X { uiapi { __typename } } query Y", // the injection payload from W-22731343 "}}__schema{types{name", // brace injection ])("rejects the invalid GraphQL Name %j", (value) => { expect(() => assertGraphqlName(value, "buildX", "operationName")).toThrow( /is not a valid GraphQL Name/, ); }); it("throws the exact builder- and field-prefixed message", () => { expect(() => assertGraphqlName("my-op", "buildList", "operationName")).toThrow( "buildList: operationName 'my-op' is not a valid GraphQL Name (must match /^[A-Za-z_][A-Za-z0-9_]*$/)", ); }); it("uses the supplied builder and field labels verbatim", () => { expect(() => assertGraphqlName("1x", "buildRaw", "typeName")).toThrow( /^buildRaw: typeName '1x' is not a valid GraphQL Name/, ); }); }); describe("GRAPHQL_NAME_RE", () => { it("matches the GraphQL Name production and rejects non-conforming names", () => { expect(GRAPHQL_NAME_RE.test("Account")).toBe(true); expect(GRAPHQL_NAME_RE.test("_Foo")).toBe(true); expect(GRAPHQL_NAME_RE.test("Custom_Object__c")).toBe(true); expect(GRAPHQL_NAME_RE.test("1Bad")).toBe(false); expect(GRAPHQL_NAME_RE.test("has spaces")).toBe(false); expect(GRAPHQL_NAME_RE.test("")).toBe(false); }); }); // W-22735537: dotted field-path guard for returnFields / fields / parentFields // / childRelationships.fields. Each '.'-separated segment must be a GraphQL // Name; a breakout string (containing { } : or whitespace) cannot match. describe("assertDottedGraphqlName", () => { it.each([ "Id", "Name", "Owner.Name", "CreatedBy.Profile.Name", "Amount__c", // Salesforce custom field (PR #678 review: __c regression guard) "Owner.Custom__c", // dotted path ending in a custom field "MyNS__Field__c", // namespaced custom field (double underscore in the middle) "_a._b", "a1.b2.c3", "_", ])("accepts the valid dotted field path %j", (value) => { expect(() => assertDottedGraphqlName(value, "buildX", "returnFields entry")).not.toThrow(); }); it.each([ "", // empty ".Name", // leading dot "Owner.", // trailing dot "Owner..Name", // empty segment "1Bad.Name", // segment leading digit "Owner.Na-me", // hyphen in segment "a b", // space "Id } injectedAlias: Name { value", // the W-22735537 selection-set breakout "Name { value } evil", "Id @skip(if:true)", "Owner.Name { value", ])("rejects the invalid field path %j", (value) => { expect(() => assertDottedGraphqlName(value, "buildX", "returnFields entry")).toThrow( /is not a valid field path/, ); }); it("throws the exact builder- and field-prefixed message", () => { expect(() => assertDottedGraphqlName("a b", "buildMutation", "returnFields entry")).toThrow( /^buildMutation: returnFields entry 'a b' is not a valid field path/, ); }); }); describe("DOTTED_GRAPHQL_NAME_RE", () => { it("accepts dotted paths and rejects breakout strings", () => { expect(DOTTED_GRAPHQL_NAME_RE.test("Owner.Name")).toBe(true); expect(DOTTED_GRAPHQL_NAME_RE.test("Id")).toBe(true); expect(DOTTED_GRAPHQL_NAME_RE.test("Id } x: y { z")).toBe(false); expect(DOTTED_GRAPHQL_NAME_RE.test("Owner.")).toBe(false); expect(DOTTED_GRAPHQL_NAME_RE.test("")).toBe(false); }); }); });