Skip to content

Commit 7fc81b6

Browse files
committed
test: enhance uploadCoverageFiles tests with error handling and context variations
1 parent c277c36 commit 7fc81b6

1 file changed

Lines changed: 180 additions & 1 deletion

File tree

src/__tests__/uploader.test.ts

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)