Skip to content

Commit fd95d42

Browse files
Ugzuzgdyladanvmarchaudrauno56
authored
test(fetch): make prepareData async to enforce fakeTime order (#2969)
* test(fetch): make prepareData async to enforce fakeTime order * test: remove unnecessary intermediate variable * test(fetch): stub endSpan to await its call * Update experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts Co-authored-by: Daniel Dyla <[email protected]> Co-authored-by: Valentin Marchaud <[email protected]> Co-authored-by: Rauno Viskus <[email protected]>
1 parent e44f6d1 commit fd95d42

File tree

1 file changed

+87
-88
lines changed
  • experimental/packages/opentelemetry-instrumentation-fetch/test

1 file changed

+87
-88
lines changed

experimental/packages/opentelemetry-instrumentation-fetch/test/fetch.test.ts

Lines changed: 87 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,7 @@ describe('fetch', () => {
155155
lastResponse = undefined;
156156
};
157157

158-
const prepareData = (
159-
done: any,
158+
const prepareData = async (
160159
fileUrl: string,
161160
config: FetchInstrumentationConfig,
162161
method?: string,
@@ -238,37 +237,42 @@ describe('fetch', () => {
238237
new tracing.SimpleSpanProcessor(dummySpanExporter)
239238
);
240239

240+
// endSpan is called after the whole response body is read
241+
// this process is scheduled at the same time the fetch promise is resolved
242+
// due to this we can't rely on getData resolution to know that the span has ended
243+
let resolveEndSpan: (value: unknown) => void;
244+
const spanEnded = new Promise(r => resolveEndSpan = r);
245+
const readSpy = sinon.spy(window.ReadableStreamDefaultReader.prototype, 'read');
246+
const endSpanStub: sinon.SinonStub<any> = sinon.stub(FetchInstrumentation.prototype, '_endSpan' as any)
247+
.callsFake(async function (this: FetchInstrumentation, ...args: any[]) {
248+
resolveEndSpan({});
249+
return endSpanStub.wrappedMethod.apply(this, args);
250+
});
251+
241252
rootSpan = webTracerWithZone.startSpan('root');
242-
api.context.with(api.trace.setSpan(api.context.active(), rootSpan), () => {
253+
await api.context.with(api.trace.setSpan(api.context.active(), rootSpan), async () => {
243254
fakeNow = 0;
244-
void getData(fileUrl, method)
245-
.then(
246-
response => {
247-
// this is a bit tricky as the only way to get all request headers from
248-
// fetch is to use json()
249-
return response.json().then(
250-
json => {
251-
lastResponse = json;
252-
const headers: { [key: string]: string } = {};
253-
Object.keys(lastResponse.headers).forEach(key => {
254-
headers[key.toLowerCase()] = lastResponse.headers[key];
255-
});
256-
lastResponse.headers = headers;
257-
},
258-
() => {
259-
lastResponse = undefined;
260-
}
261-
);
262-
},
263-
() => {
264-
lastResponse = undefined;
265-
}
266-
)
267-
.then(sinon.clock.runAllAsync)
268-
.then(() => {
269-
done();
255+
try {
256+
const responsePromise = getData(fileUrl, method);
257+
fakeNow = 300;
258+
const response = await responsePromise;
259+
260+
// if the url is not ignored, body.read should be called by now
261+
// awaiting for the span to end
262+
if (readSpy.callCount > 0) await spanEnded;
263+
264+
// this is a bit tricky as the only way to get all request headers from
265+
// fetch is to use json()
266+
lastResponse = await response.json();
267+
const headers: { [key: string]: string } = {};
268+
Object.keys(lastResponse.headers).forEach(key => {
269+
headers[key.toLowerCase()] = lastResponse.headers[key];
270270
});
271-
fakeNow = 300;
271+
lastResponse.headers = headers;
272+
} catch (e) {
273+
lastResponse = undefined;
274+
}
275+
await sinon.clock.runAllAsync();
272276
});
273277
};
274278

@@ -290,9 +294,9 @@ describe('fetch', () => {
290294
});
291295

292296
describe('when request is successful', () => {
293-
beforeEach(done => {
297+
beforeEach(async () => {
294298
const propagateTraceHeaderCorsUrls = [url];
295-
prepareData(done, url, { propagateTraceHeaderCorsUrls });
299+
await prepareData(url, { propagateTraceHeaderCorsUrls });
296300
});
297301

298302
afterEach(() => {
@@ -580,13 +584,13 @@ describe('fetch', () => {
580584

581585
describe('when propagateTraceHeaderCorsUrls does NOT MATCH', () => {
582586
let spyDebug: sinon.SinonSpy;
583-
beforeEach(done => {
587+
beforeEach(async () => {
584588
const diagLogger = new api.DiagConsoleLogger();
585589
spyDebug = sinon.spy();
586590
diagLogger.debug = spyDebug;
587591
api.diag.setLogger(diagLogger, api.DiagLogLevel.ALL);
588592
clearData();
589-
prepareData(done, url, {});
593+
await prepareData(url, {});
590594
});
591595
afterEach(() => {
592596
sinon.restore();
@@ -619,15 +623,13 @@ describe('fetch', () => {
619623
});
620624

621625
describe('applyCustomAttributesOnSpan option', () => {
622-
const noop = () => {};
623-
const prepare = (
626+
const prepare = async (
624627
url: string,
625628
applyCustomAttributesOnSpan: FetchCustomAttributeFunction,
626-
cb: VoidFunction = noop
627629
) => {
628630
const propagateTraceHeaderCorsUrls = [url];
629631

630-
prepareData(cb, url, {
632+
await prepareData(url, {
631633
propagateTraceHeaderCorsUrls,
632634
applyCustomAttributesOnSpan,
633635
});
@@ -637,74 +639,71 @@ describe('fetch', () => {
637639
clearData();
638640
});
639641

640-
it('applies attributes when the request is succesful', done => {
641-
prepare(
642+
it('applies attributes when the request is succesful', async () => {
643+
await prepare(
642644
url,
643645
span => {
644646
span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value');
645647
},
646-
() => {
647-
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
648-
const attributes = span.attributes;
649-
650-
assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
651-
done();
652-
}
653648
);
649+
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
650+
const attributes = span.attributes;
651+
652+
assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
654653
});
655654

656-
it('applies custom attributes when the request fails', done => {
657-
prepare(
655+
it('applies custom attributes when the request fails', async () => {
656+
await prepare(
658657
badUrl,
659658
span => {
660659
span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value');
661660
},
662-
() => {
663-
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
664-
const attributes = span.attributes;
665-
666-
assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
667-
done();
668-
}
669661
);
662+
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
663+
const attributes = span.attributes;
664+
665+
assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
670666
});
671667

672-
it('has request and response objects in callback arguments', done => {
668+
it('has request and response objects in callback arguments', async () => {
669+
let request: any;
670+
let response: any;
673671
const applyCustomAttributes: FetchCustomAttributeFunction = (
674672
span,
675-
request,
676-
response
673+
req,
674+
res
677675
) => {
678-
assert.ok(request.method === 'GET');
679-
assert.ok(response.status === 200);
680-
681-
done();
676+
request = req;
677+
response = res;
682678
};
683679

684-
prepare(url, applyCustomAttributes);
680+
await prepare(url, applyCustomAttributes);
681+
assert.ok(request.method === 'GET');
682+
assert.ok(response.status === 200);
685683
});
686684

687-
it('get response body from callback arguments response', done => {
685+
it('get response body from callback arguments response', async () => {
686+
let response: any;
688687
const applyCustomAttributes: FetchCustomAttributeFunction = async (
689688
span,
690-
request,
691-
response
689+
req,
690+
res
692691
) => {
693-
if(response instanceof Response ){
694-
const rsp = await response.json();
695-
assert.deepStrictEqual(rsp.args, {});
696-
done();
692+
if (res instanceof Response) {
693+
response = res;
697694
}
698695
};
699696

700-
prepare(url, applyCustomAttributes);
697+
await prepare(url, applyCustomAttributes);
698+
const rsp = await response.json();
699+
assert.deepStrictEqual(rsp.args, {});
701700
});
702701
});
703702

704703
describe('when url is ignored', () => {
705-
beforeEach(done => {
704+
beforeEach(async () => {
706705
const propagateTraceHeaderCorsUrls = url;
707-
prepareData(done, url, {
706+
await prepareData(url, {
708707
propagateTraceHeaderCorsUrls,
709708
ignoreUrls: [propagateTraceHeaderCorsUrls],
710709
});
@@ -726,9 +725,9 @@ describe('fetch', () => {
726725
});
727726

728727
describe('when clearTimingResources is TRUE', () => {
729-
beforeEach(done => {
728+
beforeEach(async () => {
730729
const propagateTraceHeaderCorsUrls = url;
731-
prepareData(done, url, {
730+
await prepareData(url, {
732731
propagateTraceHeaderCorsUrls,
733732
clearTimingResources: true,
734733
});
@@ -746,9 +745,9 @@ describe('fetch', () => {
746745
});
747746

748747
describe('when request is NOT successful (wrong url)', () => {
749-
beforeEach(done => {
748+
beforeEach(async () => {
750749
const propagateTraceHeaderCorsUrls = badUrl;
751-
prepareData(done, badUrl, { propagateTraceHeaderCorsUrls });
750+
await prepareData(badUrl, { propagateTraceHeaderCorsUrls });
752751
});
753752
afterEach(() => {
754753
clearData();
@@ -764,9 +763,9 @@ describe('fetch', () => {
764763
});
765764

766765
describe('when request is NOT successful (405)', () => {
767-
beforeEach(done => {
766+
beforeEach(async () => {
768767
const propagateTraceHeaderCorsUrls = url;
769-
prepareData(done, url, { propagateTraceHeaderCorsUrls }, 'DELETE');
768+
await prepareData(url, { propagateTraceHeaderCorsUrls }, 'DELETE');
770769
});
771770
afterEach(() => {
772771
clearData();
@@ -783,11 +782,11 @@ describe('fetch', () => {
783782
});
784783

785784
describe('when PerformanceObserver is used by default', () => {
786-
beforeEach(done => {
785+
beforeEach(async () => {
787786
// All above tests test it already but just in case
788787
// lets explicitly turn getEntriesByType off so we can be sure
789788
// that the perf entries come from the observer.
790-
prepareData(done, url, {}, undefined, false, true);
789+
await prepareData(url, {}, undefined, false, true);
791790
});
792791
afterEach(() => {
793792
clearData();
@@ -813,8 +812,8 @@ describe('fetch', () => {
813812
});
814813

815814
describe('when fetching with relative url', () => {
816-
beforeEach(done => {
817-
prepareData(done, '/get', {}, undefined, false, true);
815+
beforeEach(async () => {
816+
await prepareData('/get', {}, undefined, false, true);
818817
});
819818
afterEach(() => {
820819
clearData();
@@ -841,8 +840,8 @@ describe('fetch', () => {
841840
});
842841

843842
describe('when PerformanceObserver is undefined', () => {
844-
beforeEach(done => {
845-
prepareData(done, url, {}, undefined, true, false);
843+
beforeEach(async () => {
844+
await prepareData(url, {}, undefined, true, false);
846845
});
847846

848847
afterEach(() => {
@@ -868,8 +867,8 @@ describe('fetch', () => {
868867
});
869868

870869
describe('when PerformanceObserver and performance.getEntriesByType are undefined', () => {
871-
beforeEach(done => {
872-
prepareData(done, url, {}, undefined, true, true);
870+
beforeEach(async () => {
871+
await prepareData(url, {}, undefined, true, true);
873872
});
874873
afterEach(() => {
875874
clearData();

0 commit comments

Comments
 (0)