__tests__/session-vault.test.js
Rama main · réplica de lectura del repositorio original.
/*
* A LOGIN MADE BY HAND MUST SURVIVE A DEPLOY.
*
* It did not. The owner signed in to Google in the console. Three pod rolls later the `google`
* profile held 63 google.com cookies and not one auth cookie, Search Console reported no access to
* the property, the dashboard raised a manual-action flag invented from its own inability to read
* the page, and a whole day's audit slot was spent on a locked door.
*
* Closing contexts in parallel with a timeout narrowed that window but cannot close it: it still
* needs SIGTERM to arrive and the closes to finish inside the grace period. SIGKILL, OOM, a drained
* node or a wedged Chromium all still lose the jar. So the session is written down on our own
* schedule and put back when the jar returns empty.
*
* THE TEST THAT MATTERS MOST is "refuses to overwrite a good snapshot with a signed-out jar". A
* signed-out profile is not empty — it had 63 cookies — so an unconditional save would let one
* signed-out moment erase the only backup of a login a human made by hand. That failure would be
* worse than the bug this whole module exists to fix, because it would be silent AND permanent.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createRequire } from 'module';
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
const require = createRequire(import.meta.url);
const DIR = mkdtempSync(join(tmpdir(), 'gb-vault-'));
process.env.PROFILE_DIR = DIR; // read once at load, so set it before requiring
const vault = require('../src/sessionVault');
/* The shapes Playwright actually hands back. */
const ck = (name, domain = '.google.com', expires = -1) => ({ name, value: 'v', domain, path: '/', expires });
const GOOGLE_SESSION = [
ck('SID'), ck('HSID'), ck('SSID'), ck('APISID'), ck('SAPISID'), ck('__Secure-1PSID'), ck('LSID'),
];
/* What a signed-out google profile really looks like: plenty of cookies, no session. */
const GOOGLE_SIGNED_OUT = ['NID', 'AEC', '1P_JAR', 'CONSENT', 'OTZ', 'SEARCH_SAMESITE', 'DV']
.map((n) => ck(n));
describe('sessionVault: which jars hold a session', () => {
it('knows a real Google session when it sees one', () => {
expect(vault.sessionHosts(GOOGLE_SESSION)).toEqual(['google.com']);
expect(vault.worthSaving(GOOGLE_SESSION)).toBe(true);
});
/* The measured case. 63 cookies, no session. */
it('knows that a jar full of cookies can still be signed out', () => {
expect(vault.sessionHosts(GOOGLE_SIGNED_OUT)).toEqual([]);
expect(vault.worthSaving(GOOGLE_SIGNED_OUT)).toBe(false);
});
it('recognises the other logins this browser holds by hand', () => {
expect(vault.sessionHosts([ck('c_user', '.facebook.com'), ck('xs', '.facebook.com')])).toEqual(['facebook.com']);
expect(vault.sessionHosts([ck('li_at', '.www.linkedin.com')])).toEqual(['linkedin.com']);
expect(vault.sessionHosts([ck('sessionid', 'useme.com')])).toEqual(['useme.com']);
});
it('is not fooled by an auth cookie name on an unrelated domain', () => {
expect(vault.sessionHosts([ck('SID', '.evil.example')])).toEqual([]);
});
it('drops expired cookies but keeps session cookies', () => {
const past = Math.floor(Date.now() / 1000) - 60;
const future = Math.floor(Date.now() / 1000) + 3600;
const kept = vault.live([ck('SID', '.google.com', past), ck('SAPISID', '.google.com', future), ck('LSID', '.google.com', -1)]);
expect(kept.map((c) => c.name).sort()).toEqual(['LSID', 'SAPISID']);
});
it('tolerates rubbish instead of throwing', () => {
for (const j of [null, undefined, [], [null, {}, { name: '' }]]) {
expect(vault.worthSaving(j)).toBe(false);
expect(() => vault.live(j)).not.toThrow();
}
});
});
describe('sessionVault: saving never makes things worse', () => {
const PROF = 'google';
beforeEach(() => { try { rmSync(join(DIR, PROF), { recursive: true, force: true }); } catch { /* fresh */ } });
afterEach(() => { try { rmSync(join(DIR, PROF), { recursive: true, force: true }); } catch { /* gone */ } });
it('writes a session down, with owner-only permissions', () => {
const r = vault.save(PROF, GOOGLE_SESSION);
expect(r.saved).toBe(true);
expect(r.hosts).toEqual(['google.com']);
expect(existsSync(vault.fileFor(PROF))).toBe(true);
const back = vault.read(PROF);
expect(back.cookies.map((c) => c.name)).toContain('SAPISID');
});
/*
* THE ONE THAT MATTERS. If this ever regresses, a single signed-out moment silently destroys the
* only copy of a login a human made by hand, and nobody finds out until the channel stops.
*/
it('refuses to overwrite a good snapshot with a signed-out jar', () => {
expect(vault.save(PROF, GOOGLE_SESSION).saved).toBe(true);
const r = vault.save(PROF, GOOGLE_SIGNED_OUT);
expect(r.saved).toBe(false);
expect(r.why).toMatch(/no session/i);
/* and the good one is still there, untouched */
expect(vault.read(PROF).cookies.map((c) => c.name)).toContain('SAPISID');
});
it('refuses an empty jar too, for the same reason', () => {
expect(vault.save(PROF, GOOGLE_SESSION).saved).toBe(true);
expect(vault.save(PROF, []).saved).toBe(false);
expect(vault.read(PROF).cookies.length).toBeGreaterThan(0);
});
it('will not save a jar whose session has entirely expired', () => {
const past = Math.floor(Date.now() / 1000) - 60;
const dead = GOOGLE_SESSION.map((c) => ({ ...c, expires: past }));
expect(vault.save(PROF, dead).saved).toBe(false);
});
it('reads nothing rather than throwing when there is no snapshot', () => {
expect(vault.read('never-existed')).toBe(null);
});
it('survives a corrupt snapshot on disk', () => {
mkdirSync(join(DIR, PROF), { recursive: true });
writeFileSync(vault.fileFor(PROF), '{not json');
expect(vault.read(PROF)).toBe(null);
/* and a good save repairs it */
expect(vault.save(PROF, GOOGLE_SESSION).saved).toBe(true);
expect(vault.read(PROF).cookies.length).toBe(GOOGLE_SESSION.length);
});
it('leaves no temp file behind, so a kill mid-write cannot masquerade as a snapshot', () => {
vault.save(PROF, GOOGLE_SESSION);
expect(existsSync(vault.fileFor(PROF) + '.tmp')).toBe(false);
});
});
describe('sessionVault: restoring only what is missing', () => {
it('puts a session back when the jar has lost it', () => {
const plan = vault.planRestore(GOOGLE_SIGNED_OUT, { at: Date.now(), cookies: GOOGLE_SESSION });
expect(plan.hosts).toEqual(['google.com']);
expect(plan.restore.map((c) => c.name)).toContain('SAPISID');
});
/* Never stomp a live session: it could be a NEWER login than the snapshot. */
it('leaves a browser that is already signed in alone', () => {
const plan = vault.planRestore(GOOGLE_SESSION, { at: Date.now(), cookies: GOOGLE_SESSION });
expect(plan.restore).toEqual([]);
expect(plan.why).toMatch(/already holds/i);
});
it('restores only the sites that are missing, not the whole snapshot', () => {
const snap = { at: Date.now(), cookies: [...GOOGLE_SESSION, ck('c_user', '.facebook.com'), ck('xs', '.facebook.com')] };
const plan = vault.planRestore(GOOGLE_SESSION, snap); // google present, facebook missing
expect(plan.hosts).toEqual(['facebook.com']);
expect(plan.restore.every((c) => /facebook/.test(c.domain))).toBe(true);
});
it('does not restore a snapshot whose cookies have all expired', () => {
const past = Math.floor(Date.now() / 1000) - 60;
const snap = { at: 1, cookies: GOOGLE_SESSION.map((c) => ({ ...c, expires: past })) };
expect(vault.planRestore(GOOGLE_SIGNED_OUT, snap).restore).toEqual([]);
});
it('says so plainly when there is nothing to restore from', () => {
expect(vault.planRestore(GOOGLE_SIGNED_OUT, null).why).toMatch(/no snapshot/i);
expect(vault.planRestore(GOOGLE_SIGNED_OUT, { cookies: [] }).why).toMatch(/no snapshot/i);
});
});