Updated the process for retrieving records from CRMServiceRequests
This commit is contained in:
@@ -4,7 +4,8 @@ const { loadApplication } = require("./support/application.cjs");
|
||||
const { response } = require("./support/characterization.cjs");
|
||||
|
||||
function setup(fetch) {
|
||||
const app = loadApplication("content", { 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 = () => {};
|
||||
|
||||
@@ -2,10 +2,10 @@ const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const { loadApplication } = require("./support/application.cjs");
|
||||
const response = data => new Response(JSON.stringify(data), { status: 200 });
|
||||
function setup(fetch) {
|
||||
function setup(fetch, searchFetch = async () => response({ items: [{ savedSearchNumber: "dynamic-sr-search" }] })) {
|
||||
const document = { createElement: () => ({ attributes: {}, append() {}, addEventListener() {}, setAttribute(key, value) { this.attributes[key] = value; } }) };
|
||||
document.createElementNS = (_, tag) => document.createElement(tag);
|
||||
const app = loadApplication("content", { fetch, document, Intl, Date });
|
||||
const app = loadApplication("content", { fetch: (url, options) => options.method === "GET" ? searchFetch(url, options) : fetch(url, options), document, Intl, Date });
|
||||
app.currentUser.allowsCrmActivities = () => true;
|
||||
app.debugContentView.renderDebugPanel = () => {};
|
||||
app.opportunitiesQueryState.periodRequestVersion = 1;
|
||||
@@ -15,6 +15,42 @@ const sr = (number, id = "SR-1") => ({ SrNumber: id, Title: "<script>synthetic</
|
||||
OpportunityName_Id_c: { OptyNumber: number }, Owner: { PersonFirstName: "Test", PersonLastName: "Owner",
|
||||
PrimaryEmail: { EmailAddress: "test@example.invalid" } }, DoNotCallFlag: false });
|
||||
|
||||
test("SR saved search runs once before pages and uses the first item, not the default", async () => {
|
||||
const calls = [];
|
||||
const app = setup(async (_url, options) => {
|
||||
const payload = JSON.parse(options.body); calls.push(payload);
|
||||
assert.equal(payload.copiedFrom, "queries/first-search");
|
||||
return response({ items: [sr("A")], count: 1, offset: payload.offset, hasMore: payload.offset === 0 });
|
||||
}, async (url, options) => {
|
||||
assert.equal(options.method, "GET");
|
||||
assert.equal(options.headers.Authorization, "Bearer synthetic");
|
||||
assert.equal(url, "https://eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/latest/custom-actions/queries?applications=461e5791-9f15-4cd1-b5cd-19e8480cbce4&entity=CRMServiceRequest&language=en&limit=-1&onlyData=true");
|
||||
calls.push("lookup");
|
||||
return response({ items: [{ savedSearchNumber: "first-search" }, { savedSearchNumber: "other", isDefault: true }] });
|
||||
});
|
||||
await app.fusionCrmServiceRequests.requestCrmServiceRequest("synthetic", 1);
|
||||
assert.equal(calls.length, 3); assert.equal(calls[0], "lookup");
|
||||
assert.equal(app.debugContentView.getRequestCategory(app.debugState.requestLog[0]), "serviceRequests");
|
||||
});
|
||||
|
||||
test("failed or empty SR search blocks POST; superseded search never starts pagination", async () => {
|
||||
for (const lookup of [
|
||||
async () => new Response("Denied", { status: 401 }),
|
||||
async () => response({ items: [] }),
|
||||
async () => response({ items: [{ savedSearchNumber: " " }] }),
|
||||
async () => new Response("<html>Login</html>")
|
||||
]) {
|
||||
const app = setup(() => assert.fail("No POST without a saved search"), lookup);
|
||||
await assert.rejects(app.fusionCrmServiceRequests.requestCrmServiceRequest("synthetic", 1));
|
||||
assert.equal(app.debugState.requestLog[0].status, "failed");
|
||||
}
|
||||
const app = setup(() => assert.fail("Stale POST"), async () => {
|
||||
app.opportunitiesQueryState.periodRequestVersion = 2;
|
||||
return response({ items: [{ savedSearchNumber: "first" }] });
|
||||
});
|
||||
assert.equal(await app.fusionCrmServiceRequests.requestCrmServiceRequest("synthetic", 1), null);
|
||||
});
|
||||
|
||||
test("SR badge shows reported hours, sums linked SRs and preserves missing values", async () => {
|
||||
for (const [values, expected] of [[[4], "4h"], [[0], "0h"], [[2, "1.5"], "3.5h"], [[0.1, 0.2], "0.3h"], [[null], "N/A"], [[4, null], "N/A"], [[""], "N/A"], [["invalid"], "N/A"]]) {
|
||||
const app = setup(async () => response({ items: values.map((value, index) => ({ ...sr("A", `SR-${index}`), ReportedHours_c: value })), hasMore: false }));
|
||||
@@ -79,7 +115,7 @@ test("CRM query preserves payload and pagination after opportunities, uses token
|
||||
assert.equal(options.headers.Preference, "transient");
|
||||
assert.deepEqual(payload, {
|
||||
aggregationResults: false, applications: ["ORACLE-ISS-APP"], onlyData: true, entity: "CRMServiceRequest",
|
||||
copiedFrom: "queries/c521309c-1310-49a0-9723-da2eb1988205",
|
||||
copiedFrom: "queries/dynamic-sr-search",
|
||||
q: { op: "$and", criteria: [
|
||||
{ op: "$in", attribute: "StatusCd", values: ["SVC_PENDING_DELIVERY"] },
|
||||
{ op: "$eq", attribute: "RecordSet", value: "ORA_SVC_MY_SR_TEAM_SRS" }
|
||||
@@ -90,8 +126,9 @@ test("CRM query preserves payload and pagination after opportunities, uses token
|
||||
keywordsFields: ["SrNumber", "ServiceNew_Id_c.RecordName", "CustomerAccount", "OpportunityName_Id_c", "OpportunityName_Id_c.OptyNumber", "Owner"],
|
||||
skipInValidFields: true, limit: 15, offset: 0
|
||||
});
|
||||
assert.equal(app.debugState.requestLog.length, 2);
|
||||
for (const log of app.debugState.requestLog) {
|
||||
assert.equal(app.debugState.requestLog.length, 3);
|
||||
assert.equal(app.debugState.requestLog[0].name, "requestSRSearchResources");
|
||||
for (const log of app.debugState.requestLog.slice(1)) {
|
||||
assert.equal(log.name, "resquestCrmServiceRequest");
|
||||
assert.equal(log.requestHeaders.Origin, "https://eeho.fa.us2.oraclecloud.com");
|
||||
assert.equal(log.requestHeaders.Preference, "transient");
|
||||
@@ -144,6 +181,7 @@ test("stale CRM results cannot overwrite a reopened modal or continue pagination
|
||||
let resolveOld;
|
||||
const app = setup(() => new Promise(resolve => { resolveOld = resolve; }));
|
||||
const old = app.opportunityCrmServiceRequests.load("token", 1);
|
||||
while (!resolveOld) await new Promise(resolve => setImmediate(resolve));
|
||||
app.opportunitiesQueryState.periodRequestVersion = 2;
|
||||
app.opportunityCrmServiceRequests.reset();
|
||||
app.fusionCrmServiceRequests.requestCrmServiceRequest = async () => [sr("NEW")];
|
||||
@@ -152,7 +190,7 @@ test("stale CRM results cannot overwrite a reopened modal or continue pagination
|
||||
await old;
|
||||
assert.equal(app.opportunityCrmServiceRequests.createBadge("NEW").textContent, "0h");
|
||||
assert.equal(app.opportunityCrmServiceRequests.createBadge("OLD").textContent, "0h");
|
||||
assert.equal(app.debugState.requestLog.length, 1);
|
||||
assert.equal(app.debugState.requestLog.length, 2);
|
||||
});
|
||||
|
||||
test("Reference changes share pending pagination and reuse completed CRM results", async () => {
|
||||
@@ -167,6 +205,7 @@ test("Reference changes share pending pagination and reuse completed CRM results
|
||||
app.opportunitiesQueryState.periodRequestVersion = 2;
|
||||
const second = app.opportunityCrmServiceRequests.load("token", 2);
|
||||
assert.equal(first, second);
|
||||
while (!finish) await new Promise(resolve => setImmediate(resolve));
|
||||
finish(response({ items: [sr("A")], count: 1, offset: 0, hasMore: true }));
|
||||
await second;
|
||||
app.opportunitiesQueryState.periodRequestVersion = 3;
|
||||
|
||||
@@ -250,44 +250,6 @@ test("Primary pillar lookup uses Session, orders enabled name/code options and r
|
||||
assert.equal((await loadBackground({ fetch, messageTimeout: 10000 }).dispatch({ type: "opportunitiesExtension.requestSrLookupPrimaryPillar" })).ok, false);
|
||||
}
|
||||
});
|
||||
for (const family of ["chrome", "firefox"]) {
|
||||
test(`${family}: 401 is returned to the caller without opening tabs`, async () => {
|
||||
let count = 0;
|
||||
const h = loadBackground({ family, fetch: async () => ++count < 3 ? response({}, 401) : response({ items: [] }) });
|
||||
const result = await h.dispatch({ type, srId: "123" });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.httpStatus, 401);
|
||||
assert.equal(count, 1);
|
||||
assert.equal(h.tabs.length, 0);
|
||||
assert.deepEqual(h.removedTabs, []);
|
||||
assert.deepEqual(result.debugRequests.map(entry => entry.httpStatus), [401]);
|
||||
});
|
||||
}
|
||||
test("concurrent 401 responses do not cause background tab recovery", async () => {
|
||||
let calls = 0;
|
||||
const h = loadBackground({ fetch: async url => {
|
||||
if (++calls <= 2) return response({}, 401);
|
||||
const id = new URL(url).searchParams.get("q");
|
||||
return response({ items: [{ ServiceRequest_SRToInternalSR_Tgt: [{ Title: id }] }] });
|
||||
} });
|
||||
const results = await Promise.all([h.dispatch({ type, srId: "1" }), h.dispatch({ type, srId: "2" })]);
|
||||
assert.equal(h.tabs.length, 0);
|
||||
assert.deepEqual(h.removedTabs, []);
|
||||
assert.ok(results.every(result => result.httpStatus === 401));
|
||||
});
|
||||
test("background never creates recovery tabs after an error", async () => {
|
||||
for (const status of [401, 500]) {
|
||||
let count = 0;
|
||||
const h = loadBackground({ fetch: async () => response({}, ++count === 1 ? 401 : status) });
|
||||
const result = await h.dispatch({ type, srId: "1" });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(count, 1);
|
||||
assert.deepEqual(h.removedTabs, []);
|
||||
}
|
||||
const h = loadBackground({ fetch: async () => response({}, 403) });
|
||||
assert.equal((await h.dispatch({ type, srId: "1" })).ok, false);
|
||||
assert.equal(h.tabs.length, 0);
|
||||
});
|
||||
for (const family of ["chrome", "firefox"]) {
|
||||
test(`${family}: SR detail GET uses exact scope and Session authentication`, async () => {
|
||||
const calls = [];
|
||||
|
||||
@@ -84,7 +84,7 @@ test("internal SR form validates Save, maps parent data, prevents duplicate post
|
||||
assert.ok(!h.walk(h.modal).includes(form));
|
||||
});
|
||||
|
||||
test("internal SR uses Session without TokenRelay and reports unavailable iframe host", async () => {
|
||||
test("internal SR uses Session and offers manual retry after 401 without automatic probes", async () => {
|
||||
const h = setup();
|
||||
let renewals = 0;
|
||||
const sent = [];
|
||||
@@ -106,6 +106,11 @@ test("internal SR uses Session without TokenRelay and reports unavailable iframe
|
||||
assert.equal(sent[0].token, undefined);
|
||||
assert.ok(sent.every(item => item.type === "opportunitiesExtension.requestInternalServiceRequest"));
|
||||
assert.ok(h.walk(h.panel()).some(node => node.attributes.role === "alert"));
|
||||
const retry = h.walk(h.panel()).find(node => node.attributes["aria-label"] === "Retry loading Internal Service Requests");
|
||||
assert.ok(retry);
|
||||
retry.listeners.click();
|
||||
await flush();
|
||||
assert.equal(sent.length, 2);
|
||||
});
|
||||
|
||||
function setup() {
|
||||
|
||||
14
tests/internalSrRecovery.test.cjs
Normal file
14
tests/internalSrRecovery.test.cjs
Normal file
@@ -0,0 +1,14 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const factory = vm.runInNewContext(fs.readFileSync('src/services/fusion/sr-details.js', 'utf8') + '\ncreateFusionSrDetails');
|
||||
test('Internal SR background returns 401 without opening any tab or retrying', async () => {
|
||||
let calls = 0;
|
||||
const api = factory({
|
||||
fetchJson: async () => { calls++; throw Object.assign(new Error('Unauthorized'), {httpStatus:401}); },
|
||||
tabsCreate: () => assert.fail('Internal SR must not open a tab')
|
||||
});
|
||||
await assert.rejects(api.requestInternalServiceRequest({srId:'123'}), /Unauthorized/);
|
||||
assert.equal(calls,1);
|
||||
});
|
||||
@@ -1,145 +1,69 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const fs = require("node:fs");
|
||||
const vm = require("node:vm");
|
||||
const path = require("node:path");
|
||||
const factory = vm.runInNewContext(`${fs.readFileSync(path.join(__dirname, "../src/features/opportunities/internal-sr-session.js"), "utf8")}\ncreateInternalSrSession`);
|
||||
function setup(query, options = {}) {
|
||||
let elapsed = 0;
|
||||
const frames = [], removed = [], delays = [], stopped = [];
|
||||
const body = { append(frame) { frame.initialSrc = frame.src; frames.push(frame); } };
|
||||
const document = { body, createElement(tag) {
|
||||
assert.equal(tag, "iframe");
|
||||
return { contentWindow: { stop() { stopped.push(true); } }, style: {}, attributes: {}, listeners: {}, setAttribute(k, v) { this.attributes[k] = v; },
|
||||
addEventListener(k, fn) { this.listeners[k] = fn; }, remove() { removed.push(this); } };
|
||||
} };
|
||||
const api = factory({ ...options, query, environment: { document, performance: { now: () => elapsed }, setTimeout: (fn, ms) => { delays.push(ms); elapsed += ms; return setImmediate(fn); } } });
|
||||
return { api, frames, removed, delays, stopped };
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const factory = vm.runInNewContext(fs.readFileSync('src/features/opportunities/internal-sr-session.js', 'utf8') + '\ncreateInternalSrSession');
|
||||
test('without a document, session recovery stops without polling', async () => {
|
||||
for (const result of [{ok:false,httpStatus:401}, {ok:false,debugRequests:[{httpStatus:401}]}, {ok:false,error:'Request failed with status 401.'}]) {
|
||||
let calls=0;
|
||||
const service=factory({query:async()=>{calls++; return result;},environment:{document:{createElement(){assert.fail('No iframe');}},setTimeout(){assert.fail('No polling');}}});
|
||||
await assert.rejects(service.request('123'), /Internal SR|status 401/);
|
||||
await new Promise(resolve=>setImmediate(resolve));
|
||||
assert.equal(calls,1);
|
||||
}
|
||||
});
|
||||
|
||||
function harness() {
|
||||
const timers = [], listeners = new Map();
|
||||
let appended = 0, removed = 0;
|
||||
const frame = {style:{},setAttribute(){},addEventListener(k,v){listeners.set(k,v);},removeEventListener(k){listeners.delete(k);},remove(){removed++;}};
|
||||
return { timers, frame, listeners, counts:()=>({appended,removed}), environment:{
|
||||
document:{createElement:()=>frame,body:{append(){appended++;}}},
|
||||
setTimeout(fn,delay){timers.push({fn,delay});}
|
||||
}};
|
||||
}
|
||||
|
||||
test("lookup requests recover together using the configured page and preserve each result", async () => {
|
||||
let authenticated = false;
|
||||
let calls = 0;
|
||||
const h = setup(async message => {
|
||||
if (++calls <= 4) return { ok: false, httpStatus: 401 };
|
||||
authenticated = true;
|
||||
return { ok: true, items: [message.type] };
|
||||
}, { sessionUrl: "https://eeho.fa.us2.oraclecloud.com/?view=Show%20Service%20Request", sessionLabel: "Service Request" });
|
||||
const types = ["pillar", "language", "platform", "services"];
|
||||
const results = await Promise.all(types.map(type => h.api.request({ type })));
|
||||
assert.deepEqual(results, types.map(type => [type]));
|
||||
assert.equal(authenticated, true);
|
||||
assert.equal(h.frames.length, 1);
|
||||
assert.match(h.frames[0].initialSrc, /Show%20Service%20Request$/);
|
||||
assert.equal(h.frames[0].title, "Service Request session initialization");
|
||||
assert.equal(h.removed.length, 1);
|
||||
assert.equal(h.stopped.length, 1);
|
||||
const flush = () => new Promise(resolve=>setImmediate(resolve));
|
||||
test('hidden iframe remains alive after load and closes only after successful session query',async()=>{
|
||||
const h=harness(); let calls=0;
|
||||
const api=factory({environment:h.environment,query:async()=>++calls<3?{ok:false,httpStatus:401}:{ok:true,items:[{SrId:1}]}});
|
||||
const first=api.request('123'); assert.equal(first,api.request(123));
|
||||
await flush();
|
||||
assert.equal(h.counts().appended,1);
|
||||
h.listeners.get('load')?.();
|
||||
assert.equal(h.counts().removed,0);
|
||||
assert.equal(calls,1);
|
||||
assert.equal(h.timers[0].delay,2000);
|
||||
h.timers.shift().fn(); await flush();
|
||||
assert.equal(h.counts().removed,0);
|
||||
assert.equal(h.timers[0].delay,4000);
|
||||
h.timers.shift().fn();
|
||||
assert.equal((await first)[0].SrId,1);
|
||||
assert.equal(h.counts().removed,1);
|
||||
});
|
||||
test("401 initializes a hidden iframe in the calling document and removes it on success", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ++calls < 3 ? { ok: false, httpStatus: 401 } : { ok: true, items: [{ SrId: 1 }] });
|
||||
const items = await h.api.request("1");
|
||||
assert.equal(items[0].SrId, 1);
|
||||
assert.equal(h.frames.length, 1);
|
||||
const frame = h.frames[0];
|
||||
assert.equal(frame.tabIndex, -1);
|
||||
assert.equal(frame.attributes["aria-hidden"], "true");
|
||||
assert.match(frame.style.cssText, /pointer-events:none/);
|
||||
assert.ok(!frame.attributes.sandbox.includes("allow-top-navigation"));
|
||||
assert.ok(frame.initialSrc.endsWith("&view=showisr"));
|
||||
assert.equal(frame.src, "about:blank");
|
||||
assert.equal(h.stopped.length, 1);
|
||||
assert.deepEqual(h.delays, [250]);
|
||||
assert.equal(h.removed[0], frame);
|
||||
});
|
||||
|
||||
test("an immediately available session stops the iframe without waiting for load or a timer", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ++calls === 1 ? { ok: false, httpStatus: 401 } : { ok: true, items: [] });
|
||||
await h.api.request("1");
|
||||
assert.equal(calls, 2);
|
||||
assert.equal(h.delays.length, 0);
|
||||
assert.equal(h.stopped.length, 1);
|
||||
assert.equal(h.removed.length, 1);
|
||||
});
|
||||
|
||||
test("lookup 401 errors from older background responses still initialize the session", async () => {
|
||||
for (const failure of [
|
||||
{ ok: false, error: "requestSrLookupLanguage failed with status 401." },
|
||||
{ ok: false, debugRequests: [{ httpStatus: 401 }] },
|
||||
{ ok: false, httpStatus: "401" }
|
||||
]) {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ++calls <= 2 ? failure : { ok: true, items: ["ready"] });
|
||||
assert.deepEqual(await h.api.request("language"), ["ready"]);
|
||||
assert.equal(h.frames.length, 1);
|
||||
assert.equal(h.removed.length, 1);
|
||||
test('persistent 401 is bounded to five recovery checks and removes iframe',async()=>{
|
||||
const h=harness(); let calls=0;
|
||||
const api=factory({environment:h.environment,query:async()=>{calls++;return {ok:false,httpStatus:401};}});
|
||||
const failed=assert.rejects(api.request('123'),/still unavailable/);
|
||||
for(const delay of [2000,4000,8000,12000,16000]) {
|
||||
await flush(); assert.equal(h.timers[0].delay,delay); h.timers.shift().fn();
|
||||
}
|
||||
await failed;
|
||||
assert.equal(calls,6);
|
||||
assert.equal(h.counts().removed,1);
|
||||
assert.equal(h.timers.length,0);
|
||||
});
|
||||
|
||||
test("a lookup that still returns 401 after shared recovery initializes its own session", async () => {
|
||||
const calls = new Map();
|
||||
const h = setup(async id => {
|
||||
const count = (calls.get(id) || 0) + 1;
|
||||
calls.set(id, count);
|
||||
return count <= (id === "language" ? 1 : 2)
|
||||
? { ok: false, httpStatus: 401 } : { ok: true, items: [id] };
|
||||
});
|
||||
assert.deepEqual(await Promise.all([h.api.request("language"), h.api.request("platform")]), [["language"], ["platform"]]);
|
||||
assert.equal(h.frames.length, 2);
|
||||
assert.equal(h.removed.length, 2);
|
||||
test('concurrent requests share work and explicit retry can recover', async () => {
|
||||
let calls=0, complete;
|
||||
const service=factory({query:()=>{calls++; return new Promise(resolve=>{complete=resolve;});}});
|
||||
const first=service.request('123'); const second=service.request(123);
|
||||
assert.equal(first,second); await Promise.resolve();
|
||||
complete({ok:false,httpStatus:401}); await assert.rejects(first);
|
||||
const retry=service.request('123'); await Promise.resolve(); complete({ok:true,items:[{SrId:'456'}]});
|
||||
assert.equal((await retry)[0].SrId,'456'); assert.equal(calls,2);
|
||||
});
|
||||
|
||||
test("cross-origin redirects still allow iframe removal when stop is blocked", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => {
|
||||
if (++calls === 1) return { ok: false, httpStatus: 401 };
|
||||
h.frames[0].contentWindow.stop = () => { throw new Error("Cross-origin"); };
|
||||
return { ok: true, items: [] };
|
||||
});
|
||||
await h.api.request("1");
|
||||
assert.equal(h.frames[0].src, "about:blank");
|
||||
assert.equal(h.removed.length, 1);
|
||||
});
|
||||
test("concurrent queries share one iframe without mixing SR results", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async id => ++calls <= 2 ? { ok: false, httpStatus: 401 } : { ok: true, items: [id] });
|
||||
const results = await Promise.all([h.api.request("1"), h.api.request("2")]);
|
||||
assert.deepEqual(results, [["1"], ["2"]]);
|
||||
assert.equal(h.frames.length, 1);
|
||||
assert.equal(h.removed.length, 1);
|
||||
});
|
||||
test("normal success does not mount an iframe; errors and timeout always clean it up", async () => {
|
||||
const success = setup(async () => ({ ok: true, items: [] }));
|
||||
await success.api.request("1");
|
||||
assert.equal(success.frames.length, 0);
|
||||
for (const status of [401, 500]) {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ({ ok: false, httpStatus: ++calls === 1 ? 401 : status }));
|
||||
await assert.rejects(h.api.request("1"));
|
||||
if (status === 401) {
|
||||
assert.ok(calls > 31);
|
||||
assert.equal(h.delays.reduce((sum, ms) => sum + ms, 0), 60000);
|
||||
} else assert.equal(calls, 2);
|
||||
assert.equal(h.removed.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
test("slow session initialization can succeed after the old thirty-attempt limit", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => ++calls < 45 ? { ok: false, httpStatus: 401 } : { ok: true, items: ["ready"] });
|
||||
assert.deepEqual(await h.api.request("1"), ["ready"]);
|
||||
assert.equal(calls, 45);
|
||||
assert.equal(h.removed.length, 1);
|
||||
assert.ok(h.delays.reduce((sum, ms) => sum + ms, 0) < 60000);
|
||||
});
|
||||
|
||||
test("iframe load alone never confirms authentication", async () => {
|
||||
let calls = 0;
|
||||
const h = setup(async () => {
|
||||
if (++calls > 1) h.frames[0].listeners.load();
|
||||
return { ok: false, httpStatus: 401 };
|
||||
});
|
||||
await assert.rejects(h.api.request("1"), /HTTP 401.*load event, but authentication was not confirmed/);
|
||||
assert.equal(h.removed.length, 1);
|
||||
test('other errors do not retry and successful empty results are preserved', async()=>{
|
||||
const failed=factory({query:async()=>({ok:false,error:'Forbidden',httpStatus:403})});
|
||||
await assert.rejects(failed.request('123'),/Forbidden/);
|
||||
const ok=factory({query:async()=>({ok:true,items:[]})}); assert.equal((await ok.request('123')).length,0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user