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

188 lines
9.6 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 = {}, sessionStorage = {}, cookies = () => [], storageError = false, ruleError = false, updateTabError = false, messageTimeout = 2000 } = {}) {
const time = clock();
const listeners = [];
const progress = [];
const requestDebug = [];
const tabs = [];
const removedTabs = [];
const updatedTabs = [];
const cookieQueries = [];
const headerListeners = [];
const responseHeaderListeners = [];
const headerRules = [];
let spaDocument = 0;
let identityRequest;
const api = { runtime: { onMessage: { addListener: fn => listeners.push(fn) },
getURL: path => `${family === "firefox" ? "moz" : "chrome"}-extension://synthetic-extension${path}` },
webRequest: {
onBeforeSendHeaders: { addListener: (listener, filter, options) => headerListeners.push({ listener, filter: plain(filter), options: plain(options) }) },
onHeadersReceived: {
addListener: (listener, filter, options) => responseHeaderListeners.push({ listener, filter: plain(filter), options: plain(options) }),
removeListener: listener => {
const index = responseHeaderListeners.findIndex(item => item.listener === listener);
if (index >= 0) responseHeaderListeners.splice(index, 1);
}
}
},
declarativeNetRequest: { updateSessionRules: async rules => { if (ruleError) throw new Error("Rule permission denied"); headerRules.push(plain(rules)); } }
};
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 newSpaDocument = () => { spaDocument++; identityRequest = null; };
const openTab = details => { tabs.push(plain(details)); newSpaDocument(); return { id: 91 }; };
const send = (tabId, message, options) => {
if (message.type === "opportunitiesExtension.inspectSpaPage") return { status: "ready", documentId: String(spaDocument) };
if (message.type === "opportunitiesExtension.readSpaIdentity") {
assert.equal(message.documentId, String(spaDocument));
if (!identityRequest) identityRequest = (async () => {
try {
const result = await fetch("https://spa.oracle.com/oalcrm/web/api/identity-management/users/current", {
method: "GET", credentials: "include", cache: "no-store", headers: { Accept: "application/json" }
});
return { status: result.status, statusText: result.statusText, contentType: result.headers.get("content-type"), body: await result.text() };
} catch (error) { return { error: error.message }; }
})();
return identityRequest;
}
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),
update: async (id, details) => {
updatedTabs.push({ id, details: plain(details) });
if (updateTabError) throw new Error("Synthetic navigation failure");
newSpaDocument();
return { 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 = {
update(id, details, callback) {
updatedTabs.push({ id, details: plain(details) });
api.runtime.lastError = updateTabError ? { message: "Synthetic navigation failure" } : undefined;
if (!updateTabError) newSpaDocument();
try { callback({ id }); } finally { delete api.runtime.lastError; }
},
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; }
Promise.resolve(send(tabId, message, options)).then(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" }]); }
};
}
api.storage.session = {
get: async key => ({ [key]: sessionStorage[key] === undefined ? undefined : plain(sessionStorage[key]) }),
set: async value => Object.assign(sessionStorage, plain(value))
};
const context = {
[family === "firefox" ? "browser" : "chrome"]: api,
fetch, URL, Date: time.Date, clearTimeout,
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")), messageTimeout);
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, updatedTabs, cookieQueries, storage, sessionStorage, headerRules, headerListeners, responseHeaderListeners, advance: time.advance };
}
module.exports = { root, plain, response, loadContent, loadBackground };