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:
Lucas RayanandClaude Sonnet 5.5 committed 2026-11-01 22:44:39 -03:00
commit 640e52c0c9
99 files changed
+12115

No files matched your search

+149
View File
@@ -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);
});
});