mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-09 22:31:14 +00:00
* fix(api): tell callers why a request was forbidden
The scope filter already computes an exact reason ("Missing API token
scope: skill:delete", "API token cannot access endpoint: /x") and the
access-denied handler discarded it, returning a bare "Forbidden" for
every case: missing scope, endpoint closed to API tokens, and paths
that simply don't exist. Clients cannot tell those apart, so they
guess — the published CLI reports every 403 as "token may lack
required scope", which sent us debugging token scopes for an hour when
the real causes were a revoked token and a mistyped namespace path.
The reason now rides in the response via a new error.forbidden.detail
message (en + zh), and is logged alongside the exception type.
Signed-off-by: Gal Eyal <gal.e@popai.health>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(api): safely expose API token denial reasons
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
---------
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
import { CliError } from '../../../src/shared/errors'
|
|
import { printResult, renderError } from '../../../src/shared/output'
|
|
|
|
describe('renderError', () => {
|
|
test('renders stable json error to stderr payload', () => {
|
|
const error = new CliError('authentication failed', 2, { registry: 'https://registry.example.com' })
|
|
expect(renderError(error, true)).toBe(JSON.stringify({
|
|
ok: false,
|
|
message: 'authentication failed',
|
|
exitCode: 2,
|
|
details: { registry: 'https://registry.example.com' }
|
|
}))
|
|
})
|
|
|
|
test('renders human error without stack trace', () => {
|
|
const error = new CliError('registry unreachable', 3, {
|
|
registry: 'https://registry.example.com',
|
|
requestId: 'req-610',
|
|
next: 'check network or pass --registry'
|
|
})
|
|
expect(renderError(error, false)).toBe([
|
|
'Error: registry unreachable',
|
|
'Context: registry https://registry.example.com',
|
|
'Request ID: req-610',
|
|
'Next: check network or pass --registry'
|
|
].join('\n'))
|
|
})
|
|
})
|
|
|
|
describe('printResult', () => {
|
|
test('returns string as-is in non-JSON mode', () => {
|
|
expect(printResult('hello', false)).toBe('hello')
|
|
})
|
|
|
|
test('wraps string in JSON envelope', () => {
|
|
expect(printResult('hello', true)).toBe(JSON.stringify({ ok: true, message: 'hello' }))
|
|
})
|
|
|
|
test('formats object in non-JSON mode', () => {
|
|
const result = printResult({ ok: true, handle: 'me', email: 'a@b' }, false)
|
|
expect(result).toBe('handle: me\nemail: a@b')
|
|
})
|
|
})
|