347 lines
21 KiB
JavaScript
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/);
|
|
}
|
|
});
|
|
}
|