From 952f0c92c046a321d3eab07ef98728529551ae60 Mon Sep 17 00:00:00 2001 From: subash-rumsan Date: Thu, 11 Dec 2025 14:45:22 +0545 Subject: [PATCH 1/3] fix: refactor trigger moduls codes --- apps/triggers/src/phases/dto/get.phase.dto.ts | 2 +- apps/triggers/src/phases/phases.service.ts | 4 +- .../dto/activate-trigger-payload.dto.ts | 52 ++ .../src/trigger/dto/create-trigger.dto.ts | 22 +- .../dto/get-by-location-payload.dto.ts | 32 ++ apps/triggers/src/trigger/dto/index.ts | 2 + .../src/trigger/dto/update-trigger.dto.ts | 27 +- .../triggers/src/trigger/trigger.constants.ts | 4 + .../src/trigger/trigger.controller.ts | 57 +- apps/triggers/src/trigger/trigger.service.ts | 530 +++++++----------- .../src/trigger/validation/trigger.schema.ts | 9 +- 11 files changed, 364 insertions(+), 377 deletions(-) create mode 100644 apps/triggers/src/trigger/dto/activate-trigger-payload.dto.ts create mode 100644 apps/triggers/src/trigger/dto/get-by-location-payload.dto.ts create mode 100644 apps/triggers/src/trigger/trigger.constants.ts diff --git a/apps/triggers/src/phases/dto/get.phase.dto.ts b/apps/triggers/src/phases/dto/get.phase.dto.ts index 6e40c5d..dae9bf7 100644 --- a/apps/triggers/src/phases/dto/get.phase.dto.ts +++ b/apps/triggers/src/phases/dto/get.phase.dto.ts @@ -42,7 +42,7 @@ export class GetPhaseByName { example: Phases.PREPAREDNESS, }) @IsEnum(Phases) - @IsNotEmpty() + @IsOptional() phase?: Phases; @ApiProperty({ diff --git a/apps/triggers/src/phases/phases.service.ts b/apps/triggers/src/phases/phases.service.ts index 723d479..a6a3691 100644 --- a/apps/triggers/src/phases/phases.service.ts +++ b/apps/triggers/src/phases/phases.service.ts @@ -744,7 +744,7 @@ export class PhasesService { for (const trigger of phase.Trigger) { const { repeatKey } = trigger; if (trigger.source === DataSource.MANUAL) { - await this.triggerService.create( + await this.triggerService.createTrigger( appId, { title: trigger.title, @@ -756,7 +756,7 @@ export class PhasesService { trigger.createdBy, ); } else { - await this.triggerService.create( + await this.triggerService.createTrigger( appId, { title: trigger.title, diff --git a/apps/triggers/src/trigger/dto/activate-trigger-payload.dto.ts b/apps/triggers/src/trigger/dto/activate-trigger-payload.dto.ts new file mode 100644 index 0000000..a4043a5 --- /dev/null +++ b/apps/triggers/src/trigger/dto/activate-trigger-payload.dto.ts @@ -0,0 +1,52 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsArray } from 'class-validator'; + +export class ActivateTriggerPayloadDto { + @ApiProperty({ + description: 'Trigger UUID', + required: false, + }) + @IsOptional() + @IsString() + uuid?: string; + + @ApiProperty({ + description: 'Trigger repeat key', + required: false, + }) + @IsOptional() + @IsString() + repeatKey?: string; + + @ApiProperty({ + description: 'Application ID', + required: false, + }) + @IsOptional() + @IsString() + appId?: string; + + @ApiProperty({ + description: 'Trigger documents', + required: false, + }) + @IsOptional() + @IsArray() + triggerDocuments?: unknown[]; + + @ApiProperty({ + description: 'User information', + required: false, + }) + @IsOptional() + user?: { name?: string }; + + @ApiProperty({ + description: 'Notes', + required: false, + }) + @IsOptional() + @IsString() + notes?: string; +} + diff --git a/apps/triggers/src/trigger/dto/create-trigger.dto.ts b/apps/triggers/src/trigger/dto/create-trigger.dto.ts index 5a44de4..3a6d470 100644 --- a/apps/triggers/src/trigger/dto/create-trigger.dto.ts +++ b/apps/triggers/src/trigger/dto/create-trigger.dto.ts @@ -139,13 +139,29 @@ export class CreateTriggerDto { riverBasin?: string; } -export class BulkCreateTriggerDto { +export class CreateTriggerPayloadDto { @ApiProperty({ + description: 'User information', + required: false, + }) + @IsOptional() + user?: { name?: string }; + + @ApiProperty({ + description: 'Application ID', + example: 'app-123', + }) + @IsString() + appId: string; + + @ApiProperty({ + description: 'Array of triggers for bulk create', type: [CreateTriggerDto], - description: 'An array of triggers to be created', + required: false, }) + @IsOptional() @IsArray() @ValidateNested({ each: true }) @Type(() => CreateTriggerDto) - triggers: CreateTriggerDto[]; + triggers?: CreateTriggerDto[]; } diff --git a/apps/triggers/src/trigger/dto/get-by-location-payload.dto.ts b/apps/triggers/src/trigger/dto/get-by-location-payload.dto.ts new file mode 100644 index 0000000..e7f94a5 --- /dev/null +++ b/apps/triggers/src/trigger/dto/get-by-location-payload.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsNumber } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class GetByLocationPayloadDto { + @ApiProperty({ + description: 'River basin name', + required: false, + }) + @IsOptional() + @IsString() + riverBasin?: string; + + @ApiProperty({ + description: 'Page number', + required: false, + }) + @IsOptional() + @Type(() => Number) + @IsNumber() + page?: number; + + @ApiProperty({ + description: 'Items per page', + required: false, + }) + @IsOptional() + @Type(() => Number) + @IsNumber() + perPage?: number; +} + diff --git a/apps/triggers/src/trigger/dto/index.ts b/apps/triggers/src/trigger/dto/index.ts index bde2fa1..8ddc5f4 100644 --- a/apps/triggers/src/trigger/dto/index.ts +++ b/apps/triggers/src/trigger/dto/index.ts @@ -1,3 +1,5 @@ export * from './create-trigger.dto'; export * from './update-trigger.dto'; export * from './get-triggers.dto'; +export * from './activate-trigger-payload.dto'; +export * from './get-by-location-payload.dto'; diff --git a/apps/triggers/src/trigger/dto/update-trigger.dto.ts b/apps/triggers/src/trigger/dto/update-trigger.dto.ts index 8ff4c60..2d49541 100644 --- a/apps/triggers/src/trigger/dto/update-trigger.dto.ts +++ b/apps/triggers/src/trigger/dto/update-trigger.dto.ts @@ -1,9 +1,32 @@ -import { PartialType } from '@nestjs/swagger'; +import { ApiProperty, PartialType } from '@nestjs/swagger'; import { CreateTriggerDto } from './create-trigger.dto'; +import { IsString } from 'class-validator'; -export class UpdateTriggerDto extends PartialType(CreateTriggerDto) {} +export class UpdateTriggerPayloadDto extends PartialType(CreateTriggerDto) { + @ApiProperty({ + description: 'Application ID', + }) + @IsString() + appId: string; +} export class UpdateTriggerTransactionDto { + @ApiProperty({ + description: 'Trigger UUID', + }) + @IsString() uuid: string; + + @ApiProperty({ + description: 'Transaction Hash', + }) transactionHash: string; } + +export class RemoveTriggerPayloadDto { + @ApiProperty({ + description: 'Trigger repeat key', + }) + @IsString() + repeatKey: string; +} diff --git a/apps/triggers/src/trigger/trigger.constants.ts b/apps/triggers/src/trigger/trigger.constants.ts new file mode 100644 index 0000000..640e854 --- /dev/null +++ b/apps/triggers/src/trigger/trigger.constants.ts @@ -0,0 +1,4 @@ +export const TRIGGER_CONSTANTS = { + MICROSERVICE_TIMEOUT_SHORT_MS: 3000, + MICROSERVICE_TIMEOUT_LONG_MS: 30000, +} as const; diff --git a/apps/triggers/src/trigger/trigger.controller.ts b/apps/triggers/src/trigger/trigger.controller.ts index c5f1fc6..892d4e5 100644 --- a/apps/triggers/src/trigger/trigger.controller.ts +++ b/apps/triggers/src/trigger/trigger.controller.ts @@ -1,35 +1,28 @@ -import { BadRequestException, Controller } from '@nestjs/common'; +import { Controller, Logger } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { MS_TRIGGERS_JOBS } from 'src/constant'; -import { GetTriggersDto, UpdateTriggerTransactionDto } from './dto'; -import { TriggerService } from './trigger.service'; import { - triggerPayloadSchema, - bulkTriggerPayloadSchema, -} from './validation/trigger.schema'; + GetTriggersDto, + UpdateTriggerTransactionDto, + CreateTriggerPayloadDto, + ActivateTriggerPayloadDto, + UpdateTriggerPayloadDto, + GetByLocationPayloadDto, + RemoveTriggerPayloadDto, +} from './dto'; +import { TriggerService } from './trigger.service'; @Controller('trigger') export class TriggerController { + private readonly logger = new Logger(TriggerController.name); + constructor(private readonly triggerService: TriggerService) {} @MessagePattern({ cmd: MS_TRIGGERS_JOBS.TRIGGER.ADD, }) - async create(payload: any) { - // here we are checking if the payload is an array for bulk create - // also we are checking if the payload is an object as it is may be use for single create in others modules - // we are using here at once because we have to use the same method for different moddules that is job schedule - - const { user, appId, ...rest } = payload; - if (Array.isArray(payload?.triggers)) { - return await this.triggerService.bulkCreate( - appId, - payload.triggers, - user?.name, - ); - } - - return await this.triggerService.create(appId, rest, user?.name); + async create(payload: CreateTriggerPayloadDto) { + return this.triggerService.create(payload as CreateTriggerPayloadDto); } @MessagePattern({ @@ -49,41 +42,35 @@ export class TriggerController { @MessagePattern({ cmd: MS_TRIGGERS_JOBS.TRIGGER.GET_BY_LOCATION, }) - getByLocation(payload): Promise { + getByLocation(payload: GetByLocationPayloadDto): Promise { return this.triggerService.findByLocation(payload); } @MessagePattern({ cmd: MS_TRIGGERS_JOBS.TRIGGER.ACTIVATE, }) - activateTrigger(payload) { - const { uuid, appId, ...dto } = payload; - return this.triggerService.activateTrigger(uuid, appId, dto); + activateTrigger(payload: ActivateTriggerPayloadDto) { + return this.triggerService.activateTrigger(payload); } @MessagePattern({ cmd: MS_TRIGGERS_JOBS.TRIGGER.UPDATE, }) - updateTrigger(payload) { - const { uuid, appId, ...dto } = payload; - return this.triggerService.update(uuid, appId, dto); + updateTrigger(payload: UpdateTriggerPayloadDto) { + return this.triggerService.update(payload); } @MessagePattern({ cmd: MS_TRIGGERS_JOBS.TRIGGER.UPDATE_TRANSCTION, }) updateTriggerTransaction(payload: UpdateTriggerTransactionDto) { - return this.triggerService.updateTransaction( - payload.uuid, - payload.transactionHash, - ); + return this.triggerService.updateTransaction(payload); } @MessagePattern({ cmd: MS_TRIGGERS_JOBS.TRIGGER.REMOVE, }) - remove(payload) { - const { repeatKey } = payload; - return this.triggerService.remove(repeatKey); + remove(payload: RemoveTriggerPayloadDto) { + return this.triggerService.remove(payload); } } diff --git a/apps/triggers/src/trigger/trigger.service.ts b/apps/triggers/src/trigger/trigger.service.ts index 606aa6d..9875d57 100644 --- a/apps/triggers/src/trigger/trigger.service.ts +++ b/apps/triggers/src/trigger/trigger.service.ts @@ -5,7 +5,15 @@ import { Injectable, Logger, } from '@nestjs/common'; -import { CreateTriggerDto, GetTriggersDto, UpdateTriggerDto } from './dto'; +import { + ActivateTriggerPayloadDto, + CreateTriggerDto, + CreateTriggerPayloadDto, + GetTriggersDto, + RemoveTriggerPayloadDto, + UpdateTriggerPayloadDto, + UpdateTriggerTransactionDto, +} from './dto'; import { paginator, PaginatorTypes, @@ -22,6 +30,7 @@ import { AddTriggerJobDto, UpdateTriggerParamsJobDto } from 'src/common/dto'; import { catchError, lastValueFrom, of, timeout } from 'rxjs'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { triggerPayloadSchema } from './validation/trigger.schema'; +import { TRIGGER_CONSTANTS } from './trigger.constants'; const paginate: PaginatorTypes.PaginateFunction = paginator({ perPage: 10 }); @@ -38,178 +47,66 @@ export class TriggerService { private eventEmitter: EventEmitter2, ) {} - async create(appId: string, dto: CreateTriggerDto, createdBy: string) { - this.logger.log(`Creating trigger for app: ${appId}`); - try { - /* - We don't need to create a trigger sperately if source is manual, because, - we are creating a trigger in the phase itself. and phase is linked with datasource - */ - - let trigger = null; - - if (dto.source === 'MANUAL') { - this.logger.log( - `User requested MANUAL Trigger, So creating manul trigger`, - ); - delete dto.triggerDocuments?.type; - trigger = await this.createManualTrigger(appId, dto, createdBy); - } else { - const result = triggerPayloadSchema.safeParse(dto); - console.log(result); - if (!result.success) { - throw new BadRequestException({ - message: `Invalid trigger payload: ${JSON.stringify(result.error.flatten())}`, - }); - } + async create(payload: CreateTriggerPayloadDto) { + const { user, appId, triggers } = payload; - const sanitizedPayload = { - title: dto.title, - description: dto.description, - triggerStatement: dto.triggerStatement, - phaseId: dto.phaseId, - isMandatory: dto.isMandatory, - dataSource: dto.source, - riverBasin: dto.riverBasin, - repeatEvery: '30000', - notes: dto.notes, - createdBy, - }; - trigger = await this.scheduleJob(sanitizedPayload); - } + if (!appId) { + throw new BadRequestException('appId is required'); + } - const queueData: AddTriggerJobDto = { - id: trigger.uuid, - trigger_type: trigger.isMandatory ? 'MANDATORY' : 'OPTIONAL', - phase: trigger.phase.name, - title: trigger.title, - description: trigger.description, - source: trigger.source, - river_basin: trigger.phase.riverBasin, - params: JSON.parse(JSON.stringify(trigger.triggerStatement)), - is_mandatory: trigger.isMandatory, - notes: trigger.notes, - }; + try { + const triggersData = await Promise.all( + triggers.map((item) => this.createTriggerItem(appId, item, user?.name)), + ); - const res = await lastValueFrom( - this.client - .send( - { cmd: JOBS.STELLAR.ADD_ONCHAIN_TRIGGER_QUEUE, uuid: appId }, - { triggers: [queueData] }, - ) - .pipe( - timeout(3000), - catchError((error) => { - if (error.name === 'TimeoutError') { - // Handle timeout specifically - this.logger.error( - `Error while adding trigger onChain, action ${JOBS.STELLAR.ADD_ONCHAIN_TRIGGER_QUEUE} for AA ${appId}, timeout in 3 Seconds`, - ); - return of(null); - } + const queueData: AddTriggerJobDto[] = triggersData.map((trigger) => + this.buildAddTriggerJobDto(trigger), + ); - this.logger.error( - `Error while adding trigger onChain. Action ${JOBS.STELLAR.ADD_ONCHAIN_TRIGGER_QUEUE} for AA ${appId}, error: ${error.message}`, - ); + const res = await this.sendAddTriggerToOnChain(appId, queueData); - return of(null); - }), - ), - ); - if (!res) { - this.logger.warn(`Trigger Statement onChain not added for AA ${appId}`); - } else { - this.logger.log(` - Trigger added to stellar queue action: ${res?.name} with id: ${queueData.id} for AA ${appId} + this.logger.log(` + Total ${triggersData.length} triggers added for action: ${res?.name} to stellar queue for AA ${appId} `); - } - - return trigger; + return triggersData; } catch (error: any) { - console.log(error); - this.logger.error(error); + this.logger.error(`Error in create triggers for app ${appId}:`, error); throw new RpcException(error.message); } } - async bulkCreate(appId: string, payload, createdBy: string) { - try { - const k = await Promise.all( - payload.map(async (item) => { - if (item.source === 'MANUAL') { - this.logger.log( - `User requested MANUAL Trigger, So creating manul trigger`, - ); - return await this.createManualTrigger(appId, item, createdBy); - } - - const sanitizedPayload = { - title: item.title, - triggerStatement: item.triggerStatement, - phaseId: item.phaseId, - isMandatory: item.isMandatory, - source: item.source, - riverBasin: item.riverBasin, - repeatEvery: '30000', - notes: item.notes, - createdBy, - description: item.description, - }; - - return await this.scheduleJob(sanitizedPayload); - }), + private async createTriggerItem( + appId: string, + dto: CreateTriggerDto, + createdBy: string, + ) { + if (dto.source === DataSource.MANUAL) { + this.logger.log( + `User requested MANUAL Trigger, So creating manual trigger`, ); - const queueData: AddTriggerJobDto[] = k.map((trigger) => { - return { - id: trigger.uuid, - trigger_type: trigger.isMandatory ? 'MANDATORY' : 'OPTIONAL', - phase: trigger.phase.name, - title: trigger.title, - description: trigger.description, - source: trigger.source, - river_basin: trigger.phase.riverBasin, - params: JSON.parse(JSON.stringify(trigger.triggerStatement)), - is_mandatory: trigger.isMandatory, - notes: trigger.notes, - }; - }); + // const dtoCopy = { ...dto }; + // delete dtoCopy.triggerDocuments?.type; + return await this.createTrigger(appId, dto, createdBy); + } - const res = await lastValueFrom( - this.client - .send( - { cmd: JOBS.STELLAR.ADD_ONCHAIN_TRIGGER_QUEUE, uuid: appId }, - { triggers: queueData }, - ) - .pipe( - timeout(30000), - catchError((error) => { - this.logger.error( - `Microservice call failed for add trigger onChain:`, - error, - ); - throw error; - }), - ), - ).catch((error) => { - this.logger.error( - `Microservice call failed for add trigger onChain queue:`, - error, - ); - throw error; + const result = triggerPayloadSchema.safeParse(dto); + if (!result.success) { + this.logger.warn( + `Invalid trigger payload: ${JSON.stringify(result.error.flatten())}`, + ); + throw new BadRequestException({ + message: `Invalid trigger payload: ${JSON.stringify(result.error.flatten())}`, }); - - this.logger.log(` - Total ${k.length} triggers added for action: ${res?.name} to stellar queue for AA ${appId} - `); - return k; - } catch (error: any) { - console.log(error); - throw new RpcException(error.message); } + + return await this.createTrigger(appId, dto, createdBy); } - async updateTransaction(uuid: string, transactionHash: string) { - this.logger.log(`Updating trigger trasaction with uuid: ${uuid}`); + async updateTransaction(payload: UpdateTriggerTransactionDto) { + const { uuid, transactionHash } = payload; + this.logger.log( + `Updating trigger transaction hash on trigger with uuid: ${uuid}`, + ); try { const trigger = await this.prisma.trigger.findUnique({ @@ -234,14 +131,26 @@ export class TriggerService { return updatedTrigger; } catch (error: any) { - this.logger.error(error); + this.logger.error( + `Error in updating trigger transaction hash on trigger with uuid: ${uuid}:`, + error, + ); throw new RpcException(error.message); } } - async update(uuid: string, appId: string, payload: UpdateTriggerDto) { + async update(payload: UpdateTriggerPayloadDto) { + const { uuid, appId, ...dto } = payload; + this.logger.log(`Updating trigger with uuid: ${uuid}`); + if (!uuid) { + throw new BadRequestException('uuid is required'); + } + if (!appId) { + throw new BadRequestException('appId is required'); + } + try { const trigger = await this.prisma.trigger.findUnique({ where: { @@ -256,17 +165,17 @@ export class TriggerService { if (trigger.isTriggered) { this.logger.warn( - 'Trigger has already been activated. Connot update, Activated trigger.', + 'Trigger has already been activated. Cannot update an activated trigger.', ); throw new RpcException('Trigger has already been activated.'); } const fields = { - title: payload.title || trigger.title, - triggerStatement: payload.triggerStatement || trigger.triggerStatement, - notes: payload.notes ?? trigger.notes, - description: payload.description ?? trigger.description, - isMandatory: payload.isMandatory ?? trigger.isMandatory, + title: dto.title || trigger.title, + triggerStatement: dto.triggerStatement || trigger.triggerStatement, + notes: dto.notes ?? trigger.notes, + description: dto.description ?? trigger.description, + isMandatory: dto.isMandatory ?? trigger.isMandatory, }; const updatedTrigger = await this.prisma.trigger.update({ @@ -278,42 +187,9 @@ export class TriggerService { }, }); - // Add job in queue to update trigger onChain hash - const queueData: UpdateTriggerParamsJobDto = { - id: updatedTrigger.uuid, - isTriggered: updatedTrigger.isTriggered, - params: JSON.parse(JSON.stringify(updatedTrigger.triggerStatement)), - source: updatedTrigger.source, - }; + const queueData = this.buildUpdateTriggerParamsJobDto(updatedTrigger); - const res = await lastValueFrom( - this.client - .send( - { - cmd: JOBS.STELLAR.UPDATE_ONCHAIN_TRIGGER_PARAMS_QUEUE, - uuid: appId, - }, - { - trigger: queueData, - }, - ) - .pipe( - timeout(30000), - catchError((error) => { - this.logger.error( - `Microservice call failed for update trigger onChain:`, - error, - ); - throw error; - }), - ), - ).catch((error) => { - this.logger.error( - `Microservice call failed for update trigger onChain queue:`, - error, - ); - throw error; - }); + const res = await this.sendUpdateTriggerToOnChain(appId, queueData); this.logger.log(` Trigger added to stellar queue with id: ${res?.name} for AA ${appId} @@ -394,18 +270,8 @@ export class TriggerService { } } - isValidDataSource(value: string): value is DataSource { - return (Object.values(DataSource) as DataSource[]).includes( - value as DataSource, - ); - } - - async createManualTrigger( - appId: string, - dto: CreateTriggerDto, - createdBy: string, - ) { - this.logger.log(`Creating manual trigger for app: ${appId}`); + async createTrigger(appId: string, dto: CreateTriggerDto, createdBy: string) { + this.logger.log(`Creating ${dto.source} trigger for app: ${appId}`); try { const { phaseId, ...rest } = dto; const phase = await this.phasesService.getOne(phaseId); @@ -422,7 +288,7 @@ export class TriggerService { uuid: phaseId, }, }, - source: DataSource.MANUAL, + source: dto.source, isDeleted: false, repeatKey: randomUUID(), createdBy, @@ -442,8 +308,15 @@ export class TriggerService { } } - async remove(repeatKey: string) { + async remove(payload: RemoveTriggerPayloadDto) { + const { repeatKey } = payload; + this.logger.log(`Removing trigger with repeatKey: ${repeatKey}`); + + if (!repeatKey) { + throw new BadRequestException('repeatKey is required'); + } + try { const trigger = await this.prisma.trigger.findUnique({ where: { @@ -454,19 +327,21 @@ export class TriggerService { }); if (!trigger) { - this.logger.error(`Active trigger with id: ${repeatKey} not found.`); - throw new RpcException( - `Active trigger with id: ${repeatKey} not found.`, - ); + this.logger.error(`Trigger with id: ${repeatKey} not found.`); + throw new RpcException(`Trigger with id: ${repeatKey} not found.`); } if (trigger.isTriggered) { - this.logger.error(`Active trigger with id: ${repeatKey} not found.`); + this.logger.error( + `Trigger with id: ${repeatKey} is activated. Cannot remove an activated trigger.`, + ); throw new RpcException(`Cannot remove an activated trigger.`); } if (trigger.phase.isActive) { - this.logger.error(`Active trigger with id: ${repeatKey} not found.`); + this.logger.error( + `Trigger with id: ${repeatKey} is in an active phase. Cannot remove triggers from an active phase.`, + ); throw new RpcException(`Cannot remove triggers from an active phase.`); } @@ -490,7 +365,6 @@ export class TriggerService { // } // } - await this.scheduleQueue.removeRepeatableByKey(repeatKey); const updatedTrigger = await this.prisma.trigger.update({ where: { repeatKey: repeatKey, @@ -500,15 +374,6 @@ export class TriggerService { }, }); - this.triggerQueue.add(JOBS.TRIGGER.REACHED_THRESHOLD, trigger, { - attempts: 3, - removeOnComplete: true, - backoff: { - type: 'exponential', - delay: 1000, - }, - }); - return updatedTrigger; } catch (error: any) { this.logger.error(error); @@ -516,73 +381,19 @@ export class TriggerService { } } - private async scheduleJob(payload: any) { - this.logger.log( - `Scheduling trigger with payload: ${JSON.stringify(payload)}`, - ); - try { - const uuid = randomUUID(); - const { app, source, ...rest } = payload; - - const jobPayload = { - ...rest, - uuid, - }; - - const repeatable = await this.scheduleQueue.add( - JOBS.SCHEDULE.ADD, - jobPayload, - { - jobId: uuid, - attempts: 3, - removeOnComplete: true, - backoff: { - type: 'exponential', - delay: 1000, - }, - repeat: { - every: Number(payload.repeatEvery), - }, - removeOnFail: true, - }, - ); - const repeatableKey = repeatable.opts.repeat.key; - const { phaseId, ...restJob } = jobPayload; - - const createData = { - ...restJob, - repeatKey: repeatableKey, - phase: { - connect: { - uuid: phaseId, - }, - }, - source, - createdBy: payload.createdBy, - isDeleted: false, - }; - const trigger = await this.prisma.trigger.create({ - data: createData, - include: { - phase: true, - }, - }); - this.logger.log(`Trigger created with repeatKey: ${repeatableKey}`); + async activateTrigger(data: ActivateTriggerPayloadDto) { + const { uuid, appId, ...payload } = data; + this.logger.log(`Activating trigger with uuid: ${uuid}`); - return trigger; - return trigger; - } catch (error: any) { - this.logger.error(error); - throw new RpcException(error.message); + if (!uuid && !payload.repeatKey) { + throw new BadRequestException('uuid or repeatKey is required'); } - } - - async activateTrigger(uuid: string, appId: string, payload: any) { - this.logger.log(`Activating trigger with uuid: ${uuid}`); try { - const { triggeredBy, triggerDocuments, user } = payload; - console.log('payload', payload); + const { triggerDocuments, user } = payload; + this.logger.debug( + `Activating trigger with payload: ${JSON.stringify(payload)}`, + ); const trigger = await this.prisma.trigger.findUnique({ where: { @@ -633,12 +444,7 @@ export class TriggerService { }, }); - const jobDetails: UpdateTriggerParamsJobDto = { - id: updatedTrigger.uuid, - isTriggered: updatedTrigger.isTriggered, - params: JSON.parse(JSON.stringify(updatedTrigger.triggerStatement)), - source: updatedTrigger.source, - }; + const jobDetails = this.buildUpdateTriggerParamsJobDto(updatedTrigger); if (trigger.isMandatory) { await this.prisma.phase.update({ @@ -695,34 +501,10 @@ export class TriggerService { return updatedTrigger; } - const res = await lastValueFrom( - this.client - .send( - { - cmd: JOBS.STELLAR.UPDATE_ONCHAIN_TRIGGER_PARAMS_QUEUE, - uuid: appId ? appId : appIds?.app, - }, - { - trigger: jobDetails, - }, - ) - .pipe( - timeout(30000), - catchError((error) => { - this.logger.error( - `Microservice call failed for update trigger onChain:`, - error, - ); - throw error; - }), - ), - ).catch((error) => { - this.logger.error( - `Microservice call failed for update trigger onChain queue:`, - error, - ); - throw error; - }); + const res = await this.sendUpdateTriggerToOnChain( + appId ? appId : appIds?.app, + jobDetails, + ); this.logger.log(` Trigger added to stellar queue with id: ${jobDetails.id}, action: ${res?.name} for appId ${appId} @@ -761,7 +543,6 @@ export class TriggerService { ); } - await this.scheduleQueue.removeRepeatableByKey(repeatKey); const updatedTrigger = await this.prisma.trigger.update({ where: { repeatKey: repeatKey, @@ -827,4 +608,99 @@ export class TriggerService { throw new RpcException(error.message); } } + + private buildAddTriggerJobDto(trigger: any): AddTriggerJobDto { + return { + id: trigger.uuid, + trigger_type: trigger.isMandatory ? 'MANDATORY' : 'OPTIONAL', + phase: trigger.phase.name, + title: trigger.title, + description: trigger.description, + source: trigger.source, + river_basin: trigger.phase.riverBasin, + params: JSON.parse(JSON.stringify(trigger.triggerStatement)), + is_mandatory: trigger.isMandatory, + notes: trigger.notes, + }; + } + + private buildUpdateTriggerParamsJobDto( + trigger: any, + ): UpdateTriggerParamsJobDto { + return { + id: trigger.uuid, + isTriggered: trigger.isTriggered, + params: JSON.parse(JSON.stringify(trigger.triggerStatement)), + source: trigger.source, + }; + } + + private async sendAddTriggerToOnChain( + appId: string, + triggers: AddTriggerJobDto[], + ): Promise { + const timeoutMs = + triggers.length > 1 + ? TRIGGER_CONSTANTS.MICROSERVICE_TIMEOUT_LONG_MS + : TRIGGER_CONSTANTS.MICROSERVICE_TIMEOUT_SHORT_MS; + + return lastValueFrom( + this.client + .send( + { cmd: JOBS.STELLAR.ADD_ONCHAIN_TRIGGER_QUEUE, uuid: appId }, + { triggers }, + ) + .pipe( + timeout(timeoutMs), + catchError((error) => { + if (error.name === 'TimeoutError') { + this.logger.error( + `Error while adding trigger onChain, action ${JOBS.STELLAR.ADD_ONCHAIN_TRIGGER_QUEUE} for AA ${appId}, timeout in ${timeoutMs}ms`, + ); + return of(null); + } + + this.logger.error( + `Error while adding trigger onChain. Action ${JOBS.STELLAR.ADD_ONCHAIN_TRIGGER_QUEUE} for AA ${appId}, error: ${error.message}`, + ); + + return of(null); + }), + ), + ); + } + + private async sendUpdateTriggerToOnChain( + appId: string, + trigger: UpdateTriggerParamsJobDto, + ): Promise { + return lastValueFrom( + this.client + .send( + { + cmd: JOBS.STELLAR.UPDATE_ONCHAIN_TRIGGER_PARAMS_QUEUE, + uuid: appId, + }, + { + trigger, + }, + ) + .pipe( + timeout(TRIGGER_CONSTANTS.MICROSERVICE_TIMEOUT_LONG_MS), + catchError((error) => { + this.logger.error( + `Microservice call failed for update trigger onChain:`, + error, + ); + throw error; + }), + ), + ).catch((error) => { + this.logger.error( + `Microservice call failed for update trigger onChain queue:`, + error, + ); + throw error; + }); + } } diff --git a/apps/triggers/src/trigger/validation/trigger.schema.ts b/apps/triggers/src/trigger/validation/trigger.schema.ts index b6ed788..4a8add9 100644 --- a/apps/triggers/src/trigger/validation/trigger.schema.ts +++ b/apps/triggers/src/trigger/validation/trigger.schema.ts @@ -117,19 +117,14 @@ export const triggerPayloadSchema = z.object({ .transform((val) => (val === null ? undefined : val)), notes: z.string().trim().max(500).optional().default(''), title: z.string().trim().min(3).max(120), - description: z.string().trim().min(3).max(500), + description: z.string().trim().min(3).max(500).optional(), isMandatory: z.boolean().optional().default(false), isTriggered: z.boolean().optional().default(false), isDeleted: z.boolean().optional().default(false), phaseId: z.string().trim().min(1, 'phaseId is required'), - riverBasin: z.string().trim().min(1, 'riverBasin is required'), + riverBasin: z.string().trim().min(1, 'riverBasin is required').optional(), source: z.nativeEnum(DataSource), }); -export const bulkTriggerPayloadSchema = z - .array(triggerPayloadSchema) - .min(1, 'At least one trigger is required'); - export type TriggerPayload = z.infer; export type TriggerStatement = z.infer; -export type BulkTriggerPayload = z.infer; From c164e462a0240523d760542a23a9cccfad909cd0 Mon Sep 17 00:00:00 2001 From: subash-rumsan Date: Thu, 11 Dec 2025 16:12:48 +0545 Subject: [PATCH 2/3] fix: update test cases for trigger controller and trigger service firls --- .../src/trigger/trigger.controller.spec.ts | 186 ++++--- .../src/trigger/trigger.service.spec.ts | 485 ++++-------------- 2 files changed, 207 insertions(+), 464 deletions(-) diff --git a/apps/triggers/src/trigger/trigger.controller.spec.ts b/apps/triggers/src/trigger/trigger.controller.spec.ts index cb280a5..f8cc404 100644 --- a/apps/triggers/src/trigger/trigger.controller.spec.ts +++ b/apps/triggers/src/trigger/trigger.controller.spec.ts @@ -4,7 +4,11 @@ import { TriggerController } from './trigger.controller'; import { TriggerService } from './trigger.service'; import { PhasesService } from 'src/phases/phases.service'; import { CORE_MODULE } from 'src/constant'; -import { GetTriggersDto, UpdateTriggerTransactionDto } from './dto'; +import { + GetTriggersDto, + UpdateTriggerTransactionDto, + GetByLocationPayloadDto, +} from './dto'; import { EventEmitter2 } from '@nestjs/event-emitter'; describe('TriggerController', () => { @@ -115,37 +119,39 @@ describe('TriggerController', () => { }); describe('create', () => { - it('should create single trigger', async () => { + it('should create triggers with payload', async () => { const mockPayload = { user: { name: 'test-user' }, appId: 'app-id', - title: 'Test Trigger', - description: 'Test Description', - source: DataSource.MANUAL, + triggers: [ + { + title: 'Test Trigger', + description: 'Test Description', + source: DataSource.MANUAL, + }, + ], }; - const mockCreatedTrigger = { - uuid: 'trigger-uuid', - title: 'Test Trigger', - description: 'Test Description', - }; + const mockCreatedTriggers = [ + { + uuid: 'trigger-uuid', + title: 'Test Trigger', + description: 'Test Description', + phase: { + name: 'Test Phase', + riverBasin: 'Test Basin', + }, + }, + ]; jest .spyOn(mockTriggerService, 'create') - .mockResolvedValue(mockCreatedTrigger); + .mockResolvedValue(mockCreatedTriggers as any); const result = await controller.create(mockPayload); - expect(mockTriggerService.create).toHaveBeenCalledWith( - 'app-id', - { - title: 'Test Trigger', - description: 'Test Description', - source: DataSource.MANUAL, - }, - 'test-user', - ); - expect(result).toEqual(mockCreatedTrigger); + expect(mockTriggerService.create).toHaveBeenCalledWith(mockPayload); + expect(result).toEqual(mockCreatedTriggers); }); it('should create bulk triggers', async () => { @@ -165,21 +171,31 @@ describe('TriggerController', () => { }; const mockCreatedTriggers = [ - { uuid: 'trigger-1', title: 'Trigger 1' }, - { uuid: 'trigger-2', title: 'Trigger 2' }, + { + uuid: 'trigger-1', + title: 'Trigger 1', + phase: { + name: 'Test Phase', + riverBasin: 'Test Basin', + }, + }, + { + uuid: 'trigger-2', + title: 'Trigger 2', + phase: { + name: 'Test Phase', + riverBasin: 'Test Basin', + }, + }, ]; jest - .spyOn(mockTriggerService, 'bulkCreate') - .mockResolvedValue(mockCreatedTriggers); + .spyOn(mockTriggerService, 'create') + .mockResolvedValue(mockCreatedTriggers as any); const result = await controller.create(mockPayload); - expect(mockTriggerService.bulkCreate).toHaveBeenCalledWith( - 'app-id', - mockPayload.triggers, - 'test-user', - ); + expect(mockTriggerService.create).toHaveBeenCalledWith(mockPayload); expect(result).toEqual(mockCreatedTriggers); }); @@ -187,27 +203,33 @@ describe('TriggerController', () => { const mockPayload = { user: { id: 'user-id', name: 'test-user' }, appId: 'app-id', - title: 'Test Trigger', - source: DataSource.MANUAL, + triggers: [ + { + title: 'Test Trigger', + source: DataSource.MANUAL, + }, + ], }; - const mockCreatedTrigger = { - uuid: 'trigger-uuid', - title: 'Test Trigger', - }; + const mockCreatedTriggers = [ + { + uuid: 'trigger-uuid', + title: 'Test Trigger', + phase: { + name: 'Test Phase', + riverBasin: 'Test Basin', + }, + }, + ]; jest .spyOn(mockTriggerService, 'create') - .mockResolvedValue(mockCreatedTrigger); + .mockResolvedValue(mockCreatedTriggers as any); const result = await controller.create(mockPayload); - expect(mockTriggerService.create).toHaveBeenCalledWith( - 'app-id', - { title: 'Test Trigger', source: DataSource.MANUAL }, - 'test-user', - ); - expect(result).toEqual(mockCreatedTrigger); + expect(mockTriggerService.create).toHaveBeenCalledWith(mockPayload); + expect(result).toEqual(mockCreatedTriggers); }); }); @@ -312,15 +334,28 @@ describe('TriggerController', () => { }); describe('getByLocation', () => { - const mockPayload = { - location: 'Test Location', - appId: 'app-id', + const mockPayload: GetByLocationPayloadDto = { + riverBasin: 'Test Basin', + page: 1, + perPage: 10, }; it('should successfully get triggers by location', async () => { const mockTriggers = [ - { uuid: 'trigger-1', title: 'Trigger 1', location: 'Test Location' }, - { uuid: 'trigger-2', title: 'Trigger 2', location: 'Test Location' }, + { + uuid: 'trigger-1', + title: 'Trigger 1', + phase: { + riverBasin: 'Test Basin', + }, + }, + { + uuid: 'trigger-2', + title: 'Trigger 2', + phase: { + riverBasin: 'Test Basin', + }, + }, ]; jest @@ -336,16 +371,19 @@ describe('TriggerController', () => { }); it('should handle getByLocation with different location', async () => { - const mockPayloadDifferentLocation = { - location: 'Different Location', - appId: 'app-id', + const mockPayloadDifferentLocation: GetByLocationPayloadDto = { + riverBasin: 'Different Basin', + page: 1, + perPage: 10, }; const mockDifferentTriggers = [ { uuid: 'trigger-3', title: 'Trigger 3', - location: 'Different Location', + phase: { + riverBasin: 'Different Basin', + }, }, ]; @@ -368,8 +406,8 @@ describe('TriggerController', () => { const mockPayload = { uuid: 'trigger-uuid', appId: 'app-id', - activatedBy: 'test-user', - activatedAt: new Date(), + user: { name: 'test-user' }, + notes: 'Test notes', }; it('should successfully activate trigger', async () => { @@ -387,9 +425,7 @@ describe('TriggerController', () => { const result = await controller.activateTrigger(mockPayload); expect(mockTriggerService.activateTrigger).toHaveBeenCalledWith( - 'trigger-uuid', - 'app-id', - { activatedBy: 'test-user', activatedAt: expect.any(Date) }, + mockPayload, ); expect(result).toEqual(mockActivatedTrigger); }); @@ -398,10 +434,9 @@ describe('TriggerController', () => { const mockPayloadWithExtra = { uuid: 'trigger-uuid', appId: 'app-id', - activatedBy: 'test-user', - activatedAt: new Date(), - reason: 'Test reason', - metadata: { key: 'value' }, + user: { name: 'test-user' }, + notes: 'Test notes', + triggerDocuments: [{ key: 'value' }], }; const mockActivatedTrigger = { @@ -418,14 +453,7 @@ describe('TriggerController', () => { const result = await controller.activateTrigger(mockPayloadWithExtra); expect(mockTriggerService.activateTrigger).toHaveBeenCalledWith( - 'trigger-uuid', - 'app-id', - { - activatedBy: 'test-user', - activatedAt: expect.any(Date), - reason: 'Test reason', - metadata: { key: 'value' }, - }, + mockPayloadWithExtra, ); expect(result).toEqual(mockActivatedTrigger); }); @@ -452,11 +480,7 @@ describe('TriggerController', () => { const result = await controller.updateTrigger(mockPayload); - expect(mockTriggerService.update).toHaveBeenCalledWith( - 'trigger-uuid', - 'app-id', - { title: 'Updated Trigger', description: 'Updated Description' }, - ); + expect(mockTriggerService.update).toHaveBeenCalledWith(mockPayload); expect(result).toEqual(mockUpdatedTrigger); }); @@ -483,9 +507,7 @@ describe('TriggerController', () => { ); expect(mockTriggerService.update).toHaveBeenCalledWith( - 'trigger-uuid', - 'app-id', - { isMandatory: true, notes: 'Updated notes' }, + mockPayloadWithDifferentData, ); expect(result).toEqual(mockUpdatedTrigger); }); @@ -510,8 +532,7 @@ describe('TriggerController', () => { const result = await controller.updateTriggerTransaction(mockPayload); expect(mockTriggerService.updateTransaction).toHaveBeenCalledWith( - 'trigger-uuid', - 'tx-hash-123', + mockPayload, ); expect(result).toEqual(mockUpdatedTrigger); }); @@ -536,8 +557,7 @@ describe('TriggerController', () => { ); expect(mockTriggerService.updateTransaction).toHaveBeenCalledWith( - 'trigger-uuid', - 'different-tx-hash-456', + mockPayloadWithDifferentHash, ); expect(result).toEqual(mockUpdatedTrigger); }); @@ -560,7 +580,7 @@ describe('TriggerController', () => { const result = await controller.remove(mockPayload); - expect(mockTriggerService.remove).toHaveBeenCalledWith('repeat-key-123'); + expect(mockTriggerService.remove).toHaveBeenCalledWith(mockPayload); expect(result).toEqual(mockRemovedTrigger); }); @@ -581,7 +601,7 @@ describe('TriggerController', () => { const result = await controller.remove(mockPayloadWithDifferentKey); expect(mockTriggerService.remove).toHaveBeenCalledWith( - 'different-repeat-key-456', + mockPayloadWithDifferentKey, ); expect(result).toEqual(mockRemovedTrigger); }); diff --git a/apps/triggers/src/trigger/trigger.service.spec.ts b/apps/triggers/src/trigger/trigger.service.spec.ts index f20787a..5840dc7 100644 --- a/apps/triggers/src/trigger/trigger.service.spec.ts +++ b/apps/triggers/src/trigger/trigger.service.spec.ts @@ -6,7 +6,7 @@ import { of } from 'rxjs'; import { TriggerService } from './trigger.service'; import { PhasesService } from 'src/phases/phases.service'; import { CORE_MODULE, JOBS, EVENTS } from 'src/constant'; -import { CreateTriggerDto, GetTriggersDto, UpdateTriggerDto } from './dto'; +import { GetTriggersDto } from './dto'; import { EventEmitter2 } from '@nestjs/event-emitter'; // Mock the paginator function @@ -23,8 +23,6 @@ describe('TriggerService', () => { let eventEmitter: jest.Mocked; let mockScheduleQueue: jest.Mocked; - let mockTriggerQueue: jest.Mocked; - let mockStellarQueue: jest.Mocked; const mockPrismaServiceImplementation = { trigger: { @@ -130,8 +128,6 @@ describe('TriggerService', () => { mockPhasesService = module.get(PhasesService); eventEmitter = module.get(EventEmitter2); mockScheduleQueue = module.get('BullQueue_SCHEDULE'); - mockTriggerQueue = module.get('BullQueue_TRIGGER'); - mockStellarQueue = module.get('BullQueue_STELLAR'); }); afterEach(() => { @@ -143,15 +139,21 @@ describe('TriggerService', () => { }); describe('create', () => { - const mockCreateTriggerDto: CreateTriggerDto = { - title: 'Test Trigger', - description: 'Test Description', - triggerStatement: { condition: 'test' }, - phaseId: 'phase-uuid', - isMandatory: true, - source: DataSource.MANUAL, - riverBasin: 'Test Basin', - notes: 'Test Notes', + const mockCreateTriggerPayload = { + user: { name: 'user-name' }, + appId: 'app-id', + triggers: [ + { + title: 'Test Trigger', + description: 'Test Description', + triggerStatement: { condition: 'test' }, + phaseId: 'phase-uuid', + isMandatory: true, + source: DataSource.MANUAL, + riverBasin: 'Test Basin', + notes: 'Test Notes', + }, + ], }; const mockCreatedTrigger = { @@ -180,11 +182,7 @@ describe('TriggerService', () => { mockPrismaService.trigger.create.mockResolvedValue(mockCreatedTrigger); mockClientProxy.send.mockReturnValue(of({ name: 'test-action' })); - const result = await service.create( - 'app-id', - mockCreateTriggerDto, - 'user-name', - ); + const result = await service.create(mockCreateTriggerPayload); expect(mockPrismaService.trigger.create).toHaveBeenCalled(); expect(mockClientProxy.send).toHaveBeenCalledWith( @@ -195,82 +193,29 @@ describe('TriggerService', () => { ]), }), ); - expect(result).toEqual(mockCreatedTrigger); + expect(result).toEqual([mockCreatedTrigger]); }); it('should successfully create a non-manual trigger', async () => { - const nonManualDto = { - ...mockCreateTriggerDto, - source: DataSource.DHM, - }; - - // Mock the schedule queue to return a job with repeat key - const mockJob = { - opts: { - repeat: { - key: 'repeat-key-123', + const nonManualPayload = { + ...mockCreateTriggerPayload, + triggers: [ + { + ...mockCreateTriggerPayload.triggers[0], + source: DataSource.DHM, + triggerStatement: { + source: 'water_level_m', + sourceSubType: 'warning_level', + operator: '>', + value: 10, + expression: 'warning_level > 10', + }, + riverBasin: 'Test Basin', }, - }, + ], }; - mockScheduleQueue.add.mockResolvedValue(mockJob as any); - - mockPrismaService.trigger.create.mockResolvedValue(mockCreatedTrigger); - mockClientProxy.send.mockReturnValue(of({ name: 'test-action' })); - - const result = await service.create('app-id', nonManualDto, 'user-name'); - - expect(mockScheduleQueue.add).toHaveBeenCalled(); - expect(mockClientProxy.send).toHaveBeenCalled(); - expect(result).toEqual(mockCreatedTrigger); - }); - - it('should handle create error', async () => { - const error = new Error('Database error'); - mockPrismaService.trigger.create.mockRejectedValue(error); - - await expect( - service.create('app-id', mockCreateTriggerDto, 'user-name'), - ).rejects.toThrow(RpcException); - }); - }); - describe('bulkCreate', () => { - const mockTriggers = [ - { - title: 'Trigger 1', - source: DataSource.MANUAL, - phaseId: 'phase-uuid', - }, - { - title: 'Trigger 2', - source: DataSource.DHM, - phaseId: 'phase-uuid', - }, - ]; - - it('should successfully create multiple triggers', async () => { - const mockCreatedTriggers = [ - { - uuid: 'trigger-1', - title: 'Trigger 1', - isMandatory: true, - source: DataSource.MANUAL, - phase: { name: 'Test Phase', riverBasin: 'Test Basin' }, - triggerStatement: { condition: 'test' }, - notes: 'Test notes', - }, - { - uuid: 'trigger-2', - title: 'Trigger 2', - isMandatory: false, - source: DataSource.DHM, - phase: { name: 'Test Phase', riverBasin: 'Test Basin' }, - triggerStatement: { condition: 'test' }, - notes: 'Test notes', - }, - ]; - - // Mock phase service for manual triggers + // Mock phase service to return a valid phase mockPhasesService.getOne.mockResolvedValue({ id: 1, uuid: 'phase-uuid', @@ -278,55 +223,40 @@ describe('TriggerService', () => { riverBasin: 'Test Basin', } as any); - // Mock schedule queue for non-manual triggers - const mockJob = { - opts: { - repeat: { - key: 'repeat-key-456', - }, - }, - }; - mockScheduleQueue.add.mockResolvedValue(mockJob as any); - - // Mock trigger creation for both manual and non-manual triggers - mockPrismaService.trigger.create - .mockResolvedValueOnce(mockCreatedTriggers[0]) - .mockResolvedValueOnce(mockCreatedTriggers[1]); - + mockPrismaService.trigger.create.mockResolvedValue(mockCreatedTrigger); mockClientProxy.send.mockReturnValue(of({ name: 'test-action' })); - const result = await service.bulkCreate( - 'app-id', - mockTriggers, - 'user-name', - ); + const result = await service.create(nonManualPayload); - expect(result).toEqual(mockCreatedTriggers); + expect(mockClientProxy.send).toHaveBeenCalled(); + expect(result).toEqual([mockCreatedTrigger]); }); - it('should handle bulk create error', async () => { - const error = new Error('Bulk create error'); + it('should handle create error', async () => { + const error = new Error('Database error'); mockPrismaService.trigger.create.mockRejectedValue(error); - await expect( - service.bulkCreate('app-id', mockTriggers, 'user-name'), - ).rejects.toThrow(RpcException); + await expect(service.create(mockCreateTriggerPayload)).rejects.toThrow( + RpcException, + ); }); }); describe('updateTransaction', () => { - const mockUuid = 'trigger-uuid'; - const mockTransactionHash = 'tx-hash-123'; + const mockPayload = { + uuid: 'trigger-uuid', + transactionHash: 'tx-hash-123', + }; it('should successfully update transaction hash', async () => { const mockExistingTrigger = { - uuid: mockUuid, + uuid: 'trigger-uuid', title: 'Existing Trigger', }; const mockUpdatedTrigger = { - uuid: mockUuid, - transactionHash: mockTransactionHash, + uuid: 'trigger-uuid', + transactionHash: 'tx-hash-123', }; mockPrismaService.trigger.findUnique.mockResolvedValue( @@ -334,17 +264,14 @@ describe('TriggerService', () => { ); mockPrismaService.trigger.update.mockResolvedValue(mockUpdatedTrigger); - const result = await service.updateTransaction( - mockUuid, - mockTransactionHash, - ); + const result = await service.updateTransaction(mockPayload); expect(mockPrismaService.trigger.findUnique).toHaveBeenCalledWith({ - where: { uuid: mockUuid }, + where: { uuid: 'trigger-uuid' }, }); expect(mockPrismaService.trigger.update).toHaveBeenCalledWith({ - where: { uuid: mockUuid }, - data: { transactionHash: mockTransactionHash }, + where: { uuid: 'trigger-uuid' }, + data: { transactionHash: 'tx-hash-123' }, }); expect(result).toEqual(mockUpdatedTrigger); }); @@ -352,23 +279,23 @@ describe('TriggerService', () => { it('should handle trigger not found', async () => { mockPrismaService.trigger.findUnique.mockResolvedValue(null); - await expect( - service.updateTransaction(mockUuid, mockTransactionHash), - ).rejects.toThrow(RpcException); + await expect(service.updateTransaction(mockPayload)).rejects.toThrow( + RpcException, + ); }); }); describe('update', () => { - const mockUuid = 'trigger-uuid'; - const mockAppId = 'app-id'; - const mockUpdateTriggerDto: UpdateTriggerDto = { + const mockUpdateTriggerPayload = { + uuid: 'trigger-uuid', + appId: 'app-id', title: 'Updated Trigger', description: 'Updated Description', }; it('should successfully update a trigger', async () => { const mockExistingTrigger = { - uuid: mockUuid, + uuid: 'trigger-uuid', title: 'Existing Trigger', isTriggered: false, triggerStatement: { condition: 'existing' }, @@ -379,7 +306,7 @@ describe('TriggerService', () => { }; const mockUpdatedTrigger = { - uuid: mockUuid, + uuid: 'trigger-uuid', title: 'Updated Trigger', description: 'Updated Description', triggerStatement: { condition: 'existing' }, @@ -393,17 +320,13 @@ describe('TriggerService', () => { mockPrismaService.trigger.update.mockResolvedValue(mockUpdatedTrigger); mockClientProxy.send.mockReturnValue(of({ name: 'test-action' })); - const result = await service.update( - mockUuid, - mockAppId, - mockUpdateTriggerDto, - ); + const result = await service.update(mockUpdateTriggerPayload); expect(mockPrismaService.trigger.findUnique).toHaveBeenCalledWith({ - where: { uuid: mockUuid }, + where: { uuid: 'trigger-uuid' }, }); expect(mockPrismaService.trigger.update).toHaveBeenCalledWith({ - where: { uuid: mockUuid }, + where: { uuid: 'trigger-uuid' }, data: expect.objectContaining({ title: 'Updated Trigger', description: 'Updated Description', @@ -415,14 +338,14 @@ describe('TriggerService', () => { it('should handle trigger not found', async () => { mockPrismaService.trigger.findUnique.mockResolvedValue(null); - await expect( - service.update(mockUuid, mockAppId, mockUpdateTriggerDto), - ).rejects.toThrow(RpcException); + await expect(service.update(mockUpdateTriggerPayload)).rejects.toThrow( + RpcException, + ); }); it('should handle already triggered trigger', async () => { const mockTriggeredTrigger = { - uuid: mockUuid, + uuid: 'trigger-uuid', isTriggered: true, }; @@ -430,9 +353,9 @@ describe('TriggerService', () => { mockTriggeredTrigger, ); - await expect( - service.update(mockUuid, mockAppId, mockUpdateTriggerDto), - ).rejects.toThrow(RpcException); + await expect(service.update(mockUpdateTriggerPayload)).rejects.toThrow( + RpcException, + ); }); }); @@ -457,8 +380,7 @@ describe('TriggerService', () => { }, }; - // Mock the paginate function directly - const mockPaginate = jest.fn().mockResolvedValue(mockPaginatedResult); + // Mock the getAll method directly jest .spyOn(service as any, 'getAll') .mockImplementation(async () => mockPaginatedResult); @@ -500,100 +422,14 @@ describe('TriggerService', () => { }); }); - describe('isValidDataSource', () => { - it('should return true for valid data source', () => { - const result = service.isValidDataSource(DataSource.MANUAL); - expect(result).toBe(true); - }); - - it('should return false for invalid data source', () => { - const result = service.isValidDataSource('INVALID' as DataSource); - expect(result).toBe(false); - }); - }); - - describe('createManualTrigger', () => { - const mockCreateTriggerDto: CreateTriggerDto = { - title: 'Manual Trigger', - description: 'Manual Description', - triggerStatement: { condition: 'manual' }, - phaseId: 'phase-uuid', - isMandatory: true, - source: DataSource.MANUAL, - notes: 'Manual Notes', - }; - - it('should successfully create manual trigger', async () => { - const mockManualTrigger = { - uuid: 'manual-trigger-uuid', - title: 'Manual Trigger', - description: 'Manual Description', - triggerStatement: { condition: 'manual' }, - phase: { - name: 'Manual Phase', - riverBasin: 'Manual Basin', - }, - isMandatory: true, - source: DataSource.MANUAL, - notes: 'Manual Notes', - }; - - mockPhasesService.getOne.mockResolvedValue({ - id: 1, - uuid: 'phase-uuid', - name: 'Manual Phase', - riverBasin: 'Manual Basin', - } as any); - - mockPrismaService.trigger.create.mockResolvedValue(mockManualTrigger); - - const result = await service.createManualTrigger( - 'app-id', - mockCreateTriggerDto, - 'user-name', - ); - - expect(mockPhasesService.getOne).toHaveBeenCalledWith('phase-uuid'); - expect(mockPrismaService.trigger.create).toHaveBeenCalledWith({ - data: expect.objectContaining({ - title: 'Manual Trigger', - description: 'Manual Description', - triggerStatement: { condition: 'manual' }, - isMandatory: true, - source: DataSource.MANUAL, - notes: 'Manual Notes', - phase: { - connect: { - uuid: 'phase-uuid', - }, - }, - }), - include: { - phase: true, - }, - }); - expect(result).toEqual(mockManualTrigger); - }); - - it('should handle phase not found', async () => { - mockPhasesService.getOne.mockResolvedValue(null); - - await expect( - service.createManualTrigger( - 'app-id', - mockCreateTriggerDto, - 'user-name', - ), - ).rejects.toThrow(RpcException); - }); - }); - describe('remove', () => { - const mockRepeatKey = 'repeat-key-123'; + const mockRemovePayload = { + repeatKey: 'repeat-key-123', + }; it('should successfully remove trigger', async () => { const mockTrigger = { - repeatKey: mockRepeatKey, + repeatKey: 'repeat-key-123', isDeleted: false, isTriggered: false, isMandatory: true, @@ -613,30 +449,26 @@ describe('TriggerService', () => { } as any; const mockRemovedTrigger = { - repeatKey: mockRepeatKey, + repeatKey: 'repeat-key-123', isDeleted: true, }; mockPrismaService.trigger.findUnique.mockResolvedValue(mockTrigger); mockPhasesService.getOne.mockResolvedValue(mockPhaseDetail); - mockScheduleQueue.removeRepeatableByKey.mockResolvedValue(undefined); mockPrismaService.trigger.update.mockResolvedValue(mockRemovedTrigger); mockPrismaService.phase.update.mockResolvedValue({}); - const result = await service.remove(mockRepeatKey); + const result = await service.remove(mockRemovePayload); expect(mockPrismaService.trigger.findUnique).toHaveBeenCalledWith({ where: { - repeatKey: mockRepeatKey, + repeatKey: 'repeat-key-123', isDeleted: false, }, include: { phase: true }, }); - expect(mockScheduleQueue.removeRepeatableByKey).toHaveBeenCalledWith( - mockRepeatKey, - ); expect(mockPrismaService.trigger.update).toHaveBeenCalledWith({ - where: { repeatKey: mockRepeatKey }, + where: { repeatKey: 'repeat-key-123' }, data: { isDeleted: true }, }); expect(result).toEqual(mockRemovedTrigger); @@ -645,12 +477,14 @@ describe('TriggerService', () => { it('should handle trigger not found', async () => { mockPrismaService.trigger.findUnique.mockResolvedValue(null); - await expect(service.remove(mockRepeatKey)).rejects.toThrow(RpcException); + await expect(service.remove(mockRemovePayload)).rejects.toThrow( + RpcException, + ); }); it('should handle already triggered trigger', async () => { const mockTriggeredTrigger = { - repeatKey: mockRepeatKey, + repeatKey: 'repeat-key-123', isDeleted: false, isTriggered: true, }; @@ -659,12 +493,14 @@ describe('TriggerService', () => { mockTriggeredTrigger, ); - await expect(service.remove(mockRepeatKey)).rejects.toThrow(RpcException); + await expect(service.remove(mockRemovePayload)).rejects.toThrow( + RpcException, + ); }); it('should throw error when trigger belongs to an active phase', async () => { const mockTrigger = { - repeatKey: mockRepeatKey, + repeatKey: 'repeat-key-123', isDeleted: false, isTriggered: false, isMandatory: true, @@ -676,141 +512,36 @@ describe('TriggerService', () => { mockPrismaService.trigger.findUnique.mockResolvedValue(mockTrigger); - await expect(service.remove(mockRepeatKey)).rejects.toThrow( + await expect(service.remove(mockRemovePayload)).rejects.toThrow( new RpcException('Cannot remove triggers from an active phase.'), ); expect(mockPrismaService.trigger.findUnique).toHaveBeenCalledWith({ where: { - repeatKey: mockRepeatKey, + repeatKey: 'repeat-key-123', isDeleted: false, }, include: { phase: true }, }); // Ensure no further calls are made expect(mockPhasesService.getOne).not.toHaveBeenCalled(); - expect(mockScheduleQueue.removeRepeatableByKey).not.toHaveBeenCalled(); expect(mockPrismaService.trigger.update).not.toHaveBeenCalled(); }); - - it('should add job to trigger queue on successful removal', async () => { - const mockTrigger = { - repeatKey: mockRepeatKey, - isDeleted: false, - isTriggered: false, - isMandatory: true, - phaseId: 'phase-uuid', - phase: { - isActive: false, - }, - }; - - const mockPhaseDetail: any = { - triggerRequirements: { - optionalTriggers: { - totalTriggers: 5, - }, - }, - requiredOptionalTriggers: 3, - }; - - const mockRemovedTrigger = { - repeatKey: mockRepeatKey, - isDeleted: true, - }; - - mockPrismaService.trigger.findUnique.mockResolvedValue(mockTrigger); - mockPhasesService.getOne.mockResolvedValue(mockPhaseDetail); - mockScheduleQueue.removeRepeatableByKey.mockResolvedValue(undefined); - mockPrismaService.trigger.update.mockResolvedValue(mockRemovedTrigger); - mockTriggerQueue.add.mockResolvedValue(undefined); // Mock triggerQueue.add - - const result = await service.remove(mockRepeatKey); - - expect(mockTriggerQueue.add).toHaveBeenCalledWith( - JOBS.TRIGGER.REACHED_THRESHOLD, - mockTrigger, - { - attempts: 3, - removeOnComplete: true, - backoff: { - type: 'exponential', - delay: 1000, - }, - }, - ); - expect(result).toEqual(mockRemovedTrigger); - }); - }); - - describe('scheduleJob', () => { - const mockPayload = { - title: 'Scheduled Trigger', - description: 'Scheduled Description', - triggerStatement: { condition: 'scheduled' }, - phaseId: 'phase-uuid', - isMandatory: false, - dataSource: DataSource.DHM, - riverBasin: 'Scheduled Basin', - repeatEvery: '30000', - notes: 'Scheduled Notes', - createdBy: 'user-name', - }; - - it('should successfully schedule job', async () => { - const mockScheduledTrigger = { - uuid: 'scheduled-trigger-uuid', - title: 'Scheduled Trigger', - description: 'Scheduled Description', - triggerStatement: { condition: 'scheduled' }, - phase: { - name: 'Scheduled Phase', - riverBasin: 'Scheduled Basin', - }, - isMandatory: false, - source: DataSource.DHM, - notes: 'Scheduled Notes', - }; - - const mockJob = { - opts: { - repeat: { - key: 'repeat-key-789', - }, - }, - }; - - mockScheduleQueue.add.mockResolvedValue(mockJob as any); - mockPrismaService.trigger.create.mockResolvedValue(mockScheduledTrigger); - - const result = await service['scheduleJob'](mockPayload); - - expect(mockScheduleQueue.add).toHaveBeenCalledWith( - JOBS.SCHEDULE.ADD, - expect.objectContaining({ - title: 'Scheduled Trigger', - description: 'Scheduled Description', - }), - expect.any(Object), - ); - expect(result).toEqual(mockScheduledTrigger); - }); }); describe('activateTrigger', () => { - const mockUuid = 'trigger-uuid'; - const mockAppId = 'app-id'; - const mockPayload = { - triggeredBy: 'user-name', - activatedAt: new Date(), + const mockActivatePayload = { + uuid: 'trigger-uuid', + appId: 'app-id', user: { name: 'user-name' }, + notes: 'Test notes', }; it('should successfully activate trigger', async () => { const uuid = 'test-uuid'; const mockTrigger = { - uuid: mockUuid, + uuid: 'trigger-uuid', isTriggered: false, source: DataSource.MANUAL, isMandatory: true, @@ -820,7 +551,7 @@ describe('TriggerService', () => { }; const mockActivatedTrigger = { - uuid: mockUuid, + uuid: 'trigger-uuid', isTriggered: true, triggeredBy: 'user-name', triggeredAt: new Date(), @@ -840,14 +571,10 @@ describe('TriggerService', () => { mockPrismaService.activity.findFirst.mockResolvedValue({ app: 'app-id' }); mockClientProxy.send.mockReturnValue(of({ name: 'test-action' })); - const result = await service.activateTrigger( - mockUuid, - mockAppId, - mockPayload, - ); + const result = await service.activateTrigger(mockActivatePayload); expect(mockPrismaService.trigger.findUnique).toHaveBeenCalledWith({ - where: { uuid: mockUuid }, + where: { uuid: 'trigger-uuid' }, include: { phase: { include: { @@ -877,13 +604,13 @@ describe('TriggerService', () => { mockPrismaService.trigger.findUnique.mockResolvedValue(null); await expect( - service.activateTrigger(mockUuid, mockAppId, mockPayload), + service.activateTrigger(mockActivatePayload), ).rejects.toThrow(RpcException); }); it('should handle already triggered trigger', async () => { const mockTriggeredTrigger = { - uuid: mockUuid, + uuid: 'trigger-uuid', isTriggered: true, }; @@ -892,13 +619,13 @@ describe('TriggerService', () => { ); await expect( - service.activateTrigger(mockUuid, mockAppId, mockPayload), + service.activateTrigger(mockActivatePayload), ).rejects.toThrow(RpcException); }); it('should handle automated trigger activation', async () => { const mockAutomatedTrigger = { - uuid: mockUuid, + uuid: 'trigger-uuid', isTriggered: false, source: DataSource.DHM, }; @@ -908,7 +635,7 @@ describe('TriggerService', () => { ); await expect( - service.activateTrigger(mockUuid, mockAppId, mockPayload), + service.activateTrigger(mockActivatePayload), ).rejects.toThrow(RpcException); }); }); @@ -928,7 +655,6 @@ describe('TriggerService', () => { }; mockPrismaService.trigger.findUnique.mockResolvedValue(mockTrigger); - mockScheduleQueue.removeRepeatableByKey.mockResolvedValue(undefined); mockPrismaService.trigger.update.mockResolvedValue(mockArchivedTrigger); const result = await service.archive(mockRepeatKey); @@ -939,9 +665,6 @@ describe('TriggerService', () => { isDeleted: false, }, }); - expect(mockScheduleQueue.removeRepeatableByKey).toHaveBeenCalledWith( - mockRepeatKey, - ); expect(mockPrismaService.trigger.update).toHaveBeenCalledWith({ where: { repeatKey: mockRepeatKey }, data: { isDeleted: true }, From bb5dd54461a72eb72a513aaaa0597c1d7da36935 Mon Sep 17 00:00:00 2001 From: subash-rumsan Date: Mon, 15 Dec 2025 15:29:16 +0545 Subject: [PATCH 3/3] fix: change get and updte trigger base on repeatKey to uuid --- .../src/trigger/dto/get-triggers.dto.ts | 12 +++++- .../src/trigger/dto/update-trigger.dto.ts | 4 +- .../src/trigger/trigger.controller.spec.ts | 18 ++++----- .../src/trigger/trigger.controller.ts | 7 ++-- .../src/trigger/trigger.service.spec.ts | 28 +++++++------- apps/triggers/src/trigger/trigger.service.ts | 38 +++++++++---------- pnpm-lock.yaml | 5 ++- 7 files changed, 63 insertions(+), 49 deletions(-) diff --git a/apps/triggers/src/trigger/dto/get-triggers.dto.ts b/apps/triggers/src/trigger/dto/get-triggers.dto.ts index 4b5fcb9..640b5a8 100644 --- a/apps/triggers/src/trigger/dto/get-triggers.dto.ts +++ b/apps/triggers/src/trigger/dto/get-triggers.dto.ts @@ -2,7 +2,17 @@ import { PartialType } from '@nestjs/swagger'; import { PaginationDto } from 'src/common/dto'; import { ApiProperty } from '@nestjs/swagger'; import { DataSource } from '@lib/database'; -import { IsEnum, IsOptional, IsString } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class findOneTriggerDto { + @ApiProperty({ + example: 'trigger-id', + description: 'The UUID of the trigger to retrieve', + }) + @IsString() + @IsNotEmpty() + uuid: string; +} export class GetTriggersDto extends PartialType(PaginationDto) { @ApiProperty({ diff --git a/apps/triggers/src/trigger/dto/update-trigger.dto.ts b/apps/triggers/src/trigger/dto/update-trigger.dto.ts index 2d49541..8700c6f 100644 --- a/apps/triggers/src/trigger/dto/update-trigger.dto.ts +++ b/apps/triggers/src/trigger/dto/update-trigger.dto.ts @@ -25,8 +25,8 @@ export class UpdateTriggerTransactionDto { export class RemoveTriggerPayloadDto { @ApiProperty({ - description: 'Trigger repeat key', + description: 'Trigger UUID', }) @IsString() - repeatKey: string; + uuid: string; } diff --git a/apps/triggers/src/trigger/trigger.controller.spec.ts b/apps/triggers/src/trigger/trigger.controller.spec.ts index f8cc404..bedb6ca 100644 --- a/apps/triggers/src/trigger/trigger.controller.spec.ts +++ b/apps/triggers/src/trigger/trigger.controller.spec.ts @@ -295,12 +295,12 @@ describe('TriggerController', () => { }; jest - .spyOn(mockTriggerService, 'getOne') + .spyOn(mockTriggerService, 'findOne') .mockResolvedValue(mockTrigger as any); const result = await controller.getOne(mockPayload); - expect(mockTriggerService.getOne).toHaveBeenCalledWith(mockPayload); + expect(mockTriggerService.findOne).toHaveBeenCalledWith(mockPayload); expect(result).toEqual(mockTrigger); }); @@ -321,12 +321,12 @@ describe('TriggerController', () => { }; jest - .spyOn(mockTriggerService, 'getOne') + .spyOn(mockTriggerService, 'findOne') .mockResolvedValue(mockTriggerWithPhase as any); const result = await controller.getOne(mockPayloadWithExtra); - expect(mockTriggerService.getOne).toHaveBeenCalledWith( + expect(mockTriggerService.findOne).toHaveBeenCalledWith( mockPayloadWithExtra, ); expect(result).toEqual(mockTriggerWithPhase); @@ -565,12 +565,12 @@ describe('TriggerController', () => { describe('remove', () => { const mockPayload = { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', }; it('should successfully remove trigger', async () => { const mockRemovedTrigger = { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', isDeleted: true, }; @@ -584,13 +584,13 @@ describe('TriggerController', () => { expect(result).toEqual(mockRemovedTrigger); }); - it('should handle remove with different repeat key', async () => { + it('should handle remove with different uuid', async () => { const mockPayloadWithDifferentKey = { - repeatKey: 'different-repeat-key-456', + uuid: 'different-trigger-uuid', }; const mockRemovedTrigger = { - repeatKey: 'different-repeat-key-456', + uuid: 'different-trigger-uuid', isDeleted: true, }; diff --git a/apps/triggers/src/trigger/trigger.controller.ts b/apps/triggers/src/trigger/trigger.controller.ts index 892d4e5..aca17a2 100644 --- a/apps/triggers/src/trigger/trigger.controller.ts +++ b/apps/triggers/src/trigger/trigger.controller.ts @@ -9,6 +9,7 @@ import { UpdateTriggerPayloadDto, GetByLocationPayloadDto, RemoveTriggerPayloadDto, + findOneTriggerDto, } from './dto'; import { TriggerService } from './trigger.service'; @@ -22,7 +23,7 @@ export class TriggerController { cmd: MS_TRIGGERS_JOBS.TRIGGER.ADD, }) async create(payload: CreateTriggerPayloadDto) { - return this.triggerService.create(payload as CreateTriggerPayloadDto); + return this.triggerService.create(payload); } @MessagePattern({ @@ -35,8 +36,8 @@ export class TriggerController { @MessagePattern({ cmd: MS_TRIGGERS_JOBS.TRIGGER.GET_ONE, }) - getOne(payload: any) { - return this.triggerService.getOne(payload); + getOne(payload: findOneTriggerDto) { + return this.triggerService.findOne(payload); } @MessagePattern({ diff --git a/apps/triggers/src/trigger/trigger.service.spec.ts b/apps/triggers/src/trigger/trigger.service.spec.ts index c987f52..d5aef0c 100644 --- a/apps/triggers/src/trigger/trigger.service.spec.ts +++ b/apps/triggers/src/trigger/trigger.service.spec.ts @@ -393,24 +393,24 @@ describe('TriggerService', () => { }); }); - describe('getOne', () => { + describe('findOne', () => { const mockPayload = { uuid: 'trigger-uuid', }; - it('should successfully get one trigger', async () => { + it('should successfully find one trigger', async () => { const mockTrigger = { uuid: 'trigger-uuid', title: 'Test Trigger', }; - mockPrismaService.trigger.findFirst.mockResolvedValue(mockTrigger); + mockPrismaService.trigger.findUnique.mockResolvedValue(mockTrigger); - const result = await service.getOne(mockPayload); + const result = await service.findOne(mockPayload); - expect(mockPrismaService.trigger.findFirst).toHaveBeenCalledWith({ + expect(mockPrismaService.trigger.findUnique).toHaveBeenCalledWith({ where: { - OR: [{ uuid: 'trigger-uuid' }, { repeatKey: undefined }], + uuid: 'trigger-uuid', }, include: { phase: { @@ -426,12 +426,12 @@ describe('TriggerService', () => { describe('remove', () => { const mockRemovePayload = { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', }; it('should successfully remove trigger', async () => { const mockTrigger = { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', isDeleted: false, isTriggered: false, isMandatory: true, @@ -451,7 +451,7 @@ describe('TriggerService', () => { } as any; const mockRemovedTrigger = { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', isDeleted: true, }; @@ -464,13 +464,13 @@ describe('TriggerService', () => { expect(mockPrismaService.trigger.findUnique).toHaveBeenCalledWith({ where: { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', isDeleted: false, }, include: { phase: true }, }); expect(mockPrismaService.trigger.update).toHaveBeenCalledWith({ - where: { repeatKey: 'repeat-key-123' }, + where: { uuid: 'trigger-uuid' }, data: { isDeleted: true }, }); expect(result).toEqual(mockRemovedTrigger); @@ -486,7 +486,7 @@ describe('TriggerService', () => { it('should handle already triggered trigger', async () => { const mockTriggeredTrigger = { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', isDeleted: false, isTriggered: true, }; @@ -502,7 +502,7 @@ describe('TriggerService', () => { it('should throw error when trigger belongs to an active phase', async () => { const mockTrigger = { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', isDeleted: false, isTriggered: false, isMandatory: true, @@ -520,7 +520,7 @@ describe('TriggerService', () => { expect(mockPrismaService.trigger.findUnique).toHaveBeenCalledWith({ where: { - repeatKey: 'repeat-key-123', + uuid: 'trigger-uuid', isDeleted: false, }, include: { phase: true }, diff --git a/apps/triggers/src/trigger/trigger.service.ts b/apps/triggers/src/trigger/trigger.service.ts index f4500ef..a2fb337 100644 --- a/apps/triggers/src/trigger/trigger.service.ts +++ b/apps/triggers/src/trigger/trigger.service.ts @@ -9,6 +9,7 @@ import { ActivateTriggerPayloadDto, CreateTriggerDto, CreateTriggerPayloadDto, + findOneTriggerDto, GetTriggersDto, RemoveTriggerPayloadDto, UpdateTriggerPayloadDto, @@ -249,13 +250,13 @@ export class TriggerService { } } - async getOne(payload: any) { - const { repeatKey, uuid } = payload; - this.logger.log(`Getting trigger with repeatKey: ${repeatKey}`); + async findOne(payload: findOneTriggerDto) { + const { uuid } = payload; + this.logger.log(`Getting trigger with uuid: ${uuid}`); try { - return await this.prisma.trigger.findFirst({ + return await this.prisma.trigger.findUnique({ where: { - OR: [{ uuid: uuid }, { repeatKey: repeatKey }], + uuid, }, include: { phase: { @@ -310,38 +311,38 @@ export class TriggerService { } async remove(payload: RemoveTriggerPayloadDto) { - const { repeatKey } = payload; + const { uuid } = payload; - this.logger.log(`Removing trigger with repeatKey: ${repeatKey}`); + this.logger.log(`Removing trigger with uuid: ${uuid}`); - if (!repeatKey) { - throw new BadRequestException('repeatKey is required'); + if (!uuid) { + throw new BadRequestException('Uuid is required'); } try { const trigger = await this.prisma.trigger.findUnique({ where: { - repeatKey: repeatKey, + uuid: uuid, isDeleted: false, }, include: { phase: true }, }); if (!trigger) { - this.logger.error(`Trigger with id: ${repeatKey} not found.`); - throw new RpcException(`Trigger with id: ${repeatKey} not found.`); + this.logger.error(`Trigger with id: ${uuid} not found.`); + throw new RpcException(`Trigger with id: ${uuid} not found.`); } if (trigger.isTriggered) { this.logger.error( - `Trigger with id: ${repeatKey} is activated. Cannot remove an activated trigger.`, + `Trigger with id: ${uuid} is activated. Cannot remove an activated trigger.`, ); throw new RpcException(`Cannot remove an activated trigger.`); } if (trigger.phase.isActive) { this.logger.error( - `Trigger with id: ${repeatKey} is in an active phase. Cannot remove triggers from an active phase.`, + `Trigger with id: ${uuid} is in an active phase. Cannot remove triggers from an active phase.`, ); throw new RpcException(`Cannot remove triggers from an active phase.`); } @@ -368,7 +369,7 @@ export class TriggerService { const updatedTrigger = await this.prisma.trigger.update({ where: { - repeatKey: repeatKey, + uuid, }, data: { isDeleted: true, @@ -502,8 +503,8 @@ export class TriggerService { const { uuid, appId, ...payload } = data; this.logger.log(`Activating trigger with uuid: ${uuid}`); - if (!uuid && !payload.repeatKey) { - throw new BadRequestException('uuid or repeatKey is required'); + if (!uuid) { + throw new BadRequestException('uuid is required'); } try { @@ -514,8 +515,7 @@ export class TriggerService { const trigger = await this.prisma.trigger.findUnique({ where: { - ...(payload?.repeatKey && { repeatKey: payload?.repeatKey }), - ...(uuid && { uuid: uuid }), + uuid, }, include: { phase: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e804d0..820a817 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -179,7 +179,7 @@ importers: specifier: ^4.2.0 version: 4.2.0 - apps/mock-api: + apps/forcast-api: dependencies: '@lib/core': specifier: workspace:* @@ -296,6 +296,9 @@ importers: cheerio: specifier: ^1.0.0 version: 1.1.2 + expr-eval: + specifier: ^2.0.2 + version: 2.0.2 google-auth-library: specifier: ^9.12.0 version: 9.15.1