Skip to content

Commit aa44f66

Browse files
Merge pull request #568 from Alt-Org/feature/add-battle-queue-endpoint-561
Added /online-players/battleQueue GET endpoint to retrieve online players waiting in battle queue
2 parents 0bb97ee + 97de359 commit aa44f66

21 files changed

+677
-23
lines changed

src/__tests__/common/service/redis/mocks/RedisServiceInMemory.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ import IRedisService from '../../../../../common/service/redis/IRedisService';
77
*/
88
@Injectable()
99
export class RedisServiceInMemory implements IRedisService, OnModuleDestroy {
10+
private static instance: RedisServiceInMemory;
1011
private readonly store = new Map<
1112
string,
1213
{ value: string; expiresAt?: number }
1314
>();
1415

16+
constructor() {
17+
if (RedisServiceInMemory.instance) return RedisServiceInMemory.instance;
18+
RedisServiceInMemory.instance = this;
19+
}
20+
1521
private isDestroyed = false;
1622

1723
async set(key: string, value: string, ttlS?: number): Promise<void> {

src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { CacheKeys } from '../../../common/service/redis/cacheKeys.enum';
66
import { OnlinePlayerStatus } from '../../../onlinePlayers/enum/OnlinePlayerStatus';
77
import OnlinePlayersCommonModule from '../modules/onlinePlayersCommon.module';
88
import { RedisService } from '../../../common/service/redis/redis.service';
9+
import OnlinePlayersBuilderFactory from '../data/onlinePlayersBuilderFactory';
10+
import { OnlinePlayerBuilder } from '../data/onlinePlayers/OnlinePlayerBuilder';
11+
import { BattleWaitStatus } from '../../../onlinePlayers/payload/additionalTypes/BattleWaitStatus';
912

1013
describe('OnlinePlayersService.addPlayerOnline() test suite', () => {
1114
let service: OnlinePlayersService;
@@ -18,6 +21,12 @@ describe('OnlinePlayersService.addPlayerOnline() test suite', () => {
1821
.setName('player1')
1922
.build();
2023

24+
const addPlayerBuilder =
25+
OnlinePlayersBuilderFactory.getBuilder('AddOnlinePlayer');
26+
const onlinePlayerBuilder = OnlinePlayersBuilderFactory.getBuilder(
27+
'OnlinePlayer',
28+
) as OnlinePlayerBuilder<BattleWaitStatus>;
29+
2130
const playerModel = PlayerModule.getPlayerModel();
2231

2332
beforeEach(async () => {
@@ -33,21 +42,50 @@ describe('OnlinePlayersService.addPlayerOnline() test suite', () => {
3342
});
3443

3544
it('Should be able to add one player to cache', async () => {
45+
const playerToAdd = addPlayerBuilder
46+
.setPlayerId(player1._id)
47+
.setStatus(OnlinePlayerStatus.BATTLE)
48+
.build();
49+
3650
const expectedKey = `${CacheKeys.ONLINE_PLAYERS}:${player1._id}`;
37-
const expectedPayload = JSON.stringify({
38-
_id: player1._id,
39-
name: player1.name,
40-
status: OnlinePlayerStatus.BATTLE,
41-
});
51+
const expectedPayload = JSON.stringify(
52+
onlinePlayerBuilder
53+
.setId(playerToAdd.player_id)
54+
.setName(player1.name)
55+
.setStatus(playerToAdd.status)
56+
.build(),
57+
);
58+
59+
const redisSet = jest.spyOn(redisService, 'set');
60+
61+
await service.addPlayerOnline(playerToAdd);
62+
63+
expect(redisSet).toHaveBeenCalledTimes(1);
64+
expect(redisSet).toHaveBeenCalledWith(expectedKey, expectedPayload, 90);
65+
});
66+
67+
it(`Should set queue number if player has status ${OnlinePlayerStatus.BATTLE_WAIT}`, async () => {
68+
const playerToAdd = addPlayerBuilder
69+
.setPlayerId(player1._id)
70+
.setStatus(OnlinePlayerStatus.BATTLE_WAIT)
71+
.build();
72+
const expectedKey = `${CacheKeys.ONLINE_PLAYERS}:${player1._id}`;
73+
const expectedPayload = JSON.stringify(
74+
onlinePlayerBuilder
75+
.setId(playerToAdd.player_id)
76+
.setName(player1.name)
77+
.setStatus(playerToAdd.status)
78+
.setAdditional({ queueNumber: 0 })
79+
.build(),
80+
);
4281

4382
const redisSet = jest.spyOn(redisService, 'set');
4483

4584
await service.addPlayerOnline({
4685
player_id: player1._id,
47-
status: OnlinePlayerStatus.BATTLE,
86+
status: OnlinePlayerStatus.BATTLE_WAIT,
4887
});
4988

50-
expect(redisSet).toHaveBeenCalledTimes(1);
5189
expect(redisSet).toHaveBeenCalledWith(expectedKey, expectedPayload, 90);
5290
});
5391
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { OnlinePlayersService } from '../../../onlinePlayers/onlinePlayers.service';
2+
import { RedisService } from '../../../common/service/redis/redis.service';
3+
import PlayerBuilderFactory from '../../player/data/playerBuilderFactory';
4+
import PlayerModule from '../../player/modules/player.module';
5+
import OnlinePlayersModule from '../modules/onlinePlayers.module';
6+
import OnlinePlayersCommonModule from '../modules/onlinePlayersCommon.module';
7+
import OnlinePlayer from '../../../onlinePlayers/payload/OnlinePlayer';
8+
import { OnlinePlayerStatus } from '../../../onlinePlayers/enum/OnlinePlayerStatus';
9+
10+
describe('OnlinePlayersService.getOnlinePlayerById() test suite', () => {
11+
let service: OnlinePlayersService;
12+
13+
let redisService: RedisService;
14+
15+
const playerBuilder = PlayerBuilderFactory.getBuilder('Player');
16+
const player1 = playerBuilder
17+
.setUniqueIdentifier('player1')
18+
.setName('player1')
19+
.build();
20+
21+
const playerModel = PlayerModule.getPlayerModel();
22+
23+
beforeEach(async () => {
24+
jest.clearAllMocks();
25+
service = await OnlinePlayersModule.getOnlinePlayersService();
26+
27+
const player1Resp = await playerModel.create(player1);
28+
player1._id = player1Resp._id.toString();
29+
30+
redisService = (await OnlinePlayersCommonModule.getModule()).get(
31+
RedisService,
32+
);
33+
});
34+
35+
it('Should return player if it exists', async () => {
36+
const existingPlayer: OnlinePlayer = {
37+
_id: player1._id,
38+
name: player1.name,
39+
status: OnlinePlayerStatus.UI,
40+
};
41+
jest
42+
.spyOn(redisService, 'get')
43+
.mockResolvedValue(JSON.stringify(existingPlayer));
44+
45+
const [player, errors] = await service.getOnlinePlayerById(player1._id);
46+
47+
expect(errors).toBeNull();
48+
expect(player).toEqual(existingPlayer);
49+
});
50+
51+
it('Should return ServiceError NOT_FOUND if player does not exists', async () => {
52+
jest.spyOn(redisService, 'get').mockResolvedValue(null);
53+
54+
const [player, errors] = await service.getOnlinePlayerById(player1._id);
55+
56+
expect(player).toBeNull();
57+
expect(errors).toContainSE_NOT_FOUND();
58+
});
59+
});

src/__tests__/onlinePlayers/OnlinePlayersService/getAllOnlinePlayers.test.ts renamed to src/__tests__/onlinePlayers/OnlinePlayersService/getOnlinePlayers.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { OnlinePlayerStatus } from '../../../onlinePlayers/enum/OnlinePlayerStat
77
import { RedisService } from '../../../common/service/redis/redis.service';
88
import OnlinePlayersCommonModule from '../modules/onlinePlayersCommon.module';
99

10-
describe('OnlinePlayersService.getAllOnlinePlayers() test suite', () => {
10+
describe('OnlinePlayersService.getOnlinePlayers() test suite', () => {
1111
let service: OnlinePlayersService;
1212

1313
let redisService: RedisService;
@@ -57,7 +57,7 @@ describe('OnlinePlayersService.getAllOnlinePlayers() test suite', () => {
5757
[_id2]: JSON.stringify(payload2),
5858
});
5959

60-
const player_ids = await service.getAllOnlinePlayers();
60+
const player_ids = await service.getOnlinePlayers();
6161

6262
expect(player_ids).toContainEqual(payload1);
6363
expect(player_ids).toContainEqual(payload2);
@@ -82,7 +82,7 @@ describe('OnlinePlayersService.getAllOnlinePlayers() test suite', () => {
8282
[_id2]: JSON.stringify(payload2),
8383
});
8484

85-
const player_ids = await service.getAllOnlinePlayers({
85+
const player_ids = await service.getOnlinePlayers({
8686
filter: { status: [OnlinePlayerStatus.UI] },
8787
});
8888

@@ -93,7 +93,7 @@ describe('OnlinePlayersService.getAllOnlinePlayers() test suite', () => {
9393
it('Should not return players if there are no players', async () => {
9494
jest.spyOn(redisService, 'getValuesByKeyPattern').mockResolvedValue({});
9595

96-
const player_ids = await service.getAllOnlinePlayers();
96+
const player_ids = await service.getOnlinePlayers();
9797

9898
expect(player_ids).not.toContain(_id1);
9999
});
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import BattleQueueModule from '../../modules/battleQueue.module';
2+
import PlayerModule from '../../../player/modules/player.module';
3+
import PlayerBuilderFactory from '../../../player/data/playerBuilderFactory';
4+
import { BattleQueueService } from '../../../../onlinePlayers/battleQueue/battleQueue.service';
5+
import { OnlinePlayerStatus } from '../../../../onlinePlayers/enum/OnlinePlayerStatus';
6+
import OnlinePlayersBuilderFactory from '../../data/onlinePlayersBuilderFactory';
7+
import { OnlinePlayerBuilder } from '../../data/onlinePlayers/OnlinePlayerBuilder';
8+
import { BattleWaitStatus } from '../../../../onlinePlayers/payload/additionalTypes/BattleWaitStatus';
9+
10+
describe('BattleQueueService.getPlayerQueueNumber() test suite', () => {
11+
let service: BattleQueueService;
12+
13+
const playerBuilder = PlayerBuilderFactory.getBuilder('Player');
14+
const player1 = playerBuilder
15+
.setName('player1')
16+
.setUniqueIdentifier('player1')
17+
.build();
18+
const player2 = playerBuilder
19+
.setName('player2')
20+
.setUniqueIdentifier('player2')
21+
.build();
22+
const player3 = playerBuilder
23+
.setName('player3')
24+
.setUniqueIdentifier('player3')
25+
.build();
26+
const playerModel = PlayerModule.getPlayerModel();
27+
28+
const onlinePlayerBuilder = OnlinePlayersBuilderFactory.getBuilder(
29+
'OnlinePlayer',
30+
) as OnlinePlayerBuilder<BattleWaitStatus>;
31+
32+
beforeEach(async () => {
33+
service = await BattleQueueModule.getBattleQueueService();
34+
35+
const createdPlayer1 = await playerModel.create(player1);
36+
player1._id = createdPlayer1._id.toString();
37+
const createdPlayer2 = await playerModel.create(player2);
38+
player2._id = createdPlayer2._id.toString();
39+
const createdPlayer3 = await playerModel.create(player3);
40+
player3._id = createdPlayer3._id.toString();
41+
});
42+
43+
it('Should return increased by 1 order number for each player', async () => {
44+
const onlinePlayer1 = onlinePlayerBuilder
45+
.setId(player1._id)
46+
.setName(player1.name)
47+
.build();
48+
const onlinePlayer2 = onlinePlayerBuilder
49+
.setId(player2._id)
50+
.setName(player2.name)
51+
.build();
52+
const onlinePlayer3 = onlinePlayerBuilder
53+
.setId(player3._id)
54+
.setName(player3.name)
55+
.build();
56+
57+
const [number1, errors1] =
58+
await service.getPlayerQueueNumber(onlinePlayer1);
59+
const [number2, errors2] =
60+
await service.getPlayerQueueNumber(onlinePlayer2);
61+
const [number3, errors3] =
62+
await service.getPlayerQueueNumber(onlinePlayer3);
63+
64+
expect(errors1).toBeNull();
65+
expect(number1).toBe(0);
66+
67+
expect(errors2).toBeNull();
68+
expect(number2).toBe(1);
69+
70+
expect(errors3).toBeNull();
71+
expect(number3).toBe(2);
72+
});
73+
74+
it('Should return the same number the player has if the player is already in queue', async () => {
75+
const queueNumber = 0;
76+
const onlinePlayer1 = onlinePlayerBuilder
77+
.setId(player1._id)
78+
.setName(player1.name)
79+
.setStatus(OnlinePlayerStatus.BATTLE_WAIT)
80+
.setAdditional({ queueNumber })
81+
.build();
82+
83+
const [number1, errors1] =
84+
await service.getPlayerQueueNumber(onlinePlayer1);
85+
const [number2, errors2] =
86+
await service.getPlayerQueueNumber(onlinePlayer1);
87+
const [number3, errors3] =
88+
await service.getPlayerQueueNumber(onlinePlayer1);
89+
90+
expect(errors1).toBeNull();
91+
expect(number1).toBe(queueNumber);
92+
93+
expect(errors2).toBeNull();
94+
expect(number2).toBe(queueNumber);
95+
96+
expect(errors3).toBeNull();
97+
expect(number3).toBe(queueNumber);
98+
});
99+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { BattleQueueService } from 'src/onlinePlayers/battleQueue/battleQueue.service';
2+
import BattleQueueModule from '../../modules/battleQueue.module';
3+
import OnlinePlayersBuilderFactory from '../../data/onlinePlayersBuilderFactory';
4+
import { OnlinePlayerBuilder } from '../../data/onlinePlayers/OnlinePlayerBuilder';
5+
import { BattleWaitStatus } from '../../../../onlinePlayers/payload/additionalTypes/BattleWaitStatus';
6+
7+
describe('BattleQueueService.sortPlayersByQueueNumber', () => {
8+
let service: BattleQueueService;
9+
let playerBuilder: OnlinePlayerBuilder<BattleWaitStatus>;
10+
11+
beforeEach(async () => {
12+
service = await BattleQueueModule.getBattleQueueService();
13+
playerBuilder = OnlinePlayersBuilderFactory.getBuilder('OnlinePlayer');
14+
});
15+
16+
it('should sort players in ascending queueNumber order', () => {
17+
const players = [
18+
playerBuilder.setAdditional({ queueNumber: 10 }).build(),
19+
playerBuilder.setAdditional({ queueNumber: 5 }).build(),
20+
playerBuilder.setAdditional({ queueNumber: 7 }).build(),
21+
];
22+
23+
const [sorted, errors] = service.sortPlayersByQueueNumber(players);
24+
const sortedNumbers = sorted.map((p) => p.additional.queueNumber);
25+
26+
expect(errors).toBeNull();
27+
expect(sortedNumbers).toEqual([5, 7, 10]);
28+
});
29+
30+
it('should sort players considering wrap-around queue numbers', () => {
31+
const players = [
32+
playerBuilder.setAdditional({ queueNumber: 1 }).build(),
33+
playerBuilder.setAdditional({ queueNumber: 9999 }).build(),
34+
playerBuilder.setAdditional({ queueNumber: 5 }).build(),
35+
playerBuilder.setAdditional({ queueNumber: 9998 }).build(),
36+
playerBuilder.setAdditional({ queueNumber: 0 }).build(),
37+
];
38+
39+
const [sorted, errors] = service.sortPlayersByQueueNumber(players);
40+
const sortedNumbers = sorted.map((p) => p.additional.queueNumber);
41+
42+
expect(errors).toBeNull();
43+
expect(sortedNumbers).toEqual([9998, 9999, 0, 1, 5]);
44+
});
45+
46+
it('should still sort correctly with gaps and wrap', () => {
47+
const players = [
48+
playerBuilder.setAdditional({ queueNumber: 9998 }).build(),
49+
playerBuilder.setAdditional({ queueNumber: 3 }).build(),
50+
playerBuilder.setAdditional({ queueNumber: 9995 }).build(),
51+
playerBuilder.setAdditional({ queueNumber: 9 }).build(),
52+
playerBuilder.setAdditional({ queueNumber: 1000 }).build(),
53+
];
54+
55+
const [sorted, errors] = service.sortPlayersByQueueNumber(players);
56+
const sortedNumbers = sorted.map((p) => p.additional.queueNumber);
57+
58+
expect(errors).toBeNull();
59+
expect(sortedNumbers).toEqual([9995, 9998, 3, 9, 1000]);
60+
});
61+
62+
it('Should return ServiceError NOT_FOUND if empty array is provided', () => {
63+
const [sorted, errors] = service.sortPlayersByQueueNumber([]);
64+
expect(sorted).toBeNull();
65+
expect(errors).toContainSE_NOT_FOUND();
66+
});
67+
68+
it('Should return ServiceError NOT_FOUND if no players have the order number', () => {
69+
const players1 = [playerBuilder.build(), playerBuilder.build()];
70+
const [sorted, errors] = service.sortPlayersByQueueNumber(players1);
71+
expect(sorted).toBeNull();
72+
expect(errors).toContainSE_NOT_FOUND();
73+
});
74+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import AddOnlinePlayer from '../../../../onlinePlayers/payload/AddOnlinePlayer';
2+
import { OnlinePlayerStatus } from '../../../../onlinePlayers/enum/OnlinePlayerStatus';
3+
4+
export class AddOnlinePlayerBuilder {
5+
private readonly base: Partial<AddOnlinePlayer> = {
6+
player_id: undefined,
7+
status: OnlinePlayerStatus.UI,
8+
};
9+
10+
build(): AddOnlinePlayer {
11+
return { ...this.base } as AddOnlinePlayer;
12+
}
13+
14+
setPlayerId(player_id: string): this {
15+
this.base.player_id = player_id;
16+
return this;
17+
}
18+
19+
setStatus(status: OnlinePlayerStatus): this {
20+
this.base.status = status;
21+
return this;
22+
}
23+
}

0 commit comments

Comments
 (0)