fix(platform): preserve session failures and URL syntax
This commit is contained in:
@@ -366,3 +366,20 @@ test('RuntimeSession aggregates event, dispose, and invalidation failures after
|
||||
[setup.errors.event, setup.errors.dispose, setup.errors.invalidate],
|
||||
);
|
||||
});
|
||||
|
||||
test('RuntimeSession flattens attach cleanup failures before appending lease invalidation', () => {
|
||||
const setup = makeLeaseSession();
|
||||
setup.setFailures('attach', 'dispose', 'invalidate');
|
||||
|
||||
const error = caughtError(() => setup.session.handleLogin(fixture('player-login-room.json')));
|
||||
|
||||
assert.equal(setup.session.state, 'faulted');
|
||||
assert.equal(setup.session.fault, error);
|
||||
assert.equal(setup.gameSession.state, 'idle');
|
||||
assert.equal(setup.leases[0]?.invalidations, 1);
|
||||
assert.equal(setup.getModuleDisposals(), 1);
|
||||
assert.deepEqual(
|
||||
(error as Error & { errors?: readonly unknown[] }).errors,
|
||||
[setup.errors.attach, setup.errors.dispose, setup.errors.invalidate],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -512,6 +512,10 @@ test('PlatformHandlers validates every switch URL before closing the active game
|
||||
'http://invalid.example',
|
||||
'ws://user@example.com',
|
||||
'ws://example.com/#fragment',
|
||||
'ws://example.com/#',
|
||||
'ws://example.com#',
|
||||
'ws://@example.com',
|
||||
'ws://:@example.com',
|
||||
'ws:///path',
|
||||
'ws://example.com:99999',
|
||||
'ws://example.com:abc',
|
||||
@@ -544,7 +548,10 @@ test('PlatformHandlers validates every switch URL before closing the active game
|
||||
|
||||
test('PlatformHandlers accepts case-insensitive WS/WSS schemes and preserves full payload identity', () => {
|
||||
const setup = makeHandlers();
|
||||
const roomData = { roomserver: 'WS://EXAMPLE.com:80/path?x=1', extra: { trace: 1 } };
|
||||
const roomData = {
|
||||
roomserver: 'WS://EXAMPLE.com:80/path/%23anchor?x=1',
|
||||
extra: { trace: 1 },
|
||||
};
|
||||
const agentData = {
|
||||
agentserver: 'WSS://secure.example:443/socket', opt: 'free_room', extra: { trace: 2 },
|
||||
};
|
||||
|
||||
@@ -39,6 +39,16 @@ function makeEntry(createModule: () => GameModule): GameEntry {
|
||||
};
|
||||
}
|
||||
|
||||
function caughtError(operation: () => void): Error {
|
||||
try {
|
||||
operation();
|
||||
} catch (error) {
|
||||
assert.ok(error instanceof Error);
|
||||
return error;
|
||||
}
|
||||
assert.fail('expected operation to throw');
|
||||
}
|
||||
|
||||
function makeModule(log: string[], onRestore?: (deskinfo: unknown) => void): GameModule {
|
||||
let attached = false;
|
||||
return {
|
||||
@@ -153,6 +163,64 @@ test('GameSessionHost disposes a module when attachment throws and returns to id
|
||||
assert.equal(sessions.state, 'idle');
|
||||
});
|
||||
|
||||
test('GameSessionHost preserves create and contract failures when open cleanup succeeds', () => {
|
||||
const createError = new Error('create failed');
|
||||
let createCalls = 0;
|
||||
const createSessions = new GameSessionHost(makeEntry(() => {
|
||||
createCalls += 1;
|
||||
if (createCalls === 1) return makeModule([]);
|
||||
throw createError;
|
||||
}));
|
||||
|
||||
assert.equal(caughtError(() => createSessions.open(makeHost())), createError);
|
||||
assert.equal(createSessions.state, 'idle');
|
||||
|
||||
let contractCalls = 0;
|
||||
let contractDisposals = 0;
|
||||
const contractSessions = new GameSessionHost(makeEntry(() => {
|
||||
contractCalls += 1;
|
||||
if (contractCalls === 1) return makeModule([]);
|
||||
return {
|
||||
handlePlatformEvent() {},
|
||||
handleGameMessage() {},
|
||||
restore() {},
|
||||
dispose() { contractDisposals += 1; },
|
||||
} as unknown as GameModule;
|
||||
}));
|
||||
|
||||
assert.match(caughtError(() => contractSessions.open(makeHost())).message, /implement attach/);
|
||||
assert.equal(contractDisposals, 1);
|
||||
assert.equal(contractSessions.state, 'idle');
|
||||
});
|
||||
|
||||
test('GameSessionHost preserves attach then dispose failures by identity and occurrence order', () => {
|
||||
const attachError = new Error('attach primary');
|
||||
const disposeError = new Error('open cleanup');
|
||||
let createCalls = 0;
|
||||
let disposals = 0;
|
||||
const sessions = new GameSessionHost(makeEntry(() => {
|
||||
createCalls += 1;
|
||||
if (createCalls === 1) return makeModule([]);
|
||||
return {
|
||||
...makeModule([]),
|
||||
attach() { throw attachError; },
|
||||
dispose() {
|
||||
disposals += 1;
|
||||
throw disposeError;
|
||||
},
|
||||
};
|
||||
}));
|
||||
|
||||
const error = caughtError(() => sessions.open(makeHost()));
|
||||
|
||||
assert.deepEqual(
|
||||
(error as Error & { errors?: readonly unknown[] }).errors,
|
||||
[attachError, disposeError],
|
||||
);
|
||||
assert.equal(disposals, 1);
|
||||
assert.equal(sessions.state, 'idle');
|
||||
});
|
||||
|
||||
test('GameSessionHost disposes a module exactly once and becomes terminal after dispose', () => {
|
||||
const log: string[] = [];
|
||||
const sessions = new GameSessionHost(makeEntry(() => makeModule(log)));
|
||||
|
||||
Reference in New Issue
Block a user