const assert = require("node:assert/strict"); const { loadApplication } = require("./support/application.cjs"); class Element { constructor(tag) { this.tag = tag; this.children = []; this.dataset = {}; this.attributes = {}; this.listeners = {}; this.classes = new Set(); this.classList = { toggle: (name, on) => on ? this.classes.add(name) : this.classes.delete(name) }; } append(...children) { this.children.push(...children); } setAttribute(name, value) { this.attributes[name] = value; } addEventListener(name, callback) { this.listeners[name] = callback; } replaceWith(element) { this.replacement = element; } click() { this.listeners.click({ preventDefault() {}, stopPropagation() {} }); } } function setup(mockSalesToken = true) { const calls = []; let now = Date.now(); class TestDate extends Date { constructor(...args) { super(...(args.length ? args : [now])); } static now() { return now; } } let visibleAlerts = []; const context = { document: { createElement: (tag) => new Element(tag), createElementNS: (_, tag) => new Element(tag), querySelectorAll: () => visibleAlerts }, fetch: (url, options) => new Promise((resolve, reject) => calls.push({ url, options, resolve, reject })), Intl, Date: TestDate, Map, Set }; const app = loadApplication("content", context); app.authSessionState.accessToken = "test-bearer-secret"; app.authXsrfContent.getXsrfToken = async () => ({ token: "test-xsrf-cookie", cookieName: "XSRF-TOKEN-DYNAMIC", source: "test" }); app.platformContentMessages.refreshXsrfCookie = async () => ({ refreshed: false }); if (mockSalesToken) app.authSalesCommon.requestTokenRelaySalesCommon = async () => "test-sales-common-secret"; app.debugContentView.renderDebugPanel = () => {}; app.opportunitiesTable.renderOpportunitiesTable = () => {}; return { ...app.aiSummaryContent, ...app.productsDetails, ...app.aiSummaryState, ...app.productsContentState, ...app.debugContentLog, ...app.debugState, requestTokenRelay: app.authTokenRelay.requestTokenRelay, requestTokenRelaySalesCommon: app.authSalesCommon.requestTokenRelaySalesCommon, getAccessToken: () => app.authSessionState.accessToken, calls, advance: (ms) => { now += ms; }, show: (alerts) => { visibleAlerts = alerts; } }; } function json(data, status = 200) { return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json" } }); } const flush = () => new Promise((resolve) => setImmediate(resolve)); async function run() { const h = setup(); const products = { items: [{ ProdGroupName: "Cloud", RevnAmount: "10000", WinProb: 30 }] }; h.opptyProductsByNumber.set("A1", products); h.createProductsCountButton("A1", products).click(); const first = h.aiSummariesByOptyNumber.get("A1").promise; await flush(); assert.equal(h.calls.length, 1); assert.equal(h.requestGenerateAiSummary("A1"), first); assert.equal(h.calls.length, 1, "in-flight requests are deduplicated"); const { url, options } = h.calls[0]; assert.equal(url, "https://eeho.fa.us2.oraclecloud.com/api/sales-common/gen-ai/v1/summarizations/generate"); assert.equal(options.method, "POST"); assert.equal(options.headers.Authorization, "Bearer test-sales-common-secret"); assert.equal(options.headers["Content-Type"], "application/json"); assert.deepEqual(JSON.parse(options.body), { path: "/api/sales-common/gen-ai/v1/summarizations/generate", promptCode: "cx.sales.generate_opportunity_summary", object: "opportunities", puid: "A1" }); const loadingRow = h.createProductsDetailRow({ OptyNumber: "A1" }); const panel = loadingRow.children[0].children[0]; assert.equal(panel.children[1].attributes["aria-busy"], "true"); assert.equal(panel.children[2].tag, "table", "products remain below the summary"); assert.equal(panel.children[2].children[1].children.length, 1); h.createProductsCountButton("A1", products).click(); assert.equal(h.calls.length, 1, "collapsing does not request another summary"); assert.equal(h.expandedProductOptyNumbers.has("A1"), false); const second = h.requestGenerateAiSummary("A2"); await flush(); const visible = h.createAiSummaryAlert("A2"); h.show([visible]); h.calls[1].resolve(json({ summaryText: "Second opportunity" })); await second; assert.equal(visible.replacement.children[1].textContent, "Second opportunity"); const unsafeText = "\nLong summary"; h.calls[0].resolve(json({ summaryText: unsafeText, lastUpdateDate: "not displayed" })); await first; assert.equal(h.expandedProductOptyNumbers.has("A1"), false, "late responses do not reopen rows"); assert.equal(visible.replacement.children[1].textContent, "Second opportunity"); const ready = h.createAiSummaryAlert("A1"); assert.equal(ready.children[0].textContent, "Summary generated by AI"); assert.equal(ready.children[1].textContent, unsafeText); assert.equal(ready.children[1].children.length, 0, "AI text is not parsed as HTML"); assert.equal(ready.children.length, 2); assert.equal(h.requestLog[0].name, "requestGenerateAiSummary"); assert.equal(h.requestLog[0].httpStatus, 200); assert.equal(h.requestLog[0].status, "success"); assert.ok(h.requestLog[0].responseBody.includes("summaryText")); assert.ok(!h.formatHeaderBlock(h.requestLog[0].requestHeaders).includes("test-sales-common-secret")); h.createProductsCountButton("A1", products).click(); await flush(); assert.equal(h.calls.length, 3, "expanding again generates a fresh summary"); h.createProductsCountButton("A1", products).click(); h.createProductsCountButton("A1", products).click(); assert.equal(h.calls.length, 3, "rapid collapse/expand reuses the pending request"); h.calls[2].resolve(json({ summaryText: "Fresh summary" })); await h.aiSummariesByOptyNumber.get("A1").promise; for (const response of [json({}, 500), new Response("login"), json({}), json({ summaryText: " " }), json({ summaryText: 123 })]) { const failure = h.requestGenerateAiSummary("error"); await flush(); h.calls.at(-1).resolve(response); await failure; assert.equal(h.aiSummariesByOptyNumber.get("error").status, "error"); assert.equal(h.requestLog.at(-1).status, "failed"); assert.ok(h.requestLog.at(-1).httpStatus); assert.ok(h.createAiSummaryAlert("error").classes.has("opportunities-extension-ai-summary-error")); } const networkFailure = h.requestGenerateAiSummary("error"); await flush(); h.calls.at(-1).reject(new Error("Network unavailable")); await networkFailure; assert.equal(h.requestLog.at(-1).error, "Network unavailable"); const retry = h.requestGenerateAiSummary("error"); await flush(); h.calls.at(-1).resolve(json({ summaryText: "Recovered" })); await retry; assert.equal(h.aiSummariesByOptyNumber.get("error").status, "ready"); const scoped = setup(false); const generation = scoped.requestGenerateAiSummary("SC1"); await flush(); assert.equal(scoped.calls.length, 1, "summary waits for its scoped token"); assert.equal(scoped.calls[0].url, "https://eeho.fa.us2.oraclecloud.com/fscmRestApi/tokenrelay?scopesfq=urn%3Aopc%3Aresource%3Afusion%3Aeeho%3Asales-common%2F"); assert.equal(scoped.calls[0].options.method, "GET"); assert.equal(scoped.calls[0].options.credentials, "include"); assert.equal(scoped.calls[0].options.headers["x-xsrf-token"], "test-xsrf-cookie"); scoped.calls[0].resolve(json({ access_token: "scoped-token", expires_in: 3600 })); await flush(); assert.equal(scoped.calls[1].options.headers.Authorization, "Bearer scoped-token"); scoped.calls[1].resolve(json({ summaryText: "Scoped summary" })); await generation; assert.equal(scoped.requestLog[0].name, "requestTokenRelaySalesCommon"); assert.equal(scoped.requestLog[0].httpStatus, 200); assert.equal(scoped.getAccessToken(), "test-bearer-secret", "scoped token does not replace the general token"); const normal = scoped.requestTokenRelay(); await flush(); assert.equal(scoped.calls[2].url, "https://eeho.fa.us2.oraclecloud.com/fscmRestApi/tokenrelay"); assert.equal(scoped.calls[2].options.headers["x-xsrf-token"], scoped.calls[0].options.headers["x-xsrf-token"]); scoped.calls[2].resolve(json({ access_token: "normal-token" })); await normal; assert.equal(scoped.requestLog[2].name, "requestTokenRelay"); assert.equal(await scoped.requestTokenRelaySalesCommon(), "scoped-token"); assert.equal(scoped.calls.length, 3, "valid token is reused"); scoped.advance(3600 * 1000); const failedScope = scoped.requestGenerateAiSummary("SC2"); await flush(); scoped.calls[3].resolve(json({ error: "Unavailable" }, 500)); await failedScope; assert.equal(scoped.calls.length, 4, "no summary POST after scoped authentication failure"); assert.equal(scoped.aiSummariesByOptyNumber.get("SC2").status, "error"); assert.equal(scoped.requestLog[3].status, "failed"); const pendingToken = scoped.requestTokenRelaySalesCommon(); assert.equal(scoped.requestTokenRelaySalesCommon(), pendingToken, "concurrent refresh is deduplicated"); await flush(); scoped.calls[4].resolve(json({ access_token: "renewed-token", expires_in: 3600 })); await pendingToken; assert.equal(await scoped.requestTokenRelaySalesCommon(), "renewed-token"); assert.equal(scoped.calls.length, 5, "failed refresh can be retried and then cached"); const rejected = scoped.requestGenerateAiSummary("REJECTED"); const concurrent = scoped.requestGenerateAiSummary("CONCURRENT"); await flush(); assert.equal(scoped.calls[5].options.headers.Authorization, "Bearer renewed-token"); scoped.calls[5].resolve(json({}, 401)); await flush(); assert.equal(scoped.calls[7].options.method, "GET"); scoped.calls[7].resolve(json({ access_token: "replacement-token", expires_in: 3600 })); await flush(); assert.equal(scoped.calls[8].options.headers.Authorization, "Bearer replacement-token"); scoped.calls[8].resolve(json({ summaryText: "Recovered after refresh" })); await rejected; scoped.calls[6].resolve(json({}, 401)); await flush(); assert.equal(scoped.calls[9].options.method, "POST", "late rejection must reuse the already refreshed token"); assert.equal(scoped.calls[9].options.headers.Authorization, "Bearer replacement-token"); scoped.calls[9].resolve(json({ summaryText: "Concurrent recovery" })); await concurrent; assert.equal(scoped.calls.length, 10); const forbidden = scoped.requestGenerateAiSummary("FORBIDDEN"); await flush(); scoped.calls[10].resolve(json({}, 403)); await flush(); scoped.calls[11].resolve(json({ access_token: "still-forbidden", expires_in: 3600 })); await flush(); scoped.calls[12].resolve(json({}, 403)); await forbidden; assert.equal(scoped.calls.length, 13, "only one renewal and one summary retry per generation"); assert.equal(scoped.aiSummariesByOptyNumber.get("FORBIDDEN").status, "error"); assert.equal(scoped.requestLog.at(-1).httpStatus, 403); assert.equal(scoped.getAccessToken(), "test-bearer-secret"); console.log("AI summary tests passed."); } run().catch((error) => { console.error(error); process.exitCode = 1; });