test(transactions): add unit tests for TransactionsService
This commit is contained in:
parent
19fd999441
commit
880c2a7018
1 changed files with 172 additions and 0 deletions
172
src/transactions/transactions.service.spec.ts
Normal file
172
src/transactions/transactions.service.spec.ts
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
import { NotFoundException } from '@nestjs/common';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TransactionsService } from './transactions.service';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { OutboxService } from '../events/outbox.service';
|
||||
|
||||
jest.mock('../db/client', () => ({
|
||||
db: {
|
||||
query: {
|
||||
transactions: {
|
||||
findMany: jest.fn(),
|
||||
findFirst: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
import { db } from '../db/client';
|
||||
|
||||
const mockAuditService = { record: jest.fn().mockResolvedValue(undefined) };
|
||||
const mockOutboxService = {
|
||||
record: jest.fn().mockResolvedValue(undefined),
|
||||
withTransaction: jest.fn().mockImplementation(async (fn: (tx: unknown) => Promise<unknown>) => fn({})),
|
||||
};
|
||||
|
||||
const fakeSession = {
|
||||
tenantId: 'tenant-1',
|
||||
workspaceId: 'ws-1',
|
||||
userId: 'user-1',
|
||||
correlationId: 'corr-1',
|
||||
};
|
||||
|
||||
const fakeTx = {
|
||||
id: 'tx-uuid-1',
|
||||
tenantId: 'tenant-1',
|
||||
organizationId: 'org-uuid-1',
|
||||
type: 'invoice',
|
||||
amountMinorUnits: '150000',
|
||||
currency: 'EUR',
|
||||
transactionDate: new Date('2026-01-15'),
|
||||
evidenceStatus: 'missing',
|
||||
source: 'manual',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
version: 1,
|
||||
};
|
||||
|
||||
describe('TransactionsService', () => {
|
||||
let service: TransactionsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
TransactionsService,
|
||||
{ provide: AuditService, useValue: mockAuditService },
|
||||
{ provide: OutboxService, useValue: mockOutboxService },
|
||||
],
|
||||
}).compile();
|
||||
service = module.get<TransactionsService>(TransactionsService);
|
||||
});
|
||||
|
||||
describe('list', () => {
|
||||
it('returns transactions for the tenant', async () => {
|
||||
// Arrange
|
||||
(db.query.transactions.findMany as jest.Mock).mockResolvedValue([fakeTx]);
|
||||
|
||||
// Act
|
||||
const result = await service.list(fakeSession as any);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([fakeTx]);
|
||||
});
|
||||
|
||||
it('filters by organizationId when provided', async () => {
|
||||
// Arrange
|
||||
(db.query.transactions.findMany as jest.Mock).mockResolvedValue([fakeTx]);
|
||||
|
||||
// Act
|
||||
await service.list(fakeSession as any, 'org-uuid-1');
|
||||
|
||||
// Assert
|
||||
expect(db.query.transactions.findMany).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getById', () => {
|
||||
it('returns transaction when found', async () => {
|
||||
// Arrange
|
||||
(db.query.transactions.findFirst as jest.Mock).mockResolvedValue(fakeTx);
|
||||
|
||||
// Act
|
||||
const result = await service.getById(fakeSession as any, fakeTx.id);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(fakeTx);
|
||||
});
|
||||
|
||||
it('throws NotFoundException for unknown id', async () => {
|
||||
// Arrange
|
||||
(db.query.transactions.findFirst as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.getById(fakeSession as any, 'bad')).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('stores amount as minor units string and emits transaction.created', async () => {
|
||||
// Arrange
|
||||
mockOutboxService.withTransaction.mockImplementationOnce(
|
||||
async (fn: (tx: unknown) => Promise<unknown>) => {
|
||||
const mockTx = {
|
||||
insert: jest.fn().mockReturnValue({
|
||||
values: jest.fn().mockReturnValue({
|
||||
returning: jest.fn().mockResolvedValue([{ ...fakeTx, amountMinorUnits: '150000' }]),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
return fn(mockTx);
|
||||
},
|
||||
);
|
||||
|
||||
// Act
|
||||
const result = await service.create(fakeSession as any, {
|
||||
organizationId: 'org-uuid-1',
|
||||
type: 'invoice',
|
||||
amountMinorUnits: 150000,
|
||||
currency: 'EUR',
|
||||
transactionDate: '2026-01-15',
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.amountMinorUnits).toBe('150000');
|
||||
expect(mockOutboxService.record).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ eventType: 'transaction.created' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('emits transaction.evidence_updated when evidenceStatus changes', async () => {
|
||||
// Arrange
|
||||
(db.query.transactions.findFirst as jest.Mock).mockResolvedValue(fakeTx);
|
||||
mockOutboxService.withTransaction.mockImplementationOnce(
|
||||
async (fn: (tx: unknown) => Promise<unknown>) => {
|
||||
const mockTx = {
|
||||
update: jest.fn().mockReturnValue({
|
||||
set: jest.fn().mockReturnValue({
|
||||
where: jest.fn().mockReturnValue({
|
||||
returning: jest.fn().mockResolvedValue([{ ...fakeTx, evidenceStatus: 'complete' }]),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
return fn(mockTx);
|
||||
},
|
||||
);
|
||||
|
||||
// Act
|
||||
await service.update(fakeSession as any, fakeTx.id, { evidenceStatus: 'complete' });
|
||||
|
||||
// Assert
|
||||
expect(mockOutboxService.record).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ eventType: 'transaction.evidence_updated' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue