mirror of https://github.com/actions/toolkit
Remove spyon, use readable stream to test pipeResponseToFile (#651)
* Remove spyon, use readable stream to test pipeResponseToFile * Check file contentspull/660/head
parent
2bf7365352
commit
ff4308098f
|
@ -12,8 +12,12 @@ import {
|
||||||
ListArtifactsResponse,
|
ListArtifactsResponse,
|
||||||
QueryArtifactResponse
|
QueryArtifactResponse
|
||||||
} from '../src/internal/contracts'
|
} from '../src/internal/contracts'
|
||||||
|
import * as stream from 'stream'
|
||||||
|
import {gzip} from 'zlib'
|
||||||
|
import {promisify} from 'util'
|
||||||
|
|
||||||
const root = path.join(__dirname, '_temp', 'artifact-download-tests')
|
const root = path.join(__dirname, '_temp', 'artifact-download-tests')
|
||||||
|
const defaultEncoding = 'utf8'
|
||||||
|
|
||||||
jest.mock('../src/internal/config-variables')
|
jest.mock('../src/internal/config-variables')
|
||||||
jest.mock('@actions/http-client')
|
jest.mock('@actions/http-client')
|
||||||
|
@ -114,33 +118,49 @@ describe('Download Tests', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Test downloading an individual artifact with gzip', async () => {
|
it('Test downloading an individual artifact with gzip', async () => {
|
||||||
setupDownloadItemResponse(true, 200)
|
const fileContents = Buffer.from(
|
||||||
|
'gzip worked on the first try\n',
|
||||||
|
defaultEncoding
|
||||||
|
)
|
||||||
|
const targetPath = path.join(root, 'FileA.txt')
|
||||||
|
|
||||||
|
setupDownloadItemResponse(true, 200, fileContents)
|
||||||
const downloadHttpClient = new DownloadHttpClient()
|
const downloadHttpClient = new DownloadHttpClient()
|
||||||
|
|
||||||
const items: DownloadItem[] = []
|
const items: DownloadItem[] = []
|
||||||
items.push({
|
items.push({
|
||||||
sourceLocation: `${configVariables.getRuntimeUrl()}_apis/resources/Containers/13?itemPath=my-artifact%2FFileA.txt`,
|
sourceLocation: `${configVariables.getRuntimeUrl()}_apis/resources/Containers/13?itemPath=my-artifact%2FFileA.txt`,
|
||||||
targetPath: path.join(root, 'FileA.txt')
|
targetPath
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
downloadHttpClient.downloadSingleArtifact(items)
|
downloadHttpClient.downloadSingleArtifact(items)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
|
|
||||||
|
await checkDestinationFile(targetPath, fileContents)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Test downloading an individual artifact without gzip', async () => {
|
it('Test downloading an individual artifact without gzip', async () => {
|
||||||
setupDownloadItemResponse(false, 200)
|
const fileContents = Buffer.from(
|
||||||
|
'plaintext worked on the first try\n',
|
||||||
|
defaultEncoding
|
||||||
|
)
|
||||||
|
const targetPath = path.join(root, 'FileB.txt')
|
||||||
|
|
||||||
|
setupDownloadItemResponse(false, 200, fileContents)
|
||||||
const downloadHttpClient = new DownloadHttpClient()
|
const downloadHttpClient = new DownloadHttpClient()
|
||||||
|
|
||||||
const items: DownloadItem[] = []
|
const items: DownloadItem[] = []
|
||||||
items.push({
|
items.push({
|
||||||
sourceLocation: `${configVariables.getRuntimeUrl()}_apis/resources/Containers/13?itemPath=my-artifact%2FFileB.txt`,
|
sourceLocation: `${configVariables.getRuntimeUrl()}_apis/resources/Containers/13?itemPath=my-artifact%2FFileB.txt`,
|
||||||
targetPath: path.join(root, 'FileB.txt')
|
targetPath
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
downloadHttpClient.downloadSingleArtifact(items)
|
downloadHttpClient.downloadSingleArtifact(items)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
|
|
||||||
|
await checkDestinationFile(targetPath, fileContents)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Test retryable status codes during artifact download', async () => {
|
it('Test retryable status codes during artifact download', async () => {
|
||||||
|
@ -148,18 +168,23 @@ describe('Download Tests', () => {
|
||||||
// the download should successfully finish
|
// the download should successfully finish
|
||||||
const retryableStatusCodes = [429, 502, 503, 504]
|
const retryableStatusCodes = [429, 502, 503, 504]
|
||||||
for (const statusCode of retryableStatusCodes) {
|
for (const statusCode of retryableStatusCodes) {
|
||||||
setupDownloadItemResponse(false, statusCode)
|
const fileContents = Buffer.from('try, try again\n', defaultEncoding)
|
||||||
|
const targetPath = path.join(root, `FileC-${statusCode}.txt`)
|
||||||
|
|
||||||
|
setupDownloadItemResponse(false, statusCode, fileContents)
|
||||||
const downloadHttpClient = new DownloadHttpClient()
|
const downloadHttpClient = new DownloadHttpClient()
|
||||||
|
|
||||||
const items: DownloadItem[] = []
|
const items: DownloadItem[] = []
|
||||||
items.push({
|
items.push({
|
||||||
sourceLocation: `${configVariables.getRuntimeUrl()}_apis/resources/Containers/13?itemPath=my-artifact%2FFileC.txt`,
|
sourceLocation: `${configVariables.getRuntimeUrl()}_apis/resources/Containers/13?itemPath=my-artifact%2FFileC.txt`,
|
||||||
targetPath: path.join(root, 'FileC.txt')
|
targetPath
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
downloadHttpClient.downloadSingleArtifact(items)
|
downloadHttpClient.downloadSingleArtifact(items)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
|
|
||||||
|
await checkDestinationFile(targetPath, fileContents)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -227,51 +252,96 @@ describe('Download Tests', () => {
|
||||||
*/
|
*/
|
||||||
function setupDownloadItemResponse(
|
function setupDownloadItemResponse(
|
||||||
isGzip: boolean,
|
isGzip: boolean,
|
||||||
firstHttpResponseCode: number
|
firstHttpResponseCode: number,
|
||||||
|
fileContents: Buffer
|
||||||
): void {
|
): void {
|
||||||
jest
|
const spyInstance = jest
|
||||||
.spyOn(DownloadHttpClient.prototype, 'pipeResponseToFile')
|
|
||||||
.mockImplementationOnce(async () => {
|
|
||||||
return new Promise<void>(resolve => {
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(HttpClient.prototype, 'get')
|
.spyOn(HttpClient.prototype, 'get')
|
||||||
.mockImplementationOnce(async () => {
|
.mockImplementationOnce(async () => {
|
||||||
const mockMessage = new http.IncomingMessage(new net.Socket())
|
if (firstHttpResponseCode === 200) {
|
||||||
mockMessage.statusCode = firstHttpResponseCode
|
return {
|
||||||
if (isGzip) {
|
message: getDownloadResponseMessage(
|
||||||
mockMessage.headers = {
|
firstHttpResponseCode,
|
||||||
'content-type': 'gzip'
|
isGzip,
|
||||||
|
await constructResponse(isGzip, fileContents)
|
||||||
|
),
|
||||||
|
readBody: emptyMockReadBody
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
message: getDownloadResponseMessage(
|
||||||
|
firstHttpResponseCode,
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
readBody: emptyMockReadBody
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<HttpClientResponse>(resolve => {
|
|
||||||
resolve({
|
|
||||||
message: mockMessage,
|
|
||||||
readBody: emptyMockReadBody
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.mockImplementationOnce(async () => {
|
|
||||||
|
// set up a second mock only if we expect a retry. Otherwise this mock will affect other tests.
|
||||||
|
if (firstHttpResponseCode !== 200) {
|
||||||
|
spyInstance.mockImplementationOnce(async () => {
|
||||||
// chained response, if the HTTP GET function gets called again, return a successful response
|
// chained response, if the HTTP GET function gets called again, return a successful response
|
||||||
const mockMessage = new http.IncomingMessage(new net.Socket())
|
return {
|
||||||
mockMessage.statusCode = 200
|
message: getDownloadResponseMessage(
|
||||||
if (isGzip) {
|
200,
|
||||||
mockMessage.headers = {
|
isGzip,
|
||||||
'content-type': 'gzip'
|
await constructResponse(isGzip, fileContents)
|
||||||
}
|
),
|
||||||
|
readBody: emptyMockReadBody
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<HttpClientResponse>(resolve => {
|
|
||||||
resolve({
|
|
||||||
message: mockMessage,
|
|
||||||
readBody: emptyMockReadBody
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function constructResponse(
|
||||||
|
isGzip: boolean,
|
||||||
|
plaintext: Buffer | string
|
||||||
|
): Promise<Buffer> {
|
||||||
|
if (isGzip) {
|
||||||
|
return <Buffer>await promisify(gzip)(plaintext)
|
||||||
|
} else if (typeof plaintext === 'string') {
|
||||||
|
return Buffer.from(plaintext, defaultEncoding)
|
||||||
|
} else {
|
||||||
|
return plaintext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDownloadResponseMessage(
|
||||||
|
httpResponseCode: number,
|
||||||
|
isGzip: boolean,
|
||||||
|
response: Buffer | null
|
||||||
|
): http.IncomingMessage {
|
||||||
|
let readCallCount = 0
|
||||||
|
const mockMessage = <http.IncomingMessage>new stream.Readable({
|
||||||
|
read(size) {
|
||||||
|
switch (readCallCount++) {
|
||||||
|
case 0:
|
||||||
|
if (!!response && response.byteLength > size) {
|
||||||
|
throw new Error(
|
||||||
|
`test response larger than requested size (${size})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.push(response)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
// end the stream
|
||||||
|
this.push(null)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mockMessage.statusCode = httpResponseCode
|
||||||
|
mockMessage.headers = {}
|
||||||
|
|
||||||
|
if (isGzip) {
|
||||||
|
mockMessage.headers['content-encoding'] = 'gzip'
|
||||||
|
}
|
||||||
|
|
||||||
|
return mockMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -347,4 +417,14 @@ describe('Download Tests', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkDestinationFile(
|
||||||
|
targetPath: string,
|
||||||
|
expectedContents: Buffer
|
||||||
|
): Promise<void> {
|
||||||
|
const fileContents = await fs.readFile(targetPath)
|
||||||
|
|
||||||
|
expect(fileContents.byteLength).toEqual(expectedContents.byteLength)
|
||||||
|
expect(fileContents.equals(expectedContents)).toBeTruthy()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue