2023-08-23 20:54:31 +00:00
|
|
|
import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification'
|
|
|
|
import * as zip from '../src/internal/upload/zip'
|
|
|
|
import * as util from '../src/internal/shared/util'
|
|
|
|
import * as retention from '../src/internal/upload/retention'
|
|
|
|
import * as config from '../src/internal/shared/config'
|
2023-08-23 21:02:50 +00:00
|
|
|
import {Timestamp, ArtifactServiceClientJSON} from '../src/generated'
|
2023-08-23 20:54:31 +00:00
|
|
|
import * as blobUpload from '../src/internal/upload/blob-upload'
|
|
|
|
import {uploadArtifact} from '../src/internal/upload/upload-artifact'
|
2023-12-01 01:32:45 +00:00
|
|
|
import {noopLogs} from './common'
|
2023-12-05 17:35:46 +00:00
|
|
|
import {FilesNotFoundError} from '../src/internal/shared/errors'
|
2024-04-09 18:02:48 +00:00
|
|
|
import {BlockBlobClient} from '@azure/storage-blob'
|
2024-04-09 18:52:19 +00:00
|
|
|
import fs from 'fs'
|
|
|
|
import {Readable} from 'stream'
|
|
|
|
|
2023-08-23 20:54:31 +00:00
|
|
|
describe('upload-artifact', () => {
|
|
|
|
beforeEach(() => {
|
2023-12-01 00:31:27 +00:00
|
|
|
noopLogs()
|
2023-08-23 20:54:31 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
jest.restoreAllMocks()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should successfully upload an artifact', () => {
|
|
|
|
const mockDate = new Date('2020-01-01')
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
|
|
.mockReturnValue()
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
|
|
.mockReturnValue([
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
|
|
destinationPath: 'file1.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
|
|
destinationPath: 'file2.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
|
|
destinationPath: 'dir/file3.txt'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
|
|
|
|
jest
|
|
|
|
.spyOn(zip, 'createZipUploadStream')
|
|
|
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
2023-08-23 20:55:26 +00:00
|
|
|
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
|
|
workflowRunBackendId: '1234',
|
|
|
|
workflowJobRunBackendId: '5678'
|
|
|
|
})
|
2023-08-23 20:54:31 +00:00
|
|
|
jest
|
|
|
|
.spyOn(retention, 'getExpiration')
|
|
|
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
|
|
jest
|
|
|
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
|
|
|
.mockReturnValue(
|
|
|
|
Promise.resolve({
|
|
|
|
ok: true,
|
|
|
|
signedUploadUrl: 'https://signed-upload-url.com'
|
|
|
|
})
|
|
|
|
)
|
2023-08-23 20:55:26 +00:00
|
|
|
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
|
|
|
Promise.resolve({
|
|
|
|
uploadSize: 1234,
|
2023-10-16 16:20:24 +00:00
|
|
|
sha256Hash: 'test-sha256-hash'
|
2023-08-23 20:55:26 +00:00
|
|
|
})
|
|
|
|
)
|
2023-08-23 20:54:31 +00:00
|
|
|
jest
|
|
|
|
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
|
|
|
.mockReturnValue(Promise.resolve({ok: true, artifactId: '1'}))
|
|
|
|
|
|
|
|
// ArtifactHttpClient mocks
|
|
|
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
|
|
jest
|
|
|
|
.spyOn(config, 'getResultsServiceUrl')
|
|
|
|
.mockReturnValue('https://test-url.com')
|
|
|
|
|
|
|
|
const uploadResp = uploadArtifact(
|
|
|
|
'test-artifact',
|
|
|
|
[
|
|
|
|
'/home/user/files/plz-upload/file1.txt',
|
|
|
|
'/home/user/files/plz-upload/file2.txt',
|
|
|
|
'/home/user/files/plz-upload/dir/file3.txt'
|
|
|
|
],
|
|
|
|
'/home/user/files/plz-upload'
|
|
|
|
)
|
|
|
|
|
2023-12-05 17:35:46 +00:00
|
|
|
expect(uploadResp).resolves.toEqual({size: 1234, id: 1})
|
2023-08-23 20:54:31 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should throw an error if the root directory is invalid', () => {
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
|
|
.mockImplementation(() => {
|
|
|
|
throw new Error('Invalid root directory')
|
|
|
|
})
|
|
|
|
|
|
|
|
const uploadResp = uploadArtifact(
|
|
|
|
'test-artifact',
|
|
|
|
[
|
|
|
|
'/home/user/files/plz-upload/file1.txt',
|
|
|
|
'/home/user/files/plz-upload/file2.txt',
|
|
|
|
'/home/user/files/plz-upload/dir/file3.txt'
|
|
|
|
],
|
|
|
|
'/home/user/files/plz-upload'
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(uploadResp).rejects.toThrow('Invalid root directory')
|
|
|
|
})
|
|
|
|
|
2023-12-05 17:35:46 +00:00
|
|
|
it('should reject if there are no files to upload', () => {
|
2023-08-23 20:54:31 +00:00
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
|
|
.mockReturnValue()
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
|
|
.mockReturnValue([])
|
|
|
|
|
|
|
|
const uploadResp = uploadArtifact(
|
|
|
|
'test-artifact',
|
|
|
|
[
|
|
|
|
'/home/user/files/plz-upload/file1.txt',
|
|
|
|
'/home/user/files/plz-upload/file2.txt',
|
|
|
|
'/home/user/files/plz-upload/dir/file3.txt'
|
|
|
|
],
|
|
|
|
'/home/user/files/plz-upload'
|
|
|
|
)
|
2023-12-05 17:35:46 +00:00
|
|
|
expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
|
2023-08-23 20:54:31 +00:00
|
|
|
})
|
|
|
|
|
2023-12-01 01:32:45 +00:00
|
|
|
it('should reject if no backend IDs are found', () => {
|
2023-08-23 20:54:31 +00:00
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
|
|
.mockReturnValue()
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
|
|
.mockReturnValue([
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
|
|
destinationPath: 'file1.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
|
|
destinationPath: 'file2.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
|
|
destinationPath: 'dir/file3.txt'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
|
|
|
|
jest
|
|
|
|
.spyOn(zip, 'createZipUploadStream')
|
|
|
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
|
|
|
|
|
|
|
const uploadResp = uploadArtifact(
|
|
|
|
'test-artifact',
|
|
|
|
[
|
|
|
|
'/home/user/files/plz-upload/file1.txt',
|
|
|
|
'/home/user/files/plz-upload/file2.txt',
|
|
|
|
'/home/user/files/plz-upload/dir/file3.txt'
|
|
|
|
],
|
|
|
|
'/home/user/files/plz-upload'
|
|
|
|
)
|
|
|
|
|
2023-12-01 01:32:45 +00:00
|
|
|
expect(uploadResp).rejects.toThrow()
|
2023-08-23 20:54:31 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should return false if the creation request fails', () => {
|
|
|
|
const mockDate = new Date('2020-01-01')
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
|
|
.mockReturnValue()
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
|
|
.mockReturnValue([
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
|
|
destinationPath: 'file1.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
|
|
destinationPath: 'file2.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
|
|
destinationPath: 'dir/file3.txt'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
|
|
|
|
jest
|
|
|
|
.spyOn(zip, 'createZipUploadStream')
|
|
|
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
2023-08-23 20:55:26 +00:00
|
|
|
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
|
|
workflowRunBackendId: '1234',
|
|
|
|
workflowJobRunBackendId: '5678'
|
|
|
|
})
|
2023-08-23 20:54:31 +00:00
|
|
|
jest
|
|
|
|
.spyOn(retention, 'getExpiration')
|
|
|
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
|
|
jest
|
|
|
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
|
|
|
.mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''}))
|
|
|
|
|
|
|
|
// ArtifactHttpClient mocks
|
|
|
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
|
|
jest
|
|
|
|
.spyOn(config, 'getResultsServiceUrl')
|
|
|
|
.mockReturnValue('https://test-url.com')
|
|
|
|
|
|
|
|
const uploadResp = uploadArtifact(
|
|
|
|
'test-artifact',
|
|
|
|
[
|
|
|
|
'/home/user/files/plz-upload/file1.txt',
|
|
|
|
'/home/user/files/plz-upload/file2.txt',
|
|
|
|
'/home/user/files/plz-upload/dir/file3.txt'
|
|
|
|
],
|
|
|
|
'/home/user/files/plz-upload'
|
|
|
|
)
|
|
|
|
|
2023-12-05 17:35:46 +00:00
|
|
|
expect(uploadResp).rejects.toThrow()
|
2023-08-23 20:54:31 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should return false if blob storage upload is unsuccessful', () => {
|
|
|
|
const mockDate = new Date('2020-01-01')
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
|
|
.mockReturnValue()
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
|
|
.mockReturnValue([
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
|
|
destinationPath: 'file1.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
|
|
destinationPath: 'file2.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
|
|
destinationPath: 'dir/file3.txt'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
|
|
|
|
jest
|
|
|
|
.spyOn(zip, 'createZipUploadStream')
|
|
|
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
2023-08-23 20:55:26 +00:00
|
|
|
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
|
|
workflowRunBackendId: '1234',
|
|
|
|
workflowJobRunBackendId: '5678'
|
|
|
|
})
|
2023-08-23 20:54:31 +00:00
|
|
|
jest
|
|
|
|
.spyOn(retention, 'getExpiration')
|
|
|
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
|
|
jest
|
|
|
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
|
|
|
.mockReturnValue(
|
|
|
|
Promise.resolve({
|
|
|
|
ok: true,
|
|
|
|
signedUploadUrl: 'https://signed-upload-url.com'
|
|
|
|
})
|
|
|
|
)
|
|
|
|
jest
|
|
|
|
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
2023-12-05 17:35:46 +00:00
|
|
|
.mockReturnValue(Promise.reject(new Error('boom')))
|
2023-08-23 20:54:31 +00:00
|
|
|
|
|
|
|
// ArtifactHttpClient mocks
|
|
|
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
|
|
jest
|
|
|
|
.spyOn(config, 'getResultsServiceUrl')
|
|
|
|
.mockReturnValue('https://test-url.com')
|
|
|
|
|
|
|
|
const uploadResp = uploadArtifact(
|
|
|
|
'test-artifact',
|
|
|
|
[
|
|
|
|
'/home/user/files/plz-upload/file1.txt',
|
|
|
|
'/home/user/files/plz-upload/file2.txt',
|
|
|
|
'/home/user/files/plz-upload/dir/file3.txt'
|
|
|
|
],
|
|
|
|
'/home/user/files/plz-upload'
|
|
|
|
)
|
|
|
|
|
2023-12-05 17:35:46 +00:00
|
|
|
expect(uploadResp).rejects.toThrow()
|
2023-08-23 20:54:31 +00:00
|
|
|
})
|
|
|
|
|
2023-12-05 17:35:46 +00:00
|
|
|
it('should reject if finalize artifact fails', () => {
|
2023-08-23 20:54:31 +00:00
|
|
|
const mockDate = new Date('2020-01-01')
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
|
|
.mockReturnValue()
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
|
|
.mockReturnValue([
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
|
|
destinationPath: 'file1.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
|
|
destinationPath: 'file2.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
|
|
destinationPath: 'dir/file3.txt'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
|
|
|
|
jest
|
|
|
|
.spyOn(zip, 'createZipUploadStream')
|
|
|
|
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
|
2023-08-23 20:55:26 +00:00
|
|
|
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
|
|
workflowRunBackendId: '1234',
|
|
|
|
workflowJobRunBackendId: '5678'
|
|
|
|
})
|
2023-08-23 20:54:31 +00:00
|
|
|
jest
|
|
|
|
.spyOn(retention, 'getExpiration')
|
|
|
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
|
|
jest
|
|
|
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
|
|
|
.mockReturnValue(
|
|
|
|
Promise.resolve({
|
|
|
|
ok: true,
|
|
|
|
signedUploadUrl: 'https://signed-upload-url.com'
|
|
|
|
})
|
|
|
|
)
|
2023-08-23 20:55:26 +00:00
|
|
|
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
|
|
|
|
Promise.resolve({
|
|
|
|
uploadSize: 1234,
|
2023-10-16 16:20:24 +00:00
|
|
|
sha256Hash: 'test-sha256-hash'
|
2023-08-23 20:55:26 +00:00
|
|
|
})
|
|
|
|
)
|
2023-08-23 20:54:31 +00:00
|
|
|
jest
|
|
|
|
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
|
|
|
|
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
|
|
|
|
|
|
|
|
// ArtifactHttpClient mocks
|
|
|
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
|
|
jest
|
|
|
|
.spyOn(config, 'getResultsServiceUrl')
|
|
|
|
.mockReturnValue('https://test-url.com')
|
|
|
|
|
|
|
|
const uploadResp = uploadArtifact(
|
|
|
|
'test-artifact',
|
|
|
|
[
|
|
|
|
'/home/user/files/plz-upload/file1.txt',
|
|
|
|
'/home/user/files/plz-upload/file2.txt',
|
|
|
|
'/home/user/files/plz-upload/dir/file3.txt'
|
|
|
|
],
|
|
|
|
'/home/user/files/plz-upload'
|
|
|
|
)
|
|
|
|
|
2023-12-05 17:35:46 +00:00
|
|
|
expect(uploadResp).rejects.toThrow()
|
2023-08-23 20:54:31 +00:00
|
|
|
})
|
2024-04-09 18:02:48 +00:00
|
|
|
|
|
|
|
it('should throw an error uploading blob chunks get delayed', async () => {
|
|
|
|
const mockDate = new Date('2020-01-01')
|
2024-04-09 18:52:19 +00:00
|
|
|
|
|
|
|
// Mock fs.createReadStream to return a mock stream
|
|
|
|
fs.createReadStream = jest.fn().mockImplementation(() => {
|
|
|
|
const mockStream = new Readable()
|
|
|
|
mockStream.push('file content')
|
|
|
|
mockStream.push(null)
|
|
|
|
return mockStream
|
|
|
|
})
|
|
|
|
|
2024-04-09 18:02:48 +00:00
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'validateRootDirectory')
|
|
|
|
.mockReturnValue()
|
|
|
|
jest
|
|
|
|
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
|
|
|
|
.mockReturnValue([
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file1.txt',
|
|
|
|
destinationPath: 'file1.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/file2.txt',
|
|
|
|
destinationPath: 'file2.txt'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
|
|
|
|
destinationPath: 'dir/file3.txt'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
|
|
|
|
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
|
|
|
|
workflowRunBackendId: '1234',
|
|
|
|
workflowJobRunBackendId: '5678'
|
|
|
|
})
|
|
|
|
jest
|
|
|
|
.spyOn(retention, 'getExpiration')
|
|
|
|
.mockReturnValue(Timestamp.fromDate(mockDate))
|
|
|
|
jest
|
|
|
|
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
|
|
|
|
.mockReturnValue(
|
|
|
|
Promise.resolve({
|
|
|
|
ok: true,
|
|
|
|
signedUploadUrl: 'https://signed-upload-url.com'
|
|
|
|
})
|
|
|
|
)
|
|
|
|
jest
|
|
|
|
.spyOn(blobUpload, 'uploadZipToBlobStorage')
|
|
|
|
.mockReturnValue(Promise.reject(new Error('Upload progress stalled.')))
|
|
|
|
|
|
|
|
// ArtifactHttpClient mocks
|
|
|
|
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
|
|
|
|
jest
|
|
|
|
.spyOn(config, 'getResultsServiceUrl')
|
|
|
|
.mockReturnValue('https://test-url.com')
|
|
|
|
|
|
|
|
BlockBlobClient.prototype.uploadStream = jest
|
|
|
|
.fn()
|
|
|
|
.mockImplementation(
|
|
|
|
async (stream, bufferSize, maxConcurrency, options) => {
|
|
|
|
return new Promise<void>(resolve => {
|
|
|
|
// Call the onProgress callback with a progress event
|
|
|
|
options.onProgress({loadedBytes: 0})
|
|
|
|
|
|
|
|
// Wait for 31 seconds before resolving the promise
|
|
|
|
setTimeout(() => {
|
|
|
|
// Call the onProgress callback again to simulate progress
|
|
|
|
options.onProgress({loadedBytes: 100})
|
|
|
|
|
|
|
|
resolve()
|
2024-04-09 19:24:52 +00:00
|
|
|
}, 61000) // Delay longer than your timeout
|
2024-04-09 18:02:48 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-04-09 19:01:54 +00:00
|
|
|
jest.mock('fs')
|
2024-04-09 18:02:48 +00:00
|
|
|
const uploadResp = uploadArtifact(
|
|
|
|
'test-artifact',
|
|
|
|
[
|
|
|
|
'/home/user/files/plz-upload/file1.txt',
|
|
|
|
'/home/user/files/plz-upload/file2.txt',
|
|
|
|
'/home/user/files/plz-upload/dir/file3.txt'
|
|
|
|
],
|
|
|
|
'/home/user/files/plz-upload'
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(uploadResp).rejects.toThrow('Upload progress stalled.')
|
|
|
|
})
|
2023-08-23 20:54:31 +00:00
|
|
|
})
|