Implemented the functionality to create Service Requests

This commit is contained in:
2026-10-05 16:30:29 -03:00
parent e9c295be91
commit 7a7143b6e7
24 changed files with 1579 additions and 46 deletions

View File

@@ -40,7 +40,7 @@ test("distribution contracts: manifests, execution boundaries and deterministic
const base = {
manifest_version: 3, name: "Opportunities Extension",
description: "Adiciona um atalho de Opportunities Extension nas paginas Oracle Fusion permitidas.",
version: "1.0.2", permissions: ["cookies", "storage"],
version: "1.0.2", 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*"],
@@ -93,7 +93,7 @@ test("opportunity query preserves fields, record-set OR, date AND, and paginatio
{ 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"],
fields: [...fields, "CustomerAccount.PartyUniqueName", "CustomerAccount.PartyId", "CustomerAccount.PartyNumber", "CustomerAccount.Region_c"],
sort: [], language: "en", skipInValidFields: true, skipHiddenFromUIFields: true,
copiedFrom: "queries/d5dbcd01-9ff3-40be-9b5b-64744cbf7162", limit: 15, offset: 15
});

View File

@@ -84,7 +84,7 @@ test("ramp months preserve zero and flag missing values beside the product count
assert.equal(badge(), undefined);
for (const [values, expected] of [
[[], null], [[null], "N/D"], [[null, undefined, "", " "], "N/D"], [[6], "6"],
[[0], "0"], [[0, "3", null, " 6 ", 3], "0,3,N/D,6,3"]
[[0], "0"], [[0, "3", null, " 6 ", 3], "0 | 3 | N/D | 6 | 3"]
]) {
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", {
fromCache: true, items: values.map(RampMonths_c => ({ RampMonths_c }))
@@ -94,7 +94,7 @@ test("ramp months preserve zero and flag missing values beside the product count
else {
assert.equal(badge().textContent, expected);
assert.equal(badge().attributes["data-missing"], String(expected.includes("N/D")));
assert.equal(badge().title, `Ramp Months: ${expected.split(",").join(", ")}`);
assert.equal(badge().title, `Ramp Months: ${expected.split(" | ").join(", ")}`);
assert.equal(badge().attributes["aria-label"], badge().title);
assert.equal(h.cell("name").children[0].children[1].children[1], badge());
}
@@ -243,7 +243,8 @@ test("table styles reserve compact widths, prioritize Name/Customer and keep nes
assert.match(css, /td\[data-column="customer"\] \{\s*position: relative;/);
assert.match(css, /grid-template-columns: minmax\(0, 1fr\) 12px/);
const tableRules = css.replace(/[^{}]*\.opportunities-extension-products-ramp\s*\{[^}]*\}/g, "")
.replace(/[^{}]*\.opportunities-extension-title-row[^{}]*\{[^}]*\}/g, "");
.replace(/[^{}]*\.opportunities-extension-title-row[^{}]*\{[^}]*\}/g, "")
.replace(/[^{}]*\.opportunities-extension-sr-confirm-dialog\s*\{[^}]*\}/g, "");
assert.doesNotMatch(tableRules, /text-overflow: ellipsis|overflow: hidden|th:nth-child|td:nth-child/);
assert.match(css, /\.opportunities-extension-products-ramp\s*\{[^}]*background: #f0e8f7/);
assert.match(css, /\[data-theme="dark"\] \.opportunities-extension-products-ramp\s*\{[^}]*color: #ead5fc/);

244
tests/srCreate.test.cjs Normal file
View File

@@ -0,0 +1,244 @@
const assert = require("node:assert/strict");
const { test } = require("node:test");
const { loadBackground, response } = require("./support/characterization.cjs");
const type = "opportunitiesExtension.requestPostServiceRequest";
const emailAddress = "signed.in+sr@example.invalid";
const payload = () => ({ AccountPartyId: 100000000000101, OpportunityName_Id_c: 202,
ServiceNew_Id_c: 404, Title: "Service-OP1-Customer", RegionText_c: "LAD", CountryText_c: "BR",
ProblemDescription: "Test description", Language_c: "ENGLISH", CustomerDeadline_c: "2027-01-31",
TechPlatformLookUp_c: "NO_ALL_PLATFORM", PrimaryPillarNew_c: "Tech_Cloud_Infrastructure" });
const withClientId = (body, status, value) => {
const result = response(body, status);
result.headers.set("X-AppBuilder-Client-Id", value);
return result;
};
for (const family of ["chrome", "firefox"]) {
test(`${family}: response client ID from resource lookup appears in confirmation and token request`, async () => {
const calls = [];
const h = loadBackground({ family, fetch: async (url, options) => {
calls.push({ url, options });
if (url.includes("/resourceUsers?")) return withClientId({ resourcePartyId: 303 }, 200, "response-client");
if (url.endsWith("/Catalogue_c/505")) return response({ result: "test-token" });
return response({ SrNumber: "SR1" }, 201);
} });
const prepared = await h.dispatch({ type, phase: "prepare", emailAddress, payload: payload() });
assert.equal(prepared.ok, true);
assert.equal(prepared.headers.requestSrTokenCatalog["x-appbuilder-client-id"], "response-client");
assert.deepEqual(prepared.headers.requestPostServiceRequest, { ...prepared.headers.requestSrTokenCatalog,
"vb-proxy-header-x-oracle-ectoken": "(generated after confirmation)" });
assert.equal(calls.length, 1);
const result = await h.dispatch({ type, phase: "confirm", catalogId: 505, payload: prepared.payload });
assert.equal(result.ok, true);
assert.equal(calls[1].options.headers["x-appbuilder-client-id"], "response-client");
assert.deepEqual({ ...calls[2].options.headers }, { ...calls[1].options.headers, "vb-proxy-header-x-oracle-ectoken": "test-token" });
assert.equal(calls.filter(call => call.url.endsWith("/mgmt/serviceRequests")).length, 1);
});
}
test("response observation includes background responses and excludes other apps and private requests", async () => {
const h = loadBackground();
assert.equal(h.responseHeaderListeners.length, 1);
const capture = h.responseHeaderListeners[0];
assert.deepEqual(capture.options, ["responseHeaders"]);
const details = { tabId: -1,
url: "https://comcipapic-oalprod.integration.ocp.oraclecloud.com/ic/builder/rt/SeaaS_initiation/live;profile=PROD/services/auth/1.1/proxy/example",
responseHeaders: [{ name: "X-AppBuilder-Client-ID", value: "background-response-client" }, { name: "Set-Cookie", value: "not-retained" }] };
capture.listener({ ...details, incognito: true });
capture.listener({ ...details, url: details.url.replace("SeaaS_initiation", "unrelated") });
assert.deepEqual(h.sessionStorage, {});
capture.listener(details);
assert.equal(Object.values(h.sessionStorage)[0].value, "background-response-client");
assert.ok(!JSON.stringify(h.sessionStorage).includes("not-retained"));
});
test("token 403 retries once only with a newly returned client ID; SR POST is never retried", async () => {
for (const mode of ["recovered", "still-forbidden", "unchanged", "no-header", "save-failure"]) {
const calls = [];
let tokens = 0;
const h = loadBackground({ fetch: async (url, options) => {
calls.push({ url, options });
if (url.includes("/resourceUsers?")) return withClientId({ resourcePartyId: 303 }, 200, "old-client");
if (url.endsWith("/Catalogue_c/505")) {
tokens += 1;
if (tokens === 1) return mode === "no-header" ? response({}, 403)
: withClientId({}, 403, mode === "unchanged" ? "old-client" : "new-client");
if (mode === "still-forbidden") return withClientId({}, 403, "another-client");
return response({ result: "retry-token" });
}
return response({ SrNumber: "SR1" }, mode === "save-failure" ? 403 : 201);
} });
const prepared = await h.dispatch({ type, phase: "prepare", emailAddress, payload: payload() });
const result = await h.dispatch({ type, phase: "confirm", catalogId: 505, payload: prepared.payload });
assert.equal(result.ok, mode === "recovered");
const expectedTokens = ["unchanged", "no-header"].includes(mode) ? 1 : 2;
assert.equal(tokens, expectedTokens);
const tokenCalls = calls.filter(call => call.url.endsWith("/Catalogue_c/505"));
assert.equal(tokenCalls[0].options.headers["x-appbuilder-client-id"], "old-client");
if (expectedTokens === 2) assert.equal(tokenCalls[1].options.headers["x-appbuilder-client-id"], "new-client");
assert.equal(calls.filter(call => call.url.endsWith("/mgmt/serviceRequests")).length, ["recovered", "save-failure"].includes(mode) ? 1 : 0);
if (["recovered", "save-failure"].includes(mode)) {
assert.deepEqual({ ...calls.at(-1).options.headers }, { ...tokenCalls.at(-1).options.headers, "vb-proxy-header-x-oracle-ectoken": "retry-token" });
}
assert.ok(!JSON.stringify(result).includes("retry-token"));
}
});
for (const family of ["chrome", "firefox"]) {
test(`${family}: Save resolves current user, discovers catalog, obtains token and creates SR once with protected diagnostics`, async () => {
const calls = [];
const h = loadBackground({ family, fetch: async (url, options) => {
calls.push({ url, options });
if (calls.length === 1) return response({ emailAddress, resourceEmail: emailAddress, resourcePartyId: 303, resourceProfileId: 999 });
if (calls.length === 2) return response({ items: [{ Id: 505 }] });
if (calls.length === 3) return response({ result: "synthetic-secret-token" });
return response({ SrId: 606, SrNumber: "SR606" }, 201);
} });
const prepared = await h.dispatch({ type, phase: "prepare", emailAddress, payload: { ...payload(), ReportedByPartyId: 999 } });
assert.equal(prepared.ok, true);
assert.equal(calls.length, 1, "preview only resolves the user, never generates a token or creates SR");
assert.equal(h.headerRules.length, 0, "preview does not install request modifications");
assert.equal(prepared.headers.requestSrTokenCatalog["Content-Type"], "application/vnd.oracle.adf.action+json");
assert.equal(prepared.headers.requestPostServiceRequest["vb-proxy-header-x-oracle-ectoken"], "(generated after confirmation)");
assert.equal(prepared.payload.ReportedByPartyId, 303);
const result = await h.dispatch({ type, phase: "confirm", payload: prepared.payload });
assert.equal(result.ok, true);
assert.equal(result.result.SrNumber, "SR606");
assert.equal(calls.length, 4);
assert.equal(calls[0].options.method, "GET");
assert.equal(new URL(calls[0].url).searchParams.get("emailAddress"), emailAddress);
assert.ok(calls[0].url.includes("/set/seaas/crm/resourceUsers?"));
assert.equal(calls[1].options.method, "GET");
assert.ok(calls[2].url.endsWith("/Catalogue_c/505"));
assert.deepEqual(JSON.parse(calls[2].options.body), { name: "getJwtToken" });
assert.equal(calls[2].options.headers["Content-Type"], "application/vnd.oracle.adf.action+json");
assert.equal(calls[2].options.headers.Origin, new URL(calls[2].url).origin);
assert.deepEqual({ ...calls[3].options.headers }, { ...calls[2].options.headers, "vb-proxy-header-x-oracle-ectoken": "synthetic-secret-token" });
assert.equal(h.headerRules.length, 1);
const rule = h.headerRules[0].addRules[0];
const serviceRule = h.headerRules[0].addRules[1];
assert.ok(new RegExp(serviceRule.condition.regexFilter).test(calls[3].url));
assert.ok(!new RegExp(serviceRule.condition.regexFilter).test(calls[2].url));
assert.ok(!new RegExp(serviceRule.condition.regexFilter).test(calls[3].url + "/other"));
assert.deepEqual(serviceRule.condition.initiatorDomains, ["synthetic-extension"]);
assert.deepEqual(serviceRule.condition.requestMethods, ["post"]);
assert.deepEqual(serviceRule.action, rule.action);
assert.deepEqual(rule.condition.initiatorDomains, ["synthetic-extension"]);
assert.deepEqual(rule.condition.requestMethods, ["post"]);
assert.ok(new RegExp(rule.condition.regexFilter).test(calls[2].url));
assert.ok(!new RegExp(rule.condition.regexFilter).test(calls[3].url));
assert.ok(!new RegExp(rule.condition.regexFilter).test(calls[2].url + "/other"));
assert.deepEqual(rule.action.requestHeaders, [{ header: "Origin", operation: "set", value: new URL(calls[2].url).origin }]);
assert.equal(calls[3].options.method, "POST");
assert.ok(calls[3].url.endsWith("/set/seaas/mgmt/serviceRequests"));
assert.equal(calls[3].options.headers["vb-proxy-header-x-oracle-ectoken"], "synthetic-secret-token");
assert.ok(calls.slice(0, 3).every(call => !call.options.headers["vb-proxy-header-x-oracle-ectoken"]));
assert.ok(calls.every(call => call.options.credentials === "include" && call.options.headers.Authorization === "Session"));
assert.deepEqual(JSON.parse(calls[3].options.body), { ...payload(), ReportedByPartyId: 303, SvcQuestionsCollection_c: [],
SourceCd: "SVC_OPPORTUNITY_SCREEN", SeverityCd: "ORA_SVC_SEV2", ChannelTypeCd: "ORA_SVC_WEB" });
assert.equal(calls[3].options.body, JSON.stringify(prepared.payload));
assert.ok(!JSON.stringify(result).includes("synthetic-secret-token"));
assert.equal(h.tabs.length, 0);
});
}
test("missing region, invalid dates or unsafe IDs stop before token acquisition", async () => {
const h = loadBackground({ fetch: async () => assert.fail("Unexpected network") });
for (const change of [{ RegionText_c: null }, { CustomerDeadline_c: "2027-02-30" },
{ AccountPartyId: "9007199254740993" }, { OpportunityName_Id_c: "bad/id" }]) {
assert.equal((await h.dispatch({ type, phase: "prepare", emailAddress, payload: { ...payload(), ...change } })).ok, false);
}
assert.equal((await h.dispatch({ type, phase: "prepare", payload: payload() })).ok, false);
assert.equal((await h.dispatch({ type, phase: "confirm", payload: payload() })).ok, false);
assert.equal((await h.dispatch({ type, emailAddress, payload: payload() })).ok, false);
});
for (const family of ["chrome", "firefox"]) {
test(`${family}: captures only SeaaS client ID, reuses it after worker restart and expires stale values`, async () => {
const tokenCalls = [];
const sessionStorage = {};
const fetch = async (url, options) => {
if (url.includes("/resourceUsers?")) return response({ resourcePartyId: 303 });
if (url.endsWith("/Catalogue_c/505")) {
tokenCalls.push(options);
return response({ result: "synthetic-token" });
}
return response({ SrNumber: "SR1" }, 201);
};
const h = loadBackground({ family, fetch, sessionStorage });
assert.equal(h.headerListeners.length, 1);
const capture = h.headerListeners[0];
assert.deepEqual(capture.options, ["requestHeaders"]);
assert.deepEqual(capture.filter.types, ["xmlhttprequest"]);
const request = { tabId: 12, url: "https://comcipapic-oalprod.integration.ocp.oraclecloud.com/ic/builder/rt/SeaaS_initiation/live;profile=PROD/services/auth/1.1/proxy/example",
requestHeaders: [{ name: "X-AppBuilder-Client-Id", value: "synthetic-client" },
{ name: "Cookie", value: "do-not-store-cookie" }, { name: "Authorization", value: "do-not-store-auth" }] };
capture.listener({ ...request, url: request.url.replace("SeaaS_initiation", "otherApp") });
capture.listener({ ...request, url: request.url.replace("comcipapic-oalprod.integration.ocp.oraclecloud.com", "other.example") });
capture.listener({ ...request, tabId: -1 });
capture.listener({ ...request, incognito: true });
capture.listener({ ...request, requestHeaders: [{ name: "x-appbuilder-client-id", value: "invalid\r\nheader" }] });
assert.deepEqual(sessionStorage, {});
capture.listener(request);
assert.equal(Object.keys(sessionStorage).length, 1);
assert.ok(!JSON.stringify(sessionStorage).includes("do-not-store"));
const prepared = await h.dispatch({ type, phase: "prepare", emailAddress, payload: payload() });
assert.equal(prepared.headers.requestSrTokenCatalog["x-appbuilder-client-id"], "synthetic-client");
assert.equal(tokenCalls.length, 0);
const restarted = loadBackground({ family, fetch, sessionStorage });
const result = await restarted.dispatch({ type, phase: "confirm", catalogId: 505, payload: prepared.payload });
assert.equal(result.ok, true);
assert.equal(tokenCalls[0].headers["x-appbuilder-client-id"], "synthetic-client");
assert.equal(result.debugRequests[0].metadata.appBuilderClientId, "Observed in SeaaS requests");
restarted.advance(60 * 60 * 1000 + 1);
const expired = await restarted.dispatch({ type, phase: "confirm", catalogId: 505, payload: prepared.payload });
assert.equal(expired.ok, true);
assert.equal(tokenCalls[1].headers["x-appbuilder-client-id"], undefined);
assert.equal(expired.debugRequests[0].metadata.appBuilderClientId, "Not observed in SeaaS requests");
capture.listener({ ...request, requestHeaders: [{ name: "x-appbuilder-client-id", value: "renewed-client" }] });
const refreshed = await h.dispatch({ type, phase: "prepare", emailAddress, payload: payload() });
assert.equal(refreshed.headers.requestSrTokenCatalog["x-appbuilder-client-id"], "renewed-client");
assert.equal(h.tabs.length, 0, "header discovery does not add background page loads");
});
}
test("Origin rule failure blocks token acquisition and SR creation", async () => {
const h = loadBackground({ ruleError: true, fetch: async () => assert.fail("No request expected") });
const result = await h.dispatch({ type, phase: "confirm", catalogId: 505, payload: { ...payload(), ReportedByPartyId: 303 } });
assert.equal(result.ok, false);
assert.match(result.error, /Rule permission denied/);
});
test("resource lookup failures or invalid resourcePartyId never obtain a token or create SR", async () => {
for (const body of [{ resourcePartyId: null }, { resourceProfileId: 999 }, { resourcePartyId: "9007199254740993" }, { resourcePartyId: 0 }, "unauthorized"]) {
let calls = 0;
const h = loadBackground({ fetch: async () => {
calls += 1;
return response(body, body === "unauthorized" ? 401 : 200);
} });
const result = await h.dispatch({ type, phase: "prepare", emailAddress, payload: payload() });
assert.equal(result.ok, false);
assert.equal(calls, 1);
assert.equal(result.debugRequests[0].name, "requestResourceUser");
}
});
test("token rejection never creates SR; creation failures are never automatically retried", async () => {
for (const failure of ["token", "create", "empty-token", "network"]) {
let calls = 0;
const h = loadBackground({ fetch: async () => {
calls += 1;
if (calls === 1) return response({ resourcePartyId: 303 });
if (calls === 2) return failure === "token" ? response({}, 401)
: response({ result: failure === "empty-token" ? "" : "secret-token" });
if (failure === "network") throw new Error("Network error");
return response({}, 500);
} });
const prepared = await h.dispatch({ type, phase: "prepare", emailAddress, payload: payload() });
const result = await h.dispatch({ type, phase: "confirm", catalogId: 505, payload: prepared.payload });
assert.equal(result.ok, false);
assert.equal(calls, failure === "token" || failure === "empty-token" ? 2 : 3);
assert.ok(!JSON.stringify(result).includes("secret-token"));
assert.equal(h.tabs.length, 0);
}
});

View File

@@ -57,7 +57,7 @@ function loadContent({ fetch = unexpectedFetch, storage = new Map(), xsrf = null
};
}
function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage = {}, cookies = () => [], storageError = false, messageTimeout = 2000 } = {}) {
function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage = {}, sessionStorage = {}, cookies = () => [], storageError = false, ruleError = false, messageTimeout = 2000 } = {}) {
const time = clock();
const listeners = [];
const progress = [];
@@ -65,7 +65,17 @@ function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage =
const tabs = [];
const removedTabs = [];
const cookieQueries = [];
const api = { runtime: { onMessage: { addListener: fn => listeners.push(fn) } } };
const headerListeners = [];
const responseHeaderListeners = [];
const headerRules = [];
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) }) }
},
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));
@@ -110,6 +120,10 @@ function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage =
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,
@@ -132,7 +146,7 @@ function loadBackground({ family = "chrome", fetch = unexpectedFetch, storage =
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 };
return { dispatch, listener: listeners[0], progress, requestDebug, tabs, removedTabs, cookieQueries, storage, sessionStorage, headerRules, headerListeners, responseHeaderListeners, advance: time.advance };
}
module.exports = { root, plain, response, loadContent, loadBackground };

View File

@@ -118,6 +118,129 @@ test("missing SR plus opens pillar form, handles retry, ignores closed requests
assert.equal(h.host.children.length, 0);
});
test("SR Save validates form, maps dynamic values, locks duplicate clicks and preserves failures", async () => {
const h = setup();
h.app.opportunitiesQueryState.periodRequestVersion = 1;
h.app.fusionCrmServiceRequests.requestCrmServiceRequest = async () => [];
h.app.debugContentView.renderDebugPanel = () => {};
await h.app.opportunityCrmServiceRequests.load("test", 1);
h.app.currentUser.getUserId = () => "current@example.invalid";
h.app.opportunitiesTableState.opportunitiesTableState.items = [{ OptyNumber: "B", CustomerAccount: { PartyId: 999, Region_c: "EMEA" } }, {
OptyNumber: "A", OptyId: 202, PartyId: 888, AccountPartyId: 777, Owner: { PartyId: 666 }, Region_c: "EMEA", RegionText_c: "LAD", Name: "Opportunity",
CustomerAccount: { PartyId: 100000000000101, PartyUniqueName: "Customer", Region_c: "APAC", PrimaryAddress: { CountryCode: "BR" } }
}];
const posts = [];
const notices = [];
h.app.opportunitiesLinks.showCopyConfirmation = (...args) => notices.push(args);
h.app.platformContentMessages.sendRuntimeMessage = message => {
if (message.type.endsWith("requestPostServiceRequest")) return new Promise(resolve => posts.push({ message, resolve }));
return Promise.resolve({ ok: true, items: message.type.endsWith("requestSrLookupOpportunityBusiness")
? [{ Id: "404", RecordName: "Opportunity Pursuit", Abstract_c: "Description" }] : [{ code: "CODE", name: "Label" }] });
};
const plus = h.app.opportunityCrmServiceRequests.createBadge("A").children.find(node => node.tag === "button");
plus.click();
await flush();
const save = () => h.nodes().find(node => node.tag === "button" && ["Save", "Preparing...", "Saving..."].includes(node.textContent));
const submit = () => h.form().listeners.submit({ preventDefault() {} });
assert.equal(save().disabled, false);
await submit();
assert.equal(posts.length, 0);
assert.ok(h.nodes().some(node => node.attributes["aria-invalid"] === "true"));
for (const name of ["primaryPillar", "language", "platform"]) h.nodes().find(node => node.name === name).value = "CODE";
h.nodes().find(node => node.name === "customerDeadline").value = "2027-01-31";
h.nodes().find(node => node.name === "description").value = "Dynamic description";
h.nodes().find(node => node.name === "service").checked = true;
const saving = submit();
await submit();
assert.equal(posts.length, 1);
assert.equal(save().textContent, "Preparing...");
assert.equal(save().disabled, true);
h.nodes().find(node => node.textContent === "Cancel").click();
assert.ok(h.form(), "cannot dismiss while Save is in flight");
const payload = posts[0].message.payload;
assert.equal(payload.AccountPartyId, 100000000000101, "uses only the selected item's CustomerAccount.PartyId");
assert.equal(payload.OpportunityName_Id_c, 202);
assert.equal(payload.ReportedByPartyId, undefined);
assert.equal(posts[0].message.emailAddress, "current@example.invalid");
assert.equal(payload.Title, "Opportunity Pursuit-A-Customer");
assert.equal(payload.ServiceNew_Id_c, "404");
assert.equal(payload.PrimaryPillarNew_c, "CODE");
assert.equal(payload.Language_c, "CODE");
assert.equal(payload.TechPlatformLookUp_c, "CODE");
assert.equal(payload.CountryText_c, "BR");
assert.equal(payload.RegionText_c, "APAC", "uses CustomerAccount.Region_c from the selected opportunity, not a root-level region or another opportunity");
assert.equal(payload.ProblemDescription, "Dynamic description");
assert.equal(payload.CustomerDeadline_c, "2027-01-31");
assert.equal(posts[0].message.phase, "prepare");
const preparedPayload = { ...payload, ReportedByPartyId: 303, SvcQuestionsCollection_c: [],
SourceCd: "SVC_OPPORTUNITY_SCREEN", SeverityCd: "ORA_SVC_SEV2", ChannelTypeCd: "ORA_SVC_WEB" };
const preparedHeaders = {
requestSrTokenCatalog: { Authorization: "Session", "Content-Type": "application/vnd.oracle.adf.action+json", Origin: "https://example.invalid" },
requestPostServiceRequest: { Authorization: "Session", "Content-Type": "application/vnd.oracle.adf.action+json", Origin: "https://example.invalid", "vb-proxy-header-x-oracle-ectoken": "(generated after confirmation)" }
};
posts[0].resolve({ ok: true, payload: preparedPayload, headers: preparedHeaders });
await flush();
const confirmation = () => h.nodes().find(node => node.className?.includes("opportunities-extension-sr-confirmation"));
const confirm = () => h.nodes().find(node => node.textContent === "Confirm Save");
assert.ok(confirmation());
const preview = h.nodes().find(node => node.tag === "pre");
assert.equal(preview.textContent, JSON.stringify(preparedPayload, null, 2));
const previews = h.nodes().filter(node => node.tag === "pre");
assert.equal(previews.length, 3);
assert.equal(previews[1].textContent, JSON.stringify(preparedHeaders.requestSrTokenCatalog, null, 2));
assert.equal(previews[2].textContent, JSON.stringify(preparedHeaders.requestPostServiceRequest, null, 2));
assert.ok(h.nodes().some(node => node.textContent === "x-appbuilder-client-id: not yet observed in SeaaS requests."));
assert.equal(h.form().parent.inert, true);
assert.equal(h.document.activeElement.textContent, "Cancel");
await submit();
assert.equal(posts.length, 1, "awaiting confirmation must never send a creation request");
confirm().focus();
confirmation().listeners.keydown({ key: "Tab", preventDefault() {}, stopPropagation() {} });
assert.equal(h.document.activeElement, preview);
h.nodes().filter(node => node.tag === "button" && node.textContent === "Cancel").at(-1).click();
await saving;
assert.equal(posts.length, 1, "Cancel never creates SR");
assert.equal(confirmation(), undefined);
assert.equal(h.form().parent.inert, false);
assert.equal(save().disabled, false);
assert.equal(h.nodes().find(node => node.name === "description").value, "Dynamic description");
assert.equal(h.document.activeElement, save());
assert.equal(notices.length, 0);
const escaped = submit();
posts[1].resolve({ ok: true, payload: preparedPayload });
await flush();
confirmation().listeners.keydown({ key: "Escape", preventDefault() {}, stopPropagation() {} });
await escaped;
assert.equal(posts.length, 2);
assert.ok(h.form(), "Escape closes only the confirmation");
const failed = submit();
posts[2].resolve({ ok: true, payload: preparedPayload });
await flush();
const confirmButton = confirm();
confirmButton.click();
confirmButton.click();
await flush();
assert.equal(posts.length, 4, "double confirm sends only one creation request");
assert.equal(posts[3].message.phase, "confirm");
assert.deepEqual(posts[3].message.payload, preparedPayload);
assert.equal(posts[3].message.payload.AccountPartyId, 100000000000101);
assert.equal(save().textContent, "Saving...");
posts[3].resolve({ ok: false, error: "Synthetic failure" });
await failed;
assert.equal(notices.at(-1)[1], true);
const retry = submit();
posts[4].resolve({ ok: true, payload: preparedPayload });
await flush();
confirm().click();
await flush();
posts[5].resolve({ ok: true, result: { SrId: 505, SrNumber: "SR505" } });
await retry;
assert.equal(h.form(), undefined);
assert.equal(notices.at(-1)[0], "Service Request saved successfully.");
});
test("only plus opens the nine-field form; a missing workload stays disabled without a save operation", () => {
const h = setup();
const badge = h.app.opportunityWorkloadActions.createBadge("A");