From a331ebb45fb7656a7cb9e648289665d7816cbbc1 Mon Sep 17 00:00:00 2001 From: EemeliJ Date: Thu, 1 Oct 2026 12:24:42 +0300 Subject: [PATCH 1/2] feat(daily-tasks): penalize cancellation and reset released tasks --- .../dailyTasks/clanTaskProgress.test.ts | 1 + .../dailyTasks/dailyTaskCancellation.test.ts | 266 ++++++++++++++++++ .../dailyTasks/formAnInnerConnection.test.ts | 47 +++- .../dailyTasks/playWithEmotions.test.ts | 1 + .../dailyTasks/strongerSoldier.test.ts | 1 + .../rewardForPlayerTask.test.ts | 17 ++ src/dailyTasks/dailyTasks.controller.ts | 2 +- src/dailyTasks/dailyTasks.service.ts | 98 ++++++- .../playerRewarder/playerRewarder.service.ts | 45 +++ 9 files changed, 454 insertions(+), 24 deletions(-) create mode 100644 src/__tests__/dailyTasks/dailyTaskCancellation.test.ts diff --git a/src/__tests__/dailyTasks/clanTaskProgress.test.ts b/src/__tests__/dailyTasks/clanTaskProgress.test.ts index 0b2e90eef..571454c88 100644 --- a/src/__tests__/dailyTasks/clanTaskProgress.test.ts +++ b/src/__tests__/dailyTasks/clanTaskProgress.test.ts @@ -18,6 +18,7 @@ describe('DailyTasksService.updateClanTask', () => { }), } as any, {} as any, + {} as any, ); const basicService = { readOne: jest.fn().mockResolvedValue([ diff --git a/src/__tests__/dailyTasks/dailyTaskCancellation.test.ts b/src/__tests__/dailyTasks/dailyTaskCancellation.test.ts new file mode 100644 index 000000000..d5c5e69eb --- /dev/null +++ b/src/__tests__/dailyTasks/dailyTaskCancellation.test.ts @@ -0,0 +1,266 @@ +import { DailyTasksService } from '../../dailyTasks/dailyTasks.service'; +import DailyTasksCommonModule from './modules/dailyTasksCommon.module'; +import DailyTaskBuilderFactory from './data/dailyTaskBuilderFactory'; +import PlayerModule from '../player/modules/player.module'; +import LoggedUser from '../test_utils/const/loggedUser'; +import { ObjectId } from 'mongodb'; +import ServiceError from '../../common/service/basicService/ServiceError'; +import { SEReason } from '../../common/service/basicService/SEReason'; + +describe('Daily task cancellation penalties', () => { + let dailyTasksService: DailyTasksService; + const playerModel = PlayerModule.getPlayerModel(); + const taskBuilder = DailyTaskBuilderFactory.getBuilder('DailyTask'); + + beforeEach(async () => { + const module = await DailyTasksCommonModule.getModule(); + dailyTasksService = module.get(DailyTasksService); + }); + + async function createReservedTask(options?: { + clanId?: string; + playerId?: string; + amount?: number; + amountLeft?: number; + progress?: object; + }) { + return dailyTasksService.model.create( + taskBuilder + .setClanId(options?.clanId ?? new ObjectId().toString()) + .setPlayerId( + options && 'playerId' in options + ? options.playerId + : LoggedUser.getPlayer()._id, + ) + .setAmount(options?.amount ?? 5) + .setAmountLeft(options?.amountLeft ?? 2) + .build(), + ); + } + + it('unreserves a task as fresh and deducts the cancellation penalty', async () => { + const playerId = LoggedUser.getPlayer()._id; + await playerModel.updateOne({ _id: playerId }, { $set: { points: 15 } }); + const task = await createReservedTask({ + playerId, + amount: 5, + amountLeft: 2, + }); + await dailyTasksService.model.updateOne( + { _id: task._id }, + { $set: { progress: { key: 'partial-progress', steps: [1] } } }, + ); + + const [result, errors] = await dailyTasksService.unreserveTask(playerId); + + expect(errors).toBeNull(); + expect(result).toBe(true); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 5, + }); + expect( + await dailyTasksService.model.findById(task._id).lean(), + ).toMatchObject({ + player_id: null, + startedAt: null, + amount: 5, + amountLeft: 5, + progress: {}, + }); + }); + + it('clamps cancellation points at zero', async () => { + const playerId = LoggedUser.getPlayer()._id; + await playerModel.updateOne({ _id: playerId }, { $set: { points: 5 } }); + await createReservedTask({ playerId }); + + const [, errors] = await dailyTasksService.unreserveTask(playerId); + + expect(errors).toBeNull(); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 0, + }); + }); + + it('deducts once and resets the old task when switching to a new task', async () => { + const playerId = LoggedUser.getPlayer()._id; + const clanId = new ObjectId().toString(); + await playerModel.updateOne({ _id: playerId }, { $set: { points: 15 } }); + const oldTask = await createReservedTask({ + clanId, + playerId, + amount: 5, + amountLeft: 2, + }); + await dailyTasksService.model.updateOne( + { _id: oldTask._id }, + { $set: { progress: { key: 'partial-progress', steps: [1] } } }, + ); + const newTask = await createReservedTask({ clanId, playerId: null }); + + const [, errors] = await dailyTasksService.reserveTask( + playerId, + newTask._id.toString(), + clanId, + ); + + expect(errors).toBeNull(); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 5, + }); + expect( + await dailyTasksService.model.findById(oldTask._id).lean(), + ).toMatchObject({ + player_id: null, + amountLeft: 5, + progress: {}, + }); + expect( + await dailyTasksService.model.findById(newTask._id).lean(), + ).toMatchObject({ + player_id: new ObjectId(playerId), + }); + }); + + it('replaces an owned task and deducts the cancellation penalty', async () => { + const playerId = LoggedUser.getPlayer()._id; + const clanId = new ObjectId().toString(); + await playerModel.updateOne({ _id: playerId }, { $set: { points: 15 } }); + const task = await createReservedTask({ clanId, playerId }); + + const [result, errors] = await dailyTasksService.relinquishTaskById( + task._id.toString(), + clanId, + playerId, + ); + + expect(errors).toBeNull(); + expect(result).toBe(true); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 5, + }); + expect( + await dailyTasksService.model.findById(task._id).lean(), + ).toMatchObject({ + player_id: null, + startedAt: null, + progress: {}, + }); + }); + + it('does not deduct points when the caller does not own the task', async () => { + const playerId = LoggedUser.getPlayer()._id; + const clanId = new ObjectId().toString(); + const otherPlayerId = new ObjectId().toString(); + await playerModel.updateOne({ _id: playerId }, { $set: { points: 15 } }); + const task = await createReservedTask({ clanId, playerId: otherPlayerId }); + + const [result, errors] = await dailyTasksService.relinquishTaskById( + task._id.toString(), + clanId, + playerId, + ); + + expect(result).toBeNull(); + expect(errors).toContainSE_NOT_FOUND(); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 15, + }); + expect( + await dailyTasksService.model.findById(task._id).lean(), + ).toMatchObject({ + player_id: new ObjectId(otherPlayerId), + }); + }); + + it('does not deduct points for a repeated unreserve request', async () => { + const playerId = LoggedUser.getPlayer()._id; + await playerModel.updateOne({ _id: playerId }, { $set: { points: 15 } }); + await createReservedTask({ playerId }); + + const [, firstErrors] = await dailyTasksService.unreserveTask(playerId); + const [secondResult, secondErrors] = + await dailyTasksService.unreserveTask(playerId); + + expect(firstErrors).toBeNull(); + expect(secondResult).toBeNull(); + expect(secondErrors).toContainSE_NOT_FOUND(); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 5, + }); + }); + + it('deducts points only once for concurrent unreserve requests', async () => { + const playerId = LoggedUser.getPlayer()._id; + await playerModel.updateOne({ _id: playerId }, { $set: { points: 15 } }); + await createReservedTask({ playerId }); + + const results = await Promise.all([ + dailyTasksService.unreserveTask(playerId), + dailyTasksService.unreserveTask(playerId), + ]); + + expect(results.filter(([, errors]) => !errors)).toHaveLength(1); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 5, + }); + }); + + it('does not charge a cancellation penalty when replacing a completed task', async () => { + const playerId = LoggedUser.getPlayer()._id; + const clanId = new ObjectId().toString(); + await playerModel.updateOne({ _id: playerId }, { $set: { points: 15 } }); + const task = await createReservedTask({ clanId, playerId }); + + const [, errors] = await dailyTasksService.deleteTask( + task._id.toString(), + clanId, + playerId, + ); + + expect(errors).toBeNull(); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 15, + }); + }); + + it('rolls back the task reset when point deduction fails', async () => { + const playerId = LoggedUser.getPlayer()._id; + await playerModel.updateOne({ _id: playerId }, { $set: { points: 15 } }); + const task = await createReservedTask({ + playerId, + amount: 5, + amountLeft: 2, + }); + await dailyTasksService.model.updateOne( + { _id: task._id }, + { $set: { progress: { key: 'partial-progress', steps: [1] } } }, + ); + jest + .spyOn((dailyTasksService as any).playerRewarder, 'deductPlayerPoints') + .mockResolvedValue([ + null, + [ + new ServiceError({ + reason: SEReason.UNEXPECTED, + message: 'Point deduction failed', + }), + ], + ]); + + const [result, errors] = await dailyTasksService.unreserveTask(playerId); + + expect(result).toBeNull(); + expect(errors).toContainSE_UNEXPECTED(); + expect(await playerModel.findById(playerId).lean()).toMatchObject({ + points: 15, + }); + expect( + await dailyTasksService.model.findById(task._id).lean(), + ).toMatchObject({ + player_id: new ObjectId(playerId), + amountLeft: 2, + progress: { key: 'partial-progress', steps: [1] }, + }); + }); +}); diff --git a/src/__tests__/dailyTasks/formAnInnerConnection.test.ts b/src/__tests__/dailyTasks/formAnInnerConnection.test.ts index d3fa36f55..6a3e75ff7 100644 --- a/src/__tests__/dailyTasks/formAnInnerConnection.test.ts +++ b/src/__tests__/dailyTasks/formAnInnerConnection.test.ts @@ -9,6 +9,7 @@ import { DailyTaskProgressService } from '../../dailyTasks/dailyTaskProgress.ser import { ServerTaskName } from '../../dailyTasks/enum/serverTaskName.enum'; import { ChatEmotion } from '../../chat/enum/chatEmotion.enum'; import { ChatResponseType } from '../../chat/enum/chatResponseType.enum'; +import { PlayerRewarder } from '../../rewarder/playerRewarder/playerRewarder.service'; describe('FORM_AN_INNER_CONNECTION Daily Task', () => { let service: DailyTasksService; @@ -56,7 +57,13 @@ describe('FORM_AN_INNER_CONNECTION Daily Task', () => { }, { provide: DailyTaskProgressService, - useValue: { handleProgress: jest.fn().mockResolvedValue([true, null]) }, + useValue: { + handleProgress: jest.fn().mockResolvedValue([true, null]), + }, + }, + { + provide: PlayerRewarder, + useValue: { deductPlayerPoints: jest.fn() }, }, ], }).compile(); @@ -114,8 +121,12 @@ describe('FORM_AN_INNER_CONNECTION Daily Task', () => { type: ServerTaskName.FORM_AN_INNER_CONNECTION, }; - jest.spyOn(service['basicService'], 'readOne').mockResolvedValue([mockTask as any, null]); - jest.spyOn(service['basicService'], 'updateOne').mockResolvedValue([true, null]); + jest + .spyOn(service['basicService'], 'readOne') + .mockResolvedValue([mockTask as any, null]); + jest + .spyOn(service['basicService'], 'updateOne') + .mockResolvedValue([true, null]); const [res, err] = await service.handleDailyTaskEvent({ playerId: 'player-1', @@ -141,7 +152,9 @@ describe('FORM_AN_INNER_CONNECTION Daily Task', () => { type: ServerTaskName.FORM_AN_INNER_CONNECTION, }; - jest.spyOn(service['basicService'], 'readOne').mockResolvedValue([mockTask as any, null]); + jest + .spyOn(service['basicService'], 'readOne') + .mockResolvedValue([mockTask as any, null]); jest.spyOn(service, 'deleteTask').mockResolvedValue([true as any, null]); const [res, err] = await service.handleDailyTaskEvent({ @@ -166,10 +179,12 @@ describe('FORM_AN_INNER_CONNECTION Daily Task', () => { }); it('returns error when task is not found or owned by another clan', async () => { - jest.spyOn(service['basicService'], 'readOne').mockResolvedValue([ - null, - [{ reason: 'NOT_FOUND', message: 'Task not found' }] as any, - ]); + jest + .spyOn(service['basicService'], 'readOne') + .mockResolvedValue([ + null, + [{ reason: 'NOT_FOUND', message: 'Task not found' }] as any, + ]); const [res, err] = await service.handleDailyTaskEvent({ playerId: 'player-1', @@ -194,11 +209,15 @@ describe('FORM_AN_INNER_CONNECTION Daily Task', () => { type: ServerTaskName.FORM_AN_INNER_CONNECTION, }; - jest.spyOn(service['basicService'], 'readOne').mockResolvedValue([mockTask as any, null]); - jest.spyOn(service['basicService'], 'updateOne').mockResolvedValue([ - null, - [{ reason: 'DATABASE_ERROR', message: 'Write failed' }] as any, - ]); + jest + .spyOn(service['basicService'], 'readOne') + .mockResolvedValue([mockTask as any, null]); + jest + .spyOn(service['basicService'], 'updateOne') + .mockResolvedValue([ + null, + [{ reason: 'DATABASE_ERROR', message: 'Write failed' }] as any, + ]); const [res, err] = await service.handleDailyTaskEvent({ playerId: 'player-1', @@ -212,4 +231,4 @@ describe('FORM_AN_INNER_CONNECTION Daily Task', () => { expect(err).toBeDefined(); }); }); -}); \ No newline at end of file +}); diff --git a/src/__tests__/dailyTasks/playWithEmotions.test.ts b/src/__tests__/dailyTasks/playWithEmotions.test.ts index a6974ec2d..31a025375 100644 --- a/src/__tests__/dailyTasks/playWithEmotions.test.ts +++ b/src/__tests__/dailyTasks/playWithEmotions.test.ts @@ -28,6 +28,7 @@ describe('PLAY_WITH_EMOTIONS daily task', () => { }), } as any, progressService as any, + {} as any, ); const basicService = { updateOne: jest.fn().mockResolvedValue([true, null]), diff --git a/src/__tests__/dailyTasks/strongerSoldier.test.ts b/src/__tests__/dailyTasks/strongerSoldier.test.ts index bb1e64089..bde45c3c5 100644 --- a/src/__tests__/dailyTasks/strongerSoldier.test.ts +++ b/src/__tests__/dailyTasks/strongerSoldier.test.ts @@ -27,6 +27,7 @@ describe('STRONGER_SOLDIER daily task', () => { }), } as any, progressService as any, + {} as any, ); const basicService = { updateOne: jest.fn().mockResolvedValue([true, null]), diff --git a/src/__tests__/rewarder/PlayerRewarder/rewardForPlayerTask.test.ts b/src/__tests__/rewarder/PlayerRewarder/rewardForPlayerTask.test.ts index add68d67d..c5be6e121 100644 --- a/src/__tests__/rewarder/PlayerRewarder/rewardForPlayerTask.test.ts +++ b/src/__tests__/rewarder/PlayerRewarder/rewardForPlayerTask.test.ts @@ -39,6 +39,23 @@ describe('PlayerRewarder.rewardForPlayerTask() test suite', () => { expect(playerAfter.points).toBe(playerBefore.points + pointsToAdd); }); + it('Should deduct regular points and clamp the result at zero', async () => { + await playerModel.updateOne( + { _id: existingPlayer._id }, + { $set: { points: 5 } }, + ); + + const [isSuccess, errors] = await rewarder.deductPlayerPoints( + existingPlayer._id, + 10, + ); + + const playerAfter = await playerModel.findById(existingPlayer._id); + expect(errors).toBeNull(); + expect(isSuccess).toBeTruthy(); + expect(playerAfter.points).toBe(0); + }); + it('Should not update points amount if the specified amount is a negative number and return LESS_THAN_MIN ServiceError', async () => { const pointsToAdd = -10; const playerBefore = await playerModel.findById(existingPlayer._id); diff --git a/src/dailyTasks/dailyTasks.controller.ts b/src/dailyTasks/dailyTasks.controller.ts index e088e084e..8518224fe 100644 --- a/src/dailyTasks/dailyTasks.controller.ts +++ b/src/dailyTasks/dailyTasks.controller.ts @@ -210,7 +210,7 @@ export class DailyTasksController { @UniformResponse(ModelName.DAILY_TASK, DailyTaskDto) async deleteTask(@Param() param: _idDto, @LoggedUser() user: User) { const clanId = await this.playerService.getPlayerClanId(user.player_id); - const [_result, errors] = await this.dailyTasksService.deleteTask( + const [_result, errors] = await this.dailyTasksService.relinquishTaskById( param._id, clanId, user.player_id, diff --git a/src/dailyTasks/dailyTasks.service.ts b/src/dailyTasks/dailyTasks.service.ts index fc88d51c7..b1a6f672d 100644 --- a/src/dailyTasks/dailyTasks.service.ts +++ b/src/dailyTasks/dailyTasks.service.ts @@ -32,6 +32,8 @@ import { ChatEmotion } from '../chat/enum/chatEmotion.enum'; import { ChatResponseType } from '../chat/enum/chatResponseType.enum'; import { StrongerSoldierStep } from './enum/strongerSoldierStep.enum'; import ServiceError from '../common/service/basicService/ServiceError'; +import { PlayerRewarder } from '../rewarder/playerRewarder/playerRewarder.service'; +import { TASK_CONSTS } from './consts/taskConstants'; const isChatEmotion = (emotion: unknown): emotion is ChatEmotion => typeof emotion === 'number' && Object.values(ChatEmotion).includes(emotion); @@ -55,6 +57,7 @@ export class DailyTasksService { private readonly taskQueue: DailyTaskQueue, private readonly taskGenerator: TaskGeneratorService, private readonly progressService: DailyTaskProgressService, + private readonly playerRewarder: PlayerRewarder, ) { this.basicService = new BasicService(model); this.modelName = ModelName.DAILY_TASK; @@ -109,13 +112,14 @@ export class DailyTasksService { if (error) throw error; if (task.player_id && task.player_id !== playerId) return [null, taskReservedError]; + if (task.player_id === playerId) return [task, null]; const [session, initErrors] = await initializeSession(this.connection); if (initErrors) return [null, initErrors]; const [, unreserveError] = await this.unreserveTask(playerId, session); if (unreserveError && unreserveError[0].reason !== SEReason.NOT_FOUND) - await cancelTransaction(session, unreserveError); + return cancelTransaction(session, unreserveError); const startedAt = new Date(); task.player_id = playerId; @@ -126,14 +130,15 @@ export class DailyTasksService { task, { session }, ); - if (updateError) await cancelTransaction(session, updateError); + if (updateError) return cancelTransaction(session, updateError); - await endTransaction(session, task); + const [reservedTask, endErrors] = await endTransaction(session, task); + if (endErrors) return [null, endErrors]; - await this.taskQueue.addDailyTask(task); - this.notifier.taskReceived(playerId, task); + await this.taskQueue.addDailyTask(reservedTask); + this.notifier.taskReceived(playerId, reservedTask); - return [task, null]; + return [reservedTask, null]; } /** @@ -144,10 +149,85 @@ export class DailyTasksService { * @returns A promise that resolves with the result of the update operation. */ async unreserveTask(playerId: string, session?: ClientSession) { - return this.basicService.updateOne( - { $unset: { player_id: '', startedAt: '' } }, + if (session) return this.relinquishActiveTask(playerId, session); + + const [newSession, initErrors] = await initializeSession(this.connection); + if (!newSession) return [null, initErrors]; + + const [wasUnreserved, unreserveErrors] = await this.relinquishActiveTask( + playerId, + newSession, + ); + if (unreserveErrors) return cancelTransaction(newSession, unreserveErrors); + + return endTransaction(newSession, wasUnreserved); + } + + /** + * Releases the player's active task back to the pool in a fresh state and + * applies one cancellation penalty. The caller owns the transaction. + */ + private async relinquishActiveTask(playerId: string, session: ClientSession) { + const [wasUnreserved, unreserveErrors] = await this.basicService.updateOne( + [ + { + $set: { + player_id: null, + startedAt: null, + amountLeft: '$amount', + progress: {}, + }, + }, + ], { filter: { player_id: playerId }, session }, ); + if (unreserveErrors) return [null, unreserveErrors] as const; + + const [, deductionErrors] = await this.playerRewarder.deductPlayerPoints( + playerId, + TASK_CONSTS.POINTS.DAILY_TASK.CANCEL_PENALTY, + session, + ); + if (deductionErrors) return [null, deductionErrors] as const; + + return [wasUnreserved, null] as const; + } + + /** + * Replaces an active player-owned task and applies one cancellation + * penalty. This is intentionally separate from deleteTask, which is also + * used by task-completion flows and must not charge a penalty. + */ + async relinquishTaskById(taskId: string, clanId: string, playerId: string) { + const [session, initErrors] = await initializeSession(this.connection); + if (!session) return [null, initErrors]; + + const newValues = this.taskGenerator.createTaskRandomValues(); + const [, replacementErrors] = await this.basicService.updateOne( + { + $set: { + ...newValues, + amountLeft: newValues.amount, + progress: {}, + player_id: null, + startedAt: null, + }, + }, + { + filter: { _id: taskId, clan_id: clanId, player_id: playerId }, + session, + }, + ); + if (replacementErrors) return cancelTransaction(session, replacementErrors); + + const [, deductionErrors] = await this.playerRewarder.deductPlayerPoints( + playerId, + TASK_CONSTS.POINTS.DAILY_TASK.CANCEL_PENALTY, + session, + ); + if (deductionErrors) return cancelTransaction(session, deductionErrors); + + return endTransaction(session, true); } /** @@ -612,4 +692,4 @@ export class DailyTasksService { async getRewards() { return { prizePool: prizePool }; } -} \ No newline at end of file +} diff --git a/src/rewarder/playerRewarder/playerRewarder.service.ts b/src/rewarder/playerRewarder/playerRewarder.service.ts index 20d6fb9c2..2f3209c30 100644 --- a/src/rewarder/playerRewarder/playerRewarder.service.ts +++ b/src/rewarder/playerRewarder/playerRewarder.service.ts @@ -77,6 +77,51 @@ export class PlayerRewarder { return this.increasePlayerPoints(player_id, points, session); } + /** + * Deducts regular player points without allowing the stored value to fall + * below zero. + * + * @param player_id player _id to update + * @param points positive amount of regular points to deduct + * @param session optional client session for transaction + */ + async deductPlayerPoints( + player_id: string, + points: number, + session?: ClientSession, + ): Promise> { + if (points < 0) + return [ + null, + [ + new ServiceError({ + reason: SEReason.LESS_THAN_MIN, + field: 'points', + value: points, + message: 'Points amount can not be less than 0', + }), + ], + ]; + + const [, errors] = await this.playerService.updateOneById( + player_id, + [ + { + $set: { + points: { + $max: [0, { $subtract: ['$points', points] }], + }, + }, + }, + ], + { session }, + ); + + if (errors) return [null, errors]; + + return [true, null]; + } + /** * Increases specified player's regular points amount. * @param player_id player _id From 4b8c1b50faca4527208ac611db658cd1ffc69d1d Mon Sep 17 00:00:00 2001 From: EemeliJ Date: Fri, 2 Oct 2026 11:45:11 +0300 Subject: [PATCH 2/2] (daily-tasks): await cancellation transaction results --- src/dailyTasks/dailyTasks.service.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/dailyTasks/dailyTasks.service.ts b/src/dailyTasks/dailyTasks.service.ts index b1a6f672d..6563b4fb6 100644 --- a/src/dailyTasks/dailyTasks.service.ts +++ b/src/dailyTasks/dailyTasks.service.ts @@ -119,7 +119,7 @@ export class DailyTasksService { const [, unreserveError] = await this.unreserveTask(playerId, session); if (unreserveError && unreserveError[0].reason !== SEReason.NOT_FOUND) - return cancelTransaction(session, unreserveError); + return await cancelTransaction(session, unreserveError); const startedAt = new Date(); task.player_id = playerId; @@ -130,7 +130,7 @@ export class DailyTasksService { task, { session }, ); - if (updateError) return cancelTransaction(session, updateError); + if (updateError) return await cancelTransaction(session, updateError); const [reservedTask, endErrors] = await endTransaction(session, task); if (endErrors) return [null, endErrors]; @@ -158,9 +158,10 @@ export class DailyTasksService { playerId, newSession, ); - if (unreserveErrors) return cancelTransaction(newSession, unreserveErrors); + if (unreserveErrors) + return await cancelTransaction(newSession, unreserveErrors); - return endTransaction(newSession, wasUnreserved); + return await endTransaction(newSession, wasUnreserved); } /** @@ -218,16 +219,18 @@ export class DailyTasksService { session, }, ); - if (replacementErrors) return cancelTransaction(session, replacementErrors); + if (replacementErrors) + return await cancelTransaction(session, replacementErrors); const [, deductionErrors] = await this.playerRewarder.deductPlayerPoints( playerId, TASK_CONSTS.POINTS.DAILY_TASK.CANCEL_PENALTY, session, ); - if (deductionErrors) return cancelTransaction(session, deductionErrors); + if (deductionErrors) + return await cancelTransaction(session, deductionErrors); - return endTransaction(session, true); + return await endTransaction(session, true); } /**