From 3dcf32ae19007bec143e14f30d89291a6423a72e Mon Sep 17 00:00:00 2001 From: Sukju Hong Date: Wed, 14 May 2025 15:35:44 +0900 Subject: [PATCH 1/4] test: add e2e test for inherited optional dependency handling --- .../e2e/inherited-optional-dependency.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 integration/injector/e2e/inherited-optional-dependency.ts diff --git a/integration/injector/e2e/inherited-optional-dependency.ts b/integration/injector/e2e/inherited-optional-dependency.ts new file mode 100644 index 00000000000..11b06a69da7 --- /dev/null +++ b/integration/injector/e2e/inherited-optional-dependency.ts @@ -0,0 +1,64 @@ +import { Injectable, mixin, Module, Optional } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; + +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { expect } from 'chai'; +import { UnknownDependenciesException } from '@nestjs/core/errors/exceptions/unknown-dependencies.exception'; + +chai.use(chaiAsPromised); + +describe('Inherited optional dependency', () => { + const Foo = () => { + class FooMixin { + constructor(@Optional() option: any) {} + } + return mixin(FooMixin); + }; + + @Injectable() + class NeededService { + exec() { + return 'exec'; + } + } + + @Module({ + providers: [NeededService], + exports: [NeededService], + }) + class SomeModule {} + + @Injectable() + class FooService extends Foo() { + constructor(private readonly neededService: NeededService) { + super(); + } + + doSomething() { + return this.neededService.exec(); + } + } + + @Module({ + imports: [], + providers: [FooService], + exports: [FooService], + }) + class FooModule {} + + /** + * You can see details on this issue here: https://github.com/nestjs/nest/issues/2581 + */ + it('should remove optional dependency metadata inherited from base class', async () => { + const module = Test.createTestingModule({ + imports: [SomeModule, FooModule], + }); + + await expect( + module.compile(), + ).to.eventually.be.rejected.and.be.an.instanceOf( + UnknownDependenciesException, + ); + }); +}); From eda5624062c384d6aa1fc0b5c7be45492c6f3659 Mon Sep 17 00:00:00 2001 From: Sukju Hong Date: Wed, 14 May 2025 20:15:32 +0900 Subject: [PATCH 2/4] feat: Get optional parameter metadata with getOwnMetadata --- packages/core/injector/injector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/injector/injector.ts b/packages/core/injector/injector.ts index d6b5d78a28a..592ec975b58 100644 --- a/packages/core/injector/injector.ts +++ b/packages/core/injector/injector.ts @@ -395,7 +395,7 @@ export class Injector { } public reflectOptionalParams(type: Type): any[] { - return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || []; + return Reflect.getOwnMetadata(OPTIONAL_DEPS_METADATA, type) || []; } public reflectSelfParams(type: Type): any[] { From 21c3278b27acb055ae6f577191e42b73816a0cbe Mon Sep 17 00:00:00 2001 From: Sukju Hong Date: Sun, 25 May 2025 19:48:47 +0900 Subject: [PATCH 3/4] chore: rename test file with .spec.ts suffix --- .../e2e/inherited-optional-dependency.spec.ts | 64 +++++++++++++++++++ .../e2e/inherited-optional-dependency.ts | 64 ------------------- 2 files changed, 64 insertions(+), 64 deletions(-) create mode 100644 integration/injector/e2e/inherited-optional-dependency.spec.ts delete mode 100644 integration/injector/e2e/inherited-optional-dependency.ts diff --git a/integration/injector/e2e/inherited-optional-dependency.spec.ts b/integration/injector/e2e/inherited-optional-dependency.spec.ts new file mode 100644 index 00000000000..d6e541330ca --- /dev/null +++ b/integration/injector/e2e/inherited-optional-dependency.spec.ts @@ -0,0 +1,64 @@ +import { Injectable, mixin, Module, Optional } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { UnknownDependenciesException } from '@nestjs/core/errors/exceptions/unknown-dependencies.exception'; +import { expect } from 'chai'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +chai.use(chaiAsPromised); + +@Injectable() +class NeededService { + exec() { + return 'exec'; + } +} + +@Module({ + providers: [NeededService], + exports: [NeededService], +}) +class NeededModule {} + +const Foo = () => { + class FooMixin { + constructor(@Optional() option: any) {} + } + return mixin(FooMixin); +}; + +@Injectable() +class FooService extends Foo() { + constructor(private readonly neededService: NeededService) { + super(); + } + + doSomething() { + return this.neededService.exec(); + } +} + +@Module({ + imports: [], + providers: [FooService], + exports: [FooService], +}) +class FooModule {} + +describe('Inherited optional dependency', () => { + /** + * You can see details on this issue here: https://github.com/nestjs/nest/issues/2581 + */ + describe('when a dependency with an @Optional() parameter inherited from a parent is missing', () => { + it('should throw an UnknownDependenciesException due to the missing dependency', async () => { + const module = Test.createTestingModule({ + imports: [NeededModule, FooModule], + }); + + await expect( + module.compile(), + ).to.eventually.be.rejected.and.be.an.instanceOf( + UnknownDependenciesException, + ); + }); + }); +}); diff --git a/integration/injector/e2e/inherited-optional-dependency.ts b/integration/injector/e2e/inherited-optional-dependency.ts deleted file mode 100644 index 11b06a69da7..00000000000 --- a/integration/injector/e2e/inherited-optional-dependency.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Injectable, mixin, Module, Optional } from '@nestjs/common'; -import { Test } from '@nestjs/testing'; - -import * as chai from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; -import { expect } from 'chai'; -import { UnknownDependenciesException } from '@nestjs/core/errors/exceptions/unknown-dependencies.exception'; - -chai.use(chaiAsPromised); - -describe('Inherited optional dependency', () => { - const Foo = () => { - class FooMixin { - constructor(@Optional() option: any) {} - } - return mixin(FooMixin); - }; - - @Injectable() - class NeededService { - exec() { - return 'exec'; - } - } - - @Module({ - providers: [NeededService], - exports: [NeededService], - }) - class SomeModule {} - - @Injectable() - class FooService extends Foo() { - constructor(private readonly neededService: NeededService) { - super(); - } - - doSomething() { - return this.neededService.exec(); - } - } - - @Module({ - imports: [], - providers: [FooService], - exports: [FooService], - }) - class FooModule {} - - /** - * You can see details on this issue here: https://github.com/nestjs/nest/issues/2581 - */ - it('should remove optional dependency metadata inherited from base class', async () => { - const module = Test.createTestingModule({ - imports: [SomeModule, FooModule], - }); - - await expect( - module.compile(), - ).to.eventually.be.rejected.and.be.an.instanceOf( - UnknownDependenciesException, - ); - }); -}); From 5dce088bf4807b48d3639a1f43eacbc5bceae74e Mon Sep 17 00:00:00 2001 From: SukJu Hong Date: Sun, 25 May 2025 21:09:04 +0900 Subject: [PATCH 4/4] chore: rename test --- integration/injector/e2e/inherited-optional-dependency.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/injector/e2e/inherited-optional-dependency.spec.ts b/integration/injector/e2e/inherited-optional-dependency.spec.ts index d6e541330ca..a08081ac50e 100644 --- a/integration/injector/e2e/inherited-optional-dependency.spec.ts +++ b/integration/injector/e2e/inherited-optional-dependency.spec.ts @@ -48,7 +48,7 @@ describe('Inherited optional dependency', () => { /** * You can see details on this issue here: https://github.com/nestjs/nest/issues/2581 */ - describe('when a dependency with an @Optional() parameter inherited from a parent is missing', () => { + describe('when the parent has an @Optional() parameter', () => { it('should throw an UnknownDependenciesException due to the missing dependency', async () => { const module = Test.createTestingModule({ imports: [NeededModule, FooModule],