11import { once } from "events" ;
2+ import * as fs from "fs" ;
3+ import { ClientRequest , IncomingMessage } from "http" ;
24import * as path from "path" ;
35
6+ import * as core from "@actions/core" ;
47import * as toolcache from "@actions/tool-cache" ;
58import test from "ava" ;
9+ import { https } from "follow-redirects" ;
610import nock from "nock" ;
711import * as sinon from "sinon" ;
812
913import { getRunnerLogger } from "./logging" ;
1014import * as tar from "./tar" ;
1115import { setupTests } from "./testing-utils" ;
1216import { downloadAndExtract } from "./tools-download" ;
13- import { withTmpDir } from "./util" ;
17+ import * as util from "./util" ;
1418
1519setupTests ( test ) ;
1620
1721test . serial (
1822 "downloadAndExtract reports the durations when downloading before extracting" ,
1923 async ( t ) => {
20- await withTmpDir ( async ( tmpDir ) => {
24+ await util . withTmpDir ( async ( tmpDir ) => {
2125 const archivePath = path . join ( tmpDir , "codeql-bundle.tar.gz" ) ;
2226 const destination = path . join ( tmpDir , "codeql" ) ;
2327 sinon . stub ( toolcache , "downloadTool" ) . resolves ( archivePath ) ;
@@ -43,13 +47,16 @@ test.serial(
4347test . serial (
4448 "downloadAndExtract falls back to downloading before extracting if streaming fails" ,
4549 async ( t ) => {
46- await withTmpDir ( async ( tmpDir ) => {
50+ await util . withTmpDir ( async ( tmpDir ) => {
4751 sinon . stub ( process , "platform" ) . value ( "linux" ) ;
4852 const archivePath = path . join ( tmpDir , "codeql-bundle.tar.zst" ) ;
4953 const destination = path . join ( tmpDir , "codeql" ) ;
5054 const downloadTool = sinon
5155 . stub ( toolcache , "downloadTool" )
52- . resolves ( archivePath ) ;
56+ . callsFake ( async ( ) => {
57+ t . false ( fs . existsSync ( destination ) ) ;
58+ return archivePath ;
59+ } ) ;
5360 const extract = sinon . stub ( tar , "extract" ) . resolves ( destination ) ;
5461 const extractTarZst = sinon . stub ( tar , "extractTarZst" ) . resolves ( ) ;
5562 const request = nock ( "https://example.com" )
@@ -78,10 +85,134 @@ test.serial(
7885 } ,
7986) ;
8087
88+ test . serial (
89+ "downloadAndExtract rethrows a 404 rather than retrying the download" ,
90+ async ( t ) => {
91+ await util . withTmpDir ( async ( tmpDir ) => {
92+ sinon . stub ( process , "platform" ) . value ( "linux" ) ;
93+ const destination = path . join ( tmpDir , "codeql" ) ;
94+ const downloadTool = sinon . stub ( toolcache , "downloadTool" ) ;
95+ const extractTarZst = sinon . stub ( tar , "extractTarZst" ) . resolves ( ) ;
96+ const request = nock ( "https://example.com" )
97+ . get ( "/codeql-bundle.tar.zst" )
98+ . reply ( 404 , "Not found" ) ;
99+
100+ const error = await t . throwsAsync (
101+ downloadAndExtract (
102+ "https://example.com/codeql-bundle.tar.zst" ,
103+ "zstd" ,
104+ destination ,
105+ undefined ,
106+ { } ,
107+ { type : "gnu" , version : "1.34" } ,
108+ getRunnerLogger ( true ) ,
109+ ) ,
110+ {
111+ instanceOf : util . HTTPError ,
112+ message :
113+ "Failed to download CodeQL bundle from https://example.com/codeql-bundle.tar.zst. HTTP status code: 404." ,
114+ } ,
115+ ) ;
116+
117+ t . is ( error ?. status , 404 ) ;
118+ t . true ( request . isDone ( ) ) ;
119+ t . false ( extractTarZst . called ) ;
120+ t . false ( downloadTool . called ) ;
121+ t . false ( fs . existsSync ( destination ) ) ;
122+ } ) ;
123+ } ,
124+ ) ;
125+
126+ test . serial (
127+ "downloadAndExtract falls back to downloading before extracting on a server error" ,
128+ async ( t ) => {
129+ await util . withTmpDir ( async ( tmpDir ) => {
130+ sinon . stub ( process , "platform" ) . value ( "linux" ) ;
131+ const archivePath = path . join ( tmpDir , "codeql-bundle.tar.zst" ) ;
132+ const destination = path . join ( tmpDir , "codeql" ) ;
133+ const downloadTool = sinon
134+ . stub ( toolcache , "downloadTool" )
135+ . callsFake ( async ( ) => {
136+ t . false ( fs . existsSync ( destination ) ) ;
137+ return archivePath ;
138+ } ) ;
139+ const extract = sinon . stub ( tar , "extract" ) . resolves ( destination ) ;
140+ const extractTarZst = sinon . stub ( tar , "extractTarZst" ) . resolves ( ) ;
141+ const request = nock ( "https://example.com" )
142+ . get ( "/codeql-bundle.tar.zst" )
143+ . reply ( 500 ) ;
144+
145+ const statusReport = await downloadAndExtract (
146+ "https://example.com/codeql-bundle.tar.zst" ,
147+ "zstd" ,
148+ destination ,
149+ undefined ,
150+ { } ,
151+ { type : "gnu" , version : "1.34" } ,
152+ getRunnerLogger ( true ) ,
153+ ) ;
154+
155+ t . assert ( Number . isInteger ( statusReport . downloadDurationMs ) ) ;
156+ t . true ( request . isDone ( ) ) ;
157+ t . false ( extractTarZst . called ) ;
158+ t . true ( downloadTool . calledOnce ) ;
159+ t . true ( extract . calledOnce ) ;
160+ } ) ;
161+ } ,
162+ ) ;
163+
164+ test . serial (
165+ "downloadAndExtract handles an unknown status as a non-HTTP error" ,
166+ async ( t ) => {
167+ const asHTTPError = sinon . spy ( util , "asHTTPError" ) ;
168+ await util . withTmpDir ( async ( tmpDir ) => {
169+ sinon . stub ( process , "platform" ) . value ( "linux" ) ;
170+ const archivePath = path . join ( tmpDir , "codeql-bundle.tar.zst" ) ;
171+ const destination = path . join ( tmpDir , "codeql" ) ;
172+ const response = sinon . createStubInstance ( IncomingMessage ) ;
173+ response . statusCode = undefined ;
174+ sinon
175+ . stub ( https , "get" )
176+ . callsArgWith ( 2 , response )
177+ . returns ( sinon . createStubInstance ( ClientRequest ) ) ;
178+ const warning = sinon . stub ( core , "warning" ) ;
179+ const downloadTool = sinon
180+ . stub ( toolcache , "downloadTool" )
181+ . resolves ( archivePath ) ;
182+ const extract = sinon . stub ( tar , "extract" ) . resolves ( destination ) ;
183+ const extractTarZst = sinon . stub ( tar , "extractTarZst" ) . resolves ( ) ;
184+
185+ await downloadAndExtract (
186+ "https://example.com/codeql-bundle.tar.zst" ,
187+ "zstd" ,
188+ destination ,
189+ undefined ,
190+ { } ,
191+ { type : "gnu" , version : "1.34" } ,
192+ getRunnerLogger ( true ) ,
193+ ) ;
194+
195+ t . is (
196+ warning . firstCall . args [ 0 ] ,
197+ "Failed to download and extract CodeQL bundle using streaming with error: Failed to download CodeQL bundle from https://example.com/codeql-bundle.tar.zst." ,
198+ ) ;
199+ t . true ( response . resume . calledOnce ) ;
200+ t . false ( extractTarZst . called ) ;
201+ t . true ( downloadTool . calledOnce ) ;
202+ t . true ( extract . calledOnce ) ;
203+ } ) ;
204+
205+ t . true ( asHTTPError . calledOnce ) ;
206+ t . true ( asHTTPError . firstCall . args [ 0 ] instanceof Error ) ;
207+ t . false ( asHTTPError . firstCall . args [ 0 ] instanceof util . HTTPError ) ;
208+ t . is ( asHTTPError . firstCall . returnValue , undefined ) ;
209+ } ,
210+ ) ;
211+
81212test . serial (
82213 "downloadAndExtract reports only the total duration when streaming extraction" ,
83214 async ( t ) => {
84- await withTmpDir ( async ( tmpDir ) => {
215+ await util . withTmpDir ( async ( tmpDir ) => {
85216 sinon . stub ( process , "platform" ) . value ( "linux" ) ;
86217 const downloadTool = sinon . stub ( toolcache , "downloadTool" ) ;
87218 const extractTarZst = sinon
0 commit comments