const test = require("node:test"); const assert = require("node:assert/strict"); const { loadBackground, plain, response } = require("./support/characterization.cjs"); const { loadApplication } = require("./support/application.cjs"); const TYPE = "opportunitiesExtension.requestWorkloads"; const CACHE = "opportunitiesExtension.workloadsCache.v1"; const flush = () => new Promise(resolve => setImmediate(resolve)); const request = (extra = {}) => ({ type: TYPE, customerId: "123", principal: "user@example.invalid", authorization: { value: "synthetic-wwb-token" }, cacheMaxAgeMs: 3600000, ...extra }); const page = (ids, extra = {}) => ({ workloads: ids.map(id => ({ id, name: `Workload ${id}`, forecast: [] })), hasNext: false, ...extra }); for (const family of ["chrome", "firefox"]) { test(`${family}: workloads paginate with response offset/limit and reuse persistent per-account cache`, async () => { const calls = [], storage = {}; const h = loadBackground({ family, storage, fetch: async (url, options) => { calls.push({ url: new URL(url), options }); const offset = Number(new URL(url).searchParams.get("offset")); return response(offset === 0 ? page([11, 12], { hasNext: true, offset: 0, limit: 2 }) : offset === 2 ? page([13], { hasNext: true, offset: 2, limit: 1 }) : page([14])); } }); const result = await h.dispatch(request()); assert.equal(result.ok, true); assert.deepEqual(result.workloads.map(item => item.id), [11, 12, 13, 14]); assert.deepEqual(calls.map(call => call.url.searchParams.get("offset")), ["0", "2", "3"]); for (const { url, options } of calls) { assert.equal(url.origin, "https://spa.oracle.com"); assert.ok(url.pathname.endsWith("/customers/123/workloads")); assert.equal(url.searchParams.get("workloadStatus"), "Pipeline,Implementation,Live"); assert.equal(url.searchParams.get("opportunityForecastType"), "All"); assert.equal(url.searchParams.get("includeForEstimates"), "ALL"); assert.equal(url.searchParams.get("financialYear"), "ROLLING_4_QTRS"); assert.equal(options.method, "GET"); assert.equal(options.credentials, "include"); assert.equal(options.headers["wwb-provider-authorization"], "synthetic-wwb-token"); } assert.equal(result.debugRequests.length, 3); assert.ok(result.debugRequests.every(entry => entry.name === "requestWorkloads")); assert.equal(result.fromCache, false); assert.equal((await h.dispatch(request())).fromCache, true); assert.equal(calls.length, 3); assert.doesNotMatch(JSON.stringify(storage), /synthetic-wwb-token|debugRequests|requestHeaders/); const restarted = loadBackground({ family, storage }); const cached = await restarted.dispatch(request()); assert.equal(cached.fromCache, true); assert.deepEqual(cached.workloads, result.workloads); assert.equal(cached.cachedAt, result.cachedAt); h.advance(3600001); assert.equal((await h.dispatch(request())).fromCache, false); assert.equal(calls.length, 6); assert.equal((await h.dispatch(request({ customerId: "456" }))).fromCache, false); assert.equal((await h.dispatch(request({ principal: "other@example.invalid" }))).fromCache, false); assert.equal(calls.length, 12); }); test(`${family}: workload cache coalesces flights and Refresh all prevents late cache restoration`, async () => { const pending = [], storage = {}; const h = loadBackground({ family, storage, fetch: () => new Promise(resolve => pending.push(resolve)) }); const old = h.dispatch(request()), shared = h.dispatch(request()); await flush(); assert.equal(pending.length, 1); assert.equal((await h.dispatch({ type: "opportunitiesExtension.invalidateProductsCache" })).ok, true); const fresh = h.dispatch(request()); await flush(); assert.equal(pending.length, 2); pending[1](response(page([22]))); await fresh; pending[0](response(page([11]))); assert.deepEqual(await old, await shared); const cached = await h.dispatch(request()); assert.equal(cached.fromCache, true); assert.deepEqual(cached.workloads.map(item => item.id), [22]); assert.equal(pending.length, 2); }); test(`${family}: successful empty workloads are cached; storage failures do not lose results`, async () => { let calls = 0; const h = loadBackground({ family, fetch: async () => { calls++; return response(page([])); } }); assert.deepEqual((await h.dispatch(request())).workloads, []); assert.equal((await h.dispatch(request())).fromCache, true); assert.equal(calls, 1); const unavailable = loadBackground({ family, storageError: true, fetch: async () => response(page([10])) }); assert.equal((await unavailable.dispatch(request())).ok, true); }); } test("workload failures and stalled pagination are not cached as complete results", async () => { for (const bad of [response({}, 403), response({ workloads: [] }), response(page([], { hasNext: true })), response(page([11], { hasNext: true, offset: 0, limit: 0 }))]) { let calls = 0; const h = loadBackground({ fetch: async () => ++calls === 1 ? bad : response(page([12])) }); const failed = await h.dispatch(request()); assert.equal(failed.ok, false); assert.equal(failed.debugRequests.length, 1); assert.equal((await h.dispatch(request())).ok, true); assert.equal(calls, 2); } let calls = 0; const repeated = loadBackground({ fetch: async () => { calls++; return response(page([11], { hasNext: true })); } }); assert.match((await repeated.dispatch(request())).error, /did not advance/); assert.equal(calls, 2); let attempt = 0; const partial = loadBackground({ fetch: async () => ++attempt === 1 ? response(page([11], { hasNext: true })) : response({}, 500) }); const failure = await partial.dispatch(request()); assert.equal(failure.ok, false); assert.equal(failure.debugRequests.length, 2); assert.equal(Object.keys(partial.storage[CACHE] || {}).length, 0); }); test("workload validation rejects unsafe IDs or missing WWB tokens before requesting", async () => { const h = loadBackground(); for (const extra of [{ customerId: "../123" }, { customerId: "" }, { authorization: {} }]) { const result = await h.dispatch(request(extra)); assert.equal(result.ok, false); assert.deepEqual(result.debugRequests, []); } }); test("workloads without a known principal are never persisted or reused across users", async () => { const storage = {}; let calls = 0; const h = loadBackground({ storage, fetch: async () => { calls++; return response(page([11])); } }); await h.dispatch(request({ principal: "" })); await h.dispatch(request({ principal: "" })); assert.equal(calls, 2); assert.equal(storage[CACHE], undefined); }); function content() { let listener; const app = loadApplication("content", { document: { getElementById: () => null }, window: { requestAnimationFrame() {} }, chrome: { runtime: { onMessage: { addListener(fn) { listener = fn; } } } } }); const calls = [], logs = []; let tokens = 0; app.currentUser.getUserId = () => "user@example.invalid"; app.authContentSession.requestConsumerTokenServiceOnce = async () => { tokens++; return { workloadActions: { value: "wwb" } }; }; app.debugContentLog.appendBackgroundRequestLog = entries => logs.push(...(entries || [])); app.platformContentMessages.sendRuntimeMessage = message => { if (message.type !== TYPE) return Promise.resolve({ ok: true }); return new Promise(resolve => calls.push({ message: plain(message), resolve })); }; app.productsContentJobs.ensureProductsBackgroundMessageHandler(); const start = opportunities => { app.productsContentJobs.requestOpptyProductsInBackground(opportunities, "fusion-token"); return app.productsContentState.currentProductsRequestId; }; const progress = (id, number, items, fromCache = false) => listener({ type: "opportunitiesExtension.opptyProductsProgress", requestId: id, optyNumber: number, items, fromCache, state: "running" }); return { app, calls, logs, start, progress, tokens: () => tokens }; } const opportunity = (OptyNumber, PartyId) => ({ OptyNumber, CustomerAccount: { PartyId } }); for (const productsFirst of [true, false]) { test(`accounts deduplicate independently of filters; products-first=${productsFirst} joins by account and RevnId`, async () => { const h = content(); h.app.preferencesState.opptyProductsCacheHours = 12; h.app.opportunitiesFilterState.tableSearch = "hides all opportunities"; const id = h.start([opportunity("A", 123), opportunity("B", "123"), opportunity("C", 456), { OptyNumber: "NO_ACCOUNT", PartyId: 999, Owner: { PartyId: 888 } }]); await flush(); assert.deepEqual(h.calls.map(call => call.message.customerId), ["123", "456"]); assert.deepEqual(plain(h.app.productsContentState.workloadCustomerIds), ["123", "456"]); assert.equal(h.tokens(), 1); assert.equal(h.calls[0].message.authorization.value, "wwb"); assert.equal(h.calls[0].message.cacheMaxAgeMs, 12 * 3600000); assert.equal(h.calls[0].message.principal, "user@example.invalid"); const original = { RevnId: 11, Name: "Original product" }; function products() { h.progress(id, "A", [original, { RevnId: "999" }]); h.progress(id, "B", [{ RevnId: "12" }], true); h.progress(id, "C", [{ RevnId: 11 }, { RevnId: 22 }]); } if (productsFirst) products(); h.calls[0].resolve({ ok: true, ...page([11, 12]), cachedAt: 100, fromCache: true, debugRequests: [{ name: "requestWorkloads" }] }); h.calls[1].resolve({ ok: true, ...page([22]), cachedAt: 200 }); await flush(); if (!productsFirst) products(); const results = h.app.productsContentState.opptyProductsByNumber; assert.equal(results.get("A").items[0].workload.id, 11); assert.equal(results.get("A").items[0].Name, "Original product"); assert.equal(original.workload, undefined, "do not mutate raw product responses or their cache"); assert.equal(results.get("A").items[1].workload, null); assert.equal(results.get("B").items[0].workload.id, 12); assert.equal(results.get("B").fromCache, true); assert.equal(results.get("C").items[0].workload, null, "never join a workload from another account"); assert.equal(results.get("C").items[1].workload.id, 22); assert.equal(h.app.productsContentState.workloadsByCustomer.get("123").fromCache, true); assert.equal(h.logs.length, 1); }); } test("workload queue limits concurrency to four and ignores a superseded Reference", async () => { const h = content(); h.start(Array.from({ length: 9 }, (_, i) => opportunity(`A${i}`, i + 1))); await flush(); assert.equal(h.calls.length, 4); h.app.opportunitiesQueryState.periodRequestVersion++; const id = h.start([opportunity("NEW", 100)]); await flush(); assert.equal(h.calls.length, 5); for (const call of h.calls.slice(0, 4)) call.resolve({ ok: true, ...page([11]) }); await flush(); assert.equal(h.calls.length, 5, "do not continue accounts from the old Reference"); assert.deepEqual([...h.app.productsContentState.workloadsByCustomer.keys()], ["100"]); h.calls[4].resolve({ ok: true, ...page([22]) }); await flush(); h.progress(id, "NEW", [{ RevnId: 22 }]); assert.equal(h.app.productsContentState.opptyProductsByNumber.get("NEW").items[0].workload.id, 22); }); test("one failed account does not block other accounts or discard products", async () => { const h = content(); const id = h.start([opportunity("A", 123), opportunity("B", 456)]); await flush(); h.progress(id, "A", [{ RevnId: 11 }]); h.calls[0].resolve({ ok: false, error: "Synthetic failure", debugRequests: [{ name: "requestWorkloads", httpStatus: 403 }] }); h.calls[1].resolve({ ok: true, ...page([22]) }); await flush(); assert.equal(h.app.productsContentState.workloadsByCustomer.get("123").status, "error"); assert.equal(h.app.productsContentState.workloadsByCustomer.get("456").status, "ready"); assert.equal(h.app.productsContentState.opptyProductsByNumber.get("A").items.length, 1); assert.equal(h.logs[0].httpStatus, 403); }); test("no accounts skips authentication, and failed authentication settles workload states", async () => { const h = content(); h.start([]); await flush(); assert.equal(h.tokens(), 0); h.app.authContentSession.requestConsumerTokenServiceOnce = async () => { throw new Error("Session unavailable"); }; h.start([opportunity("A", 123)]); await flush(); assert.equal(h.calls.length, 0); assert.equal(h.app.productsContentState.workloadsByCustomer.get("123").error, "Session unavailable"); });