const assert = require("node:assert/strict"); const { test } = require("node:test"); const { loadBackground, response } = require("./support/characterization.cjs"); const { loadApplication } = require("./support/application.cjs"); const type = "opportunitiesExtension.requestCurrentUser"; const validIdentity = { user: { id: "synthetic@example.invalid", name: "Synthetic User", onWhiteList: false, provisioned: true, administrator: false, roles: ["SALES_REP", "SIMPLE_USER"] }, token: "synthetic-secret" }; for (const family of ["chrome", "firefox"]) { test(`${family}: current user GET preserves identity token only in debug data for explicit reveal`, async () => { const calls = []; const app = loadBackground({ family, fetch: async (url, options) => { calls.push({ url, options }); return response(validIdentity); } }); const result = await app.dispatch({ type }); assert.equal(result.ok, true); assert.equal(calls.length, 1); assert.equal(calls[0].url, "https://spa.oracle.com/oalcrm/web/api/identity-management/users/current"); assert.equal(calls[0].options.method, "GET"); assert.equal(calls[0].options.credentials, "include"); assert.equal(calls[0].options.cache, "no-store"); assert.equal(result.debugRequests[0].name, "requestCurrentUser"); assert.equal(result.debugRequests[0].httpStatus, 200); assert.equal(JSON.parse(result.debugRequests[0].responseBody).token, "synthetic-secret"); const content = loadApplication("content"); assert.doesNotMatch(content.debugContentLog.formatDebugBody(result.debugRequests[0].responseBody, "requestCurrentUser"), /synthetic-secret/); content.debugState.debugSecretsVisible = true; assert.match(content.debugContentLog.formatDebugBody(result.debugRequests[0].responseBody, "requestCurrentUser"), /synthetic-secret/); assert.equal(result.token, undefined); assert.equal(app.tabs.length, 0, "a valid existing session must not open a tab"); assert.equal(app.removedTabs.length, 0); }); } test("identity failures retain diagnostics but hide response tokens by default", async () => { for (const fetch of [ async () => new Response("synthetic-secretfallback"), async () => response({ token: "synthetic-secret", user: {} }), async () => response({ token: "synthetic-secret", user: { roles: [null] } }), async () => response({ token: "synthetic-secret" }, 401), async () => { throw new Error("Synthetic network failure"); } ]) { const app = loadBackground({ fetch }); const result = await app.dispatch({ type }); assert.equal(result.ok, false); assert.equal(result.debugRequests[0].status, "failed"); const content = loadApplication("content"); assert.doesNotMatch(content.debugContentLog.formatDebugBody(result.debugRequests[0].responseBody, "requestCurrentUser"), /synthetic-secret/); } }); test("retry timer is called without the environment object as its receiver", async () => { let attempts = 0; let timers = 0; const { app } = content(() => ++attempts === 1 ? { ok: false } : { ok: true, user: validIdentity.user }, { setTimeout: function(callback) { "use strict"; assert.equal(this, undefined, "Window timers reject an arbitrary receiver in Edge"); timers++; setImmediate(callback); } }); await app.currentUser.ensure(); assert.equal(attempts, 2); assert.equal(timers, 1); }); for (const family of ["chrome", "firefox"]) { test(`${family}: current-user SSO recovery shares one verified SPA tab and closes on valid JSON`, async () => { let attempts = 0; const app = loadBackground({ family, fetch: async (url, options) => { assert.equal(url, "https://spa.oracle.com/oalcrm/web/api/identity-management/users/current"); assert.equal(options.credentials, "include"); attempts++; if (attempts <= 2) return new Response('
'); assert.equal(app.tabs.length, 1, "SSO must be started before retry can succeed"); assert.equal(app.removedTabs.length, 0, "do not close before a valid identity response"); return response(validIdentity); } }); const [first, second] = await Promise.all([app.dispatch({ type }), app.dispatch({ type })]); assert.equal(first.ok, true); assert.deepEqual(first, second); assert.equal(attempts, 3); assert.deepEqual(app.tabs, [{ url: "https://spa.oracle.com/oalcrm/web/api/g2m-consumer-application/ui/index.html?ojr=whitespace", active: false, windowId: 3, ...(family === "firefox" ? { cookieStoreId: "test-store" } : {}) }]); assert.deepEqual(app.removedTabs, [91]); assert.equal(first.debugRequests[0].metadata.sessionBootstrapState, "opened-inactive-tab"); assert.equal(first.debugRequests[1].metadata.sessionBootstrapState, "waiting-for-valid-session"); assert.equal(first.debugRequests[2].metadata.sessionBootstrapState, "closed-after-valid-json"); }); } test("expired SPA session recovery handles 401, 403 and network errors, then reuses its successful identity", async () => { let attempts = 0; const app = loadBackground({ fetch: async () => { attempts++; if (attempts === 1) return response({}, 401); if (attempts === 2) return response({}, 403); if (attempts === 3) throw new TypeError("Failed to fetch"); return response(validIdentity); } }); const first = await app.dispatch({ type }); assert.equal(first.ok, true); assert.equal(app.tabs.length, 1); assert.deepEqual(first.debugRequests.map(entry => entry.httpStatus), [401, 403, null, 200]); assert.deepEqual(app.removedTabs, [91]); assert.equal((await app.dispatch({ type })).ok, true); assert.equal(attempts, 4, "successful identity must not be fetched again"); assert.equal((await app.dispatch({ type })).debugRequests.length, 0); assert.equal(app.tabs.length, 1); }); test("failed recovery closes its own tab and allows another cycle; server errors do not open SPA", async () => { let ready = false; const app = loadBackground({ fetch: async () => ready ? response(validIdentity) : new Response("Sign in") }); const failure = await app.dispatch({ type }); assert.equal(failure.ok, false); assert.equal(failure.debugRequests.length, 6); assert.deepEqual(app.removedTabs, [91]); assert.equal(failure.debugRequests.at(-1).metadata.retryLimitReached, "true"); ready = true; assert.equal((await app.dispatch({ type })).ok, true); const unavailable = loadBackground({ fetch: async () => response({}, 503) }); assert.equal((await unavailable.dispatch({ type })).ok, false); assert.equal(unavailable.tabs.length, 0); }); test("SPA tab opening failures stop API probes; a later cycle and cleanup failure preserve valid identity", async () => { let listener; let attempts = 0; let opens = 0; const app = loadApplication("background", { chrome: { runtime: { onMessage: { addListener: callback => { listener = callback; } } } }, URL, fetch: async () => { attempts++; return new Response("SSO"); } }); app.platformBackgroundApi.tabsCreate = async details => { assert.equal(details.active, false); if (++opens === 1) throw new Error("Temporary tab error"); return { id: 92 }; }; app.platformBackgroundApi.wait = async () => {}; app.platformBackgroundApi.tabsSendMessage = async (id, message) => message.type === "opportunitiesExtension.inspectSpaPage" ? { status: "ready", documentId: "test-document" } : { status: 200, contentType: "application/json", body: JSON.stringify(validIdentity) }; app.platformBackgroundApi.tabsRemove = async id => { assert.equal(id, 92); throw new Error("Tab already closed"); }; app.start(); const dispatch = () => new Promise(resolve => listener({ type }, { tab: { windowId: 3 } }, resolve)); const failure = await dispatch(); assert.equal(failure.ok, false); assert.equal(attempts, 1); assert.match(failure.debugRequests[0].metadata.sessionBootstrapState, /tab-open-failed/); const result = await dispatch(); assert.equal(result.ok, true); assert.equal(opens, 2); assert.equal(attempts, 2); assert.match(result.debugRequests[1].metadata.sessionBootstrapState, /tab-close-failed/); }); function content(identity, overrides = {}) { const app = loadApplication("content", { document: { createElement: () => ({ setAttribute() {} }) }, ...overrides }); app.debugContentView.renderDebugPanel = () => {}; const calls = []; app.platformContentMessages.sendRuntimeMessage = async message => { calls.push(message.type); if (message.type === type) return identity(); return { ok: true, actions: [], hasNext: false }; }; app.authContentSession.requestTokenRelayOnce = async () => { calls.push("tokenrelay"); return "synthetic"; }; app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ workloadActions: { value: "synthetic" } }); app.fusionCrmServiceRequests.requestCrmServiceRequest = async () => { calls.push("crm"); return []; }; app.fusionOpportunities.requestOpportunitiesPage = async () => { calls.push("opportunities"); return []; }; app.productsContentJobs.renderProductsProgress = () => {}; app.productsContentJobs.requestOpptyProductsInBackground = () => {}; app.productsContentJobs.queueProductsTableRender = () => {}; app.opportunitiesTable.setOpportunitiesTableState = () => {}; app.preferencesState.selectedOpportunityTypeValues = new Set(["ORA_ALLOPTIES"]); return { app, calls }; } for (const [roles, allowed] of [[ ["SALES_REP", "SIMPLE_USER"], false ], [ ["SOLUTION_ENG_NOTERR"], true ], [ [], true ]]) { test(`identity runs first, once; CRM/Actions enabled=${allowed} for ${JSON.stringify(roles)}`, async () => { let finish; const { app, calls } = content(() => new Promise(resolve => { finish = resolve; })); const first = app.opportunitiesController.requestOpportunitiesForPeriod("Current Quarter"); assert.deepEqual(calls, [type]); assert.equal(app.currentUser.allowsCrmActivities(), false); const same = app.currentUser.ensure(); assert.equal(same, app.currentUser.ensure()); finish({ ok: true, user: { ...validIdentity.user, roles } }); await first; assert.deepEqual(calls, [type, "tokenrelay", "opportunities", ...(allowed ? ["crm"] : [])]); app.productsContentState.currentProductsRequestId = "products-cycle"; app.productActivities.begin("products-cycle", "synthetic"); const products = { items: [{ RevnId: "123" }] }; app.productsContentState.opptyProductsByNumber.set("OP", products); app.productActivities.complete("products-cycle"); await new Promise(resolve => setImmediate(resolve)); assert.equal(calls.includes("opportunitiesExtension.requestWlActions"), allowed); if (!allowed) { assert.equal(app.opportunityCrmServiceRequests.createBadge("OP"), null); assert.equal(app.opportunityWorkloadActions.createBadge("OP"), null); } await app.opportunitiesController.requestOpportunitiesForPeriod("Next Quarter"); app.opportunityCrmServiceRequests.reset(); app.opportunityWorkloadActions.reopen(); await app.currentUser.ensure(); assert.equal(calls.filter(value => value === type).length, 1); }); } test("identity failures retry with shared backoff instead of being cached as a final result", async () => { for (const identity of [async () => ({ ok: false }), async () => ({ ok: true, user: {} }), async () => { throw new Error("Offline"); }]) { const timers = []; const delays = []; let attempts = 0; const { app, calls } = content(() => ++attempts <= 6 ? identity() : { ok: true, user: validIdentity.user }, { setTimeout: (callback, delay) => { timers.push(callback); delays.push(delay); } }); const first = app.opportunitiesController.requestOpportunitiesForPeriod("Current Quarter"); const second = app.opportunitiesController.requestOpportunitiesForPeriod("Next Quarter"); assert.equal(app.currentUser.ensure(), app.currentUser.ensure()); for (let i = 0; i < 6; i++) { await new Promise(resolve => setImmediate(resolve)); assert.ok(calls.every(value => value === type)); assert.equal(timers.length, 1); assert.equal(app.currentUser.getUserId(), ""); timers.shift()(); } await Promise.all([first, second]); assert.deepEqual(delays, [1000, 2000, 4000, 8000, 10000, 10000]); assert.equal(attempts, 7); assert.deepEqual(calls.slice(-2), ["tokenrelay", "opportunities"]); await app.currentUser.ensure(); assert.equal(attempts, 7); assert.equal(app.currentUser.getUserId(), validIdentity.user.id); } }); for (const family of ["chrome", "firefox"]) { test(`${family}: HTML, malformed JSON and invalid identity objects retry until valid user/token JSON`, async () => { const payloads = [ () => new Response("Sign in"), () => new Response('{"user":'), () => response(null), () => response([]), () => response({}), () => response({ ...validIdentity, token: "" }), () => response({ ...validIdentity, user: { ...validIdentity.user, id: "" } }), () => response({ ...validIdentity, user: { ...validIdentity.user, name: null } }), () => response({ ...validIdentity, user: { ...validIdentity.user, roles: [null] } }), () => response({ ...validIdentity, user: { ...validIdentity.user, provisioned: "true" } }), () => response(validIdentity) ]; let attempts = 0; const background = loadBackground({ family, fetch: async () => payloads[attempts++]() }); const { app } = content(() => background.dispatch({ type }), { setTimeout: callback => setImmediate(callback) }); const logs = []; app.debugContentLog.appendBackgroundRequestLog = entries => logs.push(...entries); const pending = app.currentUser.ensure(); assert.equal(pending, app.currentUser.ensure()); await pending; assert.equal(attempts, payloads.length); assert.equal(app.currentUser.getUserId(), validIdentity.user.id); assert.equal(app.currentUser.allowsCrmActivities(), false, "valid SALES_REP identity must not retry"); assert.equal(logs.filter(entry => entry.status === "failed").length, payloads.length - 1); assert.equal(logs.at(-1).status, "success"); await app.currentUser.ensure(); assert.equal(attempts, payloads.length); }); } test("identity cache survives background recreation, shares windows and isolates containers", async () => { const factory = new (require("node:vm").Script)(require("node:fs").readFileSync("src/services/spa/current-user.js", "utf8") + "\ncreateSpaCurrentUser").runInNewContext(); const data = {}; const sessionStorage = { async get(key) { return { [key]: data[key] }; }, async set(values) { Object.assign(data, values); } }; let calls = 0; const deps = { sessionStorage, fetchJson: async () => { calls++; return validIdentity; } }; const service = factory(deps); const sender = (windowId, cookieStoreId = "", incognito = false) => ({ tab: { windowId, cookieStoreId, incognito } }); await Promise.all([service.requestCurrentUser(sender(1)), service.requestCurrentUser(sender(2))]); assert.equal(calls, 1); await service.requestCurrentUser(sender(3)); assert.equal(calls, 1); const restored = await factory(deps).requestCurrentUser(sender(4)); assert.equal(restored.user.id, validIdentity.user.id); assert.equal(restored.debugRequests.length, 0); assert.equal(calls, 1); await service.requestCurrentUser(sender(1, "container-2")); await service.requestCurrentUser(sender(1, "", true)); assert.equal(calls, 3); assert.ok(Object.values(data).every(user => !user.token)); });