Skip to content

Commit

Permalink
test(acceptance): sheriff player attribute acceptance tests (#472)
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinezanardi committed Sep 8, 2023
1 parent 49dd67d commit ed9e09e
Show file tree
Hide file tree
Showing 22 changed files with 65,879 additions and 58,650 deletions.
4 changes: 2 additions & 2 deletions src/modules/game/helpers/game.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function getPlayersWithCurrentSide(game: Game, side: RoleSides): Player[] {
}

function getPlayerWithId(game: Game, id: Types.ObjectId): Player | undefined {
return game.players.find(({ _id }) => _id.toString() === id.toString());
return game.players.find(({ _id }) => _id.equals(id));
}

function getPlayerWithIdOrThrow(playerId: Types.ObjectId, game: Game, exception: Error): Player {
Expand Down Expand Up @@ -145,7 +145,7 @@ function getFoxSniffedPlayers(sniffedTargetId: Types.ObjectId, game: Game): Play
const rightAliveNeighbor = getNearestAliveNeighbor(sniffedTarget._id, game, { direction: "right" });
const sniffedTargets = [leftAliveNeighbor, sniffedTarget, rightAliveNeighbor].filter((player): player is Player => !!player);
return sniffedTargets.reduce<Player[]>((acc, target) => {
if (!acc.some(uniqueTarget => uniqueTarget._id.toString() === target._id.toString())) {
if (!acc.some(uniqueTarget => uniqueTarget._id.equals(target._id))) {
return [...acc, target];
}
return acc;
Expand Down
2 changes: 1 addition & 1 deletion src/modules/game/helpers/game.mutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function addPlayerAttributeInGame(playerId: Types.ObjectId, game: Game, attribut
function addPlayersAttributeInGame(playerIds: Types.ObjectId[], game: Game, attribute: PlayerAttribute): Game {
const clonedGame = createGame(game);
clonedGame.players = clonedGame.players.map(player => {
if (playerIds.includes(player._id)) {
if (playerIds.find(playerId => playerId.equals(player._id))) {
player.attributes.push(createPlayerAttribute(attribute));
}
return player;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class GamePhaseService {
clonedGame.turn++;
}
const upcomingNightPlays = await this.gamePlayService.getUpcomingNightPlays(clonedGame);
const upcomingDayPlays = this.gamePlayService.getUpcomingDayPlays();
const upcomingDayPlays = this.gamePlayService.getUpcomingDayPlays(clonedGame);
const phaseUpcomingPlays = clonedGame.phase === GamePhases.NIGHT ? upcomingNightPlays : upcomingDayPlays;
clonedGame.upcomingPlays = [...clonedGame.upcomingPlays, ...phaseUpcomingPlays];
return clonedGame;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export class GamePlayValidatorService {
this.validateGamePlayTargetsBoundaries(playTargets, { min: 1, max: 1 });
const seerPlayer = getPlayerWithCurrentRole(game, RoleNames.SEER);
const targetedPlayer = playTargets[0].player;
if (!targetedPlayer.isAlive || targetedPlayer._id === seerPlayer?._id) {
if (!targetedPlayer.isAlive || seerPlayer?._id.equals(targetedPlayer._id) === true) {
throw new BadGamePlayPayloadException(BadGamePlayPayloadReasons.BAD_SEER_TARGET);
}
}
Expand All @@ -193,7 +193,7 @@ export class GamePlayValidatorService {
this.validateGamePlayTargetsBoundaries(playTargets, { min: 1, max: 1 });
const wildChildPlayer = getPlayerWithCurrentRole(game, RoleNames.WILD_CHILD);
const targetedPlayer = playTargets[0].player;
if (!targetedPlayer.isAlive || targetedPlayer._id === wildChildPlayer?._id) {
if (!targetedPlayer.isAlive || wildChildPlayer?._id.equals(targetedPlayer._id) === true) {
throw new BadGamePlayPayloadException(BadGamePlayPayloadReasons.BAD_WILD_CHILD_TARGET);
}
}
Expand All @@ -204,7 +204,7 @@ export class GamePlayValidatorService {
const leftToCharmByPiedPiperPlayersCount = leftToCharmByPiedPiperPlayers.length;
const countToCharm = Math.min(charmedPeopleCountPerNight, leftToCharmByPiedPiperPlayersCount);
this.validateGamePlayTargetsBoundaries(playTargets, { min: countToCharm, max: countToCharm });
if (playTargets.some(({ player }) => !leftToCharmByPiedPiperPlayers.find(({ _id }) => player._id === _id))) {
if (playTargets.some(({ player }) => !leftToCharmByPiedPiperPlayers.find(({ _id }) => player._id.equals(_id)))) {
throw new BadGamePlayPayloadException(BadGamePlayPayloadReasons.BAD_PIED_PIPER_TARGETS);
}
}
Expand All @@ -215,7 +215,7 @@ export class GamePlayValidatorService {
const lastGuardHistoryRecord = await this.gameHistoryRecordService.getLastGameHistoryGuardProtectsRecord(game._id);
const lastProtectedPlayer = lastGuardHistoryRecord?.play.targets?.[0].player;
const targetedPlayer = playTargets[0].player;
if (!targetedPlayer.isAlive || !canProtectTwice && lastProtectedPlayer?._id === targetedPlayer._id) {
if (!targetedPlayer.isAlive || !canProtectTwice && lastProtectedPlayer?._id.equals(targetedPlayer._id) === true) {
throw new BadGamePlayPayloadException(BadGamePlayPayloadReasons.BAD_GUARD_TARGET);
}
}
Expand All @@ -226,9 +226,9 @@ export class GamePlayValidatorService {
if (game.currentPlay.action === GamePlayActions.DELEGATE && !targetedPlayer.isAlive) {
throw new BadGamePlayPayloadException(BadGamePlayPayloadReasons.BAD_SHERIFF_DELEGATE_TARGET);
}
const lastTieInVotesRecord = await this.gameHistoryRecordService.getLastGameHistoryTieInVotesRecord(game._id, game.currentPlay.action);
const lastTieInVotesRecord = await this.gameHistoryRecordService.getLastGameHistoryTieInVotesRecord(game._id, GamePlayActions.VOTE);
const lastTieInVotesRecordNominatedPlayers = lastTieInVotesRecord?.play.voting?.nominatedPlayers ?? [];
const isSheriffTargetInLastNominatedPlayers = lastTieInVotesRecordNominatedPlayers.find(({ _id }) => _id === targetedPlayer._id);
const isSheriffTargetInLastNominatedPlayers = lastTieInVotesRecordNominatedPlayers.find(({ _id }) => _id.equals(targetedPlayer._id));
if (game.currentPlay.action === GamePlayActions.SETTLE_VOTES && !isSheriffTargetInLastNominatedPlayers) {
throw new BadGamePlayPayloadException(BadGamePlayPayloadReasons.BAD_SHERIFF_SETTLE_VOTES_TARGET);
}
Expand Down Expand Up @@ -307,7 +307,7 @@ export class GamePlayValidatorService {
if (playVotes.some(({ target }) => !target.isAlive)) {
throw new BadGamePlayPayloadException(BadGamePlayPayloadReasons.BAD_VOTE_TARGET);
}
if (playVotes.some(({ source, target }) => source._id === target._id)) {
if (playVotes.some(({ source, target }) => source._id.equals(target._id))) {
throw new BadGamePlayPayloadException(BadGamePlayPayloadReasons.SAME_SOURCE_AND_TARGET_VOTE);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ export class GamePlayService {
return clonedGame;
}

public getUpcomingDayPlays(): GamePlay[] {
return [createGamePlayAllVote()];
public getUpcomingDayPlays(game: Game): GamePlay[] {
const upcomingDayPlays: GamePlay[] = [createGamePlayAllVote()];
if (this.isSheriffElectionTime(game.options.roles.sheriff, game.turn, game.phase)) {
upcomingDayPlays.unshift(createGamePlayAllElectSheriff());
}
return upcomingDayPlays;
}

public async getUpcomingNightPlays(game: CreateGameDto | Game): Promise<GamePlay[]> {
Expand Down Expand Up @@ -87,7 +91,7 @@ export class GamePlayService {

private async getNewUpcomingPlaysForCurrentPhase(game: Game): Promise<GamePlay[]> {
const { _id, turn, phase } = game;
const currentPhaseUpcomingPlays = game.phase === GamePhases.NIGHT ? await this.getUpcomingNightPlays(game) : this.getUpcomingDayPlays();
const currentPhaseUpcomingPlays = game.phase === GamePhases.NIGHT ? await this.getUpcomingNightPlays(game) : this.getUpcomingDayPlays(game);
const gameHistoryPhaseRecords = await this.gameHistoryRecordService.getGameHistoryPhaseRecords(_id, turn, phase);
return currentPhaseUpcomingPlays.filter(gamePlay => this.isUpcomingPlayNewForCurrentPhase(gamePlay, game, gameHistoryPhaseRecords));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class PlayerKillerService {
private applyInLovePlayerDeathOutcomes(killedPlayer: Player, game: Game): Game {
const clonedGame = createGame(game);
const otherLoverFinder = (player: Player): boolean => doesPlayerHaveActiveAttributeWithName(player, PlayerAttributeNames.IN_LOVE, game) &&
player.isAlive && player._id !== killedPlayer._id;
player.isAlive && !player._id.equals(killedPlayer._id);
const otherPlayerInLove = clonedGame.players.find(otherLoverFinder);
if (!doesPlayerHaveActiveAttributeWithName(killedPlayer, PlayerAttributeNames.IN_LOVE, clonedGame) || !otherPlayerInLove) {
return clonedGame;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import { Expose, Transform } from "class-transformer";
import { Types } from "mongoose";

import { GAME_ADDITIONAL_CARDS_API_PROPERTIES, GAME_ADDITIONAL_CARDS_FIELDS_SPECS } from "@/modules/game/schemas/game-additional-card/game-additional-card.schema.constant";
import { RoleNames } from "@/modules/role/enums/role.enum";

import { toObjectId } from "@/shared/validation/transformers/validation.transformer";

@Schema({ versionKey: false })
class GameAdditionalCard {
@ApiProperty(GAME_ADDITIONAL_CARDS_API_PROPERTIES._id)
@Type(() => String)
@Transform(toObjectId)
@Expose()
public _id: Types.ObjectId;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import { Expose, Transform, Type } from "class-transformer";
import { SchemaTypes, Types } from "mongoose";

import { GAME_HISTORY_RECORD_API_PROPERTIES, GAME_HISTORY_RECORD_FIELDS_SPECS } from "@/modules/game/schemas/game-history-record/game-history-record.schema.constant";
import { GamePhases } from "@/modules/game/enums/game.enum";
import { GameHistoryRecordPlay, GAME_HISTORY_RECORD_PLAY_SCHEMA } from "@/modules/game/schemas/game-history-record/game-history-record-play/game-history-record-play.schema";
import { PLAYER_SCHEMA, Player } from "@/modules/game/schemas/player/player.schema";

import { toObjectId } from "@/shared/validation/transformers/validation.transformer";

@Schema({
timestamps: { createdAt: true, updatedAt: false },
versionKey: false,
})
class GameHistoryRecord {
@ApiProperty(GAME_HISTORY_RECORD_API_PROPERTIES._id)
@Type(() => String)
@Transform(toObjectId)
@Expose()
public _id: Types.ObjectId;

Expand Down
6 changes: 4 additions & 2 deletions src/modules/game/schemas/game.schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import { Expose, Transform, Type } from "class-transformer";
import { Types } from "mongoose";

import { GamePhases, GameStatuses } from "@/modules/game/enums/game.enum";
Expand All @@ -11,13 +11,15 @@ import { GameVictory, GAME_VICTORY_SCHEMA } from "@/modules/game/schemas/game-vi
import { GAME_API_PROPERTIES, GAME_FIELDS_SPECS } from "@/modules/game/schemas/game.schema.constant";
import { PLAYER_SCHEMA, Player } from "@/modules/game/schemas/player/player.schema";

import { toObjectId } from "@/shared/validation/transformers/validation.transformer";

@Schema({
timestamps: true,
versionKey: false,
})
class Game {
@ApiProperty(GAME_API_PROPERTIES._id)
@Type(() => String)
@Transform(toObjectId)
@Expose()
public _id: Types.ObjectId;

Expand Down
6 changes: 4 additions & 2 deletions src/modules/game/schemas/player/player.schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import { Expose, Transform, Type } from "class-transformer";
import { Types } from "mongoose";

import { PLAYER_API_PROPERTIES, PLAYER_FIELDS_SPECS } from "@/modules/game/schemas/player/player.schema.constant";
Expand All @@ -9,10 +9,12 @@ import { PlayerDeath, PLAYER_DEATH_SCHEMA } from "@/modules/game/schemas/player/
import { PlayerRole, PLAYER_ROLE_SCHEMA } from "@/modules/game/schemas/player/player-role/player-role.schema";
import { PlayerSide, PLAYER_SIDE_SCHEMA } from "@/modules/game/schemas/player/player-side/player-side.schema";

import { toObjectId } from "@/shared/validation/transformers/validation.transformer";

@Schema({ versionKey: false })
class Player {
@ApiProperty(PLAYER_API_PROPERTIES._id)
@Type(() => String)
@Transform(toObjectId)
@Expose()
public _id: Types.ObjectId;

Expand Down
18 changes: 17 additions & 1 deletion src/shared/validation/transformers/validation.transformer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { TransformFnParams } from "class-transformer";
import { has } from "lodash";
import { isValidObjectId, Types } from "mongoose";

function toBoolean({ value }: TransformFnParams): unknown {
if (value === "true") {
Expand All @@ -9,4 +11,18 @@ function toBoolean({ value }: TransformFnParams): unknown {
return value;
}

export { toBoolean };
function toObjectId({ obj }: TransformFnParams): unknown {
if (!has(obj, "_id")) {
return undefined;
}
const { _id } = obj as { _id: unknown };
if (!isValidObjectId(_id)) {
return _id;
}
return new Types.ObjectId((_id as Types.ObjectId).toString());
}

export {
toBoolean,
toObjectId,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"roles": {
"sheriff": {
"electedAt": {
"phase": "day",
"turn": 1
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"roles": {
"sheriff": {
"electedAt": {
"phase": "night",
"turn": 2
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"roles": {
"sheriff": {
"hasDoubledVote": false
}
}
}

0 comments on commit ed9e09e

Please sign in to comment.