Files
craftdock/test/services/playerDirectory.test.js
T
Lucas RayanandClaude Sonnet 5.5 640e52c0c9 Initial commit: CraftDock as of 2026-11-01
The previous GitHub history was lost with the account (the local .git was missing
its refs and most objects), so this repository starts from the existing code.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 22:44:39 -03:00

109 lines
4.3 KiB
JavaScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import fsp from 'node:fs/promises';
import { updatePlayerDirectory, queryDirectory } from '../../src/services/playerDirectory.js';
vi.mock('node:fs/promises');
// A dockerService whose container returns the given log text, recording the opts
// each `logs()` call was made with (to assert incremental scanning).
function mockDocker(logText) {
const calls = [];
return {
calls,
getContainer: async () => ({
logs: async (opts) => { calls.push(opts); return Buffer.from(logText); },
}),
};
}
describe('playerDirectory', () => {
let store;
beforeEach(() => {
store = null;
vi.mocked(fsp.writeFile).mockImplementation(async (_p, data) => { store = data; });
vi.mocked(fsp.rm).mockResolvedValue();
// No existing directory / no legacy file by default.
vi.mocked(fsp.readFile).mockRejectedValue(new Error('ENOENT'));
});
it('records only players that actually spawned, keyed by XUID', async () => {
const log = [
'Player connected: Rejected, xuid: 111', // connect only → rejected by allowlist
'Player Spawned: Lucasrguerra xuid: 222', // real access
].join('\n');
const list = await updatePlayerDirectory({ dataRoot: '/data', dockerService: mockDocker(log), edition: 'bedrock' });
expect(list).toEqual([
expect.objectContaining({ xuid: '222', name: 'Lucasrguerra' }),
]);
// Rejected player (connected but never spawned) is absent.
expect(list.find((e) => e.name === 'Rejected')).toBeUndefined();
expect(JSON.parse(store)).toHaveProperty('222');
});
it('parses gamertags containing spaces', async () => {
const list = await updatePlayerDirectory({
dataRoot: '/data', dockerService: mockDocker('Player Spawned: Lucas Guerra xuid: 333'), edition: 'bedrock',
});
expect(list[0]).toMatchObject({ xuid: '333', name: 'Lucas Guerra' });
});
it('scans logs incrementally: bounded tail first, then since', async () => {
// Fresh module so the module-level scan cursor starts at 0 for this test.
vi.resetModules();
const { updatePlayerDirectory: fresh } = await import('../../src/services/playerDirectory.js');
const docker = mockDocker('Player Spawned: steve xuid: 1');
await fresh({ dataRoot: '/data', dockerService: docker, edition: 'bedrock' });
expect(docker.calls[0].tail).toBe(2000);
expect(docker.calls[0].since).toBeUndefined();
await fresh({ dataRoot: '/data', dockerService: docker, edition: 'bedrock' });
expect(docker.calls[1].since).toBeGreaterThan(0);
expect(docker.calls[1].tail).toBeUndefined();
});
it('keeps previously-seen players even when no longer spawning (Scenario 2)', async () => {
// Existing directory already has an old player; a new empty scan must not drop them.
vi.mocked(fsp.readFile).mockResolvedValue(JSON.stringify({
'999': { name: 'OldPlayer', xuid: '999', firstSeen: 'x', lastSeen: 'x' },
}));
const list = await updatePlayerDirectory({ dataRoot: '/data', dockerService: mockDocker(''), edition: 'bedrock' });
expect(list).toEqual([expect.objectContaining({ xuid: '999', name: 'OldPlayer' })]);
});
});
describe('queryDirectory', () => {
beforeEach(() => {
// 30 players named Player00..Player29, keyed by xuid.
const dir = {};
for (let i = 0; i < 30; i++) {
const n = String(i).padStart(2, '0');
dir[`x${n}`] = { xuid: `x${n}`, name: `Player${n}` };
}
vi.mocked(fsp.readFile).mockResolvedValue(JSON.stringify(dir));
});
it('returns the requested page with total', async () => {
const r = await queryDirectory('/data', { page: 2, pageSize: 25 });
expect(r.total).toBe(30);
expect(r.page).toBe(2);
expect(r.items).toHaveLength(5);
expect(r.items[0].name).toBe('Player25');
});
it('filters by name (case-insensitive) and paginates the filtered set', async () => {
const r = await queryDirectory('/data', { q: 'player0', pageSize: 100 });
expect(r.total).toBe(10); // Player00..Player09
expect(r.items.every((e) => e.name.startsWith('Player0'))).toBe(true);
});
it('clamps an out-of-range page to the last page', async () => {
const r = await queryDirectory('/data', { page: 99, pageSize: 25 });
expect(r.page).toBe(2);
expect(r.items).toHaveLength(5);
});
});