Skip to content

Commit

Permalink
test(fetch): make prepareData async to enforce fakeTime order (#2969)
Browse files Browse the repository at this point in the history
* 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 <dyladan@users.noreply.github.com>
Co-authored-by: Valentin Marchaud <contact@vmarchaud.fr>
Co-authored-by: Rauno Viskus <Rauno56@users.noreply.github.com>
  • Loading branch information
4 people authored May 30, 2022
1 parent e44f6d1 commit fd95d42
Showing 1 changed file with 87 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,7 @@ describe('fetch', () => {
lastResponse = undefined;
};

const prepareData = (
done: any,
const prepareData = async (
fileUrl: string,
config: FetchInstrumentationConfig,
method?: string,
Expand Down Expand Up @@ -238,37 +237,42 @@ describe('fetch', () => {
new tracing.SimpleSpanProcessor(dummySpanExporter)
);

// endSpan is called after the whole response body is read
// this process is scheduled at the same time the fetch promise is resolved
// due to this we can't rely on getData resolution to know that the span has ended
let resolveEndSpan: (value: unknown) => void;
const spanEnded = new Promise(r => resolveEndSpan = r);
const readSpy = sinon.spy(window.ReadableStreamDefaultReader.prototype, 'read');
const endSpanStub: sinon.SinonStub<any> = sinon.stub(FetchInstrumentation.prototype, '_endSpan' as any)
.callsFake(async function (this: FetchInstrumentation, ...args: any[]) {
resolveEndSpan({});
return endSpanStub.wrappedMethod.apply(this, args);
});

rootSpan = webTracerWithZone.startSpan('root');
api.context.with(api.trace.setSpan(api.context.active(), rootSpan), () => {
await api.context.with(api.trace.setSpan(api.context.active(), rootSpan), async () => {
fakeNow = 0;
void getData(fileUrl, method)
.then(
response => {
// this is a bit tricky as the only way to get all request headers from
// fetch is to use json()
return response.json().then(
json => {
lastResponse = json;
const headers: { [key: string]: string } = {};
Object.keys(lastResponse.headers).forEach(key => {
headers[key.toLowerCase()] = lastResponse.headers[key];
});
lastResponse.headers = headers;
},
() => {
lastResponse = undefined;
}
);
},
() => {
lastResponse = undefined;
}
)
.then(sinon.clock.runAllAsync)
.then(() => {
done();
try {
const responsePromise = getData(fileUrl, method);
fakeNow = 300;
const response = await responsePromise;

// if the url is not ignored, body.read should be called by now
// awaiting for the span to end
if (readSpy.callCount > 0) await spanEnded;

// this is a bit tricky as the only way to get all request headers from
// fetch is to use json()
lastResponse = await response.json();
const headers: { [key: string]: string } = {};
Object.keys(lastResponse.headers).forEach(key => {
headers[key.toLowerCase()] = lastResponse.headers[key];
});
fakeNow = 300;
lastResponse.headers = headers;
} catch (e) {
lastResponse = undefined;
}
await sinon.clock.runAllAsync();
});
};

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

describe('when request is successful', () => {
beforeEach(done => {
beforeEach(async () => {
const propagateTraceHeaderCorsUrls = [url];
prepareData(done, url, { propagateTraceHeaderCorsUrls });
await prepareData(url, { propagateTraceHeaderCorsUrls });
});

afterEach(() => {
Expand Down Expand Up @@ -580,13 +584,13 @@ describe('fetch', () => {

describe('when propagateTraceHeaderCorsUrls does NOT MATCH', () => {
let spyDebug: sinon.SinonSpy;
beforeEach(done => {
beforeEach(async () => {
const diagLogger = new api.DiagConsoleLogger();
spyDebug = sinon.spy();
diagLogger.debug = spyDebug;
api.diag.setLogger(diagLogger, api.DiagLogLevel.ALL);
clearData();
prepareData(done, url, {});
await prepareData(url, {});
});
afterEach(() => {
sinon.restore();
Expand Down Expand Up @@ -619,15 +623,13 @@ describe('fetch', () => {
});

describe('applyCustomAttributesOnSpan option', () => {
const noop = () => {};
const prepare = (
const prepare = async (
url: string,
applyCustomAttributesOnSpan: FetchCustomAttributeFunction,
cb: VoidFunction = noop
) => {
const propagateTraceHeaderCorsUrls = [url];

prepareData(cb, url, {
await prepareData(url, {
propagateTraceHeaderCorsUrls,
applyCustomAttributesOnSpan,
});
Expand All @@ -637,74 +639,71 @@ describe('fetch', () => {
clearData();
});

it('applies attributes when the request is succesful', done => {
prepare(
it('applies attributes when the request is succesful', async () => {
await prepare(
url,
span => {
span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value');
},
() => {
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
const attributes = span.attributes;

assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
done();
}
);
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
const attributes = span.attributes;

assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
});

it('applies custom attributes when the request fails', done => {
prepare(
it('applies custom attributes when the request fails', async () => {
await prepare(
badUrl,
span => {
span.setAttribute(CUSTOM_ATTRIBUTE_KEY, 'custom value');
},
() => {
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
const attributes = span.attributes;

assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
done();
}
);
const span: tracing.ReadableSpan = exportSpy.args[1][0][0];
const attributes = span.attributes;

assert.ok(attributes[CUSTOM_ATTRIBUTE_KEY] === 'custom value');
});

it('has request and response objects in callback arguments', done => {
it('has request and response objects in callback arguments', async () => {
let request: any;
let response: any;
const applyCustomAttributes: FetchCustomAttributeFunction = (
span,
request,
response
req,
res
) => {
assert.ok(request.method === 'GET');
assert.ok(response.status === 200);

done();
request = req;
response = res;
};

prepare(url, applyCustomAttributes);
await prepare(url, applyCustomAttributes);
assert.ok(request.method === 'GET');
assert.ok(response.status === 200);
});

it('get response body from callback arguments response', done => {
it('get response body from callback arguments response', async () => {
let response: any;
const applyCustomAttributes: FetchCustomAttributeFunction = async (
span,
request,
response
req,
res
) => {
if(response instanceof Response ){
const rsp = await response.json();
assert.deepStrictEqual(rsp.args, {});
done();
if (res instanceof Response) {
response = res;
}
};

prepare(url, applyCustomAttributes);
await prepare(url, applyCustomAttributes);
const rsp = await response.json();
assert.deepStrictEqual(rsp.args, {});
});
});

describe('when url is ignored', () => {
beforeEach(done => {
beforeEach(async () => {
const propagateTraceHeaderCorsUrls = url;
prepareData(done, url, {
await prepareData(url, {
propagateTraceHeaderCorsUrls,
ignoreUrls: [propagateTraceHeaderCorsUrls],
});
Expand All @@ -726,9 +725,9 @@ describe('fetch', () => {
});

describe('when clearTimingResources is TRUE', () => {
beforeEach(done => {
beforeEach(async () => {
const propagateTraceHeaderCorsUrls = url;
prepareData(done, url, {
await prepareData(url, {
propagateTraceHeaderCorsUrls,
clearTimingResources: true,
});
Expand All @@ -746,9 +745,9 @@ describe('fetch', () => {
});

describe('when request is NOT successful (wrong url)', () => {
beforeEach(done => {
beforeEach(async () => {
const propagateTraceHeaderCorsUrls = badUrl;
prepareData(done, badUrl, { propagateTraceHeaderCorsUrls });
await prepareData(badUrl, { propagateTraceHeaderCorsUrls });
});
afterEach(() => {
clearData();
Expand All @@ -764,9 +763,9 @@ describe('fetch', () => {
});

describe('when request is NOT successful (405)', () => {
beforeEach(done => {
beforeEach(async () => {
const propagateTraceHeaderCorsUrls = url;
prepareData(done, url, { propagateTraceHeaderCorsUrls }, 'DELETE');
await prepareData(url, { propagateTraceHeaderCorsUrls }, 'DELETE');
});
afterEach(() => {
clearData();
Expand All @@ -783,11 +782,11 @@ describe('fetch', () => {
});

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

describe('when fetching with relative url', () => {
beforeEach(done => {
prepareData(done, '/get', {}, undefined, false, true);
beforeEach(async () => {
await prepareData('/get', {}, undefined, false, true);
});
afterEach(() => {
clearData();
Expand All @@ -841,8 +840,8 @@ describe('fetch', () => {
});

describe('when PerformanceObserver is undefined', () => {
beforeEach(done => {
prepareData(done, url, {}, undefined, true, false);
beforeEach(async () => {
await prepareData(url, {}, undefined, true, false);
});

afterEach(() => {
Expand All @@ -868,8 +867,8 @@ describe('fetch', () => {
});

describe('when PerformanceObserver and performance.getEntriesByType are undefined', () => {
beforeEach(done => {
prepareData(done, url, {}, undefined, true, true);
beforeEach(async () => {
await prepareData(url, {}, undefined, true, true);
});
afterEach(() => {
clearData();
Expand Down

0 comments on commit fd95d42

Please sign in to comment.