Implemented the feature to identify and update estimates based on the ACR value, consumption start date, and ramp-up

This commit is contained in:
2026-10-20 15:55:58 -03:00
parent 71d550dfb1
commit 60daabfe91
31 changed files with 2800 additions and 43 deletions

237
tests/workloads.test.cjs Normal file
View File

@@ -0,0 +1,237 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const { loadBackground, plain, response } = require("./support/characterization.cjs");
const { loadApplication } = require("./support/application.cjs");
const TYPE = "opportunitiesExtension.requestWorkloads";
const CACHE = "opportunitiesExtension.workloadsCache.v1";
const flush = () => new Promise(resolve => setImmediate(resolve));
const request = (extra = {}) => ({ type: TYPE, customerId: "123", principal: "user@example.invalid",
authorization: { value: "synthetic-wwb-token" }, cacheMaxAgeMs: 3600000, ...extra });
const page = (ids, extra = {}) => ({ workloads: ids.map(id => ({ id, name: `Workload ${id}`, forecast: [] })), hasNext: false, ...extra });
for (const family of ["chrome", "firefox"]) {
test(`${family}: workloads paginate with response offset/limit and reuse persistent per-account cache`, async () => {
const calls = [], storage = {};
const h = loadBackground({ family, storage, fetch: async (url, options) => {
calls.push({ url: new URL(url), options });
const offset = Number(new URL(url).searchParams.get("offset"));
return response(offset === 0 ? page([11, 12], { hasNext: true, offset: 0, limit: 2 })
: offset === 2 ? page([13], { hasNext: true, offset: 2, limit: 1 }) : page([14]));
} });
const result = await h.dispatch(request());
assert.equal(result.ok, true);
assert.deepEqual(result.workloads.map(item => item.id), [11, 12, 13, 14]);
assert.deepEqual(calls.map(call => call.url.searchParams.get("offset")), ["0", "2", "3"]);
for (const { url, options } of calls) {
assert.equal(url.origin, "https://spa.oracle.com");
assert.ok(url.pathname.endsWith("/customers/123/workloads"));
assert.equal(url.searchParams.get("workloadStatus"), "Pipeline,Implementation,Live");
assert.equal(url.searchParams.get("opportunityForecastType"), "All");
assert.equal(url.searchParams.get("includeForEstimates"), "ALL");
assert.equal(url.searchParams.get("financialYear"), "ROLLING_4_QTRS");
assert.equal(options.method, "GET");
assert.equal(options.credentials, "include");
assert.equal(options.headers["wwb-provider-authorization"], "synthetic-wwb-token");
}
assert.equal(result.debugRequests.length, 3);
assert.ok(result.debugRequests.every(entry => entry.name === "requestWorkloads"));
assert.equal(result.fromCache, false);
assert.equal((await h.dispatch(request())).fromCache, true);
assert.equal(calls.length, 3);
assert.doesNotMatch(JSON.stringify(storage), /synthetic-wwb-token|debugRequests|requestHeaders/);
const restarted = loadBackground({ family, storage });
const cached = await restarted.dispatch(request());
assert.equal(cached.fromCache, true);
assert.deepEqual(cached.workloads, result.workloads);
assert.equal(cached.cachedAt, result.cachedAt);
h.advance(3600001);
assert.equal((await h.dispatch(request())).fromCache, false);
assert.equal(calls.length, 6);
assert.equal((await h.dispatch(request({ customerId: "456" }))).fromCache, false);
assert.equal((await h.dispatch(request({ principal: "other@example.invalid" }))).fromCache, false);
assert.equal(calls.length, 12);
});
test(`${family}: workload cache coalesces flights and Refresh all prevents late cache restoration`, async () => {
const pending = [], storage = {};
const h = loadBackground({ family, storage, fetch: () => new Promise(resolve => pending.push(resolve)) });
const old = h.dispatch(request()), shared = h.dispatch(request());
await flush();
assert.equal(pending.length, 1);
assert.equal((await h.dispatch({ type: "opportunitiesExtension.invalidateProductsCache" })).ok, true);
const fresh = h.dispatch(request());
await flush();
assert.equal(pending.length, 2);
pending[1](response(page([22])));
await fresh;
pending[0](response(page([11])));
assert.deepEqual(await old, await shared);
const cached = await h.dispatch(request());
assert.equal(cached.fromCache, true);
assert.deepEqual(cached.workloads.map(item => item.id), [22]);
assert.equal(pending.length, 2);
});
test(`${family}: successful empty workloads are cached; storage failures do not lose results`, async () => {
let calls = 0;
const h = loadBackground({ family, fetch: async () => { calls++; return response(page([])); } });
assert.deepEqual((await h.dispatch(request())).workloads, []);
assert.equal((await h.dispatch(request())).fromCache, true);
assert.equal(calls, 1);
const unavailable = loadBackground({ family, storageError: true, fetch: async () => response(page([10])) });
assert.equal((await unavailable.dispatch(request())).ok, true);
});
}
test("workload failures and stalled pagination are not cached as complete results", async () => {
for (const bad of [response({}, 403), response({ workloads: [] }), response(page([], { hasNext: true })),
response(page([11], { hasNext: true, offset: 0, limit: 0 }))]) {
let calls = 0;
const h = loadBackground({ fetch: async () => ++calls === 1 ? bad : response(page([12])) });
const failed = await h.dispatch(request());
assert.equal(failed.ok, false);
assert.equal(failed.debugRequests.length, 1);
assert.equal((await h.dispatch(request())).ok, true);
assert.equal(calls, 2);
}
let calls = 0;
const repeated = loadBackground({ fetch: async () => { calls++; return response(page([11], { hasNext: true })); } });
assert.match((await repeated.dispatch(request())).error, /did not advance/);
assert.equal(calls, 2);
let attempt = 0;
const partial = loadBackground({ fetch: async () => ++attempt === 1
? response(page([11], { hasNext: true })) : response({}, 500) });
const failure = await partial.dispatch(request());
assert.equal(failure.ok, false);
assert.equal(failure.debugRequests.length, 2);
assert.equal(Object.keys(partial.storage[CACHE] || {}).length, 0);
});
test("workload validation rejects unsafe IDs or missing WWB tokens before requesting", async () => {
const h = loadBackground();
for (const extra of [{ customerId: "../123" }, { customerId: "" }, { authorization: {} }]) {
const result = await h.dispatch(request(extra));
assert.equal(result.ok, false);
assert.deepEqual(result.debugRequests, []);
}
});
test("workloads without a known principal are never persisted or reused across users", async () => {
const storage = {};
let calls = 0;
const h = loadBackground({ storage, fetch: async () => { calls++; return response(page([11])); } });
await h.dispatch(request({ principal: "" }));
await h.dispatch(request({ principal: "" }));
assert.equal(calls, 2);
assert.equal(storage[CACHE], undefined);
});
function content() {
let listener;
const app = loadApplication("content", {
document: { getElementById: () => null }, window: { requestAnimationFrame() {} },
chrome: { runtime: { onMessage: { addListener(fn) { listener = fn; } } } }
});
const calls = [], logs = [];
let tokens = 0;
app.currentUser.getUserId = () => "user@example.invalid";
app.authContentSession.requestConsumerTokenServiceOnce = async () => { tokens++; return { workloadActions: { value: "wwb" } }; };
app.debugContentLog.appendBackgroundRequestLog = entries => logs.push(...(entries || []));
app.platformContentMessages.sendRuntimeMessage = message => {
if (message.type !== TYPE) return Promise.resolve({ ok: true });
return new Promise(resolve => calls.push({ message: plain(message), resolve }));
};
app.productsContentJobs.ensureProductsBackgroundMessageHandler();
const start = opportunities => {
app.productsContentJobs.requestOpptyProductsInBackground(opportunities, "fusion-token");
return app.productsContentState.currentProductsRequestId;
};
const progress = (id, number, items, fromCache = false) => listener({ type: "opportunitiesExtension.opptyProductsProgress",
requestId: id, optyNumber: number, items, fromCache, state: "running" });
return { app, calls, logs, start, progress, tokens: () => tokens };
}
const opportunity = (OptyNumber, PartyId) => ({ OptyNumber, CustomerAccount: { PartyId } });
for (const productsFirst of [true, false]) {
test(`accounts deduplicate independently of filters; products-first=${productsFirst} joins by account and RevnId`, async () => {
const h = content();
h.app.preferencesState.opptyProductsCacheHours = 12;
h.app.opportunitiesFilterState.tableSearch = "hides all opportunities";
const id = h.start([opportunity("A", 123), opportunity("B", "123"), opportunity("C", 456),
{ OptyNumber: "NO_ACCOUNT", PartyId: 999, Owner: { PartyId: 888 } }]);
await flush();
assert.deepEqual(h.calls.map(call => call.message.customerId), ["123", "456"]);
assert.deepEqual(plain(h.app.productsContentState.workloadCustomerIds), ["123", "456"]);
assert.equal(h.tokens(), 1);
assert.equal(h.calls[0].message.authorization.value, "wwb");
assert.equal(h.calls[0].message.cacheMaxAgeMs, 12 * 3600000);
assert.equal(h.calls[0].message.principal, "user@example.invalid");
const original = { RevnId: 11, Name: "Original product" };
function products() {
h.progress(id, "A", [original, { RevnId: "999" }]);
h.progress(id, "B", [{ RevnId: "12" }], true);
h.progress(id, "C", [{ RevnId: 11 }, { RevnId: 22 }]);
}
if (productsFirst) products();
h.calls[0].resolve({ ok: true, ...page([11, 12]), cachedAt: 100, fromCache: true, debugRequests: [{ name: "requestWorkloads" }] });
h.calls[1].resolve({ ok: true, ...page([22]), cachedAt: 200 });
await flush();
if (!productsFirst) products();
const results = h.app.productsContentState.opptyProductsByNumber;
assert.equal(results.get("A").items[0].workload.id, 11);
assert.equal(results.get("A").items[0].Name, "Original product");
assert.equal(original.workload, undefined, "do not mutate raw product responses or their cache");
assert.equal(results.get("A").items[1].workload, null);
assert.equal(results.get("B").items[0].workload.id, 12);
assert.equal(results.get("B").fromCache, true);
assert.equal(results.get("C").items[0].workload, null, "never join a workload from another account");
assert.equal(results.get("C").items[1].workload.id, 22);
assert.equal(h.app.productsContentState.workloadsByCustomer.get("123").fromCache, true);
assert.equal(h.logs.length, 1);
});
}
test("workload queue limits concurrency to four and ignores a superseded Reference", async () => {
const h = content();
h.start(Array.from({ length: 9 }, (_, i) => opportunity(`A${i}`, i + 1)));
await flush();
assert.equal(h.calls.length, 4);
h.app.opportunitiesQueryState.periodRequestVersion++;
const id = h.start([opportunity("NEW", 100)]);
await flush();
assert.equal(h.calls.length, 5);
for (const call of h.calls.slice(0, 4)) call.resolve({ ok: true, ...page([11]) });
await flush();
assert.equal(h.calls.length, 5, "do not continue accounts from the old Reference");
assert.deepEqual([...h.app.productsContentState.workloadsByCustomer.keys()], ["100"]);
h.calls[4].resolve({ ok: true, ...page([22]) });
await flush();
h.progress(id, "NEW", [{ RevnId: 22 }]);
assert.equal(h.app.productsContentState.opptyProductsByNumber.get("NEW").items[0].workload.id, 22);
});
test("one failed account does not block other accounts or discard products", async () => {
const h = content();
const id = h.start([opportunity("A", 123), opportunity("B", 456)]);
await flush();
h.progress(id, "A", [{ RevnId: 11 }]);
h.calls[0].resolve({ ok: false, error: "Synthetic failure", debugRequests: [{ name: "requestWorkloads", httpStatus: 403 }] });
h.calls[1].resolve({ ok: true, ...page([22]) });
await flush();
assert.equal(h.app.productsContentState.workloadsByCustomer.get("123").status, "error");
assert.equal(h.app.productsContentState.workloadsByCustomer.get("456").status, "ready");
assert.equal(h.app.productsContentState.opptyProductsByNumber.get("A").items.length, 1);
assert.equal(h.logs[0].httpStatus, 403);
});
test("no accounts skips authentication, and failed authentication settles workload states", async () => {
const h = content();
h.start([]);
await flush();
assert.equal(h.tokens(), 0);
h.app.authContentSession.requestConsumerTokenServiceOnce = async () => { throw new Error("Session unavailable"); };
h.start([opportunity("A", 123)]);
await flush();
assert.equal(h.calls.length, 0);
assert.equal(h.app.productsContentState.workloadsByCustomer.get("123").error, "Session unavailable");
});