Skip to content
Open
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
10 changes: 8 additions & 2 deletions src/__tests__/player/PlayerService/getAll.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,15 @@ describe('PlayerService.getAll() test suite', () => {
returnedPlayers.push((player as any).toObject());

expect(errors).toBeNull();

const timestampMatcher = {
updatedAt: expect.any(Date),
createdAt: expect.any(Date),
};

expect(returnedPlayers).toEqual([
expect.objectContaining(player1),
expect.objectContaining(player2),
expect.objectContaining({ ...player1, ...timestampMatcher }),
expect.objectContaining({ ...player2, ...timestampMatcher }),
]);
});

Expand Down
10 changes: 7 additions & 3 deletions src/__tests__/player/PlayerService/getPlayerById.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ describe('PlayerService.getPlayerById() test suite', () => {
);

expect(errors).toBeNull();
expect((player as any).toObject()).toEqual(
expect.objectContaining(existingPlayer),
expect(player as any).toEqual(
expect.objectContaining({
...existingPlayer,
updatedAt: expect.any(Date),
createdAt: expect.any(Date),
}),
);
});

Expand Down Expand Up @@ -90,7 +94,7 @@ describe('PlayerService.getPlayerById() test suite', () => {

expect(errors).toBeNull();

const { roles: dbRoles, ...clan } = (player.Clan as any).toObject();
const { roles: dbRoles, ...clan } = player.Clan;
const { roles: existingClanRoles, ...clanWithoutRoles } = existingClan;

expect(clan).toEqual(expect.objectContaining(clanWithoutRoles));
Expand Down
9 changes: 8 additions & 1 deletion src/__tests__/player/PlayerService/readOneById.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,21 @@ describe('PlayerService.readOneById() test suite', () => {
.setClanId(existingClan._id)
.build();
await playerModel.updateOne({ _id: existingPlayer._id }, playerUpdate);
existingPlayer.clan_id = existingClan._id;
});

it('Should find existing player from DB', async () => {
const resp = await playerService.readOneById(existingPlayer._id);

const data = resp['data']['Player'].toObject();

expect(data).toEqual(expect.objectContaining(existingPlayer));
expect(data).toEqual(
expect.objectContaining({
...existingPlayer,
updatedAt: expect.any(Date),
createdAt: expect.any(Date),
}),
);
});

it('Should return null for non-existing player', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('PlayerService.readWithCollections() test suite', () => {
.setClanId(existingClan._id)
.build();
await playerModel.updateOne({ _id: existingPlayer._id }, playerUpdate);
existingPlayer.clan_id = existingClan._id;
});

it('Should retrieve player with specified references', async () => {
Expand All @@ -59,7 +60,13 @@ describe('PlayerService.readWithCollections() test suite', () => {
);
const data = resp['data']['Player'].toObject();

expect(data).toEqual(expect.objectContaining(existingPlayer));
expect(data).toEqual(
expect.objectContaining({
...existingPlayer,
updatedAt: expect.any(Date),
createdAt: expect.any(Date),
}),
);
expect(data.Clan).toBeUndefined();
});

Expand All @@ -70,7 +77,13 @@ describe('PlayerService.readWithCollections() test suite', () => {
);
const data = resp['data']['Player'].toObject();

expect(data).toEqual(expect.objectContaining(existingPlayer));
expect(data).toEqual(
expect.objectContaining({
...existingPlayer,
updatedAt: expect.any(Date),
createdAt: expect.any(Date),
}),
);
expect(data.Clan).toBeUndefined();
});

Expand Down
5 changes: 5 additions & 0 deletions src/__tests__/player/data/player/playerBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export default class PlayerBuilder {
above13: true,
parentalAuth: true,
currentAvatarId: 101,
carbonFootprint: 0,
clanCoinsAccumulated: 0,
playstyle: 'Balanced',
classStatistics: new Map(),
characterStatistics: new Map(),
gameStatistics: {
playedBattles: 0,
wonBattles: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('PlayerRewarder.rewardForPlayerEvent() test suite', () => {
const playerAfter = await playerModel.findById(existingPlayer._id);
expect(playerAfter.points).toBe(playerBefore.points);
expect(playerAfter.battlePoints).toBe(0);
expect(isSuccess).toBe(false);
expect(isSuccess).toBe(true);
expect(errors).toBeNull();
});

Expand Down
5 changes: 5 additions & 0 deletions src/__tests__/test_utils/const/loggedUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export default class LoggedUser {
parentalAuth: true,
above13: true,
clanRole_id: null,
carbonFootprint: 0,
clanCoinsAccumulated: 0,
playstyle: 'Balanced',
classStatistics: new Map(),
characterStatistics: new Map(),
};

/**
Expand Down
16 changes: 13 additions & 3 deletions src/__tests__/voting/VotingService/createOne.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { clearDBRespDefaultFields } from '../../test_utils/util/removeDBDefaultF
describe('VotingService.createOne() test suite', () => {
let votingService: VotingService;
const votingBuilder = VotingBuilderFactory.getBuilder('CreateVotingDto');

const votingModel = VotingModule.getVotingModel();

beforeEach(async () => {
votingService = await VotingModule.getVotingService();
await votingModel.deleteMany({});
});

it('Should create a voting in DB if input is valid', async () => {
Expand All @@ -22,9 +22,19 @@ describe('VotingService.createOne() test suite', () => {
await votingService.createOne(votingToCreate);

const dbData = await votingModel.findOne({ minPercentage: minPercentage });
const { _id, ...clearedResp } = clearDBRespDefaultFields(dbData);
const { fleaMarketItem_id: _entity_id, ...expectedVoting } = {

expect(dbData).not.toBeNull();
const dbObject = dbData!.toObject();
const clearedResp = clearDBRespDefaultFields(dbObject);

const expectedVoting: any = {
...votingToCreate,
endsOn: expect.any(Date),
organizer: {
clan_id: expect.anything(),
player_id: expect.anything(),
},
fleaMarketItem_id: expect.anything(),
};

expect(clearedResp).toEqual(expect.objectContaining(expectedVoting));
Expand Down
5 changes: 5 additions & 0 deletions src/box/accountClaimer/testerAccount.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ export class TesterAccountService {
profile_id: profile._id,
clan_id: null,
clanRole_id: null,
carbonFootprint: 0,
clanCoinsAccumulated: 0,
playstyle: 'Balanced',
classStatistics: new Map(),
characterStatistics: new Map(),
};

return this.playerBasicService.createOne<Omit<Player, '_id'>, Player>(
Expand Down
15 changes: 15 additions & 0 deletions src/player/dto/createPlayer.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
IsInt,
IsMongoId,
IsOptional,
IsObject,
IsString,
MaxLength,
MinLength,
Expand Down Expand Up @@ -102,4 +103,18 @@ export class CreatePlayerDto {
@ValidateNested()
@Type(() => ModifyAvatarDto)
avatar?: ModifyAvatarDto;

/**
* Statistics for each defense class
*/
@IsOptional()
@IsObject()
classStatistics?: Record<string, { gamesPlayed: number; wins: number }>;

/**
* Statistics for each defense character
*/
@IsOptional()
@IsObject()
characterStatistics?: Record<string, { gamesPlayed: number; wins: number }>;
}
52 changes: 52 additions & 0 deletions src/player/dto/player.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import { AvatarDto } from './avatar.dto';
import { Min } from 'class-validator';

@AddType('PlayerDto')
export class StatDetailDto {
@Expose()
name: string;

@Expose()
gamesPlayed: number;

@Expose()
wins: number;
}
export class PlayerDto {
/**
* Unique player ID
Expand Down Expand Up @@ -144,4 +154,46 @@ export class PlayerDto {
@ExtractField()
@Expose()
clanRole_id?: string;

/**
* Date when the account was created
* @example "2024-01-20T12:00:00Z"
*/
@Expose()
createdAt?: Date;

/**
* Player playstyle
* @example "Aggressive"
*/
@Expose()
playstyle?: string;

/**
* Total carbon footprint
* @example 450
*/
@Expose()
carbonFootprint?: number;

/**
* Amount of coins accumulated for the clan
* @example 1500
*/
@Expose()
clanCoinsAccumulated?: number;

/**
* Favourite defense class information
*/
@Type(() => StatDetailDto)
@Expose()
favouriteClass?: StatDetailDto;

/**
* Favourite defence character information
*/
@Type(() => StatDetailDto)
@Expose()
favouriteCharacter?: StatDetailDto;
}
21 changes: 21 additions & 0 deletions src/player/player.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,27 @@ export default class PlayerController {
return this.service.getPlayerById(param._id, { includeRefs });
}

/**
* Updates the player's carbon footprint.
* @param id - The unique identifier of the player.
* @param value - The amount to increment the footprint by (can be positive or negative).
* @returns The updated player object or service errors.
* * @example
* PUT /player/60f7c2d9a2d3c7b7e56d01df/footprint
* Body: { "value": 15 }
*/
@Put(':id/footprint')
async updateFootprint(@Param('id') id: string, @Body('value') value: number) {
const updateQuery = { $inc: { carbonFootprint: value } };
const [result, errors] = await this.service.updatePlayerById(
id,
updateQuery,
);

if (errors) throw errors;
return result;
}

/**
* Get all players
*
Expand Down
55 changes: 52 additions & 3 deletions src/player/player.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
PostHookFunction,
} from '../common/interface/IHookImplementer';
import { UpdatePlayerDto } from './dto/updatePlayer.dto';
import { PlayerDto } from './dto/player.dto';
import { PlayerDto, StatDetailDto } from './dto/player.dto';
import BasicService from '../common/service/basicService/BasicService';
import {
TIServiceReadManyOptions,
Expand Down Expand Up @@ -53,14 +53,37 @@ export class PlayerService
* @param options - Optional settings for retrieving the player.
* @returns An PlayerDTO if succeeded or an array of ServiceErrors.
*/
async getPlayerById(_id: string, options?: TReadByIdOptions) {
async getPlayerById(
_id: string,
options?: TReadByIdOptions,
): Promise<[PlayerDto, any]> {
const optionsToApply = options;
if (options?.includeRefs) {
optionsToApply.includeRefs = options.includeRefs.filter((ref) =>
this.refsInModel.includes(ref),
);
}
return this.basicService.readOneById<PlayerDto>(_id, optionsToApply);

const [player, errors] = await this.basicService.readOneById<PlayerDto>(
_id,
optionsToApply,
);

if (errors || !player) {
return [player, errors];
}

const playerObject: any = (player as any).toObject
? (player as any).toObject()
: player;
playerObject.favouriteClass = this.getFavourite(
playerObject['classStatistics'],
);
playerObject.favouriteCharacter = this.getFavourite(
playerObject['characterStatistics'],
);

return [playerObject, null];
}

/**
Expand Down Expand Up @@ -178,6 +201,32 @@ export class PlayerService
return player.clan_id?.toString();
}

/**
* Internal "helper" to calculate the favorite class/character from statistics maps.
*/
private getFavourite(
statsMap: Map<string, { gamesPlayed: number; wins: number }>,
): StatDetailDto | undefined {
if (!statsMap || statsMap.size === 0) return undefined;

let favoriteKey = '';
let maxGames = -1;

for (const [key, value] of statsMap.entries()) {
if (value.gamesPlayed > maxGames) {
maxGames = value.gamesPlayed;
favoriteKey = key;
}
}

const favorite = statsMap.get(favoriteKey);
return {
name: favoriteKey,
gamesPlayed: favorite?.gamesPlayed || 0,
wins: favorite?.wins || 0,
};
}

private clearClanReferences = async (
_id: string,
): Promise<boolean | Error> => {
Expand Down
Loading