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;
     });

Reply via email to