refactor: improve network error and allow verbose output (#36923)

This commit is contained in:
Yunlu Wen authored and GitHub committed 2026-06-02 10:43:40 +00:00
1 parent b682591c7a
commit f19679b217
28 files changed
+339 -231

No files matched your search

+2 -2
View File
@@ -2,7 +2,7 @@ import type { StubServer } from '@test/fixtures/stub-server'
import { testHttpClient } from '@test/fixtures/http-client'
import { jsonResponder, startStubServer } from '@test/fixtures/stub-server'
import { afterEach, describe, expect, it } from 'vitest'
import { isBaseError } from '@/errors/base'
import { isHttpClientError } from '@/errors/base'
import { AccountSessionsClient } from './account-sessions.js'
const LIST_BODY = { page: 1, limit: 100, total: 0, has_more: false, data: [] }
@@ -70,7 +70,7 @@ describe('AccountSessionsClient.revoke', () => {
jsonResponder(404, { error: { code: 'not_found', message: 'session not found' } }, cap))
await expect(makeClient(stub.url).revoke('missing')).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 404,
err => isHttpClientError(err) && err.httpStatus === 404,
)
})
+2 -2
View File
@@ -2,7 +2,7 @@ import type { StubServer } from '@test/fixtures/stub-server'
import { testHttpClient } from '@test/fixtures/http-client'
import { jsonResponder, startStubServer } from '@test/fixtures/stub-server'
import { afterEach, describe, expect, it } from 'vitest'
import { isBaseError } from '@/errors/base'
import { isHttpClientError } from '@/errors/base'
import { AccountClient } from './account.js'
function makeClient(host: string): AccountClient {
@@ -35,7 +35,7 @@ describe('AccountClient.get', () => {
stub = await startStubServer(cap => jsonResponder(401, { error: 'expired' }, cap))
await expect(makeClient(stub.url).get()).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 401,
err => isHttpClientError(err) && err.httpStatus === 401,
)
})
})
+2 -2
View File
@@ -2,7 +2,7 @@ import type { StubServer } from '@test/fixtures/stub-server'
import { testHttpClient } from '@test/fixtures/http-client'
import { jsonResponder, startStubServer } from '@test/fixtures/stub-server'
import { afterEach, describe, expect, it } from 'vitest'
import { isBaseError } from '@/errors/base'
import { isHttpClientError } from '@/errors/base'
import { AppsClient } from './apps.js'
const LIST_BODY = { page: 1, limit: 20, total: 0, has_more: false, data: [] }
@@ -74,7 +74,7 @@ describe('AppsClient.list', () => {
stub = await startStubServer(cap => jsonResponder(403, { error: 'forbidden' }, cap))
await expect(makeClient(stub.url).list({ workspaceId: 'ws-1' })).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 403,
err => isHttpClientError(err) && err.httpStatus === 403,
)
})
})
+2 -2
View File
@@ -5,7 +5,7 @@ import { join } from 'node:path'
import { testHttpClient } from '@test/fixtures/http-client'
import { jsonResponder, startStubServer } from '@test/fixtures/stub-server'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { isBaseError } from '@/errors/base'
import { isHttpClientError } from '@/errors/base'
import { FileUploadClient } from './file-upload.js'
const UPLOADED = {
@@ -70,7 +70,7 @@ describe('FileUploadClient.upload', () => {
stub = await startStubServer(cap => jsonResponder(413, { error: 'file too large' }, cap))
await expect(makeClient(stub.url).upload('app-1', filePath)).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 413,
err => isHttpClientError(err) && err.httpStatus === 413,
)
})
})
+7 -7
View File
@@ -2,7 +2,7 @@ import type { StubServer } from '@test/fixtures/stub-server'
import { testHttpClient } from '@test/fixtures/http-client'
import { jsonResponder, startStubServer } from '@test/fixtures/stub-server'
import { afterEach, describe, expect, it } from 'vitest'
import { isBaseError } from '@/errors/base'
import { isHttpClientError } from '@/errors/base'
import { MembersClient } from './members.js'
import { WorkspacesClient } from './workspaces.js'
@@ -62,7 +62,7 @@ describe('MembersClient.list', () => {
stub = await startStubServer(cap => jsonResponder(403, { error: 'forbidden' }, cap))
await expect(makeClient(stub.url).list('ws-1')).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 403,
err => isHttpClientError(err) && err.httpStatus === 403,
)
})
@@ -70,7 +70,7 @@ describe('MembersClient.list', () => {
stub = await startStubServer(cap => jsonResponder(404, { error: 'not found' }, cap))
await expect(makeClient(stub.url).list('ws-missing')).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 404,
err => isHttpClientError(err) && err.httpStatus === 404,
)
})
})
@@ -117,7 +117,7 @@ describe('MembersClient.invite', () => {
await expect(
makeClient(stub.url).invite('ws-1', { email: 'u@e.com', role: 'normal' }),
).rejects.toSatisfy(err => isBaseError(err) && err.httpStatus === 400)
).rejects.toSatisfy(err => isHttpClientError(err) && err.httpStatus === 400)
})
})
@@ -142,7 +142,7 @@ describe('MembersClient.remove', () => {
stub = await startStubServer(cap => jsonResponder(400, { error: 'cannot operate self' }, cap))
await expect(makeClient(stub.url).remove('ws-1', 'm-1')).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 400,
err => isHttpClientError(err) && err.httpStatus === 400,
)
})
})
@@ -170,7 +170,7 @@ describe('MembersClient.updateRole', () => {
await expect(
makeClient(stub.url).updateRole('ws-1', 'm-1', { role: 'admin' }),
).rejects.toSatisfy(err => isBaseError(err) && err.httpStatus === 400)
).rejects.toSatisfy(err => isHttpClientError(err) && err.httpStatus === 400)
})
})
@@ -209,7 +209,7 @@ describe('WorkspacesClient.switch (integration with stub)', () => {
const client = new WorkspacesClient(testHttpClient(stub.url, 'dfoa_test'))
await expect(client.switch('ws-x')).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 404,
err => isHttpClientError(err) && err.httpStatus === 404,
)
})
})
+4 -4
View File
@@ -1,5 +1,5 @@
import type { HttpClient } from '@/http/types'
import { BaseError } from '@/errors/base'
import { BaseError, HttpClientError } from '@/errors/base'
import { ErrorCode } from '@/errors/codes'
export const DEFAULT_CLIENT_ID = 'difyctl'
@@ -80,7 +80,7 @@ export class DeviceFlowApi {
if (res.status === 404)
throw versionSkew()
if (!res.ok) {
throw new BaseError({
throw new HttpClientError({
code: ErrorCode.Server4xxOther,
message: `device/code: HTTP ${res.status}`,
httpStatus: res.status,
@@ -133,8 +133,8 @@ export class DeviceFlowApi {
}
}
function versionSkew(): BaseError {
return new BaseError({
function versionSkew(): HttpClientError {
return new HttpClientError({
code: ErrorCode.UnsupportedEndpoint,
message: 'this Dify host does not implement the OAuth device flow',
httpStatus: 404,
+3 -3
View File
@@ -2,7 +2,7 @@ import type { StubServer } from '@test/fixtures/stub-server'
import { testHttpClient } from '@test/fixtures/http-client'
import { jsonResponder, startStubServer } from '@test/fixtures/stub-server'
import { afterEach, describe, expect, it } from 'vitest'
import { isBaseError } from '@/errors/base'
import { isHttpClientError } from '@/errors/base'
import { WorkspacesClient } from './workspaces.js'
// WorkspacesClient.switch is covered in members.test.ts; this file covers list().
@@ -30,14 +30,14 @@ describe('WorkspacesClient.list', () => {
expect(stub.captured.method).toBe('GET')
expect(stub.captured.url).toBe('/openapi/v1/workspaces')
expect(res.workspaces[0].id).toBe('ws-1')
expect(res.workspaces[0]?.id).toBe('ws-1')
})
it('maps 401 to a classified BaseError', async () => {
stub = await startStubServer(cap => jsonResponder(401, { error: 'expired' }, cap))
await expect(makeClient(stub.url).list()).rejects.toSatisfy(
err => isBaseError(err) && err.httpStatus === 401,
err => isHttpClientError(err) && err.httpStatus === 401,
)
})
})