import test from 'node:test'; import assert from 'node:assert/strict'; import PocketBase from 'pocketbase'; import { loadYrkaSession, extractPbCookieValue, tokenExpiresAtMs } from '../yrka-auth.js'; import { makeJwt, makePbCookie, startServer, stopServer, urlOf, TEST_RECORD } from './helpers.js'; test('extractPbCookieValue handles prefixed and bare values', () => { assert.equal(extractPbCookieValue('pb_auth=abc123; Path=/; HttpOnly'), 'abc123'); assert.equal(extractPbCookieValue('bare-value'), 'bare-value'); }); test('tokenExpiresAtMs reads exp from a JWT payload', () => { const expSec = Math.floor(Date.now() / 1000) + 60; const token = makeJwt({ expiresInSec: 60 }); assert.equal(tokenExpiresAtMs(token), expSec * 1000); assert.equal(tokenExpiresAtMs('garbage'), 0); assert.equal(tokenExpiresAtMs(''), 0); }); test('loadYrkaSession returns null without a cookie', async () => { assert.equal(await loadYrkaSession(''), null); assert.equal(await loadYrkaSession(undefined), null); }); test('loadYrkaSession returns null on malformed cookie values', async () => { let constructed = false; const pb = await loadYrkaSession('not-a-valid-envelope', { pbUrl: 'http://127.0.0.1:9', createPb: (url) => { constructed = true; return new PocketBase(url); }, }); assert.equal(pb, null); assert.equal(constructed, true, 'a PocketBase client was constructed before parsing failed'); }); // The remaining tests exercise the real SDK against a fake PocketBase server, // so the cookie envelope and auth-refresh handling match production exactly. async function withFakePb(refreshStatus, fn) { const server = await startServer((req, res) => { if (req.method === 'POST' && req.url.startsWith('/api/collections/users/auth-refresh')) { res.writeHead(refreshStatus, { 'content-type': 'application/json' }); if (refreshStatus === 200) { res.end(JSON.stringify({ token: makeJwt({ expiresInSec: 3600 }), record: TEST_RECORD })); } else { res.end(JSON.stringify({ code: 401, message: 'The request is not authenticated.' })); } return; } res.writeHead(404, { 'content-type': 'application/json' }); res.end('{}'); }); try { await fn(urlOf(server)); } finally { await stopServer(server); } } test('loadYrkaSession accepts a valid, not-nearly-expired token without refreshing', async () => { await withFakePb(200, async (pbUrl) => { const cookie = makePbCookie(makeJwt({ expiresInSec: 3600 })); const yrka = await loadYrkaSession(cookie, { pbUrl }); assert.ok(yrka); assert.equal(yrka.refreshed, false); assert.ok(yrka.pb.authStore.token); assert.equal(yrka.pb.authStore.record.id, TEST_RECORD.id); assert.equal(yrka.cookie, undefined); }); }); test('loadYrkaSession refreshes proactively when expiry is within the buffer', async () => { await withFakePb(200, async (pbUrl) => { const oldToken = makeJwt({ expiresInSec: 60 }); // inside the 5-minute buffer const cookie = makePbCookie(oldToken); const yrka = await loadYrkaSession(cookie, { pbUrl }); assert.ok(yrka, 'session recovered'); assert.equal(yrka.refreshed, true); assert.notEqual(yrka.pb.authStore.token, oldToken); assert.ok(yrka.cookie, 'refreshed cookie value is returned for persistence'); }); }); test('loadYrkaSession returns null when the token is expired and refresh fails', async () => { await withFakePb(401, async (pbUrl) => { const cookie = makePbCookie(makeJwt({ expiresInSec: -3600 })); const yrka = await loadYrkaSession(cookie, { pbUrl }); assert.equal(yrka, null); }); });