110 lines
4.9 KiB
JavaScript
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);
|
|
});
|