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

chore(gateway): Simplify startup code paths #440

Merged
merged 19 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
152 changes: 152 additions & 0 deletions gateway-js/src/__tests__/integration/configuration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Logger } from 'apollo-server-types';
import { ApolloGateway } from '../..';
import {
mockSDLQuerySuccess,
mockStorageSecretSuccess,
mockCompositionConfigLinkSuccess,
mockCompositionConfigsSuccess,
mockImplementingServicesSuccess,
mockRawPartialSchemaSuccess,
apiKeyHash,
graphId,
} from './nockMocks';
import { getTestingCsdl } from '../execution-utils';
import { MockService } from './networkRequests.test';
import { parse } from 'graphql';

let logger: Logger;

const service: MockService = {
gcsDefinitionPath: 'service-definition.json',
partialSchemaPath: 'accounts-partial-schema.json',
url: 'http://localhost:4001',
sdl: `#graphql
extend type Query {
me: User
everyone: [User]
}

"This is my User"
type User @key(fields: "id") {
id: ID!
name: String
username: String
}
`,
};

beforeEach(() => {
const warn = jest.fn();
const debug = jest.fn();
const error = jest.fn();
const info = jest.fn();

logger = {
warn,
debug,
error,
info,
};
});

describe('gateway configuration warnings', () => {
it('warns when both csdl and studio configuration are provided', async () => {
const gateway = new ApolloGateway({
csdl: getTestingCsdl(),
logger,
});

await gateway.load({
apollo: { keyHash: apiKeyHash, graphId, graphVariant: 'current' },
});

expect(logger.warn).toHaveBeenCalledWith(
'A local gateway configuration is overriding a managed federation configuration.' +
' To use the managed configuration, do not specify a service list or csdl locally.',
);
});

it('conflicting configurations are warned about when present', async () => {
mockSDLQuerySuccess(service);

const gateway = new ApolloGateway({
serviceList: [{ name: 'accounts', url: service.url }],
logger,
});

await gateway.load({
apollo: { keyHash: apiKeyHash, graphId, graphVariant: 'current' },
});

expect(logger.warn).toHaveBeenCalledWith(
expect.stringMatching(
/A local gateway configuration is overriding a managed federation configuration/,
),
);
});

it('conflicting configurations are not warned about when absent', async () => {
mockStorageSecretSuccess();
mockCompositionConfigLinkSuccess();
mockCompositionConfigsSuccess([service]);
mockImplementingServicesSuccess(service);
mockRawPartialSchemaSuccess(service);

const gateway = new ApolloGateway({
logger,
});

await gateway.load({
apollo: { keyHash: apiKeyHash, graphId, graphVariant: 'current' },
});

expect(logger.warn).not.toHaveBeenCalledWith(
expect.stringMatching(
/A local gateway configuration is overriding a managed federation configuration/,
),
);
});

it('throws when no configuration is provided', async () => {
const gateway = new ApolloGateway({
logger,
});

expect(gateway.load()).rejects.toThrowErrorMatchingInlineSnapshot(
`"When a manual configuration is not provided, gateway requires an Apollo configuration. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ for more information. Manual configuration options include: \`serviceList\`, \`csdl\`, and \`experimental_updateServiceDefinitions\`."`,
);
});
});

describe('gateway startup errors', () => {
it("throws when static config can't be composed", async () => {
const uncomposableSdl = parse(`#graphql
type Query {
me: User
everyone: [User]
account(id: String): Account
}

type User @key(fields: "id") {
name: String
username: String
}

type Account @key(fields: "id") {
name: String
username: String
}
`);

const gateway = new ApolloGateway({
localServiceList: [
{ name: 'accounts', url: service.url, typeDefs: uncomposableSdl },
],
logger,
});

expect(gateway.load()).rejects.toThrowError(
"A valid schema couldn't be composed",
);
});
});
104 changes: 2 additions & 102 deletions gateway-js/src/__tests__/integration/networkRequests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ import {
apiKeyHash,
graphId,
} from './nockMocks';
import loadServicesFromStorage = require('../../loadServicesFromStorage');
import { getTestingCsdl } from '../execution-utils';

// This is a nice DX hack for GraphQL code highlighting and formatting within the file.
// Anything wrapped within the gql tag within this file is just a string, not an AST.
const gql = String.raw;

export interface MockService {
gcsDefinitionPath: string;
Expand All @@ -37,7 +31,7 @@ const service: MockService = {
gcsDefinitionPath: 'service-definition.json',
partialSchemaPath: 'accounts-partial-schema.json',
url: 'http://localhost:4001',
sdl: gql`
sdl: `#graphql
extend type Query {
me: User
everyone: [User]
Expand All @@ -56,7 +50,7 @@ const updatedService: MockService = {
gcsDefinitionPath: 'updated-service-definition.json',
partialSchemaPath: 'updated-accounts-partial-schema.json',
url: 'http://localhost:4002',
sdl: gql`
sdl: `#graphql
extend type Query {
me: User
everyone: [User]
Expand Down Expand Up @@ -130,100 +124,6 @@ it('Extracts service definitions from remote storage', async () => {
expect(gateway.schema!.getType('User')!.description).toBe('This is my User');
});

it.each([
['warned', 'present'],
['not warned', 'absent'],
])('conflicting configurations are %s about when %s', async (_word, mode) => {
const isConflict = mode === 'present';
let blockerResolve: () => void;
const blocker = new Promise((resolve) => (blockerResolve = resolve));
const original = loadServicesFromStorage.getServiceDefinitionsFromStorage;
const spyGetServiceDefinitionsFromStorage = jest
.spyOn(loadServicesFromStorage, 'getServiceDefinitionsFromStorage')
.mockImplementationOnce(async (...args) => {
try {
return await original(...args);
} catch (e) {
throw e;
} finally {
setImmediate(blockerResolve);
}
});

mockStorageSecretSuccess();
if (isConflict) {
mockCompositionConfigLinkSuccess();
mockCompositionConfigsSuccess([service]);
mockImplementingServicesSuccess(service);
mockRawPartialSchemaSuccess(service);
} else {
mockCompositionConfigLink().reply(403);
}

mockSDLQuerySuccess(service);

const gateway = new ApolloGateway({
serviceList: [{ name: 'accounts', url: service.url }],
logger,
});

await gateway.load({
apollo: { keyHash: apiKeyHash, graphId, graphVariant: 'current' },
});
await blocker; // Wait for the definitions to be "fetched".

(isConflict
? expect(logger.warn)
: expect(logger.warn).not
).toHaveBeenCalledWith(
expect.stringMatching(
/A local gateway configuration is overriding a managed federation configuration/,
),
);
spyGetServiceDefinitionsFromStorage.mockRestore();
});

it('warns when both csdl and studio configuration are provided', async () => {
mockStorageSecretSuccess();
mockCompositionConfigLinkSuccess();
mockCompositionConfigsSuccess([service]);
mockImplementingServicesSuccess(service);
mockRawPartialSchemaSuccess(service);

let blockerResolve: () => void;
const blocker = new Promise((resolve) => (blockerResolve = resolve));
const original = loadServicesFromStorage.getServiceDefinitionsFromStorage;
const spyGetServiceDefinitionsFromStorage = jest
.spyOn(loadServicesFromStorage, 'getServiceDefinitionsFromStorage')
.mockImplementationOnce(async (...args) => {
try {
return await original(...args);
} catch (e) {
throw e;
} finally {
setImmediate(blockerResolve);
}
});

const gateway = new ApolloGateway({
csdl: getTestingCsdl(),
logger,
});

await gateway.load({
apollo: { keyHash: apiKeyHash, graphId, graphVariant: 'current' },
});

await blocker;

expect(logger.warn).toHaveBeenCalledWith(
'A local gateway configuration is overriding a managed federation configuration.' +
' To use the managed configuration, do not specify a service list or csdl locally.',
);

spyGetServiceDefinitionsFromStorage.mockRestore();
});

// This test has been flaky for a long time, and fails consistently after changes
// introduced by https://github.com/apollographql/apollo-server/pull/4277.
// I've decided to skip this test for now with hopes that we can one day
Expand Down
Loading