Skip to content

Commit

Permalink
Unbreak the onSchemaChange contract
Browse files Browse the repository at this point in the history
  • Loading branch information
trevor-scheer committed Apr 2, 2020
1 parent e6edbc7 commit 565d96e
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ afterEach(() => {
expect(nock.isDone()).toBeTruthy();
nock.cleanAll();
nock.restore();
jest.useRealTimers();
});

it('Queries remote endpoints for their SDLs', async () => {
Expand Down Expand Up @@ -399,21 +398,44 @@ describe('Downstream service health checks', () => {
let resolve: () => void;
const schemaChangeBlocker = new Promise(res => (resolve = res));

const onChange = jest
.fn()
.mockImplementationOnce(() => resolve())

const gateway = new ApolloGateway({ serviceHealthCheck: true, logger });
// @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
gateway.experimental_pollInterval = 300;
gateway.experimental_pollInterval = 100;

// load the gateway as usual
await gateway.load({ engine: { apiKeyHash, graphId } });
gateway.onSchemaChange(onChange);
expect(gateway.schema!.getType('User')!.description).toBe('This is my User');

// @ts-ignore for testing purposes, we'll call the original `updateComposition`
// function from our mock
const original = gateway.updateComposition;
const mockUpdateComposition = jest
.fn(original)
.mockImplementationOnce(async opts => {
// mock the first poll and handle the error which would otherwise be caught
// and logged from within the `pollServices` class method
await expect(original.apply(gateway, [opts]))
.rejects
.toThrowErrorMatchingInlineSnapshot(
`"500: Internal Server Error"`,
);
// finally resolve the promise which drives this test
resolve();
});

// @ts-ignore for testing purposes, replace the `updateComposition`
// function on the gateway with our mock
gateway.updateComposition = mockUpdateComposition;

// This kicks off polling within the gateway
gateway.onSchemaChange(() => {});

await schemaChangeBlocker;
expect(onChange.mock.calls.length).toBe(1);

// At this point, the mock update should have been called but the schema
// should not have updated to the new one.
expect(mockUpdateComposition.mock.calls.length).toBe(1);
expect(gateway.schema!.getType('User')!.description).toBe('This is my User');
});
})
});
});
24 changes: 10 additions & 14 deletions packages/apollo-gateway/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,6 @@ export class ApolloGateway implements GraphQLService {
'The gateway did not update its schema due to failed service health checks. ' +
'The gateway will continue to operate with the previous schema and reattempt updates.' + e
);
// TODO: Questionable notify here, but it does enable code-driven testing via schema listener callbacks
this.notifySchemaListeners(this.schema);
throw e;
}
}
Expand All @@ -391,7 +389,15 @@ export class ApolloGateway implements GraphQLService {
if (this.queryPlanStore) this.queryPlanStore.flush();

this.schema = this.createSchema(result.serviceDefinitions);
this.notifySchemaListeners(this.schema);

// Notify the schema listeners of the updated schema
try {
this.onSchemaChangeListeners.forEach(listener => listener(this.schema!));
} catch (e) {
this.logger.error(
"An error was thrown from an 'onSchemaChange' listener. " +
"The schema will still update: " + (e && e.message || e));
}

if (this.experimental_didUpdateComposition) {
this.experimental_didUpdateComposition(
Expand All @@ -414,16 +420,6 @@ export class ApolloGateway implements GraphQLService {
}
}

private notifySchemaListeners(schema?: GraphQLSchema) {
try {
this.onSchemaChangeListeners.forEach(listener => listener(schema!));
} catch (e) {
this.logger.error(
"An error was thrown from an 'onSchemaChange' listener. " +
"The schema will still update: " + (e && e.message || e));
}
}

/**
* This can be used without an argument in order to perform an ad-hoc health check
* of the downstream services like so:
Expand Down Expand Up @@ -780,7 +776,7 @@ export class ApolloGateway implements GraphQLService {

public async stop() {
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
clearTimeout(this.pollingTimer);
this.pollingTimer = undefined;
}
}
Expand Down

0 comments on commit 565d96e

Please sign in to comment.