Skip to content

Commit

Permalink
fix(transactional): preserve swagger metadata on methods decorated with
Browse files Browse the repository at this point in the history
  • Loading branch information
nicobuzeta authored May 22, 2024
1 parent 238d1a8 commit 52b067e
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 13 deletions.
28 changes: 15 additions & 13 deletions packages/transactional/src/lib/transactional.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,22 @@ export function Transactional(
`The @Transactional decorator can be only used on functions, but ${propertyKey.toString()} is not a function.`,
);
}
descriptor.value = function (...args: any[]) {
if (!this[transactionHostProperty]) {
throw new Error(
`Failed to inject transaction host into ${target.constructor.name}`,
descriptor.value = new Proxy(original, {
apply: function (_, outerThis, args: any[]) {
if (!outerThis[transactionHostProperty]) {
throw new Error(
`Failed to inject transaction host into ${target.constructor.name}`,
);
}
return (
outerThis[transactionHostProperty] as TransactionHost
).withTransaction(
propagation as Propagation,
options as never,
original.bind(outerThis, ...args),
);
}
return (
this[transactionHostProperty] as TransactionHost
).withTransaction(
propagation as Propagation,
options as never,
original.bind(this, ...args),
);
};
},
});
copyMethodMetadata(original, descriptor.value);
}) as MethodDecorator;
}
Expand Down
130 changes: 130 additions & 0 deletions packages/transactional/test/transactional.decorator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Injectable, Module } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { ClsModule } from 'nestjs-cls';
import { ClsPluginTransactional, Transactional, TransactionHost } from '../src';
import {
MockDbConnection,
TransactionAdapterMock,
} from './transaction-adapter-mock';

export function MetadataDefiningDecorator(): MethodDecorator {
return (
_target: any,
_propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
Reflect.defineMetadata('testproperty', 'testvalue', descriptor.value);
return descriptor;
};
}

export function PropertyDefiningDecorator(): MethodDecorator {
return (
_target: any,
_propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
descriptor.value.testproperty = 'testvalue';
return descriptor;
};
}

@Injectable()
class CalledService {
constructor(
private readonly txHost: TransactionHost<TransactionAdapterMock>,
) {}

async doWork(num: number) {
return this.txHost.tx.query(`SELECT ${num}`);
}

async doOtherWork(num: number) {
return this.txHost.tx.query(`SELECT ${num}`);
}
}

@Injectable()
class CallingService {
constructor(
private readonly calledService: CalledService,
) {}

@Transactional<TransactionAdapterMock>({ serializable: true })
@MetadataDefiningDecorator()
@PropertyDefiningDecorator()
async transactionWithDecoratorWithOptions() {
await this.calledService.doWork(1);
await this.calledService.doOtherWork(2);
}
}

@Module({
providers: [MockDbConnection],
exports: [MockDbConnection],
})
class DbConnectionModule {}

@Module({
imports: [
ClsModule.forRoot({
plugins: [
new ClsPluginTransactional({
imports: [DbConnectionModule],
adapter: new TransactionAdapterMock({
connectionToken: MockDbConnection,
}),
}),
],
}),
],
providers: [CallingService, CalledService],
})
class AppModule {}

describe('Transactional with other decorators', () => {
let module: TestingModule;
let callingService: CallingService;
let mockDbConnection: MockDbConnection;
beforeEach(async () => {
module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
await module.init();
callingService = module.get(CallingService);
mockDbConnection = module.get(MockDbConnection);
});

describe('when using the @Transactional decorator with other decorators', () => {
it('should start a transaction with options', async () => {
await callingService.transactionWithDecoratorWithOptions();
const queries = mockDbConnection.getClientsQueries();
expect(queries).toEqual([
[
'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN TRANSACTION;',
'SELECT 1',
'SELECT 2',
'COMMIT TRANSACTION;',
],
]);
});
});
describe('should mantain method properties set by other decorators', () => {
it('should mantain metadata', () => {
expect(
Reflect.getMetadata(
'testproperty',
callingService.transactionWithDecoratorWithOptions,
),
).toEqual('testvalue');
});

it('should mantain property', () => {
expect(
callingService.transactionWithDecoratorWithOptions[
'testproperty'
],
).toEqual('testvalue');
});
});
});

0 comments on commit 52b067e

Please sign in to comment.