Files
opportunities-extension/tests/support/characterization.cjs

139 lines
6.7 KiB
JavaScript

const assert = require("node:assert/strict");
const path = require("node:path");
const vm = require("node:vm");
const { loadApplication, composeScript } = require("./application.cjs");
const root = path.resolve(__dirname, "../..");
const plain = (value) => JSON.parse(JSON.stringify(value));
const response = (body, status = 200) => new Response(JSON.stringify(body), {
status, headers: { "Content-Type": "application/json" }
});
const unexpectedFetch = async () => { throw new Error("Unexpected network request in isolated test"); };
function clock() {
let now = Date.UTC(2026, 8, 16, 12);
class ClockDate extends Date {
constructor(...args) { super(...(args.length ? args : [now])); }
static now() { return now; }
}
return { Date: ClockDate, advance: (ms) => { now += ms; } };
}
function loadContent({ fetch = unexpectedFetch, storage = new Map(), xsrf = null } = {}) {
const refreshes = [];
const context = {
fetch, testXsrf: xsrf, refreshes,
document: { querySelector: () => ({ value: "Current Quarter" }) },
localStorage: {
getItem: key => storage.get(key) ?? null,
setItem: (key, value) => storage.set(key, value)
},
Date, Intl, URL
};
const app = loadApplication("content", context);
app.debugContentView.renderDebugPanel = () => {};
if (xsrf) app.authXsrfContent.getXsrfToken = xsrf;
app.platformContentMessages.refreshXsrfCookie = async () => { refreshes.push(true); return { refreshed: true }; };
return {
...app.opportunitiesFiscalPeriods, ...app.fusionOpportunities, ...app.authTokenRelay,
...app.authSalesCommon, ...app.preferencesStorage, ...app.opportunitiesFilters,
...app.opportunitiesFormatters, ...app.contentTheme, ...app.debugState,
setVersion: value => { app.opportunitiesQueryState.periodRequestVersion = value; },
setTypes: values => { app.preferencesState.selectedOpportunityTypeValues = new Set(values); },
setTable: (items, sortKey = "", sortDirection = "ascending") => {
app.opportunitiesTableState.opportunitiesTableState = { items, sortKey, sortDirection };
app.opportunitiesFilters.resetCustomerFilter(items);
app.opportunitiesFilters.resetOwnerFilter(items);
},
filters: () => {
const state = app.opportunitiesFilterState;
return {
stages: Array.from(state.selectedStages), statuses: Array.from(state.selectedStatuses),
customerKeys: Array.from(state.selectedCustomerKeys), ownerKeys: Array.from(state.selectedOwnerKeys),
customerSearch: state.customerSearch, ownerSearch: state.ownerSearch, tableSearch: state.tableSearch
};
},
storage, refreshes
};
}
function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage = {}, cookies = () => [], storageError = false } = {}) {
const time = clock();
const listeners = [];
const progress = [];
const requestDebug = [];
const tabs = [];
const removedTabs = [];
const cookieQueries = [];
const api = { runtime: { onMessage: { addListener: fn => listeners.push(fn) } } };
const readCookies = details => { cookieQueries.push(plain(details)); return cookies(details); };
const getStored = key => ({ [key]: storage[key] === undefined ? undefined : plain(storage[key]) });
const store = value => Object.assign(storage, plain(value));
const openTab = details => { tabs.push(plain(details)); return { id: 91 }; };
const send = (tabId, message, options) => {
const events = message.type === "opportunitiesExtension.opptyProductsRequestDebug" ? requestDebug : progress;
events.push({ tabId, message: plain(message), options: plain(options || {}) });
};
if (family === "firefox") {
api.runtime.getBrowserInfo = async () => ({ name: "Firefox" });
api.storage = { local: {
get: async key => { if (storageError) throw new Error("Storage unavailable"); return getStored(key); },
set: async value => { if (storageError) throw new Error("Storage unavailable"); store(value); }
} };
api.tabs = { create: async details => openTab(details), remove: async id => removedTabs.push(id),
sendMessage: async (tabId, message, options) => send(tabId, message, options) };
api.cookies = {
get: async details => readCookies(details).find(cookie => cookie.name === details.name) || null,
getAll: async details => readCookies(details),
getAllCookieStores: async () => [{ id: "test-store" }]
};
} else {
const withStorageError = callback => {
api.runtime.lastError = storageError ? { message: "Storage unavailable" } : undefined;
try { callback(); } finally { delete api.runtime.lastError; }
};
api.storage = { local: {
get(key, callback) { withStorageError(() => callback(getStored(key))); },
set(value, callback) { withStorageError(() => { if (!storageError) store(value); callback(); }); }
} };
api.tabs = {
create(details, callback) { callback(openTab(details)); },
remove(id, callback) { removedTabs.push(id); callback(); },
sendMessage(tabId, message, options, callback) {
if (typeof options === "function") { callback = options; options = undefined; }
send(tabId, message, options); callback();
}
};
api.cookies = {
get(details, callback) { callback(readCookies(details).find(cookie => cookie.name === details.name) || null); },
getAll(details, callback) { callback(readCookies(details)); },
getAllCookieStores(callback) { callback([{ id: "test-store" }]); }
};
}
const context = {
[family === "firefox" ? "browser" : "chrome"]: api,
fetch, URL, Date: time.Date,
setTimeout: (callback, ms) => { time.advance(ms); return setTimeout(callback, 0); }
};
new vm.Script(composeScript("background"), { filename: "background.js" })
.runInNewContext(context);
assert.equal(listeners.length, 1, "Background must register one listener synchronously");
const sender = { id: "synthetic-extension", url: "https://eeho.fa.us2.oraclecloud.com/fscmUI/faces/FuseWelcome",
tab: { id: 7, windowId: 3, cookieStoreId: "test-store" }, frameId: 2 };
function dispatch(message) {
return new Promise((resolve, reject) => {
let replies = 0;
const timer = setTimeout(() => reject(new Error("Message did not receive a response")), 2000);
const pending = listeners[0](message, sender, result => {
clearTimeout(timer);
assert.equal(++replies, 1, "Message must receive exactly one response");
resolve(plain(result));
});
if (pending !== true) { clearTimeout(timer); reject(new Error("Expected asynchronous response contract")); }
});
}
return { dispatch, listener: listeners[0], progress, requestDebug, tabs, removedTabs, cookieQueries, storage, advance: time.advance };
}
module.exports = { root, plain, response, loadContent, loadBackground };