Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions frontend/src/composables/__tests__/useAuthFetch.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,37 @@ describe('useAuthFetch', () => {
)
})

it('对象 detail: stringify 而非 [object Object]', async () => {
it('对象 detail: hint/error 当作 message · 原对象挂到 err.detail', async () => {
fetchMock.mockResolvedValueOnce(
new Response(
JSON.stringify({ detail: { error: 'fetch_failed', hint: '抓取失败,请改用粘贴正文模式' } }),
{ status: 400 }
)
)

const { authFetchJson } = useAuthFetch()
try {
await authFetchJson('/library/articles', { url: 'https://x.example' })
throw new Error('should have thrown')
} catch (err) {
expect(err.message).toBe('抓取失败,请改用粘贴正文模式')
expect(err.detail).toEqual({ error: 'fetch_failed', hint: '抓取失败,请改用粘贴正文模式' })
}
})

it('对象 detail 无 hint/error: 退回 JSON 串当 message · 仍挂 err.detail', async () => {
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({ detail: { code: 'X', reason: 'Y' } }), { status: 500 })
)

const { authFetchJson } = useAuthFetch()
await expect(authFetchJson('/api/weird', {})).rejects.toThrow(/"code":"X"/)
try {
await authFetchJson('/api/weird', {})
throw new Error('should have thrown')
} catch (err) {
expect(err.message).toMatch(/"code":"X"/)
expect(err.detail).toEqual({ code: 'X', reason: 'Y' })
}
})

it('network 错误路径: 透传 fetch 抛出的错误', async () => {
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/composables/useAuthFetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function useAuthFetch() {
})
if (!resp.ok) {
let detail = `HTTP ${resp.status}`
let structured = null
try {
const body = await resp.json()
const raw = body?.detail ?? body?.error
Expand All @@ -100,12 +101,17 @@ export function useAuthFetch() {
const parts = raw.map((d) => (d && typeof d === 'object' && d.msg) || JSON.stringify(d))
if (parts.length) detail = parts.join('; ')
} else if (raw != null && typeof raw === 'object') {
detail = JSON.stringify(raw)
// 结构化 detail(如 {error, hint}):message 取 hint/error 作可读文案,
// 同时把原对象挂到 err.detail,让调用方能按业务字段降级处理
structured = raw
detail = raw.hint || raw.error || JSON.stringify(raw)
}
} catch {
/* ignore parse error */
}
throw new Error(detail)
const err = new Error(detail)
if (structured) err.detail = structured
throw err
}
return resp.json()
}
Expand Down
Loading