import { readFileSync } from 'node:fs'; import { runInNewContext } from 'node:vm'; import { expect, it, vi } from 'vitest'; const source = readFileSync(new URL('../../internal/api/runtime_session.go', import.meta.url), 'utf8').split('const runtimeSessionJS = `')[1].split('`')[0]; it.each([401, 403, 409, 500])('runtime only reloads a confirmed revoked session (%s)', async status => { const reload = vi.fn(); let renew: (() => void) | undefined; const fetch = vi.fn(async (path: string) => new Response('{}', {status: path === '/api/refresh' ? status : 401})); runInNewContext(source, { location:{pathname:'/',reload}, navigator:{}, fetch, document:{cookie:'dsh_csrf=csrf',addEventListener() {}}, window:{addEventListener(_name: string, handler: () => void) {renew = handler;}}, setInterval() {}, setTimeout(resolve: () => void) {resolve();}, }); await renew?.(); expect(reload).toHaveBeenCalledTimes(status === 401 || status === 403 ? 1 : 0); expect(fetch.mock.calls.filter(c => c[0] === '/api/refresh')).toHaveLength(1); expect(fetch.mock.calls.filter(c => c[0] === '/api/me')).toHaveLength(status === 500 ? 0 : 4); }); it.each([false, true])('renews before access expiry, with Web Locks=%s', async withLocks => { let now = 0, expires = 300_000; let tick!: () => Promise; const locks = {request: vi.fn(async (_name: string, run: () => Promise) => run())}; const fetch = vi.fn(async (path: string) => { if (path === '/api/refresh') expires = now + 300_000; return new Response('{}', {status: now < expires ? 200 : 401}); }); runInNewContext(source, { location: {pathname: '/', reload: vi.fn()}, navigator: withLocks ? {locks} : {}, fetch, document: {cookie: 'dsh_csrf=csrf', addEventListener() {}}, window: {addEventListener() {}}, setInterval(fn: typeof tick, interval: number) {tick = fn; expect(interval).toBe(240_000);}, setTimeout, }); for (now = 240_000; now <= 960_000; now += 240_000) { expect(now).toBeLessThan(expires); await tick(); expect(expires).toBe(now + 300_000); } expect(fetch.mock.calls.map(c => c[0])).toEqual(Array(4).fill('/api/refresh')); expect(locks.request).toHaveBeenCalledTimes(withLocks ? 4 : 0); }); it('coalesces renewal events and keeps a session recovered by a sibling page', async () => { let tick!: () => Promise, finish!: (response: Response) => void; const reload = vi.fn(); const fetch = vi.fn((path: string) => path === '/api/refresh' ? new Promise(resolve => {finish = resolve;}) : Promise.resolve(new Response('{}', {status: 200}))); runInNewContext(source, { location: {pathname: '/', reload}, navigator: {}, fetch, document: {cookie: 'dsh_csrf=csrf', addEventListener() {}}, window: {addEventListener() {}}, setInterval(fn: typeof tick) {tick = fn;}, setTimeout, }); const first = tick(), second = tick(); expect(fetch).toHaveBeenCalledOnce(); finish(new Response('{}', {status: 409})); await Promise.all([first, second]); expect(fetch.mock.calls.map(c => c[0])).toEqual(['/api/refresh', '/api/me']); expect(reload).not.toHaveBeenCalled(); }); it('reads the latest CSRF cookie after waiting for the refresh lock', async () => { let tick!: () => Promise, enter!: () => void; const document = {cookie: 'dsh_csrf=old', addEventListener() {}}; const fetch = vi.fn(async () => new Response('{}')); const locks = {request: (_name: string, run: () => Promise) => new Promise((resolve, reject) => {enter = () => {run().then(resolve, reject);};})}; runInNewContext(source, { location: {pathname: '/', reload: vi.fn()}, navigator: {locks}, document, fetch, window: {addEventListener() {}}, setInterval(fn: typeof tick) {tick = fn;}, setTimeout, }); const pending = tick(); expect(fetch).not.toHaveBeenCalled(); document.cookie = 'dsh_csrf=new'; enter(); await pending; expect(fetch).toHaveBeenCalledWith('/api/refresh', expect.objectContaining({headers: {'X-CSRF-Token': 'new'}})); });