Merge pull request #16246 from 0xC0FFE2/test/add-unit-tests-discovery-and-serializer

test: add unit tests for DiscoveryService and ClassSerializerInterceptor
This commit is contained in:
Kamil Mysliwiec
2026-01-25 18:48:54 +01:00
committed by GitHub
2 changed files with 1007 additions and 0 deletions

View File

@@ -0,0 +1,468 @@
import { expect } from 'chai';
import * as sinon from 'sinon';
import { of, throwError } from 'rxjs';
import { ClassSerializerInterceptor } from '../../serializer/class-serializer.interceptor';
import { ExecutionContext, CallHandler } from '../../interfaces';
import { StreamableFile } from '../../file-stream';
describe('ClassSerializerInterceptor', () => {
let interceptor: ClassSerializerInterceptor;
let mockReflector: any;
let mockTransformerPackage: any;
let sandbox: sinon.SinonSandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
mockReflector = {
getAllAndOverride: sandbox.stub(),
};
mockTransformerPackage = {
classToPlain: sandbox.stub(),
plainToInstance: sandbox.stub(),
};
});
afterEach(() => {
sandbox.restore();
});
describe('constructor', () => {
it('should create interceptor with default transformer package', () => {
// This would normally load 'class-transformer' package
// For testing, we pass a mock transformer package
const options = {
transformerPackage: mockTransformerPackage,
};
interceptor = new ClassSerializerInterceptor(mockReflector, options);
expect(interceptor).to.be.instanceOf(ClassSerializerInterceptor);
});
it('should use provided transformer package from options', () => {
const customTransformer = {
classToPlain: sandbox.stub(),
plainToInstance: sandbox.stub(),
};
const options = {
transformerPackage: customTransformer,
};
interceptor = new ClassSerializerInterceptor(mockReflector, options);
expect(interceptor).to.be.instanceOf(ClassSerializerInterceptor);
});
});
describe('intercept', () => {
let mockExecutionContext: ExecutionContext;
let mockCallHandler: CallHandler;
beforeEach(() => {
interceptor = new ClassSerializerInterceptor(mockReflector, {
transformerPackage: mockTransformerPackage,
});
mockExecutionContext = {
getHandler: sandbox.stub(),
getClass: sandbox.stub(),
} as any;
mockCallHandler = {
handle: sandbox.stub(),
} as any;
});
it('should transform plain object response', done => {
const response = { id: 1, name: 'Test' };
const transformedResponse = { id: 1, name: 'Test' };
mockReflector.getAllAndOverride.returns(undefined);
mockTransformerPackage.classToPlain.returns(transformedResponse);
(mockCallHandler.handle as sinon.SinonStub).returns(of(response));
interceptor
.intercept(mockExecutionContext, mockCallHandler)
.subscribe(result => {
expect(result).to.equal(transformedResponse);
done();
});
});
it('should transform array of objects', done => {
const response = [
{ id: 1, name: 'Test1' },
{ id: 2, name: 'Test2' },
];
const transformedItem1 = { id: 1, name: 'Test1' };
const transformedItem2 = { id: 2, name: 'Test2' };
mockReflector.getAllAndOverride.returns(undefined);
mockTransformerPackage.classToPlain
.onFirstCall()
.returns(transformedItem1);
mockTransformerPackage.classToPlain
.onSecondCall()
.returns(transformedItem2);
(mockCallHandler.handle as sinon.SinonStub).returns(of(response));
interceptor
.intercept(mockExecutionContext, mockCallHandler)
.subscribe(result => {
expect(result).to.be.an('array').with.lengthOf(2);
expect(result[0]).to.equal(transformedItem1);
expect(result[1]).to.equal(transformedItem2);
done();
});
});
it('should merge context options with default options', done => {
const response = { id: 1, name: 'Test' };
const defaultOptions = { excludeExtraneousValues: true };
const contextOptions = { groups: ['user'] };
const transformedResponse = { id: 1 };
interceptor = new ClassSerializerInterceptor(mockReflector, {
transformerPackage: mockTransformerPackage,
...defaultOptions,
});
mockReflector.getAllAndOverride.returns(contextOptions);
mockTransformerPackage.classToPlain.returns(transformedResponse);
(mockCallHandler.handle as sinon.SinonStub).returns(of(response));
interceptor
.intercept(mockExecutionContext, mockCallHandler)
.subscribe(result => {
const callArgs = mockTransformerPackage.classToPlain.getCall(0).args;
expect(callArgs[1]).to.deep.include(defaultOptions);
expect(callArgs[1]).to.deep.include(contextOptions);
done();
});
});
it('should call reflector with handler and class', done => {
const response = { id: 1 };
const mockHandler = {};
const mockClass = {};
(mockExecutionContext.getHandler as sinon.SinonStub).returns(mockHandler);
(mockExecutionContext.getClass as sinon.SinonStub).returns(mockClass);
mockReflector.getAllAndOverride.returns(undefined);
mockTransformerPackage.classToPlain.returns(response);
(mockCallHandler.handle as sinon.SinonStub).returns(of(response));
interceptor
.intercept(mockExecutionContext, mockCallHandler)
.subscribe(() => {
expect(mockReflector.getAllAndOverride.calledOnce).to.be.true;
const args = mockReflector.getAllAndOverride.getCall(0).args;
expect(args[1]).to.deep.equal([mockHandler, mockClass]);
done();
});
});
});
describe('serialize', () => {
beforeEach(() => {
interceptor = new ClassSerializerInterceptor(mockReflector, {
transformerPackage: mockTransformerPackage,
});
});
it('should return primitive values unchanged', () => {
expect(interceptor.serialize('string' as any, {})).to.equal('string');
expect(interceptor.serialize(123 as any, {})).to.equal(123);
expect(interceptor.serialize(true as any, {})).to.equal(true);
});
it('should return null unchanged', () => {
expect(interceptor.serialize(null as any, {})).to.be.null;
});
it('should return undefined unchanged', () => {
expect(interceptor.serialize(undefined as any, {})).to.be.undefined;
});
it('should return StreamableFile unchanged', () => {
const streamableFile = new StreamableFile(Buffer.from('test'));
const result = interceptor.serialize(streamableFile as any, {});
expect(result).to.equal(streamableFile);
expect(mockTransformerPackage.classToPlain.called).to.be.false;
});
it('should transform plain object', () => {
const input = { id: 1, name: 'Test' };
const output = { id: 1 };
mockTransformerPackage.classToPlain.returns(output);
const result = interceptor.serialize(input, {});
expect(result).to.equal(output);
expect(mockTransformerPackage.classToPlain.calledOnce).to.be.true;
});
it('should transform array of objects', () => {
const input = [{ id: 1 }, { id: 2 }];
const output1 = { id: 1 };
const output2 = { id: 2 };
mockTransformerPackage.classToPlain.onFirstCall().returns(output1);
mockTransformerPackage.classToPlain.onSecondCall().returns(output2);
const result = interceptor.serialize(input, {});
expect(result).to.be.an('array').with.lengthOf(2);
expect(result[0]).to.equal(output1);
expect(result[1]).to.equal(output2);
expect(mockTransformerPackage.classToPlain.calledTwice).to.be.true;
});
it('should handle empty array', () => {
const input: any[] = [];
const result = interceptor.serialize(input, {});
expect(result).to.be.an('array').that.is.empty;
expect(mockTransformerPackage.classToPlain.called).to.be.false;
});
});
describe('transformToPlain', () => {
beforeEach(() => {
interceptor = new ClassSerializerInterceptor(mockReflector, {
transformerPackage: mockTransformerPackage,
});
});
it('should return falsy values unchanged', () => {
expect(interceptor.transformToPlain(null, {})).to.be.null;
expect(interceptor.transformToPlain(undefined, {})).to.be.undefined;
expect(interceptor.transformToPlain(0 as any, {})).to.equal(0);
expect(interceptor.transformToPlain(false as any, {})).to.be.false;
expect(interceptor.transformToPlain('' as any, {})).to.equal('');
});
it('should use classToPlain when no type option provided', () => {
const input = { id: 1, name: 'Test' };
const output = { id: 1 };
mockTransformerPackage.classToPlain.returns(output);
const result = interceptor.transformToPlain(input, {});
expect(result).to.equal(output);
expect(mockTransformerPackage.classToPlain.calledOnceWith(input, {})).to
.be.true;
expect(mockTransformerPackage.plainToInstance.called).to.be.false;
});
it('should use classToPlain when input is instance of options.type', () => {
class UserDto {
id: number;
name: string;
}
const input = new UserDto();
input.id = 1;
input.name = 'Test';
const output = { id: 1 };
const options = { type: UserDto };
mockTransformerPackage.classToPlain.returns(output);
const result = interceptor.transformToPlain(input, options);
expect(result).to.equal(output);
expect(mockTransformerPackage.classToPlain.calledOnce).to.be.true;
expect(mockTransformerPackage.plainToInstance.called).to.be.false;
});
it('should convert plain to instance then to plain when type provided but not matching', () => {
class UserDto {
id: number;
}
const plainInput = { id: 1, name: 'Test', password: 'secret' };
const instanceOutput = new UserDto();
instanceOutput.id = 1;
const finalOutput = { id: 1 };
const options = { type: UserDto };
mockTransformerPackage.plainToInstance.returns(instanceOutput);
mockTransformerPackage.classToPlain.returns(finalOutput);
const result = interceptor.transformToPlain(plainInput, options);
expect(result).to.equal(finalOutput);
expect(
mockTransformerPackage.plainToInstance.calledOnceWith(
UserDto,
plainInput,
options,
),
).to.be.true;
expect(
mockTransformerPackage.classToPlain.calledOnceWith(
instanceOutput,
options,
),
).to.be.true;
});
it('should handle complex nested objects', () => {
const input = {
user: { id: 1, name: 'Test' },
posts: [{ id: 1, title: 'Post 1' }],
};
const output = {
user: { id: 1 },
posts: [{ id: 1 }],
};
mockTransformerPackage.classToPlain.returns(output);
const result = interceptor.transformToPlain(input, {});
expect(result).to.equal(output);
});
});
describe('getContextOptions', () => {
beforeEach(() => {
interceptor = new ClassSerializerInterceptor(mockReflector, {
transformerPackage: mockTransformerPackage,
});
});
it('should call reflector getAllAndOverride with correct arguments', () => {
const mockHandler = {};
const mockClass = {};
const mockContext = {
getHandler: sandbox.stub().returns(mockHandler),
getClass: sandbox.stub().returns(mockClass),
} as any;
const expectedOptions = { groups: ['admin'] };
mockReflector.getAllAndOverride.returns(expectedOptions);
const result = (interceptor as any).getContextOptions(mockContext);
expect(mockReflector.getAllAndOverride.calledOnce).to.be.true;
const callArgs = mockReflector.getAllAndOverride.getCall(0).args;
expect(callArgs[1]).to.deep.equal([mockHandler, mockClass]);
expect(result).to.equal(expectedOptions);
});
it('should return undefined when no metadata exists', () => {
const mockContext = {
getHandler: sandbox.stub().returns({}),
getClass: sandbox.stub().returns({}),
} as any;
mockReflector.getAllAndOverride.returns(undefined);
const result = (interceptor as any).getContextOptions(mockContext);
expect(result).to.be.undefined;
});
it('should respect handler metadata over class metadata', () => {
const mockHandler = {};
const mockClass = {};
const mockContext = {
getHandler: sandbox.stub().returns(mockHandler),
getClass: sandbox.stub().returns(mockClass),
} as any;
// getAllAndOverride should merge with handler taking precedence
const handlerOptions = {
groups: ['user'],
excludeExtraneousValues: true,
};
mockReflector.getAllAndOverride.returns(handlerOptions);
const result = (interceptor as any).getContextOptions(mockContext);
expect(result).to.deep.equal(handlerOptions);
// Verify it's checking handler first, then class
const callArgs = mockReflector.getAllAndOverride.getCall(0).args;
expect(callArgs[1][0]).to.equal(mockHandler);
expect(callArgs[1][1]).to.equal(mockClass);
});
});
describe('edge cases and error handling', () => {
beforeEach(() => {
interceptor = new ClassSerializerInterceptor(mockReflector, {
transformerPackage: mockTransformerPackage,
});
});
it('should handle array with mixed types', () => {
const input = [
{ id: 1, name: 'Test' },
null,
undefined,
{ id: 2, name: 'Test2' },
];
mockTransformerPackage.classToPlain
.onCall(0)
.returns({ id: 1, name: 'Test' });
mockTransformerPackage.classToPlain.onCall(1).returns(null);
mockTransformerPackage.classToPlain.onCall(2).returns(undefined);
mockTransformerPackage.classToPlain
.onCall(3)
.returns({ id: 2, name: 'Test2' });
const result = interceptor.serialize(input, {});
expect(result).to.be.an('array').with.lengthOf(4);
expect(result[1]).to.be.null;
expect(result[2]).to.be.undefined;
});
it('should not transform when response is not an object', () => {
const input = 'plain string';
const result = interceptor.serialize(input as any, {});
expect(result).to.equal(input);
expect(mockTransformerPackage.classToPlain.called).to.be.false;
});
it('should handle Date objects', () => {
const date = new Date();
const output = { date: date.toISOString() };
mockTransformerPackage.classToPlain.returns(output);
const result = interceptor.serialize({ date } as any, {});
expect(mockTransformerPackage.classToPlain.calledOnce).to.be.true;
});
it('should pass through options to transformer', () => {
const input = { id: 1, name: 'Test', password: 'secret' };
const options = {
excludeExtraneousValues: true,
groups: ['public'],
strategy: 'excludeAll',
};
mockTransformerPackage.classToPlain.returns({ id: 1, name: 'Test' });
interceptor.transformToPlain(input, options as any);
expect(mockTransformerPackage.classToPlain.calledOnce).to.be.true;
const callArgs = mockTransformerPackage.classToPlain.getCall(0).args;
expect(callArgs[1]).to.deep.include(options);
});
});
});

View File

@@ -0,0 +1,539 @@
import { expect } from 'chai';
import * as sinon from 'sinon';
import { DiscoveryService } from '../../discovery/discovery-service';
import { InstanceWrapper } from '../../injector/instance-wrapper';
import { Module } from '../../injector/module';
import { ModulesContainer } from '../../injector/modules-container';
import { DiscoverableMetaHostCollection } from '../../discovery/discoverable-meta-host-collection';
describe('DiscoveryService', () => {
let discoveryService: DiscoveryService;
let modulesContainer: ModulesContainer;
let sandbox: sinon.SinonSandbox;
beforeEach(() => {
modulesContainer = new ModulesContainer();
discoveryService = new DiscoveryService(modulesContainer);
sandbox = sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
describe('createDecorator', () => {
it('should create a decorator with a unique KEY', () => {
const decorator1 = DiscoveryService.createDecorator();
const decorator2 = DiscoveryService.createDecorator();
expect(decorator1.KEY).to.be.a('string');
expect(decorator2.KEY).to.be.a('string');
expect(decorator1.KEY).to.not.equal(decorator2.KEY);
});
it('should create a decorator that can decorate classes', () => {
const TestDecorator = DiscoveryService.createDecorator<{
value: string;
}>();
@TestDecorator({ value: 'test' })
class TestClass {}
const metadata = Reflect.getMetadata(TestDecorator.KEY, TestClass);
expect(metadata).to.deep.equal({ value: 'test' });
});
it('should create a decorator that can decorate methods', () => {
const TestDecorator = DiscoveryService.createDecorator<{
event: string;
}>();
class TestClass {
@TestDecorator({ event: 'click' })
handleClick() {}
}
const metadata = Reflect.getMetadata(
TestDecorator.KEY,
new TestClass().handleClick,
);
expect(metadata).to.deep.equal({ event: 'click' });
});
it('should use empty object as default metadata when no options provided', () => {
const TestDecorator = DiscoveryService.createDecorator();
@TestDecorator()
class TestClass {}
const metadata = Reflect.getMetadata(TestDecorator.KEY, TestClass);
expect(metadata).to.deep.equal({});
});
it('should add class to DiscoverableMetaHostCollection when decorating a class', () => {
const addClassMetaHostLinkSpy = sandbox.spy(
DiscoverableMetaHostCollection,
'addClassMetaHostLink',
);
const TestDecorator = DiscoveryService.createDecorator();
@TestDecorator()
class TestClass {}
expect(addClassMetaHostLinkSpy.calledOnce).to.be.true;
expect(addClassMetaHostLinkSpy.calledWith(TestClass, TestDecorator.KEY))
.to.be.true;
});
it('should not add to DiscoverableMetaHostCollection when decorating a method', () => {
const addClassMetaHostLinkSpy = sandbox.spy(
DiscoverableMetaHostCollection,
'addClassMetaHostLink',
);
const TestDecorator = DiscoveryService.createDecorator();
class TestClass {
@TestDecorator()
testMethod() {}
}
expect(addClassMetaHostLinkSpy.called).to.be.false;
});
});
describe('getProviders', () => {
it('should return all providers from all modules when no options provided', () => {
const module1 = new Module(class Module1 {}, modulesContainer as any);
const module2 = new Module(class Module2 {}, modulesContainer as any);
// Clear default providers
module1.providers.clear();
module2.providers.clear();
const provider1 = new InstanceWrapper({
name: 'Provider1',
token: 'PROVIDER_1',
});
const provider2 = new InstanceWrapper({
name: 'Provider2',
token: 'PROVIDER_2',
});
const provider3 = new InstanceWrapper({
name: 'Provider3',
token: 'PROVIDER_3',
});
module1.providers.set('PROVIDER_1', provider1);
module1.providers.set('PROVIDER_2', provider2);
module2.providers.set('PROVIDER_3', provider3);
modulesContainer.set('Module1', module1);
modulesContainer.set('Module2', module2);
const providers = discoveryService.getProviders();
expect(providers).to.have.lengthOf(3);
expect(providers).to.include(provider1);
expect(providers).to.include(provider2);
expect(providers).to.include(provider3);
});
it('should return empty array when no modules exist', () => {
const providers = discoveryService.getProviders();
expect(providers).to.be.an('array').that.is.empty;
});
it('should return empty array when modules have no providers', () => {
const module1 = new Module(class Module1 {}, modulesContainer as any);
module1.providers.clear();
modulesContainer.set('Module1', module1);
const providers = discoveryService.getProviders();
expect(providers).to.be.an('array').that.is.empty;
});
it('should filter providers by metadataKey when provided', () => {
const metadataKey = 'test-metadata-key';
const provider1 = new InstanceWrapper({
name: 'Provider1',
token: 'PROVIDER_1',
});
const provider2 = new InstanceWrapper({
name: 'Provider2',
token: 'PROVIDER_2',
});
const providerSet = new Set([provider1, provider2]);
const getProvidersByMetaKeyStub = sandbox
.stub(DiscoverableMetaHostCollection, 'getProvidersByMetaKey')
.returns(providerSet);
const providers = discoveryService.getProviders({ metadataKey });
expect(getProvidersByMetaKeyStub.calledOnce).to.be.true;
expect(
getProvidersByMetaKeyStub.calledWith(modulesContainer, metadataKey),
).to.be.true;
expect(providers).to.have.lengthOf(2);
expect(providers).to.include(provider1);
expect(providers).to.include(provider2);
});
it('should return empty array when no providers match the metadataKey', () => {
const metadataKey = 'non-existent-key';
const emptySet = new Set<InstanceWrapper>();
sandbox
.stub(DiscoverableMetaHostCollection, 'getProvidersByMetaKey')
.returns(emptySet);
const providers = discoveryService.getProviders({ metadataKey });
expect(providers).to.be.an('array').that.is.empty;
});
it('should filter providers by included modules', () => {
class Module1 {}
class Module2 {}
class Module3 {}
const module1 = new Module(Module1, modulesContainer as any);
const module2 = new Module(Module2, modulesContainer as any);
const module3 = new Module(Module3, modulesContainer as any);
// Clear default providers
module1.providers.clear();
module2.providers.clear();
module3.providers.clear();
const provider1 = new InstanceWrapper({
name: 'Provider1',
token: 'PROVIDER_1',
});
const provider2 = new InstanceWrapper({
name: 'Provider2',
token: 'PROVIDER_2',
});
const provider3 = new InstanceWrapper({
name: 'Provider3',
token: 'PROVIDER_3',
});
module1.providers.set('PROVIDER_1', provider1);
module2.providers.set('PROVIDER_2', provider2);
module3.providers.set('PROVIDER_3', provider3);
modulesContainer.set('Module1', module1);
modulesContainer.set('Module2', module2);
modulesContainer.set('Module3', module3);
const providers = discoveryService.getProviders({
include: [Module1, Module2],
});
expect(providers).to.have.lengthOf(2);
expect(providers).to.include(provider1);
expect(providers).to.include(provider2);
expect(providers).to.not.include(provider3);
});
it('should return empty array when include option is empty array', () => {
const module1 = new Module(class Module1 {}, modulesContainer as any);
const provider1 = new InstanceWrapper({
name: 'Provider1',
token: 'PROVIDER_1',
});
module1.providers.set('PROVIDER_1', provider1);
modulesContainer.set('Module1', module1);
const providers = discoveryService.getProviders({ include: [] });
expect(providers).to.be.an('array').that.is.empty;
});
});
describe('getControllers', () => {
it('should return all controllers from all modules when no options provided', () => {
const module1 = new Module(class Module1 {}, modulesContainer as any);
const module2 = new Module(class Module2 {}, modulesContainer as any);
const controller1 = new InstanceWrapper({
name: 'Controller1',
token: 'CONTROLLER_1',
});
const controller2 = new InstanceWrapper({
name: 'Controller2',
token: 'CONTROLLER_2',
});
module1.controllers.set('CONTROLLER_1', controller1);
module2.controllers.set('CONTROLLER_2', controller2);
modulesContainer.set('Module1', module1);
modulesContainer.set('Module2', module2);
const controllers = discoveryService.getControllers();
expect(controllers).to.have.lengthOf(2);
expect(controllers).to.include(controller1);
expect(controllers).to.include(controller2);
});
it('should return empty array when no controllers exist', () => {
const module1 = new Module(class Module1 {}, modulesContainer as any);
modulesContainer.set('Module1', module1);
const controllers = discoveryService.getControllers();
expect(controllers).to.be.an('array').that.is.empty;
});
it('should filter controllers by metadataKey when provided', () => {
const metadataKey = 'controller-metadata-key';
const controller1 = new InstanceWrapper({
name: 'Controller1',
token: 'CONTROLLER_1',
});
const controllerSet = new Set([controller1]);
const getControllersByMetaKeyStub = sandbox
.stub(DiscoverableMetaHostCollection, 'getControllersByMetaKey')
.returns(controllerSet);
const controllers = discoveryService.getControllers({ metadataKey });
expect(getControllersByMetaKeyStub.calledOnce).to.be.true;
expect(
getControllersByMetaKeyStub.calledWith(modulesContainer, metadataKey),
).to.be.true;
expect(controllers).to.have.lengthOf(1);
expect(controllers).to.include(controller1);
});
it('should filter controllers by included modules', () => {
class Module1 {}
class Module2 {}
const module1 = new Module(Module1, modulesContainer as any);
const module2 = new Module(Module2, modulesContainer as any);
const controller1 = new InstanceWrapper({
name: 'Controller1',
token: 'CONTROLLER_1',
});
const controller2 = new InstanceWrapper({
name: 'Controller2',
token: 'CONTROLLER_2',
});
module1.controllers.set('CONTROLLER_1', controller1);
module2.controllers.set('CONTROLLER_2', controller2);
modulesContainer.set('Module1', module1);
modulesContainer.set('Module2', module2);
const controllers = discoveryService.getControllers({
include: [Module1],
});
expect(controllers).to.have.lengthOf(1);
expect(controllers).to.include(controller1);
expect(controllers).to.not.include(controller2);
});
});
describe('getMetadataByDecorator', () => {
it('should retrieve metadata from class using decorator', () => {
const TestDecorator = DiscoveryService.createDecorator<{
role: string;
}>();
@TestDecorator({ role: 'admin' })
class TestClass {}
const instance = new TestClass();
const wrapper = new InstanceWrapper({
name: 'TestClass',
token: TestClass,
metatype: TestClass,
instance,
});
const metadata = discoveryService.getMetadataByDecorator(
TestDecorator,
wrapper,
);
expect(metadata).to.deep.equal({ role: 'admin' });
});
it('should retrieve metadata from method using decorator and methodKey', () => {
const TestDecorator = DiscoveryService.createDecorator<{
event: string;
}>();
class TestClass {
@TestDecorator({ event: 'created' })
onCreate() {}
}
const instance = new TestClass();
const wrapper = new InstanceWrapper({
name: 'TestClass',
token: TestClass,
metatype: TestClass,
instance,
});
const metadata = discoveryService.getMetadataByDecorator(
TestDecorator,
wrapper,
'onCreate',
);
expect(metadata).to.deep.equal({ event: 'created' });
});
it('should return undefined when metadata does not exist', () => {
const TestDecorator = DiscoveryService.createDecorator();
class TestClass {}
const instance = new TestClass();
const wrapper = new InstanceWrapper({
name: 'TestClass',
token: TestClass,
metatype: TestClass,
instance,
});
const metadata = discoveryService.getMetadataByDecorator(
TestDecorator,
wrapper,
);
expect(metadata).to.be.undefined;
});
it('should return undefined when methodKey does not exist on instance', () => {
const TestDecorator = DiscoveryService.createDecorator();
class TestClass {
existingMethod() {}
}
const instance = new TestClass();
const wrapper = new InstanceWrapper({
name: 'TestClass',
token: TestClass,
metatype: TestClass,
instance,
});
const metadata = discoveryService.getMetadataByDecorator(
TestDecorator,
wrapper,
'existingMethod', // Use existing method to avoid undefined reference error
);
expect(metadata).to.be.undefined;
});
it('should use metatype when instance.constructor is undefined', () => {
const TestDecorator = DiscoveryService.createDecorator<{
value: number;
}>();
@TestDecorator({ value: 42 })
class TestClass {}
const instanceWithoutConstructor: any = Object.create(null);
const wrapper = new InstanceWrapper({
name: 'TestClass',
token: TestClass,
metatype: TestClass,
instance: instanceWithoutConstructor,
});
const metadata = discoveryService.getMetadataByDecorator(
TestDecorator,
wrapper,
);
expect(metadata).to.deep.equal({ value: 42 });
});
it('should handle undefined instance gracefully', () => {
const TestDecorator = DiscoveryService.createDecorator<{
test: string;
}>();
@TestDecorator({ test: 'value' })
class TestClass {}
const wrapper = new InstanceWrapper({
name: 'TestClass',
token: TestClass,
metatype: TestClass,
instance: undefined,
});
const metadata = discoveryService.getMetadataByDecorator(
TestDecorator,
wrapper,
);
expect(metadata).to.deep.equal({ test: 'value' });
});
});
describe('getModules', () => {
it('should return all modules when no options provided', () => {
const module1 = new Module(class Module1 {}, modulesContainer as any);
const module2 = new Module(class Module2 {}, modulesContainer as any);
modulesContainer.set('Module1', module1);
modulesContainer.set('Module2', module2);
const modules = (discoveryService as any).getModules();
expect(modules).to.have.lengthOf(2);
expect(modules).to.include(module1);
expect(modules).to.include(module2);
});
it('should return empty array when no modules exist', () => {
const modules = (discoveryService as any).getModules();
expect(modules).to.be.an('array').that.is.empty;
});
it('should filter modules by include option', () => {
class Module1 {}
class Module2 {}
class Module3 {}
const module1 = new Module(Module1, modulesContainer as any);
const module2 = new Module(Module2, modulesContainer as any);
const module3 = new Module(Module3, modulesContainer as any);
modulesContainer.set('Module1', module1);
modulesContainer.set('Module2', module2);
modulesContainer.set('Module3', module3);
const modules = (discoveryService as any).getModules({
include: [Module1, Module3],
});
expect(modules).to.have.lengthOf(2);
expect(modules).to.include(module1);
expect(modules).to.include(module3);
expect(modules).to.not.include(module2);
});
it('should return empty array when include option is empty', () => {
const module1 = new Module(class Module1 {}, modulesContainer as any);
modulesContainer.set('Module1', module1);
const modules = (discoveryService as any).getModules({ include: [] });
expect(modules).to.be.an('array').that.is.empty;
});
});
});