Implemented the functionality to add a WL Action

This commit is contained in:
2026-09-28 17:32:32 -03:00
parent 1a3decbeb6
commit a182609415
23 changed files with 1981 additions and 79 deletions

View File

@@ -0,0 +1,110 @@
const assert = require("node:assert/strict");
const { test } = require("node:test");
const { loadBackground, response } = require("./support/characterization.cjs");
const lookups = () => Object.fromEntries(["ROLE", "NAME", "TEAM", "STATUS", "COMPLEXITY"].map(key => [
`WWB_ACTION_${key}`, [{ code: `${key}_CODE`, name: `${key} label` }]
]));
const teamType = "opportunitiesExtension.requestActionTeamMembers";
const lookupType = "opportunitiesExtension.requestActionFindLookup";
test("Save sends the WWB POST, accepts empty success and never retries errors", async () => {
const payload = { parentType: "WORKLOAD", parentId: "123", action: {
ownerEmail: "owner@example.invalid", role: "CA", name: "REVIEW", team: "TEAM", tags: "",
startDate: "2026-09-14T00:00:00.000Z", endDate: "2026-09-15T00:00:00.000Z",
status: "COMPLETE", complexity: "LOW", notes: [], objective: "",
createdBy: "creator@example.invalid", updatedBy: "creator@example.invalid"
} };
for (const status of [200, 201, 204, 403, 500]) {
const calls = [];
const h = loadBackground({ family: "chrome", fetch: async (url, options) => {
calls.push({ url, options });
return status === 204 ? new Response(null, { status }) : response({ id: 123 }, status);
} });
const result = await h.dispatch({ type: "opportunitiesExtension.requestMgmtAction", payload, authorization: { value: "secret-wwb" } });
assert.equal(result.ok, status < 300, `${status}: ${result.error}`);
assert.equal(calls.length, 1);
assert.ok(calls[0].url.endsWith("/wwb/mgmt/actions"));
assert.equal(calls[0].options.method, "POST");
assert.equal(calls[0].options.headers["Wwb-Provider-Authorization"], "secret-wwb");
assert.deepEqual(JSON.parse(calls[0].options.body), payload);
assert.ok(!JSON.stringify(result).includes("secret-wwb"));
}
});
for (const family of ["chrome", "firefox"]) {
test(`${family}: Action options use dynamic RevnId, WWB-TS and masked GET diagnostics`, async () => {
const calls = [];
const h = loadBackground({ family, fetch: async (url, options) => {
calls.push({ url, options });
if (url.endsWith("consumerTokenService")) return response([
{ tokenService: "ACCP-TS", uiaasHeader: { value: "summary-token" } },
{ tokenService: "OCICONS-TS", uiaasHeader: { value: "wrong-token" } },
{ tokenService: "WWB-TS", uiaasHeader: { value: "synthetic-wwb" } }
]);
return response(url.includes("teamMembers") ? [{ email: "owner@example.invalid" }] : lookups());
} });
const auth = await h.dispatch({ type: "opportunitiesExtension.requestConsumerTokenService" });
const authorization = auth.authorizations.workloadActions;
const team = await h.dispatch({ type: teamType, workloadId: "987654", authorization, url: "https://example.invalid" });
const lookup = await h.dispatch({ type: lookupType, authorization });
assert.equal(team.ok, true);
assert.deepEqual(team.members, [{ email: "owner@example.invalid" }]);
assert.deepEqual(lookup.lookups, lookups());
const base = "https://spa.oracle.com/oalcrm/web/api/provider-proxy/wwb-provider/service/workbench-proxy/wwb/mgmt/workloads";
assert.equal(calls[1].url, `${base}/987654/teamMembers?includeLeads=true`);
assert.equal(calls[2].url, `${base}/fndLookup`);
for (const call of calls.slice(1)) {
assert.equal(call.options.method, "GET");
assert.equal(call.options.credentials, "include");
assert.deepEqual({ ...call.options.headers }, { Accept: "application/json", "Wwb-Provider-Authorization": "synthetic-wwb" });
}
for (const [result, name] of [[team, "requestActionTeamMembers"], [lookup, "requestActionFindLookup"]]) {
const log = result.debugRequests[0];
assert.equal(log.name, name);
assert.equal(log.status, "success");
assert.equal(log.httpStatus, 200);
assert.ok(log.durationMs >= 0);
assert.equal(log.requestHeaders["Wwb-Provider-Authorization"], "(present)");
assert.doesNotMatch(JSON.stringify(result), /synthetic-wwb|wrong-token/);
}
});
}
test("invalid workload IDs and missing authorization never fetch or accept an arbitrary target", async () => {
const h = loadBackground({ fetch: () => assert.fail("No network expected") });
for (const workloadId of [undefined, "", "abc", "12/../fndLookup", "12?other=1"]) {
assert.equal((await h.dispatch({ type: teamType, workloadId, authorization: { value: "synthetic" } })).ok, false);
}
for (const type of [teamType, lookupType]) {
assert.equal((await h.dispatch({ type, workloadId: "123" })).ok, false);
}
});
test("Action options require exactly HTTP 200 and valid collections and preserve failed debug entries", async () => {
for (const type of [teamType, lookupType]) {
const valid = type === teamType ? [{ email: "owner@example.invalid" }] : lookups();
for (const fetch of [
async () => response(valid, 201), async () => response({}, 401), async () => response({}, 403),
async () => { throw new Error("Synthetic network failure"); },
async () => new Response("<html>Unexpected session page</html>"),
async () => response(type === teamType ? [{ name: "Missing email" }] : { ...lookups(), WWB_ACTION_NAME: null })
]) {
const h = loadBackground({ fetch });
const result = await h.dispatch({ type, workloadId: "123", authorization: { value: "synthetic-secret" } });
assert.equal(result.ok, false);
assert.equal(result.debugRequests.length, 1);
assert.equal(result.debugRequests[0].status, "failed");
assert.ok(result.error);
assert.doesNotMatch(JSON.stringify(result), /synthetic-secret/);
}
}
});
test("empty option lists are valid responses, not invented defaults", async () => {
const empty = Object.fromEntries(Object.keys(lookups()).map(key => [key, []]));
const h = loadBackground({ fetch: async url => response(url.includes("teamMembers") ? [] : empty) });
const members = await h.dispatch({ type: teamType, workloadId: "123", authorization: { value: "synthetic" } });
const lookup = await h.dispatch({ type: lookupType, authorization: { value: "synthetic" } });
assert.deepEqual(members.members, []);
assert.deepEqual(lookup.lookups, empty);
});

View File

@@ -37,7 +37,7 @@ test("activity badge filters precede Status, are accessible and combine without
assert.equal(h.button("sr", "matched").textContent, "");
assert.equal(h.button("actions", "missing").textContent, "");
assert.equal(h.button("actions", "matched").type, "button");
assert.equal(h.controls.children[1].children[0].textContent, "Act");
assert.equal(h.controls.children[1].children[0].textContent, "Action");
for (const value of ["matched", "missing"]) {
assert.equal(h.button("actions", value).textContent, "");
assert.ok(h.button("actions", value).title.startsWith("Whether the Action is registered in Workload Workbench.\n"));

View File

@@ -1,6 +1,10 @@
const assert = require("node:assert/strict");
const { test } = require("node:test");
const { loadApplication } = require("./support/application.cjs");
const flush = () => new Promise(resolve => setImmediate(resolve));
const lookupData = () => Object.fromEntries(["ROLE", "NAME", "TEAM", "STATUS", "COMPLEXITY"].map(key => [
`WWB_ACTION_${key}`, [{ code: `${key}_CODE`, name: `${key} <synthetic>` }]
]));
function setup() {
let document;
@@ -31,7 +35,7 @@ function setup() {
return { app, host, document, nodes, form };
}
test("only plus on the missing Action badge opens a form with six fields and no save operation", () => {
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");
assert.equal(badge.textContent, "0+");
@@ -43,10 +47,12 @@ test("only plus on the missing Action badge opens a form with six fields and no
plus.click();
assert.equal(h.host.children.length, 1);
assert.equal(h.host.children[0].attributes.role, "dialog");
assert.deepEqual(h.nodes().filter(node => node.name).map(node => node.name), ["team", "startDate", "endDate", "status", "complexity", "notes"]);
assert.deepEqual(h.nodes().filter(node => node.name).map(node => node.name), ["owner", "role", "action", "team", "startDate", "endDate", "status", "complexity", "notes"]);
assert.equal(h.nodes().find(node => node.name === "notes").tag, "textarea");
assert.equal(h.nodes().find(node => node.name === "startDate").type, "date");
assert.equal(h.document.activeElement.name, "team");
assert.equal(h.document.activeElement.title, "Close");
assert.ok(h.nodes().filter(node => node.name).every(node => node.disabled));
assert.equal(h.form().attributes["data-state"], "error");
assert.equal(h.nodes().find(node => node.textContent === "Save").disabled, true);
let prevented = false;
h.form().listeners.submit({ preventDefault() { prevented = true; } });
@@ -113,3 +119,297 @@ test("Action header shows the selected opportunity customer, name and formatted
h.app.opportunityWorkloadActions.createBadge("B").children.find(node => node.tag === "button").click();
assert.deepEqual(h.nodes().filter(node => node.tag === "dd").map(node => node.textContent), ["Customer B", "Opportunity B", "-"]);
});
function optionsSetup(products = [{ RevnId: "123", WorkloadName_c: "Synthetic workload" }]) {
const h = setup();
h.calls = [];
h.app.productsContentState.opptyProductsByNumber.set("A", { items: products });
h.app.opportunityWorkloadActions.getSummary = () => ({ state: "missing" });
h.app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ workloadActions: { value: "synthetic-wwb" } });
h.app.debugContentView.renderDebugPanel = () => {};
h.app.platformContentMessages.sendRuntimeMessage = message => new Promise((resolve, reject) => h.calls.push({ message, resolve, reject }));
h.field = name => h.nodes().find(node => node.name === name);
h.open = () => h.app.workloadActionForm.open("A", null);
h.workload = () => h.nodes().find(node => node.id === "opportunities-extension-action-form-workload");
h.completeLookup = () => h.calls.find(call => call.message.type.endsWith("requestActionFindLookup")).resolve({ ok: true, lookups: lookupData(), debugRequests: [] });
return h;
}
test("both options requests must succeed before fields enable; email and lookup codes stay distinct from labels", async () => {
const h = optionsSetup();
h.open();
assert.equal(h.form().attributes["aria-busy"], "true");
assert.ok(h.nodes().filter(node => node.name).every(node => node.disabled));
await flush();
assert.equal(h.calls.length, 2);
assert.deepEqual(h.calls.map(call => call.message.type), ["opportunitiesExtension.requestActionFindLookup", "opportunitiesExtension.requestActionTeamMembers"]);
const team = h.calls[1];
assert.equal(team.message.workloadId, "123");
for (const call of h.calls) assert.equal(call.message.authorization.value, "synthetic-wwb");
team.resolve({ ok: true, members: [{ email: "owner@example.invalid" }], debugRequests: [] });
await flush();
assert.equal(h.field("owner").disabled, true, "a partial result cannot enable the form");
h.completeLookup();
await flush();
assert.equal(h.form().attributes["data-state"], "ready");
assert.equal(h.form().attributes["aria-busy"], "false");
assert.equal(h.field("owner").tag, "select");
assert.equal(h.field("owner").children[1].textContent, "owner@example.invalid");
assert.equal(h.field("owner").children[1].value, "owner@example.invalid");
assert.equal(h.field("owner").value, "", "unmatched user is not replaced by the first owner");
for (const [name, key] of [["role", "ROLE"], ["action", "NAME"], ["team", "TEAM"], ["status", "STATUS"], ["complexity", "COMPLEXITY"]]) {
const field = h.field(name);
assert.equal(field.tag, "select");
assert.equal(field.disabled, false);
assert.equal(field.children[1].value, `${key}_CODE`);
assert.equal(field.children[1].textContent, `${key} <synthetic>`);
assert.equal(field.children[1].children.length, 0);
}
assert.equal(h.document.activeElement.id, `${h.field("owner").id}-search`);
assert.equal(h.nodes().find(node => node.textContent === "Save").disabled, false);
let prevented = false;
h.form().listeners.submit({ preventDefault() { prevented = true; } });
assert.equal(prevented, true);
assert.equal(h.calls.length, 2, "no save request");
});
test("Action defaults use current user and lookup labels; only Notes is optional", async () => {
const h = optionsSetup();
const identity = h.app.currentUser.ensure();
h.calls[0].resolve({ ok: true, user: { id: "OWNER@example.invalid", roles: ["SIMPLE_USER"] } });
await identity;
assert.equal(h.app.currentUser.getUserId(), "OWNER@example.invalid");
h.calls.length = 0;
h.open();
await flush();
const data = lookupData();
data.WWB_ACTION_ROLE = [{ code: "CLOUD_ARCHITECT", name: "Cloud Architect" }];
data.WWB_ACTION_NAME = [{ code: "SUCCESS_PLAN_CONSUM_RVW", name: "Success Plan & Consumption review" }];
data.WWB_ACTION_STATUS = [{ code: "COMPLETE", name: "Complete" }];
h.calls[0].resolve({ ok: true, lookups: data });
h.calls[1].resolve({ ok: true, members: [{ email: "other@example.invalid" }, { email: "owner@example.invalid" }] });
await flush();
for (const [name, value, label] of [
["owner", "owner@example.invalid", "owner@example.invalid"],
["role", "CLOUD_ARCHITECT", "Cloud Architect"],
["action", "SUCCESS_PLAN_CONSUM_RVW", "Success Plan & Consumption review"],
["status", "COMPLETE", "Complete"]
]) {
assert.equal(h.field(name).value, value);
assert.equal(h.nodes().find(node => node.id === `${h.field(name).id}-search`).value, label);
}
for (const name of ["owner", "role", "action", "team", "startDate", "endDate", "status", "complexity"]) {
const field = h.field(name);
assert.equal(field.required, true);
assert.equal(field.attributes["aria-required"], "true");
assert.ok(field.parent.children[0].textContent.endsWith("(required)"));
if (field.tag === "select") {
assert.equal(h.nodes().find(node => node.id === `${field.id}-search`).required, true);
}
}
assert.equal(h.field("team").value, "");
assert.equal(h.field("complexity").value, "");
assert.equal(h.field("notes").required, false);
assert.equal(h.field("notes").parent.children[0].textContent, "Notes (optional)");
assert.equal(h.calls.length, 2);
});
test("Save validates, prevents duplicate POSTs, preserves errors and reports success", async () => {
const h = optionsSetup();
h.app.productsContentJobs.queueProductsTableRender = () => {};
const notifications = [];
h.app.opportunitiesLinks.showCopyConfirmation = (...args) => notifications.push(args);
h.app.currentUser.getUserId = () => "creator@example.invalid";
h.open();
await flush();
h.completeLookup();
h.calls[1].resolve({ ok: true, members: [{ email: "owner@example.invalid" }] });
await flush();
const submit = () => h.form().listeners.submit({ preventDefault() {} });
await submit();
assert.equal(h.calls.length, 2);
assert.equal(h.nodes().find(node => node.id === `${h.field("owner").id}-search`).attributes["aria-invalid"], "true");
for (const name of ["owner", "role", "action", "team", "status", "complexity"]) h.field(name).value = h.field(name).children[1].value;
h.field("startDate").value = "2026-09-14";
h.field("endDate").value = "2026-09-13";
await submit();
assert.equal(h.calls.length, 2);
assert.equal(h.field("endDate").attributes["aria-invalid"], "true");
h.field("endDate").value = "2026-09-15";
h.field("notes").value = "My note";
const first = submit();
await flush();
await submit();
assert.equal(h.calls.length, 3);
const call = h.calls[2];
assert.equal(call.message.type, "opportunitiesExtension.requestMgmtAction");
assert.equal(call.message.authorization.value, "synthetic-wwb");
assert.deepEqual(JSON.parse(JSON.stringify(call.message.payload)), {
parentType: "WORKLOAD", parentId: "123", action: {
ownerEmail: "owner@example.invalid", role: "ROLE_CODE", name: "NAME_CODE", team: "TEAM_CODE",
tags: "", startDate: "2026-09-14T00:00:00.000Z", endDate: "2026-09-15T00:00:00.000Z",
status: "STATUS_CODE", complexity: "COMPLEXITY_CODE", notes: ["My note"], objective: "",
createdBy: "creator@example.invalid", updatedBy: "creator@example.invalid"
}
});
call.resolve({ ok: false, error: "HTTP 403" });
await first;
assert.equal(h.field("notes").value, "My note");
assert.deepEqual(notifications[0], ["HTTP 403", true]);
h.field("notes").value = "";
const second = submit();
await flush();
assert.equal(h.calls[3].message.payload.action.notes.length, 0);
h.calls[3].resolve({ ok: true, result: { id: 77, ownerEmail: "synthetic@example.invalid", name: "REVIEW" } });
await second;
assert.equal(h.host.children.length, 0);
assert.deepEqual(notifications[1], ["Action saved successfully."]);
assert.equal(h.app.opportunityWorkloadActions.createBadge("A").attributes["data-state"], "matched");
});
test("autocomplete searches labels and codes, selects by keyboard and restores committed values", async () => {
const h = optionsSetup();
h.open();
await flush();
const data = lookupData();
data.WWB_ACTION_ROLE = [{ code: "CA", name: "Cloud Architect" }, { code: "SE", name: "Solution Engineer" }];
h.calls[0].resolve({ ok: true, lookups: data });
h.calls[1].resolve({ ok: true, members: [{ email: "owner@example.invalid" }] });
await flush();
const input = h.nodes().find(node => node.id === `${h.field("role").id}-search`);
const list = h.nodes().find(node => node.id === input.attributes["aria-controls"]);
list.style = {};
input.closest = () => ({ getBoundingClientRect: () => ({ top: 20, bottom: 600 }) });
input.getBoundingClientRect = () => ({ top: 100, bottom: 138 });
const key = key => input.listeners.keydown({ key, preventDefault() {}, stopPropagation() {} });
input.value = "architect";
input.listeners.input();
assert.equal(list.attributes["data-placement"], "below");
assert.equal(list.style.maxHeight, "180px");
assert.equal(list.children.length, 1);
assert.equal(list.children[0].textContent, "Cloud Architect");
key("ArrowDown");
assert.equal(list.children[0].attributes["aria-selected"], "true");
key("Enter");
assert.equal(h.field("role").value, "CA");
assert.equal(input.value, "Cloud Architect");
assert.equal(list.hidden, true);
input.value = "not present";
input.getBoundingClientRect = () => ({ top: 500, bottom: 538 });
input.listeners.input();
assert.equal(list.attributes["data-placement"], "above");
assert.equal(list.style.maxHeight, "180px");
assert.equal(list.textContent, "No results");
key("Escape");
assert.equal(input.value, "Cloud Architect");
input.value = "SE";
input.listeners.input();
list.children[0].click();
assert.equal(h.field("role").value, "SE");
assert.equal(input.value, "Solution Engineer");
input.value = "free text";
input.listeners.blur();
assert.equal(input.value, "Solution Engineer");
assert.equal(h.calls.length, 2, "searching never issues requests");
});
test("workload autocomplete uses the selected RevnId and disables metadata search during loading", async () => {
const h = optionsSetup([{ RevnId: "123", WorkloadName_c: "First" }, { RevnId: "456", WorkloadName_c: "Second" }]);
h.open();
await flush();
const input = h.nodes().find(node => node.id === `${h.workload().id}-search`);
input.value = "second";
input.listeners.input();
input.listeners.keydown({ key: "Enter", preventDefault() {} });
await flush();
assert.equal(h.workload().value, "456");
assert.equal(h.calls[1].message.workloadId, "456");
assert.ok(h.nodes().filter(node => node.attributes.role === "combobox" && node !== input).every(node => node.disabled));
});
test("multiple workloads require selection, share the lookup and ignore an older member response", async () => {
const h = optionsSetup([{ RevnId: "123", WorkloadName_c: "First" }, { RevnId: "456", WorkloadName_c: "Second" }, { RevnId: "123" }]);
h.open();
await flush();
assert.equal(h.calls.length, 1);
assert.equal(h.form().attributes["data-state"], "waiting");
const workload = h.workload();
assert.equal(workload.children.length, 3, "duplicate revenue IDs do not duplicate workloads");
assert.equal(workload.parent.hidden, false);
workload.value = "123";
workload.listeners.change();
await flush();
workload.value = "456";
workload.listeners.change();
await flush();
assert.deepEqual(h.calls.slice(1).map(call => call.message.workloadId), ["123", "456"]);
h.completeLookup();
h.calls[2].resolve({ ok: true, members: [{ email: "second@example.invalid" }] });
await flush();
assert.equal(h.field("owner").children[1].value, "second@example.invalid");
h.calls[1].resolve({ ok: true, members: [{ email: "first@example.invalid" }] });
await flush();
assert.equal(h.field("owner").children[1].value, "second@example.invalid");
assert.equal(h.form().attributes["data-state"], "ready");
});
test("errors remain local, register diagnostics under Actions and can be retried without duplicate requests", async () => {
const h = optionsSetup();
h.open();
await flush();
h.completeLookup();
h.calls[1].resolve({ ok: false, error: "Synthetic HTTP 403", debugRequests: [{
name: "requestActionTeamMembers", status: "failed", httpStatus: 403, startedAt: "2026-09-28T12:00:00Z",
completedAt: "2026-09-28T12:00:01Z", durationMs: 1000, requestHeaders: { "Wwb-Provider-Authorization": "(present)" }
}] });
await flush();
assert.equal(h.form().attributes["data-state"], "error");
assert.equal(h.field("owner").disabled, true);
assert.equal(h.field("owner").children[0].textContent, "Unavailable");
const retry = h.nodes().find(node => node.textContent === "Retry");
assert.equal(retry.hidden, false);
assert.equal(h.app.debugState.requestLog.length, 1);
assert.equal(h.app.debugContentView.getRequestCategory(h.app.debugState.requestLog[0]), "actions");
assert.equal(h.app.debugContentView.getRequestCategory({ name: "requestActionFindLookup" }), "actions");
retry.click(); retry.click();
await flush();
assert.equal(h.calls.length, 4);
h.calls[2].resolve({ ok: true, lookups: lookupData() });
h.calls[3].resolve({ ok: true, members: [] });
await flush();
assert.equal(h.form().attributes["data-state"], "ready");
assert.equal(h.field("owner").disabled, true);
assert.equal(h.field("owner").children[0].textContent, "No options available");
assert.equal(h.field("role").disabled, false);
});
test("closing or reopening the form discards late responses and waits for fresh options", async () => {
const h = optionsSetup();
h.open();
await flush();
const previous = h.form();
h.app.workloadActionForm.close(false);
h.open();
await flush();
h.completeLookup();
h.calls[1].resolve({ ok: true, members: [{ email: "old@example.invalid" }] });
await flush();
assert.notEqual(h.form(), previous);
assert.equal(h.form().attributes["data-state"], "loading");
h.calls[2].resolve({ ok: true, lookups: lookupData() });
h.calls[3].resolve({ ok: true, members: [{ email: "fresh@example.invalid" }] });
await flush();
assert.equal(h.field("owner").children[1].value, "fresh@example.invalid");
});
test("closing before authentication finishes prevents both options requests", async () => {
const h = optionsSetup();
let finish;
const auth = new Promise(resolve => { finish = resolve; });
h.app.authContentSession.requestConsumerTokenServiceOnce = () => auth;
h.open();
h.app.workloadActionForm.close(false);
finish({ workloadActions: { value: "synthetic" } });
await flush();
assert.equal(h.calls.length, 0);
});

View File

@@ -64,6 +64,45 @@ function contentFixture(send) {
return app;
}
test("saved response updates the workload badge, tooltip and matching cache without fetching", async () => {
const app = contentFixture(() => assert.fail("Save response must not trigger a GET"));
const writes = [];
app.activityCache.write = async (...args) => writes.push(args);
const products = { items: [{ RevnId: "123" }], cachedAt: 1000 };
app.productsContentState.opptyProductsByNumber.set("A", products);
app.productsContentState.opptyProductsByNumber.set("B", { items: [{ RevnId: "456" }] });
const action = { id: 77, name: "REVIEW", ownerEmail: "test@example.invalid", revenueId: 123, status: "COMPLETE" };
assert.equal(await app.opportunityWorkloadActions.applySavedAction("A", "123", action), true);
assert.equal(app.opportunityWorkloadActions.getSummary("A").state, "matched");
assert.equal(app.opportunityWorkloadActions.getSummary("A").actions.length, 1);
assert.equal(app.opportunityWorkloadActions.getSummary("B").actions.length, 0);
assert.ok(app.opportunityWorkloadActions.createBadge("A").title.includes("ID: 77"));
assert.deepEqual(writes[0].slice(0, 3), ["actions", "123", 1000]);
assert.equal(writes[0][3][0].id, 77);
await app.opportunityWorkloadActions.applySavedAction("A", "123", { action: { ...action, status: "OPEN" } });
assert.equal(app.opportunityWorkloadActions.getSummary("A").actions.length, 1);
assert.equal(app.opportunityWorkloadActions.getSummary("A").actions[0].status, "OPEN");
await app.opportunityWorkloadActions.applySavedAction("A", "123", { actions: [{ ...action, id: 78, ownerEmail: "other@example.invalid" }] });
assert.equal(app.opportunityWorkloadActions.getSummary("A").actions.length, 2);
assert.equal(await app.opportunityWorkloadActions.applySavedAction("B", "123", action), false);
assert.equal(await app.opportunityWorkloadActions.applySavedAction("A", "123", null), false);
});
test("saved Action uses its email rather than display name for green badge and removes plus", async () => {
const app = contentFixture(() => assert.fail("No follow-up GET"));
app.activityCache.write = async () => {};
app.productsContentState.opptyProductsByNumber.set("A", { items: [{ RevnId: "123" }] });
const saved = { id: 90, owner: "Test User", ownerEmail: " TEST@example.invalid ", name: "REVIEW" };
await app.opportunityWorkloadActions.applySavedAction("A", "123", saved);
const badge = app.opportunityWorkloadActions.createBadge("A");
assert.equal(badge.attributes["data-state"], "matched");
assert.equal(badge.textContent, "1");
await app.opportunityWorkloadActions.applySavedAction("A", "123", { id: 90, owner: "Test User" }, { ownerEmail: "test@example.invalid" });
assert.equal(app.opportunityWorkloadActions.getSummary("A").state, "matched");
app.authSessionState.principal = "another@example.invalid";
assert.equal(app.opportunityWorkloadActions.getSummary("A").state, "missing");
});
test("Actions completion includes queued calls, shared pending records, failures and cache writes", async () => {
const pending = [];
let finishWrite;