Consolidate JDK metadata retry handling (#1162)

* Consolidate JDK metadata retries

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a5e2549a-0d89-4c8f-b7f3-411ad21c8a06

* Expand distribution retry coverage

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a5e2549a-0d89-4c8f-b7f3-411ad21c8a06

---------

Copilot-Session: a5e2549a-0d89-4c8f-b7f3-411ad21c8a06
This commit is contained in:
Bruno Borges
2026-07-29 00:09:37 -04:00
committed by GitHub
parent e1ce3a3428
commit 5894ef6b27
8 changed files with 792 additions and 213 deletions
@@ -605,6 +605,31 @@ describe('setupJava', () => {
expect(spyCoreSetOutput).not.toHaveBeenCalled();
});
it('should not repeat version resolution when downloadTool fails', async () => {
mockJavaBase = new EmptyJavaBase({
version: '11',
architecture: 'x86',
packageType: 'jdk',
checkLatest: false,
forceDownload: true
});
const findPackageForDownload = jest.fn(async () => ({
version: '11.0.9',
url: 'https://example.com/jdk.tar.gz'
}));
const downloadError = new Error('download failed');
const downloadTool = jest.fn(async () => {
throw downloadError;
});
mockJavaBase['findPackageForDownload'] = findPackageForDownload;
mockJavaBase['downloadTool'] = downloadTool;
await expect(mockJavaBase.setupJava()).rejects.toBe(downloadError);
expect(findPackageForDownload).toHaveBeenCalledTimes(1);
expect(downloadTool).toHaveBeenCalledTimes(1);
});
it.each([
[
{
@@ -1,6 +1,38 @@
import {getJavaDistribution} from '../../src/distributions/distribution-factory.js';
import {RetryingHttpClient} from '../../src/retrying-http-client.js';
describe('getJavaDistribution', () => {
it.each([
'adopt',
'adopt-hotspot',
'adopt-openj9',
'temurin',
'zulu',
'liberica',
'liberica-nik',
'microsoft',
'semeru',
'corretto',
'oracle',
'dragonwell',
'sapmachine',
'graalvm',
'graalvm-community',
'jetbrains',
'kona',
'oracle-openjdk'
])('uses the shared retrying HTTP client for %s', distributionName => {
const distribution = getJavaDistribution(distributionName, {
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
expect(distribution).not.toBeNull();
expect(distribution!['http']).toBeInstanceOf(RetryingHttpClient);
});
it("rejects java-package 'jdk+jmods' for non-Temurin distributions", () => {
expect(() =>
getJavaDistribution('zulu', {
@@ -9,7 +9,9 @@ import {
afterAll
} from '@jest/globals';
import https from 'https';
import {HttpClient} from '@actions/http-client';
import {HttpClient, HttpClientResponse} from '@actions/http-client';
import type {IncomingMessage} from 'http';
import {Readable} from 'stream';
import manifestData from '../data/jetbrains.json' with {type: 'json'};
import os from 'os';
@@ -44,6 +46,18 @@ jest.unstable_mockModule('@actions/core', () => ({
const core = await import('@actions/core');
const {JetBrainsDistribution} =
await import('../../src/distributions/jetbrains/installer.js');
const {RetryingHttpClient} = await import('../../src/retrying-http-client.js');
function response(
statusCode: number,
body = '',
headers: IncomingMessage['headers'] = {}
): HttpClientResponse {
const message = Readable.from([Buffer.from(body)]) as IncomingMessage;
message.statusCode = statusCode;
message.headers = headers;
return new HttpClientResponse(message);
}
describe('getAvailableVersions', () => {
let spyHttpClient: any;
@@ -95,6 +109,42 @@ describe('getAvailableVersions', () => {
os.platform() === 'win32' ? manifestData.length : manifestData.length + 2;
expect(availableVersions.length).toBe(length);
}, 10_000);
it('retries a GitHub rate limit using Retry-After', async () => {
spyHttpClient.mockRestore();
const sleep = jest.fn(async () => undefined);
const requestRaw = jest
.spyOn(HttpClient.prototype, 'requestRaw')
.mockResolvedValueOnce(response(429, '', {'retry-after': '2'}))
.mockResolvedValueOnce(response(200, '[]'))
.mockResolvedValueOnce(response(200))
.mockResolvedValueOnce(response(200));
const distribution = new JetBrainsDistribution({
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['http'] = new RetryingHttpClient('test', {
sleep,
random: () => 0
});
const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).toHaveLength(2);
expect(requestRaw).toHaveBeenCalledTimes(4);
expect(requestRaw.mock.calls[0][0].options.path).toBe(
requestRaw.mock.calls[1][0].options.path
);
expect(requestRaw.mock.calls[0][0].options.path).toContain(
'/repos/JetBrains/JetBrainsRuntime/releases'
);
expect(sleep).toHaveBeenCalledWith(2000);
expect(core.info).toHaveBeenCalledWith(
'Request attempt 1 of 4 failed (HTTP 429); retrying in 2000 ms'
);
});
});
describe('findPackageForDownload', () => {