fix(platform): snapshot session fault errors safely
This commit is contained in:
@@ -416,6 +416,41 @@ test('RuntimeSession flattens a foreign-realm branded open fault before lease in
|
||||
);
|
||||
});
|
||||
|
||||
test('RuntimeSession snapshots branded open errors without invoking their hostile iterator', () => {
|
||||
const setup = makeLeaseSession();
|
||||
const iteratorReplacement = new Error('iterator replacement');
|
||||
let iteratorReads = 0;
|
||||
const sourceErrors = [setup.errors.attach, setup.errors.dispose];
|
||||
Object.defineProperty(sourceErrors, Symbol.iterator, {
|
||||
configurable: true,
|
||||
get() {
|
||||
iteratorReads += 1;
|
||||
sourceErrors[0] = iteratorReplacement;
|
||||
throw new Error('source iterator must not run');
|
||||
},
|
||||
});
|
||||
const brandedFault = new Error('hostile branded open fault');
|
||||
Object.defineProperties(brandedFault, {
|
||||
[Symbol.for('youle.framework.sdk.GameSessionOpenFault')]: { value: true },
|
||||
errors: { value: sourceErrors },
|
||||
});
|
||||
setup.setFailures('invalidate');
|
||||
setup.gameSession.open = () => { throw brandedFault; };
|
||||
|
||||
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(iteratorReads, 0);
|
||||
assert.equal(sourceErrors[0], setup.errors.attach);
|
||||
assert.deepEqual(
|
||||
(error as Error & { errors?: readonly unknown[] }).errors,
|
||||
[setup.errors.attach, setup.errors.dispose, setup.errors.invalidate],
|
||||
);
|
||||
});
|
||||
|
||||
test('RuntimeSession keeps ordinary aggregates and unbranded or accessor spoofs as single errors', () => {
|
||||
const primary = new Error('user primary');
|
||||
const cleanup = new Error('user cleanup');
|
||||
@@ -445,12 +480,26 @@ test('RuntimeSession keeps ordinary aggregates and unbranded or accessor spoofs
|
||||
},
|
||||
errors: { value: [primary, cleanup] },
|
||||
});
|
||||
const descriptorTarget = new Error('descriptor failure');
|
||||
Object.defineProperties(descriptorTarget, {
|
||||
[Symbol.for('youle.framework.sdk.GameSessionOpenFault')]: { value: true },
|
||||
errors: { value: [primary, cleanup] },
|
||||
});
|
||||
let descriptorReads = 0;
|
||||
const descriptorFailure = new Proxy(descriptorTarget, {
|
||||
getOwnPropertyDescriptor(target, key) {
|
||||
descriptorReads += 1;
|
||||
if (key === 'errors') throw new Error('descriptor trap failed');
|
||||
return Reflect.getOwnPropertyDescriptor(target, key);
|
||||
},
|
||||
});
|
||||
|
||||
for (const candidate of [
|
||||
ordinaryAggregate,
|
||||
unbranded,
|
||||
accessorSpoof,
|
||||
brandAccessorSpoof,
|
||||
descriptorFailure,
|
||||
]) {
|
||||
const setup = makeLeaseSession();
|
||||
setup.setFailures('invalidate');
|
||||
@@ -467,4 +516,5 @@ test('RuntimeSession keeps ordinary aggregates and unbranded or accessor spoofs
|
||||
assert.equal(setup.leases[0]?.invalidations, 1);
|
||||
}
|
||||
assert.equal(accessorReads, 0);
|
||||
assert.equal(descriptorReads, 2);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user