Skip to content

Commit 93458fb

Browse files
authored
Merge pull request #54 from x-team/feature/xtg-294
Feature/xtg 294: fix Arena /getState endpoint
2 parents 3c005cb + 7387317 commit 93458fb

File tree

4 files changed

+167
-21
lines changed

4 files changed

+167
-21
lines changed

.env.test

+9-5
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ HOST=0.0.0.0
33
PORT=3000
44

55
# DATABASE
6-
DB_USERNAME=postgres
7-
DB_PASSWORD=*games-2021
8-
DB_NAME=gameshq_api_test
9-
DB_HOSTNAME=127.0.0.1
10-
DB_PORT=5435
6+
DB_USERNAME=postgres
7+
DB_PASSWORD=*games-2021
8+
DB_NAME=gameshq_api_test
9+
DB_HOSTNAME=127.0.0.1
10+
DB_PORT=5435
11+
12+
SLACK_ARENA_SIGNING_SECRET=fake
13+
SLACK_ARENA_XHQ_CHANNEL=fake
14+
SLACK_ARENA_TOKEN=fake
1115

1216
# FIREBASE
1317
GOOGLE_APPLICATION_CREDENTIALS={"type": "fake", "project_id": "fake", "private_key": "fake", "client_email": "fake"}

src/models/Game.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
PrimaryKey,
1212
AutoIncrement,
1313
HasOne,
14+
HasMany,
1415
} from 'sequelize-typescript';
1516

1617
import { GAME_TYPE } from '../games/consts/global';
@@ -19,7 +20,7 @@ import { GameError } from '../games/utils/GameError';
1920

2021
import { findGameTypeByName } from './GameType';
2122

22-
import { User, ArenaGame, GameType, TowerGame, TowerFloor, TowerFloorEnemy } from './';
23+
import { ArenaPlayer, User, ArenaGame, GameType, TowerGame, TowerFloor, TowerFloorEnemy } from './';
2324

2425
function includeArrayByGameType(gameTypeName: string) {
2526
return gameTypeName === GAME_TYPE.ARENA
@@ -126,11 +127,15 @@ export class Game extends Model<GameAttributes, GameCreationAttributes> implemen
126127
@HasOne(() => TowerGame)
127128
declare _tower?: TowerGame;
128129

130+
@HasMany(() => ArenaPlayer, '_gameId')
131+
declare _arenaPlayers?: ArenaPlayer[];
132+
129133
static associations: {
130134
_arena: Association<Game, ArenaGame>;
131135
_tower: Association<Game, TowerGame>;
132136
_createdBy: Association<Game, User>;
133137
_gameType: Association<Game, GameType>;
138+
_arenaPlayers: Association<Game, ArenaPlayer>;
134139
};
135140

136141
async endGame(transaction?: Transaction) {

src/modules/dashboard/admin/adminHandlers/arenaAdminHandlers.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@ import { ArenaGame, findActiveArenaGame } from '../../../../models/ArenaGame';
55

66
export const getCurrentArenaGameState: Lifecycle.Method = async (_request, h) => {
77
const activeGame = await findActiveArenaGame();
8+
89
await activeGame?.reload({
9-
include: {
10-
model: ArenaGame,
11-
include: [
12-
{
13-
model: ArenaPlayer,
14-
include: [
15-
{
16-
association: ArenaPlayer.associations._weapons,
17-
include: [Item.associations._weapon, Item.associations._traits],
18-
as: '_weapons',
19-
},
20-
],
21-
},
22-
],
23-
},
10+
include: [
11+
{
12+
model: ArenaGame,
13+
},
14+
{
15+
model: ArenaPlayer,
16+
include: [
17+
{
18+
association: ArenaPlayer.associations._weapons,
19+
include: [Item.associations._weapon, Item.associations._traits],
20+
as: '_weapons',
21+
},
22+
],
23+
},
24+
],
2425
});
2526

2627
return h.response({ arenaGame: activeGame }).code(200);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { expect } from 'chai';
2+
import sinon from 'sinon';
3+
import { signMessage } from '../../../src/utils/cryptography';
4+
import { slackCommandRoute } from '../../../src/modules/slack/slackArenaRoute';
5+
import { getCustomTestServer } from '../../test-utils';
6+
import { User } from '../../../src/models';
7+
import { ARENA_SLACK_COMMANDS } from '../../../src/games/arena/consts';
8+
import * as arenaUtils from '../../../src/games/arena/utils';
9+
import * as utils from '../../../src/games/utils';
10+
11+
describe('slackArenaRoute', () => {
12+
const testServer = getCustomTestServer();
13+
14+
testServer.route([slackCommandRoute]);
15+
16+
describe('slackCommandRoute', () => {
17+
let stubbedPublishArenaMessage: any;
18+
let stubbedNotifyEphemeral: any;
19+
20+
it('should be configured as expected', async () => {
21+
stubbedPublishArenaMessage = sinon.stub(arenaUtils, 'publishArenaMessage').resolves();
22+
stubbedNotifyEphemeral = sinon.stub(utils, 'notifyEphemeral').resolves();
23+
24+
await startArenaGame();
25+
await startRound(1);
26+
await new Promise((resolve) => setTimeout(resolve, 1000));
27+
await startRound(2);
28+
});
29+
30+
async function startArenaGame() {
31+
const user = await User.findByPk(1);
32+
33+
const jsonPayload: { [key: string]: string } = {
34+
command: ARENA_SLACK_COMMANDS.NEW_GAME,
35+
text: '',
36+
response_url: '',
37+
trigger_id: '',
38+
user_id: String(user!.slackId),
39+
user_name: '',
40+
team_id: '',
41+
team_domain: '',
42+
channel_id: '',
43+
channel_name: '',
44+
};
45+
46+
const postPayload = Object.keys(jsonPayload)
47+
.map((k: string) => `${k}=${jsonPayload![k]}`)
48+
.join('&');
49+
const rslt = await testServer.inject(await postSlackCommandInjectOptions(postPayload));
50+
const payload = JSON.parse(rslt.payload);
51+
52+
expect(rslt.statusCode).to.equal(200);
53+
expect(payload.text).contains('*The Arena*\n Game "The Arena');
54+
expect(payload.text).contains('" has been created.');
55+
}
56+
57+
async function startRound(roundNum: number) {
58+
const user = await User.findByPk(1);
59+
60+
const jsonPayload: { [key: string]: string } = {
61+
command: ARENA_SLACK_COMMANDS.START_ROUND,
62+
text: '',
63+
response_url: '',
64+
trigger_id: '',
65+
user_id: String(user!.slackId),
66+
user_name: '',
67+
team_id: '',
68+
team_domain: '',
69+
channel_id: '',
70+
channel_name: '',
71+
};
72+
73+
const postPayload = Object.keys(jsonPayload)
74+
.map((k: string) => `${k}=${jsonPayload![k]}`)
75+
.join('&');
76+
const rslt = await testServer.inject(await postSlackCommandInjectOptions(postPayload));
77+
const payload = JSON.parse(rslt.payload);
78+
79+
expect(rslt.statusCode).to.equal(200);
80+
expect(payload.text).contains('Resolved last round and started a new one');
81+
82+
if (roundNum === 1) {
83+
checkRound1();
84+
} else if (roundNum === 2) {
85+
checkRound2();
86+
}
87+
88+
sinon.reset();
89+
}
90+
91+
function checkRound1() {
92+
expect(stubbedPublishArenaMessage).callCount(0);
93+
expect(stubbedNotifyEphemeral).callCount(0);
94+
}
95+
96+
function checkRound2() {
97+
expect(stubbedPublishArenaMessage).callCount(4);
98+
expect(stubbedPublishArenaMessage.args[0]).to.deep.equal(['Ending the round...', true]);
99+
expect(stubbedPublishArenaMessage.args[1]).to.deep.equal([
100+
'_*Total players still alive:* 0_',
101+
]);
102+
expect(stubbedPublishArenaMessage.args[2]).to.deep.equal([
103+
'_*Areas still active:*_ :arena-closed-gate: :arena-obsidian-tower: :arena-white-fortress: :arena-shrine-of-time: :arena-bamboo-forest: :arena-pridelands: :arena-ursine-darkwoods: :arena-phoenix-pyramids: :arena-canine-mansion: :arena-viking-watchtower: :arena-portal:',
104+
]);
105+
expect(stubbedPublishArenaMessage.args[3]).to.deep.equal([
106+
':spinner: Alive players are moving to a different area.\n :eyes: Check your `Status`',
107+
]);
108+
109+
expect(stubbedNotifyEphemeral).callCount(1);
110+
expect(stubbedNotifyEphemeral.args[0]).to.deep.equal([
111+
'Resolved last round and started a new one.',
112+
'UBZ9PC0SK',
113+
'UBZ9PC0SK',
114+
'fake',
115+
undefined,
116+
]);
117+
}
118+
});
119+
});
120+
121+
async function postSlackCommandInjectOptions(payload?: string) {
122+
const timestamp = String(new Date().getTime() / 1000);
123+
const signatureMessage = `v0:${timestamp}:${payload || ''}`;
124+
125+
let injectOptions = {
126+
method: 'POST',
127+
url: `/slack-integrations/arena-commands`,
128+
headers: {
129+
'x-slack-request-timestamp': timestamp,
130+
'x-slack-signature': `v0=${signMessage(signatureMessage, 'fake')}`,
131+
},
132+
payload: payload || {},
133+
};
134+
135+
return injectOptions;
136+
}

0 commit comments

Comments
 (0)