@@ -276,9 +276,47 @@ test("normalizes Claude images and attachment cache behavior", async () => {
276276 assert . equal ( first , "https://jieli.example.test/attachments/cached.png" ) ;
277277 assert . equal ( second , "https://jieli.example.test/attachments/cached.png" ) ;
278278 assert . deepEqual ( calls , [ imagePath ] ) ;
279+
280+ const failingCalls = [ ] ;
281+ const failingUpload = async ( path ) => {
282+ failingCalls . push ( path ) ;
283+ throw new Error ( "backend is down" ) ;
284+ } ;
285+ const otherImage = join ( tmp , "2.png" ) ;
286+ writeFileSync ( otherImage , Buffer . from ( [ 0x89 , 0x50 , 0x4e , 0x47 , 0x0d , 0x0a ] ) ) ;
287+ await assert . rejects ( ( ) => runtime . uploadAttachmentCached ( otherImage , "https://jieli.example.test/" , "secret" , failingUpload ) , / b a c k e n d i s d o w n / ) ;
288+ await assert . rejects ( ( ) => runtime . uploadAttachmentCached ( otherImage , "https://jieli.example.test/" , "secret" , failingUpload ) , / b a c k e n d i s d o w n / ) ;
289+ assert . deepEqual ( failingCalls , [ otherImage , otherImage ] ) ;
279290 } ) ;
280291} ) ;
281292
293+ test ( "keeps the existing image label when the uploader fails instead of inserting a placeholder" , async ( ) => {
294+ const tmp = makeTempDir ( ) ;
295+ const imagePath = join ( tmp , "1.png" ) ;
296+ writeFileSync ( imagePath , Buffer . from ( [ 0x89 , 0x50 , 0x4e , 0x47 ] ) ) ;
297+ const transcript = join ( tmp , "session.jsonl" ) ;
298+ writeJsonl ( transcript , [
299+ {
300+ type : "user" ,
301+ uuid : "u-image-fallback" ,
302+ sessionId : "cc-image-fallback" ,
303+ message : { role : "user" , content : [ { type : "text" , text : "我测试下图片:\n\n[Image #1]\n你看到了什么" } , { type : "image" , source : imagePath } ] } ,
304+ } ,
305+ ] ) ;
306+
307+ const payload = await runtime . buildPayloadFromHook (
308+ { session_id : "cc-image-fallback" , transcript_path : transcript , cwd : "/Users/alice/work/jieli" } ,
309+ "https://jieli.example.test" ,
310+ async ( ) => {
311+ throw new Error ( "backend is down" ) ;
312+ } ,
313+ ) ;
314+
315+ assert . equal ( payload . thread . messages . length , 1 ) ;
316+ assert . equal ( payload . thread . messages [ 0 ] . content , "我测试下图片:\n\n[Image #1]\n你看到了什么" ) ;
317+ assert . doesNotMatch ( JSON . stringify ( payload ) , / \[ I m a g e u n a v a i l a b l e \] / ) ;
318+ } ) ;
319+
282320test ( "handles Claude model aliases, local command noise, loaded skills, and split assistant messages" , async ( ) => {
283321 const tmp = makeTempDir ( ) ;
284322 const transcript = join ( tmp , "session.jsonl" ) ;
@@ -361,6 +399,23 @@ test("configuration, upload, lock, and transcript flush helpers match hook behav
361399 assert . deepEqual ( runtime . buildMissingConfigHookResponse ( "userpromptsubmit" , [ "JIELI_API_KEY" ] ) , { } ) ;
362400 } ) ;
363401
402+ const settingsHome = makeTempDir ( ) ;
403+ mkdirSync ( join ( settingsHome , ".config" , "jieli" ) , { recursive : true } ) ;
404+ writeFileSync (
405+ join ( settingsHome , ".config" , "jieli" , "settings.json" ) ,
406+ JSON . stringify ( { api_key : "settings-key" , base_url : "https://settings.example.test/" } ) ,
407+ "utf8" ,
408+ ) ;
409+ await withEnv ( { HOME : settingsHome , JIELI_API_KEY : undefined , CLAUDE_PLUGIN_OPTION_API_KEY : undefined , JIELI_BASE_URL : undefined } , async ( ) => {
410+ assert . deepEqual ( runtime . missingConfigVars ( ) , [ ] ) ;
411+ assert . equal ( runtime . requiredEnv ( "JIELI_API_KEY" , "CLAUDE_PLUGIN_OPTION_API_KEY" ) , "settings-key" ) ;
412+ assert . equal ( runtime . optionalEnv ( "JIELI_BASE_URL" , "CLAUDE_PLUGIN_OPTION_BASE_URL" ) , "https://settings.example.test" ) ;
413+ } ) ;
414+ await withEnv ( { HOME : settingsHome , JIELI_API_KEY : "env-key" , JIELI_BASE_URL : "https://env.example.test/" } , async ( ) => {
415+ assert . equal ( runtime . requiredEnv ( "JIELI_API_KEY" , "CLAUDE_PLUGIN_OPTION_API_KEY" ) , "env-key" ) ;
416+ assert . equal ( runtime . optionalEnv ( "JIELI_BASE_URL" , "CLAUDE_PLUGIN_OPTION_BASE_URL" ) , "https://env.example.test/" ) ;
417+ } ) ;
418+
364419 const { server, state } = createMockJieliServer ( ) ;
365420 const baseUrl = await listen ( server ) ;
366421 state . baseUrl = baseUrl ;
0 commit comments