Files
opportunities-extension/tests/activityRetries.test.cjs

110 lines
4.9 KiB
JavaScript

const assert = require("node:assert/strict");
const { test } = require("node:test");
const { loadApplication } = require("./support/application.cjs");
const { response } = require("./support/characterization.cjs");
function setup(fetch) {
const app = loadApplication("content", { fetch: (url, options) => options.method === "GET"
? response({ items: [{ savedSearchNumber: "dynamic-sr-search" }] }) : fetch(url, options) });
app.currentUser.allowsCrmActivities = () => true;
app.opportunitiesQueryState.periodRequestVersion = 1;
app.debugContentView.renderDebugPanel = () => {};
app.activityProgress.update = () => {};
app.productsContentJobs.renderProductsProgress = () => {};
app.productsContentJobs.queueProductsTableRender = () => {};
app.authContentSession.requestConsumerTokenServiceOnce = async () => ({ workloadActions: { value: "synthetic" } });
const actions = [];
app.platformContentMessages.sendRuntimeMessage = async message => {
actions.push(message);
return { ok: true, actions: [], hasNext: false };
};
async function cycle(id) {
app.productsContentState.currentProductsRequestId = id;
app.productsContentState.opptyProductsByNumber.set("A", { items: [{ RevnId: "123" }], cachedAt: Date.now(), fromCache: false });
app.productActivities.begin(id, "synthetic");
await app.productActivities.complete(id);
}
return { app, actions, cycle };
}
test("SR succeeds on fifth attempt, is shared in flight and reused across Reference/product reloads", async () => {
let calls = 0;
const h = setup(async () => {
calls += 1;
if (calls < 5) throw new Error("Synthetic offline");
return response({ items: [], hasMore: false });
});
const first = h.app.opportunityCrmServiceRequests.load("synthetic", 1);
assert.equal(h.app.opportunityCrmServiceRequests.load("synthetic", 1), first);
assert.equal(await first, true);
assert.equal(calls, 5);
await h.cycle("current-quarter");
h.app.opportunitiesQueryState.periodRequestVersion += 1;
await h.cycle("next-quarter");
assert.equal(calls, 5);
assert.equal(h.actions.length, 2);
assert.equal(h.app.debugState.requestLog.filter(entry => entry.name === "resquestCrmServiceRequest").length, 5);
h.app.opportunityCrmServiceRequests.reset();
await h.cycle("explicit-refresh");
assert.equal(calls, 6);
});
for (const failure of [() => new Response("Denied", { status: 403 }), () => new Response("<html>Login</html>")]) {
test("SR exhausting five attempts blocks Actions and preserves the last completed timestamp", async () => {
let calls = 0;
const h = setup(async () => { calls += 1; return failure(); });
h.app.productsContentState.productsLastCachedAt = 123;
await h.cycle("failed");
assert.equal(calls, 5);
assert.equal(h.actions.length, 0);
assert.equal(h.app.opportunityCrmServiceRequests.getSummary("A").state, "error");
assert.equal(h.app.productsContentState.productsLastCachedAt, 123);
h.app.authSessionState.principal = "synthetic@example.invalid";
assert.equal(h.app.opportunityWorkloadActions.getSummary("A").state, "error");
});
}
test("pagination retries only the failed SR page, with no duplicate records", async () => {
const offsets = [];
let failures = 0;
const h = setup(async (_url, options) => {
const { offset } = JSON.parse(options.body);
offsets.push(offset);
if (offset === 1 && failures++ < 2) return response({ invalid: true });
return response({ items: [{ SrNumber: `SR${offset}`, ReportedHours_c: 2, OpportunityName_Id_c: { OptyNumber: "A" } }],
offset, count: 1, hasMore: offset === 0 });
});
await h.cycle("paged");
assert.deepEqual(offsets, [0, 1, 1, 1]);
assert.equal(h.app.opportunityCrmServiceRequests.getSummary("A").reportedHours, 4);
assert.equal(h.actions.length, 1);
});
test("Actions retries each workload up to five times and stops as soon as it succeeds", async () => {
const h = setup(async () => response({ items: [], hasMore: false }));
let attempts = 0;
h.app.platformContentMessages.sendRuntimeMessage = async () => {
attempts += 1;
return attempts < 3 ? { ok: false, error: "Synthetic" } : { ok: true, actions: [], hasNext: false };
};
await h.cycle("success");
assert.equal(attempts, 3);
attempts = 0;
h.app.platformContentMessages.sendRuntimeMessage = async () => { attempts += 1; throw new Error("Synthetic"); };
await h.cycle("failed-actions");
assert.equal(attempts, 5);
});
test("an invalidated SR request does not continue retrying or launch Actions", async () => {
let reject, calls = 0;
const h = setup(() => { calls += 1; return new Promise((_resolve, fail) => { reject = fail; }); });
const pending = h.cycle("old");
await new Promise(resolve => setImmediate(resolve));
h.app.opportunityCrmServiceRequests.reset();
h.app.productsContentState.currentProductsRequestId = "new";
reject(new Error("Synthetic"));
await pending;
assert.equal(calls, 1);
assert.equal(h.actions.length, 0);
});