Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: chore(@override): add e2e testing of progressive overriding #2978

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
176 changes: 176 additions & 0 deletions gateway-js/src/__tests__/gateway/endToEnd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,182 @@ describe('caching', () => {
* have basic end-to-end testing, thus ensuring those feature don't break in places we didn't expect.
*/
describe('end-to-end features', () => {
it('@override without progressive', async () => {
const subgraphA = {
name: 'A',
url: 'https://A',
typeDefs: gql`
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.7"
import: ["@key", "@override"]
)

type Query {
t: T
}

type T @key(fields: "k") {
k: ID
x: Int
}
`,
resolvers: {
Query: {
t: () => ({
k: 42,
x: 1,
}),
},
},
};

const subgraphB = {
name: 'B',
url: 'https://B',
typeDefs: gql`
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.7"
import: ["@key", "@override"]
)

type T @key(fields: "k") {
k: ID
x: Int @override(from: "A")
}
`,
resolvers: {
T: {
__resolveReference: ({ k }: { k: string }) => {
return k === '42' ? { x: 2 } : undefined;
},
},
},
};

services = await startSubgraphsAndGateway([subgraphA, subgraphB]);
const limit = 100;
const query = `
{
t {
k
x
}
}
`;
let hitsA = 0;
let hitsB = 0;
for (let i = 0; i < limit; i++) {
const response = await services.queryGateway(query);
const result = await response.json();
if (result.data.t.x === 1) {
hitsA++;
}
if (result.data.t.x === 2) {
hitsB++;
}
}
expect(hitsB).toEqual(100);
expect(hitsA).toEqual(0);

const supergraphSdl = services.gateway.__testing().supergraphSdl;
expect(supergraphSdl).toBeDefined();
const supergraph = buildSchema(supergraphSdl!);
const typeT = supergraph.type('T') as ObjectType;
expect(
typeT.field('x')?.appliedDirectivesOf('@override').toString(),
).toStrictEqual('');
});

it('@override with progressive override', async () => {
const subgraphA = {
name: 'A',
url: 'https://A',
typeDefs: gql`
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.7"
import: ["@key", "@override"]
)

type Query {
t: T
}

type T @key(fields: "k") {
k: ID
x: Int
}
`,
resolvers: {
Query: {
t: () => ({
k: 42,
x: 1,
}),
},
},
};

const subgraphB = {
name: 'B',
url: 'https://B',
typeDefs: gql`
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.7"
import: ["@key", "@override"]
)

type T @key(fields: "k") {
k: ID
x: Int @override(from: "A", label: "percent(50)")
}
`,
resolvers: {
T: {
__resolveReference: ({ k }: { k: string }) => {
return k === '42' ? { x: 2 } : undefined;
},
},
},
};

services = await startSubgraphsAndGateway([subgraphA, subgraphB]);
const limit = 100;
const query = `
{
t {
k
x
}
}
`;
let hitsA = 0;
let hitsB = 0;
for (let i = 0; i < limit; i++) {
const response = await services.queryGateway(query);
const result = await response.json();
if (result.data.t.x === 1) {
hitsA++;
}
if (result.data.t.x === 2) {
hitsB++;
}
}
expect(hitsB).toBeGreaterThan(0);
expect(hitsA).toBeGreaterThan(0);

const supergraphSdl = services.gateway.__testing().supergraphSdl;
expect(supergraphSdl).toBeDefined();
const supergraph = buildSchema(supergraphSdl!);
const typeT = supergraph.type('T') as ObjectType;
expect(
typeT.field('x')?.appliedDirectivesOf('@override').toString(),
).toStrictEqual('');
});

it('@tag renaming', async () => {
const subgraphA = {
name: 'A',
Expand Down
2 changes: 1 addition & 1 deletion gateway-js/src/__tests__/gateway/queryPlanCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ it('caches the query plan for a request', async () => {
});

expect(unwrapSingleResultKind(secondResult).data).toEqual(result1Data);
expect(buildQueryPlanSpy).toHaveBeenCalledTimes(1);
expect(buildQueryPlanSpy).toHaveBeenCalledTimes(2);
});

it('supports multiple operations and operationName', async () => {
Expand Down
6 changes: 4 additions & 2 deletions gateway-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ export class ApolloGateway implements GatewayInterface {
span.setStatus({ code: SpanStatusCode.ERROR });
return { errors: validationErrors };
}
let queryPlan = await this.queryPlanStore.get(queryPlanStoreKey);
let queryPlan

if (!queryPlan) {
queryPlan = tracer.startActiveSpan(
Expand All @@ -806,7 +806,9 @@ export class ApolloGateway implements GatewayInterface {
{ operationName: request.operationName },
);
// TODO(#631): Can we be sure the query planner has been initialized here?
return this.queryPlanner!.buildQueryPlan(operation);
return this.queryPlanner!.buildQueryPlan(operation, {
overrideConditions: new Map(Object.entries({ "percent(50)": Math.random() * 100 > 50 }))
});
} catch (err) {
recordExceptions(span, [err], this.config.telemetry);
span.setStatus({ code: SpanStatusCode.ERROR });
Expand Down