fix(platform): preserve recoverable outcomes and terminal sessions
This commit is contained in:
@@ -73,7 +73,7 @@ function makeModule(log: string[], onRestore?: (deskinfo: unknown) => void): Gam
|
||||
};
|
||||
}
|
||||
|
||||
test('assertGameEntry rejects invalid identity, routes, seat counts, and module factories', () => {
|
||||
test('assertGameEntry rejects invalid identity, routes, resolver presence, and module factories', () => {
|
||||
const entry = makeEntry(() => makeModule([]));
|
||||
|
||||
assert.throws(() => assertGameEntry({ ...entry, key: '' }), /key/);
|
||||
@@ -81,11 +81,24 @@ test('assertGameEntry rejects invalid identity, routes, seat counts, and module
|
||||
for (const route of ['platform', 'agent', 'room']) {
|
||||
assert.throws(() => assertGameEntry({ ...entry, route }), /reserved.*route/);
|
||||
}
|
||||
assert.throws(() => assertGameEntry({ ...entry, resolveSeatCount: () => 0 }), /seat count/);
|
||||
assert.throws(() => assertGameEntry({ ...entry, resolveSeatCount: () => 2.5 }), /seat count/);
|
||||
assert.throws(() => assertGameEntry({ ...entry, resolveSeatCount: undefined as never }), /resolveSeatCount/);
|
||||
assert.throws(() => assertGameEntry({ ...entry, createModule: () => undefined as never }), /module/);
|
||||
});
|
||||
|
||||
test('GameEntry preflight does not call a resolver before its game-owned roomtype exists', () => {
|
||||
const calls: Array<readonly unknown[]> = [];
|
||||
const entry = {
|
||||
...makeEntry(() => makeModule([])),
|
||||
resolveSeatCount(roomtype: readonly unknown[]) {
|
||||
calls.push(roomtype);
|
||||
if (roomtype[1] !== 4) throw new Error('Game roomtype requires four seats at index 1');
|
||||
return 4;
|
||||
},
|
||||
};
|
||||
assert.doesNotThrow(() => new GameSessionHost(entry));
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
test('assertGameEntry disposes its validation module exactly once', () => {
|
||||
let disposals = 0;
|
||||
const entry = makeEntry(() => ({
|
||||
@@ -428,3 +441,76 @@ test('GameSessionHost returns to active after a restore handler throws', () => {
|
||||
assert.throws(() => sessions.restore({}), /restore failed/);
|
||||
assert.equal(sessions.state, 'active');
|
||||
});
|
||||
|
||||
for (const callback of ['attach', 'restore'] as const) {
|
||||
for (const throwsAfterStop of [false, true]) {
|
||||
test(`terminal dispose during ${callback} survives callback completion (throws=${throwsAfterStop})`, () => {
|
||||
let sessions: GameSessionHost;
|
||||
let disposals = 0;
|
||||
const failure = new Error('callback failed after disposal');
|
||||
sessions = new GameSessionHost(makeEntry(() => ({
|
||||
...makeModule([]),
|
||||
[callback]() {
|
||||
sessions.dispose();
|
||||
sessions.dispose();
|
||||
assert.equal(sessions.state, 'disposed');
|
||||
if (throwsAfterStop) throw failure;
|
||||
},
|
||||
dispose() { disposals += 1; },
|
||||
})));
|
||||
disposals = 0;
|
||||
const invoke = () => {
|
||||
sessions.open(makeHost());
|
||||
if (callback === 'restore') sessions.restore({ round: 1 });
|
||||
};
|
||||
if (throwsAfterStop) assert.equal(caughtError(invoke), failure);
|
||||
else assert.doesNotThrow(invoke);
|
||||
assert.equal(disposals, 1);
|
||||
assert.equal(sessions.state, 'disposed');
|
||||
sessions.dispose();
|
||||
assert.equal(disposals, 1);
|
||||
assert.throws(() => sessions.open(makeHost()), /disposed/);
|
||||
assert.throws(() => sessions.restore({}), /disposed/);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const release of ['close', 'dispose'] as const) {
|
||||
test(`${release} tolerates module.dispose reentering terminal disposal`, () => {
|
||||
let sessions: GameSessionHost | undefined;
|
||||
let disposals = 0;
|
||||
sessions = new GameSessionHost(makeEntry(() => ({
|
||||
...makeModule([]),
|
||||
dispose() {
|
||||
if (sessions === undefined) return;
|
||||
disposals += 1;
|
||||
sessions.dispose();
|
||||
},
|
||||
})));
|
||||
sessions.open(makeHost());
|
||||
assert.doesNotThrow(() => sessions![release]());
|
||||
assert.equal(sessions.state, 'disposed');
|
||||
assert.equal(disposals, 1);
|
||||
sessions.dispose();
|
||||
assert.equal(disposals, 1);
|
||||
});
|
||||
}
|
||||
|
||||
test('terminal disposal during module creation cleans the returned module without attaching it', () => {
|
||||
let sessions: GameSessionHost | undefined;
|
||||
let attaches = 0;
|
||||
let disposals = 0;
|
||||
sessions = new GameSessionHost(makeEntry(() => {
|
||||
if (sessions !== undefined) sessions.dispose();
|
||||
return {
|
||||
...makeModule([]),
|
||||
attach() { attaches += 1; },
|
||||
dispose() { disposals += 1; },
|
||||
};
|
||||
}));
|
||||
disposals = 0;
|
||||
assert.doesNotThrow(() => sessions!.open(makeHost()));
|
||||
assert.equal(disposals, 1);
|
||||
assert.equal(attaches, 0);
|
||||
assert.equal(sessions.state, 'disposed');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user