Files
opportunities-extension/tests/characterization.test.cjs
2026-10-13 11:14:12 -03:00

347 lines
21 KiB
JavaScript

const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
const { root, plain, response, loadContent, loadBackground } = require("./support/characterization.cjs");
const { composeScript } = require("./support/application.cjs");
const PREFIX = "opportunitiesExtension.";
const FILTER_KEY = "opportunities-extension-filter-preferences";
const TYPE_KEY = "opportunities-extension-opportunity-type-preferences";
const CACHE_KEY = "opportunitiesExtension.opptyProductsCache.v1";
const DAY = 86400000;
const range = { startCloseDate: "2026-06-01T00:00:00.000Z", endCloseDate: "2026-08-31T00:00:00.000Z" };
const drain = () => new Promise(resolve => setImmediate(resolve));
const productRequest = (requestId, optyNumbers = ["SYNTH-1"]) => ({
type: PREFIX + "requestOpptyProducts", requestId, optyNumbers, accessToken: "synthetic-products-token"
});
test("content bootstrap ignores other origins and preserves the existing tile/listener on reinjection", () => {
const source = composeScript("content");
const script = new vm.Script(source);
assert.doesNotThrow(() => script.runInNewContext({
window: { location: { hostname: "unrelated.example", pathname: "/" } },
document: { getElementById() { assert.fail("DOM touched on unrelated origin"); } }
}));
let listeners = 0;
const context = vm.createContext({
window: { location: { hostname: "eeho.fa.us2.oraclecloud.com", pathname: "/fscmUI/faces/FuseWelcome" } },
document: { getElementById: () => ({}) },
chrome: { runtime: { onMessage: { addListener() { listeners++; } } } },
MutationObserver: class { constructor() { assert.fail("Observer unnecessary when tile already exists"); } }
});
script.runInContext(context);
script.runInContext(context);
assert.equal(listeners, 1);
});
test("distribution contracts: manifests, execution boundaries and deterministic composition", () => {
const base = {
manifest_version: 3, name: "Opportunities Extension",
description: "Adiciona um atalho de Opportunities Extension nas paginas Oracle Fusion permitidas.",
version: "1.0.3", permissions: ["cookies", "storage", "webRequest", "declarativeNetRequestWithHostAccess"],
host_permissions: ["https://eeho.fa.us2.oraclecloud.com/*", "https://spa.oracle.com/*", "https://comcipapic-oalprod.integration.ocp.oraclecloud.com/*"],
content_scripts: [{
matches: ["https://eeho.fa.us2.oraclecloud.com/hcmUI/faces/FuseWelcome*", "https://eeho.fa.us2.oraclecloud.com/fscmUI/faces/FuseWelcome*"],
js: ["content.js"], all_frames: true, run_at: "document_idle"
}]
};
for (const target of ["chromium", "firefox"]) {
const expected = target === "chromium"
? { ...base, background: { service_worker: "background.js" } }
: { ...base, background: { scripts: ["background.js"] }, browser_specific_settings: { gecko: { id: "opportunities-extension@local.dev" } } };
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(root, "dist", target, "manifest.json"))), expected);
for (const file of ["content.js", "background.js"]) {
const output = fs.readFileSync(path.join(root, "dist", target, file), "utf8");
assert.equal(output, composeScript(file.replace(".js", "")));
assert.doesNotThrow(() => new vm.Script(output, { filename: `${target}/${file}` }));
}
}
});
test("fiscal dates preserve quarter/year boundaries, leap year and unsupported renewals", () => {
const h = loadContent();
const cases = [
["Current Quarter", [2026, 5, 1], "2026-06-01", "2026-08-31"],
["Current Quarter", [2026, 7, 31], "2026-06-01", "2026-08-31"],
["Current Quarter", [2026, 8, 1], "2026-09-01", "2026-11-30"],
["Current Quarter", [2028, 1, 29], "2027-12-01", "2028-02-29"],
["Next Quarter", [2026, 10, 30], "2026-12-01", "2027-02-28"],
["Previous Quarter", [2026, 7, 24], "2026-03-01", "2026-05-31"],
["Current Fiscal Year", [2026, 4, 31], "2025-06-01", "2026-05-31"],
["Current Fiscal Year", [2026, 5, 1], "2026-06-01", "2027-05-31"],
["Current Fiscal Year", [2027, 0, 1], "2026-06-01", "2027-05-31"],
["4 Rolling Quarters (CQ + 3)", [2026, 8, 16], "2026-09-01", "2027-08-31"]
];
for (const [period, date, start, end] of cases) {
assert.deepEqual(plain(h.getFiscalPeriodRange(period, new Date(...date))), {
startCloseDate: start + "T00:00:00.000Z", endCloseDate: end + "T00:00:00.000Z"
});
}
assert.equal(h.getFiscalPeriodRange("RENEWALS (Current + Past Due)", new Date()), null);
});
test("opportunity query preserves fields, record-set OR, date AND, and pagination payload", () => {
const h = loadContent();
const types = ["ORA_MYSALESTEAMOPTIES", "ORA_MYASSGTERROPTIES", "ORA_CREDITRECEIVER_ISME"];
h.setTypes(types);
const fields = ["Name", "OptyNumber", "PrimaryRevenue.WinProb", "CustomerAccount", "PrimaryRevenue.RevnAmount", "EffectiveDate", "StatusCode", "DealRisk_c", "Owner", "ForecastGroup_c", "LastUpdateDate", "PrimaryRevenue.RevnAmountCurcyCode", "OptyId"];
assert.deepEqual(plain(h.createOpportunitiesQueryPayload(range, 15)), {
aggregationResults: false, applications: ["ORACLE-ISS-APP"], onlyData: true, entity: "Opportunity",
q: { op: "$and", criteria: [
{ op: "$or", criteria: types.map(value => ({ op: "$eq", attribute: "RecordSet", value })) },
{ op: "$wi", attribute: "EffectiveDate", value1: range.startCloseDate, value2: range.endCloseDate, dynamicDate: false }
] }, keywords: null, keywordsFields: fields,
fields: [...fields, "CustomerAccount.PartyUniqueName", "CustomerAccount.PartyId", "CustomerAccount.PartyNumber", "CustomerAccount.Region_c", "CustomerAccount.PrimaryAddress.CountryCode"],
sort: [], language: "en", skipInValidFields: true, skipHiddenFromUIFields: true,
copiedFrom: "queries/d5dbcd01-9ff3-40be-9b5b-64744cbf7162", limit: 15, offset: 15
});
});
test("opportunity pagination uses response offsets/count, ignores stale versions and detects stalled pages", async () => {
const calls = [];
const h = loadContent({ fetch: async (url, options) => {
calls.push({ url, options });
return response(calls.length === 1
? { items: [{ OptyNumber: "S1" }], offset: 0, count: 1, hasMore: true }
: { items: [{ OptyNumber: "S2" }], offset: 1, count: 1, hasMore: false });
} });
const items = await h.requestOpportunitiesPage("synthetic-token", "Current Quarter", range, 0, [], 0, 1);
assert.deepEqual(plain(items), [{ OptyNumber: "S1" }, { OptyNumber: "S2" }]);
assert.deepEqual(calls.map(call => JSON.parse(call.options.body).offset), [0, 1]);
assert.equal(calls[0].url, "https://eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/11.13.18.05/custom-actions/queries");
assert.deepEqual(plain(calls[0].options.headers), {
Accept: "application/json", Authorization: "Bearer synthetic-token", "Content-Type": "application/json",
Origin: "https://eeho.fa.us2.oraclecloud.com", Preference: "transient"
});
assert.equal(calls[0].options.method, "POST");
assert.equal(calls[0].options.credentials, "include");
h.setVersion(1);
assert.equal(await h.requestOpportunitiesPage("unused", "Current Quarter", range, 0, [], 0, 1), null);
assert.equal(calls.length, 2);
const stalled = loadContent({ fetch: async () => response({ items: [], count: 0, hasMore: true }) });
await assert.rejects(stalled.requestOpportunitiesPage("synthetic", "Current Quarter", range, 0, [], 0, 1), /without additional results/);
});
const opportunity = (number, status, amount) => ({
Name: `Synthetic cloud ${number}`, OptyNumber: number, StatusCode: status, ForecastGroup_c: "PIPELINE",
CustomerAccount: { PartyId: "customer-1", PartyUniqueName: "Synthetic Customer", PartyNumber: "party-1" },
Owner: { PartyId: "owner-1", PersonFirstName: "Test", PersonLastName: "Owner", PartyName: "Long Test Owner Name" },
PrimaryRevenue: { RevnAmount: amount, RevnAmountCurcyCode: "USD", WinProb: 60 },
EffectiveDate: "2026-08-28", LastUpdateDate: "2026-08-28T13:40:00"
});
test("filter storage round-trip, WON_PENDING membership, search, sorting and reference resets", () => {
const preferences = { period: "Next Quarter", stages: ["PIPELINE"], statuses: ["WON"],
customerKeys: ["party:customer-1"], ownerKeys: ["party:owner-1"],
customerSearch: "Synthetic", ownerSearch: "Test", tableSearch: "cloud" };
const storage = new Map([[FILTER_KEY, JSON.stringify(preferences)]]);
const h = loadContent({ storage });
const select = { value: "Current Quarter" };
h.restoreFilterPreferences(select);
assert.equal(select.value, "Next Quarter");
const items = [opportunity("S1", "WON", "100"), opportunity("S2", "WON_PENDING", "20"), opportunity("S3", "OPEN", "5")];
h.setTable(items, "revenue");
assert.deepEqual(plain(h.getSortedOpportunities()).map(item => item.OptyNumber), ["S2", "S1"]);
assert.equal(h.getSortedOpportunities()[0].StatusCode, "WON_PENDING");
assert.equal(h.getOwnerName(items[0]), "Test Owner");
assert.equal(h.matchesTableSearch(items[0], "28/08/2026"), true);
assert.equal(h.matchesTableSearch(items[0], "test owner"), true);
h.saveFilterPreferences(select.value);
assert.deepEqual(JSON.parse(storage.get(FILTER_KEY)), preferences);
const reopened = loadContent({ storage });
reopened.restoreFilterPreferences({ value: "Current Quarter" });
assert.equal(reopened.filters().tableSearch, "cloud");
reopened.resetCustomerAndOwnerFiltersForPeriod();
const nextItems = [...items, { ...opportunity("S4", "WON", "30"), CustomerAccount: { PartyId: "customer-2", PartyUniqueName: "Another Customer" }, Owner: { PartyId: "owner-2", PartyName: "Another Owner" } }];
reopened.setTable(nextItems);
assert.deepEqual(plain(reopened.filters().customerKeys).sort(), ["party:customer-1", "party:customer-2"]);
assert.deepEqual(plain(reopened.filters().ownerKeys).sort(), ["party:owner-1", "party:owner-2"]);
assert.equal(reopened.filters().tableSearch, "cloud");
assert.equal(reopened.filters().customerSearch, "");
assert.equal(reopened.filters().ownerSearch, "");
});
test("malformed preferences fall back, while theme/type/cache keys retain their schema", () => {
const storage = new Map([[FILTER_KEY, "not JSON"], ["opportunities-extension-theme", "dark"],
[TYPE_KEY, JSON.stringify({ opportunityTypeValues: ["ORA_MYOPTIES", "UNKNOWN"], opptyProductsCacheHours: 24 })]]);
const h = loadContent({ storage });
assert.doesNotThrow(() => h.restoreFilterPreferences({ value: "Current Quarter" }));
assert.equal(h.getSavedTheme(), "dark");
assert.deepEqual(plain(h.getSavedOpportunityTypeValues()), ["ORA_MYOPTIES"]);
assert.equal(h.getSavedOpptyProductsCacheHours(), 24);
assert.equal(h.filters().tableSearch, "");
});
test("tokenrelay missing-cookie recovery and HTTP failure do not change the request contract", async () => {
let reads = 0;
const calls = [];
const h = loadContent({ xsrf: async () => ({ token: ++reads === 1 ? "" : "synthetic-xsrf", cookieName: "XSRF-TOKEN-SYNTHETIC" }),
fetch: async (url, options) => { calls.push({ url, options }); return response({ access_token: "synthetic-access" }); } });
assert.equal(await h.requestTokenRelay(), "synthetic-access");
assert.equal(h.refreshes.length, 1);
assert.equal(calls.length, 1);
assert.equal(calls[0].options.headers["x-xsrf-token"], "synthetic-xsrf");
assert.equal(h.requestLog.at(-1).requestHeaders["x-xsrf-token"], "synthetic-xsrf");
assert.equal(JSON.parse(h.requestLog.at(-1).responseBody).access_token, "synthetic-access");
assert.equal(calls[0].options.method, "GET");
for (const status of [400, 401, 403]) {
let attempts = 0;
const rejected = loadContent({ xsrf: async () => ({ token: "synthetic-xsrf" }),
fetch: async () => { attempts++; return response({}, status); } });
await assert.rejects(rejected.requestTokenRelay(), /failed with status/);
assert.equal(attempts, 2);
assert.equal(rejected.refreshes.length, 1);
}
});
for (const family of ["chrome", "firefox"]) {
test(`${family}: products paginate, route progress to sender frame and reuse persisted cache after restart`, async () => {
const calls = [];
const storage = {};
const h = loadBackground({ family, storage, fetch: async (url, options) => {
calls.push({ url, options });
const offset = Number(new URL(url).searchParams.get("offset"));
return response({ items: [{ RevnNumber: `SYNTH-${offset}` }], count: 1, offset, hasMore: offset === 0 });
} });
const result = await h.dispatch(productRequest("job-1", ["SYNTH-1", "SYNTH-1"]));
assert.deepEqual(result, { ok: true, requestId: "job-1", completed: 1, total: 1, failed: 0, cacheHits: 0 });
assert.equal(calls.length, 2);
assert.equal(new URL(calls[0].url).pathname, "/crmRestApi/rest/rv:be91c002-2e5d-4ed2-a37e-e0c837bf141f/en/11.13.18.05:9/opportunities/SYNTH-1/child/ChildRevenue");
assert.equal(new URL(calls[0].url).searchParams.get("q"), "(SplitTypeCode!='DETAILCHILDSPLIT') AND (RecurTypeCode!='CHILDRECUR')");
assert.equal(new URL(calls[0].url).searchParams.get("limit"), "15");
assert.equal(new URL(calls[1].url).searchParams.get("offset"), "1");
assert.equal(calls[0].options.headers.Authorization, "Bearer synthetic-products-token");
assert.ok(h.progress.every(event => event.tabId === 7 && event.options.frameId === 2));
assert.equal(h.progress[0].message.type, PREFIX + "opptyProductsProgress");
assert.equal(h.progress[0].message.items.length, 2);
assert.equal(h.progress.at(-1).message.state, "complete");
assert.deepEqual(Object.keys(storage[CACHE_KEY]["SYNTH-1"]).sort(), ["fetchedAt", "items"]);
const restarted = loadBackground({ family, storage });
assert.equal((await restarted.dispatch(productRequest("job-2"))).cacheHits, 1);
h.advance(DAY);
await h.dispatch(productRequest("job-3"));
assert.equal(calls.length, 4, "default TTL expires at 24 hours");
});
test(`${family}: product queue has four workers, shares in-flight results and reports partial failures`, async () => {
const calls = [];
const h = loadBackground({ family, fetch: (url) => new Promise(resolve => calls.push({ url, resolve })) });
const first = h.dispatch(productRequest("concurrent-1", ["S1", "S2", "S3", "S4", "S5"]));
await drain();
assert.equal(calls.length, 4);
const duplicate = h.dispatch(productRequest("concurrent-2", ["S1"]));
await drain();
assert.equal(calls.length, 4);
calls[0].resolve(response({ items: [{ RevnNumber: "R1" }], hasMore: false }));
await drain();
assert.equal(calls.length, 5);
calls[1].resolve(response({}, 500));
for (const call of calls.slice(2)) call.resolve(response({ items: [], hasMore: false }));
assert.equal((await duplicate).ok, true);
const result = await first;
assert.equal(result.failed, 1);
assert.equal(result.completed, 5);
assert.equal(h.progress.filter(event => event.message.requestId === "concurrent-1").at(-1).message.state, "complete-with-errors");
});
test(`${family}: custom product TTL and zero-count pagination failures`, async () => {
let calls = 0;
const h = loadBackground({ family, fetch: async () => { calls++; return response({ items: [], hasMore: false }); } });
const message = { ...productRequest("custom-ttl"), cacheMaxAgeMs: 1000 };
await h.dispatch(message);
h.advance(999);
assert.equal((await h.dispatch(message)).cacheHits, 1);
assert.equal(calls, 1);
h.advance(1);
assert.equal((await h.dispatch(message)).cacheHits, 0);
assert.equal(calls, 2);
const stalled = loadBackground({ family, fetch: async () => response({ items: [], count: 0, hasMore: true }) });
assert.equal((await stalled.dispatch(productRequest("stalled"))).failed, 1);
assert.match(stalled.progress[0].message.error, /without additional results/);
});
test(`${family}: dynamic XSRF cookie lookup, missing-cookie result and temporary-tab cleanup`, async () => {
const cookie = { name: "XSRF-TOKEN-SYNTHETIC", value: "synthetic-xsrf", domain: "eeho.fa.us2.oraclecloud.com", path: "/", storeId: "test-store" };
const h = loadBackground({ family, cookies: () => [cookie] });
const result = await h.dispatch({ type: PREFIX + "getXsrfToken" });
assert.equal(result.ok, true);
assert.equal(result.cookieName, cookie.name);
assert.equal(result.token, cookie.value);
assert.ok(h.cookieQueries.some(query => query.storeId === "test-store"));
const missing = loadBackground({ family });
assert.equal((await missing.dispatch({ type: PREFIX + "getXsrfToken" })).token, "");
const refresh = await missing.dispatch({ type: PREFIX + "refreshXsrfCookie" });
assert.equal(refresh.refreshed, false);
assert.equal(refresh.durationMs, 15000);
assert.deepEqual(missing.removedTabs, [91]);
assert.equal(missing.tabs[0].active, false);
assert.equal(missing.tabs[0].windowId, 3);
assert.equal(missing.tabs[0].cookieStoreId, family === "firefox" ? "test-store" : undefined);
let refreshHarness;
refreshHarness = loadBackground({ family, cookies: () => refreshHarness.tabs.length ? [cookie] : [] });
assert.equal((await refreshHarness.dispatch({ type: PREFIX + "refreshXsrfCookie" })).refreshed, true);
assert.deepEqual(refreshHarness.removedTabs, [91]);
});
test(`${family}: SPA token selection, account POST contracts and debug envelope`, async () => {
const calls = [];
const h = loadBackground({ family, fetch: async (url, options) => {
calls.push({ url, options });
if (calls.length === 1) return response([
{ tokenService: "UNRELATED", uiaasHeader: { value: "synthetic-unused" } },
{ tokenService: "ACCP-TS", uiaasHeader: { value: "synthetic-account" } },
{ tokenService: "OCICONS-TS", uiaasHeader: { value: "synthetic-consumption" } }
]);
if (calls.length === 2) return response({ items: [{ id: "synthetic/id" }] });
return response({ items: [{ planType: "Synthetic", subPlanNum: "plan-1" }] });
} });
const tokens = await h.dispatch({ type: PREFIX + "requestConsumerTokenService" });
const plans = await h.dispatch({ type: PREFIX + "requestAccountPlans", partyNumber: "synthetic-party", authorizations: tokens.authorizations });
assert.equal(plans.ok, true);
assert.equal(plans.accountPlanId, "synthetic/id");
assert.deepEqual(plans.plans, [{ planType: "Synthetic", subPlanNum: "plan-1" }]);
assert.equal(calls[1].url, "https://spa.oracle.com/oalcrm/web/api/provider-proxy/g2m-account/service/accountsummary");
assert.deepEqual(JSON.parse(calls[1].options.body), [{ key: "regId", value: "synthetic-party" }]);
assert.equal(calls[1].options.headers["account-ti-authorization"], "synthetic-account");
assert.equal(calls[2].options.headers["g2m-authorization"], "synthetic-consumption");
assert.equal(calls[2].url, "https://spa.oracle.com/oalcrm/web/api/provider-proxy/g2m-consumption/service/v1/account/accountPlans/synthetic%2Fid");
assert.deepEqual(JSON.parse(calls[2].options.body), {
hierarchy: "single", products: "all", language: "english",
planStatus: ["active", "awaiting_provisioning", "awaiting_provisioning_error", "draft", "expired", "final_billing", "onhold", "provisioned", "signed", "superseded", "superseded_pending_final_billing", "suspended", "terminated", "terminated_pending_final_billing"]
});
assert.ok(calls.slice(1).every(call => call.options.method === "POST" && call.options.credentials === "include"));
assert.deepEqual(plans.debugRequests.map(entry => entry.name), ["requestAccountSummary", "requestAccountPlans"]);
assert.ok(plans.debugRequests.every(entry => entry.httpStatus === 200 && entry.status === "success" && entry.durationMs >= 0));
});
test(`${family}: unknown/invalid messages fail without network; storage failure does not discard fetched products`, async () => {
const h = loadBackground({ family });
assert.equal(h.listener({ type: "unknown" }, {}, () => assert.fail("Unexpected reply")), false);
assert.equal((await h.dispatch({ type: PREFIX + "requestOpptyProducts" })).ok, false);
assert.equal((await h.dispatch({ type: PREFIX + "requestAccountPlans" })).ok, false);
assert.equal((await h.dispatch({ type: PREFIX + "requestConsumptionTrend", subPlanNum: "S1", period: "unsupported" })).ok, false);
const noStorage = loadBackground({ family, storageError: true, fetch: async () => response({ items: [{ RevnNumber: "R1" }], hasMore: false }) });
assert.equal((await noStorage.dispatch(productRequest("no-storage"))).ok, true);
assert.equal(noStorage.progress[0].message.items.length, 1);
});
test(`${family}: consumption network/HTTP/JSON failures retain the response and debug contracts`, async () => {
for (const failure of ["network", "http", "json"]) {
const h = loadBackground({ family, fetch: async () => {
if (failure === "network") throw new Error("Synthetic network error");
return failure === "http" ? response({}, 401) : new Response("<html>Synthetic fallback</html>");
} });
const result = await h.dispatch({ type: PREFIX + "requestConsumptionTrend", subPlanNum: "synthetic-plan", period: "weekly",
authorization: { name: "g2m-authorization", value: "synthetic-token" } });
assert.equal(result.ok, false);
assert.equal(result.debugRequests.length, 1);
assert.equal(result.debugRequests[0].name, "requestConsumptionServiceWeekly");
assert.equal(result.debugRequests[0].status, "failed");
assert.equal(result.debugRequests[0].httpStatus, failure === "network" ? null : failure === "http" ? 401 : 200);
assert.match(result.error, failure === "network" ? /Synthetic network error/ : failure === "http" ? /status 401/ : /invalid JSON/);
}
});
}