This is an automated email from the ASF dual-hosted git repository.
dengliming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new 0177c9c7 fix: handle failed download responses (#666)
0177c9c7 is described below
commit 0177c9c7e839cca237da95bed16f78e5240e0e03
Author: Efe <[email protected]>
AuthorDate: Thu Oct 1 10:31:36 2026 +0300
fix: handle failed download responses (#666)
* fix: handle failed download responses
Assisted-by: Codex (GPT-5)
Signed-off-by: Efe Gökdemir <[email protected]>
* fix: avoid download store import cycle
Assisted-by: Codex (GPT-5)
Signed-off-by: Efe Gökdemir <[email protected]>
* test: mock download error dependencies
Assisted-by: Codex (GPT-5)
Signed-off-by: Efe Gökdemir <[email protected]>
---------
Signed-off-by: Efe Gökdemir <[email protected]>
---
src/models/common.test.js | 9 ++++++++-
src/utils/download.js | 31 ++++++++++++++++++++++++++++++-
src/utils/download.test.js | 27 +++++++++++++++++++++++++++
src/utils/request.js | 18 +++++++++++-------
4 files changed, 76 insertions(+), 9 deletions(-)
diff --git a/src/models/common.test.js b/src/models/common.test.js
index ea237559..e80a8cb7 100644
--- a/src/models/common.test.js
+++ b/src/models/common.test.js
@@ -24,10 +24,17 @@ jest.mock("antd", () => ({
success: jest.fn(),
warn: jest.fn(),
},
+ notification: {
+ error: jest.fn(),
+ },
}));
jest.mock("dva/fetch", () => jest.fn());
-jest.mock("../utils/request", () => jest.fn());
+jest.mock("../utils/request", () => ({
+ __esModule: true,
+ default: jest.fn(),
+ handleUnauthorized: jest.fn(),
+}));
document.body.innerHTML = '<div id="httpPath"></div>';
diff --git a/src/utils/download.js b/src/utils/download.js
index 9da35238..fa7c3cdc 100644
--- a/src/utils/download.js
+++ b/src/utils/download.js
@@ -16,6 +16,35 @@
*/
import fetch from "dva/fetch";
+import { notification } from "antd";
+import { handleUnauthorized } from "./request";
+
+async function checkStatus(response) {
+ if (response.ok) {
+ return response;
+ }
+
+ let errorResponse;
+ try {
+ errorResponse = await response.json();
+ } catch {
+ errorResponse = {};
+ }
+
+ const errorText =
+ (errorResponse && errorResponse.message) || response.statusText;
+ notification.error({
+ message: `请求错误 ${response.status}: ${response.url}`,
+ description: errorText,
+ });
+ if (response.status === 401) {
+ handleUnauthorized();
+ }
+ const error = new Error(errorText);
+ error.name = response.status;
+ error.response = response;
+ throw error;
+}
async function getDownloadErrorMessage(response) {
const fallbackMessage =
@@ -59,7 +88,7 @@ export default async function download(url, options) {
newOptions.headers = { ...newOptions.headers, "X-Access-Token": token };
}
try {
- const response = await fetch(url, newOptions);
+ const response = await checkStatus(await fetch(url, newOptions));
const disposition = response.headers.get("Content-Disposition");
const isAttachment =
response.ok &&
diff --git a/src/utils/download.test.js b/src/utils/download.test.js
index e72e9e76..cdc7f3b2 100644
--- a/src/utils/download.test.js
+++ b/src/utils/download.test.js
@@ -16,9 +16,13 @@
*/
import fetch from "dva/fetch";
+import { notification } from "antd";
+import { handleUnauthorized } from "./request";
import download from "./download";
jest.mock("dva/fetch", () => jest.fn());
+jest.mock("antd", () => ({ notification: { error: jest.fn() } }));
+jest.mock("./request", () => ({ handleUnauthorized: jest.fn() }));
const createHeaders = (headers = {}) => ({
get: jest.fn((name) => headers[name.toLowerCase()] || null),
@@ -44,7 +48,10 @@ describe("download", () => {
let clickSpy;
beforeEach(() => {
+ fetch.mockReset();
window.sessionStorage.clear();
+ notification.error.mockReset();
+ handleUnauthorized.mockReset();
Object.defineProperty(window.URL, "createObjectURL", {
configurable: true,
value: jest.fn(() => "blob:config"),
@@ -91,6 +98,26 @@ describe("download", () => {
expect(clickSpy).not.toHaveBeenCalled();
});
+ it("clears session and permissions for an unauthorized response", async ()
=> {
+ const response = createResponse({
+ ok: false,
+ status: 401,
+ statusText: "Unauthorized",
+ json: { code: 401, message: "Authentication failed", data: null },
+ });
+ fetch.mockResolvedValue(response);
+
+ await expect(download("/configs/export")).rejects.toThrow(
+ "Authentication failed",
+ );
+
+ expect(handleUnauthorized).toHaveBeenCalledTimes(1);
+ expect(notification.error).toHaveBeenCalledWith({
+ message: "请求错误 401: undefined",
+ description: "Authentication failed",
+ });
+ });
+
it.each([601, 500])(
"rejects an HTTP 200 Admin error with code %s",
async (code) => {
diff --git a/src/utils/request.js b/src/utils/request.js
index e8ee1586..8f84c35e 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -72,6 +72,16 @@ const checkResponseCode = (response) => {
}
};
+export const handleUnauthorized = () => {
+ const { dispatch } = store;
+ dispatch({
+ type: "login/logout",
+ });
+ dispatch({
+ type: "global/resetPermission",
+ });
+};
+
/**
* Requests a URL, returning a promise.
*
@@ -128,15 +138,9 @@ export default function request(url, options) {
}
})
.catch((e) => {
- const { dispatch } = store;
const status = e.name;
if (status === 401) {
- dispatch({
- type: "login/logout",
- });
- dispatch({
- type: "global/resetPermission",
- });
+ handleUnauthorized();
}
throw e;
});