Skip to content

Commit

Permalink
chore: increase timeout for test io connection (#670)
Browse files Browse the repository at this point in the history
* chore: increase timeout for test io connection

* fix: joining a socket to a room is an async operator when using the Redis adapter

* chore: Maybe dont need the timeout anymore after connecting?

* chore: cleanup some test setup

* fix: attach listeners earlier after successful connection

* chore: remove some weirdness after a merge conflict

* refactor: some general cleanup
  • Loading branch information
niekcandaele authored Nov 18, 2023
1 parent 571933c commit 0234d1f
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 117 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { IntegrationTest, expect, integrationConfig, EventsAwaiter, SetupGameServerPlayers } from '@takaro/test';
import { GameServerOutputDTO, ModuleOutputDTO, PlayerOutputDTO, VariableOutputDTO } from '@takaro/apiclient';
import { IntegrationTest, expect, SetupGameServerPlayers } from '@takaro/test';
import { VariableOutputDTO } from '@takaro/apiclient';
import { config } from '../../config.js';
import { EventTypes } from '@takaro/modules';

const group = 'VariableController';

Expand All @@ -14,59 +13,6 @@ const setup = async function (this: IntegrationTest<VariableOutputDTO>): Promise
).data.data;
};

interface ISetupWithGameServersAndPlayers {
gameServer1: GameServerOutputDTO;
gameServer2: GameServerOutputDTO;
players: PlayerOutputDTO[];
mod: ModuleOutputDTO;
}

const setupWithGameServersAndPlayers = async function (
this: IntegrationTest<ISetupWithGameServersAndPlayers>
): Promise<ISetupWithGameServersAndPlayers> {
const gameServer1 = await this.client.gameserver.gameServerControllerCreate({
name: 'Gameserver 1',
type: 'MOCK',
connectionInfo: JSON.stringify({
host: integrationConfig.get('mockGameserver.host'),
}),
});

const gameServer2 = await this.client.gameserver.gameServerControllerCreate({
name: 'Gameserver 2',
type: 'MOCK',
connectionInfo: JSON.stringify({
host: integrationConfig.get('mockGameserver.host'),
}),
});

const mod = (
await this.client.module.moduleControllerCreate({
name: 'Test module',
})
).data.data;

const eventsAwaiter = new EventsAwaiter();
await eventsAwaiter.connect(this.client);
const connectedEvents = eventsAwaiter.waitForEvents(EventTypes.PLAYER_CONNECTED, 10);

await Promise.all([
this.client.gameserver.gameServerControllerExecuteCommand(gameServer1.data.data.id, { command: 'connectAll' }),
this.client.gameserver.gameServerControllerExecuteCommand(gameServer2.data.data.id, { command: 'connectAll' }),
]);

await connectedEvents;

const players = (await this.client.player.playerControllerSearch()).data.data;

return {
gameServer1: gameServer1.data.data,
gameServer2: gameServer2.data.data,
players,
mod,
};
};

const tests = [
new IntegrationTest<VariableOutputDTO>({
group,
Expand Down Expand Up @@ -329,11 +275,11 @@ const tests = [
},
expectedStatus: 400,
}),
new IntegrationTest<ISetupWithGameServersAndPlayers>({
new IntegrationTest<SetupGameServerPlayers.ISetupData>({
group,
snapshot: true,
name: 'Create with moduleId',
setup: setupWithGameServersAndPlayers,
setup: SetupGameServerPlayers.setup,
test: async function () {
return this.client.variable.variableControllerCreate({
key: 'Test variable',
Expand All @@ -343,11 +289,11 @@ const tests = [
},
filteredFields: ['moduleId'],
}),
new IntegrationTest<ISetupWithGameServersAndPlayers>({
new IntegrationTest<SetupGameServerPlayers.ISetupData>({
group,
snapshot: true,
name: 'Prevents creating duplicate variables with same moduleId',
setup: setupWithGameServersAndPlayers,
setup: SetupGameServerPlayers.setup,
test: async function () {
// First creation should succeed
await this.client.variable.variableControllerCreate({
Expand Down
2 changes: 1 addition & 1 deletion packages/app-api/src/lib/socketServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class SocketServer {
this.log.error('No domain found in context');
return next(new errors.UnauthorizedError());
}
socket.join(ctxData.domain);
await socket.join(ctxData.domain);
next();
} catch (error) {
this.log.error('Unknown error when routing socket', error);
Expand Down
3 changes: 2 additions & 1 deletion packages/app-mock-gameserver/src/lib/socket/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class SocketServer {

this.io.on('connection', async (socket) => {
const instance = await getMockServer();
await instance.ensurePlayersPersisted();

this.log.info(`New connection: ${socket.id}`);
socket.onAny(async (event: keyof IMockGameServer | 'ping', ...args) => {
Expand All @@ -64,6 +63,8 @@ class SocketServer {
args[args.length - 1](new Error(`Event ${event} not found`));
}
});

await instance.ensurePlayersPersisted();
});

this.log.info('Socket server started');
Expand Down
69 changes: 30 additions & 39 deletions packages/lib-gameserver/src/gameservers/mock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,50 +33,56 @@ export class Mock implements IGameServer {
return this.emitter;
}

private async getClient(timeout = 5000): Promise<Socket> {
private async getClient(timeout = 2500): Promise<Socket> {
if (this.io.connected) {
return this.io;
}

return Promise.race([
new Promise<Socket>((resolve, reject) => {
this.io.on('connect', () => {
const onConnect = () => {
this.io.off('connect_error', onConnectError);
resolve(this.io);
});
this.io.on('connect_error', (err) => {
};

const onConnectError = (err: Error) => {
this.io.off('connect', onConnect);
reject(err);
});
};

this.io.on('connect', onConnect);
this.io.on('connect_error', onConnectError);
}),
new Promise<Socket>((_, reject) => {
setTimeout(() => {
this.io.off('connect');
this.io.off('connect_error');
reject(new Error(`Connection timed out after ${timeout}ms`));
}, timeout);
}),
]);
}

async getPlayer(player: IPlayerReferenceDTO): Promise<IGamePlayer | null> {
private async requestFromServer(event: string, ...args: any[]) {
const client = await this.getClient();
const data = await client.emitWithAck('getPlayer', player);
return data;
return client.timeout(2500).emitWithAck(event, ...args);
}

async getPlayer(player: IPlayerReferenceDTO): Promise<IGamePlayer | null> {
return this.requestFromServer('getPlayer', player);
}

async getPlayers(): Promise<IGamePlayer[]> {
const client = await this.getClient();
const data = await client.emitWithAck('getPlayers');
return data;
return this.requestFromServer('getPlayers');
}

async getPlayerLocation(player: IPlayerReferenceDTO): Promise<IPosition | null> {
const client = await this.getClient();
const data = await client.emitWithAck('getPlayerLocation', player);
return data;
return this.requestFromServer('getPlayerLocation', player);
}

async testReachability(): Promise<TestReachabilityOutputDTO> {
try {
const client = await this.getClient();
const data = await client.emitWithAck('ping');
const data = await this.requestFromServer('ping');
assert(data === 'pong');
} catch (error) {
if (!error || !(error instanceof Error)) {
Expand Down Expand Up @@ -105,49 +111,34 @@ export class Mock implements IGameServer {
}

async executeConsoleCommand(rawCommand: string): Promise<CommandOutput> {
const client = await this.getClient();
const data = await client.emitWithAck('executeConsoleCommand', rawCommand);
return data;
return this.requestFromServer('executeConsoleCommand', rawCommand);
}

async sendMessage(message: string, opts: IMessageOptsDTO) {
const client = await this.getClient();
const data = await client.emitWithAck('sendMessage', message, opts);
return data;
return this.requestFromServer('sendMessage', message, opts);
}

async teleportPlayer(player: IGamePlayer, x: number, y: number, z: number) {
const client = await this.getClient();
const data = await client.emitWithAck('teleportPlayer', player, x, y, z);
return data;
return this.requestFromServer('teleportPlayer', player, x, y, z);
}

async kickPlayer(player: IGamePlayer, reason: string) {
const client = await this.getClient();
const data = await client.emitWithAck('kickPlayer', player, reason);
return data;
return this.requestFromServer('kickPlayer', player, reason);
}

async banPlayer(options: BanDTO) {
const client = await this.getClient();
const data = await client.emitWithAck('banPlayer', options);
return data;
return this.requestFromServer('banPlayer', options);
}

async unbanPlayer(player: IGamePlayer) {
const client = await this.getClient();
const data = await client.emitWithAck('unbanPlayer', player);
return data;
return this.requestFromServer('unbanPlayer', player);
}

async listBans(): Promise<BanDTO[]> {
const client = await this.getClient();
const data = await client.emitWithAck('listBans');
return data;
return this.requestFromServer('listBans');
}

async giveItem(player: IPlayerReferenceDTO, item: IItemDTO): Promise<void> {
const client = await this.getClient();
await client.emitWithAck('giveItem', player, item);
return this.requestFromServer('giveItem', player, item);
}
}
10 changes: 9 additions & 1 deletion packages/test/src/setups/gameServerWithPlayers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EventsAwaiter } from '../test/waitForEvents.js';
import { GameServerOutputDTO, PlayerOutputDTO } from '@takaro/apiclient';
import { GameServerOutputDTO, ModuleOutputDTO, PlayerOutputDTO } from '@takaro/apiclient';
import { EventTypes } from '@takaro/modules';
import { IntegrationTest } from '../integrationTest.js';
import { integrationConfig } from '../test/integrationConfig.js';
Expand All @@ -8,6 +8,7 @@ export interface ISetupData {
gameServer1: GameServerOutputDTO;
gameServer2: GameServerOutputDTO;
players: PlayerOutputDTO[];
mod: ModuleOutputDTO;
}

export const setup = async function (this: IntegrationTest<ISetupData>): Promise<ISetupData> {
Expand All @@ -27,6 +28,12 @@ export const setup = async function (this: IntegrationTest<ISetupData>): Promise
}),
});

const mod = (
await this.client.module.moduleControllerCreate({
name: 'Test module',
})
).data.data;

const eventsAwaiter = new EventsAwaiter();
await eventsAwaiter.connect(this.client);
const connectedEvents = eventsAwaiter.waitForEvents(EventTypes.PLAYER_CONNECTED, 10);
Expand All @@ -44,5 +51,6 @@ export const setup = async function (this: IntegrationTest<ISetupData>): Promise
gameServer1: gameServer1.data.data,
gameServer2: gameServer2.data.data,
players,
mod,
};
};
6 changes: 1 addition & 5 deletions packages/test/src/test/waitForEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { GameEvents } from '@takaro/modules';
import { Client } from '@takaro/apiclient';
import { io, Socket } from 'socket.io-client';
import { integrationConfig } from './integrationConfig.js';
import { sleep } from '@takaro/util';

export interface IDetectedEvent {
event: GameEvents;
Expand All @@ -21,9 +20,6 @@ export class EventsAwaiter {
});

this.socket.on('connect', async () => {
// There's a race condition happening somewhere with the io connection
// I couldn't get to the bottom of it, so I'm just adding a sleep here for now...
await sleep(500);
return resolve();
});

Expand Down Expand Up @@ -62,7 +58,7 @@ export class EventsAwaiter {
console.warn(`Event ${expectedEvent} timed out`);
console.warn(JSON.stringify(events, null, 2));
reject(new Error(`Event ${expectedEvent} timed out`));
}, 15000);
}, 5000);
}),
]);
}
Expand Down
10 changes: 0 additions & 10 deletions packages/test/src/waitUntilReady.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,6 @@ import { logger } from '@takaro/util';
const log = logger('tests');

before(async () => {
if (process.env.LOGGING_LEVEL === 'none') {
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.log = () => {};
}

if (process.env.LOGGING_LEVEL === 'none') {
// eslint-disable-next-line @typescript-eslint/no-empty-function
console.log = () => {};
}

const client = new Client({
url: integrationConfig.get('host'),
auth: {},
Expand Down

0 comments on commit 0234d1f

Please sign in to comment.