Skip to content

Commit

Permalink
test: add tests for new transports
Browse files Browse the repository at this point in the history
  • Loading branch information
pichlermarc committed Aug 2, 2024
1 parent a2ecf3b commit 0e75af3
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,15 @@ export abstract class OTLPExporterBrowserBase<
if (useXhr) {
this._transport = createRetryingTransport({
transport: createXhrTransport({
blobType: contentType,
headers: Object.assign(
{},
parseHeaders(config.headers),
baggageUtils.parseKeyPairsIntoRecord(
getEnv().OTEL_EXPORTER_OTLP_HEADERS
)
),
{ 'Content-Type': contentType }
),
url: this.url,
timeoutMillis: this.timeoutMillis,
}),
});
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { IExporterTransport } from '../../exporter-transport';
import { ExportResponse } from '../../export-response';
import { diag } from '@opentelemetry/api';

export interface SendBeaconParams {
export interface SendBeaconParameters {
url: string;
/**
* for instance 'application/x-protobuf'
Expand All @@ -27,7 +27,7 @@ export interface SendBeaconParams {
}

class SendBeaconTransport implements IExporterTransport {
constructor(private _params: SendBeaconParams) {}
constructor(private _params: SendBeaconParameters) {}
send(data: Uint8Array): Promise<ExportResponse> {
return new Promise<ExportResponse>(resolve => {
if (
Expand Down Expand Up @@ -56,7 +56,7 @@ class SendBeaconTransport implements IExporterTransport {
}

export function createSendBeaconTransport(
parameters: SendBeaconParams
parameters: SendBeaconParameters
): IExporterTransport {
return new SendBeaconTransport(parameters);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,17 @@ import { isExportRetryable, parseRetryAfterToMills } from '../../util';
import { diag } from '@opentelemetry/api';

export interface XhrRequestParameters {
timeoutMillis: number;
url: string;
headers: Record<string, string>;
/**
* for instance 'application/x-protobuf'
*/
blobType: string;
}

class XhrTransport implements IExporterTransport {
constructor(private _parameters: XhrRequestParameters) {}

send(data: Uint8Array): Promise<ExportResponse> {
send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
return new Promise<ExportResponse>(resolve => {
const xhr = new XMLHttpRequest();
xhr.timeout = this._parameters.timeoutMillis;
xhr.timeout = timeoutMillis;
xhr.open('POST', this._parameters.url);
Object.entries(this._parameters.headers).forEach(([k, v]) => {
xhr.setRequestHeader(k, v);
Expand Down Expand Up @@ -82,7 +77,9 @@ class XhrTransport implements IExporterTransport {
});
};

xhr.send(new Blob([data], { type: this._parameters.blobType }));
xhr.send(
new Blob([data], { type: this._parameters.headers['Content-Type'] })
);
});
}
shutdown() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as sinon from 'sinon';
import { createSendBeaconTransport } from '../../src/platform/browser/send-beacon-transport';
import * as assert from 'assert';

describe('SendBeaconTransport', function () {
afterEach(function () {
sinon.restore();
});

describe('send', function () {
it('returns failure when sendBeacon fails', async function () {
// arrange
const sendStub = sinon.stub(navigator, 'sendBeacon').returns(false);
const transport = createSendBeaconTransport({
url: 'http://example.test',
blobType: 'application/json',
});

// act
const result = await transport.send(Uint8Array.from([1, 2, 3]), 1000);

// assert
sinon.assert.calledOnceWithMatch(
sendStub,
'http://example.test',
sinon.match
.instanceOf(Blob)
.and(
sinon.match(
actual => actual.type === 'application/json',
'Expected Blob type to match.'
)
)
);
assert.strictEqual(result.status, 'failure');
});

it('returns success when sendBeacon succeeds', async function () {
// arrange
const sendStub = sinon.stub(navigator, 'sendBeacon').returns(true);
const transport = createSendBeaconTransport({
url: 'http://example.test',
blobType: 'application/json',
});

// act
const result = await transport.send(Uint8Array.from([1, 2, 3]), 1000);

// assert
sinon.assert.calledOnceWithMatch(
sendStub,
'http://example.test',
sinon.match
.instanceOf(Blob)
.and(
sinon.match(
actual => actual.type === 'application/json',
'Expected Blob type to match.'
)
)
);
assert.strictEqual(result.status, 'success');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as sinon from 'sinon';
import * as assert from 'assert';
import { createXhrTransport } from '../../src/platform/browser/xhr-transport';
import {
ExportResponseRetryable,
ExportResponseFailure,
ExportResponseSuccess,
} from '../../src';
import { ensureHeadersContain } from '../testHelper';

const testTransportParameters = {
url: 'http://example.test',
headers: {
foo: 'foo-value',
bar: 'bar-value',
'Content-Type': 'application/json',
},
};

const requestTimeout = 1000;
const testPayload = Uint8Array.from([1, 2, 3]);

describe('XhrTransport', function () {
afterEach(() => {
sinon.restore();
});

describe('send', function () {
it('returns success when request succeeds', function (done) {
// arrange
const server = sinon.fakeServer.create();
const transport = createXhrTransport(testTransportParameters);

let request: sinon.SinonFakeXMLHttpRequest;
queueMicrotask(() => {
// this executes after the act block
request = server.requests[0];
request.respond(200, {}, 'test response');
});

//act
transport.send(testPayload, requestTimeout).then(response => {
// assert
try {
assert.strictEqual(response.status, 'success');
// currently we don't do anything with the response yet, so it's dropped by the transport.
assert.strictEqual(
(response as ExportResponseSuccess).data,
undefined
);
assert.strictEqual(request.url, testTransportParameters.url);
assert.strictEqual(
(request.requestBody as unknown as Blob).type,
'application/json'
);
ensureHeadersContain(request.requestHeaders, {
foo: 'foo-value',
bar: 'bar-value',
// ;charset=utf-8 is applied by sinon.fakeServer
'Content-Type': 'application/json;charset=utf-8',
});
} catch (e) {
done(e);
}
done();
}, done /* catch any rejections */);
});

it('returns failure when request fails', function (done) {
// arrange
const server = sinon.fakeServer.create();
const transport = createXhrTransport(testTransportParameters);

queueMicrotask(() => {
// this executes after the act block
const request = server.requests[0];
request.respond(404, {}, '');
});

//act
transport.send(testPayload, requestTimeout).then(response => {
// assert
try {
assert.strictEqual(response.status, 'failure');
} catch (e) {
done(e);
}
done();
}, done /* catch any rejections */);
});

it('returns retryable when request is retryable', function (done) {
// arrange
const server = sinon.fakeServer.create();
const transport = createXhrTransport(testTransportParameters);

queueMicrotask(() => {
// this executes after the act block
const request = server.requests[0];
request.respond(503, { 'Retry-After': 5 }, '');
});

//act
transport.send(testPayload, requestTimeout).then(response => {
// assert
try {
assert.strictEqual(response.status, 'retryable');
assert.strictEqual(
(response as ExportResponseRetryable).retryInMillis,
5000
);
} catch (e) {
done(e);
}
done();
}, done /* catch any rejections */);
});

it('returns failure when request times out', function (done) {
// arrange
// A fake server needed, otherwise the message will not be a timeout but a failure to connect.
sinon.useFakeServer();
const clock = sinon.useFakeTimers();
const transport = createXhrTransport(testTransportParameters);

//act
transport.send(testPayload, requestTimeout).then(response => {
// assert
try {
assert.strictEqual(response.status, 'failure');
assert.strictEqual(
(response as ExportResponseFailure).error.message,
'XHR request timed out'
);
} catch (e) {
done(e);
}
done();
}, done /* catch any rejections */);
clock.tick(requestTimeout + 100);
});

it('returns failure when no server exists', function (done) {
// arrange
const clock = sinon.useFakeTimers();
const transport = createXhrTransport(testTransportParameters);

//act
transport.send(testPayload, requestTimeout).then(response => {
// assert
try {
assert.strictEqual(response.status, 'failure');
assert.strictEqual(
(response as ExportResponseFailure).error.message,
'XHR request errored'
);
} catch (e) {
done(e);
}
done();
}, done /* catch any rejections */);
clock.tick(requestTimeout + 100);
});
});
});

0 comments on commit 0e75af3

Please sign in to comment.