1
0
Fork 0

update download-artifact tests for public and internal impl

pull/1591/head
Rob Herley 2023-12-01 01:32:45 +00:00 committed by GitHub
parent 22b7aeb707
commit 32549e8197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 224 additions and 53 deletions

View File

@ -3,7 +3,7 @@ import * as net from 'net'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import * as config from '../src/internal/shared/config' import * as config from '../src/internal/shared/config'
import {internalArtifactTwirpClient} from '../src/internal/shared/artifact-twirp-client' import {internalArtifactTwirpClient} from '../src/internal/shared/artifact-twirp-client'
import {noopLogs} from './common.test' import {noopLogs} from './common'
jest.mock('@actions/http-client') jest.mock('@actions/http-client')

View File

@ -2,7 +2,7 @@ import * as core from '@actions/core'
// noopLogs mocks the console.log and core.* functions to prevent output in the console while testing // noopLogs mocks the console.log and core.* functions to prevent output in the console while testing
export const noopLogs = (): void => { export const noopLogs = (): void => {
// jest.spyOn(console, 'log').mockImplementation(() => {}) jest.spyOn(console, 'log').mockImplementation(() => {})
jest.spyOn(core, 'debug').mockImplementation(() => {}) jest.spyOn(core, 'debug').mockImplementation(() => {})
jest.spyOn(core, 'info').mockImplementation(() => {}) jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {}) jest.spyOn(core, 'warning').mockImplementation(() => {})

View File

@ -7,9 +7,15 @@ import {HttpClient} from '@actions/http-client'
import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types' import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types'
import archiver from 'archiver' import archiver from 'archiver'
import {downloadArtifactPublic} from '../src/internal/download/download-artifact' import {
downloadArtifactInternal,
downloadArtifactPublic
} from '../src/internal/download/download-artifact'
import {getUserAgentString} from '../src/internal/shared/user-agent' import {getUserAgentString} from '../src/internal/shared/user-agent'
import {noopLogs} from './common.test' import {noopLogs} from './common'
import * as config from '../src/internal/shared/config'
import {ArtifactServiceClientJSON} from '../src/generated'
import * as util from '../src/internal/shared/util'
type MockedDownloadArtifact = jest.MockedFunction< type MockedDownloadArtifact = jest.MockedFunction<
RestEndpointMethods['actions']['downloadArtifact'] RestEndpointMethods['actions']['downloadArtifact']
@ -32,10 +38,16 @@ const fixtures = {
] ]
}, },
artifactID: 1234, artifactID: 1234,
artifactName: 'my-artifact',
artifactSize: 123456,
repositoryOwner: 'actions', repositoryOwner: 'actions',
repositoryName: 'toolkit', repositoryName: 'toolkit',
token: 'ghp_1234567890', token: 'ghp_1234567890',
blobStorageUrl: 'https://blob-storage.local?signed=true' blobStorageUrl: 'https://blob-storage.local?signed=true',
backendIds: {
workflowRunBackendId: 'c4d7c21f-ba3f-4ddc-a8c8-6f2f626f8422',
workflowJobRunBackendId: '760803a1-f890-4d25-9a6e-a3fc01a0c7cf'
}
} }
jest.mock('@actions/github', () => ({ jest.mock('@actions/github', () => ({
@ -88,6 +100,24 @@ const cleanup = async (): Promise<void> => {
delete process.env['GITHUB_WORKSPACE'] delete process.env['GITHUB_WORKSPACE']
} }
const mockGetArtifactSuccess = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.push(fs.readFileSync(fixtures.exampleArtifact.path))
return {
message
}
})
const mockGetArtifactFailure = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 500
message.push('Internal Server Error')
return {
message
}
})
describe('download-artifact', () => { describe('download-artifact', () => {
describe('public', () => { describe('public', () => {
beforeEach(setup) beforeEach(setup)
@ -105,18 +135,10 @@ describe('download-artifact', () => {
data: Buffer.from('') data: Buffer.from('')
}) })
const getMock = jest.fn(() => { const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.push(fs.readFileSync(fixtures.exampleArtifact.path))
return {
message
}
})
const httpClientMock = (HttpClient as jest.Mock).mockImplementation(
() => { () => {
return { return {
get: getMock get: mockGetArtifactSuccess
} }
} }
) )
@ -137,11 +159,11 @@ describe('download-artifact', () => {
redirect: 'manual' redirect: 'manual'
} }
}) })
expect(httpClientMock).toHaveBeenCalledWith(getUserAgentString()) expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(getMock).toHaveBeenCalledWith(fixtures.blobStorageUrl) expect(mockGetArtifactSuccess).toHaveBeenCalledWith(
fixtures.blobStorageUrl
)
expectExtractedArchive(fixtures.workspaceDir) expectExtractedArchive(fixtures.workspaceDir)
expect(response.success).toBe(true) expect(response.success).toBe(true)
expect(response.downloadPath).toBe(fixtures.workspaceDir) expect(response.downloadPath).toBe(fixtures.workspaceDir)
}) })
@ -160,18 +182,10 @@ describe('download-artifact', () => {
data: Buffer.from('') data: Buffer.from('')
}) })
const getMock = jest.fn(() => { const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
message.push(fs.readFileSync(fixtures.exampleArtifact.path))
return {
message
}
})
const httpClientMock = (HttpClient as jest.Mock).mockImplementation(
() => { () => {
return { return {
get: getMock get: mockGetArtifactSuccess
} }
} }
) )
@ -195,11 +209,11 @@ describe('download-artifact', () => {
redirect: 'manual' redirect: 'manual'
} }
}) })
expect(httpClientMock).toHaveBeenCalledWith(getUserAgentString()) expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(getMock).toHaveBeenCalledWith(fixtures.blobStorageUrl) expect(mockGetArtifactSuccess).toHaveBeenCalledWith(
fixtures.blobStorageUrl
)
expectExtractedArchive(customPath) expectExtractedArchive(customPath)
expect(response.success).toBe(true) expect(response.success).toBe(true)
expect(response.downloadPath).toBe(customPath) expect(response.downloadPath).toBe(customPath)
}) })
@ -246,18 +260,10 @@ describe('download-artifact', () => {
data: Buffer.from('') data: Buffer.from('')
}) })
const getMock = jest.fn(() => { const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 500
message.push('Internal Server Error')
return {
message
}
})
const httpClientMock = (HttpClient as jest.Mock).mockImplementation(
() => { () => {
return { return {
get: getMock get: mockGetArtifactFailure
} }
} }
) )
@ -280,8 +286,176 @@ describe('download-artifact', () => {
redirect: 'manual' redirect: 'manual'
} }
}) })
expect(httpClientMock).toHaveBeenCalledWith(getUserAgentString()) expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(getMock).toHaveBeenCalledWith(fixtures.blobStorageUrl) expect(mockGetArtifactFailure).toHaveBeenCalledWith(
fixtures.blobStorageUrl
)
})
})
describe('internal', () => {
beforeEach(async () => {
await setup()
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(util, 'getBackendIdsFromToken')
.mockReturnValue(fixtures.backendIds)
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://results.local')
})
afterEach(async () => {
await cleanup()
})
it('should successfully download an artifact to $GITHUB_WORKSPACE', async () => {
const mockListArtifacts = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: [
{
...fixtures.backendIds,
databaseId: fixtures.artifactID.toString(),
name: fixtures.artifactName,
size: fixtures.artifactSize.toString()
}
]
})
const mockGetSignedArtifactURL = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'GetSignedArtifactURL')
.mockReturnValue(
Promise.resolve({
signedUrl: fixtures.blobStorageUrl
})
)
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetArtifactSuccess
}
}
)
const response = await downloadArtifactInternal(fixtures.artifactID)
expectExtractedArchive(fixtures.workspaceDir)
expect(response.success).toBe(true)
expect(response.downloadPath).toBe(fixtures.workspaceDir)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockListArtifacts).toHaveBeenCalledWith({
...fixtures.backendIds
})
expect(mockGetSignedArtifactURL).toHaveBeenCalledWith({
...fixtures.backendIds,
name: fixtures.artifactName
})
})
it('should successfully download an artifact to user defined path', async () => {
const customPath = path.join(testDir, 'custom')
const mockListArtifacts = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: [
{
...fixtures.backendIds,
databaseId: fixtures.artifactID.toString(),
name: fixtures.artifactName,
size: fixtures.artifactSize.toString()
}
]
})
const mockGetSignedArtifactURL = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'GetSignedArtifactURL')
.mockReturnValue(
Promise.resolve({
signedUrl: fixtures.blobStorageUrl
})
)
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetArtifactSuccess
}
}
)
const response = await downloadArtifactInternal(fixtures.artifactID, {
path: customPath
})
expectExtractedArchive(customPath)
expect(response.success).toBe(true)
expect(response.downloadPath).toBe(customPath)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockListArtifacts).toHaveBeenCalledWith({
...fixtures.backendIds
})
expect(mockGetSignedArtifactURL).toHaveBeenCalledWith({
...fixtures.backendIds,
name: fixtures.artifactName
})
})
it('should fail if download artifact API does not respond with location', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockRejectedValue(new Error('boom'))
await expect(
downloadArtifactInternal(fixtures.artifactID)
).rejects.toBeInstanceOf(Error)
})
it('should fail if blob storage response is non-200', async () => {
const mockListArtifacts = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'ListArtifacts')
.mockResolvedValue({
artifacts: [
{
...fixtures.backendIds,
databaseId: fixtures.artifactID.toString(),
name: fixtures.artifactName,
size: fixtures.artifactSize.toString()
}
]
})
const mockGetSignedArtifactURL = jest
.spyOn(ArtifactServiceClientJSON.prototype, 'GetSignedArtifactURL')
.mockReturnValue(
Promise.resolve({
signedUrl: fixtures.blobStorageUrl
})
)
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockGetArtifactFailure
}
}
)
await expect(
downloadArtifactInternal(fixtures.artifactID)
).rejects.toBeInstanceOf(Error)
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockListArtifacts).toHaveBeenCalledWith({
...fixtures.backendIds
})
expect(mockGetSignedArtifactURL).toHaveBeenCalledWith({
...fixtures.backendIds,
name: fixtures.artifactName
})
}) })
}) })
}) })

View File

@ -3,7 +3,7 @@ import {
validateFilePath validateFilePath
} from '../src/internal/upload/path-and-artifact-name-validation' } from '../src/internal/upload/path-and-artifact-name-validation'
import {noopLogs} from './common.test' import {noopLogs} from './common'
describe('Path and artifact name validation', () => { describe('Path and artifact name validation', () => {
beforeAll(() => { beforeAll(() => {

View File

@ -6,7 +6,7 @@ import * as config from '../src/internal/shared/config'
import {Timestamp, ArtifactServiceClientJSON} from '../src/generated' import {Timestamp, ArtifactServiceClientJSON} from '../src/generated'
import * as blobUpload from '../src/internal/upload/blob-upload' import * as blobUpload from '../src/internal/upload/blob-upload'
import {uploadArtifact} from '../src/internal/upload/upload-artifact' import {uploadArtifact} from '../src/internal/upload/upload-artifact'
import {noopLogs} from './common.test' import {noopLogs} from './common'
describe('upload-artifact', () => { describe('upload-artifact', () => {
beforeEach(() => { beforeEach(() => {
@ -127,7 +127,7 @@ describe('upload-artifact', () => {
expect(uploadResp).resolves.toEqual({success: false}) expect(uploadResp).resolves.toEqual({success: false})
}) })
it('should return false if no backend IDs are found', () => { it('should reject if no backend IDs are found', () => {
jest jest
.spyOn(uploadZipSpecification, 'validateRootDirectory') .spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue() .mockReturnValue()
@ -151,9 +151,6 @@ describe('upload-artifact', () => {
jest jest
.spyOn(zip, 'createZipUploadStream') .spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1))) .mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest
.spyOn(util, 'getBackendIdsFromToken')
.mockReturnValue({workflowRunBackendId: '', workflowJobRunBackendId: ''})
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', 'test-artifact',
@ -165,7 +162,7 @@ describe('upload-artifact', () => {
'/home/user/files/plz-upload' '/home/user/files/plz-upload'
) )
expect(uploadResp).resolves.toEqual({success: false}) expect(uploadResp).rejects.toThrow()
}) })
it('should return false if the creation request fails', () => { it('should return false if the creation request fails', () => {

View File

@ -5,7 +5,7 @@ import {
getUploadZipSpecification, getUploadZipSpecification,
validateRootDirectory validateRootDirectory
} from '../src/internal/upload/upload-zip-specification' } from '../src/internal/upload/upload-zip-specification'
import {noopLogs} from './common.test' import {noopLogs} from './common'
const root = path.join(__dirname, '_temp', 'upload-specification') const root = path.join(__dirname, '_temp', 'upload-specification')
const goodItem1Path = path.join( const goodItem1Path = path.join(