66 */
77import { type AxiosInstance } from "axios" ;
88import { type Api } from "coder/site/src/api/api" ;
9- import * as fsPromises from "node:fs/promises" ;
9+ import * as fs from "node:fs/promises" ;
1010import * as os from "node:os" ;
1111import * as path from "node:path" ;
1212import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
@@ -46,7 +46,11 @@ function setupCliUtilsMocks(version: string) {
4646
4747function createMockApi (
4848 version : string ,
49- options : { chunkSize ?: number ; delay ?: number } = { } ,
49+ options : {
50+ chunkSize ?: number ;
51+ delay ?: number ;
52+ error ?: NodeJS . ErrnoException ;
53+ } = { } ,
5054) : Api {
5155 const mockAxios = {
5256 get : vi . fn ( ) . mockImplementation ( ( ) =>
@@ -66,7 +70,7 @@ function createMockApi(
6670}
6771
6872function setupManager ( testDir : string ) : CliManager {
69- const _progressReporter = new MockProgressReporter ( ) ;
73+ const _mockProgress = new MockProgressReporter ( ) ;
7074 const mockConfig = new MockConfigurationProvider ( ) ;
7175 mockConfig . set ( "coder.disableSignatureVerification" , true ) ;
7276
@@ -81,13 +85,13 @@ describe("CliManager Concurrent Downloads", () => {
8185 let testDir : string ;
8286
8387 beforeEach ( async ( ) => {
84- testDir = await fsPromises . mkdtemp (
88+ testDir = await fs . mkdtemp (
8589 path . join ( os . tmpdir ( ) , "climanager-concurrent-" ) ,
8690 ) ;
8791 } ) ;
8892
8993 afterEach ( async ( ) => {
90- await fsPromises . rm ( testDir , { recursive : true , force : true } ) ;
94+ await fs . rm ( testDir , { recursive : true , force : true } ) ;
9195 } ) ;
9296
9397 it ( "handles multiple concurrent downloads without race conditions" , async ( ) => {
@@ -110,18 +114,16 @@ describe("CliManager Concurrent Downloads", () => {
110114 }
111115
112116 // Verify binary exists and lock/progress files are cleaned up
113- await expect ( fsPromises . access ( binaryPath ) ) . resolves . toBeUndefined ( ) ;
114- await expect ( fsPromises . access ( binaryPath + ".lock" ) ) . rejects . toThrow ( ) ;
115- await expect (
116- fsPromises . access ( binaryPath + ".progress.log" ) ,
117- ) . rejects . toThrow ( ) ;
117+ await expect ( fs . access ( binaryPath ) ) . resolves . toBeUndefined ( ) ;
118+ await expect ( fs . access ( binaryPath + ".lock" ) ) . rejects . toThrow ( ) ;
119+ await expect ( fs . access ( binaryPath + ".progress.log" ) ) . rejects . toThrow ( ) ;
118120 } ) ;
119121
120122 it ( "redownloads when version mismatch is detected concurrently" , async ( ) => {
121123 const manager = setupManager ( testDir ) ;
122124 setupCliUtilsMocks ( "1.2.3" ) ;
123125 vi . mocked ( cliUtils . version ) . mockImplementation ( async ( binPath ) => {
124- const fileContent = await fsPromises . readFile ( binPath , {
126+ const fileContent = await fs . readFile ( binPath , {
125127 encoding : "utf-8" ,
126128 } ) ;
127129 return fileContent . includes ( "1.2.3" ) ? "1.2.3" : "2.0.0" ;
@@ -151,12 +153,39 @@ describe("CliManager Concurrent Downloads", () => {
151153 }
152154
153155 // Binary should be updated to 2.0.0, lock/progress files cleaned up
154- await expect ( fsPromises . access ( binaryPath ) ) . resolves . toBeUndefined ( ) ;
155- const finalContent = await fsPromises . readFile ( binaryPath , "utf8" ) ;
156+ await expect ( fs . access ( binaryPath ) ) . resolves . toBeUndefined ( ) ;
157+ const finalContent = await fs . readFile ( binaryPath , "utf8" ) ;
156158 expect ( finalContent ) . toContain ( "v2.0.0" ) ;
157- await expect ( fsPromises . access ( binaryPath + ".lock" ) ) . rejects . toThrow ( ) ;
158- await expect (
159- fsPromises . access ( binaryPath + ".progress.log" ) ,
160- ) . rejects . toThrow ( ) ;
159+ await expect ( fs . access ( binaryPath + ".lock" ) ) . rejects . toThrow ( ) ;
160+ await expect ( fs . access ( binaryPath + ".progress.log" ) ) . rejects . toThrow ( ) ;
161+ } ) ;
162+
163+ it . each ( [
164+ {
165+ name : "disk storage insufficient" ,
166+ code : "ENOSPC" ,
167+ message : "no space left on device" ,
168+ } ,
169+ {
170+ name : "connection timeout" ,
171+ code : "ETIMEDOUT" ,
172+ message : "connection timed out" ,
173+ } ,
174+ ] ) ( "handles $name error during download" , async ( { code, message } ) => {
175+ const manager = setupManager ( testDir ) ;
176+ setupCliUtilsMocks ( "1.2.3" ) ;
177+
178+ const error = new Error ( `${ code } : ${ message } ` ) ;
179+ ( error as NodeJS . ErrnoException ) . code = code ;
180+ const mockApi = createMockApi ( "1.2.3" , { error } ) ;
181+
182+ const label = "test.coder.com" ;
183+ const binaryPath = path . join ( testDir , label , "bin" , "coder-linux-amd64" ) ;
184+
185+ await expect ( manager . fetchBinary ( mockApi , label ) ) . rejects . toThrow (
186+ `Unable to download binary: ${ code } : ${ message } ` ,
187+ ) ;
188+
189+ await expect ( fs . access ( binaryPath + ".lock" ) ) . rejects . toThrow ( ) ;
161190 } ) ;
162191} ) ;
0 commit comments