@zerosuxx/opentelemetry-nestjs
Version:
OpenTelemetry module for Nestjs with auto instrumentation and resource detection. Initially forked from https://github.com/overbit/opentelemetry-nestjs.git
311 lines (267 loc) • 8.7 kB
text/typescript
import { Test } from '@nestjs/testing';
import { Tracing } from '../../Tracing';
import { OpenTelemetryModule } from '../../OpenTelemetryModule';
import { NoopSpanProcessor } from '@opentelemetry/sdk-trace-node';
import { Controller, ForbiddenException, Get } from '@nestjs/common';
import { Span } from '../Decorators/Span';
import * as request from 'supertest';
import { ControllerInjector } from './ControllerInjector';
import waitForExpect from 'wait-for-expect';
import { EventPattern, MessagePattern } from '@nestjs/microservices';
import { SpanKind, SpanStatusCode } from '@opentelemetry/api';
describe('Tracing Controller Injector Test', () => {
const sdkModule = OpenTelemetryModule.forRoot([ControllerInjector]);
let exporterSpy: jest.SpyInstance;
const exporter = new NoopSpanProcessor();
Tracing.init({ serviceName: 'a', spanProcessors: [exporter] });
beforeEach(() => {
exporterSpy = jest.spyOn(exporter, 'onStart');
});
afterEach(() => {
exporterSpy.mockClear();
exporterSpy.mockReset();
});
describe('for microservices', () => {
it(`should trace controller method using MessagePattern`, async () => {
// given
('hello')
class HelloController {
('time.us.*')
// eslint-disable-next-line @typescript-eslint/no-empty-function
message() {}
}
const context = await Test.createTestingModule({
imports: [sdkModule],
controllers: [HelloController],
}).compile();
const app = context.createNestApplication();
await app.init();
// when
await app.get(HelloController).message();
//then
await waitForExpect(() =>
expect(exporterSpy).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Controller->HelloController.message',
kind: SpanKind.SERVER,
}),
expect.any(Object),
),
);
await app.close();
});
it(`should trace controller method using EventPattern`, async () => {
// given
('hello')
class HelloController {
('user.created')
// eslint-disable-next-line @typescript-eslint/no-empty-function
event() {}
}
const context = await Test.createTestingModule({
imports: [sdkModule],
controllers: [HelloController],
}).compile();
const app = context.createNestApplication();
await app.init();
// when
await app.get(HelloController).event();
//then
await waitForExpect(() =>
expect(exporterSpy).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Controller->HelloController.event',
kind: SpanKind.SERVER,
}),
expect.any(Object),
),
);
await app.close();
});
it(`should not trace controller method if it is not a microservice client`, async () => {
// given
('hello')
class HelloController {
('time.us.*')
// eslint-disable-next-line @typescript-eslint/no-empty-function
message() {}
('time.us.*')
// eslint-disable-next-line @typescript-eslint/no-empty-function
event() {}
// eslint-disable-next-line @typescript-eslint/no-empty-function
other() {}
}
const context = await Test.createTestingModule({
imports: [sdkModule],
controllers: [HelloController],
}).compile();
const app = context.createNestApplication();
await app.init();
const helloController = app.get(HelloController);
//when
helloController.message();
helloController.event();
helloController.other();
//then
await waitForExpect(() =>
expect(exporterSpy).not.toHaveBeenCalledWith(
expect.objectContaining({
name: 'Controller->HelloController.other',
}),
expect.any(Object),
),
);
await app.close();
});
it(`should trace controller method exception`, async () => {
// given @Controller('hello')
()
class HelloController {
('user.created')
// eslint-disable-next-line @typescript-eslint/no-empty-function
event() {
throw new Error("I'm an error");
}
}
const context = await Test.createTestingModule({
imports: [sdkModule],
controllers: [HelloController],
}).compile();
const app = context.createNestApplication();
await app.init();
// when
try {
await app.get(HelloController).event();
} catch (error) {}
//then
await waitForExpect(() =>
expect(exporterSpy).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Controller->HelloController.event',
status: {
code: SpanStatusCode.ERROR,
message: "I'm an error",
},
}),
expect.any(Object),
),
);
await app.close();
});
});
describe('for http', () => {
it(`should trace controller method`, async () => {
// given
('hello')
class HelloController {
()
// eslint-disable-next-line @typescript-eslint/no-empty-function
hi() {}
}
const context = await Test.createTestingModule({
imports: [sdkModule],
controllers: [HelloController],
}).compile();
const app = context.createNestApplication();
await app.init();
// when
await request(app.getHttpServer()).get('/hello').send().expect(200);
//then
await waitForExpect(() =>
expect(exporterSpy).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Controller->HelloController.hi',
kind: SpanKind.SERVER,
}),
expect.any(Object),
),
);
await app.close();
});
it(`should trace controller method exception`, async () => {
// given
('hello')
class HelloController {
()
hi() {
throw new ForbiddenException();
}
}
const context = await Test.createTestingModule({
imports: [sdkModule],
controllers: [HelloController],
}).compile();
const app = context.createNestApplication();
await app.init();
// when
await request(app.getHttpServer()).get('/hello').send().expect(403);
//then
await waitForExpect(() =>
expect(exporterSpy).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Controller->HelloController.hi',
status: {
code: SpanStatusCode.ERROR,
message: 'Forbidden',
},
}),
expect.any(Object),
),
);
await app.close();
});
it(`should not trace controller method if there is no path`, async () => {
// given
('hello')
class HelloController {
// eslint-disable-next-line @typescript-eslint/no-empty-function
hi() {}
}
const context = await Test.createTestingModule({
imports: [sdkModule],
controllers: [HelloController],
}).compile();
const app = context.createNestApplication();
await app.init();
const helloController = app.get(HelloController);
//when
helloController.hi();
//then
await waitForExpect(() =>
expect(exporterSpy).not.toHaveBeenCalledWith(
expect.objectContaining({ name: 'Controller->HelloController.hi' }),
expect.any(Object),
),
);
await app.close();
});
it(`should not trace controller method if already decorated`, async () => {
// given
('hello')
class HelloController {
()
('SLM_CNM')
// eslint-disable-next-line @typescript-eslint/no-empty-function
hi() {}
}
const context = await Test.createTestingModule({
imports: [sdkModule],
controllers: [HelloController],
}).compile();
const app = context.createNestApplication();
await app.init();
// when
await request(app.getHttpServer()).get('/hello').send().expect(200);
// then
await waitForExpect(() =>
expect(exporterSpy).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Controller->HelloController.SLM_CNM',
}),
expect.any(Object),
),
);
await app.close();
});
});
});