ad/src/libs/tests/unit/db/prisma-repository.base.spec.ts

288 lines
7.1 KiB
TypeScript

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<FakeProps> {
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<FakeEntity>;
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<FakeEntity, FakeModel, FakeModel, FakeResponseDto>
{
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<FakeEntity, FakeModel, FakeModel>
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>(FakeRepository);
prisma = module.get<FakePrismaService>(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,
);
});
});
});