@@ -25,10 +25,10 @@ const settings: Settings = {
2525 telemetry : false ,
2626} ;
2727
28- function jsonResponse ( body : unknown , status = 200 ) : Response {
28+ function jsonResponse ( body : unknown , status = 200 , headers : Record < string , string > = { } ) : Response {
2929 return new Response ( JSON . stringify ( body ) , {
3030 status,
31- headers : { "content-type" : "application/json" } ,
31+ headers : { "content-type" : "application/json" , ... headers } ,
3232 } ) ;
3333}
3434
@@ -73,25 +73,82 @@ test("普通 Key 优先使用已保存地址,再尝试 Workspace 专属地址
7373 } ) ,
7474 ) . toEqual ( [
7575 "https://stored.example.com/api/v1/models?page_no=1&page_size=1" ,
76- "https://llm-test-workspace.cn-beijing.maas.aliyuncs.com/api /v1/models?page_no=1&page_size=1 " ,
77- "https://llm-test-workspace.ap-southeast-1.maas.aliyuncs.com/api /v1/models?page_no=1&page_size=1 " ,
78- "https://llm-test-workspace.cn-hongkong.maas.aliyuncs.com/api /v1/models?page_no=1&page_size=1 " ,
79- "https://llm-test-workspace.ap-northeast-1.maas.aliyuncs.com/api /v1/models?page_no=1&page_size=1 " ,
80- "https://llm-test-workspace.eu-central-1.maas.aliyuncs.com/api /v1/models?page_no=1&page_size=1 " ,
81- "https://llm-test-workspace.us-east-1.maas.aliyuncs.com/api /v1/models?page_no=1&page_size=1 " ,
76+ "https://llm-test-workspace.cn-beijing.maas.aliyuncs.com/compatible-mode /v1/models" ,
77+ "https://llm-test-workspace.ap-southeast-1.maas.aliyuncs.com/compatible-mode /v1/models" ,
78+ "https://llm-test-workspace.cn-hongkong.maas.aliyuncs.com/compatible-mode /v1/models" ,
79+ "https://llm-test-workspace.ap-northeast-1.maas.aliyuncs.com/compatible-mode /v1/models" ,
80+ "https://llm-test-workspace.eu-central-1.maas.aliyuncs.com/compatible-mode /v1/models" ,
81+ "https://llm-test-workspace.us-east-1.maas.aliyuncs.com/compatible-mode /v1/models" ,
8282 "https://dashscope.aliyuncs.com/api/v1/models?page_no=1&page_size=1" ,
8383 "https://dashscope-intl.aliyuncs.com/api/v1/models?page_no=1&page_size=1" ,
8484 "https://dashscope-us.aliyuncs.com/api/v1/models?page_no=1&page_size=1" ,
8585 "https://cn-hongkong.dashscope.aliyuncs.com/api/v1/models?page_no=1&page_size=1" ,
8686 ] ) ;
8787} ) ;
8888
89+ test ( "已保存的 Workspace MaaS 地址使用 Compatible /models" , ( ) => {
90+ expect (
91+ candidateUrls ( "sk-ws-placeholder" , {
92+ storedBaseUrl : "https://ws-stored.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1" ,
93+ } ) ,
94+ ) . toEqual ( [
95+ "https://ws-stored.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/models" ,
96+ "https://dashscope.aliyuncs.com/api/v1/models?page_no=1&page_size=1" ,
97+ "https://dashscope-intl.aliyuncs.com/api/v1/models?page_no=1&page_size=1" ,
98+ "https://dashscope-us.aliyuncs.com/api/v1/models?page_no=1&page_size=1" ,
99+ "https://cn-hongkong.dashscope.aliyuncs.com/api/v1/models?page_no=1&page_size=1" ,
100+ ] ) ;
101+ } ) ;
102+
103+ test ( "Workspace 地域候选先于公共站点选择首个成功地域" , async ( ) => {
104+ const originalFetch = httpTransport . fetch ;
105+ const workspaceOrigin = "https://llm-test-workspace.ap-southeast-1.maas.aliyuncs.com" ;
106+ httpTransport . fetch = async ( input ) => {
107+ const url = requestUrl ( input ) ;
108+ if ( url === `${ workspaceOrigin } /compatible-mode/v1/models` ) {
109+ return jsonResponse ( { object : "list" , data : [ { id : "workspace-model" } ] } ) ;
110+ }
111+ if ( url . startsWith ( "https://dashscope-intl.aliyuncs.com/" ) ) {
112+ return jsonResponse ( { data : [ { id : "public-model" } ] } ) ;
113+ }
114+ const status = url . includes ( ".maas.aliyuncs.com/" ) ? 404 : 401 ;
115+ return jsonResponse ( { code : "Rejected" , message : "candidate rejected" } , status ) ;
116+ } ;
117+
118+ try {
119+ await expect (
120+ validateApiKey ( { identity, settings } , "sk-ws-placeholder" , {
121+ workspaceId : "llm-test-workspace" ,
122+ } ) ,
123+ ) . resolves . toEqual ( {
124+ baseUrl : workspaceOrigin ,
125+ kind : "ordinary" ,
126+ } ) ;
127+ } finally {
128+ httpTransport . fetch = originalFetch ;
129+ }
130+ } ) ;
131+
89132test ( "其他格式 Key 不限制端点类型" , ( ) => {
90133 expect (
91134 apiKeyValidationCandidates ( "custom-key-placeholder" , { } ) . map ( ( candidate ) => candidate . kind ) ,
92135 ) . toEqual ( [ "ordinary" , "ordinary" , "ordinary" , "ordinary" , "token-plan" , "token-plan" ] ) ;
93136} ) ;
94137
138+ test ( "未知格式 Key 指向 Token Plan host 时保留 Token Plan kind" , ( ) => {
139+ expect (
140+ apiKeyValidationCandidates ( "custom-key-placeholder" , {
141+ explicitBaseUrl : "https://token-plan.ap-southeast-1.maas.aliyuncs.com" ,
142+ } ) ,
143+ ) . toEqual ( [
144+ {
145+ baseUrl : "https://token-plan.ap-southeast-1.maas.aliyuncs.com" ,
146+ kind : "token-plan" ,
147+ modelsUrl : "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/models" ,
148+ } ,
149+ ] ) ;
150+ } ) ;
151+
95152test ( "显式 Base URL 只校验指定站点,并根据 Key 类型选择 /models 路径" , ( ) => {
96153 expect (
97154 candidateUrls ( "sk-sp-placeholder" , {
@@ -103,6 +160,11 @@ test("显式 Base URL 只校验指定站点,并根据 Key 类型选择 /models
103160 explicitBaseUrl : "https://workspace.example.com/compatible-mode/v1" ,
104161 } ) ,
105162 ) . toEqual ( [ "https://workspace.example.com/api/v1/models?page_no=1&page_size=1" ] ) ;
163+ expect (
164+ candidateUrls ( "sk-ws-placeholder" , {
165+ explicitBaseUrl : "https://ws-explicit.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1" ,
166+ } ) ,
167+ ) . toEqual ( [ "https://ws-explicit.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/models" ] ) ;
106168} ) ;
107169
108170test ( "Token Plan 多地区并行校验选择实际接受 Key 的国际站点" , async ( ) => {
@@ -140,15 +202,53 @@ test("Token Plan 多地区并行校验选择实际接受 Key 的国际站点", a
140202 }
141203} ) ;
142204
143- test ( "所有候选站点明确拒绝 Key 时返回 AUTH 校验失败 " , async ( ) => {
205+ test ( "所有候选站点拒绝 Key 时透传首个服务端错误 " , async ( ) => {
144206 const originalFetch = httpTransport . fetch ;
145- httpTransport . fetch = async ( ) =>
146- jsonResponse ( { code : "InvalidApiKey" , message : "invalid key" } , 401 ) ;
207+ httpTransport . fetch = async ( input ) => {
208+ const requestId = requestUrl ( input ) . includes ( "cn-beijing" )
209+ ? "req-cn-rejected"
210+ : "req-sg-rejected" ;
211+ return jsonResponse (
212+ { code : "InvalidApiKey" , message : "invalid key" , request_id : requestId } ,
213+ 401 ,
214+ ) ;
215+ } ;
147216
148217 try {
149218 const error = await captureValidationError ( "sk-sp-placeholder" ) ;
150- expect ( error . exitCode ) . toBe ( ExitCode . AUTH ) ;
151- expect ( error . message ) . toMatch ( / A P I k e y v a l i d a t i o n f a i l e d / ) ;
219+ expect ( error . exitCode ) . toBe ( ExitCode . GENERAL ) ;
220+ expect ( error . message ) . toBe ( "invalid key" ) ;
221+ expect ( error . api ) . toEqual ( {
222+ httpStatus : 401 ,
223+ apiCode : "InvalidApiKey" ,
224+ requestId : "req-cn-rejected" ,
225+ } ) ;
226+ } finally {
227+ httpTransport . fetch = originalFetch ;
228+ }
229+ } ) ;
230+
231+ test ( "显式 Base URL 返回 404 时保留状态和 request ID" , async ( ) => {
232+ const originalFetch = httpTransport . fetch ;
233+ httpTransport . fetch = async ( ) =>
234+ jsonResponse ( { code : "NotFound" , message : "models route not found" } , 404 , {
235+ "x-request-id" : "req-route-not-found" ,
236+ } ) ;
237+
238+ try {
239+ const error = await validateApiKey ( { identity, settings } , "sk-ws-placeholder" , {
240+ explicitBaseUrl : "https://ws-explicit.ap-southeast-1.maas.aliyuncs.com" ,
241+ } ) . catch ( ( caught ) => caught ) ;
242+ expect ( error ) . toBeInstanceOf ( BailianError ) ;
243+ expect ( error ) . toMatchObject ( {
244+ exitCode : ExitCode . GENERAL ,
245+ message : "models route not found" ,
246+ api : {
247+ httpStatus : 404 ,
248+ apiCode : "NotFound" ,
249+ requestId : "req-route-not-found" ,
250+ } ,
251+ } ) ;
152252 } finally {
153253 httpTransport . fetch = originalFetch ;
154254 }
@@ -157,11 +257,15 @@ test("所有候选站点明确拒绝 Key 时返回 AUTH 校验失败", async ()
157257test . each ( [
158258 { name : "5xx" , status : 503 , message : "temporary outage" } ,
159259 { name : "429" , status : 429 , message : "too many requests" } ,
160- ] ) ( "存在 $name 时校验结论不确定,不误报 Key 无效 " , async ( { status, message } ) => {
260+ ] ) ( "后续候选出现 $name 时优先透传该错误 " , async ( { status, message } ) => {
161261 const originalFetch = httpTransport . fetch ;
162262 httpTransport . fetch = async ( input ) => {
163- if ( requestUrl ( input ) . startsWith ( "https://dashscope.aliyuncs.com/" ) ) {
164- return jsonResponse ( { code : "TemporaryFailure" , message } , status ) ;
263+ const url = requestUrl ( input ) ;
264+ if ( url . startsWith ( "https://dashscope-intl.aliyuncs.com/" ) ) {
265+ return jsonResponse (
266+ { code : "TemporaryFailure" , message, request_id : `req-${ status } ` } ,
267+ status ,
268+ ) ;
165269 }
166270 return jsonResponse ( { code : "InvalidApiKey" , message : "invalid key" } , 401 ) ;
167271 } ;
@@ -170,6 +274,62 @@ test.each([
170274 const error = await captureValidationError ( "sk-placeholder" ) ;
171275 expect ( error . exitCode ) . toBe ( ExitCode . GENERAL ) ;
172276 expect ( error . message ) . toBe ( message ) ;
277+ expect ( error . api ) . toEqual ( {
278+ httpStatus : status ,
279+ apiCode : "TemporaryFailure" ,
280+ requestId : `req-${ status } ` ,
281+ } ) ;
282+ } finally {
283+ httpTransport . fetch = originalFetch ;
284+ }
285+ } ) ;
286+
287+ test ( "后续候选出现网络错误时优先透传该错误" , async ( ) => {
288+ const originalFetch = httpTransport . fetch ;
289+ const networkError = new TypeError ( "fetch failed" , {
290+ cause : Object . assign ( new Error ( "connection reset" ) , { code : "ECONNRESET" } ) ,
291+ } ) ;
292+ httpTransport . fetch = async ( input ) => {
293+ if ( requestUrl ( input ) . startsWith ( "https://dashscope-intl.aliyuncs.com/" ) ) {
294+ throw networkError ;
295+ }
296+ return jsonResponse ( { code : "InvalidApiKey" , message : "invalid key" } , 401 ) ;
297+ } ;
298+
299+ try {
300+ const error = await validateApiKey ( { identity, settings } , "sk-placeholder" , { } ) . catch (
301+ ( caught ) => caught ,
302+ ) ;
303+ expect ( error ) . toBe ( networkError ) ;
304+ } finally {
305+ httpTransport . fetch = originalFetch ;
306+ }
307+ } ) ;
308+
309+ test ( "同一优先级的 HTTP 错误保持候选顺序" , async ( ) => {
310+ const originalFetch = httpTransport . fetch ;
311+ httpTransport . fetch = async ( input ) => {
312+ const url = requestUrl ( input ) ;
313+ if ( url . startsWith ( "https://dashscope.aliyuncs.com/" ) ) {
314+ return jsonResponse (
315+ { code : "FirstRejected" , message : "first candidate rejected" , request_id : "req-first" } ,
316+ 401 ,
317+ ) ;
318+ }
319+ return jsonResponse (
320+ { code : "LaterRejected" , message : "later candidate rejected" , request_id : "req-later" } ,
321+ 404 ,
322+ ) ;
323+ } ;
324+
325+ try {
326+ const error = await captureValidationError ( "sk-placeholder" ) ;
327+ expect ( error . message ) . toBe ( "first candidate rejected" ) ;
328+ expect ( error . api ) . toEqual ( {
329+ httpStatus : 401 ,
330+ apiCode : "FirstRejected" ,
331+ requestId : "req-first" ,
332+ } ) ;
173333 } finally {
174334 httpTransport . fetch = originalFetch ;
175335 }
0 commit comments