const assert = require("node:assert/strict"); const { test } = require("node:test"); const { loadApplication } = require("./support/application.cjs"); test("failed preparation exposes Retry, deduplicates clicks and removes it on success", async () => { const host = { append() {} }; const nodes = []; const app = loadApplication("content", { document: { querySelector: () => host, getElementById: () => null, createElement: tag => { const node = { tag, children: [], setAttribute() {}, append(child) { this.children.push(child); }, addEventListener(_event, fn) { this.click = fn; }, set textContent(value) { this.text = value; this.children = []; } }; nodes.push(node); return node; } }, setTimeout: () => 1, clearTimeout() {} }); let attempts = 0; app.currentUser.ensure = async () => {}; app.currentUser.allowsCrmActivities = () => true; app.currentUser.getUserId = () => "test@example.invalid"; app.debugContentLog.appendBackgroundRequestLog = () => {}; app.platformContentMessages.sendRuntimeMessage = async message => { if (message.type === "opportunitiesExtension.requestResourceUser") return { ok: true }; return { ok: ++attempts > 1 }; }; await app.srSessionPreparation.ensure(); const retry = nodes.find(node => node.tag === "button"); assert.equal(retry.text, "Retry"); retry.click(); const pending = app.srSessionPreparation.retry(); assert.equal(app.srSessionPreparation.retry(), pending); await pending; assert.equal(attempts, 2); assert.equal(nodes[0].text, "SR session ready"); assert.equal(nodes[0].children.length, 0); await app.srSessionPreparation.retry(); assert.equal(attempts, 2); }); test("preparation runs once, shares pending work and reports loading and completion", async () => { const app = loadApplication("content"); const progress = []; let requests = 0; let resourceRequests = 0; let resolveUser, resolveSession; app.currentUser.ensure = () => new Promise(resolve => { resolveUser = resolve; }); app.currentUser.allowsCrmActivities = () => true; app.currentUser.getUserId = () => "test@example.invalid"; app.activityProgress.update = (...args) => progress.push(args); app.debugContentLog.appendBackgroundRequestLog = () => {}; app.platformContentMessages.sendRuntimeMessage = message => { if (message.type === "opportunitiesExtension.requestResourceUser") { resourceRequests += 1; assert.equal(message.emailAddress, "test@example.invalid"); assert.deepEqual(progress.at(-1), ["session", "complete"]); return Promise.resolve({ ok: true, resource: { resourcePartyId: 123 } }); } assert.equal(message.type, "opportunitiesExtension.prepareSrSession"); requests += 1; return new Promise(resolve => { resolveSession = resolve; }); }; const first = app.srSessionPreparation.ensure(); assert.equal(app.srSessionPreparation.ensure(), first); assert.equal(requests, 1, "SR session starts while SPA identity is pending"); assert.equal(resourceRequests, 0, "resource user still requires the identity"); resolveUser(); await new Promise(resolve => setImmediate(resolve)); assert.deepEqual(progress, [["session", "running"]]); resolveSession({ ok: true }); await first; await app.srSessionPreparation.ensure(); assert.equal(requests, 1); assert.equal(resourceRequests, 1); assert.deepEqual(progress.at(-1), ["session", "complete"]); }); test("failed parallel preparation does not auto-repeat or reject modal loading", async () => { for (const allowed of [false, true]) { const app = loadApplication("content"); let requests = 0; const progress = []; app.currentUser.ensure = async () => {}; app.currentUser.allowsCrmActivities = () => allowed; app.activityProgress.update = (...args) => progress.push(args); app.debugContentLog.appendBackgroundRequestLog = () => {}; app.platformContentMessages.sendRuntimeMessage = async () => { requests += 1; return { ok: false }; }; await app.srSessionPreparation.ensure(); await app.srSessionPreparation.ensure(); assert.equal(requests, 1); assert.deepEqual(progress, [["session", "running"], ["session", "error"]]); } });