Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/__tests__/dailyTasks/clanTaskProgress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('DailyTasksService.updateClanTask', () => {
}),
} as any,
{} as any,
{} as any,
);
const basicService = {
readOne: jest.fn().mockResolvedValue([
Expand Down
266 changes: 266 additions & 0 deletions src/__tests__/dailyTasks/dailyTaskCancellation.test.ts
Original file line number Diff line number Diff line change
@@ -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] },
});
});
});
5 changes: 5 additions & 0 deletions src/__tests__/dailyTasks/formAnInnerConnection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -60,6 +61,10 @@ describe('FORM_AN_INNER_CONNECTION Daily Task', () => {
handleProgress: jest.fn().mockResolvedValue([true, null]),
},
},
{
provide: PlayerRewarder,
useValue: { deductPlayerPoints: jest.fn() },
},
],
}).compile();

Expand Down
1 change: 1 addition & 0 deletions src/__tests__/dailyTasks/playWithEmotions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/dailyTasks/strongerSoldier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('STRONGER_SOLDIER daily task', () => {
}),
} as any,
progressService as any,
{} as any,
);
const basicService = {
updateOne: jest.fn().mockResolvedValue([true, null]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/dailyTasks/dailyTasks.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading