@@ -3,6 +3,22 @@ import * as fs from "fs/promises"
33import * as path from "path"
44import { EventEmitter } from "events"
55
6+ // Mock crypto — verifyChecksum reads the archive file (mocked via createReadStream)
7+ // and computes a SHA-256. We make digest() dynamically return the expected checksum
8+ // for the current process.platform/arch so verification always passes in unit tests.
9+ const CHECKSUMS : Record < string , string > = {
10+ "linux-x64" : "2bd4117dbd1ff7a26ed5ef44dad8d43162a4b9f431ec0bcc9dd2f9c6f5952e28" ,
11+ "linux-arm64" : "177d14f41d3272594844a2635d59d97ad20400868a874a59169fd26a868c32a5" ,
12+ "darwin-arm64" : "9130f447ff2c21803853a9aee58268f0e05134326384ac23d8b74ed22905e118" ,
13+ "win32-x64" : "c8ae86f3703675e356824e08cf79c8a20c41c602296d2a5bff15bf35d762a46b" ,
14+ }
15+ vi . mock ( "crypto" , ( ) => ( {
16+ createHash : vi . fn ( ( ) => ( {
17+ update : vi . fn ( ) . mockReturnThis ( ) ,
18+ digest : vi . fn ( ( ) => CHECKSUMS [ `${ process . platform } -${ process . arch } ` ] ?? "no-match" ) ,
19+ } ) ) ,
20+ } ) )
21+
622// Mock fs/promises
723vi . mock ( "fs/promises" , ( ) => ( {
824 mkdir : vi . fn ( ) . mockResolvedValue ( undefined ) ,
@@ -14,27 +30,37 @@ vi.mock("fs/promises", () => ({
1430 writeFile : vi . fn ( ) . mockResolvedValue ( undefined ) ,
1531} ) )
1632
17- // Mock fs (createWriteStream)
33+ // Mock fs (createWriteStream and createReadStream for checksum verification )
1834const mockWriteStream = {
1935 on : vi . fn ( ) ,
2036 close : vi . fn ( ) ,
2137}
2238vi . mock ( "fs" , ( ) => ( {
2339 createWriteStream : vi . fn ( ( ) => mockWriteStream ) ,
40+ createReadStream : vi . fn ( ( ) => {
41+ const { EventEmitter } = require ( "events" )
42+ const stream = new EventEmitter ( )
43+ setImmediate ( ( ) => {
44+ stream . emit ( "data" , Buffer . from ( "fake-archive-content" ) )
45+ stream . emit ( "end" )
46+ } )
47+ return stream
48+ } ) ,
2449} ) )
2550
26- // Mock https
27- const mockRequest = new EventEmitter ( ) as any
28- mockRequest . setTimeout = vi . fn ( )
29-
30- const mockResponse = new EventEmitter ( ) as any
31- mockResponse . statusCode = 200
32- mockResponse . headers = { }
33- mockResponse . pipe = vi . fn ( )
34- mockResponse . destroy = vi . fn ( )
51+ // Mock https — fresh emitters per invocation to avoid listener leaks across tests
52+ let mockRequest : any
53+ let mockResponse : any
3554
3655vi . mock ( "https" , ( ) => ( {
3756 get : vi . fn ( ( _url : string , callback : ( res : any ) => void ) => {
57+ mockRequest = Object . assign ( new EventEmitter ( ) , { setTimeout : vi . fn ( ) } )
58+ mockResponse = Object . assign ( new EventEmitter ( ) , {
59+ statusCode : 200 ,
60+ headers : { } ,
61+ pipe : vi . fn ( ) ,
62+ destroy : vi . fn ( ) ,
63+ } )
3864 setImmediate ( ( ) => callback ( mockResponse ) )
3965 return mockRequest
4066 } ) ,
@@ -64,11 +90,6 @@ import { spawn } from "child_process"
6490describe ( "semble-downloader" , ( ) => {
6591 beforeEach ( ( ) => {
6692 vi . clearAllMocks ( )
67- // Reset response defaults
68- mockResponse . statusCode = 200
69- mockResponse . headers = { }
70- mockResponse . pipe = vi . fn ( )
71- mockResponse . destroy = vi . fn ( )
7293 mockWriteStream . on = vi . fn ( )
7394 mockWriteStream . close = vi . fn ( )
7495 } )
@@ -252,7 +273,17 @@ describe("semble-downloader", () => {
252273 ; ( fs . readFile as any ) . mockRejectedValue ( new Error ( "ENOENT" ) )
253274
254275 // Simulate HTTP error response
255- mockResponse . statusCode = 404
276+ ; ( https . get as any ) . mockImplementation ( ( _url : string , callback : ( res : any ) => void ) => {
277+ const res = Object . assign ( new EventEmitter ( ) , {
278+ statusCode : 404 ,
279+ headers : { } ,
280+ pipe : vi . fn ( ) ,
281+ destroy : vi . fn ( ) ,
282+ } )
283+ setImmediate ( ( ) => callback ( res ) )
284+ const req = Object . assign ( new EventEmitter ( ) , { setTimeout : vi . fn ( ) } )
285+ return req
286+ } )
256287
257288 try {
258289 await expect ( downloadSemble ( "/storage" ) ) . rejects . toThrow ( "Failed to download semble" )
0 commit comments