Test: add a timeout test for downloading chunks from the stream

This commit is contained in:
Daniel Kennedy
2025-09-24 14:05:45 -04:00
parent 714f93aedc
commit d26e9423f4
2 changed files with 45 additions and 5 deletions
@@ -3,7 +3,7 @@ import * as http from 'http'
import * as net from 'net'
import * as path from 'path'
import * as github from '@actions/github'
import {HttpClient} from '@actions/http-client'
import {HttpClient, HttpClientResponse} from '@actions/http-client'
import type {RestEndpointMethods} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types'
import archiver from 'archiver'
@@ -111,6 +111,16 @@ const mockGetArtifactSuccess = jest.fn(() => {
}
})
const mockGetArtifactHung = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 200
// Don't push any data or call push(null) to end the stream
// This creates a stream that hangs and never completes
return {
message
}
})
const mockGetArtifactFailure = jest.fn(() => {
const message = new http.IncomingMessage(new net.Socket())
message.statusCode = 500
@@ -611,4 +621,34 @@ describe('download-artifact', () => {
})
})
})
describe('streamExtractExternal', () => {
it('should fail if the timeout is exceeded', async () => {
const mockSlowGetArtifact = jest
.fn(mockGetArtifactHung)
const mockHttpClient = (HttpClient as jest.Mock).mockImplementation(
() => {
return {
get: mockSlowGetArtifact
}
}
)
try {
await streamExtractExternal(
fixtures.blobStorageUrl,
fixtures.workspaceDir,
{ timeout: 2 }
)
expect(true).toBe(false) // should not be called
} catch (e: any) {
expect(e).toBeInstanceOf(Error)
expect(e.message).toContain('did not respond in 2ms')
expect(mockHttpClient).toHaveBeenCalledWith(getUserAgentString())
expect(mockSlowGetArtifact).toHaveBeenCalledTimes(1)
}
})
})
})
@@ -64,7 +64,8 @@ async function streamExtract(
export async function streamExtractExternal(
url: string,
directory: string
directory: string,
opts: { timeout: number } = { timeout: 30 * 1000 }
): Promise<StreamExtractResponse> {
const client = new httpClient.HttpClient(getUserAgentString())
const response = await client.get(url)
@@ -74,18 +75,17 @@ export async function streamExtractExternal(
)
}
const timeout = 30 * 1000 // 30 seconds
let sha256Digest: string | undefined = undefined
return new Promise((resolve, reject) => {
const timerFn = (): void => {
const timeoutError = new Error(
`Blob storage chunk did not respond in ${timeout}ms`
`Blob storage chunk did not respond in ${opts.timeout}ms`
)
response.message.destroy(timeoutError)
reject(timeoutError)
}
const timer = setTimeout(timerFn, timeout)
const timer = setTimeout(timerFn, opts.timeout)
const hashStream = crypto.createHash('sha256').setEncoding('hex')
const passThrough = new stream.PassThrough()