@@ -175,6 +175,7 @@ describe('uploader', () => {
175175 . mockRejectedValueOnce ( error )
176176 . mockRejectedValueOnce ( error )
177177 . mockResolvedValueOnce ( { status : 200 , data : { } } as AxiosResponse ) ;
178+ mockIsAxiosError . mockReturnValue ( false ) ;
178179
179180 await uploadCoverageFiles ( options ) ;
180181
@@ -255,8 +256,13 @@ describe('uploader', () => {
255256 files : [ { path : testFile , format : 'cobertura' } ] ,
256257 } ;
257258
259+ // Use an actual Error instance to ensure line 115's true branch is covered
258260 const error = new Error ( 'Persistent network error' ) ;
259- mockAxiosPost . mockRejectedValue ( error ) ;
261+ mockAxiosPost
262+ . mockRejectedValueOnce ( error )
263+ . mockRejectedValueOnce ( error )
264+ . mockRejectedValueOnce ( error ) ;
265+ mockIsAxiosError . mockReturnValue ( false ) ;
260266
261267 await expect ( uploadCoverageFiles ( options ) ) . rejects . toThrow (
262268 'Persistent network error'
@@ -358,5 +364,178 @@ describe('uploader', () => {
358364 // The form data should contain PR tags - we verify the call was made
359365 // The actual tag values are included in the FormData
360366 } ) ;
367+
368+ it ( 'should handle context without optional branch field' , async ( ) => {
369+ const noBranchContext : GitHubContext = {
370+ ...mockContext ,
371+ branch : undefined ,
372+ } ;
373+
374+ const options : UploadOptions = {
375+ ...baseOptions ,
376+ context : noBranchContext ,
377+ files : [ { path : testFile , format : 'cobertura' } ] ,
378+ } ;
379+
380+ mockAxiosPost . mockResolvedValueOnce ( { status : 200 , data : { } } as AxiosResponse ) ;
381+
382+ await uploadCoverageFiles ( options ) ;
383+
384+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 1 ) ;
385+ } ) ;
386+
387+ it ( 'should handle non-Error objects thrown during upload' , async ( ) => {
388+ const options : UploadOptions = {
389+ ...baseOptions ,
390+ files : [ { path : testFile , format : 'cobertura' } ] ,
391+ } ;
392+
393+ // Throw a string instead of an Error object to hit the non-Error branch
394+ mockAxiosPost
395+ . mockRejectedValueOnce ( 'string error' )
396+ . mockRejectedValueOnce ( 'string error' )
397+ . mockRejectedValueOnce ( 'string error' ) ;
398+ mockIsAxiosError . mockReturnValue ( false ) ;
399+
400+ await expect ( uploadCoverageFiles ( options ) ) . rejects . toThrow ( 'string error' ) ;
401+
402+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 3 ) ;
403+ expect ( mockCore . warning ) . toHaveBeenCalledWith (
404+ 'Upload attempt 1/3 failed: string error'
405+ ) ;
406+ } ) ;
407+
408+ it ( 'should handle null thrown during upload' , async ( ) => {
409+ const options : UploadOptions = {
410+ ...baseOptions ,
411+ files : [ { path : testFile , format : 'cobertura' } ] ,
412+ } ;
413+
414+ // Throw null to fully test the non-Error branch
415+ mockAxiosPost
416+ . mockRejectedValueOnce ( null )
417+ . mockRejectedValueOnce ( null )
418+ . mockRejectedValueOnce ( null ) ;
419+ mockIsAxiosError . mockReturnValue ( false ) ;
420+
421+ await expect ( uploadCoverageFiles ( options ) ) . rejects . toThrow ( 'null' ) ;
422+
423+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 3 ) ;
424+ } ) ;
425+
426+ it ( 'should handle axios error without response data' , async ( ) => {
427+ const options : UploadOptions = {
428+ ...baseOptions ,
429+ files : [ { path : testFile , format : 'cobertura' } ] ,
430+ } ;
431+
432+ // Create an axios error with 400 status but no response data
433+ const axiosError = {
434+ response : { status : 400 , data : undefined } ,
435+ message : 'Bad Request' ,
436+ isAxiosError : true ,
437+ } ;
438+ mockAxiosPost . mockRejectedValueOnce ( axiosError ) ;
439+ mockIsAxiosError . mockReturnValue ( true ) ;
440+
441+ await expect ( uploadCoverageFiles ( options ) ) . rejects . toThrow (
442+ 'Upload failed with status 400: Bad Request'
443+ ) ;
444+
445+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 1 ) ;
446+ } ) ;
447+
448+ it ( 'should handle axios error with 403 status and response data' , async ( ) => {
449+ const options : UploadOptions = {
450+ ...baseOptions ,
451+ files : [ { path : testFile , format : 'cobertura' } ] ,
452+ } ;
453+
454+ const axiosError = {
455+ response : { status : 403 , data : 'Invalid API key' } ,
456+ message : 'Forbidden' ,
457+ isAxiosError : true ,
458+ } ;
459+ mockAxiosPost . mockRejectedValueOnce ( axiosError ) ;
460+ mockIsAxiosError . mockReturnValue ( true ) ;
461+
462+ await expect ( uploadCoverageFiles ( options ) ) . rejects . toThrow (
463+ 'Upload failed with status 403: Invalid API key'
464+ ) ;
465+
466+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 1 ) ;
467+ } ) ;
468+
469+ it ( 'should use empty flags array without adding to event' , async ( ) => {
470+ const options : UploadOptions = {
471+ ...baseOptions ,
472+ files : [ { path : testFile , format : 'cobertura' } ] ,
473+ flags : [ ] ,
474+ } ;
475+
476+ mockAxiosPost . mockResolvedValueOnce ( { status : 200 , data : { } } as AxiosResponse ) ;
477+
478+ await uploadCoverageFiles ( options ) ;
479+
480+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 1 ) ;
481+ } ) ;
482+
483+ it ( 'should use unknown format when file has empty format string' , async ( ) => {
484+ const options : UploadOptions = {
485+ ...baseOptions ,
486+ files : [ { path : testFile , format : '' } ] ,
487+ } ;
488+
489+ mockAxiosPost . mockResolvedValueOnce ( { status : 200 , data : { } } as AxiosResponse ) ;
490+
491+ await uploadCoverageFiles ( options ) ;
492+
493+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 1 ) ;
494+ } ) ;
495+
496+ it ( 'should throw default error when response has non-2xx status without exception' , async ( ) => {
497+ const options : UploadOptions = {
498+ ...baseOptions ,
499+ files : [ { path : testFile , format : 'cobertura' } ] ,
500+ } ;
501+
502+ // Return non-2xx status without throwing - this makes lastError undefined
503+ mockAxiosPost
504+ . mockResolvedValueOnce ( { status : 500 , data : { } } as AxiosResponse )
505+ . mockResolvedValueOnce ( { status : 502 , data : { } } as AxiosResponse )
506+ . mockResolvedValueOnce ( { status : 503 , data : { } } as AxiosResponse ) ;
507+
508+ await expect ( uploadCoverageFiles ( options ) ) . rejects . toThrow (
509+ 'Upload failed after all retries'
510+ ) ;
511+
512+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 3 ) ;
513+ } ) ;
514+
515+ it ( 'should handle Error instance thrown during upload and preserve it' , async ( ) => {
516+ const options : UploadOptions = {
517+ ...baseOptions ,
518+ files : [ { path : testFile , format : 'cobertura' } ] ,
519+ } ;
520+
521+ // Create an actual Error instance to test the true branch of instanceof Error
522+ const error = new Error ( 'Actual Error instance' ) ;
523+ mockAxiosPost
524+ . mockRejectedValueOnce ( error )
525+ . mockRejectedValueOnce ( error )
526+ . mockRejectedValueOnce ( error ) ;
527+ mockIsAxiosError . mockReturnValue ( false ) ;
528+
529+ // The thrown error should be the exact same Error instance
530+ try {
531+ await uploadCoverageFiles ( options ) ;
532+ fail ( 'Expected an error to be thrown' ) ;
533+ } catch ( e ) {
534+ expect ( e ) . toBe ( error ) ;
535+ expect ( e ) . toBeInstanceOf ( Error ) ;
536+ }
537+
538+ expect ( mockAxiosPost ) . toHaveBeenCalledTimes ( 3 ) ;
539+ } ) ;
361540 } ) ;
362541} ) ;
0 commit comments