mirror of
https://github.com/actions/setup-java.git
synced 2026-07-30 21:26:19 +00:00
27f2c62824
* Verify JDK downloads with vendor checksums Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Handle missing vendor checksum values Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Preserve checksum error during cleanup failure Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Validate checksum metadata value types Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Clarify checksum documentation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Expand vendor checksum verification Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Accept SHA-256 or SHA-512 for JetBrains checksum sibling JetBrains publishes a single, generically-named ".checksum" sibling whose digest algorithm isn't disclosed by the filename. Older JBR 11 builds (e.g. jbrsdk_nomod-11_0_16-*-b2043.64.tar.gz) publish a SHA-256 digest there, while newer builds publish SHA-512. The JetBrains installer previously assumed SHA-512 unconditionally, so verification failed with "Malformed sha512 checksum metadata ... expected a 128-character hexadecimal digest" for those older builds, breaking the jetbrains 11 e2e job on macOS and Windows. fetchChecksum now accepts a list of candidate algorithms and infers the actual algorithm from the returned digest's length, preferring the strongest match. The JetBrains installer passes ['sha512', 'sha256']; all other callers are unaffected since they already pass a single, vendor-disclosed algorithm. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d * Use SapMachine archive checksum files Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a800a031-600e-4d28-b23e-be309555d38d
1292 lines
39 KiB
TypeScript
1292 lines
39 KiB
TypeScript
import {
|
|
jest,
|
|
describe,
|
|
it,
|
|
expect,
|
|
beforeEach,
|
|
afterEach,
|
|
beforeAll,
|
|
afterAll
|
|
} from '@jest/globals';
|
|
import type {
|
|
JavaDownloadRelease,
|
|
JavaInstallerOptions,
|
|
JavaInstallerResults
|
|
} from '../../src/distributions/base-models.js';
|
|
|
|
import path from 'path';
|
|
import * as semver from 'semver';
|
|
import fs from 'fs';
|
|
import {createHash} from 'crypto';
|
|
import {HttpClient} from '@actions/http-client';
|
|
|
|
import os from 'os';
|
|
|
|
// Mock @actions/core before importing source modules that depend on it
|
|
jest.unstable_mockModule('@actions/core', () => ({
|
|
info: jest.fn(),
|
|
warning: jest.fn(),
|
|
debug: jest.fn(),
|
|
error: jest.fn(),
|
|
notice: jest.fn(),
|
|
setFailed: jest.fn(),
|
|
setOutput: jest.fn(),
|
|
getInput: jest.fn(),
|
|
getBooleanInput: jest.fn(),
|
|
getMultilineInput: jest.fn(),
|
|
addPath: jest.fn(),
|
|
exportVariable: jest.fn(),
|
|
saveState: jest.fn(),
|
|
getState: jest.fn(),
|
|
setSecret: jest.fn(),
|
|
isDebug: jest.fn(() => false),
|
|
startGroup: jest.fn(),
|
|
endGroup: jest.fn(),
|
|
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
|
toPlatformPath: jest.fn((p: string) => p),
|
|
toWin32Path: jest.fn((p: string) => p),
|
|
toPosixPath: jest.fn((p: string) => p)
|
|
}));
|
|
|
|
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
|
find: jest.fn(),
|
|
findAllVersions: jest.fn(),
|
|
downloadTool: jest.fn(),
|
|
extractZip: jest.fn(),
|
|
extractTar: jest.fn(),
|
|
extract7z: jest.fn(),
|
|
extractXar: jest.fn(),
|
|
cacheDir: jest.fn(),
|
|
cacheFile: jest.fn(),
|
|
getManifestFromRepo: jest.fn(),
|
|
findFromManifest: jest.fn(),
|
|
evaluateVersions: jest.fn(),
|
|
HTTPError: class HTTPError extends Error {
|
|
httpStatusCode: number;
|
|
constructor(statusCode: number) {
|
|
super(`HTTP Error: ${statusCode}`);
|
|
this.httpStatusCode = statusCode;
|
|
}
|
|
}
|
|
}));
|
|
|
|
const real_util_module = await import('../../src/util.js');
|
|
jest.unstable_mockModule('../../src/util.js', () => ({
|
|
...real_util_module,
|
|
extractJdkFile: jest.fn(),
|
|
getDownloadArchiveExtension: jest.fn(),
|
|
getToolcachePath: jest.fn(),
|
|
isJobStatusSuccess: jest.fn(),
|
|
renameWinArchive: jest.fn(),
|
|
isVersionSatisfies: real_util_module.isVersionSatisfies,
|
|
getTempDir: real_util_module.getTempDir
|
|
}));
|
|
|
|
// Dynamic imports after mocking
|
|
const core = await import('@actions/core');
|
|
const tc = await import('@actions/tool-cache');
|
|
const util = await import('../../src/util.js');
|
|
const {JavaBase} = await import('../../src/distributions/base-installer.js');
|
|
|
|
class EmptyJavaBase extends JavaBase {
|
|
constructor(installerOptions: JavaInstallerOptions) {
|
|
super('Empty', installerOptions);
|
|
}
|
|
|
|
protected async downloadTool(
|
|
javaRelease: JavaDownloadRelease
|
|
): Promise<JavaInstallerResults> {
|
|
return {
|
|
version: '11.0.9',
|
|
path: path.join(
|
|
'toolcache',
|
|
this.toolcacheFolderName,
|
|
'11.0.9',
|
|
this.architecture
|
|
)
|
|
};
|
|
}
|
|
|
|
protected async findPackageForDownload(
|
|
range: string
|
|
): Promise<JavaDownloadRelease> {
|
|
const availableVersion = '11.0.9';
|
|
if (!semver.satisfies(availableVersion, range)) {
|
|
throw this.createVersionNotFoundError(range, [availableVersion]);
|
|
}
|
|
|
|
return {
|
|
version: availableVersion,
|
|
url: `some/random_url/java/${availableVersion}`
|
|
};
|
|
}
|
|
|
|
public downloadRelease(javaRelease: JavaDownloadRelease): Promise<string> {
|
|
return this.downloadAndVerify(javaRelease);
|
|
}
|
|
|
|
public fetchChecksumForTest(
|
|
checksumUrl: string,
|
|
algorithm: 'sha256' | 'sha512' | ('sha256' | 'sha512')[]
|
|
) {
|
|
return this.fetchChecksum(checksumUrl, algorithm);
|
|
}
|
|
}
|
|
|
|
describe('findInToolcache', () => {
|
|
const actualJavaVersion = '11.0.8';
|
|
const javaPath = path.join('Java_Empty_jdk', actualJavaVersion, 'x64');
|
|
|
|
let mockJavaBase: EmptyJavaBase;
|
|
let spyGetToolcachePath: any;
|
|
let spyTcFindAllVersions: any;
|
|
|
|
beforeEach(() => {
|
|
spyGetToolcachePath = util.getToolcachePath as jest.Mock;
|
|
spyTcFindAllVersions = tc.findAllVersions as jest.Mock;
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
jest.clearAllMocks();
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
{version: actualJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
{version: actualJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0.8',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
{version: actualJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: true
|
|
},
|
|
{version: actualJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: true
|
|
},
|
|
{version: actualJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0.8',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: true
|
|
},
|
|
{version: actualJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x64',
|
|
packageType: 'jre',
|
|
checkLatest: false
|
|
},
|
|
null
|
|
],
|
|
[
|
|
{
|
|
version: '8',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
null
|
|
],
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
null
|
|
],
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jre',
|
|
checkLatest: false
|
|
},
|
|
null
|
|
]
|
|
])(`should find java for path %s -> %s`, (input, expected) => {
|
|
spyTcFindAllVersions.mockReturnValue([actualJavaVersion]);
|
|
spyGetToolcachePath.mockImplementation(
|
|
(toolname: string, javaVersion: string, architecture: string) => {
|
|
const semverVersion = new semver.Range(javaVersion);
|
|
|
|
if (
|
|
path.basename(javaPath) !== architecture ||
|
|
!javaPath.includes(toolname)
|
|
) {
|
|
return '';
|
|
}
|
|
|
|
return semver.satisfies(actualJavaVersion, semverVersion)
|
|
? javaPath
|
|
: '';
|
|
}
|
|
);
|
|
mockJavaBase = new EmptyJavaBase(input);
|
|
expect(mockJavaBase['findInToolcache']()).toEqual(expected);
|
|
});
|
|
|
|
it.each([
|
|
['11', {version: '11.0.3+2', versionInPath: '11.0.3-2'}],
|
|
['11.0', {version: '11.0.3+2', versionInPath: '11.0.3-2'}],
|
|
['11.0.1', {version: '11.0.1', versionInPath: '11.0.1'}],
|
|
['11.0.3', {version: '11.0.3+2', versionInPath: '11.0.3-2'}],
|
|
['15', {version: '15.0.2+4', versionInPath: '15.0.2-4'}],
|
|
['x', {version: '15.0.2+4', versionInPath: '15.0.2-4'}],
|
|
['x-ea', {version: '17.4.4', versionInPath: '17.4.4-ea'}],
|
|
[
|
|
'11-ea',
|
|
{version: '11.3.3+5.2.1231421', versionInPath: '11.3.3-ea.5.2.1231421'}
|
|
],
|
|
['11.2-ea', {version: '11.2.1', versionInPath: '11.2.1-ea'}],
|
|
['11.2.1-ea', {version: '11.2.1', versionInPath: '11.2.1-ea'}]
|
|
])(
|
|
'should choose correct java from tool-cache for input %s',
|
|
(input, expected) => {
|
|
spyTcFindAllVersions.mockReturnValue([
|
|
'17.4.4-ea',
|
|
'11.0.2',
|
|
'15.0.2-4',
|
|
'11.0.3-2',
|
|
'11.2.1-ea',
|
|
'11.3.2-ea',
|
|
'11.3.2-ea.5',
|
|
'11.3.3-ea.5.2.1231421',
|
|
'12.3.2-0',
|
|
'11.0.1'
|
|
]);
|
|
spyGetToolcachePath.mockImplementation(
|
|
(toolname: string, javaVersion: string, architecture: string) =>
|
|
`/hostedtoolcache/${toolname}/${javaVersion}/${architecture}`
|
|
);
|
|
mockJavaBase = new EmptyJavaBase({
|
|
version: input,
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
const foundVersion = mockJavaBase['findInToolcache']();
|
|
expect(foundVersion).toEqual({
|
|
version: expected.version,
|
|
path: `/hostedtoolcache/Java_Empty_jdk/${expected.versionInPath}/x64`
|
|
});
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('setupJava', () => {
|
|
const actualJavaVersion = '11.0.9';
|
|
const installedJavaVersion = '11.0.8';
|
|
const javaPath = path.join('Java_Empty_jdk', installedJavaVersion, 'x86');
|
|
const javaPathInstalled = path.join(
|
|
'toolcache',
|
|
'Java_Empty_jdk',
|
|
actualJavaVersion,
|
|
'x86'
|
|
);
|
|
|
|
let mockJavaBase: EmptyJavaBase;
|
|
|
|
let spyGetToolcachePath: any;
|
|
let spyTcFindAllVersions: any;
|
|
let spyCoreDebug: any;
|
|
let spyCoreInfo: any;
|
|
let spyCoreExportVariable: any;
|
|
let spyCoreAddPath: any;
|
|
let spyCoreSetOutput: any;
|
|
let spyCoreError: any;
|
|
|
|
beforeEach(() => {
|
|
spyGetToolcachePath = util.getToolcachePath as jest.Mock;
|
|
spyGetToolcachePath.mockImplementation(
|
|
(toolname: string, javaVersion: string, architecture: string) => {
|
|
const semverVersion = new semver.Range(javaVersion);
|
|
|
|
if (
|
|
path.basename(javaPath) !== architecture ||
|
|
!javaPath.includes(toolname)
|
|
) {
|
|
return '';
|
|
}
|
|
|
|
return semver.satisfies(installedJavaVersion, semverVersion)
|
|
? javaPath
|
|
: '';
|
|
}
|
|
);
|
|
|
|
spyTcFindAllVersions = tc.findAllVersions as jest.Mock;
|
|
spyTcFindAllVersions.mockReturnValue([installedJavaVersion]);
|
|
|
|
// Spy on core methods
|
|
spyCoreDebug = core.debug as jest.Mock;
|
|
spyCoreDebug.mockImplementation(() => undefined);
|
|
|
|
spyCoreInfo = core.info as jest.Mock;
|
|
spyCoreInfo.mockImplementation(() => undefined);
|
|
|
|
spyCoreAddPath = core.addPath as jest.Mock;
|
|
spyCoreAddPath.mockImplementation(() => undefined);
|
|
|
|
spyCoreExportVariable = core.exportVariable as jest.Mock;
|
|
spyCoreExportVariable.mockImplementation(() => undefined);
|
|
|
|
spyCoreSetOutput = core.setOutput as jest.Mock;
|
|
spyCoreSetOutput.mockImplementation(() => undefined);
|
|
|
|
// Mock core.error to suppress error logs
|
|
spyCoreError = core.error as jest.Mock;
|
|
spyCoreError.mockImplementation(() => undefined);
|
|
|
|
jest.spyOn(os, 'arch').mockReturnValue('x86' as ReturnType<typeof os.arch>);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
jest.clearAllMocks();
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
{version: installedJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
{version: installedJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0.8',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
{version: installedJavaVersion, path: javaPath}
|
|
],
|
|
[
|
|
{version: '11', architecture: '', packageType: 'jdk', checkLatest: false},
|
|
{version: installedJavaVersion, path: javaPath}
|
|
]
|
|
])('should find java locally for %s', async (input, expected) => {
|
|
mockJavaBase = new EmptyJavaBase(input);
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual(expected);
|
|
expect(spyGetToolcachePath).toHaveBeenCalled();
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Resolved Java ${expected.version} from tool-cache`
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Setting Java ${expected.version} as the default`
|
|
);
|
|
expect(spyCoreInfo).not.toHaveBeenCalledWith(
|
|
'Trying to resolve the latest version from remote'
|
|
);
|
|
expect(spyCoreInfo).not.toHaveBeenCalledWith('Trying to download...');
|
|
});
|
|
|
|
it('should resolve the latest version from remote when java-version is "latest", even if a version is cached', async () => {
|
|
mockJavaBase = new EmptyJavaBase({
|
|
version: 'latest',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
|
version: actualJavaVersion,
|
|
path: javaPathInstalled
|
|
});
|
|
|
|
// `latest` must bypass the tool-cache short-circuit and always resolve remotely
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
'Trying to resolve the latest version from remote'
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith('Trying to download...');
|
|
expect(spyCoreInfo).not.toHaveBeenCalledWith(
|
|
`Resolved Java ${installedJavaVersion} from tool-cache`
|
|
);
|
|
});
|
|
|
|
it('should download java when force-download is enabled, even if the version is cached', async () => {
|
|
mockJavaBase = new EmptyJavaBase({
|
|
version: actualJavaVersion,
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
forceDownload: true
|
|
});
|
|
const findInToolcache = jest.fn(() => ({
|
|
version: actualJavaVersion,
|
|
path: javaPathInstalled
|
|
}));
|
|
mockJavaBase['findInToolcache'] = findInToolcache;
|
|
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
|
version: actualJavaVersion,
|
|
path: javaPathInstalled
|
|
});
|
|
|
|
expect(findInToolcache).not.toHaveBeenCalled();
|
|
expect(spyCoreInfo).toHaveBeenCalledWith('Trying to download...');
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Java ${actualJavaVersion} was downloaded`
|
|
);
|
|
expect(spyCoreInfo).not.toHaveBeenCalledWith(
|
|
`Resolved Java ${actualJavaVersion} from tool-cache`
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jre',
|
|
checkLatest: false
|
|
},
|
|
{
|
|
path: path.join('toolcache', 'Java_Empty_jre', '11.0.9', 'x86'),
|
|
version: '11.0.9'
|
|
}
|
|
],
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
},
|
|
{
|
|
path: path.join('toolcache', 'Java_Empty_jdk', '11.0.9', 'x64'),
|
|
version: '11.0.9'
|
|
}
|
|
],
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x64',
|
|
packageType: 'jre',
|
|
checkLatest: false
|
|
},
|
|
{
|
|
path: path.join('toolcache', 'Java_Empty_jre', '11.0.9', 'x64'),
|
|
version: '11.0.9'
|
|
}
|
|
],
|
|
[
|
|
{version: '11', architecture: '', packageType: 'jre', checkLatest: false},
|
|
{
|
|
path: path.join('toolcache', 'Java_Empty_jre', '11.0.9', 'x86'),
|
|
version: '11.0.9'
|
|
}
|
|
]
|
|
])('download java with configuration %s', async (input, expected) => {
|
|
mockJavaBase = new EmptyJavaBase(input);
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual(expected);
|
|
expect(spyGetToolcachePath).toHaveBeenCalled();
|
|
expect(spyCoreAddPath).toHaveBeenCalled();
|
|
expect(spyCoreExportVariable).toHaveBeenCalled();
|
|
expect(spyCoreExportVariable).toHaveBeenCalledWith(
|
|
`JAVA_HOME_${input.version}_${(
|
|
input.architecture || 'x86'
|
|
).toLocaleUpperCase()}`,
|
|
expected.path
|
|
);
|
|
expect(spyCoreSetOutput).toHaveBeenCalled();
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
'Trying to resolve the latest version from remote'
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Resolved latest version as ${expected.version}`
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith('Trying to download...');
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Java ${expected.version} was downloaded`
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Setting Java ${expected.version} as the default`
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
{
|
|
version: '11.0.9',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: true
|
|
},
|
|
{version: '11.0.9', path: javaPathInstalled}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0.9',
|
|
architecture: '',
|
|
packageType: 'jdk',
|
|
checkLatest: true
|
|
},
|
|
{version: '11.0.9', path: javaPathInstalled}
|
|
]
|
|
])(
|
|
'should check the latest java version for %s and resolve locally',
|
|
async (input, expected) => {
|
|
mockJavaBase = new EmptyJavaBase(input);
|
|
mockJavaBase['findInToolcache'] = () => ({
|
|
version: '11.0.9',
|
|
path: expected.path
|
|
});
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual(expected);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
'Trying to resolve the latest version from remote'
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Resolved latest version as ${expected.version}`
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Resolved Java ${expected.version} from tool-cache`
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Setting Java ${expected.version} as the default`
|
|
);
|
|
}
|
|
);
|
|
|
|
it('should fail when verify-signature is enabled for unsupported distributions', async () => {
|
|
mockJavaBase = new EmptyJavaBase({
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
verifySignature: true
|
|
});
|
|
|
|
await expect(mockJavaBase.setupJava()).rejects.toThrow(
|
|
"Input 'verify-signature' is not supported for distribution 'Empty'."
|
|
);
|
|
expect(spyTcFindAllVersions).not.toHaveBeenCalled();
|
|
expect(spyCoreAddPath).not.toHaveBeenCalled();
|
|
expect(spyCoreExportVariable).not.toHaveBeenCalled();
|
|
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([
|
|
[
|
|
{
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: true
|
|
},
|
|
{version: actualJavaVersion, path: javaPathInstalled}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: true
|
|
},
|
|
{version: actualJavaVersion, path: javaPathInstalled}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0.x',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: true
|
|
},
|
|
{version: actualJavaVersion, path: javaPathInstalled}
|
|
],
|
|
[
|
|
{version: '11', architecture: '', packageType: 'jdk', checkLatest: true},
|
|
{version: actualJavaVersion, path: javaPathInstalled}
|
|
]
|
|
])(
|
|
'should check the latest java version for %s and download',
|
|
async (input, expected) => {
|
|
mockJavaBase = new EmptyJavaBase(input);
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual(expected);
|
|
expect(spyGetToolcachePath).toHaveBeenCalled();
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
'Trying to resolve the latest version from remote'
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Resolved latest version as ${actualJavaVersion}`
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith('Trying to download...');
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Java ${actualJavaVersion} was downloaded`
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Setting Java ${expected.version} as the default`
|
|
);
|
|
}
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
{
|
|
version: '15',
|
|
architecture: 'x86',
|
|
packageType: 'jre',
|
|
checkLatest: false
|
|
}
|
|
],
|
|
[
|
|
{
|
|
version: '11.0.7',
|
|
architecture: 'x64',
|
|
packageType: 'jre',
|
|
checkLatest: false
|
|
}
|
|
]
|
|
])('should throw an error for version not found for %s', async input => {
|
|
mockJavaBase = new EmptyJavaBase(input);
|
|
await expect(mockJavaBase.setupJava()).rejects.toThrow(
|
|
`No matching version found for SemVer '${input.version}'`
|
|
);
|
|
expect(spyTcFindAllVersions).toHaveBeenCalled();
|
|
expect(spyCoreAddPath).not.toHaveBeenCalled();
|
|
expect(spyCoreExportVariable).not.toHaveBeenCalled();
|
|
expect(spyCoreSetOutput).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not set JAVA_HOME and PATH when setDefault is false', async () => {
|
|
mockJavaBase = new EmptyJavaBase({
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
setDefault: false
|
|
});
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
|
version: installedJavaVersion,
|
|
path: javaPath
|
|
});
|
|
expect(spyCoreExportVariable).not.toHaveBeenCalledWith(
|
|
'JAVA_HOME',
|
|
expect.anything()
|
|
);
|
|
expect(spyCoreAddPath).not.toHaveBeenCalled();
|
|
expect(spyCoreExportVariable).toHaveBeenCalledWith(
|
|
'JAVA_HOME_11_X86',
|
|
javaPath
|
|
);
|
|
expect(spyCoreSetOutput).toHaveBeenCalledWith(
|
|
'version',
|
|
installedJavaVersion
|
|
);
|
|
expect(spyCoreSetOutput).toHaveBeenCalledWith('path', javaPath);
|
|
expect(spyCoreSetOutput).toHaveBeenCalledWith('distribution', 'Empty');
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Installing Java ${installedJavaVersion} (not setting as default)`
|
|
);
|
|
});
|
|
|
|
it('should set JAVA_HOME and PATH when setDefault is true', async () => {
|
|
mockJavaBase = new EmptyJavaBase({
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
setDefault: true
|
|
});
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
|
version: installedJavaVersion,
|
|
path: javaPath
|
|
});
|
|
expect(spyCoreExportVariable).toHaveBeenCalledWith('JAVA_HOME', javaPath);
|
|
expect(spyCoreAddPath).toHaveBeenCalledWith(path.join(javaPath, 'bin'));
|
|
expect(spyCoreExportVariable).toHaveBeenCalledWith(
|
|
'JAVA_HOME_11_X86',
|
|
javaPath
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Setting Java ${installedJavaVersion} as the default`
|
|
);
|
|
});
|
|
|
|
it('should default to setting as default when setDefault is not specified', async () => {
|
|
mockJavaBase = new EmptyJavaBase({
|
|
version: '11',
|
|
architecture: 'x86',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
|
version: installedJavaVersion,
|
|
path: javaPath
|
|
});
|
|
expect(spyCoreExportVariable).toHaveBeenCalledWith('JAVA_HOME', javaPath);
|
|
expect(spyCoreAddPath).toHaveBeenCalledWith(path.join(javaPath, 'bin'));
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
`Setting Java ${installedJavaVersion} as the default`
|
|
);
|
|
});
|
|
|
|
it('should download and not set default when setDefault is false', async () => {
|
|
mockJavaBase = new EmptyJavaBase({
|
|
version: '11',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
setDefault: false
|
|
});
|
|
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
|
version: '11.0.9',
|
|
path: path.join('toolcache', 'Java_Empty_jdk', '11.0.9', 'x64')
|
|
});
|
|
expect(spyCoreExportVariable).not.toHaveBeenCalledWith(
|
|
'JAVA_HOME',
|
|
expect.anything()
|
|
);
|
|
expect(spyCoreAddPath).not.toHaveBeenCalled();
|
|
expect(spyCoreExportVariable).toHaveBeenCalledWith(
|
|
'JAVA_HOME_11_X64',
|
|
path.join('toolcache', 'Java_Empty_jdk', '11.0.9', 'x64')
|
|
);
|
|
expect(spyCoreSetOutput).toHaveBeenCalledWith('version', '11.0.9');
|
|
expect(spyCoreSetOutput).toHaveBeenCalledWith(
|
|
'path',
|
|
path.join('toolcache', 'Java_Empty_jdk', '11.0.9', 'x64')
|
|
);
|
|
expect(spyCoreInfo).toHaveBeenCalledWith(
|
|
'Installing Java 11.0.9 (not setting as default)'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('downloadAndVerify', () => {
|
|
const options: JavaInstallerOptions = {
|
|
version: '21',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
};
|
|
let temporaryDirectory: string;
|
|
let archivePath: string;
|
|
|
|
beforeEach(async () => {
|
|
temporaryDirectory = await fs.promises.mkdtemp(
|
|
path.join(os.tmpdir(), 'setup-java-base-')
|
|
);
|
|
archivePath = path.join(temporaryDirectory, 'archive');
|
|
await fs.promises.writeFile(archivePath, 'downloaded archive');
|
|
(tc.downloadTool as jest.Mock<any>).mockResolvedValue(archivePath);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fs.promises.rm(temporaryDirectory, {recursive: true, force: true});
|
|
jest.resetAllMocks();
|
|
});
|
|
|
|
it('returns a download after successful verification', async () => {
|
|
const distribution = new EmptyJavaBase(options);
|
|
const result = await distribution.downloadRelease({
|
|
version: '21.0.8',
|
|
url: 'https://vendor.example/jdk.tar.gz',
|
|
checksum: {
|
|
algorithm: 'sha256',
|
|
value: createHash('sha256').update('downloaded archive').digest('hex')
|
|
}
|
|
});
|
|
|
|
expect(result).toBe(archivePath);
|
|
expect(fs.existsSync(archivePath)).toBe(true);
|
|
expect(core.debug).toHaveBeenCalledWith(
|
|
'Verified sha256 checksum for Empty version 21.0.8.'
|
|
);
|
|
});
|
|
|
|
it('removes the download after verification failure', async () => {
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
await expect(
|
|
distribution.downloadRelease({
|
|
version: '21.0.8',
|
|
url: 'https://vendor.example/jdk.tar.gz?token=secret',
|
|
checksum: {algorithm: 'sha256', value: 'a'.repeat(64)}
|
|
})
|
|
).rejects.toThrow('Checksum verification failed for Empty version 21.0.8');
|
|
|
|
expect(fs.existsSync(archivePath)).toBe(false);
|
|
});
|
|
|
|
it('preserves the verification error when removing the download fails', async () => {
|
|
const distribution = new EmptyJavaBase(options);
|
|
const cleanupError = new Error('cleanup failed');
|
|
jest.spyOn(fs.promises, 'rm').mockRejectedValueOnce(cleanupError);
|
|
|
|
const result = distribution.downloadRelease({
|
|
version: '21.0.8',
|
|
url: 'https://vendor.example/jdk.tar.gz',
|
|
checksum: {algorithm: 'sha256', value: 'a'.repeat(64)}
|
|
});
|
|
|
|
await expect(result).rejects.toMatchObject({
|
|
message: expect.stringContaining(
|
|
'Failed to remove the downloaded archive after verification failure: cleanup failed'
|
|
),
|
|
cause: expect.objectContaining({
|
|
message: expect.stringContaining(
|
|
'Checksum verification failed for Empty version 21.0.8'
|
|
)
|
|
})
|
|
});
|
|
});
|
|
|
|
it('logs when authoritative checksum metadata is unavailable', async () => {
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
await expect(
|
|
distribution.downloadRelease({
|
|
version: '21.0.8',
|
|
url: 'https://vendor.example/jdk.tar.gz'
|
|
})
|
|
).resolves.toBe(archivePath);
|
|
|
|
expect(core.debug).toHaveBeenCalledWith(
|
|
'No authoritative checksum is available for Empty version 21.0.8; skipping checksum verification.'
|
|
);
|
|
});
|
|
|
|
it.each([undefined, '', ' '])(
|
|
'skips verification when the vendor digest is %p',
|
|
async value => {
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
await expect(
|
|
distribution.downloadRelease({
|
|
version: '21.0.8',
|
|
url: 'https://vendor.example/jdk.tar.gz',
|
|
checksum: {
|
|
algorithm: 'sha256',
|
|
value
|
|
} as JavaDownloadRelease['checksum']
|
|
})
|
|
).resolves.toBe(archivePath);
|
|
|
|
expect(core.debug).toHaveBeenCalledWith(
|
|
'No authoritative checksum is available for Empty version 21.0.8; skipping checksum verification.'
|
|
);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('fetchChecksum', () => {
|
|
const options: JavaInstallerOptions = {
|
|
version: '21',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
};
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
function mockGet(statusCode: number, body: string) {
|
|
return jest.spyOn(HttpClient.prototype, 'get').mockResolvedValue({
|
|
message: {statusCode},
|
|
readBody: async () => body
|
|
} as any);
|
|
}
|
|
|
|
it('parses a bare hex digest', async () => {
|
|
const digest = 'a'.repeat(64);
|
|
const spy = mockGet(200, digest);
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
const checksum = await distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jdk.tar.gz.sha256',
|
|
'sha256'
|
|
);
|
|
|
|
expect(spy).toHaveBeenCalledWith(
|
|
'https://vendor.example/jdk.tar.gz.sha256'
|
|
);
|
|
expect(checksum).toEqual({
|
|
algorithm: 'sha256',
|
|
value: digest,
|
|
source: 'https://vendor.example/jdk.tar.gz.sha256'
|
|
});
|
|
});
|
|
|
|
it('parses only the first token of a GNU-style checksum file', async () => {
|
|
const digest = 'b'.repeat(128);
|
|
mockGet(200, `${digest} jbrsdk-21.0.3-linux-x64-b465.3.tar.gz\n`);
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
const checksum = await distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jdk.tar.gz.checksum',
|
|
'sha512'
|
|
);
|
|
|
|
expect(checksum).toEqual({
|
|
algorithm: 'sha512',
|
|
value: digest,
|
|
source: 'https://vendor.example/jdk.tar.gz.checksum'
|
|
});
|
|
});
|
|
|
|
it('trims surrounding whitespace and newlines', async () => {
|
|
const digest = 'c'.repeat(64);
|
|
mockGet(200, `\n ${digest} \n`);
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
const checksum = await distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jdk.tar.gz.sha256',
|
|
'sha256'
|
|
);
|
|
|
|
expect(checksum.value).toBe(digest);
|
|
});
|
|
|
|
it('skips verification when the sibling checksum is not published', async () => {
|
|
mockGet(404, 'Not Found');
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
await expect(
|
|
distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jdk.tar.gz.sha256',
|
|
'sha256'
|
|
)
|
|
).resolves.toBeUndefined();
|
|
expect(core.debug).toHaveBeenCalledWith(
|
|
'No authoritative sha256 checksum is available for Empty from https://vendor.example/jdk.tar.gz.sha256; skipping checksum verification.'
|
|
);
|
|
});
|
|
|
|
it('surfaces unexpected HTTP failures without query parameters', async () => {
|
|
mockGet(500, 'Server Error');
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
await expect(
|
|
distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jdk.tar.gz.sha256?token=secret',
|
|
'sha256'
|
|
)
|
|
).rejects.toThrow(
|
|
'Failed to fetch the authoritative sha256 checksum for Empty from https://vendor.example/jdk.tar.gz.sha256 (HTTP 500).'
|
|
);
|
|
});
|
|
|
|
it('rejects an empty successful checksum response', async () => {
|
|
mockGet(200, ' \n');
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
await expect(
|
|
distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jdk.tar.gz.sha256',
|
|
'sha256'
|
|
)
|
|
).rejects.toThrow(
|
|
'Received an empty authoritative sha256 checksum for Empty from https://vendor.example/jdk.tar.gz.sha256.'
|
|
);
|
|
});
|
|
|
|
describe('with a list of candidate algorithms', () => {
|
|
it('infers sha512 when the digest is 128 hex characters', async () => {
|
|
const digest = 'd'.repeat(128);
|
|
mockGet(200, `${digest} jbrsdk.tar.gz\n`);
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
const checksum = await distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jbrsdk.tar.gz.checksum',
|
|
['sha512', 'sha256']
|
|
);
|
|
|
|
expect(checksum).toEqual({
|
|
algorithm: 'sha512',
|
|
value: digest,
|
|
source: 'https://vendor.example/jbrsdk.tar.gz.checksum'
|
|
});
|
|
});
|
|
|
|
it('infers sha256 when the digest is 64 hex characters, even though sha512 was preferred', async () => {
|
|
// Reproduces older JetBrains JBR builds (e.g. JBR 11), which publish a
|
|
// SHA-256 digest at the generic `.checksum` sibling instead of SHA-512.
|
|
const digest = 'e'.repeat(64);
|
|
mockGet(200, `${digest} jbrsdk_nomod-11_0_16-osx-x64-b2043.64.tar.gz\n`);
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
const checksum = await distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jbrsdk_nomod-11_0_16-osx-x64-b2043.64.tar.gz.checksum',
|
|
['sha512', 'sha256']
|
|
);
|
|
|
|
expect(checksum).toEqual({
|
|
algorithm: 'sha256',
|
|
value: digest,
|
|
source:
|
|
'https://vendor.example/jbrsdk_nomod-11_0_16-osx-x64-b2043.64.tar.gz.checksum'
|
|
});
|
|
});
|
|
|
|
it('falls back to the first candidate algorithm when the digest length matches none of them', async () => {
|
|
const digest = 'f'.repeat(40); // e.g. sha1, not supported
|
|
mockGet(200, `${digest} jbrsdk.tar.gz\n`);
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
const checksum = await distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jbrsdk.tar.gz.checksum',
|
|
['sha512', 'sha256']
|
|
);
|
|
|
|
// No candidate algorithm matches, so the first-listed one is kept;
|
|
// downstream verification will reject it as malformed.
|
|
expect(checksum.algorithm).toBe('sha512');
|
|
expect(checksum.value).toBe(digest);
|
|
});
|
|
|
|
it('reports the checksum as unavailable using a combined algorithm label on 404', async () => {
|
|
mockGet(404, 'Not Found');
|
|
const distribution = new EmptyJavaBase(options);
|
|
|
|
await expect(
|
|
distribution.fetchChecksumForTest(
|
|
'https://vendor.example/jbrsdk.tar.gz.checksum',
|
|
['sha512', 'sha256']
|
|
)
|
|
).resolves.toBeUndefined();
|
|
expect(core.debug).toHaveBeenCalledWith(
|
|
'No authoritative sha512 or sha256 checksum is available for Empty from https://vendor.example/jbrsdk.tar.gz.checksum; skipping checksum verification.'
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('normalizeVersion', () => {
|
|
const DummyJavaBase = JavaBase as any;
|
|
|
|
it.each([
|
|
['11', {version: '11', stable: true, latest: false}],
|
|
['11.0', {version: '11.0', stable: true, latest: false}],
|
|
['11.0.10', {version: '11.0.10', stable: true, latest: false}],
|
|
['11-ea', {version: '11', stable: false, latest: false}],
|
|
['11.0.2-ea', {version: '11.0.2', stable: false, latest: false}],
|
|
['18.0.1.1', {version: '18.0.1+1', stable: true, latest: false}],
|
|
['11.0.9.1', {version: '11.0.9+1', stable: true, latest: false}],
|
|
['12.0.2.1.0', {version: '12.0.2+1.0', stable: true, latest: false}],
|
|
['18.0.1.1-ea', {version: '18.0.1+1', stable: false, latest: false}],
|
|
['latest', {version: 'x', stable: true, latest: true}],
|
|
['LATEST', {version: 'x', stable: true, latest: true}],
|
|
[' Latest ', {version: 'x', stable: true, latest: true}]
|
|
])('normalizeVersion from %s to %s', (input, expected) => {
|
|
expect(DummyJavaBase.prototype.normalizeVersion.call(null, input)).toEqual(
|
|
expected
|
|
);
|
|
});
|
|
|
|
it('normalizeVersion should throw an error for non semver', () => {
|
|
const version = '11g';
|
|
expect(
|
|
DummyJavaBase.prototype.normalizeVersion.bind(null, version)
|
|
).toThrow(
|
|
`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`
|
|
);
|
|
});
|
|
|
|
it.each(['latest-ea', 'latest.1', 'LATEST-EA', ' latest-ea '])(
|
|
'normalizeVersion should throw a targeted error for latest combined with a qualifier (%s)',
|
|
version => {
|
|
expect(
|
|
DummyJavaBase.prototype.normalizeVersion.bind(null, version)
|
|
).toThrow(
|
|
`The 'latest' alias resolves stable (GA) releases only and cannot be combined with '-ea' or other qualifiers (received '${version}'). Use 'latest' on its own, or specify a concrete version.`
|
|
);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('createVersionNotFoundError', () => {
|
|
it('should include all required fields in error message without available versions', () => {
|
|
const mockJavaBase = new EmptyJavaBase({
|
|
version: '17.0.5',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
const error = (mockJavaBase as any).createVersionNotFoundError('17.0.5');
|
|
|
|
expect(error.message).toContain(
|
|
"No matching version found for SemVer '17.0.5'"
|
|
);
|
|
expect(error.message).toContain('Distribution: Empty');
|
|
expect(error.message).toContain('Package type: jdk');
|
|
expect(error.message).toContain('Architecture: x64');
|
|
});
|
|
|
|
it('should include available versions when provided', () => {
|
|
const mockJavaBase = new EmptyJavaBase({
|
|
version: '17.0.5',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
const availableVersions = ['11.0.1', '11.0.2', '17.0.1', '17.0.2'];
|
|
const error = (mockJavaBase as any).createVersionNotFoundError(
|
|
'17.0.5',
|
|
availableVersions
|
|
);
|
|
|
|
expect(error.message).toContain(
|
|
"No matching version found for SemVer '17.0.5'"
|
|
);
|
|
expect(error.message).toContain('Distribution: Empty');
|
|
expect(error.message).toContain('Package type: jdk');
|
|
expect(error.message).toContain('Architecture: x64');
|
|
expect(error.message).toContain(
|
|
'Available versions: 11.0.1, 11.0.2, 17.0.1, 17.0.2'
|
|
);
|
|
});
|
|
|
|
it('should truncate available versions when there are many', () => {
|
|
const mockJavaBase = new EmptyJavaBase({
|
|
version: '17.0.5',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
// Create 60 versions to test truncation
|
|
const availableVersions = Array.from({length: 60}, (_, i) => `11.0.${i}`);
|
|
const error = (mockJavaBase as any).createVersionNotFoundError(
|
|
'17.0.5',
|
|
availableVersions
|
|
);
|
|
|
|
expect(error.message).toContain('Available versions:');
|
|
expect(error.message).toContain('...');
|
|
expect(error.message).toContain('(showing first 50 of 60 versions');
|
|
});
|
|
|
|
it('should include additional context when provided', () => {
|
|
const mockJavaBase = new EmptyJavaBase({
|
|
version: '17.0.5',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
const availableVersions = ['11.0.1', '11.0.2'];
|
|
const additionalContext = 'Platform: linux';
|
|
const error = (mockJavaBase as any).createVersionNotFoundError(
|
|
'17.0.5',
|
|
availableVersions,
|
|
additionalContext
|
|
);
|
|
|
|
expect(error.message).toContain(
|
|
"No matching version found for SemVer '17.0.5'"
|
|
);
|
|
expect(error.message).toContain('Distribution: Empty');
|
|
expect(error.message).toContain('Package type: jdk');
|
|
expect(error.message).toContain('Architecture: x64');
|
|
expect(error.message).toContain('Platform: linux');
|
|
expect(error.message).toContain('Available versions: 11.0.1, 11.0.2');
|
|
});
|
|
});
|
|
|
|
describe('getToolcacheVersionName', () => {
|
|
const DummyJavaBase = JavaBase as any;
|
|
|
|
it.each([
|
|
[{version: '11', stable: true}, '11'],
|
|
[{version: '11.0.2', stable: true}, '11.0.2'],
|
|
[{version: '11.0.2+4', stable: true}, '11.0.2-4'],
|
|
[{version: '11.0.2+4.1.2563234', stable: true}, '11.0.2-4.1.2563234'],
|
|
[{version: '11.0', stable: false}, '11.0-ea'],
|
|
[{version: '11.0.3', stable: false}, '11.0.3-ea'],
|
|
[{version: '11.0.3+4', stable: false}, '11.0.3-ea.4'],
|
|
[{version: '11.0.3+4.2.256', stable: false}, '11.0.3-ea.4.2.256']
|
|
])('returns correct version name for %s', (input, expected) => {
|
|
const inputVersion = input.stable ? '11' : '11-ea';
|
|
const mockJavaBase = new EmptyJavaBase({
|
|
version: inputVersion,
|
|
packageType: 'jdk',
|
|
architecture: 'x64',
|
|
checkLatest: false
|
|
});
|
|
const actual = mockJavaBase['getToolcacheVersionName'](input.version);
|
|
expect(actual).toBe(expected);
|
|
});
|
|
});
|