Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 119 additions & 53 deletions composition-js/src/__tests__/compose.demandControl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
FieldDefinition,
InputObjectType,
ObjectType,
ScalarType,
ServiceDefinition,
Supergraph
} from '@apollo/federation-internals';
Expand All @@ -27,11 +28,19 @@ const subgraphWithCost = {
somethingWithCost: Int @cost(weight: 20)
}

scalar ExpensiveInt @cost(weight: 30)

type ExpensiveObject @cost(weight: 40) {
id: ID
}

type Query {
fieldWithCost: Int @cost(weight: 5)
argWithCost(arg: Int @cost(weight: 10)): Int
enumWithCost: AorB
inputWithCost(someInput: InputTypeWithCost): Int
scalarWithCost: ExpensiveInt
objectWithCost: ExpensiveObject
}
`),
};
Expand All @@ -41,8 +50,13 @@ const subgraphWithListSize = {
typeDefs: asFed2SubgraphDocument(gql`
extend schema @link(url: "https://specs.apollo.dev/cost/v0.1", import: ["@listSize"])

type HasInts {
ints: [Int!]
}

type Query {
fieldWithListSize: [String!] @listSize(assumedSize: 2000, requireOneSlicingArgument: false)
fieldWithDynamicListSize(first: Int!): HasInts @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)
}
`),
};
Expand All @@ -61,11 +75,19 @@ const subgraphWithRenamedCost = {
somethingWithCost: Int @renamedCost(weight: 20)
}

scalar ExpensiveInt @renamedCost(weight: 30)

type ExpensiveObject @renamedCost(weight: 40) {
id: ID
}

type Query {
fieldWithCost: Int @renamedCost(weight: 5)
argWithCost(arg: Int @renamedCost(weight: 10)): Int
enumWithCost: AorB
inputWithCost(someInput: InputTypeWithCost): Int
scalarWithCost: ExpensiveInt
objectWithCost: ExpensiveObject
}
`),
};
Expand All @@ -75,8 +97,13 @@ const subgraphWithRenamedListSize = {
typeDefs: asFed2SubgraphDocument(gql`
extend schema @link(url: "https://specs.apollo.dev/cost/v0.1", import: [{ name: "@listSize", as: "@renamedListSize" }])

type HasInts {
ints: [Int!] @shareable
}

type Query {
fieldWithListSize: [String!] @renamedListSize(assumedSize: 2000, requireOneSlicingArgument: false)
fieldWithDynamicListSize(first: Int!): HasInts @renamedListSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)
}
`),
};
Expand All @@ -94,11 +121,19 @@ const subgraphWithCostFromFederationSpec = {
somethingWithCost: Int @cost(weight: 20)
}

scalar ExpensiveInt @cost(weight: 30)

type ExpensiveObject @cost(weight: 40) {
id: ID
}

type Query {
fieldWithCost: Int @cost(weight: 5)
argWithCost(arg: Int @cost(weight: 10)): Int
enumWithCost: AorB
inputWithCost(someInput: InputTypeWithCost): Int
scalarWithCost: ExpensiveInt
objectWithCost: ExpensiveObject
}
`,
{ includeAllImports: true },
Expand All @@ -109,8 +144,13 @@ const subgraphWithListSizeFromFederationSpec = {
name: 'subgraphWithListSize',
typeDefs: asFed2SubgraphDocument(
gql`
type HasInts {
ints: [Int!]
}

type Query {
fieldWithListSize: [String!] @listSize(assumedSize: 2000, requireOneSlicingArgument: false)
fieldWithDynamicListSize(first: Int!): HasInts @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)
}
`,
{ includeAllImports: true },
Expand All @@ -132,11 +172,19 @@ const subgraphWithRenamedCostFromFederationSpec = {
somethingWithCost: Int @renamedCost(weight: 20)
}

scalar ExpensiveInt @renamedCost(weight: 30)

type ExpensiveObject @renamedCost(weight: 40) {
id: ID
}

type Query {
fieldWithCost: Int @renamedCost(weight: 5)
argWithCost(arg: Int @renamedCost(weight: 10)): Int
enumWithCost: AorB
inputWithCost(someInput: InputTypeWithCost): Int
scalarWithCost: ExpensiveInt
objectWithCost: ExpensiveObject
}
`,
};
Expand All @@ -147,8 +195,13 @@ const subgraphWithRenamedListSizeFromFederationSpec = {
gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.9", import: [{ name: "@listSize", as: "@renamedListSize" }])

type HasInts {
ints: [Int!]
}

type Query {
fieldWithListSize: [String!] @renamedListSize(assumedSize: 2000, requireOneSlicingArgument: false)
fieldWithDynamicListSize(first: Int!): HasInts @renamedListSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)
}
`,
};
Expand Down Expand Up @@ -193,7 +246,27 @@ function inputWithCost(result: CompositionResult): InputObjectType | undefined {
?.type as InputObjectType;
}

// Used to test @listSize applications on FIELD_DEFINITION
// Used to test @cost applications on SCALAR
function scalarWithCost(result: CompositionResult): ScalarType | undefined {
return result
.schema
?.schemaDefinition
.rootType('query')
?.field('scalarWithCost')
?.type as ScalarType
}

// Used to test @cost applications on OBJECT
function objectWithCost(result: CompositionResult): ObjectType | undefined {
return result
.schema
?.schemaDefinition
.rootType('query')
?.field('objectWithCost')
?.type as ObjectType
}

// Used to test @listSize applications on FIELD_DEFINITION with a statically assumed size
function fieldWithListSize(result: CompositionResult): FieldDefinition<ObjectType> | undefined {
return result
.schema
Expand All @@ -202,6 +275,15 @@ function fieldWithListSize(result: CompositionResult): FieldDefinition<ObjectTyp
?.field('fieldWithListSize');
}

// Used to test @listSize applications on FIELD_DEFINITION with dynamic size arguments
function fieldWithDynamicListSize(result: CompositionResult): FieldDefinition<ObjectType> | undefined {
return result
.schema
?.schemaDefinition
.rootType('query')
?.field('fieldWithDynamicListSize');
}

describe('demand control directive composition', () => {
it.each([
[subgraphWithCost, subgraphWithListSize],
Expand All @@ -223,8 +305,17 @@ describe('demand control directive composition', () => {
const inputCostDirectiveApplications = inputWithCost(result)?.field('somethingWithCost')?.appliedDirectivesOf('cost');
expect(inputCostDirectiveApplications?.toString()).toMatchString(`@cost(weight: 20)`);

const scalarCostDirectiveApplications = scalarWithCost(result)?.appliedDirectivesOf('cost');
expect(scalarCostDirectiveApplications?.toString()).toMatchString(`@cost(weight: 30)`);

const objectCostDirectiveApplications = objectWithCost(result)?.appliedDirectivesOf('cost');
expect(objectCostDirectiveApplications?.toString()).toMatchString(`@cost(weight: 40)`);

const listSizeDirectiveApplications = fieldWithListSize(result)?.appliedDirectivesOf('listSize');
expect(listSizeDirectiveApplications?.toString()).toMatchString(`@listSize(assumedSize: 2000, requireOneSlicingArgument: false)`);

const dynamicListSizeDirectiveApplications = fieldWithDynamicListSize(result)?.appliedDirectivesOf('listSize');
expect(dynamicListSizeDirectiveApplications?.toString()).toMatchString(`@listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)`);
});

describe('when renamed', () => {
Expand Down Expand Up @@ -252,8 +343,20 @@ describe('demand control directive composition', () => {
const enumCostDirectiveApplications = enumWithCost(result)?.appliedDirectivesOf('renamedCost');
expect(enumCostDirectiveApplications?.toString()).toMatchString(`@renamedCost(weight: 15)`);

const inputCostDirectiveApplications = inputWithCost(result)?.field('somethingWithCost')?.appliedDirectivesOf('renamedCost');
expect(inputCostDirectiveApplications?.toString()).toMatchString(`@renamedCost(weight: 20)`);

const scalarCostDirectiveApplications = scalarWithCost(result)?.appliedDirectivesOf('renamedCost');
expect(scalarCostDirectiveApplications?.toString()).toMatchString(`@renamedCost(weight: 30)`);

const objectCostDirectiveApplications = objectWithCost(result)?.appliedDirectivesOf('renamedCost');
expect(objectCostDirectiveApplications?.toString()).toMatchString(`@renamedCost(weight: 40)`);

const listSizeDirectiveApplications = fieldWithListSize(result)?.appliedDirectivesOf('renamedListSize');
expect(listSizeDirectiveApplications?.toString()).toMatchString(`@renamedListSize(assumedSize: 2000, requireOneSlicingArgument: false)`);

const dynamicListSizeDirectiveApplications = fieldWithDynamicListSize(result)?.appliedDirectivesOf('renamedListSize');
expect(dynamicListSizeDirectiveApplications?.toString()).toMatchString(`@renamedListSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)`);
});
});

Expand Down Expand Up @@ -406,6 +509,15 @@ describe('demand control directive extraction', () => {
B
}

scalar ExpensiveInt
@federation__cost(weight: 30)

type ExpensiveObject
@federation__cost(weight: 40)
{
id: ID
}

input InputTypeWithCost {
somethingWithCost: Int @federation__cost(weight: 20)
}
Expand All @@ -415,6 +527,8 @@ describe('demand control directive extraction', () => {
argWithCost(arg: Int @federation__cost(weight: 10)): Int
enumWithCost: AorB
inputWithCost(someInput: InputTypeWithCost): Int
scalarWithCost: ExpensiveInt
objectWithCost: ExpensiveObject
}
`);
});
Expand All @@ -436,63 +550,15 @@ describe('demand control directive extraction', () => {
query: Query
}

type Query {
fieldWithListSize: [String!] @federation__listSize(assumedSize: 2000, requireOneSlicingArgument: false)
}
`);
});

it('extracts @listSize with dynamic cost arguments', () => {
const subgraphA = {
name: 'subgraph-a',
typeDefs: asFed2SubgraphDocument(gql`
extend schema @link(url: "https://specs.apollo.dev/cost/v0.1", import: ["@listSize"])

type Query {
sizedList(first: Int!): HasInts @shareable @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)
}

type HasInts {
ints: [Int!] @shareable
}
`)
};
const subgraphB = {
name: 'subgraph-b',
typeDefs: asFed2SubgraphDocument(gql`
extend schema @link(url: "https://specs.apollo.dev/cost/v0.1", import: ["@listSize"])

type Query {
sizedList(first: Int!): HasInts @shareable @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: false)
}

type HasInts {
ints: [Int!] @shareable
}
`)
};

const result = composeServices([subgraphA, subgraphB]);
assertCompositionSuccess(result);
const supergraph = Supergraph.build(result.supergraphSdl);

const expectedSubgraph = `
schema
${FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS}
{
query: Query
}

type HasInts {
ints: [Int!] @shareable
ints: [Int!]
}

type Query {
sizedList(first: Int!): HasInts @shareable @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: false)
fieldWithListSize: [String!] @federation__listSize(assumedSize: 2000, requireOneSlicingArgument: false)
fieldWithDynamicListSize(first: Int!): HasInts @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)
}
`;
expect(supergraph.subgraphs().get(subgraphA.name)?.toString()).toMatchString(expectedSubgraph);
expect(supergraph.subgraphs().get(subgraphB.name)?.toString()).toMatchString(expectedSubgraph);
`);
});

describe('when used on @shareable fields', () => {
Expand Down
38 changes: 26 additions & 12 deletions internals-js/src/__tests__/schemaUpgrader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,8 @@ test('fully upgrades a schema with no @link directive', () => {

test("don't add @shareable to subscriptions", () => {
const subgraph1 = buildSubgraph(
"subgraph1",
"",
'subgraph1',
'',
`#graphql
type Query {
hello: String
Expand All @@ -376,12 +376,12 @@ test("don't add @shareable to subscriptions", () => {
type Subscription {
update: String!
}
`
`,
);

const subgraph2 = buildSubgraph(
"subgraph2",
"",
'subgraph2',
'',
`#graphql
type Query {
hello: String
Expand All @@ -390,16 +390,30 @@ test("don't add @shareable to subscriptions", () => {
type Subscription {
update: String!
}
`
`,
);
const subgraphs = new Subgraphs();
subgraphs.add(subgraph1);
subgraphs.add(subgraph2);
const result = upgradeSubgraphsIfNecessary(subgraphs);

expect(printSchema(result.subgraphs!.get("subgraph1")!.schema!)).not.toContain('update: String! @shareable');
expect(printSchema(result.subgraphs!.get("subgraph2")!.schema!)).not.toContain('update: String! @shareable');

expect(result.subgraphs!.get("subgraph1")!.schema.type('Subscription')?.appliedDirectivesOf('@shareable').length).toBe(0);
expect(result.subgraphs!.get("subgraph2")!.schema.type('Subscription')?.appliedDirectivesOf('@shareable').length).toBe(0);
expect(
printSchema(result.subgraphs!.get('subgraph1')!.schema!),
).not.toContain('update: String! @shareable');
expect(
printSchema(result.subgraphs!.get('subgraph2')!.schema!),
).not.toContain('update: String! @shareable');

expect(
result
.subgraphs!.get('subgraph1')!
.schema.type('Subscription')
?.appliedDirectivesOf('@shareable').length,
).toBe(0);
expect(
result
.subgraphs!.get('subgraph2')!
.schema.type('Subscription')
?.appliedDirectivesOf('@shareable').length,
).toBe(0);
});
Loading