You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
5.3 KiB
155 lines
5.3 KiB
|
4 days ago
|
import { acceptedCookie } from './terms-fixture.js';
|
||
|
|
import { it, expect } from 'vitest';
|
||
|
|
import { mkdtemp, rm } from 'node:fs/promises';
|
||
|
|
import { tmpdir } from 'node:os';
|
||
|
|
import { join } from 'node:path';
|
||
|
|
import { io, type Socket } from 'socket.io-client';
|
||
|
|
import { FileStore } from '../server/store.mjs';
|
||
|
|
import { createService } from '../server/service.mjs';
|
||
|
|
import type { Room } from '../src/lib/types.js';
|
||
|
|
|
||
|
|
it('HTTP exits and inactivity closure notify real sockets and update both profiles', async () => {
|
||
|
|
const directory = await mkdtemp(join(tmpdir(), 'juegoland-exit-service-'));
|
||
|
|
const store = new FileStore(join(directory, 'store.json'));
|
||
|
|
await store.init();
|
||
|
|
const origin = 'http://localhost:4173';
|
||
|
|
const service = createService(store, {
|
||
|
|
APP_ORIGIN: origin,
|
||
|
|
NODE_ENV: 'test',
|
||
|
|
});
|
||
|
|
await new Promise<void>((resolve) =>
|
||
|
|
service.server.listen(0, '127.0.0.1', resolve),
|
||
|
|
);
|
||
|
|
const address = service.server.address();
|
||
|
|
if (!address || typeof address === 'string') throw new Error('Sin dirección');
|
||
|
|
const base = `http://127.0.0.1:${address.port}`;
|
||
|
|
const sockets: Socket[] = [];
|
||
|
|
const request = (path: string, cookie = '', data?: unknown) =>
|
||
|
|
fetch(base + '/api' + path, {
|
||
|
|
method: data === undefined ? 'GET' : 'POST',
|
||
|
|
headers: {
|
||
|
|
cookie: acceptedCookie(cookie),
|
||
|
|
origin,
|
||
|
|
'content-type': 'application/json',
|
||
|
|
},
|
||
|
|
body: data === undefined ? undefined : JSON.stringify(data),
|
||
|
|
});
|
||
|
|
async function guest(name: string) {
|
||
|
|
const response = await request('/auth/guest', '', { name });
|
||
|
|
return {
|
||
|
|
user: (await response.json()).user,
|
||
|
|
cookie: response.headers.get('set-cookie')!.split(';')[0],
|
||
|
|
};
|
||
|
|
}
|
||
|
|
async function socketFor(cookie: string, roomId: string) {
|
||
|
|
const socket = io(base, {
|
||
|
|
forceNew: true,
|
||
|
|
transports: ['websocket'],
|
||
|
|
extraHeaders: { cookie: acceptedCookie(cookie), origin },
|
||
|
|
});
|
||
|
|
sockets.push(socket);
|
||
|
|
await new Promise<void>((resolve, reject) => {
|
||
|
|
socket.once('connect', resolve);
|
||
|
|
socket.once('connect_error', reject);
|
||
|
|
});
|
||
|
|
const response = await socket
|
||
|
|
.timeout(4000)
|
||
|
|
.emitWithAck('room:subscribe', { roomId });
|
||
|
|
expect(response.ok).toBe(true);
|
||
|
|
return socket;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
const a = await guest('Anfitrión'),
|
||
|
|
b = await guest('Invitado'),
|
||
|
|
outsider = await guest('Fuera');
|
||
|
|
const room = (
|
||
|
|
await (
|
||
|
|
await request('/rooms', a.cookie, {
|
||
|
|
gameId: 'oca',
|
||
|
|
name: 'Sala para salir',
|
||
|
|
visibility: 'private',
|
||
|
|
maxPlayers: 2,
|
||
|
|
})
|
||
|
|
).json()
|
||
|
|
).room;
|
||
|
|
await request('/rooms/join', b.cookie, { code: room.code });
|
||
|
|
const one = await socketFor(a.cookie, room.id),
|
||
|
|
two = await socketFor(b.cookie, room.id);
|
||
|
|
const removed = new Promise<{ roomId: string }>((resolve) =>
|
||
|
|
two.once('room:removed', resolve),
|
||
|
|
);
|
||
|
|
const before = await service.platform.view(room.id, b.user.id);
|
||
|
|
expect(
|
||
|
|
(
|
||
|
|
await request(`/rooms/${room.id}/leave`, b.cookie, {
|
||
|
|
expectedVersion: before.version,
|
||
|
|
})
|
||
|
|
).status,
|
||
|
|
).toBe(200);
|
||
|
|
expect((await removed).roomId).toBe(room.id);
|
||
|
|
expect(
|
||
|
|
(await (await request('/profile', b.cookie)).json()).pending,
|
||
|
|
).toHaveLength(0);
|
||
|
|
expect((await request(`/rooms/${room.id}`, b.cookie)).status).toBe(400);
|
||
|
|
await request('/rooms/join', b.cookie, { code: room.code });
|
||
|
|
await two.timeout(4000).emitWithAck('room:subscribe', { roomId: room.id });
|
||
|
|
await service.platform.ready(a.user, room.id, true);
|
||
|
|
await service.platform.ready(b.user, room.id, true);
|
||
|
|
const playing = await service.platform.view(room.id, b.user.id);
|
||
|
|
expect(
|
||
|
|
(
|
||
|
|
await request(`/rooms/${room.id}/close-inactive`, b.cookie, {
|
||
|
|
expectedVersion: playing.version,
|
||
|
|
})
|
||
|
|
).status,
|
||
|
|
).toBe(400);
|
||
|
|
await store.updateRoom(
|
||
|
|
room.id,
|
||
|
|
(stored: any) =>
|
||
|
|
(stored.lastGameActivityAt = new Date(
|
||
|
|
Date.now() - 11 * 60_000,
|
||
|
|
).toISOString()),
|
||
|
|
);
|
||
|
|
expect(
|
||
|
|
(
|
||
|
|
await request(`/rooms/${room.id}/close-inactive`, outsider.cookie, {
|
||
|
|
expectedVersion: playing.version,
|
||
|
|
})
|
||
|
|
).status,
|
||
|
|
).toBe(400);
|
||
|
|
const finished = (socket: Socket) =>
|
||
|
|
new Promise<Room>((resolve) => {
|
||
|
|
socket.on('room:state', function listen(state: Room) {
|
||
|
|
if (state.status === 'finished') {
|
||
|
|
socket.off('room:state', listen);
|
||
|
|
resolve(state);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
const notices = Promise.all([finished(one), finished(two)]);
|
||
|
|
expect(
|
||
|
|
(
|
||
|
|
await request(`/rooms/${room.id}/close-inactive`, b.cookie, {
|
||
|
|
expectedVersion: playing.version,
|
||
|
|
})
|
||
|
|
).status,
|
||
|
|
).toBe(200);
|
||
|
|
expect((await notices).every((state) => state.result?.cancelled)).toBe(
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
for (const player of [a, b]) {
|
||
|
|
const profile = await (await request('/profile', player.cookie)).json();
|
||
|
|
expect(profile.pending).toHaveLength(0);
|
||
|
|
expect(profile.history[0].result.cancelled).toBe(true);
|
||
|
|
expect(
|
||
|
|
profile.stats.every((s: { played: number }) => s.played === 0),
|
||
|
|
).toBe(true);
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
for (const socket of sockets) socket.disconnect();
|
||
|
|
await service.close();
|
||
|
|
await store.close();
|
||
|
|
await rm(directory, { recursive: true, force: true });
|
||
|
|
}
|
||
|
|
});
|