const assert = require("node:assert/strict"); const { test } = require("node:test"); const vm = require("node:vm"); const { composeScript } = require("../scripts/compose.cjs"); const script = new vm.Script(composeScript("spa-page")); const inspect = "opportunitiesExtension.inspectSpaPage"; const read = "opportunitiesExtension.readSpaIdentity"; const validIdentity = { user: { id: "test@example.invalid", name: "Test User", roles: [] }, token: "synthetic-token" }; function harness({ family = "chrome", origin = "https://spa.oracle.com", subframe = false, fetchResponse } = {}) { let listener; const requests = []; const timers = new Map(); const dom = { pathname: "/oalcrm/", title: "SPA", body: "Sales Planning and Analytics", readyState: "complete", controls: true, loginSelector: "" }; const window = {}; window.top = subframe ? {} : window; const document = { get title() { return dom.title; }, get body() { return { innerText: dom.body }; }, get readyState() { return dom.readyState; }, querySelector: selector => selector.startsWith("a[href]") ? dom.controls : Boolean(dom.loginSelector && selector.includes(dom.loginSelector)) }; script.runInNewContext({ window, document, location: { origin, get pathname() { return dom.pathname; } }, performance: { timeOrigin: 123456 }, [family]: { runtime: { id: "test-extension", onMessage: { addListener: callback => { listener = callback; } } } }, AbortController, setTimeout: callback => { const id = {}; timers.set(id, callback); return id; }, clearTimeout: id => timers.delete(id), fetch: async (url, options) => { requests.push({ url, options }); return fetchResponse ? fetchResponse(options) : new Response(JSON.stringify(validIdentity), { headers: { "Content-Type": "application/json", "X-Request-Id": "synthetic" } }); } }); const send = (type, documentId = "123456:/oalcrm/") => new Promise(resolve => listener({ type, documentId }, { id: "test-extension" }, resolve)); return { dom, send, requests, timers, get listener() { return listener; } }; } test("SPA inspector is restricted to the top-level SPA origin and trusted extension messages", () => { assert.equal(harness({ origin: "https://other.example.invalid" }).listener, undefined); assert.equal(harness({ subframe: true }).listener, undefined); const h = harness(); assert.equal(h.listener({ type: inspect }, { id: "other-extension" }, () => assert.fail("Must not reply")), false); assert.equal(h.listener({ type: "unknown" }, { id: "test-extension" }, () => assert.fail("Must not reply")), false); assert.equal(h.requests.length, 0, "installing the inspector never starts an API request"); }); test("error, SAML/login, blank and incomplete pages never authorize a fetch", async () => { const cases = [ [{ body: "401 Authorization Required" }, "error"], [{ title: "403 Forbidden" }, "error"], [{ body: "Internal Server Error" }, "error"], [{ pathname: "/cloudgate/v1/oauth2/callback" }, "login"], [{ title: "Oracle Sign In" }, "login"], [{ loginSelector: 'input[name="SAMLResponse"]' }, "login"], [{ loginSelector: 'input[name="SAMLRequest"]' }, "login"], [{ loginSelector: 'input[type="password"]' }, "login"], [{ loginSelector: 'meta[http-equiv="refresh" i]' }, "login"], [{ readyState: "interactive" }, "loading"], [{ readyState: "loading" }, "loading"], [{ title: "", body: "" }, "loading"], [{ controls: false }, "loading"] ]; for (const [dom, expected] of cases) { const h = harness(); Object.assign(h.dom, dom); assert.equal((await h.send(inspect)).status, expected, JSON.stringify(dom)); assert.match((await h.send(read)).error, /not ready/); assert.equal(h.requests.length, 0); } }); for (const family of ["chrome", "browser"]) { test(`${family}: a loaded application reads identity once per document in its own authenticated context`, async () => { const h = harness({ family }); const page = await h.send(inspect); assert.equal(page.status, "ready"); const [first, second] = await Promise.all([h.send(read, page.documentId), h.send(read, page.documentId)]); assert.deepEqual(first, second); assert.deepEqual(JSON.parse(first.body), validIdentity); assert.equal(first.headers["x-request-id"], "synthetic"); assert.equal((await h.send(read)).body, first.body); assert.equal(h.requests.length, 1); assert.equal(h.requests[0].url, "https://spa.oracle.com/oalcrm/web/api/identity-management/users/current"); assert.equal(h.requests[0].options.method, "GET"); assert.equal(h.requests[0].options.credentials, "include"); assert.equal(h.requests[0].options.cache, "no-store"); assert.equal(h.timers.size, 0); assert.match((await h.send(read, "old-document")).error, /navigated/); h.dom.pathname = "/login"; assert.match((await h.send(read)).error, /not ready/); assert.equal(h.requests.length, 1); }); } test("network or HTML failures are not retried in the same SPA document", async () => { for (const fetchResponse of [ () => { throw new Error("Network failure"); }, () => new Response('
', { headers: { "Content-Type": "text/html" } }) ]) { const h = harness({ fetchResponse }); const first = await h.send(read); for (let i = 0; i < 20; i++) assert.deepEqual(await h.send(read), first); assert.equal(h.requests.length, 1); assert.equal(h.timers.size, 0); } }); test("a stalled identity fetch is aborted and never leaves a retry timer", async () => { const h = harness({ fetchResponse: options => new Promise((resolve, reject) => { options.signal.addEventListener("abort", () => reject(new Error("Timed out"))); }) }); const pending = h.send(read); assert.equal(h.timers.size, 1); [...h.timers.values()][0](); assert.equal((await pending).error, "Timed out"); assert.equal(h.timers.size, 0); await h.send(read); assert.equal(h.requests.length, 1); });