Skip to content
36 changes: 36 additions & 0 deletions __tests__/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as io from '@actions/io';
import * as fs from 'fs';
import * as path from 'path';
import os from 'os';
import {XMLParser} from 'fast-xml-parser';

// Mock @actions/core before importing source modules that depend on it
jest.unstable_mockModule('@actions/core', () => ({
Expand Down Expand Up @@ -271,6 +272,41 @@ describe('auth tests', () => {
);
});

it('escapes settings.xml values while preserving parsed semantics', () => {
const id = `packages&<>"'é`;
const username = `USER&<>"'é`;
const password = `TOKEN&<>"'é`;
const gpgPassphrase = `GPG&<>"'é`;

const xml = auth.generate(id, username, password, gpgPassphrase);
const parsed = parseXmlObject(xml) as any;

expect(parsed.settings.interactiveMode).toBe('false');
expect(xmlElementText(xml, 'id')).toBe(id);
expect(xmlElementText(xml, 'username')).toBe(`\${env.${username}}`);
expect(xmlElementText(xml, 'password')).toBe(`\${env.${password}}`);
expect(xmlElementText(xml, 'gpg.passphraseEnvName')).toBe(gpgPassphrase);
expect(parsed.settings.activeProfiles.activeProfile).toBe('setup-java-gpg');
});

function xmlElementText(xml: string, tagName: string): string {
const match = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`).exec(xml);
expect(match).not.toBeNull();
return (parseXmlObject(`<value>${match?.[1]}</value>`) as {value: string})
.value;
}

function parseXmlObject(xml: string): unknown {
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '@',
parseAttributeValue: false,
parseTagValue: false,
trimValues: true
});
return parser.parse(xml);
}

it('uses deprecated input aliases and warns', () => {
const mockGetInput = core.getInput as jest.MockedFunction<
typeof core.getInput
Expand Down
56 changes: 56 additions & 0 deletions __tests__/maven-xml-loading.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {describe, expect, it, jest} from '@jest/globals';

const mockXmlBuilderFactory = jest.fn();
const mockParse = jest.fn(() => ({
toolchains: {
toolchain: [
{
type: 'foo',
provides: {id: 'custom'},
configuration: {fooHome: '/opt/foo'}
}
]
}
}));

jest.unstable_mockModule('fast-xml-parser', () => {
mockXmlBuilderFactory();
return {
XMLParser: jest.fn().mockImplementation(() => ({
parse: mockParse
}))
};
});

const toolchains = await import('../src/toolchains.js');

describe('Maven XML loading', () => {
it('does not load fast-xml-parser for new toolchains.xml generation', async () => {
const xml = await toolchains.generateToolchainDefinition(
'',
'21',
'temurin',
'temurin_21',
'/opt/java/21'
);

expect(xml).toContain('<id>temurin_21</id>');
expect(mockXmlBuilderFactory).not.toHaveBeenCalled();
expect(mockParse).not.toHaveBeenCalled();
});

it('loads fast-xml-parser for existing toolchains.xml merge generation', async () => {
await expect(
toolchains.generateToolchainDefinition(
'<toolchains><toolchain><type>foo</type></toolchain></toolchains>',
'21',
'temurin',
'temurin_21',
'/opt/java/21'
)
).resolves.toContain('<id>temurin_21</id>');

expect(mockXmlBuilderFactory).toHaveBeenCalledTimes(1);
expect(mockParse).toHaveBeenCalledTimes(1);
});
});
72 changes: 67 additions & 5 deletions __tests__/setup-java.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ jest.unstable_mockModule('../src/toolchains.js', () => ({
configureToolchains: jest.fn()
}));

jest.unstable_mockModule('../src/toolchain-ids.js', () => ({
validateToolchainIds: jest.fn()
}));

jest.unstable_mockModule('../src/cache.js', () => ({
restore: jest.fn()
}));
Expand Down Expand Up @@ -72,6 +76,7 @@ const core = await import('@actions/core');
const fs = (await import('fs')).default;
const util = await import('../src/util.js');
const toolchains = await import('../src/toolchains.js');
const toolchainIds = await import('../src/toolchain-ids.js');
const cache = await import('../src/cache.js');
const cacheFeature = await import('../src/cache-feature.js');
const factory = await import('../src/distributions/distribution-factory.js');
Expand Down Expand Up @@ -109,6 +114,9 @@ describe('setup action orchestration', () => {
}
);
(cacheFeature.isCacheFeatureAvailable as jest.Mock).mockReturnValue(true);
(toolchainIds.validateToolchainIds as jest.Mock).mockImplementation(
() => undefined
);
(toolchains.configureToolchains as jest.Mock).mockResolvedValue(undefined);
(auth.configureAuthentication as jest.Mock).mockResolvedValue(undefined);
(cache.restore as jest.Mock).mockResolvedValue(undefined);
Expand Down Expand Up @@ -215,7 +223,7 @@ describe('setup action orchestration', () => {
},
'/tmp/java.tar.gz'
);
expect(toolchains.validateToolchainIds).toHaveBeenCalledWith(
expect(toolchainIds.validateToolchainIds).toHaveBeenCalledWith(
[],
'.sdkmanrc',
['file-jdk']
Expand Down Expand Up @@ -342,23 +350,29 @@ describe('setup action orchestration', () => {
);

expect(
(toolchains.configureToolchains as jest.Mock).mock
.invocationCallOrder[0]
).toBeLessThan(
(problemMatcher.configureProblemMatcher as jest.Mock).mock
.invocationCallOrder[0]
).toBeLessThan(
(auth.configureAuthentication as jest.Mock).mock.invocationCallOrder[0]
);
expect(
(problemMatcher.configureProblemMatcher as jest.Mock).mock
.invocationCallOrder[0]
).toBeLessThan(
(auth.configureAuthentication as jest.Mock).mock.invocationCallOrder[0]
(toolchains.configureToolchains as jest.Mock).mock
.invocationCallOrder[0]
);
expect(
(auth.configureAuthentication as jest.Mock).mock.invocationCallOrder[0]
).toBeLessThan(
(mavenArgs.configureMavenArgs as jest.Mock).mock.invocationCallOrder[0]
);
expect(
(toolchains.configureToolchains as jest.Mock).mock
.invocationCallOrder[0]
).toBeLessThan(
(mavenArgs.configureMavenArgs as jest.Mock).mock.invocationCallOrder[0]
);

let completed = false;
runPromise.then(() => {
Expand All @@ -375,6 +389,54 @@ describe('setup action orchestration', () => {
expect(core.setFailed).not.toHaveBeenCalled();
});

it('overlaps independent Maven settings and toolchains configuration', async () => {
inputs.set('distribution', 'temurin');
multilineInputs.set('java-version', ['21']);
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
setupJava: jest.fn(async () => ({
version: '21.0.4+7',
path: '/opt/java/21'
}))
});
const authentication = deferred<void>();
const toolchainConfiguration = deferred<void>();
(auth.configureAuthentication as jest.Mock).mockReturnValue(
authentication.promise
);
(toolchains.configureToolchains as jest.Mock).mockReturnValue(
toolchainConfiguration.promise
);

const runPromise = run();
try {
await tick();
await tick();

expect(auth.configureAuthentication).toHaveBeenCalled();
expect(toolchains.configureToolchains).toHaveBeenCalledWith(
'21',
'temurin',
'/opt/java/21',
undefined
);
expect(mavenArgs.configureMavenArgs).not.toHaveBeenCalled();

authentication.resolve();
await tick();
expect(mavenArgs.configureMavenArgs).not.toHaveBeenCalled();

toolchainConfiguration.resolve();
await runPromise;
} finally {
authentication.resolve();
toolchainConfiguration.resolve();
await runPromise;
}

expect(mavenArgs.configureMavenArgs).toHaveBeenCalled();
expect(core.setFailed).not.toHaveBeenCalled();
});

it('skips cache restoration when the cache feature is unavailable', async () => {
inputs.set('distribution', 'temurin');
inputs.set('cache', 'maven');
Expand Down
Loading
Loading