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 type = "opportunitiesExtension.requestRatecardByPlan"; const item = { orderNumber: "SYNTH-ORDER-2", partNum: "B-SYNTH-1", productName: "Synthetic GPU compute", uom: "GPU PER HOUR", subLnStartSt: "30-Sep-2025", subLnEndDt: "27-May-2028", usageMax: "-", rateCardDiscountPerc: "12.5", listPriceUsd: "4.3585", netUnitPriceUsd: "0.9589", overagePriceUsd: "1.2501" }; for (const family of ["chrome", "firefox"]) { test(`${family}: rate card uses the active planNumber, OCICONS-TS and exact POST contract, with 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: "not-used", value: "synthetic-consumption" } } ]); return response([item]); } }); const auth = await app.dispatch({ type: "opportunitiesExtension.requestConsumerTokenService" }); const result = await app.dispatch({ type, planNumber: "PLAN-123", subPlanNum: "WRONG-ID", authorization: auth.authorizations.accountPlans }); assert.equal(result.ok, true); assert.deepEqual(result.items, [item]); assert.equal(result.planNumber, "PLAN-123"); assert.equal(calls[1].url, "https://spa.oracle.com/oalcrm/web/api/provider-proxy/g2m-consumption/service/v1/ratecard/byPlan"); assert.equal(calls[1].options.method, "POST"); assert.equal(calls[1].options.credentials, "include"); assert.equal(calls[1].options.cache, "no-store"); assert.deepEqual(JSON.parse(calls[1].options.body), { planNumber: "PLAN-123", recordsSize: 10000 }); assert.deepEqual(plain(calls[1].options.headers), { Accept: "application/json", "Content-Type": "application/json", "G2m-Authorization": "synthetic-consumption" }); const entry = result.debugRequests[0]; assert.equal(entry.name, "requestRatecardByPlan"); assert.equal(entry.httpStatus, 200); assert.equal(entry.status, "success"); assert.ok(entry.startedAt && entry.completedAt); assert.equal(typeof entry.durationMs, "number"); assert.deepEqual(JSON.parse(entry.requestBody), { planNumber: "PLAN-123", recordsSize: 10000 }); assert.match(entry.responseBody, /Synthetic GPU/); const content = loadApplication("content"); assert.doesNotMatch(content.debugContentLog.formatHeaderBlock(entry.requestHeaders), /synthetic-consumption/); assert.equal(content.debugContentView.getRequestCategory(entry), "consumption"); }); } test("rate card fails locally for missing identifiers/credentials, HTTP, network, JSON and collection errors without retries", async () => { for (const failure of ["id", "token", "401", "403", "network", "json", "shape", "item"]) { let calls = 0; const app = loadBackground({ fetch: async () => { calls += 1; if (["401", "403"].includes(failure)) return response({}, Number(failure)); if (failure === "network") throw new Error("Synthetic network failure"); if (failure === "json") return new Response("fallback"); return response(failure === "shape" ? {} : [null]); } }); const result = await app.dispatch({ type, planNumber: failure === "id" ? "" : "A", authorization: { value: failure === "token" ? "" : "synthetic" } }); assert.equal(result.ok, false, failure); assert.ok(result.error, failure); assert.equal(calls, ["id", "token"].includes(failure) ? 0 : 1, failure); if (calls) assert.equal(result.debugRequests[0].status, "failed", failure); } }); test("all eleven columns are searchable, percentages and precise prices are formatted, and sorting is typed", () => { const model = loadApplication("content").ratecardModel; assert.equal(model.columns.length, 11); for (const column of model.columns) { assert.equal(model.selectItems([item], item[column.key]).length, 1, column.key); assert.equal(model.selectItems([item], model.formatValue(item, column)).length, 1, column.key); } const format = (key, value) => model.formatValue({ [key]: value }, model.columns.find(column => column.key === key)); assert.equal(format("netUnitPriceUsd", "0.9589"), "0.9589"); assert.equal(format("listPriceUsd", 0), "0.0000"); assert.equal(format("rateCardDiscountPerc", 12.5), "12.5%"); assert.equal(format("usageMax", 0), "0"); assert.equal(format("subLnEndDt", null), "-"); assert.equal(model.selectItems([{ ...item, productName: "Synthet\u00edc" }], "synthetic").length, 1); assert.equal(model.selectItems([{ ...item, hiddenMetadata: "secretmatch" }], "secretmatch").length, 0); const rows = [ { ...item, netUnitPriceUsd: "10", subLnStartSt: "01-Jan-2027" }, { ...item, netUnitPriceUsd: "2", subLnStartSt: "31-Dec-2026" }, { ...item, netUnitPriceUsd: null, subLnStartSt: null } ]; assert.deepEqual(plain(model.selectItems(rows, "", "netUnitPriceUsd").map(row => row.netUnitPriceUsd)), ["2", "10", null]); assert.deepEqual(plain(model.selectItems(rows, "", "netUnitPriceUsd", "desc").map(row => row.netUnitPriceUsd)), ["10", "2", null]); assert.deepEqual(plain(model.selectItems(rows, "", "subLnStartSt").map(row => row.subLnStartSt)), ["31-Dec-2026", "01-Jan-2027", null]); assert.deepEqual(rows.map(row => row.netUnitPriceUsd), ["10", "2", null], "sorting does not mutate responses"); }); function setup() { const document = { activeElement: null, documentElement: {} }; 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.text = ""; this.value = ""; this.inert = false; this.classList = { add: name => { this.className = `${this.className || ""} ${name}`; } }; } 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, callback) { this.listeners[name] = callback; } focus(options) { document.activeElement = this; this.focusOptions = options; } remove() { if (this.parent) this.parent.children = this.parent.children.filter(node => node !== 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; } } 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(); const frames = new Map(); let id = 0; const app = loadApplication("content", { document, window: { addEventListener() {} }, requestAnimationFrame: fn => { frames.set(++id, fn); return id; }, cancelAnimationFrame: key => frames.delete(key), setTimeout: fn => { timers.set(++id, fn); return id; }, clearTimeout: key => timers.delete(key) }); root.id = app.contracts.MODAL_ID; app.consumptionPlanDetailController.loadPlanDetailForPanel = () => {}; app.consumptionTrendController.loadConsumptionTrendForPanel = () => {}; const plans = [{ contractId: "CONTRACT-A", planNumber: "PLAN-A", subPlanNum: "SUB-A", bookingCustomer: "Booking A", soldToPartyName: "Sold A", hdrStatusCode: "ACTIVE", startDate: "23-Aug-2024", endDate: "22-Aug-2028" }, { contractId: "CONTRACT-B", planNumber: "PLAN-B", subPlanNum: "SUB-B", bookingCustomer: "Booking B", soldToPartyName: "Sold B", hdrStatusCode: "EXPIRED", startDate: "01-Jan-2020", endDate: "01-Jan-2024" }]; const opportunity = { OptyNumber: "SYNTH", CustomerAccount: { PartyUniqueName: "Synthetic Customer" } }; app.consumptionPlansView.openAccountPlansModal(opportunity, [], "loading"); app.consumptionPlansView.openAccountPlansModal(opportunity, plans); timers.clear(); frames.clear(); const logs = [], requests = []; app.debugContentLog.appendBackgroundRequestLog = entries => logs.push(entries); app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ accountPlans: { value: "synthetic-plan" } }); app.platformContentMessages.sendRuntimeMessage = message => new Promise(resolve => requests.push({ message, resolve })); const find = suffix => root.querySelector(`.opportunities-extension-ratecard-${suffix}`); const buttons = root.querySelectorAll(".opportunities-extension-ratecard-button"); const nextFrame = () => { const callbacks = [...frames.values()]; frames.clear(); callbacks.forEach(fn => fn()); }; return { app, root, document, walk, timers, frames, nextFrame, plans, requests, logs, buttons, find }; } const tick = () => new Promise(resolve => setImmediate(resolve)); test("Rate card sits beside Contract ID, opens a skeleton, searches and sorts without HTML injection", async () => { const { app, root, buttons, find, document, requests, logs } = setup(); assert.equal(buttons.length, 2); assert.equal(buttons[0].textContent, "Rate Card"); assert.equal(buttons[0].parent.parent.className, "opportunities-extension-contract-row"); assert.equal(buttons[0].parent.children[0].textContent, "CONTRACT-A"); const opening = buttons[0].listeners.click(); assert.equal(find("drawer").attributes["aria-busy"], "true"); assert.equal(root.querySelectorAll(".opportunities-extension-ratecard-skeleton").length, 77); assert.equal(document.activeElement, find("search")); await tick(); assert.deepEqual(plain(requests[0].message), { type, planNumber: "SUB-A", authorization: { value: "synthetic-plan" } }); const parentHeader = root.querySelector(".opportunities-extension-account-plans-header"); assert.equal(find("header").querySelector("h2").textContent, "Rate Card"); assert.equal(parentHeader.querySelector("h2").textContent, "Cloud Consumption"); assert.equal(find("header").querySelector("p").textContent, parentHeader.querySelector("p").textContent); assert.equal(find("header").querySelector("p").textContent, "Synthetic Customer"); assert.notEqual(find("header").querySelector("h2").id, parentHeader.querySelector("h2").id); assert.ok(find("header").className.includes("opportunities-extension-account-plans-header")); const details = find("plan-details"); assert.deepEqual(details.querySelectorAll("dt").map(node => node.textContent), ["Booking Customer", "Sold To Party Name", "Contract ID", "Header Status", "Start Date", "End Date"]); assert.deepEqual(details.querySelectorAll("dd").map(node => node.textContent), ["Booking A", "Sold A", "CONTRACT-A", "ACTIVE", "23-Aug-2024", "22-Aug-2028"]); assert.equal(details.querySelectorAll("button").length, 0, "no recursive Rate Card action inside its own drawer"); const unsafe = ""; requests[0].resolve({ ok: true, items: [{ ...item, netUnitPriceUsd: "10", productName: unsafe }, item], debugRequests: [{ name: "requestRatecardByPlan" }] }); await opening; assert.equal(find("drawer").attributes["aria-busy"], "false"); assert.equal(find("count").textContent, "2 of 2 items"); assert.equal(root.querySelectorAll("img").length, 0); const table = find("table"); assert.equal(table.querySelector("tbody").children[0].children[2].textContent, unsafe); assert.equal(logs[0][0].name, "requestRatecardByPlan"); const priceSort = table.querySelectorAll("th")[9]; priceSort.children[0].listeners.click(); assert.equal(priceSort.attributes["aria-sort"], "ascending"); assert.equal(table.querySelector("tbody").children[0].children[9].textContent, "0.9589"); priceSort.children[0].listeners.click(); assert.equal(priceSort.attributes["aria-sort"], "descending"); assert.equal(table.querySelector("tbody").children[0].children[9].textContent, "10.0000"); find("search").value = "12.5%"; find("search").listeners.input(); assert.equal(find("count").textContent, "2 of 2 items"); find("search").value = "no match"; find("search").listeners.input(); assert.equal(find("count").textContent, "0 of 2 items"); assert.equal(find("notice").children[0].textContent, "No matching rate card items."); find("header").querySelector("button").listeners.click(); assert.equal(root.querySelector(".opportunities-extension-account-plans-modal").hidden, undefined, "drawer close keeps Cloud Consumption open"); app.ratecardView.closeRatecardDrawer({ immediate: true }); }); test("quick clicks share a request, tab changes discard stale results, closing restores focus and keyboard stays inside", async () => { const { app, root, buttons, requests, find, document, timers } = setup(); const first = buttons[0].listeners.click(); const repeat = buttons[0].listeners.click(); await tick(); assert.equal(requests.length, 1); root.querySelectorAll("[role='tab']")[1].listeners.click(); assert.equal(find("overlay"), null); const second = buttons[1].listeners.click(); await tick(); assert.equal(requests[1].message.planNumber, "SUB-B"); assert.deepEqual(find("plan-details").querySelectorAll("dd").map(node => node.textContent), ["Booking B", "Sold B", "CONTRACT-B", "EXPIRED", "01-Jan-2020", "01-Jan-2024"]); requests[1].resolve({ ok: true, items: [item] }); await second; requests[0].resolve({ ok: true, items: [{ ...item, productName: "WRONG PLAN" }] }); await Promise.all([first, repeat]); assert.equal(find("table").querySelector("tbody").children[0].children[2].textContent, item.productName); const background = root.querySelector(".opportunities-extension-account-plans-dialog"); assert.equal(background.inert, true); const overlay = find("overlay"); const close = find("header").querySelector("button"); close.focus(); let prevented = 0, stopped = 0; overlay.listeners.keydown({ key: "Tab", shiftKey: true, preventDefault() { prevented++; } }); assert.equal(document.activeElement, find("table").querySelectorAll("button")[10]); overlay.listeners.keydown({ key: "Tab", preventDefault() { prevented++; } }); assert.equal(document.activeElement, close); overlay.listeners.keydown({ key: "Escape", preventDefault() { prevented++; }, stopPropagation() { stopped++; } }); assert.equal(prevented, 3); assert.equal(stopped, 1); assert.equal(overlay.dataset.closing, "true"); assert.equal(document.activeElement, buttons[1]); assert.equal(background.inert, false); assert.equal(timers.size, 1); const reopening = buttons[1].listeners.click(); assert.equal(timers.size, 0); assert.equal(overlay.isConnected, false); await tick(); app.consumptionPlansView.closeAccountPlansModal(); assert.equal(find("overlay"), null); requests[2].resolve({ ok: true, items: [item] }); await reopening; assert.equal(find("overlay"), null, "late responses never reopen a closed drawer"); }); test("missing subPlanNum does not use another identifier; errors are local, empty results and retry on reopening work", async () => { const { app, plans, buttons, requests, find } = setup(); delete plans[0].subPlanNum; await buttons[0].listeners.click(); assert.match(find("notice").children[0].textContent, /Plan number is unavailable/); assert.equal(requests.length, 0); const failed = buttons[1].listeners.click(); await tick(); requests[0].resolve({ ok: false, error: "Synthetic 401", debugRequests: [] }); await failed; assert.equal(find("notice").children[0].attributes.role, "alert"); assert.equal(find("notice").children[0].textContent, "Synthetic 401"); const retry = buttons[1].listeners.click(); await tick(); requests[1].resolve({ ok: true, items: [] }); await retry; assert.equal(find("notice").children[0].textContent, "No rate card items found for this plan."); find("overlay").listeners.click({ target: find("overlay") }); app.ratecardView.closeRatecardDrawer({ immediate: true }); assert.equal(find("overlay"), null); }); test("opening a plan without planNumber sends its subPlanNum as the payload planNumber", async () => { const { plans, buttons, requests } = setup(); delete plans[0].planNumber; const opening = buttons[0].listeners.click(); await tick(); assert.equal(requests.length, 1); assert.equal(requests[0].message.planNumber, "SUB-A"); requests[0].resolve({ ok: true, items: [] }); await opening; }); test("drawer starts offscreen, opens after a painted frame and never scrolls when focused", () => { const { app, buttons, plans, find, frames, nextFrame } = setup(); app.ratecardView.openRatecardDrawer(plans[0], buttons[0]); const overlay = find("overlay"); assert.equal(overlay.dataset.open, "false"); assert.equal(find("search").focusOptions.preventScroll, true); nextFrame(); assert.equal(overlay.dataset.open, "false", "offscreen position gets one full paint"); nextFrame(); assert.equal(overlay.dataset.open, "true"); assert.equal(frames.size, 0); app.ratecardView.closeRatecardDrawer({ immediate: true }); assert.equal(buttons[0].focusOptions.preventScroll, true); for (const painted of [false, true]) { app.ratecardView.openRatecardDrawer(plans[0], buttons[0]); const cancelled = find("overlay"); if (painted) nextFrame(); app.ratecardView.closeRatecardDrawer({ immediate: true }); assert.equal(frames.size, 0); nextFrame(); assert.equal(cancelled.dataset.open, "false", "closing cancels pending entry frames"); assert.equal(cancelled.isConnected, false); } }); test("drawer CSS supports both themes, reduced motion, bounded scrolling and separate header icon space", () => { const css = fs.readFileSync(path.join(__dirname, "../src/features/ratecard/styles.css"), "utf8"); assert.match(css, /data-theme="dark"/); const buttonStyle = css.match(/\.opportunities-extension-ratecard-button \{([^}]+)\}/)[1]; assert.match(buttonStyle, /display: inline-flex/); assert.match(buttonStyle, /align-items: center/); assert.match(buttonStyle, /height: 28px/); assert.match(buttonStyle, /line-height: 20px/); assert.match(css, /\.opportunities-extension-contract-ratecard > span \{\s*line-height: 20px/); assert.match(css, /prefers-reduced-motion/); assert.match(css, /translateX\(100%\)/); assert.match(css, /transition: transform 320ms/); assert.match(css, /\[data-open="true"\] \.opportunities-extension-ratecard-drawer \{ transform: translateX\(0\); \}/); assert.match(css, /overflow: clip/, "the overlay cannot be scrolled by focus during entry"); assert.doesNotMatch(css, /opportunities-ratecard-(?:fade|slide|backdrop)-in/); assert.match(css, /grid-template-columns: minmax\(0, 1fr\) 12px/); assert.match(css, /min-width: 1100px/); assert.match(css, /grid-template-rows: auto auto auto minmax\(0, 1fr\)/); assert.doesNotMatch(css, /\.opportunities-extension-account-plans-dialog\s*\{/); });