improve tests

This commit is contained in:
sbriat 2023-02-08 12:19:52 +01:00
parent a45d91edcc
commit 4e1fb9a8d6
9 changed files with 128 additions and 22 deletions

3
package-lock.json generated
View File

@ -30,7 +30,8 @@
"dotenv-cli": "^6.0.0", "dotenv-cli": "^6.0.0",
"ioredis": "^5.3.0", "ioredis": "^5.3.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0" "rxjs": "^7.2.0",
"uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^9.0.0", "@nestjs/cli": "^9.0.0",

View File

@ -48,7 +48,8 @@
"dotenv-cli": "^6.0.0", "dotenv-cli": "^6.0.0",
"ioredis": "^5.3.0", "ioredis": "^5.3.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0" "rxjs": "^7.2.0",
"uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^9.0.0", "@nestjs/cli": "^9.0.0",

View File

@ -7,7 +7,7 @@ CREATE TABLE "territory" (
"name" TEXT NOT NULL, "name" TEXT NOT NULL,
"shape" geometry NOT NULL, "shape" geometry NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL, "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "territory_pkey" PRIMARY KEY ("uuid") CONSTRAINT "territory_pkey" PRIMARY KEY ("uuid")
); );

View File

@ -17,7 +17,7 @@ model Territory {
name String @unique name String @unique
shape Unsupported("geometry") shape Unsupported("geometry")
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@index([shape], name: "shape_idx", type: Gist) @@index([shape], name: "shape_idx", type: Gist)
@@map("territory") @@map("territory")

View File

@ -1,11 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime'; import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
import { Point } from '../../domain/point.type';
import { DatabaseException } from '../../exceptions/database.exception'; import { DatabaseException } from '../../exceptions/database.exception';
import { ICollection } from '../../interfaces/collection.interface'; import { ICollection } from '../../interfaces/collection.interface';
import { IRepository } from '../../interfaces/repository.interface'; import { IRepository } from '../../interfaces/repository.interface';
import { PrismaService } from './prisma-service'; import { PrismaService } from './prisma-service';
import { Territory } from 'src/modules/territories/domain/entities/territory';
/** /**
* Child classes MUST redefined _model property with appropriate model name * Child classes MUST redefined _model property with appropriate model name
@ -185,23 +183,36 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
} }
} }
async findForPoint(point: Point): Promise<ICollection<T>> { async findAllByQuery(
const strPoint = `SELECT uuid, name FROM ${this._model} WHERE ST_Intersects(ST_GeomFromText('POINT(${point.lon} ${point.lat})',4326),shape) = true`; include: Array<string>,
const territories: Array<T> = await this._prisma.$queryRawUnsafe(strPoint); where: Array<string>,
): Promise<ICollection<T>> {
const query = `SELECT ${include.join(',')} FROM ${
this._model
} WHERE ${where.join(' AND ')}`;
const data: Array<T> = await this._prisma.$queryRawUnsafe(query);
return Promise.resolve({ return Promise.resolve({
data: territories, data,
total: territories.length, total: data.length,
}); });
} }
async createTerritory(territory: Territory): Promise<T> { async createWithFields(fields: object): Promise<number> {
const command = `INSERT INTO ${this._model} VALUES ('bb281075-1b98-4456-89d6-c643d3044a91','${territory.name}', ST_GeomFromGeoJSON('{"type":"MultiPolygon","coordinates":${territory.shape}}'),'2023-02-07 15:58:00','2023-02-07 15:58:00')`; try {
const affectedRowNumber = await this._prisma.$executeRawUnsafe(command); const command = `INSERT INTO ${this._model} (${Object.keys(fields).join(
if (affectedRowNumber == 1) { ',',
return this.findOne({ )}) VALUES (${Object.values(fields).join(',')})`;
name: territory.name, return await this._prisma.$executeRawUnsafe(command);
}); } catch (e) {
if (e instanceof PrismaClientKnownRequestError) {
throw new DatabaseException(
PrismaClientKnownRequestError.name,
e.code,
e.message,
);
} else {
throw new DatabaseException();
}
} }
throw new DatabaseException();
} }
} }

View File

@ -1,3 +1,35 @@
import { PrismaRepository } from '../adapters/secondaries/prisma-repository.abstract'; import { PrismaRepository } from '../adapters/secondaries/prisma-repository.abstract';
import { ICollection } from '../interfaces/collection.interface';
import { Point } from './point.type';
import { v4 as uuidv4 } from 'uuid';
import { Territory } from 'src/modules/territories/domain/entities/territory';
import { DatabaseException } from '../exceptions/database.exception';
export class TerritoryRepository<T> extends PrismaRepository<T> {} export class TerritoryRepository<T> extends PrismaRepository<T> {
async findForPoint(point: Point): Promise<ICollection<T>> {
return await this.findAllByQuery(
['uuid', 'name'],
[
`ST_Intersects(ST_GeomFromText('POINT(${point.lon} ${point.lat})',4326),shape) = true`,
],
);
}
async createTerritory(territory: Territory): Promise<T> {
try {
const affectedRowNumber = await this.createWithFields({
uuid: `'${uuidv4()}'`,
name: `'${territory.name}'`,
shape: `ST_GeomFromGeoJSON('{"type":"MultiPolygon","coordinates":${territory.shape}}')`,
});
if (affectedRowNumber == 1) {
return this.findOne({
name: territory.name,
});
}
throw new DatabaseException();
} catch (e) {
throw e;
}
}
}

View File

@ -57,6 +57,24 @@ const mockPrismaService = {
return Promise.resolve([fakeEntities, fakeEntities.length]); return Promise.resolve([fakeEntities, fakeEntities.length]);
}), }),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
$queryRawUnsafe: jest.fn().mockImplementation((query?: string) => {
return Promise.resolve(fakeEntities);
}),
$executeRawUnsafe: jest
.fn()
.mockResolvedValueOnce(fakeEntityCreated)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.mockImplementationOnce((fields: object) => {
throw new PrismaClientKnownRequestError('unknown request', {
code: 'code',
clientVersion: 'version',
});
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.mockImplementationOnce((fields: object) => {
throw new Error('an unknown error');
}),
fake: { fake: {
create: jest create: jest
.fn() .fn()
@ -422,4 +440,47 @@ describe('PrismaRepository', () => {
).rejects.toBeInstanceOf(DatabaseException); ).rejects.toBeInstanceOf(DatabaseException);
}); });
}); });
describe('findAllByquery', () => {
it('should return an array of entities', async () => {
const entities = await fakeRepository.findAllByQuery(
['uuid', 'name'],
['name is not null'],
);
expect(entities).toStrictEqual({
data: fakeEntities,
total: fakeEntities.length,
});
});
});
describe('createwithFields', () => {
it('should create an entity', async () => {
jest.spyOn(prisma, '$queryRawUnsafe');
const newEntity = await fakeRepository.createWithFields({
uuid: '804319b3-a09b-4491-9f82-7976bfce0aff',
name: 'my-name',
});
expect(newEntity).toBe(fakeEntityCreated);
expect(prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1);
});
it('should throw a DatabaseException for client error', async () => {
await expect(
fakeRepository.createWithFields({
uuid: '804319b3-a09b-4491-9f82-7976bfce0aff',
name: 'my-name',
}),
).rejects.toBeInstanceOf(DatabaseException);
});
it('should throw a DatabaseException if uuid is not found', async () => {
await expect(
fakeRepository.createWithFields({
name: 'my-name',
}),
).rejects.toBeInstanceOf(DatabaseException);
});
});
}); });

View File

@ -104,7 +104,7 @@ export class TerritoriesController {
return this._mapper.map(territory, Territory, TerritoryPresenter); return this._mapper.map(territory, Territory, TerritoryPresenter);
} catch (e) { } catch (e) {
if (e instanceof DatabaseException) { if (e instanceof DatabaseException) {
if (e.message.includes('Already exists')) { if (e.message.includes('already exists')) {
throw new RpcException({ throw new RpcException({
code: 6, code: 6,
message: 'Territory already exists', message: 'Territory already exists',

View File

@ -34,7 +34,7 @@ export class CreateTerritoryUseCase {
return territory; return territory;
} catch (error) { } catch (error) {
let key = 'territory.create.crit'; let key = 'territory.create.crit';
if (error.message.includes('Already exists')) { if (error.message.includes('already exists')) {
key = 'territory.create.warning'; key = 'territory.create.warning';
} }
this._loggingMessager.publish( this._loggingMessager.publish(