2023-06-21 17:52:17 +02:00
import {
2026-07-14 08:08:27 +05:30
jest ,
describe ,
it ,
expect ,
beforeEach ,
afterEach ,
afterAll
} from '@jest/globals' ;
import { fileURLToPath } from 'url' ;
import path from 'path' ;
import fs from 'fs' ;
const __dirname = path . dirname ( fileURLToPath ( import . meta . url ) ) ;
// Mock @actions modules before importing anything that depends on them
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/cache' , ( ) = > ( {
saveCache : jest.fn ( ) ,
restoreCache : jest.fn ( ) ,
isFeatureAvailable : jest.fn ( )
} ) ) ;
jest . unstable_mockModule ( '@actions/glob' , ( ) = > ( {
hashFiles : jest.fn ( ) ,
create : jest.fn ( )
} ) ) ;
jest . unstable_mockModule ( '@actions/exec' , ( ) = > ( {
exec : jest.fn ( ) ,
getExecOutput : jest.fn ( )
} ) ) ;
jest . unstable_mockModule ( '@actions/io' , ( ) = > ( {
which : jest.fn ( ) ,
mkdirP : jest.fn ( ) ,
rmRF : jest.fn ( ) ,
mv : jest.fn ( ) ,
cp : jest.fn ( )
} ) ) ;
// Dynamic imports after mocking
const core = await import ( '@actions/core' ) ;
const cache = await import ( '@actions/cache' ) ;
const glob = await import ( '@actions/glob' ) ;
const exec = await import ( '@actions/exec' ) ;
const utils = await import ( '../src/cache-utils.js' ) ;
const cacheUtils = await import ( '../src/cache-utils.js' ) ;
// @ts-ignore - test file outside rootDir
const { MockGlobber } = await import ( './mock/glob-mock.js' ) ;
import type { PackageManagerInfo } from '../src/cache-utils.js' ;
const {
2023-06-21 17:52:17 +02:00
isCacheFeatureAvailable ,
supportedPackageManagers ,
2024-10-21 18:41:32 +02:00
isGhes ,
2023-06-27 13:07:43 +02:00
resetProjectDirectoriesMemoized
2026-07-14 08:08:27 +05:30
} = utils ;
// Helper: mock exec.getExecOutput to simulate getCommandOutput behavior
function mockGetCommandOutput (
spy : jest.Mock ,
fn : ( command : string , cwd? : string ) = > string
) {
spy . mockImplementation ( async ( command : any , _args : any , options : any ) = > ( {
stdout : fn ( command , options ? . cwd ) ,
stderr : '' ,
exitCode : 0
} ) ) ;
}
function mockGetCommandOutputOnce ( spy : jest.Mock , result : string ) {
spy . mockImplementationOnce ( async ( ) = > ( {
stdout : result ,
stderr : '' ,
exitCode : 0
} ) ) ;
}
2021-06-16 09:52:44 +03:00
describe ( 'cache-utils' , ( ) = > {
const versionYarn1 = '1.2.3' ;
2026-07-14 08:08:27 +05:30
let debugSpy : jest.Mock ;
let getExecOutputSpy : jest.Mock ;
let isFeatureAvailable : jest.Mock ;
let info : jest.Mock ;
let warningSpy : jest.Mock ;
let fsRealPathSyncSpy : jest.SpiedFunction < typeof fs.realpathSync > ;
2021-06-16 09:52:44 +03:00
beforeEach ( ( ) = > {
2023-07-13 14:06:23 +02:00
console . log ( '::stop-commands::stoptoken' ) ;
2021-06-16 09:52:44 +03:00
process . env [ 'GITHUB_WORKSPACE' ] = path . join ( __dirname , 'data' ) ;
2026-07-14 08:08:27 +05:30
debugSpy = core . debug as jest . Mock ;
debugSpy . mockImplementation ( ( _msg : any ) = > { } ) ;
2022-03-31 21:10:37 +02:00
2026-07-14 08:08:27 +05:30
info = core . info as jest . Mock ;
warningSpy = core . warning as jest . Mock ;
2022-03-31 21:10:37 +02:00
2026-07-14 08:08:27 +05:30
isFeatureAvailable = cache . isFeatureAvailable as jest . Mock ;
getExecOutputSpy = exec . getExecOutput as jest . Mock ;
2023-07-13 14:06:23 +02:00
fsRealPathSyncSpy = jest . spyOn ( fs , 'realpathSync' ) ;
2026-07-14 08:08:27 +05:30
fsRealPathSyncSpy . mockImplementation ( ( dirName : any ) = > {
2023-07-13 14:06:23 +02:00
return dirName ;
} ) ;
} ) ;
afterEach ( ( ) = > {
jest . resetAllMocks ( ) ;
jest . clearAllMocks ( ) ;
2021-06-16 09:52:44 +03:00
} ) ;
2023-07-13 14:06:23 +02:00
afterAll ( async ( ) = > {
console . log ( '::stoptoken::' ) ;
jest . restoreAllMocks ( ) ;
} , 100000 ) ;
2021-06-16 09:52:44 +03:00
describe ( 'getPackageManagerInfo' , ( ) = > {
2021-06-29 13:34:35 +03:00
it . each < [ string , PackageManagerInfo | null ] > ( [
2021-06-16 09:52:44 +03:00
[ 'npm' , utils . supportedPackageManagers . npm ] ,
2021-06-30 16:44:51 +01:00
[ 'pnpm' , utils . supportedPackageManagers . pnpm ] ,
2023-06-21 17:52:17 +02:00
[ 'yarn' , utils . supportedPackageManagers . yarn ] ,
2021-06-16 09:52:44 +03:00
[ 'yarn1' , null ] ,
[ 'yarn2' , null ] ,
[ 'npm7' , null ]
] ) ( 'getPackageManagerInfo for %s is %o' , async ( packageManager , result ) = > {
2026-07-14 08:08:27 +05:30
mockGetCommandOutputOnce ( getExecOutputSpy , versionYarn1 ) ;
2021-06-16 09:52:44 +03:00
await expect ( utils . getPackageManagerInfo ( packageManager ) ) . resolves . toBe (
result
) ;
} ) ;
} ) ;
2022-03-31 21:10:37 +02:00
it ( 'isCacheFeatureAvailable for GHES is false' , ( ) = > {
isFeatureAvailable . mockImplementation ( ( ) = > false ) ;
process . env [ 'GITHUB_SERVER_URL' ] = 'https://www.test.com' ;
2022-12-09 12:05:59 +01:00
expect ( isCacheFeatureAvailable ( ) ) . toBeFalsy ( ) ;
expect ( warningSpy ) . toHaveBeenCalledWith (
2022-03-31 21:10:37 +02:00
'Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.'
) ;
} ) ;
it ( 'isCacheFeatureAvailable for GHES has an interhal error' , ( ) = > {
isFeatureAvailable . mockImplementation ( ( ) = > false ) ;
process . env [ 'GITHUB_SERVER_URL' ] = '' ;
isCacheFeatureAvailable ( ) ;
expect ( warningSpy ) . toHaveBeenCalledWith (
'The runner was not able to contact the cache service. Caching will be skipped'
) ;
} ) ;
it ( 'isCacheFeatureAvailable for GHES is available' , ( ) = > {
isFeatureAvailable . mockImplementation ( ( ) = > true ) ;
expect ( isCacheFeatureAvailable ( ) ) . toStrictEqual ( true ) ;
} ) ;
2021-06-16 09:52:44 +03:00
afterEach ( ( ) = > {
2022-03-31 21:10:37 +02:00
process . env [ 'GITHUB_SERVER_URL' ] = '' ;
2021-06-16 09:52:44 +03:00
jest . resetAllMocks ( ) ;
jest . clearAllMocks ( ) ;
} ) ;
2023-06-21 17:52:17 +02:00
describe ( 'getCacheDirectoriesPaths' , ( ) = > {
2026-07-14 08:08:27 +05:30
let existsSpy : jest.SpiedFunction < typeof fs.existsSync > ;
let lstatSpy : jest.SpiedFunction < typeof fs.lstatSync > ;
let globCreateSpy : jest.Mock ;
2023-06-21 17:52:17 +02:00
beforeEach ( ( ) = > {
existsSpy = jest . spyOn ( fs , 'existsSync' ) ;
existsSpy . mockImplementation ( ( ) = > true ) ;
lstatSpy = jest . spyOn ( fs , 'lstatSync' ) ;
2026-07-14 08:08:27 +05:30
lstatSpy . mockImplementation (
( _arg : any ) = > ( { isDirectory : ( ) = > true } ) as any
) ;
2023-06-21 17:52:17 +02:00
2026-07-14 08:08:27 +05:30
globCreateSpy = glob . create as jest . Mock ;
globCreateSpy . mockImplementation ( ( _pattern : any ) = >
MockGlobber . create ( [ '/foo' , '/bar' ] )
2023-06-21 17:52:17 +02:00
) ;
2023-06-27 13:07:43 +02:00
resetProjectDirectoriesMemoized ( ) ;
2023-06-21 17:52:17 +02:00
} ) ;
afterEach ( ( ) = > {
existsSpy . mockRestore ( ) ;
lstatSpy . mockRestore ( ) ;
} ) ;
it . each ( [
[ supportedPackageManagers . npm , '' ] ,
[ supportedPackageManagers . npm , '/dir/file.lock' ] ,
[ supportedPackageManagers . npm , '/**/file.lock' ] ,
[ supportedPackageManagers . pnpm , '' ] ,
[ supportedPackageManagers . pnpm , '/dir/file.lock' ] ,
[ supportedPackageManagers . pnpm , '/**/file.lock' ]
] ) (
'getCacheDirectoriesPaths should return one dir for non yarn' ,
async ( packageManagerInfo , cacheDependency ) = > {
2026-07-14 08:08:27 +05:30
mockGetCommandOutput ( getExecOutputSpy , ( ) = > 'foo' ) ;
2023-06-21 17:52:17 +02:00
const dirs = await cacheUtils . getCacheDirectories (
packageManagerInfo ,
cacheDependency
) ;
expect ( dirs ) . toEqual ( [ 'foo' ] ) ;
2026-07-14 08:08:27 +05:30
expect ( getExecOutputSpy ) . toHaveBeenCalledTimes ( 1 ) ;
2023-06-21 17:52:17 +02:00
}
) ;
it ( 'getCacheDirectoriesPaths should return one dir for yarn without cacheDependency' , async ( ) = > {
2026-07-14 08:08:27 +05:30
mockGetCommandOutput ( getExecOutputSpy , ( ) = > 'foo' ) ;
2023-06-21 17:52:17 +02:00
const dirs = await cacheUtils . getCacheDirectories (
supportedPackageManagers . yarn ,
''
) ;
expect ( dirs ) . toEqual ( [ 'foo' ] ) ;
} ) ;
it . each ( [
[ supportedPackageManagers . npm , '' ] ,
[ supportedPackageManagers . npm , '/dir/file.lock' ] ,
[ supportedPackageManagers . npm , '/**/file.lock' ] ,
[ supportedPackageManagers . pnpm , '' ] ,
[ supportedPackageManagers . pnpm , '/dir/file.lock' ] ,
[ supportedPackageManagers . pnpm , '/**/file.lock' ] ,
[ supportedPackageManagers . yarn , '' ] ,
[ supportedPackageManagers . yarn , '/dir/file.lock' ] ,
[ supportedPackageManagers . yarn , '/**/file.lock' ]
] ) (
'getCacheDirectoriesPaths should throw for getCommandOutput returning empty' ,
async ( packageManagerInfo , cacheDependency ) = > {
2026-07-14 08:08:27 +05:30
mockGetCommandOutput ( getExecOutputSpy , ( command : string ) = >
2023-06-21 17:52:17 +02:00
command . includes ( 'version' ) ? '1.' : ''
) ;
await expect (
cacheUtils . getCacheDirectories ( packageManagerInfo , cacheDependency )
2026-07-14 08:08:27 +05:30
) . rejects . toThrow ( ) ;
2023-06-21 17:52:17 +02:00
}
) ;
it . each ( [
[ supportedPackageManagers . yarn , '/dir/file.lock' ] ,
[ supportedPackageManagers . yarn , '/**/file.lock' ]
] ) (
'getCacheDirectoriesPaths should nothrow in case of having not directories' ,
async ( packageManagerInfo , cacheDependency ) = > {
2026-07-14 08:08:27 +05:30
lstatSpy . mockImplementation (
( _arg : any ) = > ( { isDirectory : ( ) = > false } ) as any
) ;
2023-06-21 17:52:17 +02:00
await cacheUtils . getCacheDirectories (
packageManagerInfo ,
cacheDependency
) ;
expect ( warningSpy ) . toHaveBeenCalledTimes ( 1 ) ;
expect ( warningSpy ) . toHaveBeenCalledWith (
` No existing directories found containing cache-dependency-path=" ${ cacheDependency } " `
) ;
}
) ;
it . each ( [ '1.1.1' , '2.2.2' ] ) (
'getCacheDirectoriesPaths yarn v%s should return one dir without cacheDependency' ,
async version = > {
2026-07-14 08:08:27 +05:30
mockGetCommandOutputOnce ( getExecOutputSpy , version ) ;
mockGetCommandOutputOnce ( getExecOutputSpy , ` foo ${ version } ` ) ;
2023-06-21 17:52:17 +02:00
const dirs = await cacheUtils . getCacheDirectories (
supportedPackageManagers . yarn ,
''
) ;
expect ( dirs ) . toEqual ( [ ` foo ${ version } ` ] ) ;
}
) ;
it . each ( [ '1.1.1' , '2.2.2' ] ) (
'getCacheDirectoriesPaths yarn v%s should return 2 dirs with globbed cacheDependency' ,
async version = > {
let dirNo = 1 ;
2026-07-14 08:08:27 +05:30
mockGetCommandOutput ( getExecOutputSpy , ( command : string ) = >
2023-06-21 17:52:17 +02:00
command . includes ( 'version' ) ? version : ` file_ ${ version } _ ${ dirNo ++ } `
) ;
2026-07-14 08:08:27 +05:30
globCreateSpy . mockImplementation ( ( _pattern : any ) = >
MockGlobber . create ( [ '/tmp/dir1/file' , '/tmp/dir2/file' ] )
2023-06-21 17:52:17 +02:00
) ;
const dirs = await cacheUtils . getCacheDirectories (
supportedPackageManagers . yarn ,
'/tmp/**/file'
) ;
expect ( dirs ) . toEqual ( [ ` file_ ${ version } _1 ` , ` file_ ${ version } _2 ` ] ) ;
}
) ;
it . each ( [ '1.1.1' , '2.2.2' ] ) (
'getCacheDirectoriesPaths yarn v%s should return 2 dirs with globbed cacheDependency expanding to duplicates' ,
async version = > {
let dirNo = 1 ;
2026-07-14 08:08:27 +05:30
mockGetCommandOutput ( getExecOutputSpy , ( command : string ) = >
2023-06-21 17:52:17 +02:00
command . includes ( 'version' ) ? version : ` file_ ${ version } _ ${ dirNo ++ } `
) ;
2026-07-14 08:08:27 +05:30
globCreateSpy . mockImplementation ( ( _pattern : any ) = >
MockGlobber . create ( [
'/tmp/dir1/file' ,
'/tmp/dir2/file' ,
'/tmp/dir1/file'
] )
2023-06-21 17:52:17 +02:00
) ;
const dirs = await cacheUtils . getCacheDirectories (
supportedPackageManagers . yarn ,
'/tmp/**/file'
) ;
expect ( dirs ) . toEqual ( [ ` file_ ${ version } _1 ` , ` file_ ${ version } _2 ` ] ) ;
}
) ;
it . each ( [ '1.1.1' , '2.2.2' ] ) (
'getCacheDirectoriesPaths yarn v%s should return 2 uniq dirs despite duplicate cache directories' ,
async version = > {
let dirNo = 1 ;
2026-07-14 08:08:27 +05:30
mockGetCommandOutput ( getExecOutputSpy , ( command : string ) = >
2023-06-21 17:52:17 +02:00
command . includes ( 'version' )
? version
: ` file_ ${ version } _ ${ dirNo ++ % 2 } `
) ;
2026-07-14 08:08:27 +05:30
globCreateSpy . mockImplementation ( ( _pattern : any ) = >
MockGlobber . create ( [
'/tmp/dir1/file' ,
'/tmp/dir2/file' ,
'/tmp/dir3/file'
] )
2023-06-21 17:52:17 +02:00
) ;
const dirs = await cacheUtils . getCacheDirectories (
supportedPackageManagers . yarn ,
'/tmp/**/file'
) ;
expect ( dirs ) . toEqual ( [ ` file_ ${ version } _1 ` , ` file_ ${ version } _0 ` ] ) ;
2026-07-14 08:08:27 +05:30
expect ( getExecOutputSpy ) . toHaveBeenCalledTimes ( 6 ) ;
expect ( getExecOutputSpy ) . toHaveBeenCalledWith (
2023-06-21 17:52:17 +02:00
'yarn --version' ,
2026-07-14 08:08:27 +05:30
undefined ,
expect . objectContaining ( { cwd : '/tmp/dir1' } )
2023-06-21 17:52:17 +02:00
) ;
2026-07-14 08:08:27 +05:30
expect ( getExecOutputSpy ) . toHaveBeenCalledWith (
2023-06-21 17:52:17 +02:00
'yarn --version' ,
2026-07-14 08:08:27 +05:30
undefined ,
expect . objectContaining ( { cwd : '/tmp/dir2' } )
2023-06-21 17:52:17 +02:00
) ;
2026-07-14 08:08:27 +05:30
expect ( getExecOutputSpy ) . toHaveBeenCalledWith (
2023-06-21 17:52:17 +02:00
'yarn --version' ,
2026-07-14 08:08:27 +05:30
undefined ,
expect . objectContaining ( { cwd : '/tmp/dir3' } )
2023-06-21 17:52:17 +02:00
) ;
2026-07-14 08:08:27 +05:30
expect ( getExecOutputSpy ) . toHaveBeenCalledWith (
2023-06-21 17:52:17 +02:00
version . startsWith ( '1.' )
? 'yarn cache dir'
: 'yarn config get cacheFolder' ,
2026-07-14 08:08:27 +05:30
undefined ,
expect . objectContaining ( { cwd : '/tmp/dir1' } )
2023-06-21 17:52:17 +02:00
) ;
2026-07-14 08:08:27 +05:30
expect ( getExecOutputSpy ) . toHaveBeenCalledWith (
2023-06-21 17:52:17 +02:00
version . startsWith ( '1.' )
? 'yarn cache dir'
: 'yarn config get cacheFolder' ,
2026-07-14 08:08:27 +05:30
undefined ,
expect . objectContaining ( { cwd : '/tmp/dir2' } )
2023-06-21 17:52:17 +02:00
) ;
2026-07-14 08:08:27 +05:30
expect ( getExecOutputSpy ) . toHaveBeenCalledWith (
2023-06-21 17:52:17 +02:00
version . startsWith ( '1.' )
? 'yarn cache dir'
: 'yarn config get cacheFolder' ,
2026-07-14 08:08:27 +05:30
undefined ,
expect . objectContaining ( { cwd : '/tmp/dir3' } )
2023-06-21 17:52:17 +02:00
) ;
}
) ;
it . each ( [ '1.1.1' , '2.2.2' ] ) (
'getCacheDirectoriesPaths yarn v%s should return 4 dirs with multiple globs' ,
async version = > {
const cacheDependencyPath = ` /tmp/dir1/file
/ t m p / d i r 2 / f i l e
/tmp/ * * / f i l e
` ;
2026-07-14 08:08:27 +05:30
globCreateSpy . mockImplementation ( ( _pattern : any ) = >
MockGlobber . create ( [
'/tmp/dir1/file' ,
'/tmp/dir2/file' ,
'/tmp/dir3/file' ,
'/tmp/dir4/file'
] )
2023-06-21 17:52:17 +02:00
) ;
let dirNo = 1 ;
2026-07-14 08:08:27 +05:30
mockGetCommandOutput ( getExecOutputSpy , ( command : string ) = >
2023-06-21 17:52:17 +02:00
command . includes ( 'version' ) ? version : ` file_ ${ version } _ ${ dirNo ++ } `
) ;
const dirs = await cacheUtils . getCacheDirectories (
supportedPackageManagers . yarn ,
cacheDependencyPath
) ;
expect ( dirs ) . toEqual ( [
` file_ ${ version } _1 ` ,
` file_ ${ version } _2 ` ,
` file_ ${ version } _3 ` ,
` file_ ${ version } _4 `
] ) ;
}
) ;
} ) ;
2021-06-16 09:52:44 +03:00
} ) ;
2024-10-21 18:41:32 +02:00
describe ( 'isGhes' , ( ) = > {
const pristineEnv = process . env ;
beforeEach ( ( ) = > {
jest . resetModules ( ) ;
process . env = { . . . pristineEnv } ;
} ) ;
afterAll ( ( ) = > {
process . env = pristineEnv ;
} ) ;
it ( 'returns false when the GITHUB_SERVER_URL environment variable is not defined' , ( ) = > {
delete process . env [ 'GITHUB_SERVER_URL' ] ;
expect ( isGhes ( ) ) . toBeFalsy ( ) ;
} ) ;
it ( 'returns false when the GITHUB_SERVER_URL environment variable is set to github.com' , ( ) = > {
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' , ( ) = > {
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' , ( ) = > {
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' , ( ) = > {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://src.onpremise.fabrikam.com' ;
expect ( isGhes ( ) ) . toBeTruthy ( ) ;
} ) ;
} ) ;