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

tests: Expand helper for trace tests. #2554

Merged
merged 3 commits into from
Apr 10, 2019
Merged

Conversation

abernix
Copy link
Member

@abernix abernix commented Apr 9, 2019

This one very important 115-line test:

it('validation > engine > extensions > formatError', async () => {
const throwError = jest.fn(() => {
throw new Error('nope');
});
const validationRule = jest.fn(() => {
// formatError should be called after validation
expect(formatError).not.toBeCalled();
// extension should be called after validation
expect(willSendResponseInExtension).not.toBeCalled();
return true;
});
const willSendResponseInExtension = jest.fn();
const formatError = jest.fn(error => {
try {
expect(error).toBeInstanceOf(Error);
// extension should be called before formatError
expect(willSendResponseInExtension).toHaveBeenCalledTimes(1);
// validationRules should be called before formatError
expect(validationRule).toHaveBeenCalledTimes(1);
} finally {
error.message = 'masked';
return error;
}
});
class Extension<TContext = any> extends GraphQLExtension {
willSendResponse(o: {
graphqlResponse: GraphQLResponse;
context: TContext;
}) {
expect(o.graphqlResponse.errors.length).toEqual(1);
// formatError should be called after extensions
expect(formatError).not.toBeCalled();
// validationRules should be called before extensions
expect(validationRule).toHaveBeenCalledTimes(1);
willSendResponseInExtension();
}
}
let engineServerDidStart: Promise<void>;
const didReceiveTrace = new Promise<ITrace>(resolve => {
engineServerDidStart = startEngineServer({
check: (req, res) => {
const report = FullTracesReport.decode(req.body);
const header = report.header;
expect(header.schemaTag).toEqual('');
expect(header.schemaHash).toBeDefined();
const trace = Object.values(report.tracesPerQuery)[0].trace[0];
resolve(trace);
res.end();
},
});
});
await engineServerDidStart;
const { url: uri } = await createApolloServer({
typeDefs: gql`
type Query {
error: String
}
`,
resolvers: {
Query: {
error: () => {
throwError();
},
},
},
validationRules: [validationRule],
extensions: [() => new Extension()],
engine: {
endpointUrl: `http://localhost:${
(engineServer.address() as net.AddressInfo).port
}`,
apiKey: 'service:my-app:secret',
maxUncompressedReportSize: 1,
generateClientInfo: () => ({
clientName: 'testing',
clientReferenceId: '1234',
clientVersion: 'v1.0.1',
}),
},
formatError,
debug: true,
});
const apolloFetch = createApolloFetch({ uri });
const result = await apolloFetch({
query: `{error}`,
});
expect(result.data).toEqual({
error: null,
});
expect(result.errors).toBeDefined();
expect(result.errors[0].message).toEqual('masked');
expect(validationRule).toHaveBeenCalledTimes(1);
expect(throwError).toHaveBeenCalledTimes(1);
expect(formatError).toHaveBeenCalledTimes(1);
expect(willSendResponseInExtension).toHaveBeenCalledTimes(1);
const trace = await didReceiveTrace;
expect(trace.clientReferenceId).toMatch(/1234/);
expect(trace.clientName).toMatch(/testing/);
expect(trace.clientVersion).toEqual('v1.0.1');
expect(trace.root!.child![0].error![0].message).toMatch(/nope/);
expect(trace.root!.child![0].error![0].message).not.toMatch(/masked/);
});

...has grown to a point where it's a bit conflated. Rather than becoming a new test with a separate concern, it's been added to as we've continued to expand observability features.

When looking at #1639, I was tempted to do the same thing, but felt it was worth breaking out the test into various helpers for reusability in new (upcoming!) tests.

There are two main changes here:

  • Rename the error field which is used amongst many of the tests to something more recognizable, particularly since error itself is a protobuf property which these tests exercise:

    My poor brain was confused on more than one occasion by the presence of a error field within an error object, so I hope to spare those brain palpitations in the future.

  • Turn the mock engine reporting server (meant to represent a server on the receiving side of apollo-engine-reporting) into a reusable, strongly-typed class: EngineMockServer.

As I noted in the commit message for 6e218ea, another take on this might have implemented nock, but that was a more substantial re-factor.

abernix added 2 commits April 8, 2019 23:08
…lity.

While in an ideal world tests which test Engine reporting could be done
through a `nock`-like implementation (https://npm.im/nock), this slight
restructuring will make it more simple to introduce a few other Engine tests
which would otherwise conflict with this logic or necessitate duplicating
and tweaking it slightly in a number of places.

This interface should avoid the need to duplicate code with those nuances
and also DRY things up a bit.
This has confused me like 40 times while looking at this file which has
`trace.root.error` properties which are not the field itself, they are an
actual property on the `Trace.INode` which is called `error`.

This just changes the name of the field, which is actually "error", to be
something that's more unique.
@abernix abernix requested a review from martijnwalraven as a code owner April 9, 2019 15:55
@abernix abernix merged commit 5735e79 into master Apr 10, 2019
@abernix abernix deleted the abernix/refactor-engine-tests branch February 25, 2020 21:01
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant