get candidates in ad repository

This commit is contained in:
sbriat 2023-09-04 10:15:02 +02:00
parent f4097e96eb
commit 717d047aa8
8 changed files with 227 additions and 92 deletions

View File

@ -4,7 +4,7 @@ import {
AdWriteModel,
AdReadModel,
ScheduleItemModel,
AdUnsupportedWriteModel,
AdWriteExtraModel,
} from './infrastructure/ad.repository';
import { v4 } from 'uuid';
import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object';
@ -26,7 +26,7 @@ export class AdMapper
AdEntity,
AdReadModel,
AdWriteModel,
AdUnsupportedWriteModel,
AdWriteExtraModel,
undefined
>
{
@ -119,7 +119,7 @@ export class AdMapper
return entity;
};
toUnsupportedPersistence = (entity: AdEntity): AdUnsupportedWriteModel => ({
toPersistenceExtra = (entity: AdEntity): AdWriteExtraModel => ({
waypoints: this.directionEncoder.encode(entity.getProps().waypoints),
direction: this.directionEncoder.encode(entity.getProps().points),
});

View File

@ -49,7 +49,7 @@ export class CreateAdService implements ICommandHandler {
});
try {
await this.repository.insertWithUnsupportedFields(ad, 'ad');
await this.repository.insertExtra(ad, 'ad');
return ad.id;
} catch (error: any) {
if (error instanceof ConflictException) {

View File

@ -62,8 +62,6 @@ export class PassengerOrientedSelector extends Selector {
si.day,si.time,si.margin
FROM ad LEFT JOIN schedule_item si ON ad.uuid = si."adUuid"
WHERE passenger=True`;
// await this.repository.getCandidates(this.query);
}
export type QueryStringRole = {

View File

@ -9,7 +9,7 @@ import { AdMapper } from '../ad.mapper';
import { ExtendedPrismaRepositoryBase } from '@mobicoop/ddd-library/dist/db/prisma-repository.base';
import { Frequency } from '../core/domain/ad.types';
export type AdBaseModel = {
export type AdModel = {
uuid: string;
driver: boolean;
passenger: boolean;
@ -29,62 +29,42 @@ export type AdBaseModel = {
updatedAt: Date;
};
export type AdReadModel = AdBaseModel & {
export type AdReadModel = AdModel & {
waypoints: string;
schedule: ScheduleItemModel[];
};
export type AdWriteModel = AdBaseModel & {
export type AdWriteModel = AdModel & {
schedule: {
create: ScheduleItemModel[];
};
};
export type AdUnsupportedWriteModel = {
export type AdWriteExtraModel = {
waypoints: string;
direction: string;
};
export type ScheduleItemModel = {
uuid: string;
export type ScheduleItem = {
day: number;
time: Date;
margin: number;
};
export type ScheduleItemModel = ScheduleItem & {
uuid: string;
createdAt: Date;
updatedAt: Date;
};
export type RawAdBaseModel = {
uuid: string;
driver: boolean;
passenger: boolean;
frequency: Frequency;
fromDate: Date;
toDate: Date;
seatsProposed: number;
seatsRequested: number;
strict: boolean;
driverDuration?: number;
driverDistance?: number;
passengerDuration?: number;
passengerDistance?: number;
fwdAzimuth: number;
backAzimuth: number;
export type UngroupedAdModel = AdModel &
ScheduleItem & {
waypoints: string;
};
export type GroupedAdModel = AdModel & {
schedule: ScheduleItem[];
waypoints: string;
createdAt: Date;
updatedAt: Date;
};
export type RawScheduleItemModel = {
day: number;
time: Date;
margin: number;
};
export type RawAdModel = RawAdBaseModel & RawScheduleItemModel;
export type RawAdReadModel = RawAdBaseModel & {
schedule: RawScheduleItemModel[];
};
/**
@ -96,7 +76,7 @@ export class AdRepository
AdEntity,
AdReadModel,
AdWriteModel,
AdUnsupportedWriteModel
AdWriteExtraModel
>
implements AdRepositoryPort
{
@ -121,40 +101,44 @@ export class AdRepository
}
getCandidates = async (queryString: string): Promise<AdReadModel[]> =>
this.toReadModels((await this.queryRawUnsafe(queryString)) as RawAdModel[]);
this.toAdReadModels(
(await this.prismaRaw.$queryRawUnsafe(queryString)) as UngroupedAdModel[],
);
private toReadModels = (rawAds: RawAdModel[]): AdReadModel[] => {
const rawAdReadModels: RawAdReadModel[] = rawAds.map(
(rawAd: RawAdModel) => ({
uuid: rawAd.uuid,
driver: rawAd.driver,
passenger: rawAd.passenger,
frequency: rawAd.frequency,
fromDate: rawAd.fromDate,
toDate: rawAd.toDate,
private toAdReadModels = (
ungroupedAds: UngroupedAdModel[],
): AdReadModel[] => {
const groupedAdModels: GroupedAdModel[] = ungroupedAds.map(
(ungroupedAd: UngroupedAdModel) => ({
uuid: ungroupedAd.uuid,
driver: ungroupedAd.driver,
passenger: ungroupedAd.passenger,
frequency: ungroupedAd.frequency,
fromDate: ungroupedAd.fromDate,
toDate: ungroupedAd.toDate,
schedule: [
{
day: rawAd.day,
time: rawAd.time,
margin: rawAd.margin,
day: ungroupedAd.day,
time: ungroupedAd.time,
margin: ungroupedAd.margin,
},
],
seatsProposed: rawAd.seatsProposed,
seatsRequested: rawAd.seatsRequested,
strict: rawAd.strict,
driverDuration: rawAd.driverDuration,
driverDistance: rawAd.driverDistance,
passengerDuration: rawAd.passengerDuration,
passengerDistance: rawAd.passengerDistance,
fwdAzimuth: rawAd.fwdAzimuth,
backAzimuth: rawAd.backAzimuth,
waypoints: rawAd.waypoints,
createdAt: rawAd.createdAt,
updatedAt: rawAd.updatedAt,
seatsProposed: ungroupedAd.seatsProposed,
seatsRequested: ungroupedAd.seatsRequested,
strict: ungroupedAd.strict,
driverDuration: ungroupedAd.driverDuration,
driverDistance: ungroupedAd.driverDistance,
passengerDuration: ungroupedAd.passengerDuration,
passengerDistance: ungroupedAd.passengerDistance,
fwdAzimuth: ungroupedAd.fwdAzimuth,
backAzimuth: ungroupedAd.backAzimuth,
waypoints: ungroupedAd.waypoints,
createdAt: ungroupedAd.createdAt,
updatedAt: ungroupedAd.updatedAt,
}),
);
const adReadModels: AdReadModel[] = [];
rawAdReadModels.forEach((adReadModel: AdReadModel) => {
groupedAdModels.forEach((adReadModel: AdReadModel) => {
const ad: AdReadModel | undefined = adReadModels.find(
(arm: AdReadModel) => arm.uuid == adReadModel.uuid,
);

View File

@ -4,7 +4,7 @@ import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { Frequency } from '@modules/ad/core/domain/ad.types';
import {
AdReadModel,
AdUnsupportedWriteModel,
AdWriteExtraModel,
AdWriteModel,
} from '@modules/ad/infrastructure/ad.repository';
import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port';
@ -147,8 +147,7 @@ describe('Ad Mapper', () => {
});
it('should map domain entity to unsupported db persistence data', async () => {
const mapped: AdUnsupportedWriteModel =
adMapper.toUnsupportedPersistence(adEntity);
const mapped: AdWriteExtraModel = adMapper.toPersistenceExtra(adEntity);
expect(mapped.waypoints).toBe(
"'LINESTRING(6.1765102 48.689445,2.3522 48.8566)'",
);

View File

@ -48,7 +48,7 @@ const createAdProps: CreateAdProps = {
};
const mockAdRepository = {
insertWithUnsupportedFields: jest
insertExtra: jest
.fn()
.mockImplementationOnce(() => ({}))
.mockImplementationOnce(() => {

View File

@ -42,7 +42,7 @@ const matchQuery = new MatchQuery({
});
const mockMatcherRepository: AdRepositoryPort = {
insertWithUnsupportedFields: jest.fn(),
insertExtra: jest.fn(),
findOneById: jest.fn(),
findOne: jest.fn(),
insert: jest.fn(),
@ -51,7 +51,6 @@ const mockMatcherRepository: AdRepositoryPort = {
delete: jest.fn(),
count: jest.fn(),
healthCheck: jest.fn(),
queryRawUnsafe: jest.fn(),
getCandidates: jest.fn().mockImplementation(() => [
{
ad: {

View File

@ -1,13 +1,18 @@
import {
AD_DIRECTION_ENCODER,
AD_MESSAGE_PUBLISHER,
AD_ROUTE_PROVIDER,
} from '@modules/ad/ad.di-tokens';
import { AdMapper } from '@modules/ad/ad.mapper';
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
import { Frequency } from '@modules/ad/core/domain/ad.types';
import {
AdReadModel,
AdRepository,
} from '@modules/ad/infrastructure/ad.repository';
import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port';
import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { Test, TestingModule } from '@nestjs/testing';
const mockMessagePublisher = {
@ -23,17 +28,146 @@ const mockRouteProvider: RouteProviderPort = {
getBasic: jest.fn(),
};
const mockPrismaService = {
$queryRawUnsafe: jest
.fn()
.mockImplementationOnce(() => {
return [
{
uuid: 'cc260669-1c6d-441f-80a5-19cd59afb777',
driver: true,
passenger: true,
frequency: Frequency.PUNCTUAL,
fromDate: new Date('2023-06-21'),
toDate: new Date('2023-06-21'),
seatsProposed: 3,
seatsRequested: 1,
strict: false,
ddriverDistance: 350000,
driverDuration: 14400,
passengerDistance: 350000,
passengerDuration: 14400,
fwdAzimuth: 273,
backAzimuth: 93,
createdAt: new Date('2023-06-20T17:05:00Z'),
updatedAt: new Date('2023-06-20T17:05:00Z'),
waypoints: 'LINESTRING(6.1765102 48.689445,2.3522 48.8566)',
day: 3,
time: new Date('2023-06-21T07:05:00Z'),
margin: 900,
},
{
uuid: '84af18ff-8779-4cac-9651-1ed5ab0713c4',
driver: true,
passenger: false,
frequency: Frequency.PUNCTUAL,
fromDate: new Date('2023-06-21'),
toDate: new Date('2023-06-21'),
seatsProposed: 3,
seatsRequested: 1,
strict: false,
ddriverDistance: 349000,
driverDuration: 14300,
passengerDistance: 350000,
passengerDuration: 14400,
fwdAzimuth: 273,
backAzimuth: 93,
createdAt: new Date('2023-06-18T14:16:10Z'),
updatedAt: new Date('2023-06-18T14:16:10Z'),
waypoints: 'LINESTRING(6.1765109 48.689455,2.3598 48.8589)',
day: 3,
time: new Date('2023-06-21T07:14:00Z'),
margin: 900,
},
];
})
.mockImplementationOnce(() => {
return [
{
uuid: 'cc260669-1c6d-441f-80a5-19cd59afb777',
driver: true,
passenger: true,
frequency: Frequency.RECURRENT,
fromDate: new Date('2023-06-21'),
toDate: new Date('2024-06-21'),
seatsProposed: 3,
seatsRequested: 1,
strict: false,
ddriverDistance: 350000,
driverDuration: 14400,
passengerDistance: 350000,
passengerDuration: 14400,
fwdAzimuth: 273,
backAzimuth: 93,
createdAt: new Date('2023-06-20T17:05:00Z'),
updatedAt: new Date('2023-06-20T17:05:00Z'),
waypoints: 'LINESTRING(6.1765102 48.689445,2.3522 48.8566)',
day: 3,
time: new Date('2023-06-21T07:05:00Z'),
margin: 900,
},
{
uuid: 'cc260669-1c6d-441f-80a5-19cd59afb777',
driver: true,
passenger: true,
frequency: Frequency.RECURRENT,
fromDate: new Date('2023-06-21'),
toDate: new Date('2024-06-21'),
seatsProposed: 3,
seatsRequested: 1,
strict: false,
ddriverDistance: 350000,
driverDuration: 14400,
passengerDistance: 350000,
passengerDuration: 14400,
fwdAzimuth: 273,
backAzimuth: 93,
createdAt: new Date('2023-06-20T17:05:00Z'),
updatedAt: new Date('2023-06-20T17:05:00Z'),
waypoints: 'LINESTRING(6.1765102 48.689445,2.3522 48.8566)',
day: 4,
time: new Date('2023-06-21T07:15:00Z'),
margin: 900,
},
{
uuid: 'cc260669-1c6d-441f-80a5-19cd59afb777',
driver: true,
passenger: true,
frequency: Frequency.RECURRENT,
fromDate: new Date('2023-06-21'),
toDate: new Date('2024-06-21'),
seatsProposed: 3,
seatsRequested: 1,
strict: false,
ddriverDistance: 350000,
driverDuration: 14400,
passengerDistance: 350000,
passengerDuration: 14400,
fwdAzimuth: 273,
backAzimuth: 93,
createdAt: new Date('2023-06-20T17:05:00Z'),
updatedAt: new Date('2023-06-20T17:05:00Z'),
waypoints: 'LINESTRING(6.1765102 48.689445,2.3522 48.8566)',
day: 5,
time: new Date('2023-06-21T07:16:00Z'),
margin: 900,
},
];
})
.mockImplementationOnce(() => {
return [];
}),
};
describe('Ad repository', () => {
let prismaService: PrismaService;
let adMapper: AdMapper;
let eventEmitter: EventEmitter2;
let adRepository: AdRepository;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [EventEmitterModule.forRoot()],
providers: [
PrismaService,
AdMapper,
AdRepository,
{
provide: AD_DIRECTION_ENCODER,
useValue: mockDirectionEncoder,
@ -42,21 +176,42 @@ describe('Ad repository', () => {
provide: AD_ROUTE_PROVIDER,
useValue: mockRouteProvider,
},
{
provide: AD_MESSAGE_PUBLISHER,
useValue: mockMessagePublisher,
},
{
provide: PrismaService,
useValue: mockPrismaService,
},
],
}).compile();
prismaService = module.get<PrismaService>(PrismaService);
adMapper = module.get<AdMapper>(AdMapper);
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
adRepository = module.get<AdRepository>(AdRepository);
});
it('should be defined', () => {
expect(
new AdRepository(
prismaService,
adMapper,
eventEmitter,
mockMessagePublisher,
),
).toBeDefined();
expect(adRepository).toBeDefined();
});
it('should get candidates if query returns punctual Ads', async () => {
const candidates: AdReadModel[] = await adRepository.getCandidates(
'somePunctualQueryString',
);
expect(candidates.length).toBe(2);
});
it('should get candidates if query returns recurrent Ads', async () => {
const candidates: AdReadModel[] = await adRepository.getCandidates(
'someRecurrentQueryString',
);
expect(candidates.length).toBe(1);
expect(candidates[0].schedule.length).toBe(3);
});
it('should return an empty array of candidates if query does not return Ads', async () => {
const candidates: AdReadModel[] = await adRepository.getCandidates(
'someQueryString',
);
expect(candidates.length).toBe(0);
});
});