100 lines
7.6 KiB
TypeScript
100 lines
7.6 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
import { MongoClient } from 'mongodb';
|
|
import { DEFAULT_GLOBAL_SETTINGS, GLOBAL_SETTINGS_CACHE_KEY } from '../constants/global-settings.js';
|
|
import { createGlobalSettingsCache } from '../src/client/global-settings.js';
|
|
import { createGameServer } from '../src/server/create-server.js';
|
|
import { createGlobalSettingsStore } from '../src/server/global-settings.js';
|
|
import { createGameSessionStore } from '../src/server/game-session-store.js';
|
|
import { createRoomStore } from '../src/server/rooms.js';
|
|
import { createMatchEngine } from '../src/shared/match-engine.js';
|
|
import { isGlobalSettings } from '../src/shared/global-settings.js';
|
|
|
|
test('ranked acceptance timeout is a configurable positive duration', () => {
|
|
assert.equal(DEFAULT_GLOBAL_SETTINGS.rankedAcceptTimeoutMs, 10000);
|
|
assert.equal(isGlobalSettings({ ...DEFAULT_GLOBAL_SETTINGS, rankedAcceptTimeoutMs: 5000 }), true);
|
|
for (const rankedAcceptTimeoutMs of [0, -1, 60001, 1500.5]) {
|
|
assert.equal(isGlobalSettings({ ...DEFAULT_GLOBAL_SETTINGS, rankedAcceptTimeoutMs }), false);
|
|
}
|
|
});
|
|
|
|
test('browser caches settings, checks revision and retains valid settings through failures', async () => {
|
|
const memory = new Map<string, string>(), urls: string[] = [];
|
|
let settings = { ...DEFAULT_GLOBAL_SETTINGS }, offline = false, writes = 0;
|
|
const storage = { getItem: (key: string) => memory.get(key) ?? null, setItem: (key: string, value: string) => { writes++; memory.set(key, value); } };
|
|
const send = async (input: any) => {
|
|
urls.push(String(input)); if (offline) throw new Error('offline');
|
|
const revision = new URL(String(input), 'http://test').searchParams.get('revision');
|
|
return Response.json({ ok: true, data: { revision: settings.revision, ...(Number(revision) === settings.revision ? {} : { settings }) } });
|
|
};
|
|
const cache = createGlobalSettingsCache(storage, send);
|
|
assert.deepEqual(await cache.refresh(), settings); assert.equal(writes, 1);
|
|
const reloaded = createGlobalSettingsCache(storage, send);
|
|
assert.deepEqual(await reloaded.refresh(), settings); assert.equal(urls.at(-1), '/api/global-settings?revision=1'); assert.equal(writes, 1);
|
|
settings = { ...DEFAULT_GLOBAL_SETTINGS, revision: 2, turnDurationMs: 2000, finishCutsceneSpeed: 2 };
|
|
assert.deepEqual(await reloaded.refresh(), settings); assert.equal(writes, 2);
|
|
offline = true; assert.deepEqual(await reloaded.refresh(), settings);
|
|
memory.set(GLOBAL_SETTINGS_CACHE_KEY, '{bad json');
|
|
assert.deepEqual(await createGlobalSettingsCache(storage, send).refresh(), DEFAULT_GLOBAL_SETTINGS);
|
|
});
|
|
|
|
test('match settings survive reload and early resolution still rejects missing choices', () => {
|
|
let now = 0;
|
|
const settings = { ...DEFAULT_GLOBAL_SETTINGS, revision: 2, turnDurationMs: 5000, finishCutsceneSpeed: 2 };
|
|
const config = { matchId: 'settings', mode: 'friend' as const, selfIndex: 0 as const, clock: { now: () => now } };
|
|
const engine = createMatchEngine({ ...config, settings });
|
|
engine.start(); now = 3000; engine.beginTurn();
|
|
assert.equal(engine.view().deadline, 8000);
|
|
assert.throws(() => engine.resolve(['charge', null]), /BEFORE_DEADLINE/);
|
|
engine.resolve(['charge', 'charge']); assert.equal(engine.view().phase, 'revealing');
|
|
const restored = createMatchEngine(config); restored.restore(engine.checkpoint());
|
|
now = 4200; restored.beginTurn();
|
|
assert.equal(restored.view().deadline, 9200); assert.deepEqual(restored.view().settings, settings);
|
|
});
|
|
|
|
test('DB settings update without restart and friend matches share an immutable snapshot', { skip: !process.env.TEST_MONGODB_URI }, async () => {
|
|
const databaseName = 'dragon_ball_test_settings_' + randomUUID().replaceAll('-', '');
|
|
const client = await new MongoClient(process.env.TEST_MONGODB_URI!).connect(), db = client.db(databaseName);
|
|
const app = await createGameServer({ mongoUri: process.env.TEST_MONGODB_URI!, databaseName, clientDirectory: 'dist/client', adminUser: 'test', adminPassword: 'test-password', environment: 'test' });
|
|
const headers = { authorization: 'Basic ' + Buffer.from('test:test-password').toString('base64') };
|
|
try {
|
|
const store = createGlobalSettingsStore(db);
|
|
assert.deepEqual(await store.get(), DEFAULT_GLOBAL_SETTINGS);
|
|
assert.equal((await app.inject({ method: 'POST', url: '/api/admin/global-settings', payload: DEFAULT_GLOBAL_SETTINGS })).statusCode, 401);
|
|
const updated = { ...DEFAULT_GLOBAL_SETTINGS, revision: 1, turnDurationMs: 5000, finishCutsceneSpeed: 2 };
|
|
const save = await app.inject({ method: 'POST', url: '/api/admin/global-settings', headers, payload: updated });
|
|
assert.equal(save.statusCode, 200); assert.equal(save.json().data.revision, 2);
|
|
assert.equal((await app.inject({ method: 'POST', url: '/api/admin/global-settings', headers, payload: updated })).statusCode, 409);
|
|
assert.equal((await app.inject({ method: 'POST', url: '/api/admin/global-settings', headers, payload: { ...updated, revision: 2, finishCutsceneSpeed: 0 } })).statusCode, 400);
|
|
assert.equal((await app.inject('/api/global-settings?revision=1')).json().data.settings.turnDurationMs, 5000);
|
|
assert.deepEqual((await app.inject('/api/global-settings?revision=2')).json().data, { revision: 2 });
|
|
const sessions = await createGameSessionStore(db), rooms = await createRoomStore(db);
|
|
const a = await sessions.create(randomBytes(32).toString('base64url')), b = await sessions.create(randomBytes(32).toString('base64url'));
|
|
const room = await rooms.create(a.gameSessionId, 'create'); await rooms.join(b.gameSessionId, room.code);
|
|
const input = { requestId: 'register', matchId: room.currentMatchId, mode: 'friend' as const, roomId: room.id, expectedPreviousSeq: 0 };
|
|
const first = await sessions.register(a, input);
|
|
await store.update({ ...DEFAULT_GLOBAL_SETTINGS, revision: 2, turnDurationMs: 2000, finishCutsceneSpeed: 3 });
|
|
const second = await sessions.register(b, input);
|
|
assert.deepEqual(first.settings, second.settings); assert.equal(second.settings?.revision, 2);
|
|
const c = await sessions.create(randomBytes(32).toString('base64url'));
|
|
const bot = await sessions.register(c, { requestId: 'bot', matchId: 'bot', mode: 'bot', expectedPreviousSeq: 0 });
|
|
assert.equal(bot.settings?.revision, 3); assert.equal(bot.settings?.finishCutsceneSpeed, 3);
|
|
await db.collection('global_settings').updateOne({ _id: 'global' as any }, { $unset: { matchingDelayMs: '', matchSuccessDelayMs: '', matchPreparationDelayMs: '', rankedAcceptTimeoutMs: '' } });
|
|
await store.initialize();
|
|
const migrated = await store.get();
|
|
assert.equal(migrated.matchingDelayMs, 1000); assert.equal(migrated.matchSuccessDelayMs, 1000);
|
|
assert.equal(migrated.matchPreparationDelayMs, 3000);
|
|
assert.equal(migrated.rankedAcceptTimeoutMs, 10000);
|
|
assert.equal(migrated.finishCutsceneSpeed, 3); assert.equal(migrated.turnDurationMs, 2000);
|
|
assert.ok(migrated.revision > 3);
|
|
await store.initialize(); assert.deepEqual(await store.get(), migrated);
|
|
const changed = await store.update({ ...migrated, matchingDelayMs: 4000, matchSuccessDelayMs: 2000, matchPreparationDelayMs: 5000, rankedAcceptTimeoutMs: 7000 });
|
|
assert.equal(changed.matchingDelayMs, 4000); assert.equal(changed.matchSuccessDelayMs, 2000);
|
|
assert.equal((await store.get()).matchPreparationDelayMs, 5000);
|
|
assert.equal((await store.get()).rankedAcceptTimeoutMs, 7000);
|
|
await assert.rejects(store.update({ ...changed, matchPreparationDelayMs: -1 }), /INVALID_SETTINGS/);
|
|
await assert.rejects(store.update({ ...changed, matchingDelayMs: -1 }), /INVALID_SETTINGS/);
|
|
} finally { await app.close(); await db.dropDatabase(); await client.close(); }
|
|
});
|