Ctrl K
ComunidadGuías…

__tests__/session-revive.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
 * A SESSION THAT TIMED OUT COMES BACK WHEN IT IS USED AGAIN (2026-11-07). 74 sessions in 40 hours closed
 * for being idle and the AI using them read "no such session" as the browser restarting. These keep the
 * reopening honest: same id, same owner only, same profile and page, same device, and it says so.
 */
import { describe, it, expect } from 'vitest';
import { createRequire } from 'module';
import express from 'express';

const require = createRequire(import.meta.url);
const { makeReviver, revivedNote } = require('../src/sessionRevive');
const { BrowserPool } = require('../src/pool');

function fakePool({ failCreate = false } = {}) {
  const pool = { sessions: new Map(), resting: new Map(), created: [], visited: [] };
  pool.putResting = BrowserPool.prototype.putResting.bind(pool);
  pool.takeResting = BrowserPool.prototype.takeResting.bind(pool);
  pool.createSession = async (o) => {
    if (failCreate) throw Object.assign(new Error('profile "facebook" is in use'), { status: 409 });
    pool.created.push(o);
    pool.sessions.set(o.id, { id: o.id, owner: o.owner, profile: o.profile, page: { goto: async (u) => { pool.visited.push(u); } } });
    return { sessionId: o.id };
  };
  pool.get = (id) => { const s = pool.sessions.get(id); if (!s) throw Object.assign(new Error('no such session'), { status: 404 }); return s; };
  return pool;
}
/* A tiny stand-in for supertest (not a dependency here): listen, call, close. */
function request(a) {
  const call = (method, path, body) => new Promise((resolve, reject) => {
    const srv = a.listen(0, async () => {
      try {
        const r = await fetch(`http://127.0.0.1:${srv.address().port}${path}`, { method, headers: { 'content-type': 'application/json' }, ...(body ? { body: JSON.stringify(body) } : {}) });
        const text = await r.text(); let json = {}; try { json = JSON.parse(text); } catch {}
        resolve({ status: r.status, body: json, headers: Object.fromEntries(r.headers) });
      } catch (e) { reject(e); } finally { srv.close(); }
    });
  });
  return { post: (p) => ({ send: (b) => call('POST', p, b) }), delete: (p) => call('DELETE', p) };
}
function app(pool, owner = 'carla') {
  const a = express();
  a.use((req, _res, next) => { req.client = { owner, maxConcurrent: 6, console: false }; next(); });
  a.use('/v1/sessions/:id', makeReviver({ pool, log: { info() {}, warn() {} } }));
  a.post('/v1/sessions/:id/navigate', (req, res) => { const s = pool.get(req.params.id); res.json({ ok: true, ring: s.ring || null }); });
  a.delete('/v1/sessions/:id', (req, res) => { try { pool.get(req.params.id); res.json({ closed: true }); } catch { res.status(404).json({ error: 'no such session' }); } });
  return a;
}
const rest = (o = {}) => ({ owner: 'carla', profile: 'facebook', url: 'https://www.facebook.com/profile.php?id=1', ring: null, closedAt: Date.now() - 7 * 60000, unusedMs: 20 * 60000, why: 'idle', ...o });

describe('a timed-out session is reopened by its next call', () => {
  it('under the same id, in the same profile, back on the same page — and the reply says so', async () => {
    const pool = fakePool(); pool.putResting('s-1', rest());
    const r = await request(app(pool)).post('/v1/sessions/s-1/navigate').send({});
    expect(r.status).toBe(200);
    expect(pool.created[0]).toMatchObject({ id: 's-1', owner: 'carla', profile: 'facebook' });
    expect(pool.visited).toEqual(['https://www.facebook.com/profile.php?id=1']);
    const note = revivedNote(r.headers['x-gb-revived']);
    expect(note).toMatch(/was closed 7 minutes ago after 20 minutes without use and was reopened with the same id in the "facebook" profile/);
    expect(revivedNote(encodeURIComponent(JSON.stringify({ unusedMinutes: 20, closedMinutesAgo: 1 })))).toMatch(/closed 1 minute ago after 20 minutes without use/);
    expect(note).toMatch(/anything typed or uploaded but not saved is gone/);
  });
  it('a session that ran on a device goes back to that device', async () => {
    const pool = fakePool(); pool.putResting('s-2', rest({ ring: { deviceId: 'd1', name: 'SM-F926B', profile: '' } }));
    const r = await request(app(pool)).post('/v1/sessions/s-2/navigate').send({});
    expect(r.body.ring).toMatchObject({ deviceId: 'd1', name: 'SM-F926B', elements: [] });
    expect(pool.visited).toEqual([]);   // the device holds its own page
  });
  it('only for its own owner, once, and not after two hours', async () => {
    const pool = fakePool(); pool.putResting('s-3', rest());
    expect((await request(app(pool, 'someone-else')).post('/v1/sessions/s-3/navigate').send({})).status).toBe(404);   // the route's own 'no such session'
    expect(pool.created).toEqual([]);
    pool.putResting('s-4', rest({ closedAt: Date.now() - 3 * 60 * 60 * 1000 }));
    expect(pool.takeResting('s-4', 'carla')).toBeNull();
  });
  it('closing a session that already closed opens no browser', async () => {
    const pool = fakePool(); pool.putResting('s-5', rest());
    const r = await request(app(pool)).delete('/v1/sessions/s-5');
    expect(r.body).toMatchObject({ closed: true });
    expect(pool.created).toEqual([]);
  });
  it('when it cannot be reopened, says why and keeps it for another try', async () => {
    const pool = fakePool({ failCreate: true }); pool.putResting('s-6', rest());
    const r = await request(app(pool)).post('/v1/sessions/s-6/navigate').send({});
    expect(r.status).toBe(409);
    expect(r.body.error).toMatch(/closed after inactivity and could not be reopened: profile "facebook" is in use/);
    expect(pool.resting.has('s-6')).toBe(true);
  });
  it('a live session is left alone', async () => {
    const pool = fakePool(); pool.sessions.set('s-7', { id: 's-7', owner: 'carla' });
    const r = await request(app(pool)).post('/v1/sessions/s-7/navigate').send({});
    expect(r.status).toBe(200);
    expect(r.headers['x-gb-revived']).toBeUndefined();
    expect(pool.created).toEqual([]);
  });
});