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 flush = () => new Promise(resolve => setImmediate(resolve)); for (const family of ["chrome", "firefox"]) { test(`${family}: WWB-TS selection, fixed workload header, first-page GET and masked debug`, async () => { const calls = []; const h = loadBackground({ family, fetch: async (url, options) => { calls.push({ url, options }); if (url.endsWith("consumerTokenService")) return response([ { tokenService: "ACCP-TS", uiaasHeader: { value: "summary" } }, { tokenService: "OCICONS-TS", uiaasHeader: { value: "consumption" } }, { tokenService: "WWB-TS", uiaasHeader: { label: "ignored-label", value: "synthetic-wwb" } } ]); return response({ actions: [{ id: 1, name: "TEST" }], totalCount: 20, hasNext: true }); } }); const auth = await h.dispatch({ type: "opportunitiesExtension.requestConsumerTokenService" }); assert.equal(auth.authorizations.workloadActions.value, "synthetic-wwb"); const result = await h.dispatch({ type: "opportunitiesExtension.requestWlActions", revnId: "12345", authorization: auth.authorizations.workloadActions }); assert.equal(result.ok, true); assert.equal(result.hasNext, true); assert.equal(result.actions.length, 1); assert.equal(calls.length, 2, "no pagination"); assert.equal(calls[1].url, "https://spa.oracle.com/oalcrm/web/api/provider-proxy/wwb-provider/service/workbench-proxy/wwb/mgmt/actions?parentType=WORKLOAD&parentId=12345"); assert.equal(calls[1].options.method, "GET"); assert.equal(calls[1].options.headers["Wwb-Provider-Authorization"], "synthetic-wwb"); assert.equal(calls[1].options.headers.Authorization, undefined); assert.equal(result.debugRequests[0].name, "requestWlActions"); assert.equal(result.debugRequests[0].requestHeaders["Wwb-Provider-Authorization"], "(present)"); }); } test("workload actions reject missing credentials, invalid ids, HTTP/network/JSON failures", async () => { for (const fetch of [ async () => { throw new Error("Synthetic network failure"); }, async () => new Response("Denied", { status: 401 }), async () => new Response("Denied", { status: 403 }), async () => new Response("Login"), async () => response({ actions: null, hasNext: false }) ]) { const h = loadBackground({ fetch }); const result = await h.dispatch({ type: "opportunitiesExtension.requestWlActions", revnId: "123", authorization: { value: "synthetic" } }); assert.equal(result.ok, false); assert.equal(result.debugRequests.length, 1); } const h = loadBackground({ fetch: () => assert.fail("Unexpected network") }); for (const fields of [{ revnId: "123" }, { revnId: "bad&id=2", authorization: { value: "synthetic" } }]) { assert.equal((await h.dispatch({ type: "opportunitiesExtension.requestWlActions", ...fields })).ok, false); } }); function contentFixture(send) { const document = { createElement: () => ({ attributes: {}, append() {}, addEventListener() {}, setAttribute(k, v) { this.attributes[k] = v; } }) }; document.createElementNS = (_, tag) => document.createElement(tag); const app = loadApplication("content", { document }); app.currentUser.allowsCrmActivities = () => true; app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ workloadActions: { value: "synthetic-wwb" } }); app.authSessionState.principal = "test@example.invalid"; app.platformContentMessages.sendRuntimeMessage = send; app.debugContentView.renderDebugPanel = () => {}; app.productsContentJobs.queueProductsTableRender = () => {}; return app; } test("Actions progress counts product workloads, not opportunities, even with filters", async () => { const pending = new Map(); const app = contentFixture(message => new Promise(resolve => pending.set(message.revnId, resolve))); const progress = []; app.activityProgress.update = (...args) => progress.push(args); app.opportunitiesFilterState.tableSearch = "hidden"; const a = { items: [{ RevnId: "1" }, { RevnId: "2" }] }; const b = { items: [{ RevnId: "3" }] }; app.productsContentState.opptyProductsByNumber = new Map([["A", a], ["B", b]]); const loading = [app.opportunityWorkloadActions.loadProducts(a), app.opportunityWorkloadActions.loadProducts(b)]; await new Promise(resolve => setImmediate(resolve)); assert.equal(pending.size, 3); pending.get("1")({ ok: true, actions: [], hasNext: false }); await new Promise(resolve => setImmediate(resolve)); assert.deepEqual(progress.at(-1), ["actions", "running", 1, 3]); pending.get("2")({ ok: true, actions: [], hasNext: false }); await new Promise(resolve => setImmediate(resolve)); assert.deepEqual(progress.at(-1), ["actions", "running", 2, 3]); pending.get("3")({ ok: true, actions: [], hasNext: false }); await Promise.all(loading); assert.deepEqual(progress.at(-1), ["actions", "complete", 3, 3]); }); test("saved response updates the workload badge, tooltip and matching cache without fetching", async () => { const app = contentFixture(() => assert.fail("Save response must not trigger a GET")); const writes = []; app.activityCache.write = async (...args) => writes.push(args); const products = { items: [{ RevnId: "123" }], cachedAt: 1000 }; app.productsContentState.opptyProductsByNumber.set("A", products); app.productsContentState.opptyProductsByNumber.set("B", { items: [{ RevnId: "456" }] }); const action = { id: 77, name: "REVIEW", ownerEmail: "test@example.invalid", revenueId: 123, status: "COMPLETE" }; assert.equal(await app.opportunityWorkloadActions.applySavedAction("A", "123", action), true); assert.equal(app.opportunityWorkloadActions.getSummary("A").state, "matched"); assert.equal(app.opportunityWorkloadActions.getSummary("A").actions.length, 1); assert.equal(app.opportunityWorkloadActions.getSummary("B").actions.length, 0); assert.ok(app.opportunityWorkloadActions.createBadge("A").title.includes("ID: 77")); assert.deepEqual(writes[0].slice(0, 3), ["actions", "123", 1000]); assert.equal(writes[0][3][0].id, 77); await app.opportunityWorkloadActions.applySavedAction("A", "123", { action: { ...action, status: "OPEN" } }); assert.equal(app.opportunityWorkloadActions.getSummary("A").actions.length, 1); assert.equal(app.opportunityWorkloadActions.getSummary("A").actions[0].status, "OPEN"); await app.opportunityWorkloadActions.applySavedAction("A", "123", { actions: [{ ...action, id: 78, ownerEmail: "other@example.invalid" }] }); assert.equal(app.opportunityWorkloadActions.getSummary("A").actions.length, 2); assert.equal(await app.opportunityWorkloadActions.applySavedAction("B", "123", action), false); assert.equal(await app.opportunityWorkloadActions.applySavedAction("A", "123", null), false); }); test("saved Action uses its email rather than display name for green badge and removes plus", async () => { const app = contentFixture(() => assert.fail("No follow-up GET")); app.activityCache.write = async () => {}; app.productsContentState.opptyProductsByNumber.set("A", { items: [{ RevnId: "123" }] }); const saved = { id: 90, owner: "Test User", ownerEmail: " TEST@example.invalid ", name: "REVIEW" }; await app.opportunityWorkloadActions.applySavedAction("A", "123", saved); const badge = app.opportunityWorkloadActions.createBadge("A"); assert.equal(badge.attributes["data-state"], "matched"); assert.equal(badge.textContent, "1"); await app.opportunityWorkloadActions.applySavedAction("A", "123", { id: 90, owner: "Test User" }, { ownerEmail: "test@example.invalid" }); assert.equal(app.opportunityWorkloadActions.getSummary("A").state, "matched"); app.authSessionState.principal = "another@example.invalid"; assert.equal(app.opportunityWorkloadActions.getSummary("A").state, "missing"); }); test("Actions completion includes queued calls, shared pending records, failures and cache writes", async () => { const pending = []; let finishWrite; const app = contentFixture(() => new Promise(resolve => pending.push(resolve))); app.activityCache.read = async () => null; app.activityCache.write = () => new Promise(resolve => { finishWrite = resolve; }); const products = { items: Array.from({ length: 5 }, (_, i) => ({ RevnId: String(i + 1) })) }; let complete = false, sharedComplete = false; const batch = app.opportunityWorkloadActions.loadProducts(products, true, { cachedAt: 200 }).then(() => { complete = true; }); const shared = app.opportunityWorkloadActions.loadProducts(products).then(() => { sharedComplete = true; }); await flush(); assert.equal(pending.length, 4); for (let attempt = 0; attempt < 5; attempt += 1) { pending.slice(attempt * 4, attempt * 4 + 4).forEach(resolve => resolve({ ok: false, error: "Synthetic" })); await flush(); } assert.equal(pending.length, 21); assert.equal(complete, false); assert.equal(sharedComplete, false); pending[20]({ ok: true, actions: [], hasNext: false }); await flush(); assert.equal(complete, false, "cache write still pending"); finishWrite(); await batch; await shared; assert.equal(complete, true); assert.equal(sharedComplete, true); }); test("Actions reset settles discarded queue entries without waiting for unstarted requests", async () => { const pending = []; const app = contentFixture(() => new Promise(resolve => pending.push(resolve))); const batch = app.opportunityWorkloadActions.loadProducts({ items: Array.from({ length: 8 }, (_, i) => ({ RevnId: String(i) })) }); await flush(); app.opportunityWorkloadActions.reset(false); pending.forEach(resolve => resolve({ ok: true, actions: [], hasNext: false })); await batch; assert.equal(pending.length, 4); }); test("actions aggregate all products, share RevnId queries across Reference changes and restart on reopen", async () => { const calls = []; const app = contentFixture(async message => { calls.push(message); return { ok: true, hasNext: false, actions: message.revnId === "1" ? [{ id: 1, owner: "test@example.invalid", name: "" }] : [] }; }); const products = { items: [{ RevnId: "1" }, { RevnId: 2 }, { RevnId: "1" }] }; app.productsContentState.opptyProductsByNumber.set("A", products); app.opportunityWorkloadActions.loadProducts(products); assert.equal(app.opportunityWorkloadActions.createBadge("A").textContent, "..."); await flush(); assert.equal(calls.length, 2); assert.equal(calls[0].authorization.value, "synthetic-wwb"); const badge = app.opportunityWorkloadActions.createBadge("A"); assert.equal(badge.textContent, "1"); assert.match(badge.title, /