mirror of
https://github.com/actions/setup-java.git
synced 2026-08-05 17:02:57 +00:00
634b0f0d18
* Isolate Maven signing keys Import signing keys into an action-owned temporary GPG home, export GNUPGHOME, and remove the owned directory in the post action. Cover import failure, multiple keys and invocations, unrelated keyrings, missing state, and Windows path conversion. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix cleanup state assertion Account for isolated GPG-home cleanup when cache saving is disabled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Update generated action bundles Apply repository formatting and commit the setup and cleanup bundles produced by the validated Node 24 build. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address isolated GPG home review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Bruno Borges <brborges@microsoft.com>
472 lines
13 KiB
TypeScript
472 lines
13 KiB
TypeScript
import {
|
|
jest,
|
|
describe,
|
|
it,
|
|
expect,
|
|
beforeEach,
|
|
afterEach,
|
|
beforeAll,
|
|
afterAll
|
|
} from '@jest/globals';
|
|
import {HttpClient} from '@actions/http-client';
|
|
import data from '../data/microsoft.json' with {type: 'json'};
|
|
|
|
const mockOsArch = jest.fn(() => 'x64');
|
|
const mockOsPlatform = jest.fn(() => 'linux');
|
|
|
|
const real_os_module = await import('os');
|
|
jest.unstable_mockModule('os', () => ({
|
|
...real_os_module,
|
|
default: {
|
|
...real_os_module.default,
|
|
arch: mockOsArch,
|
|
platform: mockOsPlatform,
|
|
homedir: real_os_module.default.homedir
|
|
},
|
|
arch: mockOsArch,
|
|
platform: mockOsPlatform
|
|
}));
|
|
|
|
const real_fs_module = await import('fs');
|
|
const mockReaddirSync = jest.fn();
|
|
jest.unstable_mockModule('fs', () => ({
|
|
...real_fs_module,
|
|
default: {
|
|
...real_fs_module.default,
|
|
readdirSync: mockReaddirSync
|
|
},
|
|
readdirSync: mockReaddirSync
|
|
}));
|
|
|
|
// 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)
|
|
}));
|
|
|
|
const real_tc_module = await import('@actions/tool-cache');
|
|
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
|
...real_tc_module,
|
|
downloadTool: jest.fn(),
|
|
cacheDir: jest.fn(),
|
|
cacheFile: jest.fn()
|
|
}));
|
|
|
|
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
|
|
}));
|
|
|
|
jest.unstable_mockModule('../../src/gpg.js', () => ({
|
|
importKey: jest.fn(),
|
|
removeGpgHome: jest.fn(),
|
|
verifyPackageSignature: jest.fn()
|
|
}));
|
|
|
|
// Dynamic imports after mocking
|
|
const core = await import('@actions/core');
|
|
const gpg = await import('../../src/gpg.js');
|
|
const tc = await import('@actions/tool-cache');
|
|
const os = (await import('os')).default;
|
|
const fs = (await import('fs')).default;
|
|
const {MicrosoftDistributions, MICROSOFT_PUBLIC_KEY} =
|
|
await import('../../src/distributions/microsoft/installer.js');
|
|
const util = await import('../../src/util.js');
|
|
|
|
describe('findPackageForDownload', () => {
|
|
let distribution: InstanceType<typeof MicrosoftDistributions>;
|
|
let spyGetManifestFromRepo: any;
|
|
let spyHttpClientGet: any;
|
|
let spyDebug: any;
|
|
let spyCoreError: any;
|
|
|
|
const MICROSOFT_CHECKSUM = 'b'.repeat(64);
|
|
|
|
beforeEach(() => {
|
|
mockOsArch.mockReturnValue('x64');
|
|
mockOsPlatform.mockReturnValue(process.platform);
|
|
|
|
distribution = new MicrosoftDistributions({
|
|
version: '',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
spyGetManifestFromRepo = jest.spyOn(HttpClient.prototype, 'getJson');
|
|
spyGetManifestFromRepo.mockReturnValue({
|
|
result: data,
|
|
statusCode: 200,
|
|
headers: {}
|
|
});
|
|
|
|
// Every resolved release fetches `${download_url}.sha256sum.txt`; stub
|
|
// it with a GNU-style `<hex> <filename>` payload so tests never reach
|
|
// the real network.
|
|
spyHttpClientGet = jest.spyOn(HttpClient.prototype, 'get');
|
|
spyHttpClientGet.mockResolvedValue({
|
|
message: {statusCode: 200},
|
|
readBody: async () => `${MICROSOFT_CHECKSUM} microsoft-jdk.tar.gz\n`
|
|
});
|
|
|
|
spyDebug = core.debug as jest.Mock;
|
|
spyDebug.mockImplementation(() => {});
|
|
|
|
// Mock core.error to suppress error logs
|
|
spyCoreError = core.error as jest.Mock;
|
|
spyCoreError.mockImplementation(() => {});
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
'25.x',
|
|
'25.0.0',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-25.0.0-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
],
|
|
[
|
|
'21.x',
|
|
'21.0.0',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-21.0.0-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
],
|
|
[
|
|
'17.x',
|
|
'17.0.18',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-17.0.18-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
],
|
|
[
|
|
'17.0.7',
|
|
'17.0.7',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-17.0.7-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
],
|
|
[
|
|
'17.0.1',
|
|
'17.0.1+12.1',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-17.0.1.12.1-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
],
|
|
[
|
|
'16.0.x',
|
|
'16.0.2+7.1',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-16.0.2.7.1-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
],
|
|
[
|
|
'11.0.13',
|
|
'11.0.13+8.1',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-11.0.13.8.1-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
],
|
|
[
|
|
'11.0.15',
|
|
'11.0.15',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-11.0.15-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
],
|
|
[
|
|
'11.x',
|
|
'11.0.19',
|
|
'https://aka.ms/download-jdk/microsoft-jdk-11.0.19-{{OS_TYPE}}-x64.{{ARCHIVE_TYPE}}'
|
|
]
|
|
])('version is %s -> %s', async (input, expectedVersion, expectedUrl) => {
|
|
const result = await distribution['findPackageForDownload'](input);
|
|
expect(result.version).toBe(expectedVersion);
|
|
let os: string;
|
|
let archive: string;
|
|
switch (process.platform) {
|
|
case 'darwin':
|
|
os = 'macos';
|
|
archive = 'tar.gz';
|
|
break;
|
|
case 'win32':
|
|
os = 'windows';
|
|
archive = 'zip';
|
|
break;
|
|
default:
|
|
os = process.platform.toString();
|
|
archive = 'tar.gz';
|
|
break;
|
|
}
|
|
const url = expectedUrl
|
|
.replace('{{OS_TYPE}}', os)
|
|
.replace('{{ARCHIVE_TYPE}}', archive);
|
|
expect(result.url).toBe(url);
|
|
expect(result.signatureUrl).toBe(`${url}.sig`);
|
|
});
|
|
|
|
it.each([
|
|
['amd64', 'x64'],
|
|
['arm64', 'aarch64']
|
|
])(
|
|
'defaults to os.arch(): %s mapped to distro arch: %s',
|
|
async (osArch: string, distroArch: string) => {
|
|
mockOsArch.mockReturnValue(osArch);
|
|
mockOsPlatform.mockReturnValue('darwin');
|
|
|
|
const version = '17';
|
|
const distro = new MicrosoftDistributions({
|
|
version,
|
|
architecture: '', // to get default value
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
const result = await distro['findPackageForDownload'](version);
|
|
const expectedUrl = `https://aka.ms/download-jdk/microsoft-jdk-17.0.18-macos-${distroArch}.tar.gz`;
|
|
|
|
expect(result.url).toBe(expectedUrl);
|
|
}
|
|
);
|
|
|
|
it.each([
|
|
['amd64', 'x64'],
|
|
['arm64', 'aarch64']
|
|
])(
|
|
'defaults to os.arch(): %s mapped to distro arch: %s',
|
|
async (osArch: string, distroArch: string) => {
|
|
mockOsArch.mockReturnValue(osArch);
|
|
mockOsPlatform.mockReturnValue('linux');
|
|
|
|
const version = '17';
|
|
const distro = new MicrosoftDistributions({
|
|
version,
|
|
architecture: '', // to get default value
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
const result = await distro['findPackageForDownload'](version);
|
|
const expectedUrl = `https://aka.ms/download-jdk/microsoft-jdk-17.0.18-linux-${distroArch}.tar.gz`;
|
|
|
|
expect(result.url).toBe(expectedUrl);
|
|
}
|
|
);
|
|
|
|
it.each([
|
|
['amd64', 'x64'],
|
|
['arm64', 'aarch64']
|
|
])(
|
|
'defaults to os.arch(): %s mapped to distro arch: %s',
|
|
async (osArch: string, distroArch: string) => {
|
|
mockOsArch.mockReturnValue(osArch);
|
|
mockOsPlatform.mockReturnValue('win32');
|
|
|
|
const version = '17';
|
|
const distro = new MicrosoftDistributions({
|
|
version,
|
|
architecture: '', // to get default value
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
const result = await distro['findPackageForDownload'](version);
|
|
const expectedUrl = `https://aka.ms/download-jdk/microsoft-jdk-17.0.18-windows-${distroArch}.zip`;
|
|
|
|
expect(result.url).toBe(expectedUrl);
|
|
}
|
|
);
|
|
|
|
it('should throw an error', async () => {
|
|
await expect(distribution['findPackageForDownload']('8')).rejects.toThrow(
|
|
/No matching version found for SemVer */
|
|
);
|
|
});
|
|
|
|
it('uses manifest-provided signature URL when available', async () => {
|
|
spyGetManifestFromRepo.mockReturnValue({
|
|
result: [
|
|
{
|
|
version: '17.0.10',
|
|
stable: true,
|
|
release_url: 'https://example.test',
|
|
files: [
|
|
{
|
|
filename: 'microsoft-jdk-17.0.10-linux-x64.tar.gz',
|
|
arch: 'x64',
|
|
platform: 'linux',
|
|
download_url: 'https://example.test/jdk.tar.gz',
|
|
signature_url: 'https://example.test/jdk.tar.gz.custom.sig'
|
|
}
|
|
]
|
|
}
|
|
],
|
|
statusCode: 200,
|
|
headers: {}
|
|
});
|
|
mockOsPlatform.mockReturnValue('linux');
|
|
|
|
const result = await distribution['findPackageForDownload']('17.0.10');
|
|
|
|
expect(result.signatureUrl).toBe(
|
|
'https://example.test/jdk.tar.gz.custom.sig'
|
|
);
|
|
});
|
|
|
|
it('fetches the authoritative sha256 checksum from the GNU-style sibling file', async () => {
|
|
mockOsPlatform.mockReturnValue(process.platform);
|
|
|
|
const result = await distribution['findPackageForDownload']('17.0.7');
|
|
|
|
expect(result.checksum).toEqual({
|
|
algorithm: 'sha256',
|
|
value: MICROSOFT_CHECKSUM,
|
|
source: `${result.url}.sha256sum.txt`
|
|
});
|
|
expect(spyHttpClientGet).toHaveBeenCalledWith(
|
|
`${result.url}.sha256sum.txt`
|
|
);
|
|
expect(spyHttpClientGet).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('parses only the first whitespace-delimited token from the GNU checksum payload', async () => {
|
|
spyHttpClientGet.mockResolvedValue({
|
|
message: {statusCode: 200},
|
|
readBody: async () =>
|
|
`${MICROSOFT_CHECKSUM} microsoft-jdk-17.0.7-linux-x64.tar.gz\n`
|
|
});
|
|
|
|
const result = await distribution['findPackageForDownload']('17.0.7');
|
|
|
|
expect(result.checksum?.value).toBe(MICROSOFT_CHECKSUM);
|
|
});
|
|
});
|
|
|
|
describe('downloadTool', () => {
|
|
let spyDownloadTool: any;
|
|
let spyExtractJdkFile: any;
|
|
let spyCacheDir: any;
|
|
let spyVerifySignature: any;
|
|
let distribution: InstanceType<typeof MicrosoftDistributions>;
|
|
|
|
beforeEach(() => {
|
|
mockOsPlatform.mockReturnValue(process.platform);
|
|
|
|
distribution = new MicrosoftDistributions({
|
|
version: '17',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false
|
|
});
|
|
|
|
spyDownloadTool = tc.downloadTool as jest.Mock;
|
|
spyDownloadTool.mockImplementation(async () => {
|
|
return '/tmp/jdk.tar.gz';
|
|
});
|
|
|
|
spyExtractJdkFile = util.extractJdkFile as jest.Mock;
|
|
spyExtractJdkFile.mockImplementation(async () => {
|
|
return '/tmp/unpacked';
|
|
});
|
|
|
|
mockReaddirSync.mockReturnValue(['jdk'] as any);
|
|
spyCacheDir = tc.cacheDir as jest.Mock;
|
|
spyCacheDir.mockImplementation(async () => {
|
|
return '/tmp/cached';
|
|
});
|
|
|
|
(util.renameWinArchive as jest.Mock<any>).mockImplementation(
|
|
(archivePath: string) => `${archivePath}.zip`
|
|
);
|
|
|
|
spyVerifySignature = gpg.verifyPackageSignature as jest.Mock;
|
|
spyVerifySignature.mockImplementation(async () => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it('verifies signature when enabled', async () => {
|
|
const signedDistribution = new MicrosoftDistributions({
|
|
version: '17',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
verifySignature: true
|
|
});
|
|
|
|
await signedDistribution['downloadTool']({
|
|
version: '17.0.14+7',
|
|
url: 'https://example.com/jdk.tar.gz',
|
|
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
|
|
});
|
|
|
|
expect(spyVerifySignature).toHaveBeenCalledWith(
|
|
'/tmp/jdk.tar.gz',
|
|
'https://example.com/jdk.tar.gz.sig',
|
|
MICROSOFT_PUBLIC_KEY
|
|
);
|
|
});
|
|
|
|
it('uses custom public key when verifySignaturePublicKey is provided', async () => {
|
|
const customKey =
|
|
'-----BEGIN PGP PUBLIC KEY BLOCK-----\ncustom\n-----END PGP PUBLIC KEY BLOCK-----';
|
|
const signedDistribution = new MicrosoftDistributions({
|
|
version: '17',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
verifySignature: true,
|
|
verifySignaturePublicKey: customKey
|
|
});
|
|
|
|
await signedDistribution['downloadTool']({
|
|
version: '17.0.14+7',
|
|
url: 'https://example.com/jdk.tar.gz',
|
|
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
|
|
});
|
|
|
|
expect(spyVerifySignature).toHaveBeenCalledWith(
|
|
'/tmp/jdk.tar.gz',
|
|
'https://example.com/jdk.tar.gz.sig',
|
|
customKey
|
|
);
|
|
});
|
|
|
|
it('fails when signature is missing and verification is enabled', async () => {
|
|
const signedDistribution = new MicrosoftDistributions({
|
|
version: '17',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: false,
|
|
verifySignature: true
|
|
});
|
|
|
|
await expect(
|
|
signedDistribution['downloadTool']({
|
|
version: '17.0.14+7',
|
|
url: 'https://example.com/jdk.tar.gz'
|
|
})
|
|
).rejects.toThrow(
|
|
"Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version 17.0.14+7."
|
|
);
|
|
expect(spyVerifySignature).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('supports signature verification', () => {
|
|
expect(distribution['supportsSignatureVerification']()).toBe(true);
|
|
});
|
|
});
|