Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/tests/backend/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const init = async function () {
* @param {string} event - The socket.io Socket event to listen for.
* @returns The argument(s) passed to the event handler.
*/
export const waitForSocketEvent = async (socket: any, event:string) => {
export const waitForSocketEvent = async (socket: any, event:string, timeoutMs = 1000) => {
const errorEvents = [
'error',
'connect_error',
Expand All @@ -126,7 +126,7 @@ export const waitForSocketEvent = async (socket: any, event:string) => {
const timeout = setTimeout(() => {
reject(new Error(`timed out waiting for ${event} event`));
cancelTimeout = () => {};
}, 1000);
}, timeoutMs);
cancelTimeout = () => {
clearTimeout(timeout);
resolve();
Expand Down Expand Up @@ -185,7 +185,9 @@ export const connect = async (res:any = null) => {
query: {cookie: reqCookieHdr, padId},
});
try {
await waitForSocketEvent(socket, 'connect');
// Connect is a known slow path on loaded CI runners — give it a longer budget than the
// default per-message wait used elsewhere.
await waitForSocketEvent(socket, 'connect', 5000);
} catch (e) {
socket.close();
throw e;
Expand Down Expand Up @@ -213,7 +215,9 @@ export const handshake = async (socket: any, padId:string, token = padutils.gene
token,
});
logger.debug('waiting for CLIENT_VARS response...');
const msg = await waitForSocketEvent(socket, 'message');
// CLIENT_VARS is a known slow path on loaded CI runners (auth + pad load) — give it a longer
// budget than the default per-message wait used elsewhere.
const msg = await waitForSocketEvent(socket, 'message', 5000);
logger.debug('received CLIENT_VARS message');
return msg;
};
Expand Down
55 changes: 34 additions & 21 deletions src/tests/backend/specs/SessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ describe(__filename, function () {
const destroy = async () => await util.promisify(ss!.destroy).call(ss, sid);
const touch = async (sess: Session) => await util.promisify(ss!.touch).call(ss, sid, sess);

// Poll until `cond` is true. Used in place of fixed sleeps for "the cleanup timer should have
// fired by now" assertions — passes immediately when cleanup completes so tests stay fast,
// but tolerates slow CI runners where the event loop may be delayed by hundreds of ms.
const eventually = async (cond: () => Promise<boolean>, maxMs = 2000, intervalMs = 25) => {
const deadline = Date.now() + maxMs;
// First check is immediate so the helper doesn't add a fixed delay.
while (true) {
if (await cond()) return;
if (Date.now() >= deadline) throw new Error(`condition not met within ${maxMs}ms`);
await new Promise((r) => setTimeout(r, intervalMs));
}
};

before(async function () {
await common.init();
});
Expand Down Expand Up @@ -57,12 +70,11 @@ describe(__filename, function () {
});

it('set of session that expires', async function () {
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 300)}};
await set(sess);
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
await new Promise((resolve) => setTimeout(resolve, 110));
// Writing should start a timeout.
assert(await db.get(`sessionstorage:${sid}`) == null);
// Writing should start a timeout that purges the record once expiry passes.
await eventually(async () => await db.get(`sessionstorage:${sid}`) == null);
});

it('set of already expired session', async function () {
Expand All @@ -75,18 +87,17 @@ describe(__filename, function () {
it('switch from non-expiring to expiring', async function () {
const sess:any = {foo: 'bar'};
await set(sess);
const sess2:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess2:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 300)}};
await set(sess2);
await new Promise((resolve) => setTimeout(resolve, 110));
assert(await db.get(`sessionstorage:${sid}`) == null);
await eventually(async () => await db.get(`sessionstorage:${sid}`) == null);
});

it('switch from expiring to non-expiring', async function () {
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 300)}};
await set(sess);
const sess2:any = {foo: 'bar'};
await set(sess2);
await new Promise((resolve) => setTimeout(resolve, 110));
await new Promise((resolve) => setTimeout(resolve, 330));
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess2));
});
});
Expand All @@ -109,12 +120,11 @@ describe(__filename, function () {
});

it('get of record from previous run (not yet expired)', async function () {
const sess = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess = {foo: 'bar', cookie: {expires: new Date(Date.now() + 300)}};
await db.set(`sessionstorage:${sid}`, sess);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess));
await new Promise((resolve) => setTimeout(resolve, 110));
// Reading should start a timeout.
assert(await db.get(`sessionstorage:${sid}`) == null);
// Reading should start a timeout that purges the record once expiry passes.
await eventually(async () => await db.get(`sessionstorage:${sid}`) == null);
});

it('get of record from previous run (already expired)', async function () {
Expand All @@ -125,44 +135,47 @@ describe(__filename, function () {
});

it('external expiration update is picked up', async function () {
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 300)}};
await set(sess);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess));
const sess2 = {...sess, cookie: {expires: new Date(Date.now() + 200)}};
const sess2 = {...sess, cookie: {expires: new Date(Date.now() + 600)}};
await db.set(`sessionstorage:${sid}`, sess2);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess2));
await new Promise((resolve) => setTimeout(resolve, 110));
await new Promise((resolve) => setTimeout(resolve, 330));
// The original timeout should not have fired.
assert.equal(JSON.stringify(await get()), JSON.stringify(sess2));
});
});

describe('shutdown', function () {
it('shutdown cancels timeouts', async function () {
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 100)}};
// expires=500 / wait=700 keeps comfortable headroom on slow CI: setup
// (set+get+shutdown) must finish before the timer would fire (500ms is plenty), and the
// 700ms wait is past the original expiry so a cancelled timer would have fired by then.
const sess:any = {foo: 'bar', cookie: {expires: new Date(Date.now() + 500)}};
await set(sess);
assert.equal(JSON.stringify(await get()), JSON.stringify(sess));
ss!.shutdown();
await new Promise((resolve) => setTimeout(resolve, 110));
await new Promise((resolve) => setTimeout(resolve, 700));
// The record should not have been automatically purged.
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
});
});

describe('destroy', function () {
it('destroy deletes the database record', async function () {
const sess:any = {cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {cookie: {expires: new Date(Date.now() + 300)}};
await set(sess);
await destroy();
assert(await db.get(`sessionstorage:${sid}`) == null);
});

it('destroy cancels the timeout', async function () {
const sess:any = {cookie: {expires: new Date(Date.now() + 100)}};
const sess:any = {cookie: {expires: new Date(Date.now() + 300)}};
await set(sess);
await destroy();
await db.set(`sessionstorage:${sid}`, sess);
await new Promise((resolve) => setTimeout(resolve, 110));
await new Promise((resolve) => setTimeout(resolve, 330));
assert.equal(JSON.stringify(await db.get(`sessionstorage:${sid}`)), JSON.stringify(sess));
});

Expand Down
Loading