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>
150 lines
6.5 KiB
JavaScript
150 lines
6.5 KiB
JavaScript
import { describe, it, expect, vi } from 'vitest';
|
|
import express from 'express';
|
|
import request from 'supertest';
|
|
import { createPlayersRouter } from '../../src/routes/players.js';
|
|
import { NotSupportedError } from '../../src/adapters/serverAdapter.js';
|
|
|
|
function makeApp(adapterOverrides = {}, { propertiesOverrides } = {}) {
|
|
const adapter = {
|
|
capabilities: new Set(['whitelistAdd', 'whitelistRemove', 'whitelistOn', 'whitelistOff', 'whitelistList', 'ban', 'give']),
|
|
_edition: 'java',
|
|
listPlayers: vi.fn().mockResolvedValue({ online: 0, max: 20, players: [] }),
|
|
whitelistAdd: vi.fn().mockResolvedValue('Added steve'),
|
|
whitelistRemove: vi.fn().mockResolvedValue('Removed steve'),
|
|
whitelistOn: vi.fn().mockResolvedValue('Whitelist enabled'),
|
|
whitelistOff: vi.fn().mockResolvedValue('Whitelist disabled'),
|
|
whitelistList: vi.fn().mockResolvedValue(['steve', 'alex']),
|
|
ban: vi.fn().mockResolvedValue('Banned'),
|
|
give: vi.fn().mockResolvedValue('Gave'),
|
|
...adapterOverrides,
|
|
};
|
|
const propertiesService = {
|
|
read: vi.fn().mockResolvedValue({ 'white-list': 'true' }),
|
|
update: vi.fn().mockResolvedValue({}),
|
|
...propertiesOverrides,
|
|
};
|
|
const appState = { getAdapter: vi.fn().mockResolvedValue(adapter) };
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use('/api/players', createPlayersRouter({ appState, propertiesService, config: { mcDataPath: '/data' } }));
|
|
return { app, adapter, propertiesService };
|
|
}
|
|
|
|
describe('players routes', () => {
|
|
it('rejects a name-requiring action with an empty name (does not call the adapter)', async () => {
|
|
const { app, adapter } = makeApp();
|
|
const res = await request(app).post('/api/players/whitelistRemove').send({ name: ' ' });
|
|
expect(res.status).toBe(400);
|
|
expect(res.body).toEqual({ error: 'name_required' });
|
|
expect(adapter.whitelistRemove).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('allows name-less actions (whitelistOn) without a name', async () => {
|
|
const { app, adapter } = makeApp();
|
|
const res = await request(app).post('/api/players/whitelistOn').send({});
|
|
expect(res.status).toBe(200);
|
|
expect(adapter.whitelistOn).toHaveBeenCalled();
|
|
});
|
|
|
|
it('GET / returns players and capability list (no embedded directory)', async () => {
|
|
const { app } = makeApp();
|
|
const res = await request(app).get('/api/players');
|
|
expect(res.body.capabilities).toContain('ban');
|
|
expect(res.body.players).toEqual({ online: 0, max: 20, players: [] });
|
|
expect(res.body.players.directory).toBeUndefined();
|
|
});
|
|
|
|
it('GET /directory returns a paginated shape with online annotation', async () => {
|
|
const { app } = makeApp();
|
|
const res = await request(app).get('/api/players/directory?page=1&pageSize=25');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toMatchObject({ page: 1, pageSize: 25, total: expect.any(Number) });
|
|
expect(Array.isArray(res.body.items)).toBe(true);
|
|
});
|
|
|
|
it('GET /detail requires an xuid', async () => {
|
|
const { app } = makeApp();
|
|
const res = await request(app).get('/api/players/detail');
|
|
expect(res.status).toBe(400);
|
|
expect(res.body).toEqual({ error: 'xuid_required' });
|
|
});
|
|
|
|
it('GET /detail returns 404 for an unknown player', async () => {
|
|
const { app } = makeApp();
|
|
const res = await request(app).get('/api/players/detail?xuid=doesnotexist');
|
|
expect(res.status).toBe(404);
|
|
});
|
|
|
|
it('GET / returns whitelistEnabled and whitelist', async () => {
|
|
const { app } = makeApp();
|
|
const res = await request(app).get('/api/players');
|
|
expect(res.body.whitelistEnabled).toBe(true);
|
|
expect(res.body.whitelist).toEqual(['steve', 'alex']);
|
|
});
|
|
|
|
it('GET / returns whitelistEnabled=false when property is false', async () => {
|
|
const { app } = makeApp({}, {
|
|
propertiesOverrides: { read: vi.fn().mockResolvedValue({ 'white-list': 'false' }) },
|
|
});
|
|
const res = await request(app).get('/api/players');
|
|
expect(res.body.whitelistEnabled).toBe(false);
|
|
});
|
|
|
|
it('GET / reads allow-list property for bedrock edition', async () => {
|
|
const { app } = makeApp({ _edition: 'bedrock' }, {
|
|
propertiesOverrides: { read: vi.fn().mockResolvedValue({ 'allow-list': 'true' }) },
|
|
});
|
|
const res = await request(app).get('/api/players');
|
|
expect(res.body.whitelistEnabled).toBe(true);
|
|
});
|
|
|
|
it('POST /whitelistAdd delegates with name', async () => {
|
|
const { app, adapter } = makeApp();
|
|
const res = await request(app).post('/api/players/whitelistAdd').send({ name: 'steve' });
|
|
expect(res.body).toEqual({ ok: true, output: 'Added steve' });
|
|
expect(adapter.whitelistAdd).toHaveBeenCalledWith('steve');
|
|
});
|
|
|
|
it('POST /whitelistOn calls adapter and persists to properties', async () => {
|
|
const { app, adapter, propertiesService } = makeApp();
|
|
const res = await request(app).post('/api/players/whitelistOn').send({});
|
|
expect(res.body.ok).toBe(true);
|
|
expect(adapter.whitelistOn).toHaveBeenCalled();
|
|
expect(propertiesService.update).toHaveBeenCalledWith({ 'white-list': 'true' });
|
|
});
|
|
|
|
it('POST /whitelistOff calls adapter and persists to properties', async () => {
|
|
const { app, adapter, propertiesService } = makeApp();
|
|
const res = await request(app).post('/api/players/whitelistOff').send({});
|
|
expect(res.body.ok).toBe(true);
|
|
expect(adapter.whitelistOff).toHaveBeenCalled();
|
|
expect(propertiesService.update).toHaveBeenCalledWith({ 'white-list': 'false' });
|
|
});
|
|
|
|
it('POST /whitelistOn for bedrock persists allow-list property', async () => {
|
|
const { app, adapter, propertiesService } = makeApp({ _edition: 'bedrock' });
|
|
const res = await request(app).post('/api/players/whitelistOn').send({});
|
|
expect(res.body.ok).toBe(true);
|
|
expect(propertiesService.update).toHaveBeenCalledWith({ 'allow-list': 'true' });
|
|
});
|
|
|
|
it('POST /give passes item and count', async () => {
|
|
const { app, adapter } = makeApp();
|
|
await request(app).post('/api/players/give').send({ name: 'steve', item: 'minecraft:dirt', count: 3 });
|
|
expect(adapter.give).toHaveBeenCalledWith('steve', 'minecraft:dirt', 3);
|
|
});
|
|
|
|
it('returns 409 on NotSupportedError', async () => {
|
|
const { app } = makeApp({ ban: vi.fn().mockRejectedValue(new NotSupportedError('ban')) });
|
|
const res = await request(app).post('/api/players/ban').send({ name: 'x' });
|
|
expect(res.status).toBe(409);
|
|
expect(res.body).toEqual({ error: 'not_supported' });
|
|
});
|
|
|
|
it('returns 400 for unknown action', async () => {
|
|
const { app } = makeApp();
|
|
const res = await request(app).post('/api/players/frobnicate').send({ name: 'x' });
|
|
expect(res.status).toBe(400);
|
|
});
|
|
});
|