Optimized requests to retrieve SRs and Actions

This commit is contained in:
2026-09-25 11:08:02 -03:00
parent 86973f7542
commit 6a3e9e6adf
14 changed files with 294 additions and 84 deletions

View File

@@ -37,7 +37,7 @@ test("CRM fetch is called without the environment object as its receiver", async
assert.equal(items.length, 2);
});
test("CRM query preserves payload, paginates before opportunities, uses tokenrelay Bearer and logs each page", async () => {
test("CRM query preserves payload and pagination after opportunities, uses tokenrelay Bearer and logs each page", async () => {
const calls = [];
const app = setup(async (url, options) => {
const payload = JSON.parse(options.body);
@@ -49,11 +49,12 @@ test("CRM query preserves payload, paginates before opportunities, uses tokenrel
app.productsContentJobs.renderProductsProgress = () => {};
app.productsContentJobs.requestOpptyProductsInBackground = () => {};
app.fusionOpportunities.requestOpportunitiesPage = async () => {
assert.equal(calls.length, 2, "all CRM pages must finish first");
assert.equal(calls.length, 0, "CRM must not run before product loading");
return [{ OptyNumber: "TEST-1" }];
};
await app.opportunitiesController.requestOpportunities("synthetic-token", "Current Quarter", {}, 1);
assert.equal(states.at(-1).status, "ready");
await app.opportunityCrmServiceRequests.load("synthetic-token", 1);
assert.deepEqual(calls.map(call => call.payload.offset), [0, 1]);
const { payload, options, url } = calls[0];
assert.equal(url, "https://eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/latest/custom-actions/queries");
@@ -116,6 +117,7 @@ test("CRM failures do not mislabel missing SRs or block the opportunity list", a
app.fusionOpportunities.requestOpportunitiesPage = async () => { loaded = true; return []; };
await app.opportunitiesController.requestOpportunities("token", "Current Quarter", {}, 1);
assert.equal(loaded, true);
await app.opportunityCrmServiceRequests.load("token", 1);
assert.equal(app.opportunityCrmServiceRequests.createBadge("TEST").textContent, "SR ?");
assert.equal(app.debugState.requestLog.at(-1).status, "failed");
}

View File

@@ -73,10 +73,12 @@ for (const [roles, allowed] of [[ ["SALES_REP", "SIMPLE_USER"], false ], [ ["SOL
assert.equal(same, app.currentUser.ensure());
finish({ ok: true, user: { roles } });
await first;
assert.deepEqual(calls, allowed ? [type, "tokenrelay", "crm", "opportunities"] : [type, "tokenrelay", "opportunities"]);
assert.deepEqual(calls, [type, "tokenrelay", "opportunities"]);
app.productsContentState.currentProductsRequestId = "products-cycle";
app.productActivities.begin("products-cycle", "synthetic");
const products = { items: [{ RevnId: "123" }] };
app.productsContentState.opptyProductsByNumber.set("OP", products);
app.opportunityWorkloadActions.loadProducts(products);
app.productActivities.complete("products-cycle");
await new Promise(resolve => setImmediate(resolve));
assert.equal(calls.includes("opportunitiesExtension.requestWlActions"), allowed);
if (!allowed) {

View File

@@ -0,0 +1,109 @@
const assert = require("node:assert/strict");
const { test } = require("node:test");
const { loadApplication } = require("./support/application.cjs");
const flush = () => new Promise(resolve => setImmediate(resolve));
function setup() {
let listener;
const app = loadApplication("content", {
document: { getElementById: () => null }, window: { requestAnimationFrame() {} },
chrome: { runtime: { onMessage: { addListener(fn) { listener = fn; } } } }
});
const calls = [];
app.opportunityCrmServiceRequests.reset = () => calls.push("reset SR");
app.opportunityWorkloadActions.reset = () => calls.push("reset Act");
app.opportunityCrmServiceRequests.load = async token => calls.push(`SR ${token}`);
app.opportunityWorkloadActions.loadProducts = products => calls.push(products.items[0]?.RevnId || "empty products");
app.platformContentMessages.sendRuntimeMessage = async () => ({ ok: true });
app.productsContentJobs.ensureProductsBackgroundMessageHandler();
function start(token = "token") {
app.productsContentJobs.requestOpptyProductsInBackground([{ OptyNumber: "A" }], token);
return app.productsContentState.currentProductsRequestId;
}
function progress(requestId, data) {
listener({ type: "opportunitiesExtension.opptyProductsProgress", requestId, ...data });
}
return { app, calls, start, progress };
}
test("follow-up calls wait for completion, deduplicate terminal messages and refresh on every new cycle", async () => {
const h = setup();
const first = h.start();
h.progress(first, { state: "running", optyNumber: "A", items: [{ RevnId: "123" }], fromCache: false });
assert.deepEqual(h.calls, []);
h.progress(first, { state: "complete" });
h.progress(first, { state: "complete" });
await flush();
assert.deepEqual(h.calls, ["reset SR", "123", "SR token"]);
const second = h.start("new-token");
h.progress(first, { state: "complete", optyNumber: "B", items: [{ RevnId: "old" }] });
h.progress(second, { state: "running", optyNumber: "A", items: [{ RevnId: "123" }] });
assert.equal(h.calls.filter(value => value === "123").length, 1);
h.progress(second, { state: "complete" });
await flush();
assert.equal(h.calls.filter(value => value === "123").length, 2);
assert.ok(h.calls.includes("SR new-token"));
assert.equal(h.calls.includes("old"), false);
});
test("partial failures use successful products only; closed, failed and empty cycles have no follow-up", async () => {
const h = setup();
const first = h.start();
h.progress(first, { optyNumber: "A", items: [{ RevnId: "123" }] });
h.progress(first, { optyNumber: "B", items: [{ RevnId: "failed" }], error: "Synthetic failure" });
h.progress(first, { state: "complete-with-errors" });
await flush();
assert.equal(h.calls.includes("failed"), false);
assert.equal(h.calls.filter(value => value === "SR token").length, 1);
const second = h.start();
h.progress(second, { optyNumber: "A", error: "Synthetic failure" });
h.progress(second, { state: "complete-with-errors" });
const third = h.start();
h.progress(third, { state: "error" });
const fourth = h.start();
h.app.productsContentState.currentProductsRequestId = "";
h.progress(fourth, { state: "complete", optyNumber: "A", items: [{ RevnId: "closed" }] });
h.app.productsContentJobs.requestOpptyProductsInBackground([], "empty");
await flush();
assert.equal(h.calls.filter(value => value.startsWith("SR ")).length, 1);
assert.equal(h.calls.includes("closed"), false);
});
test("cached products never trigger or reset activities; mixed cycles use only fresh products", async () => {
const h = setup();
const cached = h.start();
h.progress(cached, { optyNumber: "A", items: [{ RevnId: "cached" }], fromCache: true });
h.progress(cached, { state: "complete" });
await flush();
assert.deepEqual(h.calls, []);
const mixed = h.start();
h.progress(mixed, { optyNumber: "A", items: [{ RevnId: "cached" }], fromCache: true });
h.progress(mixed, { optyNumber: "B", items: [{ RevnId: "fresh" }], fromCache: false });
h.progress(mixed, { state: "complete" });
await flush();
assert.deepEqual(h.calls, ["reset SR", "fresh", "SR token"]);
});
test("reopening an existing modal does not reload products or reset/query activities", async () => {
const modal = { hidden: true, querySelector: () => ({ focus() {} }) };
const app = loadApplication("content", {
window: {}, document: {
getElementById: id => id === "opportunities-extension-modal" ? modal : null,
documentElement: { classList: { add() {} } }, addEventListener() {}
}
});
app.contentStyles.ensureExtensionStyles = () => {};
app.currentUser.ensure = async () => true;
app.authContentSession.requestTokenRelayOnce = async () => "synthetic";
app.opportunitiesTable.renderOpportunitiesTable = () => {};
const calls = [];
app.productsContentJobs.requestOpptyProductsInBackground = () => calls.push("products");
app.opportunityCrmServiceRequests.load = () => calls.push("SR");
app.opportunityCrmServiceRequests.reset = () => calls.push("reset SR");
app.opportunityWorkloadActions.loadProducts = () => calls.push("Actions");
app.opportunityWorkloadActions.reset = () => calls.push("reset Actions");
app.contentModal.openOpportunitiesModal();
await flush();
assert.equal(modal.hidden, false);
assert.deepEqual(calls, []);
});

View File

@@ -158,7 +158,7 @@ test("actions queue caps concurrency and discards results/queued jobs from close
assert.equal(app.opportunityWorkloadActions.createBadge("A"), null);
});
test("product progress triggers Actions for cached products and ignores stale product jobs", async () => {
test("product progress triggers Actions only for fresh products and ignores stale product jobs", async () => {
let listener;
const calls = [];
const app = loadApplication("content", {
@@ -173,14 +173,19 @@ test("product progress triggers Actions for cached products and ignores stale pr
return { ok: true, actions: [], hasNext: false };
};
app.productsContentState.currentProductsRequestId = "current";
app.productActivities.begin("current", "synthetic");
app.fusionCrmServiceRequests.requestCrmServiceRequest = async () => [];
app.productsContentJobs.ensureProductsBackgroundMessageHandler();
const message = { type: app.messageTypes?.opptyProductsProgress || "opportunitiesExtension.opptyProductsProgress",
requestId: "current", optyNumber: "A", items: [{ RevnId: "123" }], fromCache: true };
requestId: "current", optyNumber: "A", items: [{ RevnId: "123" }], fromCache: false };
listener({ ...message, requestId: "old" });
await flush();
assert.equal(calls.length, 0);
listener(message);
await flush();
assert.equal(calls.length, 0);
listener({ ...message, optyNumber: undefined, state: "complete" });
await flush();
assert.equal(calls.length, 1);
assert.equal(calls[0].type, "opportunitiesExtension.requestWlActions");
assert.equal(calls[0].revnId, "123");