|
16 | 16 |
|
17 | 17 | import * as assert from 'assert'; |
18 | 18 | import { configureExporterTimeout, invalidTimeout } from '../../src/util'; |
| 19 | +import { sendWithHttp } from '../../src/platform/node/util'; |
19 | 20 | import { CompressionAlgorithm} from '../../src/platform/node/types'; |
20 | 21 | import { configureCompression} from '../../src/platform/node/util'; |
21 | 22 | import { diag } from '@opentelemetry/api'; |
22 | 23 | import * as sinon from 'sinon'; |
23 | 24 |
|
| 25 | +import { OTLPExporterNodeBase } from '../../src/platform/node/OTLPExporterNodeBase'; |
| 26 | +import { OTLPExporterNodeConfigBase } from '../../src/platform/node/types'; |
| 27 | +import { OTLPExporterError } from '../../src/types'; |
| 28 | +import { PassThrough } from 'stream'; |
| 29 | +import * as http from 'http'; |
| 30 | +import * as zlib from 'zlib'; |
| 31 | + |
| 32 | +// Meant to simulate http.IncomingMessage, at least the parts that sendWithHttp cares about |
| 33 | +// but make it a PassThrough so we can inspect it for the test |
| 34 | +class HttpResponse extends PassThrough { |
| 35 | + statusCode: number; |
| 36 | + statusMessage: string; |
| 37 | + |
| 38 | + constructor(statusCode = 200, statusMessage = 'OK') { |
| 39 | + super(); |
| 40 | + this.statusCode = statusCode; |
| 41 | + this.statusMessage = statusMessage; |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +// Meant to simulate http.ClientRequest, at least the parts that sendWithHttp cares about |
| 46 | +// but make it a PassThrough so we can inspect it for the test |
| 47 | +class HttpRequest extends PassThrough { |
| 48 | + setHeader(name: string, value: string) {} |
| 49 | +} |
| 50 | + |
| 51 | +// Barebones exporter for use by sendWithHttp |
| 52 | +type ExporterConfig = OTLPExporterNodeConfigBase; |
| 53 | +class Exporter extends OTLPExporterNodeBase<object,object> { |
| 54 | + getDefaultUrl(config: ExporterConfig): string { |
| 55 | + return config.url || ''; |
| 56 | + } |
| 57 | + |
| 58 | + convert(spans: object[]): object { |
| 59 | + return {}; |
| 60 | + } |
| 61 | +} |
| 62 | + |
24 | 63 | describe('configureExporterTimeout', () => { |
25 | 64 | const envSource = process.env; |
26 | 65 | it('should use timeoutMillis parameter as export timeout value', () => { |
@@ -124,3 +163,123 @@ describe('configureCompression', () => { |
124 | 163 | assert.strictEqual(configureCompression(undefined),CompressionAlgorithm.NONE); |
125 | 164 | }); |
126 | 165 | }); |
| 166 | + |
| 167 | +describe('sendWithHttp', () => { |
| 168 | + let exporter: Exporter; |
| 169 | + let httpRequestStub: sinon.SinonStub; |
| 170 | + let mockRequest: HttpRequest; |
| 171 | + let setHeaderSpy: sinon.SinonSpy; |
| 172 | + |
| 173 | + const spanData: object = { |
| 174 | + 'foo': 'bar', |
| 175 | + 'bar': 'baz', |
| 176 | + }; |
| 177 | + |
| 178 | + beforeEach(() => { |
| 179 | + |
| 180 | + // Create stub of http.request (used by sendWithHttp) |
| 181 | + httpRequestStub = sinon.stub(http, 'request'); |
| 182 | + |
| 183 | + // Mock out a request |
| 184 | + mockRequest = new HttpRequest(); |
| 185 | + setHeaderSpy = sinon.spy(mockRequest, 'setHeader'); |
| 186 | + |
| 187 | + // Mock out response |
| 188 | + const response = new HttpResponse(); |
| 189 | + response.end('OK'); |
| 190 | + |
| 191 | + // Stub out http.request so it calls our callback with the mocked response |
| 192 | + // and also so it returns our mocked request stream so we can observe. We don't |
| 193 | + // really care about the response for the purpose of this test, but we do want |
| 194 | + // to observe the request compression behavior. |
| 195 | + httpRequestStub.returns(mockRequest).callsArgWith(1, response); |
| 196 | + }); |
| 197 | + |
| 198 | + afterEach(function() { |
| 199 | + httpRequestStub.restore(); |
| 200 | + setHeaderSpy.restore(); |
| 201 | + }); |
| 202 | + |
| 203 | + it('should send with no compression if configured to do so', () => { |
| 204 | + exporter = new Exporter({ |
| 205 | + url: 'http://foobar.com', |
| 206 | + compression: CompressionAlgorithm.NONE, |
| 207 | + }); |
| 208 | + const data = JSON.stringify(spanData); |
| 209 | + |
| 210 | + // Show that data is written to the request stream |
| 211 | + let requestData = ''; |
| 212 | + mockRequest.on('data', chunk => requestData += chunk); |
| 213 | + mockRequest.on('end', () => { |
| 214 | + assert.strictEqual(requestData, data); |
| 215 | + }); |
| 216 | + |
| 217 | + sendWithHttp(exporter, data, 'application/json', () => { |
| 218 | + // Show that we aren't setting the gzip encoding header |
| 219 | + assert(setHeaderSpy.withArgs('Content-Encoding', 'gzip').notCalled); |
| 220 | + }, (err: OTLPExporterError) => { |
| 221 | + assert.fail(err); |
| 222 | + }); |
| 223 | + }); |
| 224 | + |
| 225 | + it('should send with gzip compression if configured to do so', () => { |
| 226 | + exporter = new Exporter({ |
| 227 | + url: 'http://foobar.com', |
| 228 | + compression: CompressionAlgorithm.GZIP, |
| 229 | + }); |
| 230 | + |
| 231 | + const data = JSON.stringify(spanData); |
| 232 | + const compressedData = zlib.gzipSync(Buffer.from(data)); |
| 233 | + |
| 234 | + // Show that compressed data is written to the request stream |
| 235 | + const buffers: Buffer[] = []; |
| 236 | + mockRequest.on('data', chunk => buffers.push(Buffer.from(chunk))); |
| 237 | + mockRequest.on('end', () => { |
| 238 | + assert(Buffer.concat(buffers).equals(compressedData)); |
| 239 | + }); |
| 240 | + |
| 241 | + sendWithHttp(exporter, data, 'application/json', () => { |
| 242 | + // Show that we are setting the gzip encoding header |
| 243 | + assert(setHeaderSpy.withArgs('Content-Encoding', 'gzip').calledOnce); |
| 244 | + }, (err: OTLPExporterError) => { |
| 245 | + assert.fail(err); |
| 246 | + }); |
| 247 | + }); |
| 248 | + |
| 249 | + it('should work with gzip compression enabled even after multiple requests', () => { |
| 250 | + exporter = new Exporter({ |
| 251 | + url: 'http://foobar.com', |
| 252 | + compression: CompressionAlgorithm.GZIP, |
| 253 | + }); |
| 254 | + |
| 255 | + const data = JSON.stringify(spanData); |
| 256 | + const compressedData = zlib.gzipSync(Buffer.from(data)); |
| 257 | + |
| 258 | + for (let i = 0; i < 5; i++) { |
| 259 | + mockRequest = new HttpRequest(); |
| 260 | + setHeaderSpy.restore(); |
| 261 | + setHeaderSpy = sinon.spy(mockRequest, 'setHeader'); |
| 262 | + |
| 263 | + const response = new HttpResponse(); |
| 264 | + response.end('OK'); |
| 265 | + |
| 266 | + httpRequestStub.restore(); |
| 267 | + httpRequestStub = sinon.stub(http, 'request'); |
| 268 | + httpRequestStub.returns(mockRequest).callsArgWith(1, response); |
| 269 | + |
| 270 | + // Show that compressed data is written to the request stream |
| 271 | + const buffers: Buffer[] = []; |
| 272 | + mockRequest.on('data', chunk => buffers.push(Buffer.from(chunk))); |
| 273 | + mockRequest.on('end', () => { |
| 274 | + assert(Buffer.concat(buffers).equals(compressedData)); |
| 275 | + }); |
| 276 | + |
| 277 | + sendWithHttp(exporter, data, 'application/json', () => { |
| 278 | + // Show that we are setting the gzip encoding header |
| 279 | + assert(setHeaderSpy.withArgs('Content-Encoding', 'gzip').calledOnce); |
| 280 | + }, (err: OTLPExporterError) => { |
| 281 | + assert.fail(err); |
| 282 | + }); |
| 283 | + } |
| 284 | + }); |
| 285 | +}); |
0 commit comments