288 lines
7.1 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|
|
});
|