mirror of
https://gitea.com/actions/setup-python.git
synced 2026-07-15 19:17:00 +00:00
Validate and retry manifest fetch to prevent silent failures (#1332)
* validate and retry manifest fetch * Refactor error handling in isRateLimitError function for improved clarity
This commit is contained in:
@@ -31,6 +31,10 @@ describe('getManifest', () => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should return manifest from repo', async () => {
|
||||
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
|
||||
const manifest = await getManifest();
|
||||
@@ -38,14 +42,70 @@ describe('getManifest', () => {
|
||||
});
|
||||
|
||||
it('should return manifest from URL if repo fetch fails', async () => {
|
||||
jest.useFakeTimers();
|
||||
(tc.getManifestFromRepo as jest.Mock).mockRejectedValue(
|
||||
new Error('Fetch failed')
|
||||
);
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: mockManifest
|
||||
});
|
||||
const promise = getManifest();
|
||||
await jest.runAllTimersAsync();
|
||||
const manifest = await promise;
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
});
|
||||
|
||||
it('should fall back to URL if repo returns a truncated/empty manifest', async () => {
|
||||
jest.useFakeTimers();
|
||||
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue([]);
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: mockManifest
|
||||
});
|
||||
const promise = getManifest();
|
||||
await jest.runAllTimersAsync();
|
||||
const manifest = await promise;
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
});
|
||||
|
||||
it('should retry on a transient invalid manifest and then succeed', async () => {
|
||||
jest.useFakeTimers();
|
||||
(tc.getManifestFromRepo as jest.Mock)
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce(mockManifest);
|
||||
const promise = getManifest();
|
||||
await jest.runAllTimersAsync();
|
||||
const manifest = await promise;
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
expect(tc.getManifestFromRepo as jest.Mock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should fail loudly when the manifest is truncated/empty on every source', async () => {
|
||||
jest.useFakeTimers();
|
||||
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue([]);
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: []
|
||||
});
|
||||
const promise = getManifest();
|
||||
// Prevent unhandled rejection before timers advance.
|
||||
const catchPromise = promise.catch(() => {});
|
||||
await jest.runAllTimersAsync();
|
||||
await catchPromise;
|
||||
await expect(promise).rejects.toThrow(
|
||||
'Failed to fetch the Python versions manifest'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not retry the API on a rate-limit error and fall back to URL immediately', async () => {
|
||||
const rateLimitError = Object.assign(new Error('API rate limit exceeded'), {
|
||||
httpStatusCode: 403
|
||||
});
|
||||
(tc.getManifestFromRepo as jest.Mock).mockRejectedValue(rateLimitError);
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: mockManifest
|
||||
});
|
||||
const manifest = await getManifest();
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
expect(tc.getManifestFromRepo as jest.Mock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user