mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
feat(ui): try to detect if API requests are intercepted by an auth proxy
This commit is contained in:
@@ -8,17 +8,29 @@ const CommonOptions = {
|
||||
};
|
||||
|
||||
const FetchRetryConfig = {
|
||||
retries: 5,
|
||||
minTimeout: 1000,
|
||||
retries: 10,
|
||||
minTimeout: 2000,
|
||||
maxTimeout: 5000
|
||||
};
|
||||
|
||||
const FetchGet = async (uri, options, retryOptions) =>
|
||||
await promiseRetry(
|
||||
(retry, number) =>
|
||||
fetch(uri, merge({}, { method: "GET" }, CommonOptions, options)).catch(
|
||||
retry
|
||||
),
|
||||
fetch(
|
||||
uri,
|
||||
merge(
|
||||
{},
|
||||
{
|
||||
method: "GET",
|
||||
mode:
|
||||
number <= Math.round(FetchRetryConfig.retries * 0.8)
|
||||
? "cors"
|
||||
: "no-cors"
|
||||
},
|
||||
CommonOptions,
|
||||
options
|
||||
)
|
||||
).catch(retry),
|
||||
FetchRetryConfig
|
||||
);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("Fetch", () => {
|
||||
};
|
||||
|
||||
const methodOptions = {
|
||||
FetchGet: { method: "GET" },
|
||||
FetchGet: { method: "GET", mode: "cors" },
|
||||
FetchPost: { method: "POST" },
|
||||
FetchDelete: { method: "DELETE" }
|
||||
};
|
||||
@@ -59,4 +59,24 @@ describe("Fetch", () => {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
it("FetchGet switches to no-cors after 80% failures", async () => {
|
||||
fetch.mockReject(new Error("Fetch error"));
|
||||
|
||||
const request = FetchGet("http://example.com", {});
|
||||
await expect(request).rejects.toThrow("Fetch error");
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(11);
|
||||
expect(fetch).toHaveBeenCalledWith("http://example.com", {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
mode: "no-cors",
|
||||
redirect: "follow"
|
||||
});
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
expect(fetch.mock.calls[i][1]).toMatchObject({
|
||||
mode: i < 8 ? "cors" : "no-cors"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -174,9 +174,15 @@ class AlertStore {
|
||||
{
|
||||
totalAlerts: 0,
|
||||
version: "unknown",
|
||||
upgradeNeeded: false
|
||||
upgradeNeeded: false,
|
||||
reloadNeeded: false,
|
||||
setReloadNeeded() {
|
||||
this.reloadNeeded = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
setReloadNeeded: action
|
||||
},
|
||||
{},
|
||||
{ name: "API response info" }
|
||||
);
|
||||
|
||||
@@ -270,6 +276,13 @@ class AlertStore {
|
||||
|
||||
return FetchGet(alertsURI, {})
|
||||
.then(result => {
|
||||
// we're sending requests with mode=cors so the response should also be type=cors
|
||||
// after a few failures in the retry loop we will switch to no-cors
|
||||
// if that request comes back as type=opaque then we might be getting
|
||||
// redirected by an auth proxy
|
||||
if (result.type === "opaque") {
|
||||
this.info.setReloadNeeded();
|
||||
}
|
||||
this.status.setProcessing();
|
||||
return result.json();
|
||||
})
|
||||
|
||||
@@ -375,7 +375,7 @@ describe("AlertStore.fetch", () => {
|
||||
const store = new AlertStore([]);
|
||||
await expect(store.fetch()).resolves.toHaveProperty("error");
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(6);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(11);
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Failure);
|
||||
expect(store.info.version).toBe("unknown");
|
||||
// there should be a trace of the error
|
||||
@@ -388,7 +388,7 @@ describe("AlertStore.fetch", () => {
|
||||
|
||||
fetch.mockReject(new Error("Fetch error"));
|
||||
await expect(store.fetch()).resolves.toHaveProperty("error");
|
||||
expect(global.fetch).toHaveBeenCalledTimes(6);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(11);
|
||||
});
|
||||
|
||||
it("fetch() retry counter is reset after successful fetch", async () => {
|
||||
@@ -397,16 +397,34 @@ describe("AlertStore.fetch", () => {
|
||||
|
||||
fetch.mockReject(new Error("Fetch error"));
|
||||
await expect(store.fetch()).resolves.toHaveProperty("error");
|
||||
expect(global.fetch).toHaveBeenCalledTimes(6);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(11);
|
||||
|
||||
const response = EmptyAPIResponse();
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
await expect(store.fetch()).resolves.toBeUndefined();
|
||||
expect(global.fetch).toHaveBeenCalledTimes(7);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(12);
|
||||
|
||||
fetch.mockReject(new Error("Fetch error"));
|
||||
await expect(store.fetch()).resolves.toHaveProperty("error");
|
||||
expect(global.fetch).toHaveBeenCalledTimes(13);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(23);
|
||||
});
|
||||
|
||||
it("fetch() reloads the page after if auth middleware is detected", async () => {
|
||||
jest.spyOn(console, "trace").mockImplementation(() => {});
|
||||
|
||||
const store = new AlertStore(["label=value"]);
|
||||
|
||||
jest.spyOn(global, "fetch").mockImplementation(async () =>
|
||||
Promise.resolve({
|
||||
type: "opaque",
|
||||
body: "auth needed",
|
||||
json: jest.fn(() => EmptyAPIResponse())
|
||||
})
|
||||
);
|
||||
|
||||
await expect(store.fetch()).resolves.toBeUndefined();
|
||||
|
||||
expect(store.info.reloadNeeded).toBe(true);
|
||||
});
|
||||
|
||||
it("unapplied filters are marked as applied on fetch error", async () => {
|
||||
|
||||
Reference in New Issue
Block a user