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>
This commit is contained in:
commit
640e52c0c9
99 files changed
+12115
No files matched your search
@@ -0,0 +1,149 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user