From 889675eee3d544104fd8597bbd10626c08eafe7d Mon Sep 17 00:00:00 2001 From: jdolle <1841898+jdolle@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:28:09 -0700 Subject: [PATCH 1/7] Support composing directive argument default values --- .changeset/smart-bees-lose.md | 5 +++++ __tests__/composition.spec.ts | 22 +++++++++++++++++++++- src/supergraph/composition/directive.ts | 4 ++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .changeset/smart-bees-lose.md diff --git a/.changeset/smart-bees-lose.md b/.changeset/smart-bees-lose.md new file mode 100644 index 00000000..ac1a6daa --- /dev/null +++ b/.changeset/smart-bees-lose.md @@ -0,0 +1,5 @@ +--- +"@theguild/federation-composition": patch +--- + +Support composing directive argument default values diff --git a/__tests__/composition.spec.ts b/__tests__/composition.spec.ts index ff054def..610b36de 100644 --- a/__tests__/composition.spec.ts +++ b/__tests__/composition.spec.ts @@ -1587,6 +1587,20 @@ testImplementations((api) => { } `), }, + { + name: "d", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key", "@composeDirective"]) + @link(url: "https://myspecs.dev/access/v1.0", import: ["@access"]) + @composeDirective(name: "@access") + + directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION + enum Scope { PUBLIC PRIVATE } + + type Query { hello: String @access } + `), + } ]); assertCompositionSuccess(result); @@ -1626,11 +1640,16 @@ testImplementations((api) => { } `); + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */` + directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION + `); + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` type Query @join__type(graph: A) @join__type(graph: B) - @join__type(graph: C) { + @join__type(graph: C) + @join__type(graph: D) { books( filter: LibraryFilter access: LibraryAccess = PUBLIC @@ -1639,6 +1658,7 @@ testImplementations((api) => { @join__field(graph: B) media(filter: MediaFilter, access: MediaAccess = PRIVATE): [Media!]! @join__field(graph: C) + hello: String @access @join__field(graph: D) } `); }); diff --git a/src/supergraph/composition/directive.ts b/src/supergraph/composition/directive.ts index c56bcee4..070233a0 100644 --- a/src/supergraph/composition/directive.ts +++ b/src/supergraph/composition/directive.ts @@ -51,6 +51,10 @@ export function directiveBuilder(): TypeBuilder { argState.ast.directives.push(directive); }); + if (typeof arg.defaultValue !== "undefined") { + argState.defaultValue = arg.defaultValue; + } + if (arg.inaccessible) { argState.inaccessible = true; } From 8998b02ad6dd82c1e80988a040007f3b8e1f2255 Mon Sep 17 00:00:00 2001 From: jdolle <1841898+jdolle@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:33:02 -0700 Subject: [PATCH 2/7] Add negative test cases; add example to change --- .changeset/smart-bees-lose.md | 24 ++- __tests__/composition.spec.ts | 382 +++++++++++++++++++++++----------- 2 files changed, 284 insertions(+), 122 deletions(-) diff --git a/.changeset/smart-bees-lose.md b/.changeset/smart-bees-lose.md index ac1a6daa..5f5b21b4 100644 --- a/.changeset/smart-bees-lose.md +++ b/.changeset/smart-bees-lose.md @@ -2,4 +2,26 @@ "@theguild/federation-composition": patch --- -Support composing directive argument default values +Support composing directive argument default values. E.g. + +```graphql +extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.5" + import: ["@key", "@composeDirective"] + ) + @link(url: "https://myspecs.dev/access/v1.0", import: ["@access"]) + @composeDirective(name: "@access") + +directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION +enum Scope { + PUBLIC + PRIVATE +} + +type Query { + hello: String @access +} +``` + +Previously, the "PUBLIC" default value in the above example would not be set in the composed result. diff --git a/__tests__/composition.spec.ts b/__tests__/composition.spec.ts index 610b36de..b746bfca 100644 --- a/__tests__/composition.spec.ts +++ b/__tests__/composition.spec.ts @@ -12,6 +12,7 @@ import { testImplementations, versions, } from "./shared/testkit.js"; +import { CompositionResult } from "../src/compose.js"; expect.addSnapshotSerializer({ serialize: (value) => print(sortSDL(parse(value as string))), @@ -1517,150 +1518,289 @@ testImplementations((api) => { `); }); - test("print default values", () => { - const result = composeServices([ - { - name: "a", - typeDefs: parse(/* GraphQL */ ` - extend schema - @link(url: "https://specs.apollo.dev/federation/${version}", import: ["@shareable"]) + describe("default values", () => { + test("print", () => { + const result = composeServices([ + { + name: "a", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link(url: "https://specs.apollo.dev/federation/${version}", import: ["@shareable"]) - input LibraryFilter { - limit: Int = 5 - } + input LibraryFilter { + limit: Int = 5 + } - enum LibraryAccess { - PUBLIC - PRIVATE - } + enum LibraryAccess { + PUBLIC + PRIVATE + } - type Query { - books(filter: LibraryFilter, access: LibraryAccess = PUBLIC): [String!]! - } - `), - }, - { - name: "b", - typeDefs: parse(/* GraphQL */ ` - extend schema - @link(url: "https://specs.apollo.dev/federation/${version}", import: ["@shareable"]) + type Query { + books(filter: LibraryFilter, access: LibraryAccess = PUBLIC): [String!]! + } + `), + }, + { + name: "b", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link(url: "https://specs.apollo.dev/federation/${version}", import: ["@shareable"]) - input UserFilter { - limit: Int = 10 - } + input UserFilter { + limit: Int = 10 + } - enum UserAccess { - PUBLIC - PRIVATE - } + enum UserAccess { + PUBLIC + PRIVATE + } - type Query { - users(filter: UserFilter, access: UserAccess = PRIVATE): [String!]! - } - `), - }, - { - name: "c", - typeDefs: parse(/* GraphQL */ ` - extend schema - @link(url: "https://specs.apollo.dev/federation/${version}", import: ["@shareable"]) + type Query { + users(filter: UserFilter, access: UserAccess = PRIVATE): [String!]! + } + `), + }, + { + name: "c", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link(url: "https://specs.apollo.dev/federation/${version}", import: ["@shareable"]) - input MediaFilter { - limit: Int = 15 - } + input MediaFilter { + limit: Int = 15 + } - enum MediaAccess { - PUBLIC - PRIVATE - } + enum MediaAccess { + PUBLIC + PRIVATE + } - interface Media { - records(filter: MediaFilter, access: MediaAccess = PRIVATE): [String!]! - } + interface Media { + records(filter: MediaFilter, access: MediaAccess = PRIVATE): [String!]! + } - type Movie implements Media { - records(filter: MediaFilter, access: MediaAccess = PUBLIC): [String!]! - } + type Movie implements Media { + records(filter: MediaFilter, access: MediaAccess = PUBLIC): [String!]! + } - type Query { - media(filter: MediaFilter, access: MediaAccess = PRIVATE): [Media!]! - } - `), - }, - { - name: "d", - typeDefs: parse(/* GraphQL */ ` - extend schema - @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key", "@composeDirective"]) - @link(url: "https://myspecs.dev/access/v1.0", import: ["@access"]) - @composeDirective(name: "@access") + type Query { + media(filter: MediaFilter, access: MediaAccess = PRIVATE): [Media!]! + } + `), + }, + { + name: "d", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.5" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") - directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION - enum Scope { PUBLIC PRIVATE } + directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION + enum Scope { + PUBLIC + PRIVATE + } - type Query { hello: String @access } - `), - } - ]); + type Query { + hello: String @access + } + `), + }, + ]); - assertCompositionSuccess(result); + assertCompositionSuccess(result); - expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` - input LibraryFilter @join__type(graph: A) { - limit: Int = 5 - } - `); + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` + input LibraryFilter @join__type(graph: A) { + limit: Int = 5 + } + `); - expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` - input UserFilter @join__type(graph: B) { - limit: Int = 10 - } - `); + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` + input UserFilter @join__type(graph: B) { + limit: Int = 10 + } + `); - expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` - input MediaFilter @join__type(graph: C) { - limit: Int = 15 - } - `); + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` + input MediaFilter @join__type(graph: C) { + limit: Int = 15 + } + `); - expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` - interface Media @join__type(graph: C) { - records( - filter: MediaFilter - access: MediaAccess = PRIVATE - ): [String!]! - } - `); + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` + interface Media @join__type(graph: C) { + records( + filter: MediaFilter + access: MediaAccess = PRIVATE + ): [String!]! + } + `); - expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` - type Movie implements Media - @join__implements(graph: C, interface: "Media") - @join__type(graph: C) { - records(filter: MediaFilter, access: MediaAccess = PUBLIC): [String!]! - } - `); + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` + type Movie implements Media + @join__implements(graph: C, interface: "Media") + @join__type(graph: C) { + records( + filter: MediaFilter + access: MediaAccess = PUBLIC + ): [String!]! + } + `); - expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */` - directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION - `); + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` + directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION + `); - expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` - type Query - @join__type(graph: A) - @join__type(graph: B) - @join__type(graph: C) - @join__type(graph: D) { - books( - filter: LibraryFilter - access: LibraryAccess = PUBLIC - ): [String!]! @join__field(graph: A) - users(filter: UserFilter, access: UserAccess = PRIVATE): [String!]! - @join__field(graph: B) - media(filter: MediaFilter, access: MediaAccess = PRIVATE): [Media!]! - @join__field(graph: C) + expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` + type Query + @join__type(graph: A) + @join__type(graph: B) + @join__type(graph: C) + @join__type(graph: D) { + books( + filter: LibraryFilter + access: LibraryAccess = PUBLIC + ): [String!]! @join__field(graph: A) + users(filter: UserFilter, access: UserAccess = PRIVATE): [String!]! + @join__field(graph: B) + media( + filter: MediaFilter + access: MediaAccess = PRIVATE + ): [Media!]! @join__field(graph: C) hello: String @access @join__field(graph: D) + } + `); + }); + + test("collision with missing default", () => { + const result = composeServices([ + { + name: "a", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.5" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") + + directive @access(scope: Scope!) on FIELD_DEFINITION + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + hello: String @access + } + `), + }, + { + name: "b", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.5" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") + + directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + hello: String @access + } + `), + }, + ]); + + assertCompositionFailure(result); + }); + + test("collision with different default", () => { + let result: CompositionResult; + try { + result = composeServices([ + { + name: "a", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.5" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") + + directive @access(scope: Scope! = PRIVATE) on FIELD_DEFINITION + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + hello: String @access + } + `), + }, + { + name: "b", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.5" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") + + directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + hello: String @access + } + `), + }, + ]); + } catch (e) { + result = { + errors: [e as any], + }; } - `); + + assertCompositionFailure(result); + }); }); test("merge enum type types when used as the return type for at least one object or interface field", () => { From 30fc9f878c97aebfa896ba18d7625257ff300995 Mon Sep 17 00:00:00 2001 From: jdolle <1841898+jdolle@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:50:12 -0700 Subject: [PATCH 3/7] Add another default check that isnt for directives --- __tests__/composition.spec.ts | 56 ++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/__tests__/composition.spec.ts b/__tests__/composition.spec.ts index b746bfca..37b28319 100644 --- a/__tests__/composition.spec.ts +++ b/__tests__/composition.spec.ts @@ -1688,7 +1688,7 @@ testImplementations((api) => { typeDefs: parse(/* GraphQL */ ` extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5" + url: "https://specs.apollo.dev/federation/${version}" import: ["@key", "@composeDirective"] ) @link( @@ -1713,7 +1713,7 @@ testImplementations((api) => { typeDefs: parse(/* GraphQL */ ` extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5" + url: "https://specs.apollo.dev/federation/${version}" import: ["@key", "@composeDirective"] ) @link( @@ -1747,7 +1747,7 @@ testImplementations((api) => { typeDefs: parse(/* GraphQL */ ` extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5" + url: "https://specs.apollo.dev/federation/${version}" import: ["@key", "@composeDirective"] ) @link( @@ -1772,7 +1772,7 @@ testImplementations((api) => { typeDefs: parse(/* GraphQL */ ` extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5" + url: "https://specs.apollo.dev/federation/${version}" import: ["@key", "@composeDirective"] ) @link( @@ -1794,6 +1794,54 @@ testImplementations((api) => { }, ]); } catch (e) { + expect(api.library).toBe('apollo'); + result = { + errors: [e as any], + }; + } + + assertCompositionFailure(result); + }); + + test("collision with different default on argument", () => { + let result: CompositionResult; + try { + result = composeServices([ + { + name: "a", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link(url: "https://specs.apollo.dev/federation/${version}") + + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + hello(scope: Scope! = PUBLIC): String + } + `), + }, + { + name: "b", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link(url: "https://specs.apollo.dev/federation/${version}") + + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + hello(scope: Scope! = PRIVATE): String + } + `), + }, + ]); + } catch (e) { + expect(api.library).toBe('apollo'); result = { errors: [e as any], }; From 05597c69896c82b4c39c9298b3868564cf873ff4 Mon Sep 17 00:00:00 2001 From: jdolle <1841898+jdolle@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:50:36 -0700 Subject: [PATCH 4/7] Format --- __tests__/composition.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/composition.spec.ts b/__tests__/composition.spec.ts index 37b28319..ce4c9c22 100644 --- a/__tests__/composition.spec.ts +++ b/__tests__/composition.spec.ts @@ -1794,7 +1794,7 @@ testImplementations((api) => { }, ]); } catch (e) { - expect(api.library).toBe('apollo'); + expect(api.library).toBe("apollo"); result = { errors: [e as any], }; @@ -1841,7 +1841,7 @@ testImplementations((api) => { }, ]); } catch (e) { - expect(api.library).toBe('apollo'); + expect(api.library).toBe("apollo"); result = { errors: [e as any], }; From e539c5703668dde7ca344207bdc06f3e391ae38b Mon Sep 17 00:00:00 2001 From: Laurin Date: Wed, 22 Jul 2026 14:47:44 +0200 Subject: [PATCH 5/7] make test cases actually assert the expected behaviour (#313) The tests were not correctly asserting the default value behaviour as the assertions were raising errors due to other invalid subgraph conflicts. --- __tests__/composition.spec.ts | 176 +++++++++++++++++++++++++--------- 1 file changed, 133 insertions(+), 43 deletions(-) diff --git a/__tests__/composition.spec.ts b/__tests__/composition.spec.ts index ce4c9c22..0ed23195 100644 --- a/__tests__/composition.spec.ts +++ b/__tests__/composition.spec.ts @@ -1681,7 +1681,11 @@ testImplementations((api) => { `); }); - test("collision with missing default", () => { + test("collision with missing default value on directive argument", () => { + // @composeDirective requires v2.1+ + if (version === "v2.0") { + return; + } const result = composeServices([ { name: "a", @@ -1704,7 +1708,7 @@ testImplementations((api) => { } type Query { - hello: String @access + a: String @access } `), }, @@ -1729,22 +1733,33 @@ testImplementations((api) => { } type Query { - hello: String @access + b: String @access } `), }, ]); assertCompositionFailure(result); + expect(result.errors.length).toEqual(1); + expect(result.errors).toContainEqual( + expect.objectContaining({ + message: expect.stringContaining( + 'Directive "@access" argument "scope" of type "Scope!" is required, but it was not provided.', + ), + }), + ); }); - test("collision with different default", () => { - let result: CompositionResult; - try { - result = composeServices([ - { - name: "a", - typeDefs: parse(/* GraphQL */ ` + test("collision with different default value on directive argument for schema directive", () => { + // @composeDirective requires v2.1+ + if (version === "v2.0") { + return; + } + + const result = composeServices([ + { + name: "a", + typeDefs: parse(/* GraphQL */ ` extend schema @link( url: "https://specs.apollo.dev/federation/${version}" @@ -1757,19 +1772,20 @@ testImplementations((api) => { @composeDirective(name: "@access") directive @access(scope: Scope! = PRIVATE) on FIELD_DEFINITION + enum Scope { PUBLIC PRIVATE } type Query { - hello: String @access + a: String @access } `), - }, - { - name: "b", - typeDefs: parse(/* GraphQL */ ` + }, + { + name: "b", + typeDefs: parse(/* GraphQL */ ` extend schema @link( url: "https://specs.apollo.dev/federation/${version}" @@ -1782,34 +1798,99 @@ testImplementations((api) => { @composeDirective(name: "@access") directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION + enum Scope { PUBLIC PRIVATE } type Query { - hello: String @access + b: String @access } `), - }, - ]); - } catch (e) { - expect(api.library).toBe("apollo"); - result = { - errors: [e as any], - }; + }, + ]); + + // This fails actually. + assertCompositionSuccess(result); + expect(result.supergraphSdl).toContainGraphQL( + "directive @access(scope: Scope! = PUBLIC) on FIELD_DEFINITION", + ); + }); + + test("collision with different default value on directive argument for executable directive", () => { + // @composeDirective requires v2.1+ + if (version === "v2.0") { + return; } - assertCompositionFailure(result); + const result = composeServices([ + { + name: "a", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/${version}" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") + + directive @access(scope: Scope! = PRIVATE) on QUERY + + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + a: String + } + `), + }, + { + name: "b", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/${version}" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") + + directive @access(scope: Scope! = PUBLIC) on QUERY + + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + b: String + } + `), + }, + ]); + + // This fails actually. + assertCompositionSuccess(result); + expect(result.supergraphSdl).toContainGraphQL( + "directive @access(scope: Scope! = PUBLIC) on QUERY", + ); }); - test("collision with different default on argument", () => { - let result: CompositionResult; - try { - result = composeServices([ - { - name: "a", - typeDefs: parse(/* GraphQL */ ` + test("collision with different default value on argument on field arugment", () => { + const result = composeServices([ + { + name: "a", + typeDefs: parse(/* GraphQL */ ` extend schema @link(url: "https://specs.apollo.dev/federation/${version}") @@ -1822,10 +1903,10 @@ testImplementations((api) => { hello(scope: Scope! = PUBLIC): String } `), - }, - { - name: "b", - typeDefs: parse(/* GraphQL */ ` + }, + { + name: "b", + typeDefs: parse(/* GraphQL */ ` extend schema @link(url: "https://specs.apollo.dev/federation/${version}") @@ -1838,16 +1919,25 @@ testImplementations((api) => { hello(scope: Scope! = PRIVATE): String } `), - }, - ]); - } catch (e) { - expect(api.library).toBe("apollo"); - result = { - errors: [e as any], - }; - } + }, + ]); assertCompositionFailure(result); + expect(result.errors.length).toEqual(2); + expect(result.errors).toContainEqual( + expect.objectContaining({ + message: expect.stringContaining( + '"Query.hello(scope:)" has incompatible default values across subgraphs:', + ), + }), + ); + expect(result.errors).toContainEqual( + expect.objectContaining({ + message: expect.stringContaining( + 'Non-shareable field "Query.hello" is resolved from multiple subgraphs', + ), + }), + ); }); }); From a493485fc09ffc9099682e4073ae9ad5abe1e84a Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Wed, 22 Jul 2026 14:53:14 +0200 Subject: [PATCH 6/7] fix typo --- __tests__/composition.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/composition.spec.ts b/__tests__/composition.spec.ts index 0ed23195..02c717db 100644 --- a/__tests__/composition.spec.ts +++ b/__tests__/composition.spec.ts @@ -1886,7 +1886,7 @@ testImplementations((api) => { ); }); - test("collision with different default value on argument on field arugment", () => { + test("collision with different default value on argument on field argument", () => { const result = composeServices([ { name: "a", From 3ee67c77a180e0f4455b005e4f4a55606e24f8cc Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Wed, 22 Jul 2026 15:02:12 +0200 Subject: [PATCH 7/7] add another testcase for an edge case --- __tests__/composition.spec.ts | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/__tests__/composition.spec.ts b/__tests__/composition.spec.ts index 02c717db..b7b80c7c 100644 --- a/__tests__/composition.spec.ts +++ b/__tests__/composition.spec.ts @@ -1886,6 +1886,73 @@ testImplementations((api) => { ); }); + test("collision with missing default value on directive argument for executable directive", () => { + // @composeDirective requires v2.1+ + if (version === "v2.0") { + return; + } + + const result = composeServices([ + { + name: "a", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/${version}" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") + + directive @access(scope: Scope!) on QUERY + + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + a: String + } + `), + }, + { + name: "b", + typeDefs: parse(/* GraphQL */ ` + extend schema + @link( + url: "https://specs.apollo.dev/federation/${version}" + import: ["@key", "@composeDirective"] + ) + @link( + url: "https://myspecs.dev/access/v1.0" + import: ["@access"] + ) + @composeDirective(name: "@access") + + directive @access(scope: Scope! = PUBLIC) on QUERY + + enum Scope { + PUBLIC + PRIVATE + } + + type Query { + b: String + } + `), + }, + ]); + + // This fails actually. + assertCompositionSuccess(result); + expect(result.supergraphSdl).toContainGraphQL( + "directive @access(scope: Scope! = PUBLIC) on QUERY", + ); + }); test("collision with different default value on argument on field argument", () => { const result = composeServices([ {