Skip to content

Commit f3f94df

Browse files
committed
write tests
1 parent 0b2307e commit f3f94df

File tree

1 file changed

+341
-0
lines changed

1 file changed

+341
-0
lines changed
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
import { useApolloInlineTrace } from '../src';
2+
import { makeExecutableSchema } from '@graphql-tools/schema';
3+
import { createTestkit } from '@envelop/testing';
4+
import { Trace } from 'apollo-reporting-protobuf';
5+
import { ExecutionResult, GraphQLError } from 'graphql';
6+
import { envelop, isAsyncIterable, Plugin, useSchema } from '@envelop/core';
7+
8+
const schema = makeExecutableSchema({
9+
typeDefs: /* GraphQL */ `
10+
type Query {
11+
hello: String!
12+
boom: String!
13+
person: Person!
14+
people: [Person!]!
15+
}
16+
type Subscription {
17+
hello: String!
18+
}
19+
type Person {
20+
name: String!
21+
}
22+
`,
23+
resolvers: {
24+
Query: {
25+
hello() {
26+
return 'world';
27+
},
28+
boom() {
29+
throw new Error('bam');
30+
},
31+
person() {
32+
return { name: 'John' };
33+
},
34+
people() {
35+
return [{ name: 'John' }, { name: 'Jane' }];
36+
},
37+
},
38+
Subscription: {
39+
hello: {
40+
async *subscribe() {
41+
yield { hello: 'world' };
42+
},
43+
},
44+
},
45+
},
46+
});
47+
48+
it('should add ftv1 tracing to result extensions', async () => {
49+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true })], schema);
50+
51+
const result = await testKit.execute('{ hello }');
52+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
53+
54+
expect(result.errors).toBeUndefined();
55+
expect(typeof result.extensions?.ftv1).toBe('string');
56+
});
57+
58+
function expectTrace(trace: Trace) {
59+
expect(trace.startTime).toBeDefined();
60+
expect(typeof trace.startTime?.seconds).toBe('number');
61+
expect(typeof trace.startTime?.nanos).toBe('number');
62+
63+
expect(typeof trace.durationNs).toBe('number');
64+
65+
expect(trace.endTime).toBeDefined();
66+
expect(typeof trace.endTime?.seconds).toBe('number');
67+
expect(typeof trace.endTime?.nanos).toBe('number');
68+
69+
expect(addSecondsAndNanos(trace.startTime!.seconds!, trace.startTime!.nanos!)).toBeLessThan(
70+
addSecondsAndNanos(trace.endTime!.seconds!, trace.endTime!.nanos!)
71+
);
72+
73+
expect(typeof trace.fieldExecutionWeight).toBe('number');
74+
75+
expect(trace.root).toBeDefined();
76+
expect(trace.root?.child).toBeInstanceOf(Array);
77+
}
78+
79+
function expectTraceNode(node: Trace.INode | null | undefined, field: string, type: string, parent: string) {
80+
expect(node).toBeDefined();
81+
82+
expect(node!.responseName).toBe(field);
83+
expect(node!.type).toBe(type);
84+
expect(node!.parentType).toBe(parent);
85+
86+
expect(typeof node!.startTime).toBe('number');
87+
expect(typeof node!.endTime).toBe('number');
88+
89+
expect(node!.startTime!).toBeLessThan(node!.endTime!);
90+
}
91+
92+
it('should have proto tracing on flat query', async () => {
93+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true })], schema);
94+
95+
const result = await testKit.execute('{ hello }');
96+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
97+
98+
//
99+
100+
const ftv1 = result.extensions?.ftv1 as string as string;
101+
expect(typeof ftv1).toBe('string');
102+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
103+
104+
expectTrace(trace);
105+
expect(trace.root?.error?.length).toBe(0);
106+
107+
const hello = trace.root?.child?.[0];
108+
expect(hello?.error?.length).toBe(0);
109+
expectTraceNode(hello, 'hello', 'String!', 'Query');
110+
});
111+
112+
it('should have proto tracing on aliased flat query', async () => {
113+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true })], schema);
114+
115+
const result = await testKit.execute('{ hi: hello }');
116+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
117+
118+
expect(result.errors).toBeUndefined();
119+
120+
//
121+
122+
const ftv1 = result.extensions?.ftv1 as string;
123+
expect(typeof ftv1).toBe('string');
124+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
125+
126+
expectTrace(trace);
127+
expect(trace.root?.error?.length).toBe(0);
128+
129+
const hi = trace.root?.child?.[0];
130+
expect(hi?.error?.length).toBe(0);
131+
expectTraceNode(hi, 'hi', 'String!', 'Query');
132+
expect(hi?.originalFieldName).toBe('hello');
133+
});
134+
135+
it('should have proto tracing on nested query', async () => {
136+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true })], schema);
137+
138+
const result = await testKit.execute('{ person { name } }');
139+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
140+
141+
expect(result.errors).toBeUndefined();
142+
143+
//
144+
145+
const ftv1 = result.extensions?.ftv1 as string;
146+
expect(typeof ftv1).toBe('string');
147+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
148+
149+
expectTrace(trace);
150+
expect(trace.root?.error?.length).toBe(0);
151+
152+
const person = trace.root?.child?.[0];
153+
expect(person?.error?.length).toBe(0);
154+
expectTraceNode(person, 'person', 'Person!', 'Query');
155+
156+
const personName = person?.child?.[0];
157+
expect(personName?.error?.length).toBe(0);
158+
expectTraceNode(personName, 'name', 'String!', 'Person');
159+
});
160+
161+
it('should have proto tracing on flat query with array field', async () => {
162+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true })], schema);
163+
164+
const result = await testKit.execute('{ people { name } }');
165+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
166+
167+
expect(result.errors).toBeUndefined();
168+
169+
//
170+
171+
const ftv1 = result.extensions?.ftv1 as string;
172+
expect(typeof ftv1).toBe('string');
173+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
174+
175+
expectTrace(trace);
176+
expect(trace.root?.error?.length).toBe(0);
177+
178+
const people = trace.root?.child?.[0];
179+
expect(people?.error?.length).toBe(0);
180+
expectTraceNode(people, 'people', '[Person!]!', 'Query');
181+
182+
const arr = people!.child!;
183+
for (let i = 0; i < arr.length; i++) {
184+
const person = arr[i];
185+
expect(person?.error?.length).toBe(0);
186+
expect(person.index).toBe(i);
187+
expectTraceNode(person.child?.[0], 'name', 'String!', 'Person');
188+
}
189+
});
190+
191+
function expectTraceNodeError(node: Trace.INode | null | undefined) {
192+
expect(node).toBeDefined();
193+
expect(node!.error).toBeDefined();
194+
const error = node!.error!;
195+
expect(error).toBeInstanceOf(Array);
196+
expect(error.length).toBeGreaterThan(0);
197+
expect(typeof error[0].message).toBe('string');
198+
expect(typeof error[0].location).toBeDefined();
199+
expect(typeof error[0].location?.[0].line).toBe('number');
200+
expect(typeof error[0].location?.[0].column).toBe('number');
201+
expect(() => {
202+
JSON.parse(error[0].json!);
203+
}).not.toThrow();
204+
}
205+
206+
it('should have proto tracing on parse fail', async () => {
207+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true })], schema);
208+
209+
const result = await testKit.execute('{ he');
210+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
211+
212+
expect(result.errors).toBeDefined();
213+
214+
//
215+
216+
const ftv1 = result.extensions?.ftv1 as string;
217+
expect(typeof ftv1).toBe('string');
218+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
219+
220+
expectTrace(trace);
221+
expectTraceNodeError(trace.root);
222+
});
223+
224+
it('should have proto tracing on validation fail', async () => {
225+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true })], schema);
226+
227+
const result = await testKit.execute('{ henlo }');
228+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
229+
230+
expect(result.errors).toBeDefined();
231+
232+
//
233+
234+
const ftv1 = result.extensions?.ftv1 as string;
235+
expect(typeof ftv1).toBe('string');
236+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
237+
238+
expectTrace(trace);
239+
expectTraceNodeError(trace.root);
240+
});
241+
242+
it('should have proto tracing on execution fail', async () => {
243+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true })], schema);
244+
245+
const result = await testKit.execute('{ boom }');
246+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
247+
248+
expect(result.errors).toBeDefined();
249+
250+
//
251+
252+
const ftv1 = result.extensions?.ftv1 as string;
253+
expect(typeof ftv1).toBe('string');
254+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
255+
256+
expectTrace(trace);
257+
expect(trace.root?.error?.length).toBe(0);
258+
259+
const boom = trace.root?.child?.[0];
260+
expectTraceNode(boom, 'boom', 'String!', 'Query');
261+
expectTraceNodeError(boom);
262+
});
263+
264+
it('should skip tracing errors through rewriteError', async () => {
265+
const testKit = createTestkit([useApolloInlineTrace({ shouldTrace: () => true, rewriteError: () => null })], schema);
266+
267+
const result = await testKit.execute('{ boom }');
268+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
269+
270+
expect(result.errors).toBeDefined();
271+
272+
//
273+
274+
const ftv1 = result.extensions?.ftv1 as string;
275+
expect(typeof ftv1).toBe('string');
276+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
277+
278+
expectTrace(trace);
279+
expect(trace.root?.error?.length).toBe(0);
280+
});
281+
282+
it('should rewrite only error messages and extensions through rewriteError', async () => {
283+
const testKit = createTestkit(
284+
[
285+
useApolloInlineTrace({
286+
shouldTrace: () => true,
287+
rewriteError: () => new GraphQLError('bim', { extensions: { str: 'ing' } }),
288+
}),
289+
],
290+
schema
291+
);
292+
293+
const result = await testKit.execute('{ boom }');
294+
if (isAsyncIterable(result)) throw new Error('Unexpected iterable');
295+
296+
expect(result.errors).toBeDefined();
297+
298+
//
299+
300+
const ftv1 = result.extensions?.ftv1 as string;
301+
expect(typeof ftv1).toBe('string');
302+
const trace = Trace.decode(Buffer.from(ftv1, 'base64'));
303+
304+
expectTrace(trace);
305+
expect(trace.root?.error?.length).toBe(0);
306+
307+
const boom = trace.root?.child?.[0];
308+
expectTraceNode(boom, 'boom', 'String!', 'Query');
309+
expectTraceNodeError(boom); // will check for location
310+
311+
const error = boom!.error!;
312+
expect(error[0].message).toBe('bim'); // not 'bam'
313+
314+
const errObj = JSON.parse(error[0].json!);
315+
expect(errObj.extensions).toEqual({ str: 'ing' });
316+
});
317+
318+
it('should not trace subscriptions', async () => {
319+
const getEnveloped = envelop({
320+
plugins: [
321+
useSchema(schema),
322+
useApolloInlineTrace({
323+
shouldTrace: () => true,
324+
}),
325+
],
326+
});
327+
328+
const { parse, subscribe } = getEnveloped();
329+
const result = await subscribe({ schema, document: parse('subscription { hello }') });
330+
if (!isAsyncIterable(result)) throw new Error('Unexpected result');
331+
332+
for await (const part of result) {
333+
expect(part.data).toEqual({ hello: 'world' });
334+
expect(part.errors).toBeUndefined();
335+
expect(part.extensions).toBeUndefined();
336+
}
337+
});
338+
339+
function addSecondsAndNanos(seconds: number, nanos: number): number {
340+
return seconds + nanos / 1e9;
341+
}

0 commit comments

Comments
 (0)