2020-05-15 16:18:50 +00:00
|
|
|
import * as core from '@actions/core'
|
2020-05-06 15:10:18 +00:00
|
|
|
import * as path from 'path'
|
2024-11-21 12:01:44 +00:00
|
|
|
import { saveCache } from '../src/cache'
|
2020-05-06 15:10:18 +00:00
|
|
|
import * as cacheHttpClient from '../src/internal/cacheHttpClient'
|
|
|
|
import * as cacheUtils from '../src/internal/cacheUtils'
|
2024-11-21 12:01:44 +00:00
|
|
|
import * as config from '../src/internal/config'
|
|
|
|
import { CacheFilename, CompressionMethod } from '../src/internal/constants'
|
2020-05-06 15:10:18 +00:00
|
|
|
import * as tar from '../src/internal/tar'
|
2024-11-21 12:01:44 +00:00
|
|
|
import { TypedResponse } from '@actions/http-client/lib/interfaces'
|
2022-04-04 10:51:58 +00:00
|
|
|
import {
|
|
|
|
ReserveCacheResponse,
|
|
|
|
ITypedResponseWithError
|
|
|
|
} from '../src/internal/contracts'
|
2024-11-21 12:01:44 +00:00
|
|
|
import { HttpClientError } from '@actions/http-client'
|
2020-05-06 15:10:18 +00:00
|
|
|
|
|
|
|
jest.mock('../src/internal/cacheHttpClient')
|
|
|
|
jest.mock('../src/internal/cacheUtils')
|
2024-11-21 12:01:44 +00:00
|
|
|
jest.mock('../src/internal/config')
|
2020-05-06 15:10:18 +00:00
|
|
|
jest.mock('../src/internal/tar')
|
|
|
|
|
|
|
|
beforeAll(() => {
|
2024-11-21 12:01:44 +00:00
|
|
|
jest.spyOn(console, 'log').mockImplementation(() => { })
|
|
|
|
jest.spyOn(core, 'debug').mockImplementation(() => { })
|
|
|
|
jest.spyOn(core, 'info').mockImplementation(() => { })
|
|
|
|
jest.spyOn(core, 'warning').mockImplementation(() => { })
|
|
|
|
jest.spyOn(core, 'error').mockImplementation(() => { })
|
2020-05-06 15:10:18 +00:00
|
|
|
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
|
|
|
|
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
|
|
|
|
return actualUtils.getCacheFileName(cm)
|
|
|
|
})
|
|
|
|
jest.spyOn(cacheUtils, 'resolvePaths').mockImplementation(async filePaths => {
|
|
|
|
return filePaths.map(x => path.resolve(x))
|
|
|
|
})
|
|
|
|
jest.spyOn(cacheUtils, 'createTempDirectory').mockImplementation(async () => {
|
|
|
|
return Promise.resolve('/foo/bar')
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-05-06 21:53:22 +00:00
|
|
|
test('save with missing input should fail', async () => {
|
|
|
|
const paths: string[] = []
|
2020-05-06 15:10:18 +00:00
|
|
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
2020-05-06 21:53:22 +00:00
|
|
|
await expect(saveCache(paths, primaryKey)).rejects.toThrowError(
|
|
|
|
`Path Validation Error: At least one directory or file path is required`
|
2020-05-06 15:10:18 +00:00
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2020-05-06 21:53:22 +00:00
|
|
|
test('save with large cache outputs should fail', async () => {
|
|
|
|
const filePath = 'node_modules'
|
2020-05-06 15:10:18 +00:00
|
|
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
2020-05-06 21:53:22 +00:00
|
|
|
const cachePaths = [path.resolve(filePath)]
|
2020-05-06 15:10:18 +00:00
|
|
|
|
|
|
|
const createTarMock = jest.spyOn(tar, 'createTar')
|
2022-06-24 05:16:20 +00:00
|
|
|
const logWarningMock = jest.spyOn(core, 'warning')
|
2020-05-06 15:10:18 +00:00
|
|
|
|
2021-11-19 11:04:33 +00:00
|
|
|
const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
|
2020-05-15 16:18:50 +00:00
|
|
|
jest
|
2021-05-03 15:09:44 +00:00
|
|
|
.spyOn(cacheUtils, 'getArchiveFileSizeInBytes')
|
2020-05-15 16:18:50 +00:00
|
|
|
.mockReturnValueOnce(cacheSize)
|
2020-05-06 15:10:18 +00:00
|
|
|
const compression = CompressionMethod.Gzip
|
|
|
|
const getCompressionMock = jest
|
|
|
|
.spyOn(cacheUtils, 'getCompressionMethod')
|
2020-05-06 21:53:22 +00:00
|
|
|
.mockReturnValueOnce(Promise.resolve(compression))
|
2022-03-31 10:41:54 +00:00
|
|
|
|
2022-06-24 05:16:20 +00:00
|
|
|
const cacheId = await saveCache([filePath], primaryKey)
|
|
|
|
expect(cacheId).toBe(-1)
|
|
|
|
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(logWarningMock).toHaveBeenCalledWith(
|
|
|
|
'Failed to save: Cache size of ~11264 MB (11811160064 B) is over the 10GB limit, not saving cache.'
|
2020-05-06 21:53:22 +00:00
|
|
|
)
|
2020-05-06 15:10:18 +00:00
|
|
|
|
|
|
|
const archiveFolder = '/foo/bar'
|
2022-03-31 20:40:08 +00:00
|
|
|
|
2020-05-06 15:10:18 +00:00
|
|
|
expect(createTarMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(createTarMock).toHaveBeenCalledWith(
|
|
|
|
archiveFolder,
|
|
|
|
cachePaths,
|
|
|
|
compression
|
|
|
|
)
|
|
|
|
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
|
|
|
|
2022-04-04 10:51:58 +00:00
|
|
|
test('save with large cache outputs should fail in GHES with error message', async () => {
|
|
|
|
const filePath = 'node_modules'
|
|
|
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
|
|
|
const cachePaths = [path.resolve(filePath)]
|
|
|
|
|
|
|
|
const createTarMock = jest.spyOn(tar, 'createTar')
|
2022-06-24 05:16:20 +00:00
|
|
|
const logWarningMock = jest.spyOn(core, 'warning')
|
2022-04-04 10:51:58 +00:00
|
|
|
|
|
|
|
const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
|
|
|
|
jest
|
|
|
|
.spyOn(cacheUtils, 'getArchiveFileSizeInBytes')
|
|
|
|
.mockReturnValueOnce(cacheSize)
|
|
|
|
const compression = CompressionMethod.Gzip
|
|
|
|
const getCompressionMock = jest
|
|
|
|
.spyOn(cacheUtils, 'getCompressionMethod')
|
|
|
|
.mockReturnValueOnce(Promise.resolve(compression))
|
|
|
|
|
2024-11-21 12:01:44 +00:00
|
|
|
jest.spyOn(config, 'isGhes').mockReturnValueOnce(true)
|
2022-04-04 10:51:58 +00:00
|
|
|
|
|
|
|
const reserveCacheMock = jest
|
|
|
|
.spyOn(cacheHttpClient, 'reserveCache')
|
|
|
|
.mockImplementation(async () => {
|
|
|
|
const response: ITypedResponseWithError<ReserveCacheResponse> = {
|
|
|
|
statusCode: 400,
|
|
|
|
result: null,
|
|
|
|
headers: {},
|
|
|
|
error: new HttpClientError(
|
|
|
|
'The cache filesize must be between 0 and 1073741824 bytes',
|
|
|
|
400
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return response
|
|
|
|
})
|
|
|
|
|
2022-06-24 05:16:20 +00:00
|
|
|
const cacheId = await saveCache([filePath], primaryKey)
|
|
|
|
expect(cacheId).toBe(-1)
|
|
|
|
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(logWarningMock).toHaveBeenCalledWith(
|
|
|
|
'Failed to save: The cache filesize must be between 0 and 1073741824 bytes'
|
2022-04-04 10:51:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const archiveFolder = '/foo/bar'
|
|
|
|
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(createTarMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(createTarMock).toHaveBeenCalledWith(
|
|
|
|
archiveFolder,
|
|
|
|
cachePaths,
|
|
|
|
compression
|
|
|
|
)
|
|
|
|
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
|
|
|
|
|
|
|
test('save with large cache outputs should fail in GHES without error message', async () => {
|
|
|
|
const filePath = 'node_modules'
|
|
|
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
|
|
|
const cachePaths = [path.resolve(filePath)]
|
|
|
|
|
|
|
|
const createTarMock = jest.spyOn(tar, 'createTar')
|
2022-06-24 05:16:20 +00:00
|
|
|
const logWarningMock = jest.spyOn(core, 'warning')
|
2022-04-04 10:51:58 +00:00
|
|
|
|
|
|
|
const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
|
|
|
|
jest
|
|
|
|
.spyOn(cacheUtils, 'getArchiveFileSizeInBytes')
|
|
|
|
.mockReturnValueOnce(cacheSize)
|
|
|
|
const compression = CompressionMethod.Gzip
|
|
|
|
const getCompressionMock = jest
|
|
|
|
.spyOn(cacheUtils, 'getCompressionMethod')
|
|
|
|
.mockReturnValueOnce(Promise.resolve(compression))
|
|
|
|
|
2024-11-21 12:01:44 +00:00
|
|
|
jest.spyOn(config, 'isGhes').mockReturnValueOnce(true)
|
2022-04-04 10:51:58 +00:00
|
|
|
|
|
|
|
const reserveCacheMock = jest
|
|
|
|
.spyOn(cacheHttpClient, 'reserveCache')
|
|
|
|
.mockImplementation(async () => {
|
|
|
|
const response: ITypedResponseWithError<ReserveCacheResponse> = {
|
|
|
|
statusCode: 400,
|
|
|
|
result: null,
|
|
|
|
headers: {}
|
|
|
|
}
|
|
|
|
return response
|
|
|
|
})
|
|
|
|
|
2022-06-24 05:16:20 +00:00
|
|
|
const cacheId = await saveCache([filePath], primaryKey)
|
|
|
|
expect(cacheId).toBe(-1)
|
|
|
|
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(logWarningMock).toHaveBeenCalledWith(
|
|
|
|
'Failed to save: Cache size of ~11264 MB (11811160064 B) is over the data cap limit, not saving cache.'
|
2022-04-04 10:51:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const archiveFolder = '/foo/bar'
|
|
|
|
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(createTarMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(createTarMock).toHaveBeenCalledWith(
|
|
|
|
archiveFolder,
|
|
|
|
cachePaths,
|
|
|
|
compression
|
|
|
|
)
|
|
|
|
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
|
|
|
|
2020-05-06 21:53:22 +00:00
|
|
|
test('save with reserve cache failure should fail', async () => {
|
|
|
|
const paths = ['node_modules']
|
2020-05-06 15:10:18 +00:00
|
|
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
2022-06-24 05:16:20 +00:00
|
|
|
const logInfoMock = jest.spyOn(core, 'info')
|
2020-05-06 15:10:18 +00:00
|
|
|
|
|
|
|
const reserveCacheMock = jest
|
|
|
|
.spyOn(cacheHttpClient, 'reserveCache')
|
|
|
|
.mockImplementation(async () => {
|
2022-05-11 21:14:25 +00:00
|
|
|
const response: TypedResponse<ReserveCacheResponse> = {
|
2022-04-04 10:51:58 +00:00
|
|
|
statusCode: 500,
|
|
|
|
result: null,
|
|
|
|
headers: {}
|
|
|
|
}
|
|
|
|
return response
|
2020-05-06 15:10:18 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
const createTarMock = jest.spyOn(tar, 'createTar')
|
|
|
|
const saveCacheMock = jest.spyOn(cacheHttpClient, 'saveCache')
|
|
|
|
const compression = CompressionMethod.Zstd
|
|
|
|
const getCompressionMock = jest
|
|
|
|
.spyOn(cacheUtils, 'getCompressionMethod')
|
2020-05-06 21:53:22 +00:00
|
|
|
.mockReturnValueOnce(Promise.resolve(compression))
|
2020-05-06 15:10:18 +00:00
|
|
|
|
2022-06-24 05:16:20 +00:00
|
|
|
const cacheId = await saveCache(paths, primaryKey)
|
|
|
|
expect(cacheId).toBe(-1)
|
|
|
|
expect(logInfoMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(logInfoMock).toHaveBeenCalledWith(
|
|
|
|
`Failed to save: Unable to reserve cache with key ${primaryKey}, another job may be creating this cache. More details: undefined`
|
2020-05-06 21:53:22 +00:00
|
|
|
)
|
2022-06-24 05:16:20 +00:00
|
|
|
|
2020-05-06 15:10:18 +00:00
|
|
|
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
2020-05-06 21:53:22 +00:00
|
|
|
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, paths, {
|
2023-01-04 06:46:25 +00:00
|
|
|
cacheSize: undefined,
|
|
|
|
compressionMethod: compression,
|
|
|
|
enableCrossOsArchive: false
|
2020-05-06 15:10:18 +00:00
|
|
|
})
|
2022-04-04 10:51:58 +00:00
|
|
|
expect(createTarMock).toHaveBeenCalledTimes(1)
|
2020-05-06 15:10:18 +00:00
|
|
|
expect(saveCacheMock).toHaveBeenCalledTimes(0)
|
|
|
|
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
|
|
|
|
2020-05-06 21:53:22 +00:00
|
|
|
test('save with server error should fail', async () => {
|
|
|
|
const filePath = 'node_modules'
|
2020-05-06 15:10:18 +00:00
|
|
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
2020-05-06 21:53:22 +00:00
|
|
|
const cachePaths = [path.resolve(filePath)]
|
2022-06-24 05:16:20 +00:00
|
|
|
const logWarningMock = jest.spyOn(core, 'warning')
|
2020-05-06 15:10:18 +00:00
|
|
|
const cacheId = 4
|
|
|
|
const reserveCacheMock = jest
|
|
|
|
.spyOn(cacheHttpClient, 'reserveCache')
|
|
|
|
.mockImplementation(async () => {
|
2022-05-11 21:14:25 +00:00
|
|
|
const response: TypedResponse<ReserveCacheResponse> = {
|
2022-04-04 10:51:58 +00:00
|
|
|
statusCode: 500,
|
2024-11-21 12:01:44 +00:00
|
|
|
result: { cacheId },
|
2022-04-04 10:51:58 +00:00
|
|
|
headers: {}
|
|
|
|
}
|
|
|
|
return response
|
2020-05-06 15:10:18 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
const createTarMock = jest.spyOn(tar, 'createTar')
|
|
|
|
|
|
|
|
const saveCacheMock = jest
|
|
|
|
.spyOn(cacheHttpClient, 'saveCache')
|
2020-05-07 00:07:39 +00:00
|
|
|
.mockImplementationOnce(() => {
|
2020-05-06 15:10:18 +00:00
|
|
|
throw new Error('HTTP Error Occurred')
|
|
|
|
})
|
|
|
|
const compression = CompressionMethod.Zstd
|
|
|
|
const getCompressionMock = jest
|
|
|
|
.spyOn(cacheUtils, 'getCompressionMethod')
|
2020-05-06 21:53:22 +00:00
|
|
|
.mockReturnValueOnce(Promise.resolve(compression))
|
2020-05-06 15:10:18 +00:00
|
|
|
|
2022-06-24 05:16:20 +00:00
|
|
|
await saveCache([filePath], primaryKey)
|
|
|
|
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(logWarningMock).toHaveBeenCalledWith(
|
|
|
|
'Failed to save: HTTP Error Occurred'
|
2020-05-06 21:53:22 +00:00
|
|
|
)
|
2022-06-24 05:16:20 +00:00
|
|
|
|
2020-05-06 15:10:18 +00:00
|
|
|
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
2020-05-06 21:53:22 +00:00
|
|
|
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, [filePath], {
|
2023-01-04 06:46:25 +00:00
|
|
|
cacheSize: undefined,
|
|
|
|
compressionMethod: compression,
|
|
|
|
enableCrossOsArchive: false
|
2020-05-06 15:10:18 +00:00
|
|
|
})
|
|
|
|
const archiveFolder = '/foo/bar'
|
|
|
|
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
|
|
|
|
expect(createTarMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(createTarMock).toHaveBeenCalledWith(
|
|
|
|
archiveFolder,
|
|
|
|
cachePaths,
|
|
|
|
compression
|
|
|
|
)
|
|
|
|
expect(saveCacheMock).toHaveBeenCalledTimes(1)
|
2020-05-12 16:37:03 +00:00
|
|
|
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile, undefined)
|
2020-05-06 15:10:18 +00:00
|
|
|
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
|
|
|
|
|
|
|
test('save with valid inputs uploads a cache', async () => {
|
2020-05-06 21:53:22 +00:00
|
|
|
const filePath = 'node_modules'
|
2020-05-06 15:10:18 +00:00
|
|
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
2020-05-06 21:53:22 +00:00
|
|
|
const cachePaths = [path.resolve(filePath)]
|
2020-05-06 15:10:18 +00:00
|
|
|
|
|
|
|
const cacheId = 4
|
|
|
|
const reserveCacheMock = jest
|
|
|
|
.spyOn(cacheHttpClient, 'reserveCache')
|
|
|
|
.mockImplementation(async () => {
|
2022-05-11 21:14:25 +00:00
|
|
|
const response: TypedResponse<ReserveCacheResponse> = {
|
2022-04-04 10:51:58 +00:00
|
|
|
statusCode: 500,
|
2024-11-21 12:01:44 +00:00
|
|
|
result: { cacheId },
|
2022-04-04 10:51:58 +00:00
|
|
|
headers: {}
|
|
|
|
}
|
|
|
|
return response
|
2020-05-06 15:10:18 +00:00
|
|
|
})
|
|
|
|
const createTarMock = jest.spyOn(tar, 'createTar')
|
|
|
|
|
|
|
|
const saveCacheMock = jest.spyOn(cacheHttpClient, 'saveCache')
|
|
|
|
const compression = CompressionMethod.Zstd
|
|
|
|
const getCompressionMock = jest
|
|
|
|
.spyOn(cacheUtils, 'getCompressionMethod')
|
|
|
|
.mockReturnValue(Promise.resolve(compression))
|
|
|
|
|
2020-05-06 21:53:22 +00:00
|
|
|
await saveCache([filePath], primaryKey)
|
2020-05-06 15:10:18 +00:00
|
|
|
|
|
|
|
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
2020-05-06 21:53:22 +00:00
|
|
|
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, [filePath], {
|
2023-01-04 06:46:25 +00:00
|
|
|
cacheSize: undefined,
|
|
|
|
compressionMethod: compression,
|
|
|
|
enableCrossOsArchive: false
|
2020-05-06 15:10:18 +00:00
|
|
|
})
|
|
|
|
const archiveFolder = '/foo/bar'
|
|
|
|
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
|
|
|
|
expect(createTarMock).toHaveBeenCalledTimes(1)
|
|
|
|
expect(createTarMock).toHaveBeenCalledWith(
|
|
|
|
archiveFolder,
|
|
|
|
cachePaths,
|
|
|
|
compression
|
|
|
|
)
|
|
|
|
expect(saveCacheMock).toHaveBeenCalledTimes(1)
|
2020-05-12 16:37:03 +00:00
|
|
|
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile, undefined)
|
2020-05-06 15:10:18 +00:00
|
|
|
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
|
|
|
})
|
2022-05-23 06:32:13 +00:00
|
|
|
|
|
|
|
test('save with non existing path should not save cache', async () => {
|
2022-05-23 06:49:26 +00:00
|
|
|
const path = 'node_modules'
|
2022-05-23 06:32:13 +00:00
|
|
|
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
2022-05-23 06:49:26 +00:00
|
|
|
jest.spyOn(cacheUtils, 'resolvePaths').mockImplementation(async () => {
|
2022-05-23 06:32:13 +00:00
|
|
|
return []
|
|
|
|
})
|
2022-05-23 06:49:26 +00:00
|
|
|
await expect(saveCache([path], primaryKey)).rejects.toThrowError(
|
2022-05-23 12:05:31 +00:00
|
|
|
`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`
|
2022-05-23 06:32:13 +00:00
|
|
|
)
|
|
|
|
})
|