0.2.3: Clementia S2 releases a dead, hidden, uncarriable or pathless rider at once (slot and weight back before the same tick's boarding checks); same-browser match recovery never takes the seat of a window that is reloading or still starting (a tab keeps its own session token; seats are stamped at the server's welcome, the older welcome wins); golden: diy-135 moves (outcome unchanged)
This commit is contained in:
1 parent
821ab827bd
commit
ba5b998a72
19 files changed
+580
-60
No files matched your search
@@ -1310,6 +1310,83 @@ describe('multi-device & browser compatibility (static)', () => {
|
||||
});
|
||||
|
||||
describe('explicit local match recovery (#431)', () => {
|
||||
async function recoveryPair() {
|
||||
const { createIdentity } = await mod('net.js');
|
||||
const local = memStorage(), hub = channelHub(), ownerSession = memStorage(), guestSession = memStorage();
|
||||
const ownerChannel = hub.create();
|
||||
const owner = createIdentity({ local, session: ownerSession, tabId: 'z-owner', channel: ownerChannel, queryMs: 5, now: () => 100 });
|
||||
const guest = createIdentity({ local, session: guestSession, tabId: 'a-guest', channel: hub.create(), queryMs: 5, now: () => 200 });
|
||||
await owner.init(); owner.saveToken('seat-owner'); owner.rememberMatch({ name: 'Owner', code: 'ROOM1' });
|
||||
await guest.init(); guest.saveToken('seat-guest');
|
||||
return { createIdentity, local, hub, ownerSession, guestSession, ownerChannel, owner, guest, id: guest.recoverable()[0].id };
|
||||
}
|
||||
test('live: a smaller tab id cannot reclaim an active seat', async () => {
|
||||
const { guest, owner, id } = await recoveryPair();
|
||||
assert.equal(await guest.resume(id), false);
|
||||
assert.equal(guest.getToken(), 'seat-guest');
|
||||
assert.equal(owner.getToken(), 'seat-owner');
|
||||
});
|
||||
test('reload: the original session wins over a recovery attempt during navigation', async () => {
|
||||
const p = await recoveryPair();
|
||||
p.ownerChannel.close();
|
||||
assert.equal(await p.guest.resume(p.id), true, 'old document is gone');
|
||||
const reloaded = p.createIdentity({ local: p.local, session: p.ownerSession, tabId: 'zz-reloaded', channel: p.hub.create(), queryMs: 5 });
|
||||
assert.equal(await reloaded.init(), 'seat-owner');
|
||||
assert.equal(p.ownerSession.getItem('sp.token'), 'seat-owner');
|
||||
assert.equal(p.guest.getToken(), 'seat-guest', 'unconfirmed recovery yields before hello');
|
||||
});
|
||||
test('midInit: the session token answers claims before init finishes', async () => {
|
||||
const p = await recoveryPair();
|
||||
p.ownerChannel.close();
|
||||
let finish;
|
||||
const reloaded = p.createIdentity({ local: p.local, session: p.ownerSession, tabId: 'zz-reloaded', channel: p.hub.create(), setTimeout: (fn) => { finish = fn; } });
|
||||
const pending = reloaded.init();
|
||||
assert.equal(await p.guest.resume(p.id), false);
|
||||
finish();
|
||||
assert.equal(await pending, 'seat-owner');
|
||||
assert.equal(p.ownerSession.getItem('sp.token'), 'seat-owner');
|
||||
assert.equal(p.guest.getToken(), 'seat-guest');
|
||||
});
|
||||
test('an occupied session token never falls back to a different recent identity', async () => {
|
||||
const p = await recoveryPair();
|
||||
p.local.setItem('sp.tokens', JSON.stringify(['seat-free', 'seat-owner']));
|
||||
const duplicate = p.createIdentity({ local: p.local, session: p.ownerSession.clone(), tabId: '0-copy', channel: p.hub.create(), queryMs: 5 });
|
||||
assert.equal(await duplicate.init(), null);
|
||||
assert.equal(duplicate.getToken(), null);
|
||||
});
|
||||
test('resume persists neither its token nor a welcome stamp until the server welcomes it', async () => {
|
||||
const p = await recoveryPair();
|
||||
p.ownerChannel.close();
|
||||
const stamp = p.guestSession.getItem('sp.tokenWelcome');
|
||||
assert.equal(await p.guest.resume(p.id), true);
|
||||
assert.equal(p.guestSession.getItem('sp.token'), 'seat-guest');
|
||||
assert.equal(p.guestSession.getItem('sp.tokenWelcome'), stamp);
|
||||
p.guest.saveToken('seat-owner');
|
||||
assert.equal(p.guestSession.getItem('sp.token'), 'seat-owner');
|
||||
assert.ok(p.guestSession.getItem('sp.tokenWelcome'));
|
||||
});
|
||||
test('an earlier welcome stamp survives reload and beats a later live welcome', async () => {
|
||||
const p = await recoveryPair();
|
||||
p.ownerChannel.close();
|
||||
assert.equal(await p.guest.resume(p.id), true);
|
||||
p.guest.saveToken('seat-owner'); // recovery's welcome is later than the original owner's
|
||||
const originalStamp = p.ownerSession.getItem('sp.tokenWelcome');
|
||||
const reloaded = p.createIdentity({ local: p.local, session: p.ownerSession, tabId: 'zz-reloaded', channel: p.hub.create(), queryMs: 5, now: () => 300 });
|
||||
assert.equal(await reloaded.init(), 'seat-owner');
|
||||
reloaded.saveToken('seat-owner');
|
||||
assert.equal(p.ownerSession.getItem('sp.tokenWelcome'), originalStamp, 'reconnect does not reset seniority');
|
||||
});
|
||||
test('a welcome received during init cannot be overwritten or erased by its pending result', async () => {
|
||||
const { createIdentity } = await mod('net.js');
|
||||
const local = memStorage(), session = memStorage(), hub = channelHub();
|
||||
session.setItem('sp.token', 'old');
|
||||
let finish;
|
||||
const id = createIdentity({ local, session, channel: hub.create(), setTimeout: (fn) => { finish = fn; } });
|
||||
const pending = id.init();
|
||||
id.saveToken('new'); finish();
|
||||
assert.equal(await pending, 'new');
|
||||
assert.equal(session.getItem('sp.token'), 'new');
|
||||
});
|
||||
test('an idle tab can reclaim a closed match tab without taking a live one', async () => {
|
||||
const { createIdentity } = await mod('net.js');
|
||||
const local = memStorage(), hub = channelHub(), ca = hub.create(), cb = hub.create();
|
||||
|
||||
Reference in new issue
Block a user