11import "../../../../tests/_mocks/fs.js" ;
2- import child_process from "node:child_process" ;
2+ import child_process , { spawn } from "node:child_process" ;
3+ import { EventEmitter } from "node:events" ;
4+ import path from "node:path" ;
35import { logger } from "../../../core/utils/logger.js" ;
46import { vol } from "memfs" ;
57import * as accountModule from "../../../core/account.js" ;
@@ -10,6 +12,26 @@ import { loadPackageJson } from "../../../core/utils/json.js";
1012
1113const pkg = loadPackageJson ( ) ;
1214
15+ // Prevent transitive jsonwebtoken/buffer-equal-constant-time loading error
16+ // by fully mocking modules that pull in Azure SDK → jsonwebtoken chain
17+ vi . mock ( "../../../core/account.js" , ( ) => ( {
18+ chooseOrCreateProjectDetails : vi . fn ( ( ) => Promise . resolve ( { resourceGroup : "mock-rg" , staticSiteName : "mock-site" } ) ) ,
19+ getStaticSiteDeployment : vi . fn ( ( ) => Promise . resolve ( { } ) ) ,
20+ authenticateWithAzureIdentity : vi . fn ( ) ,
21+ listSubscriptions : vi . fn ( ) ,
22+ listTenants : vi . fn ( ) ,
23+ } ) ) ;
24+
25+ vi . mock ( "../login/login.js" , ( ) => ( {
26+ login : vi . fn ( ( ) =>
27+ Promise . resolve ( {
28+ credentialChain : { } ,
29+ subscriptionId : "mock-subscription-id" ,
30+ } ) ,
31+ ) ,
32+ loginCommand : vi . fn ( ) ,
33+ } ) ) ;
34+
1335vi . mock ( "../../../core/utils/logger" , ( ) => {
1436 return {
1537 logger : {
@@ -22,8 +44,15 @@ vi.mock("../../../core/utils/logger", () => {
2244 } ;
2345} ) ;
2446
25- //vi.spyOn(process, "exit").mockImplementation(() => {});
26- vi . spyOn ( child_process , "spawn" ) . mockImplementation ( vi . fn ( ) ) ;
47+ vi . mock ( "node:child_process" , async ( importOriginal ) => {
48+ const actual : typeof child_process = await importOriginal ( ) ;
49+ return {
50+ ...actual ,
51+ default : { ...actual , spawn : vi . fn ( ) } ,
52+ spawn : vi . fn ( ) ,
53+ } ;
54+ } ) ;
55+
2756vi . spyOn ( deployClientModule , "getDeployClientPath" ) . mockImplementation ( ( ) => {
2857 return Promise . resolve ( {
2958 binary : "mock-binary" ,
@@ -32,17 +61,6 @@ vi.spyOn(deployClientModule, "getDeployClientPath").mockImplementation(() => {
3261} ) ;
3362vi . spyOn ( deployClientModule , "cleanUp" ) . mockImplementation ( ( ) => { } ) ;
3463
35- vi . spyOn ( accountModule , "getStaticSiteDeployment" ) . mockImplementation ( ( ) => Promise . resolve ( { } ) ) ;
36-
37- vi . spyOn ( loginModule , "login" ) . mockImplementation ( ( ) => {
38- return Promise . resolve ( {
39- credentialChain : { } as any ,
40- subscriptionId : "mock-subscription-id" ,
41- resourceGroup : "mock-resource-group-name" ,
42- staticSiteName : "mock-static-site-name" ,
43- } ) ;
44- } ) ;
45-
4664describe ( "deploy" , ( ) => {
4765 const OLD_ENV = process . env ;
4866
@@ -177,4 +195,69 @@ describe("deploy", () => {
177195 } ,
178196 } ) ;
179197 } ) ;
198+
199+ describe ( "StaticSitesClient process handling" , ( ) => {
200+ let mockChild : EventEmitter & { stdout : EventEmitter ; stderr : EventEmitter } ;
201+ let exitSpy : ReturnType < typeof vi . spyOn > ;
202+
203+ beforeEach ( ( ) => {
204+ // Create mock child process with stdout/stderr EventEmitters
205+ const stdout = new EventEmitter ( ) ;
206+ const stderr = new EventEmitter ( ) ;
207+ mockChild = Object . assign ( new EventEmitter ( ) , { stdout, stderr } ) ;
208+
209+ // Set up spawn mock to return the mock child process
210+ vi . mocked ( spawn ) . mockReturnValue ( mockChild as any ) ;
211+ vi . spyOn ( deployClientModule , "getDeployClientPath" ) . mockResolvedValue ( {
212+ binary : "mock-binary" ,
213+ buildId : "0.0.0" ,
214+ } ) ;
215+ vi . spyOn ( deployClientModule , "cleanUp" ) . mockImplementation ( ( ) => { } ) ;
216+
217+ // Mock process.exit to prevent test runner from exiting
218+ exitSpy = vi . spyOn ( process , "exit" ) . mockImplementation ( ( ( ) => { } ) as any ) ;
219+
220+ // Provide deployment token via env to skip login flow
221+ process . env . SWA_CLI_DEPLOYMENT_TOKEN = "test-token" ;
222+
223+ // Create required filesystem structure in memfs
224+ const cwd = process . cwd ( ) ;
225+ vol . fromJSON ( {
226+ [ path . join ( "/test-output" , "index.html" ) ] : "hello" ,
227+ [ path . join ( cwd , "placeholder" ) ] : "" ,
228+ } ) ;
229+ } ) ;
230+
231+ it ( "should capture stderr and pass to logger.error" , async ( ) => {
232+ await deploy ( { outputLocation : "/test-output" , dryRun : false } ) ;
233+
234+ mockChild . stderr . emit ( "data" , Buffer . from ( "some error from binary" ) ) ;
235+
236+ expect ( logger . error ) . toHaveBeenCalledWith ( "some error from binary" ) ;
237+ } ) ;
238+
239+ it ( "should fail spinner and log error on non-zero exit code" , async ( ) => {
240+ await deploy ( { outputLocation : "/test-output" , dryRun : false } ) ;
241+
242+ mockChild . emit ( "close" , 1 ) ;
243+
244+ expect ( logger . error ) . toHaveBeenCalledWith ( "The deployment binary exited with code 1." ) ;
245+ } ) ;
246+
247+ it ( "should call process.exit(1) on non-zero exit code" , async ( ) => {
248+ await deploy ( { outputLocation : "/test-output" , dryRun : false } ) ;
249+
250+ mockChild . emit ( "close" , 127 ) ;
251+
252+ expect ( exitSpy ) . toHaveBeenCalledWith ( 1 ) ;
253+ } ) ;
254+
255+ it ( "should succeed without calling process.exit on exit code 0" , async ( ) => {
256+ await deploy ( { outputLocation : "/test-output" , dryRun : false } ) ;
257+
258+ mockChild . emit ( "close" , 0 ) ;
259+
260+ expect ( exitSpy ) . not . toHaveBeenCalled ( ) ;
261+ } ) ;
262+ } ) ;
180263} ) ;
0 commit comments