2026-07-08 14:45:00 +05:30
import {
jest ,
describe ,
it ,
expect ,
beforeEach ,
afterAll ,
afterEach
} from '@jest/globals' ;
import { fileURLToPath } from 'url' ;
2025-11-25 20:36:29 +01:00
import * as fs from 'fs' ;
import * as path from 'path' ;
2026-07-08 14:45:00 +05:30
const __dirname = path . dirname ( fileURLToPath ( import . meta . url ) ) ;
// Mock @actions/cache
jest . unstable_mockModule ( '@actions/cache' , ( ) = > ( {
isFeatureAvailable : jest.fn ( ) ,
saveCache : jest.fn ( ) ,
restoreCache : jest.fn ( )
} ) ) ;
// Mock @actions/core
jest . unstable_mockModule ( '@actions/core' , ( ) = > ( {
getInput : jest.fn ( ) ,
getBooleanInput : jest.fn ( ) ,
getMultilineInput : jest.fn ( ) ,
setOutput : jest.fn ( ) ,
setFailed : jest.fn ( ) ,
warning : jest.fn ( ) ,
info : jest.fn ( ) ,
debug : jest.fn ( ) ,
error : jest.fn ( ) ,
notice : jest.fn ( ) ,
startGroup : jest.fn ( ) ,
endGroup : jest.fn ( ) ,
addPath : jest.fn ( ) ,
exportVariable : jest.fn ( ) ,
saveState : jest.fn ( ) ,
getState : jest.fn ( ) ,
setSecret : jest.fn ( ) ,
isDebug : jest.fn ( ( ) = > false ) ,
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 cache = await import ( '@actions/cache' ) ;
const core = await import ( '@actions/core' ) ;
const {
2023-04-10 10:29:19 +02:00
convertVersionToSemver ,
Implement pagination with link headers for Adoptium based apis (#1014)
* Use Link headers for Adoptium pagination
* Fix nullable pagination URL types and rebuild dist
* Add 1000-page safeguard for JetBrains pagination
* Adjust plan for pagination safeguard scope
* Move pagination safeguard to non-JetBrains installers
* Add 1000-page safeguard to Adopt Temurin and Semeru pagination
* Fix Prettier formatting in adopt, semeru, and temurin installer files
* Fix CI audit failure by updating vulnerable transitive deps
* Address PR review: RFC-compliant Link parsing, SSRF validation, centralized constant
- Make getNextPageUrlFromLinkHeader RFC 8288 compliant by splitting
link-values and checking for rel=next anywhere in the parameters,
not just as the first parameter after the semicolon.
- Add validatePaginationUrl utility to reject pagination URLs that
point to unexpected origins (SSRF mitigation).
- Centralize MAX_PAGINATION_PAGES in util.ts instead of duplicating
across Adopt, Semeru, and Temurin installers.
- Add tests for rel not being the first parameter, and for URL
origin validation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address code review feedback on pagination implementation
- Tighten rel regex with word boundary to prevent false positives
(e.g., rel="nextsomething" no longer matches).
- Use parsed.origin comparison in validatePaginationUrl to correctly
handle explicit default ports (e.g., :443 for HTTPS).
- Fix pagination safeguard tests to use same-origin URLs so they
actually exercise the 1000-page limit instead of being rejected
by origin validation on the first request.
- Add test for rel="nextsomething" not matching.
- Add test for explicit default port acceptance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix prettier formatting in util.test.ts
* Rebuild dist/ to fix check-dist CI failure
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-12 11:50:16 +01:00
getNextPageUrlFromLinkHeader ,
2025-11-25 20:36:29 +01:00
getVersionFromFileContent ,
2023-04-10 10:29:19 +02:00
isVersionSatisfies ,
2024-10-21 19:57:52 +02:00
isCacheFeatureAvailable ,
Implement pagination with link headers for Adoptium based apis (#1014)
* Use Link headers for Adoptium pagination
* Fix nullable pagination URL types and rebuild dist
* Add 1000-page safeguard for JetBrains pagination
* Adjust plan for pagination safeguard scope
* Move pagination safeguard to non-JetBrains installers
* Add 1000-page safeguard to Adopt Temurin and Semeru pagination
* Fix Prettier formatting in adopt, semeru, and temurin installer files
* Fix CI audit failure by updating vulnerable transitive deps
* Address PR review: RFC-compliant Link parsing, SSRF validation, centralized constant
- Make getNextPageUrlFromLinkHeader RFC 8288 compliant by splitting
link-values and checking for rel=next anywhere in the parameters,
not just as the first parameter after the semicolon.
- Add validatePaginationUrl utility to reject pagination URLs that
point to unexpected origins (SSRF mitigation).
- Centralize MAX_PAGINATION_PAGES in util.ts instead of duplicating
across Adopt, Semeru, and Temurin installers.
- Add tests for rel not being the first parameter, and for URL
origin validation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address code review feedback on pagination implementation
- Tighten rel regex with word boundary to prevent false positives
(e.g., rel="nextsomething" no longer matches).
- Use parsed.origin comparison in validatePaginationUrl to correctly
handle explicit default ports (e.g., :443 for HTTPS).
- Fix pagination safeguard tests to use same-origin URLs so they
actually exercise the 1000-page limit instead of being rejected
by origin validation on the first request.
- Add test for rel="nextsomething" not matching.
- Add test for explicit default port acceptance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix prettier formatting in util.test.ts
* Rebuild dist/ to fix check-dist CI failure
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-12 11:50:16 +01:00
isGhes ,
validatePaginationUrl
2026-07-08 14:45:00 +05:30
} = await import ( '../src/util.js' ) ;
2021-04-05 13:02:27 +03:00
describe ( 'isVersionSatisfies' , ( ) = > {
it . each ( [
[ 'x' , '11.0.0' , true ] ,
[ '3' , '3.7.1' , true ] ,
[ '3' , '3.7.2' , true ] ,
[ '3' , '3.7.2+4' , true ] ,
[ '2.5' , '2.5.0' , true ] ,
[ '2.5' , '2.5.0+1' , true ] ,
[ '2.5' , '2.6.1' , false ] ,
[ '2.5.1' , '2.5.0' , false ] ,
[ '2.5.1+3' , '2.5.0' , false ] ,
[ '2.5.1+3' , '2.5.1+3' , true ] ,
[ '2.5.1+3' , '2.5.1+2' , false ] ,
[ '15.0.0+14' , '15.0.0+14.1.202003190635' , false ] ,
2026-06-18 05:47:02 +02:00
[ '15.0.0+14.1.202003190635' , '15.0.0+14.1.202003190635' , true ] ,
// 4-segment versions (e.g. JetBrains Runtime '17.0.8.1+1080.1') are not
// valid semver — they should be rejected, not throw.
[ '25.0.3+480.61' , '17.0.8.1+1080.1' , false ] ,
[ '17' , '17.0.8.1+1080.1' , false ]
2023-03-09 14:49:35 +02:00
] ) (
'%s, %s -> %s' ,
( inputRange : string , inputVersion : string , expected : boolean ) = > {
const actual = isVersionSatisfies ( inputRange , inputVersion ) ;
expect ( actual ) . toBe ( expected ) ;
}
) ;
2020-05-02 04:33:15 -07:00
} ) ;
2022-04-01 00:39:57 +05:30
describe ( 'isCacheFeatureAvailable' , ( ) = > {
it ( 'isCacheFeatureAvailable disabled on GHES' , ( ) = > {
2026-07-08 14:45:00 +05:30
( cache . isFeatureAvailable as jest . Mock < any > ) . mockImplementation (
( ) = > false
) ;
const infoMock = core . warning as jest . Mock ;
2022-12-16 23:04:57 +09:00
const message =
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.' ;
2022-04-01 00:39:57 +05:30
try {
process . env [ 'GITHUB_SERVER_URL' ] = 'http://example.com' ;
2022-12-16 23:04:57 +09:00
expect ( isCacheFeatureAvailable ( ) ) . toBeFalsy ( ) ;
expect ( infoMock ) . toHaveBeenCalledWith ( message ) ;
2022-04-01 00:39:57 +05:30
} finally {
delete process . env [ 'GITHUB_SERVER_URL' ] ;
}
} ) ;
it ( 'isCacheFeatureAvailable disabled on dotcom' , ( ) = > {
2026-07-08 14:45:00 +05:30
( cache . isFeatureAvailable as jest . Mock < any > ) . mockImplementation (
( ) = > false
) ;
const infoMock = core . warning as jest . Mock ;
2023-03-09 14:49:35 +02:00
const message =
'The runner was not able to contact the cache service. Caching will be skipped' ;
2022-04-01 00:39:57 +05:30
try {
process . env [ 'GITHUB_SERVER_URL' ] = 'http://github.com' ;
expect ( isCacheFeatureAvailable ( ) ) . toBe ( false ) ;
expect ( infoMock ) . toHaveBeenCalledWith ( message ) ;
} finally {
delete process . env [ 'GITHUB_SERVER_URL' ] ;
}
} ) ;
it ( 'isCacheFeatureAvailable is enabled' , ( ) = > {
2026-07-08 14:45:00 +05:30
( cache . isFeatureAvailable as jest . Mock < any > ) . mockImplementation ( ( ) = > true ) ;
2022-04-01 00:39:57 +05:30
expect ( isCacheFeatureAvailable ( ) ) . toBe ( true ) ;
} ) ;
2026-07-08 14:45:00 +05:30
afterEach ( ( ) = > {
jest . resetAllMocks ( ) ;
jest . clearAllMocks ( ) ;
} ) ;
2022-04-01 00:39:57 +05:30
} ) ;
2023-04-10 10:29:19 +02:00
describe ( 'convertVersionToSemver' , ( ) = > {
it . each ( [
[ '12' , '12' ] ,
[ '12.0' , '12.0' ] ,
[ '12.0.2' , '12.0.2' ] ,
[ '12.0.2.1' , '12.0.2+1' ] ,
[ '12.0.2.1.0' , '12.0.2+1.0' ]
] ) ( '%s -> %s' , ( input : string , expected : string ) = > {
const actual = convertVersionToSemver ( input ) ;
expect ( actual ) . toBe ( expected ) ;
} ) ;
} ) ;
2024-10-21 19:57:52 +02:00
Implement pagination with link headers for Adoptium based apis (#1014)
* Use Link headers for Adoptium pagination
* Fix nullable pagination URL types and rebuild dist
* Add 1000-page safeguard for JetBrains pagination
* Adjust plan for pagination safeguard scope
* Move pagination safeguard to non-JetBrains installers
* Add 1000-page safeguard to Adopt Temurin and Semeru pagination
* Fix Prettier formatting in adopt, semeru, and temurin installer files
* Fix CI audit failure by updating vulnerable transitive deps
* Address PR review: RFC-compliant Link parsing, SSRF validation, centralized constant
- Make getNextPageUrlFromLinkHeader RFC 8288 compliant by splitting
link-values and checking for rel=next anywhere in the parameters,
not just as the first parameter after the semicolon.
- Add validatePaginationUrl utility to reject pagination URLs that
point to unexpected origins (SSRF mitigation).
- Centralize MAX_PAGINATION_PAGES in util.ts instead of duplicating
across Adopt, Semeru, and Temurin installers.
- Add tests for rel not being the first parameter, and for URL
origin validation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address code review feedback on pagination implementation
- Tighten rel regex with word boundary to prevent false positives
(e.g., rel="nextsomething" no longer matches).
- Use parsed.origin comparison in validatePaginationUrl to correctly
handle explicit default ports (e.g., :443 for HTTPS).
- Fix pagination safeguard tests to use same-origin URLs so they
actually exercise the 1000-page limit instead of being rejected
by origin validation on the first request.
- Add test for rel="nextsomething" not matching.
- Add test for explicit default port acceptance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix prettier formatting in util.test.ts
* Rebuild dist/ to fix check-dist CI failure
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-12 11:50:16 +01:00
describe ( 'getNextPageUrlFromLinkHeader' , ( ) = > {
it . each ( [
[
{
link : '<https://api.adoptium.net/v3/info/release_versions?page=1&page_size=10>; rel="next"'
} ,
'https://api.adoptium.net/v3/info/release_versions?page=1&page_size=10'
] ,
[
{
Link : '<https://example.com/last?page=5>; rel="last", <https://example.com/next?page=2>; rel="next"'
} ,
'https://example.com/next?page=2'
] ,
[
{
link : '<https://api.adoptium.net/v3/versions?page=3>; type="application/json"; rel="next"'
} ,
'https://api.adoptium.net/v3/versions?page=3'
] ,
[ { link : '<https://example.com/last?page=5>; rel="last"' } , null ] ,
[ { link : '<https://example.com/page?p=2>; rel="nextsomething"' } , null ] ,
[ undefined , null ]
] ) ( 'returns %s -> %s' , ( headers , expected ) = > {
expect ( getNextPageUrlFromLinkHeader ( headers ) ) . toBe ( expected ) ;
} ) ;
} ) ;
describe ( 'validatePaginationUrl' , ( ) = > {
it ( 'accepts URL with matching origin' , ( ) = > {
expect (
validatePaginationUrl (
'https://api.adoptium.net/v3/assets?page=2' ,
'https://api.adoptium.net'
)
) . toBe ( true ) ;
} ) ;
it ( 'rejects URL with different host' , ( ) = > {
expect (
validatePaginationUrl (
'https://evil.example.com/steal?data=1' ,
'https://api.adoptium.net'
)
) . toBe ( false ) ;
} ) ;
it ( 'rejects URL with different protocol' , ( ) = > {
expect (
validatePaginationUrl (
'http://api.adoptium.net/v3/assets?page=2' ,
'https://api.adoptium.net'
)
) . toBe ( false ) ;
} ) ;
it ( 'returns false for invalid URL' , ( ) = > {
expect ( validatePaginationUrl ( 'not-a-url' , 'https://api.adoptium.net' ) ) . toBe (
false
) ;
} ) ;
it ( 'accepts URL with explicit default port' , ( ) = > {
expect (
validatePaginationUrl (
'https://api.adoptium.net:443/v3/assets?page=2' ,
'https://api.adoptium.net'
)
) . toBe ( true ) ;
} ) ;
} ) ;
2025-11-25 20:36:29 +01:00
describe ( 'getVersionFromFileContent' , ( ) = > {
describe ( '.sdkmanrc' , ( ) = > {
it . each ( [
2026-07-07 20:49:12 +02:00
[ 'java=11.0.20.1-tem' , '11.0.20' , 'temurin' ] ,
[ 'java = 11.0.20.1-tem' , '11.0.20' , 'temurin' ] ,
[ 'java=11.0.20.1-tem # a comment in sdkmanrc' , '11.0.20' , 'temurin' ] ,
[ 'java=11.0.20.1-tem\n#java=21.0.20.1-tem\n' , '11.0.20' , 'temurin' ] , // choose first match
[ 'java=11.0.20.1-tem\njava=21.0.20.1-tem\n' , '11.0.20' , 'temurin' ] , // choose first match
[ '#java=11.0.20.1-tem\njava=21.0.20.1-tem\n' , '21.0.20' , 'temurin' ] , // first one is 'commented' in .sdkmanrc
[ 'java=21.0.5-zulu' , '21.0.5' , 'zulu' ] ,
[ 'java=17.0.13-albba' , '17.0.13' , 'dragonwell' ] ,
[ 'java=17.0.13-amzn' , '17' , 'corretto' ] ,
[ 'java=21.0.5-graal' , '21.0.5' , 'graalvm' ] ,
[ 'java=17.0.9-graalce' , '17.0.9' , 'graalvm' ] ,
[ 'java=11.0.25-librca' , '11.0.25' , 'liberica' ] ,
[ 'java=11.0.25-ms' , '11.0.25' , 'microsoft' ] ,
[ 'java=21.0.5-oracle' , '21.0.5' , 'oracle' ] ,
[ 'java=11.0.25-sapmchn' , '11.0.25' , 'sapmachine' ] ,
[ 'java=21.0.5-jbr' , '21.0.5' , 'jetbrains' ] ,
[ 'java=11.0.25-sem' , '11.0.25' , 'semeru' ] ,
[ 'java=17.0.13-dragonwell' , '17.0.13' , 'dragonwell' ] ,
[ 'java=21.0.5-kona' , '21.0.5' , 'kona' ]
] ) (
'parsing %s should return version %s and distribution %s' ,
( content : string , expectedVersion : string , expectedDist : string ) = > {
const actual = getVersionFromFileContent (
content ,
'openjdk' ,
'.sdkmanrc'
) ;
expect ( actual ? . version ) . toBe ( expectedVersion ) ;
expect ( actual ? . distribution ) . toBe ( expectedDist ) ;
}
) ;
it ( 'should warn and return undefined distribution for unknown identifier' , ( ) = > {
const warnSpy = jest . spyOn ( core , 'warning' ) ;
const actual = getVersionFromFileContent (
'java=21.0.5-unknown' ,
'temurin' ,
'.sdkmanrc'
) ;
expect ( actual ? . version ) . toBe ( '21.0.5' ) ;
expect ( actual ? . distribution ) . toBeUndefined ( ) ;
expect ( warnSpy ) . toHaveBeenCalledWith (
expect . stringContaining ( 'Unknown SDKMAN distribution identifier' )
) ;
} ) ;
it ( 'should return version without distribution when no suffix provided' , ( ) = > {
const actual = getVersionFromFileContent (
'java=11.0.20' ,
'temurin' ,
'.sdkmanrc'
) ;
expect ( actual ? . version ) . toBe ( '11.0.20' ) ;
expect ( actual ? . distribution ) . toBeUndefined ( ) ;
2025-11-25 20:36:29 +01:00
} ) ;
describe ( 'known versions' , ( ) = > {
const csv = fs . readFileSync (
path . join ( __dirname , 'data/sdkman-java-versions.csv' ) ,
'utf8'
) ;
const versions = csv . split ( '\n' ) . map ( r = > r . split ( ', ' ) ) ;
it . each ( versions ) (
'parsing %s should return %s' ,
( sdkmanJavaVersion : string , expected : string ) = > {
const asContent = ` java= ${ sdkmanJavaVersion } ` ;
const actual = getVersionFromFileContent (
asContent ,
'openjdk' ,
'.sdkmanrc'
) ;
2026-07-07 20:49:12 +02:00
expect ( actual ? . version ) . toBe ( expected ) ;
2025-11-25 20:36:29 +01:00
}
) ;
} ) ;
} ) ;
} ) ;
2024-10-21 19:57:52 +02:00
describe ( 'isGhes' , ( ) = > {
const pristineEnv = process . env ;
beforeEach ( ( ) = > {
process . env = { . . . pristineEnv } ;
} ) ;
afterAll ( ( ) = > {
process . env = pristineEnv ;
} ) ;
it ( 'returns false when the GITHUB_SERVER_URL environment variable is not defined' , async ( ) = > {
delete process . env [ 'GITHUB_SERVER_URL' ] ;
expect ( isGhes ( ) ) . toBeFalsy ( ) ;
} ) ;
it ( 'returns false when the GITHUB_SERVER_URL environment variable is set to github.com' , async ( ) = > {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://github.com' ;
expect ( isGhes ( ) ) . toBeFalsy ( ) ;
} ) ;
it ( 'returns false when the GITHUB_SERVER_URL environment variable is set to a GitHub Enterprise Cloud-style URL' , async ( ) = > {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://contoso.ghe.com' ;
expect ( isGhes ( ) ) . toBeFalsy ( ) ;
} ) ;
it ( 'returns false when the GITHUB_SERVER_URL environment variable has a .localhost suffix' , async ( ) = > {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://mock-github.localhost' ;
expect ( isGhes ( ) ) . toBeFalsy ( ) ;
} ) ;
it ( 'returns true when the GITHUB_SERVER_URL environment variable is set to some other URL' , async ( ) = > {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://src.onpremise.fabrikam.com' ;
expect ( isGhes ( ) ) . toBeTruthy ( ) ;
} ) ;
} ) ;