Initial commit: ssessesse ki battle

This commit is contained in:
2026-10-13 11:15:26 +09:00
commit 4f24129e29
104 changed files with 10520 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createResultReporter } from '../src/client/result-reporter.js';
import { emptyPlayerStats, type ResultSubmission } from '../src/shared/p2p-types.js';
import { PENDING_RESULT_KEY } from '../constants/recording.js';
function storage() {
const data = new Map<string, string>();
return { getItem: (key: string) => data.get(key) ?? null, setItem: (key: string, value: string) => { data.set(key, value); }, removeItem: (key: string) => { data.delete(key); } };
}
const result: ResultSubmission = { matchId: 'm1', seq: 1, mode: 'friend', outcome: 'win', reason: 'hit' };
test('lost result response retries identical payload and clears persisted result only after acknowledgement', async () => {
const store = storage(), sent: ResultSubmission[] = []; const timers: { fn: () => void; ms: number }[] = [];
const reporter = createResultReporter({ gameSessionId: 's1', record: async data => {
sent.push(structuredClone(data)); if (sent.length === 1) throw new Error('network');
return { status: 'duplicate', stats: emptyPlayerStats() };
} }, store, { setTimeout: (fn, ms) => { timers.push({ fn, ms }); return timers.length; }, clearTimeout: () => {} });
const promise = reporter.submit(result);
await new Promise(r => setImmediate(r));
assert.ok(store.getItem(PENDING_RESULT_KEY)); assert.equal(timers[0].ms, 2000);
timers.shift()!.fn();
assert.equal((await promise).status, 'duplicate');
assert.deepEqual(sent, [result, result]); assert.equal(store.getItem(PENDING_RESULT_KEY), null);
reporter.destroy();
});
test('reload resumes the pending result but a different session never submits it', async () => {
const store = storage(); store.setItem(PENDING_RESULT_KEY, JSON.stringify({ gameSessionId: 's1', result }));
const sent: ResultSubmission[] = [];
const wrong = createResultReporter({ gameSessionId: 's2', record: async data => { sent.push(data); return { status: 'applied', stats: emptyPlayerStats() }; } }, store);
await assert.rejects(wrong.resumePending(), /SESSION_MISMATCH/); assert.equal(sent.length, 0); wrong.destroy();
const correct = createResultReporter({ gameSessionId: 's1', record: async data => { sent.push(data); return { status: 'applied', stats: emptyPlayerStats() }; } }, store);
assert.equal((await correct.resumePending())?.status, 'applied'); assert.deepEqual(sent, [result]); correct.destroy();
});
test('permanent rejection stops retries and a different result cannot replace an in-flight one', async () => {
const store = storage(); let count = 0;
const reporter = createResultReporter({ gameSessionId: 's1', record: async () => { count++; throw Object.assign(new Error('INVALID_SESSION'), { status: 401 }); } }, store);
const first = reporter.submit(result);
await assert.rejects(reporter.submit({ ...result, matchId: 'm2' }), /RESULT_PENDING/);
await assert.rejects(first, /INVALID_SESSION/); assert.equal(count, 1); assert.ok(store.getItem(PENDING_RESULT_KEY)); reporter.destroy();
});