const assert = require("node:assert/strict"); const { readFileSync } = require("node:fs"); const path = require("node:path"); const { test } = require("node:test"); const vm = require("node:vm"); require("../src/shared/config.js"); require("../src/shared/utils.js"); require("../src/domain/workload-dataset.js"); require("../src/repositories/crm-repository.js"); require("../src/repositories/workbench-repository.js"); require("../src/repositories/cache-repository.js"); const config = globalThis.ArchPanelConfig; const utils = globalThis.ArchPanelUtils; const domain = globalThis.ArchPanelWorkloadDatasetDomain.create({ ...config, ...utils, normalizePendingServiceRequests: (items) => items, }); const source = (file) => readFileSync(path.join(__dirname, "..", file), "utf8"); const createCrm = (fetchJson, sendComcipRequest) => globalThis.ArchPanelCrmRepository.create({ ...config, ...utils, fetchJson, sendComcipRequest, }); const sr = (index, optyNumber = `OPTY-${index}`) => ({ _entity: "CRMServiceRequest", SrId: 300000000000000 + index, SrNumber: `SR-${index}`, OpportunityName_Id_c: { OptyNumber: optyNumber }, ReportedHours_c: index / 2, }); test("fetches all 83 SRs in six POST pages with the requested filters and projection", async () => { const rows = Array.from({ length: 83 }, (_, i) => sr(i)); const requests = []; const progress = []; const repository = createCrm(async (url, options) => { assert.equal(url, config.API_ENDPOINTS.opportunityServiceRequests); assert.equal(options.method, "POST"); assert.equal(options.forcePageBridge, true); assert.equal(options.headers["content-type"], "application/json"); const payload = JSON.parse(options.body); requests.push(payload); return { items: rows.slice(payload.offset, payload.offset + payload.limit), hasMore: payload.offset + payload.limit < rows.length, offset: payload.offset, limit: 15, totalResults: 83, }; }); const items = await repository.fetchOpportunityServiceRequests({ onPage: (value) => progress.push(value) }); assert.equal(items.length, 83); assert.deepEqual(requests.map((request) => request.offset), [0, 15, 30, 45, 60, 75]); assert.ok(requests.every((request) => request.limit === 15)); assert.deepEqual(requests[0], { aggregationResults: false, applications: ["ORACLE-ISS-APP"], onlyData: true, entity: "CRMServiceRequest", copiedFrom: "queries/c521309c-1310-49a0-9723-da2eb1988205", q: { op: "$and", criteria: [ { op: "$eq", attribute: "RecordSet", value: "ORA_SVC_MY_SR_TEAM_SRS" }, { op: "$in", attribute: "StatusCd", values: ["SVC_PENDING_DELIVERY"] }, ] }, keywords: null, fields: ["SrNumber", "OpportunityName_Id_c.OptyNumber", "ReportedHours_c", "_actions", "DoNotCallFlag", "DoNotEmailFlag"], sort: [{ attribute: "_score", direction: "descending" }], language: "en", keywordsFields: ["SrNumber", "ServiceNew_Id_c.RecordName", "CustomerAccount", "OpportunityName_Id_c", "OpportunityName_Id_c.OptyNumber", "Owner"], skipInValidFields: true, limit: 15, offset: 0, }); assert.deepEqual(items[82], { srId: "300000000000082", srNumber: "SR-82", opportunityNumber: "OPTY-82", reportedHours: 41, }); assert.deepEqual(progress.at(-1), { page: 6, loaded: 83, hasMore: false }); }); test("hasMore controls pagination even on short pages or misleading totals; duplicate SRs are deduplicated", async () => { const offsets = []; const pages = [ { items: [sr(1)], hasMore: true, totalResults: 1 }, { items: [sr(1), sr(2)], hasMore: false, totalResults: 1000 }, ]; const items = await createCrm(async (_, options) => { offsets.push(JSON.parse(options.body).offset); return pages.shift(); }).fetchOpportunityServiceRequests(); assert.deepEqual(offsets, [0, 1]); assert.deepEqual(items.map((item) => item.srNumber), ["SR-1", "SR-2"]); assert.deepEqual(await createCrm(async () => ({ items: [], hasMore: false })).fetchOpportunityServiceRequests(), []); }); test("invalid responses and stalled pagination fail instead of generating false SR coverage", async () => { for (const payload of ["Sign in", { items: [] }, { items: [], hasMore: true }, { items: [sr(1)], hasMore: true, offset: 15 }]) { await assert.rejects(createCrm(async () => payload).fetchOpportunityServiceRequests(), /response|pagination/); } let calls = 0; await assert.rejects(createCrm(async () => { calls += 1; return { items: [sr(1)], hasMore: true }; }).fetchOpportunityServiceRequests(), /did not advance/); assert.equal(calls, 2); }); function createDashboardHarness({ failSecondPage = false } = {}) { const calls = []; const cached = []; const appState = { status: "idle", dataset: null }; // Different workloads, including two on the same opportunity. Same customer // is not sufficient to grant SR coverage; missing OptyNumber must not match. const workloadOpportunities = ["A5LLT2", " a5llt2 ", "A7H8FT", "NO-SR", "", "A5LLT"]; const fetchJson = async (url, options = {}) => { calls.push({ url, options }); if (url === config.API_ENDPOINTS.currentUser) { return { email: "architect@example.com", name: "Architect" }; } if (url === config.API_ENDPOINTS.opportunityServiceRequests) { const { offset } = JSON.parse(options.body); if (offset === 0) { return { items: [sr(1, "A5LLT2"), sr(2, "")], offset, hasMore: true }; } assert.equal(offset, 2); if (failSecondPage) throw Object.assign(new Error("CRM page failed (500)"), { status: 500 }); return { items: [sr(3, "A7H8FT"), sr(4, "A5LLT2")], offset, hasMore: false }; } const parsed = new URL(url, config.TARGET_ORIGIN); if (parsed.pathname === config.API_ENDPOINTS.customerSummary) { return [{ id: "customer-1", name: "Customer" }]; } if (parsed.pathname === `${config.API_ENDPOINTS.customerWorkloads}/customer-1/workloads`) { return workloadOpportunities.map((opportunityNumber, index) => ({ workloadId: `w-${index}`, name: `Workload ${index}`, opportunityNumber, opportunityForecastType: "Pipeline", workloadStatus: "Pipeline", })); } if (parsed.pathname === config.API_ENDPOINTS.workloadActions) { return [{ owner: "architect@example.com", name: "Action" }]; } assert.fail(`Unexpected request: ${url}`); }; const workbenchRepository = globalThis.ArchPanelWorkbenchRepository.create({ ...config, ...utils, ...domain, fetchJson }); const context = vm.createContext({ document: { getElementById: () => null }, window: { requestAnimationFrame: (callback) => callback() }, }); vm.runInContext(source("src/content/workbench-controller.js"), context); const controller = context.ArchPanelWorkbenchController.create({ ...config, ...utils, ...domain, appState, workbenchRepository, crmRepository: createCrm(fetchJson), comcipRepository: { fetchResourceCurrentUser: async () => null, fetchPendingServiceRequests: async () => [] }, hcmPage: { syncWorklistIframeToken: () => null }, readCachedWorklistToken: async () => "", readCachedDataset: async () => null, writeCachedDataset: async (payload) => { cached.push(payload); }, refreshTaskTypeCache: async () => null, renderModal() {}, }); return { controller, appState, cached, calls, workbenchRepository }; } test("dashboard joins all pages by OptyNumber, keeps per-workload actions and persists the opportunity SR data", async () => { const { controller, appState, cached, calls, workbenchRepository } = createDashboardHarness(); await controller.refreshData(); assert.equal(appState.status, "ready", appState.errorMessage); const workloads = appState.dataset.summary.allWorkloads; assert.deepEqual(Array.from(workloads, (item) => item.hasSR), [true, true, true, false, false, false]); assert.ok(workloads.every((item) => item.hasAction)); assert.equal(workloads[0].serviceRequests.length, 2); assert.equal(workloads[2].serviceRequests[0].srNumber, "SR-3"); assert.equal(cached.length, 1); const restored = domain.createDatasetSnapshot(cached[0]); assert.equal(restored.summary.allWorkloads[2].serviceRequests[0].opportunityNumber, "A7H8FT"); assert.equal(restored.summary.allWorkloads[2].serviceRequests[0].reportedHours, 1.5); assert.equal(restored.summary.srStatus.trueCount, 3); assert.equal(calls.filter((call) => call.url === config.API_ENDPOINTS.opportunityServiceRequests).length, 2); assert.equal(workbenchRepository.fetchWorkloadServiceRequests, undefined); assert.ok(calls.every((call) => !/\/workloads\/[^/]+\/serviceRequests/.test(call.url))); await controller.refreshData(); assert.equal(appState.status, "ready", appState.errorMessage); assert.equal(calls.filter((call) => call.url === config.API_ENDPOINTS.opportunityServiceRequests).length, 4); }); test("failed later CRM pages never save a partial dashboard or fall back to workload SRs", async () => { const { controller, appState, cached, calls } = createDashboardHarness({ failSecondPage: true }); await controller.refreshData(); assert.equal(appState.status, "error"); assert.match(appState.errorMessage, /500/); assert.equal(cached.length, 0); assert.equal(appState.dataset, null); assert.ok(calls.every((call) => !/\/workloads\/[^/]+\/serviceRequests/.test(call.url))); }); test("old workload-based snapshots are not reused; forecast still targets the workload endpoint", async () => { let cacheKey; const cache = globalThis.ArchPanelCacheRepository.create({ ...utils, CACHE: config.CACHE, normalizeCachedExportPayload: domain.normalizeCachedExportPayload, storageService: { readRecord: async (_, key) => { cacheKey = key; return null; } }, }); assert.equal(await cache.readCachedDataset(), null); assert.equal(cacheKey, "workload-dashboard-opportunity-srs"); let request; const repository = globalThis.ArchPanelWorkbenchRepository.create({ ...config, ...utils, ...domain, fetchJson: async (url, options) => { request = { url, options }; }, }); await repository.updateWorkloadForecast("w-1", { forecast: [] }); assert.equal(request.url, `${config.API_ENDPOINTS.workloads}/w-1/forecast`); assert.equal(request.options.method, "PATCH"); }); function createPageSession(pathname = "/hcmUI/faces/FuseWelcome", { queryStatus = 200, sendMessage } = {}) { const networkCalls = []; const listeners = []; class MockXhr { open() {} setRequestHeader() {} send() {} } const window = { location: new URL(`https://eeho.fa.us2.oraclecloud.com${pathname}`), localStorage: { length: 0 }, sessionStorage: { length: 0 }, XMLHttpRequest: MockXhr, setTimeout, clearTimeout, addEventListener: (_, listener) => listeners.push(listener), postMessage: (data) => { for (const listener of listeners) listener({ source: window, origin: window.location.origin, data }); }, fetch: async (url, options) => { networkCalls.push({ url, options }); const offset = options.body ? JSON.parse(options.body).offset : 0; const status = String(url).includes("/custom-actions/queries") ? queryStatus : 200; return { ok: status === 200, status, text: async () => status !== 200 ? "" : JSON.stringify({ items: [sr(offset + 1)], hasMore: offset === 0, offset, }) }; }, }; window.top = window; const context = vm.createContext({ window, URL, Headers, navigator: { language: "en" }, document: { getElementById: () => ({}) }, }); vm.runInContext(source("page-bridge.js"), context); vm.runInContext(source("src/services/transport-service.js"), context); const transport = context.ArchPanelTransportService.create({ ...config, ...utils, chromeService: { sendMessage: sendMessage || (() => assert.fail("Authenticated CRM calls must use the Fusion page session")) }, isHcmWelcomePage: () => true, getSessionAuthHeaders: () => ({ "wwb-provider-authorization": "synthetic-spa-token" }), setSessionAuthHeaders() {}, }); window.addEventListener("message", transport.handleBridgeMessage); return { window, transport, networkCalls }; } for (const pagePath of ["/hcmUI/faces/FuseWelcome", "/fscmUI/faces/FuseWelcome"]) { test(`CRM pagination reuses captured headers and cookies through the real page bridge on ${pagePath}`, async () => { const { window, transport, networkCalls } = createPageSession(pagePath); const headers = { authorization: "Bearer synthetic-crm-token", "x-csrf-token": "synthetic-csrf", "rest-framework-version": "4", }; await window.fetch("/crmRestApi/resources/latest/serviceRequests", { headers }); // Capture unrelated SPA traffic after CRM traffic to exercise isolation. await window.fetch(`${config.TARGET_ORIGIN}${config.API_ENDPOINTS.workloads}/w-1`, { headers: { authorization: "Bearer wrong-spa-token", "wwb-provider-authorization": "synthetic-provider-token" }, }); await createCrm(transport.fetchJson).fetchOpportunityServiceRequests(); const calls = networkCalls.filter((call) => String(call.url).includes("/custom-actions/queries")); assert.equal(calls.length, 2); for (const { options } of calls) { assert.equal(options.credentials, "include"); assert.equal(options.method, "POST"); for (const [name, value] of Object.entries(headers)) assert.equal(options.headers.get(name), value); assert.equal(options.headers.get("wwb-provider-authorization"), null); assert.equal(options.headers.get("content-type"), "application/json"); } const snapshot = window.__archPanelExtensionBridge.buildHeaderSnapshot({ url: config.API_ENDPOINTS.opportunityServiceRequests, includeAllAuth: true, }); assert.equal(snapshot.authorization, headers.authorization); assert.equal(snapshot["wwb-provider-authorization"], undefined); }); } test("CRM headers from XHR are reused and updated without leaking into SPA snapshots", async () => { const { window, transport, networkCalls } = createPageSession(); const xhr = new window.XMLHttpRequest(); xhr.open("POST", config.API_ENDPOINTS.opportunityServiceRequests); xhr.setRequestHeader("authorization", "Bearer synthetic-refreshed-token"); xhr.setRequestHeader("x-csrf-token", "synthetic-refreshed-csrf"); xhr.send(); await createCrm(transport.fetchJson).fetchOpportunityServiceRequests(); assert.equal(networkCalls.at(-1).options.headers.get("authorization"), "Bearer synthetic-refreshed-token"); assert.equal(networkCalls.at(-1).options.headers.get("x-csrf-token"), "synthetic-refreshed-csrf"); const snapshot = window.__archPanelExtensionBridge.buildHeaderSnapshot({ url: `${config.TARGET_ORIGIN}${config.API_ENDPOINTS.workloads}/w-1`, includeAllAuth: true, }); assert.equal(snapshot.authorization, undefined); assert.equal(snapshot["x-csrf-token"], undefined); }); test("manifest loads the CRM repository and captures Fusion headers at document_start", () => { const manifest = JSON.parse(source("manifest.json")); const bridge = manifest.content_scripts.find((entry) => entry.world === "MAIN"); assert.equal(bridge.run_at, "document_start"); for (const route of ["hcmUI", "fscmUI"]) { assert.ok(bridge.matches.includes(`https://eeho.fa.us2.oraclecloud.com/${route}/faces/FuseWelcome*`)); } const scripts = manifest.content_scripts.find((entry) => entry.js.includes("content-script.js")).js; assert.ok(scripts.indexOf("src/repositories/crm-repository.js") < scripts.indexOf("content-script.js")); }); for (const status of [401, 403]) { test(`a Fusion ${status} with no captured auth recovers through COMCIP and reads all 83 SRs`, async () => { const rows = Array.from({ length: 83 }, (_, i) => sr(i)); const proxyCalls = []; const { transport, networkCalls } = createPageSession("/hcmUI/faces/FuseWelcome", { queryStatus: status, sendMessage: async (message) => { proxyCalls.push(message); const { offset, limit } = message.payload; return { ok: true, status: 200, payload: { items: rows.slice(offset, offset + limit), offset, hasMore: offset + limit < rows.length, } }; }, }); const repository = createCrm(transport.fetchJson, transport.sendComcipRequestMessage); const items = await repository.fetchOpportunityServiceRequests(); assert.equal(items.length, 83); assert.equal(networkCalls.length, 1, "Do not repeat failed direct auth on each page"); assert.equal(networkCalls[0].options.headers.get("authorization"), null); assert.deepEqual(proxyCalls.map((message) => message.payload.offset), [0, 15, 30, 45, 60, 75]); assert.deepEqual(proxyCalls[0].payload, JSON.parse(networkCalls[0].options.body)); for (const message of proxyCalls) { assert.equal(message.type, "ARCH_PANEL_COMCIP_REQUEST"); assert.equal(message.method, "POST"); assert.equal(message.url, config.COMCIP_REQUEST.url); assert.ok(message.url.endsWith("/uri/https/eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/latest/custom-actions/queries")); assert.equal(message.headers["x-vb-application-version"], config.COMCIP_REQUEST.appVersion); assert.equal(message.headers["wwb-provider-authorization"], undefined); assert.equal(message.payload.q.criteria[0].value, "ORA_SVC_MY_SR_TEAM_SRS"); assert.deepEqual(message.payload.q.criteria[1].values, ["SVC_PENDING_DELIVERY"]); } await repository.fetchOpportunityServiceRequests(); assert.equal(networkCalls.length, 2, "A fresh refresh can use a restored Fusion session"); }); } test("an auth failure mid-pagination retries the same offset through COMCIP", async () => { const directOffsets = []; const proxyOffsets = []; const rows = Array.from({ length: 32 }, (_, i) => sr(i)); const items = await createCrm(async (_, options) => { const { offset, limit } = JSON.parse(options.body); directOffsets.push(offset); if (offset === 15) throw Object.assign(new Error("Unauthorized"), { status: 401 }); return { items: rows.slice(offset, offset + limit), hasMore: true, offset }; }, async ({ payload: { offset, limit } }) => { proxyOffsets.push(offset); return { ok: true, status: 200, payload: { items: rows.slice(offset, offset + limit), hasMore: offset + limit < rows.length, offset, } }; }).fetchOpportunityServiceRequests(); assert.deepEqual(directOffsets, [0, 15]); assert.deepEqual(proxyOffsets, [15, 30]); assert.equal(items.length, 32); assert.equal(items.at(-1).srNumber, "SR-31"); }); test("non-auth failures do not switch routes; proxy failures are surfaced without retry loops", async () => { let proxyCalls = 0; const failedDirect = Object.assign(new Error("Server failed"), { status: 500 }); await assert.rejects(createCrm(async () => { throw failedDirect; }, async () => { proxyCalls += 1; }) .fetchOpportunityServiceRequests(), (error) => error === failedDirect); assert.equal(proxyCalls, 0); await assert.rejects(createCrm(async () => { throw Object.assign(new Error("Unauthorized"), { status: 401 }); }, async () => { proxyCalls += 1; return { ok: false, status: 401 }; }).fetchOpportunityServiceRequests(), (error) => error.status === 401 && /Sign in to COMCIP/.test(error.message)); assert.equal(proxyCalls, 1); }); test("the existing background proxy fetch uses its own Session auth, negotiated client ID and cookies", async () => { const calls = []; const browserContext = vm.createContext({ window: { location: new URL("https://comcipapic-oalprod.integration.ocp.oraclecloud.com/") }, navigator: { language: "en" }, fetch: async (url, options) => { calls.push({ url, options }); if (options.method === "GET") { return { headers: new Headers({ "x-appbuilder-client-id": "synthetic-client-id" }) }; } return { ok: true, status: 200, headers: new Headers(), text: async () => JSON.stringify({ items: [sr(1)], hasMore: false }) }; }, }); const workerContext = vm.createContext({ chrome: { runtime: { onInstalled: { addListener() {} }, onStartup: { addListener() {} }, onMessage: { addListener() {} } }, scripting: { executeScript: async ({ func, args }) => { browserContext.args = args; const result = await vm.runInContext(`(${func.toString()})(...args)`, browserContext); return [{ result }]; } }, }, }); workerContext.importScripts = (file) => vm.runInContext(source(file), workerContext); vm.runInContext(source("background.js"), workerContext); const query = { entity: "CRMServiceRequest", offset: 15, limit: 15 }; const response = await workerContext.executeComcipFetch(1, config.COMCIP_REQUEST.url, "POST", query, {}, {}); assert.equal(response.ok, true); const sent = calls.at(-1); assert.equal(sent.url, config.COMCIP_REQUEST.url); assert.equal(sent.options.headers.authorization, "Session"); assert.equal(sent.options.headers["x-appbuilder-client-id"], "synthetic-client-id"); assert.equal(sent.options.headers["vb-proxy-header-preference"], "transient"); assert.equal(sent.options.credentials, "include"); assert.deepEqual(JSON.parse(sent.options.body), query); });