Skip to content

Commit

Permalink
Merge pull request #972 from micalevisk/master
Browse files Browse the repository at this point in the history
fix(): support processors registered with useFactory/useValue
  • Loading branch information
kamilmysliwiec authored Oct 18, 2021
2 parents f1213d9 + 3fd95e1 commit 86bba1b
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 3 deletions.
112 changes: 111 additions & 1 deletion e2e/module.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MetadataScanner } from '@nestjs/core';
import { Queue } from 'bull';
import { BullModule, getQueueToken } from '../lib';
import { BullModule, getQueueToken, Processor } from '../lib';

describe('BullModule', () => {
describe('registerQueue', () => {
Expand Down Expand Up @@ -403,4 +404,113 @@ describe('BullModule', () => {
});
});
});

describe('handles all kind of valid processors providers', () => {
@Processor('test_processor_registering')
class MyProcessorA {}

@Processor('test_processor_registering')
class MyProcessorB {}

@Processor('test_processor_registering')
class MyProcessorC {}

let testingModule: TestingModule;

let metadataScanner: MetadataScanner;

beforeAll(async () => {
testingModule = await Test.createTestingModule({
imports: [
BullModule.registerQueue({
name: 'test_processor_registering',
redis: {
host: '0.0.0.0',
port: 6380,
},
}),
],
providers: [
{
provide: 'A',
useClass: MyProcessorA,
},
{
provide: 'B',
useValue: new MyProcessorB(),
},
{
provide: 'C',
useFactory: () => new MyProcessorC(),
},
],
}).compile();

metadataScanner = testingModule.get(MetadataScanner);
jest.spyOn(metadataScanner, 'scanFromPrototype');

await testingModule.init();
});
afterAll(async () => {
await testingModule.close();
});

it('should use MetadataScanner#scanFromPrototype when exploring', () => {
expect(metadataScanner.scanFromPrototype).toHaveBeenCalled();
});

it('should reach the processor supplied with `useClass`', () => {
const scanPrototypeCalls = jest.spyOn(
metadataScanner,
'scanFromPrototype',
).mock.calls;

const scanPrototypeCallsFirstArgsEveryCall = scanPrototypeCalls.flatMap(
(args) => args[0],
);

expect(
scanPrototypeCallsFirstArgsEveryCall.some(
(instanceWrapperInstance) =>
instanceWrapperInstance.constructor.name === MyProcessorA.name,
),
).toBeTruthy();
});

it('should reach the processor supplied with `useValue`', () => {
const scanPrototypeCalls = jest.spyOn(
metadataScanner,
'scanFromPrototype',
).mock.calls;

const scanPrototypeCallsFirstArgsEveryCall = scanPrototypeCalls.flatMap(
(args) => args[0],
);

expect(
scanPrototypeCallsFirstArgsEveryCall.some(
(instanceWrapperInstance) =>
instanceWrapperInstance.constructor.name === MyProcessorB.name,
),
).toBeTruthy();
});

it('should reach the processor supplied with `useFactory`', () => {
const scanPrototypeCalls = jest.spyOn(
metadataScanner,
'scanFromPrototype',
).mock.calls;

const scanPrototypeCallsFirstArgsEveryCall = scanPrototypeCalls.flatMap(
(args) => args[0],
);

expect(
scanPrototypeCallsFirstArgsEveryCall.some(
(instanceWrapperInstance) =>
instanceWrapperInstance.constructor.name === MyProcessorC.name,
),
).toBeTruthy();
});
});
});
25 changes: 23 additions & 2 deletions lib/bull.explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,36 @@ export class BullExplorer implements OnModuleInit {
const providers: InstanceWrapper[] = this.discoveryService
.getProviders()
.filter((wrapper: InstanceWrapper) =>
this.metadataAccessor.isQueueComponent(wrapper.metatype),
this.metadataAccessor.isQueueComponent(
// NOTE: Regarding the ternary statement below,
// - The condition `!wrapper.metatype` is because when we use `useValue`
// the value of `wrapper.metatype` will be `null`.
// - The condition `wrapper.inject` is needed here because when we use
// `useFactory`, the value of `wrapper.metatype` will be the supplied
// factory function.
// For both cases, we should use `wrapper.instance.constructor` instead
// of `wrapper.metatype` to resolve processor's class properly.
// But since calling `wrapper.instance` could degrade overall performance
// we must defer it as much we can. But there's no other way to grab the
// right class that could be annotated with `@Processor()` decorator
// without using this property.
!wrapper.metatype || wrapper.inject
? wrapper.instance?.constructor
: wrapper.metatype,
),
);

providers.forEach((wrapper: InstanceWrapper) => {
const { instance, metatype } = wrapper;
const isRequestScoped = !wrapper.isDependencyTreeStatic();
const {
name: queueName,
} = this.metadataAccessor.getQueueComponentMetadata(metatype);
} =
this.metadataAccessor.getQueueComponentMetadata(
// NOTE: We are relying on `instance.constructor` to properly support
// `useValue` and `useFactory` providers besides `useClass`.
instance.constructor || metatype,
);

const queueToken = getQueueToken(queueName);
const bullQueue = this.getQueue(queueToken, queueName);
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"module": "commonjs",
"declaration": true,
"noImplicitAny": false,
"lib": ["ES2019"],
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
Expand Down

0 comments on commit 86bba1b

Please sign in to comment.