import { ResponseBase } from '@libs/api/response.base'; import { PrismaRepositoryBase } from '@libs/db/prisma-repository.base'; import { PrismaService } from '@libs/db/prisma.service'; import { AggregateID, AggregateRoot, Mapper, RepositoryPort } from '@libs/ddd'; import { ConflictException, DatabaseErrorException, NotFoundException, } from '@libs/exceptions'; import { Injectable, Logger } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Test, TestingModule } from '@nestjs/testing'; import { Prisma } from '@prisma/client'; import { v4 } from 'uuid'; interface FakeProps { name: string; } interface CreateFakeProps { name: string; } class FakeEntity extends AggregateRoot { protected readonly _id: AggregateID; static create = (create: CreateFakeProps): FakeEntity => { const id = v4(); const props: FakeProps = { ...create }; const fake = new FakeEntity({ id, props }); return fake; }; validate(): void { // not implemented } } type FakeModel = { uuid: string; name: string; createdAt: Date; updatedAt: Date; }; type FakeRepositoryPort = RepositoryPort; class FakeResponseDto extends ResponseBase { name: string; } const fakeRecord: FakeModel = { uuid: 'd567ea3b-4981-43c9-9449-a409b5fa9fed', name: 'fakeName', createdAt: new Date('2023-06-28T16:02:00Z'), updatedAt: new Date('2023-06-28T16:02:00Z'), }; let recordId = 2; const recordUuid = 'uuid-'; const recordName = 'fakeName-'; const createRandomRecord = (): FakeModel => { const fakeRecord: FakeModel = { uuid: `${recordUuid}${recordId}`, name: `${recordName}${recordId}`, createdAt: new Date('2023-06-30T08:00:00Z'), updatedAt: new Date('2023-06-30T08:00:00Z'), }; recordId++; return fakeRecord; }; const fakeRecords: FakeModel[] = []; Array.from({ length: 10 }).forEach(() => { fakeRecords.push(createRandomRecord()); }); @Injectable() class FakeMapper implements Mapper { toPersistence = (entity: FakeEntity): FakeModel => { const copy = entity.getProps(); const record: FakeModel = { uuid: copy.id, name: copy.name, createdAt: copy.createdAt, updatedAt: copy.updatedAt, }; return record; }; toDomain = (record: FakeModel): FakeEntity => { const entity = new FakeEntity({ id: record.uuid, createdAt: new Date(record.createdAt), updatedAt: new Date(record.updatedAt), props: { name: record.name, }, }); return entity; }; toResponse = (entity: FakeEntity): FakeResponseDto => { const props = entity.getProps(); const response = new FakeResponseDto(entity); response.name = props.name; return response; }; } @Injectable() class FakePrismaService extends PrismaService { fake: any; } const mockPrismaService = { $queryRaw: jest .fn() .mockImplementationOnce(() => { return true; }) .mockImplementation(() => { throw new Prisma.PrismaClientKnownRequestError('Database unavailable', { code: 'code', clientVersion: 'version', }); }) .mockImplementationOnce(() => { throw new Error(); }), fake: { create: jest .fn() .mockResolvedValueOnce(fakeRecord) .mockImplementationOnce(() => { throw new Prisma.PrismaClientKnownRequestError('Already exists', { code: 'code', clientVersion: 'version', }); }) .mockImplementationOnce(() => { throw new Error('An unknown error'); }), findUnique: jest.fn().mockImplementation(async (params?: any) => { let record: FakeModel; if (params?.where?.uuid) { record = fakeRecords.find( (record) => record.uuid === params?.where?.uuid, ); } if (!record && params?.where?.uuid == 'uuid-triggering-error') { throw new Prisma.PrismaClientKnownRequestError('unknown request', { code: 'code', clientVersion: 'version', }); } return record; }), }, }; @Injectable() class FakeRepository extends PrismaRepositoryBase implements FakeRepositoryPort { constructor( prisma: FakePrismaService, mapper: FakeMapper, eventEmitter: EventEmitter2, ) { super( prisma.fake, prisma, mapper, eventEmitter, new Logger(FakeRepository.name), ); } } describe('PrismaRepositoryBase', () => { let fakeRepository: FakeRepository; let prisma: FakePrismaService; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ EventEmitter2, FakeRepository, FakeMapper, { provide: FakePrismaService, useValue: mockPrismaService, }, ], }).compile(); fakeRepository = module.get(FakeRepository); prisma = module.get(FakePrismaService); }); it('should be defined', () => { expect(fakeRepository).toBeDefined(); expect(prisma).toBeDefined(); }); describe('insert', () => { it('should create a record', async () => { jest.spyOn(prisma.fake, 'create'); await fakeRepository.insert( FakeEntity.create({ name: 'someFakeName', }), ); expect(prisma.fake.create).toHaveBeenCalledTimes(1); }); it('should throw a ConflictException if record already exists', async () => { await expect( fakeRepository.insert( FakeEntity.create({ name: 'someFakeName', }), ), ).rejects.toBeInstanceOf(ConflictException); }); it('should throw an Error if an error occurs', async () => { await expect( fakeRepository.insert( FakeEntity.create({ name: 'someFakeName', }), ), ).rejects.toBeInstanceOf(Error); }); }); describe('findOneById', () => { it('should find a record by its id', async () => { const record = await fakeRepository.findOneById('uuid-3'); expect(record.getProps().name).toBe('fakeName-3'); }); it('should throw an Error for client error', async () => { await expect( fakeRepository.findOneById('uuid-triggering-error'), ).rejects.toBeInstanceOf(Error); }); it('should throw a NotFoundException if id is not found', async () => { await expect( fakeRepository.findOneById('wrong-id'), ).rejects.toBeInstanceOf(NotFoundException); }); }); describe('healthCheck', () => { it('should return a healthy result', async () => { const res = await fakeRepository.healthCheck(); expect(res).toBeTruthy(); }); it('should throw an exception if database is not available', async () => { await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf( DatabaseErrorException, ); }); it('should throw a DatabaseErrorException if an error occurs', async () => { await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf( DatabaseErrorException, ); }); }); });