const assert = require("node:assert/strict"); const { test } = require("node:test"); const fs = require("node:fs"); const path = require("node:path"); const { loadBackground, response, plain } = require("./support/characterization.cjs"); const { loadApplication } = require("./support/application.cjs"); const detail = { projectedStatus: "Forfeit", pricingModel: "Funded Allocation", lineStatus: "ACTIVE", startDate: "17-Aug-2023", endDate: "16-Aug-2027", committedCredits: 7488782, consumed: 4106739, consumptionL30Days: 152200, monthsRemaining: null, projectedConsumption: null, projectedDelta: null, currencyCode: "BRL", orderId: "SYNTH-ORDER", orderTrackerURL: "https://accountdashboard.oracle.com/app/udb/?appId=12#/ordertracker/orderNumber/SYNTH-ORDER", clines: [{ projectedStatus: "Overage", pricingModel: "Funded Allocation", monthsRemaining: 0, committedCredits: 100, consumed: 110, projectedConsumption: 120, projectedDelta: -20, orderId: "SYNTH-CHILD", orderTrackerURL: "https://accountdashboard.oracle.com/app/udb/#/ordertracker/orderNumber/SYNTH-CHILD" }] }; const type = "opportunitiesExtension.requestConsumptionPlanDetail"; for (const family of ["chrome", "firefox"]) { test(`${family}: plan details use OCICONS value in G2m-Authorization, encoded subPlanNum, and debug`, async () => { const calls = []; const app = loadBackground({ family, fetch: async (url, options) => { calls.push({ url, options }); if (url.endsWith("consumerTokenService")) return response([ { tokenService: "ACCP-TS", uiaasHeader: { value: "synthetic-account" } }, { tokenService: "OCICONS-TS", uiaasHeader: { label: "unused-label", value: "synthetic-plan" } } ]); return response([detail]); } }); const auth = await app.dispatch({ type: "opportunitiesExtension.requestConsumerTokenService" }); const result = await app.dispatch({ type, subPlanNum: "plan/test", authorization: auth.authorizations.accountPlans }); assert.equal(result.ok, true); assert.deepEqual(result.items, [detail]); assert.equal(result.subPlanNum, "plan/test"); assert.equal(calls[1].url, "https://spa.oracle.com/oalcrm/web/api/provider-proxy/g2m-consumption/service/v1/plan/plandetail/plan%2Ftest?active=true"); assert.equal(calls[1].options.method, "GET"); assert.equal(calls[1].options.credentials, "include"); assert.equal(calls[1].options.cache, "no-store"); assert.equal(calls[1].options.body, undefined); assert.deepEqual(plain(calls[1].options.headers), { Accept: "application/json", "G2m-Authorization": "synthetic-plan" }); assert.equal(result.debugRequests[0].name, "requestConsumptionPlanDetail"); assert.equal(result.debugRequests[0].httpStatus, 200); const content = loadApplication("content"); assert.doesNotMatch(content.debugContentLog.formatHeaderBlock(result.debugRequests[0].requestHeaders), /synthetic-plan/); assert.equal(content.debugContentView.getRequestCategory(result.debugRequests[0]), "consumption"); }); } test("missing token/subPlanNum, HTTP, network, invalid JSON and malformed nested lines do not silently succeed", async () => { for (const kind of ["token", "id", "http", "network", "json", "array", "clines"]) { let calls = 0; const app = loadBackground({ fetch: async () => { calls += 1; if (kind === "http") return response({}, 403); if (kind === "network") throw new Error("Synthetic network failure"); if (kind === "json") return new Response("fallback"); return response(kind === "array" ? {} : [{ clines: {} }]); } }); const result = await app.dispatch({ type, subPlanNum: kind === "id" ? "" : "test", authorization: { value: kind === "token" ? "" : "synthetic-plan" } }); assert.equal(result.ok, false, kind); assert.ok(result.error, kind); assert.equal(calls, ["token", "id"].includes(kind) ? 0 : 1); if (calls) assert.equal(result.debugRequests[0].status, "failed"); } }); function createDom() { const document = { activeElement: null, listeners: new Map(), documentElement: {} }; const window = { innerWidth: 420, innerHeight: 640, listeners: new Map() }; for (const target of [document, window]) { target.addEventListener = (name, fn) => { if (!target.listeners.has(name)) target.listeners.set(name, new Set()); target.listeners.get(name).add(fn); }; target.removeEventListener = (name, fn) => target.listeners.get(name)?.delete(fn); } const walk = node => [node, ...node.children.flatMap(walk)]; class Element { constructor(tag) { this.tag = tag; this.children = []; this.attributes = {}; this.listeners = {}; this.style = {}; this.dataset = {}; this.hidden = false; this.text = ""; this.classList = { add: name => { this.className = `${this.className || ""} ${name}`.trim(); } }; } append(...nodes) { nodes.forEach(node => { node.parent = this; }); this.children.push(...nodes); } set textContent(value) { this.text = String(value); this.children.forEach(node => { node.parent = null; }); this.children = []; } get textContent() { return this.text; } setAttribute(key, value) { this.attributes[key] = value; } getAttribute(key) { return this.attributes[key]; } addEventListener(name, fn) { this.listeners[name] = fn; } removeEventListener(name) { delete this.listeners[name]; } focus() { document.activeElement = this; } contains(node) { return !!node && walk(this).includes(node); } remove() { if (this.parent) this.parent.children = this.parent.children.filter(child => child !== this); this.parent = null; } get isConnected() { return this === root || !!this.parent?.isConnected; } querySelectorAll(selector) { return walk(this).slice(1).filter(node => selector.split(",").some(part => { const value = part.trim(); if (value.startsWith(".")) return (node.className || "").split(" ").includes(value.slice(1)); const attr = value.match(/^\[([^=]+)='([^']+)'\]$/); return attr ? node.attributes[attr[1]] === attr[2] : node.tag === value; })); } querySelector(selector) { return this.querySelectorAll(selector)[0] || null; } getBoundingClientRect() { return this.tag === "button" ? { left: 350, top: 530, bottom: 550, height: 20 } : { height: 500 }; } } const root = new Element("main"); document.createElement = tag => new Element(tag); document.createElementNS = (_, tag) => new Element(tag); document.createTextNode = text => { const node = new Element("#text"); node.textContent = text; return node; }; document.getElementById = id => walk(root).find(node => node.id === id) || null; const timers = new Map(); let timerId = 0; return { root, document, window, walk, timers, setTimeout: fn => { timers.set(++timerId, fn); return timerId; }, clearTimeout: id => timers.delete(id) }; } function setup() { const dom = createDom(); const app = loadApplication("content", { ...dom, URL, Intl, requestAnimationFrame: () => {} }); dom.root.id = app.contracts.MODAL_ID; app.debugContentLog.appendBackgroundRequestLog = () => {}; return { ...dom, app }; } test("nested tooltip preserves values/null/zero and restricts order links to the expected HTTPS origin", () => { const { app, root, walk } = setup(); app.consumptionPlanDetailView.renderPlanDetailBadges(root, [detail, { projectedStatus: "Other", pricingModel: "", orderId: "bad", orderTrackerURL: "javascript:bad()" }]); assert.equal(root.children.length, 2); assert.equal(root.children[0].children[0].dataset.status, "forfeit"); const tooltip = root.children[0].children[1]; assert.equal(tooltip.hidden, true); const nesting = tooltip.querySelector(".opportunities-extension-projection-children"); assert.ok(nesting); assert.equal(nesting.querySelector(".opportunities-extension-projection-badge").dataset.status, "overage"); const terms = tooltip.querySelectorAll("dt"); assert.equal(terms.length, 22); assert.ok(terms.some(term => term.textContent === "Order number")); const values = tooltip.querySelectorAll("dd").map(value => value.textContent); assert.ok(values.includes("7,488,782.00")); assert.ok(values.includes("-20.00")); assert.ok(values.includes("0")); assert.ok(values.includes("-")); assert.ok(values.includes("17-Aug-2023")); assert.equal(root.querySelectorAll("a").length, 2); assert.ok(root.querySelectorAll("a").every(link => link.target === "_blank" && link.rel === "noopener noreferrer")); assert.ok(walk(root).some(node => node.textContent === "")); for (const orderTrackerURL of ["https://accountdashboard.oracle.com.evil.example/x", "https://user@accountdashboard.oracle.com/x", "http://accountdashboard.oracle.com/x"]) { assert.equal(app.consumptionPlanDetailView.createLineDetails({ orderId: "test", orderTrackerURL }).querySelectorAll("a").length, 0); } }); test("hover, click, keyboard, outside dismissal and cleanup keep the popover within the viewport", () => { const { app, root, document, window, timers } = setup(); app.consumptionPlanDetailView.renderPlanDetailBadges(root, [detail]); const wrapper = root.children[0]; const [button, tooltip] = wrapper.children; wrapper.listeners.pointerenter(); assert.equal(tooltip.hidden, false); assert.equal(button.attributes["aria-expanded"], "true"); assert.ok(parseFloat(tooltip.style.left) + parseFloat(tooltip.style.width) <= window.innerWidth); assert.ok(parseFloat(tooltip.style.top) >= 12); wrapper.listeners.pointerleave(); assert.equal(timers.size, 1); wrapper.listeners.pointerenter(); assert.equal(timers.size, 0); button.listeners.click(); wrapper.listeners.pointerleave(); assert.equal(timers.size, 0, "click pins the tooltip so the order links remain usable"); let stopped = false; wrapper.listeners.keydown({ key: "Escape", preventDefault() {}, stopPropagation() { stopped = true; } }); assert.equal(stopped, true); assert.equal(tooltip.hidden, true); assert.equal(document.activeElement, button); assert.equal(document.listeners.get("pointerdown").size, 0); assert.equal(window.listeners.get("resize").size, 0); wrapper.listeners.focusin(); for (const listener of document.listeners.get("pointerdown")) listener({ target: root }); assert.equal(tooltip.hidden, true); }); test("controller shares in-flight requests, preserves per-panel identity and ignores removed panels", async () => { const { app, root, document } = setup(); const calls = []; app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ accountPlans: { value: "synthetic-plan" } }); app.platformContentMessages.sendRuntimeMessage = message => new Promise(resolve => calls.push({ message, resolve })); const panel = document.createElement("section"); panel.append(app.consumptionPlanDetailController.createPlanDetailSection({ subPlanNum: "A" })); root.append(panel); const first = app.consumptionPlanDetailController.loadPlanDetailForPanel(panel, { subPlanNum: "A" }); const repeat = app.consumptionPlanDetailController.loadPlanDetailForPanel(panel, { subPlanNum: "A" }); assert.equal(first, repeat); assert.equal(panel.children[0].attributes["aria-busy"], "true"); const other = document.createElement("section"); other.append(app.consumptionPlanDetailController.createPlanDetailSection({ subPlanNum: "B" })); root.append(other); const second = app.consumptionPlanDetailController.loadPlanDetailForPanel(other, { subPlanNum: "B" }); await new Promise(resolve => setImmediate(resolve)); assert.equal(calls.length, 2); assert.deepEqual(plain(calls[0].message), { type, subPlanNum: "A", authorization: { value: "synthetic-plan" } }); calls[1].resolve({ ok: true, items: [{ projectedStatus: "Overage" }] }); await second; panel.remove(); calls[0].resolve({ ok: true, items: [detail] }); await first; assert.equal(other.querySelector(".opportunities-extension-projection-badge").textContent, "Overage"); assert.equal(panel.querySelector(".opportunities-extension-projection-badge"), null); await app.consumptionPlanDetailController.loadPlanDetailForPanel(other, { subPlanNum: "B" }); assert.equal(calls.length, 2, "ready details remain in the current modal only"); }); test("Cloud Consumption loads details for the first tab and each newly selected plan, without delaying trends", () => { const { app, root, document } = setup(); const calls = []; const trends = []; app.consumptionPlanDetailController.loadPlanDetailForPanel = (_, plan) => calls.push(plan.subPlanNum); app.consumptionTrendController.loadConsumptionTrendForPanel = (_, plan) => trends.push(plan.subPlanNum); app.consumptionPlansView.openAccountPlansModal({ OptyNumber: "SYNTH" }, [], "loading"); app.consumptionPlansView.openAccountPlansModal({ OptyNumber: "SYNTH" }, [{ subPlanNum: "A" }, { subPlanNum: "B" }], "ready"); assert.deepEqual(calls, ["A"]); assert.deepEqual(trends, ["A"]); const badges = root.querySelector(".opportunities-extension-plan-detail-badges"); const trendHeader = root.querySelector(".opportunities-extension-consumption-trend-header"); assert.equal(trendHeader.children[0], badges, "projection badges are left of the period controls"); assert.equal(trendHeader.children[1].className, "opportunities-extension-consumption-periods"); assert.deepEqual(trendHeader.children[1].children.map(button => button.textContent), ["Quarterly", "Monthly", "Weekly", "Daily"]); assert.equal(root.querySelector(".opportunities-extension-account-plan-details").querySelector(".opportunities-extension-plan-detail-badges"), null); app.consumptionPlanDetailView.renderPlanDetailBadges(badges, [detail]); const wrapper = badges.children[0]; wrapper.listeners.pointerenter(); root.querySelectorAll("[role='tab']")[1].listeners.click(); assert.equal(wrapper.children[1].hidden, true, "switching tabs closes the previous tooltip"); assert.deepEqual(calls, ["A", "B"]); assert.deepEqual(trends, ["A", "B"]); assert.equal(root.querySelectorAll(".opportunities-extension-plan-detail-badges").length, 2); app.consumptionPlansView.closeAccountPlansModal(); assert.equal(document.getElementById(app.contracts.ACCOUNT_PLANS_MODAL_ID).hidden, true); assert.equal(document.listeners.get("pointerdown").size, 0); }); test("a detail failure stays local and the next activation can retry and display the empty state", async () => { const { app, root, document } = setup(); const panel = document.createElement("section"); const section = app.consumptionPlanDetailController.createPlanDetailSection({ subPlanNum: "A" }); panel.append(section); root.append(panel); app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ accountPlans: { value: "synthetic-plan" } }); let count = 0; app.platformContentMessages.sendRuntimeMessage = async () => ++count === 1 ? { ok: false, error: "Synthetic detail failure" } : { ok: true, items: [] }; await app.consumptionPlanDetailController.loadPlanDetailForPanel(panel, { subPlanNum: "A" }); assert.equal(section.attributes["aria-busy"], "false"); assert.equal(section.children[0].attributes.role, "alert"); assert.equal(section.children[0].textContent, "Synthetic detail failure"); await app.consumptionPlanDetailController.loadPlanDetailForPanel(panel, { subPlanNum: "A" }); assert.equal(count, 2); assert.equal(section.children[0].textContent, "No projection data"); }); test("plan projection CSS is scoped, has red/blue states in both themes and bounds only the tooltip scrolling", () => { const css = fs.readFileSync(path.join(__dirname, "../src/features/consumption/plan-detail.css"), "utf8"); assert.match(css, /data-status="forfeit"/); assert.match(css, /data-status="overage"/); assert.match(css, /data-theme="dark"/); assert.match(css, /position: fixed/); const headerRule = css.match(/\.opportunities-extension-consumption-trend-header \{([^}]+)\}/)[1]; assert.match(headerRule, /justify-content: space-between/, "badges align with the chart's left edge while period controls stay right"); assert.match(css, /prefers-reduced-motion/); assert.doesNotMatch(css, /\.opportunities-extension-account-plans-dialog\s*\{/); });