From 63bfba0bdbbb1a9daf932b60a121158bfadf889a Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Wed, 8 Apr 2026 18:38:33 +0800 Subject: [PATCH] fix: update how ky handle error (#34735) --- dev/start-docker-compose | 16 +++++++------- .../smoke/unauthenticated-entry.feature | 7 ++++++ .../step-definitions/common/auth.steps.ts | 7 ++++++ .../common/navigation.steps.ts | 4 ++++ e2e/features/support/hooks.ts | 6 ++++- e2e/features/support/world.ts | 12 ++++++++-- web/service/fetch.ts | 22 ++++++++++++++++++- 7 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 e2e/features/smoke/unauthenticated-entry.feature diff --git a/dev/start-docker-compose b/dev/start-docker-compose index aa4f66a6cf..1321c3210f 100755 --- a/dev/start-docker-compose +++ b/dev/start-docker-compose @@ -1,8 +1,8 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(dirname "$(realpath "$0")")" -ROOT="$(dirname "$SCRIPT_DIR")" - -cd "$ROOT/docker" -docker compose --env-file middleware.env -f docker-compose.middleware.yaml -p dify up -d +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +ROOT="$(dirname "$SCRIPT_DIR")" + +cd "$ROOT/docker" +docker compose --env-file middleware.env -f docker-compose.middleware.yaml -p dify up -d diff --git a/e2e/features/smoke/unauthenticated-entry.feature b/e2e/features/smoke/unauthenticated-entry.feature new file mode 100644 index 0000000000..a2783c1cba --- /dev/null +++ b/e2e/features/smoke/unauthenticated-entry.feature @@ -0,0 +1,7 @@ +@smoke @unauthenticated +Feature: Unauthenticated app console entry + Scenario: Redirect to the sign-in page when opening the apps console without logging in + Given I am not signed in + When I open the apps console + Then I should be redirected to the signin page + And I should see the "Sign in" button diff --git a/e2e/features/step-definitions/common/auth.steps.ts b/e2e/features/step-definitions/common/auth.steps.ts index bf03c2d8f4..bed35244c5 100644 --- a/e2e/features/step-definitions/common/auth.steps.ts +++ b/e2e/features/step-definitions/common/auth.steps.ts @@ -9,3 +9,10 @@ Given('I am signed in as the default E2E admin', async function (this: DifyWorld 'text/plain', ) }) + +Given('I am not signed in', async function (this: DifyWorld) { + this.attach( + 'Using a clean browser context without the shared authenticated storage state.', + 'text/plain', + ) +}) diff --git a/e2e/features/step-definitions/common/navigation.steps.ts b/e2e/features/step-definitions/common/navigation.steps.ts index b18ff035fa..28e6953d65 100644 --- a/e2e/features/step-definitions/common/navigation.steps.ts +++ b/e2e/features/step-definitions/common/navigation.steps.ts @@ -10,6 +10,10 @@ Then('I should stay on the apps console', async function (this: DifyWorld) { await expect(this.getPage()).toHaveURL(/\/apps(?:\?.*)?$/) }) +Then('I should be redirected to the signin page', async function (this: DifyWorld) { + await expect(this.getPage()).toHaveURL(/\/signin(?:\?.*)?$/) +}) + Then('I should see the {string} button', async function (this: DifyWorld, label: string) { await expect(this.getPage().getByRole('button', { name: label })).toBeVisible() }) diff --git a/e2e/features/support/hooks.ts b/e2e/features/support/hooks.ts index a6862d79f5..9e8c025ef8 100644 --- a/e2e/features/support/hooks.ts +++ b/e2e/features/support/hooks.ts @@ -46,7 +46,11 @@ BeforeAll(async () => { Before(async function (this: DifyWorld, { pickle }) { if (!browser) throw new Error('Shared Playwright browser is not available.') - await this.startAuthenticatedSession(browser) + const isUnauthenticatedScenario = pickle.tags.some((tag) => tag.name === '@unauthenticated') + + if (isUnauthenticatedScenario) await this.startUnauthenticatedSession(browser) + else await this.startAuthenticatedSession(browser) + this.scenarioStartedAt = Date.now() const tags = pickle.tags.map((tag) => tag.name).join(' ') diff --git a/e2e/features/support/world.ts b/e2e/features/support/world.ts index 15ab8daf16..bf63199107 100644 --- a/e2e/features/support/world.ts +++ b/e2e/features/support/world.ts @@ -25,12 +25,12 @@ export class DifyWorld extends World { this.pageErrors = [] } - async startAuthenticatedSession(browser: Browser) { + async startSession(browser: Browser, authenticated: boolean) { this.resetScenarioState() this.context = await browser.newContext({ baseURL, locale: defaultLocale, - storageState: authStatePath, + ...(authenticated ? { storageState: authStatePath } : {}), }) this.context.setDefaultTimeout(30_000) this.page = await this.context.newPage() @@ -44,6 +44,14 @@ export class DifyWorld extends World { }) } + async startAuthenticatedSession(browser: Browser) { + await this.startSession(browser, true) + } + + async startUnauthenticatedSession(browser: Browser) { + await this.startSession(browser, false) + } + getPage() { if (!this.page) throw new Error('Playwright page has not been initialized for this scenario.') diff --git a/web/service/fetch.ts b/web/service/fetch.ts index 1d92c0c62f..74ee7a9614 100644 --- a/web/service/fetch.ts +++ b/web/service/fetch.ts @@ -38,6 +38,26 @@ export type ResponseError = { status: number } +const createResponseFromHTTPError = (error: HTTPError): Response => { + const headers = new Headers(error.response.headers) + headers.delete('content-length') + + let body: BodyInit | null = null + if (typeof error.data === 'string') + body = error.data + else if (error.data !== undefined) + body = JSON.stringify(error.data) + + if (body !== null && !headers.has('content-type')) + headers.set('content-type', ContentType.json) + + return new Response(body, { + status: error.response.status, + statusText: error.response.statusText, + headers, + }) +} + const afterResponseErrorCode = (otherOptions: IOtherOptions): AfterResponseHook => { return async ({ response }) => { if (!/^([23])\d{2}$/.test(String(response.status))) { @@ -209,7 +229,7 @@ async function base(url: string, options: FetchOptionType = {}, otherOptions: } catch (error) { if (error instanceof HTTPError) - throw error.response.clone() + throw createResponseFromHTTPError(error) throw error }