11import { EventEmitter } from "events" ;
22import { Readable } from "stream" ;
3- import { describe , it , expect , vi , beforeEach , afterEach } from "bun:test" ;
3+ import { describe , it , expect , vi , beforeEach , afterEach , spyOn } from "bun:test" ;
44import { CoderService , compareVersions } from "./coderService" ;
5+ import * as childProcess from "child_process" ;
6+
7+ // eslint-disable-next-line @typescript-eslint/no-empty-function
8+ const noop = ( ) => { } ;
59
610function mockExecOk ( stdout : string , stderr = "" ) : void {
711 mockExecAsync . mockReturnValue ( {
@@ -17,6 +21,12 @@ function mockExecError(error: Error): void {
1721 } ) ;
1822}
1923
24+ /**
25+ * Mock spawn for streaming createWorkspace() tests.
26+ * Uses spyOn instead of vi.mock to avoid polluting other test files.
27+ */
28+ let spawnSpy : ReturnType < typeof spyOn < typeof childProcess , "spawn" > > | null = null ;
29+
2030function mockCoderCommandResult ( options : {
2131 stdout ?: string ;
2232 stderr ?: string ;
@@ -26,7 +36,7 @@ function mockCoderCommandResult(options: {
2636 const stderr = Readable . from ( options . stderr ? [ Buffer . from ( options . stderr ) ] : [ ] ) ;
2737 const events = new EventEmitter ( ) ;
2838
29- mockSpawn . mockReturnValue ( {
39+ spawnSpy ? .mockReturnValue ( {
3040 stdout,
3141 stderr,
3242 exitCode : null ,
@@ -39,19 +49,8 @@ function mockCoderCommandResult(options: {
3949 // Emit close after handlers are attached.
4050 setTimeout ( ( ) => events . emit ( "close" , options . exitCode ) , 0 ) ;
4151}
42- // eslint-disable-next-line @typescript-eslint/no-empty-function
43- const noop = ( ) => { } ;
44-
45- // Mock execAsync
4652
47- // Mock spawn for streaming createWorkspace()
48- void vi . mock ( "child_process" , ( ) => ( {
49- spawn : vi . fn ( ) ,
50- } ) ) ;
51-
52- import { spawn } from "child_process" ;
53-
54- const mockSpawn = spawn as ReturnType < typeof vi . fn > ;
53+ // Mock execAsync (this is safe - it's our own module, not a Node.js built-in)
5554void vi . mock ( "@/node/utils/disposableExec" , ( ) => ( {
5655 execAsync : vi . fn ( ) ,
5756} ) ) ;
@@ -67,10 +66,14 @@ describe("CoderService", () => {
6766 beforeEach ( ( ) => {
6867 service = new CoderService ( ) ;
6968 vi . clearAllMocks ( ) ;
69+ // Set up spawn spy for tests that use mockCoderCommandResult
70+ spawnSpy = spyOn ( childProcess , "spawn" ) ;
7071 } ) ;
7172
7273 afterEach ( ( ) => {
7374 service . clearCache ( ) ;
75+ spawnSpy ?. mockRestore ( ) ;
76+ spawnSpy = null ;
7477 } ) ;
7578
7679 describe ( "getCoderInfo" , ( ) => {
@@ -245,42 +248,24 @@ describe("CoderService", () => {
245248 } ) ;
246249
247250 describe ( "listWorkspaces" , ( ) => {
248- it ( "returns only running workspaces by default " , async ( ) => {
251+ it ( "returns all workspaces regardless of status " , async ( ) => {
249252 mockExecAsync . mockReturnValue ( {
250253 result : Promise . resolve ( {
251254 stdout : JSON . stringify ( [
252255 { name : "ws-1" , template_name : "t1" , latest_build : { status : "running" } } ,
253256 { name : "ws-2" , template_name : "t2" , latest_build : { status : "stopped" } } ,
254- { name : "ws-3" , template_name : "t3" , latest_build : { status : "running " } } ,
257+ { name : "ws-3" , template_name : "t3" , latest_build : { status : "starting " } } ,
255258 ] ) ,
256259 } ) ,
257260 [ Symbol . dispose ] : noop ,
258261 } ) ;
259262
260263 const workspaces = await service . listWorkspaces ( ) ;
261264
262- expect ( workspaces ) . toEqual ( [
263- { name : "ws-1" , templateName : "t1" , status : "running" } ,
264- { name : "ws-3" , templateName : "t3" , status : "running" } ,
265- ] ) ;
266- } ) ;
267-
268- it ( "returns all workspaces when filterRunning is false" , async ( ) => {
269- mockExecAsync . mockReturnValue ( {
270- result : Promise . resolve ( {
271- stdout : JSON . stringify ( [
272- { name : "ws-1" , template_name : "t1" , latest_build : { status : "running" } } ,
273- { name : "ws-2" , template_name : "t2" , latest_build : { status : "stopped" } } ,
274- ] ) ,
275- } ) ,
276- [ Symbol . dispose ] : noop ,
277- } ) ;
278-
279- const workspaces = await service . listWorkspaces ( false ) ;
280-
281265 expect ( workspaces ) . toEqual ( [
282266 { name : "ws-1" , templateName : "t1" , status : "running" } ,
283267 { name : "ws-2" , templateName : "t2" , status : "stopped" } ,
268+ { name : "ws-3" , templateName : "t3" , status : "starting" } ,
284269 ] ) ;
285270 } ) ;
286271
@@ -483,7 +468,7 @@ describe("CoderService", () => {
483468 const stderr = Readable . from ( [ Buffer . from ( "err-1\n" ) ] ) ;
484469 const events = new EventEmitter ( ) ;
485470
486- mockSpawn . mockReturnValue ( {
471+ spawnSpy ! . mockReturnValue ( {
487472 stdout,
488473 stderr,
489474 kill : vi . fn ( ) ,
@@ -498,7 +483,7 @@ describe("CoderService", () => {
498483 lines . push ( line ) ;
499484 }
500485
501- expect ( mockSpawn ) . toHaveBeenCalledWith (
486+ expect ( spawnSpy ) . toHaveBeenCalledWith (
502487 "coder" ,
503488 [ "create" , "my-workspace" , "-t" , "my-template" , "--yes" ] ,
504489 { stdio : [ "ignore" , "pipe" , "pipe" ] }
@@ -517,7 +502,7 @@ describe("CoderService", () => {
517502 const stderr = Readable . from ( [ ] ) ;
518503 const events = new EventEmitter ( ) ;
519504
520- mockSpawn . mockReturnValue ( {
505+ spawnSpy ! . mockReturnValue ( {
521506 stdout,
522507 stderr,
523508 kill : vi . fn ( ) ,
@@ -530,7 +515,7 @@ describe("CoderService", () => {
530515 // drain
531516 }
532517
533- expect ( mockSpawn ) . toHaveBeenCalledWith (
518+ expect ( spawnSpy ) . toHaveBeenCalledWith (
534519 "coder" ,
535520 [ "create" , "ws" , "-t" , "tmpl" , "--yes" , "--preset" , "preset" ] ,
536521 { stdio : [ "ignore" , "pipe" , "pipe" ] }
@@ -549,7 +534,7 @@ describe("CoderService", () => {
549534 const stderr = Readable . from ( [ ] ) ;
550535 const events = new EventEmitter ( ) ;
551536
552- mockSpawn . mockReturnValue ( {
537+ spawnSpy ! . mockReturnValue ( {
553538 stdout,
554539 stderr,
555540 kill : vi . fn ( ) ,
@@ -562,7 +547,7 @@ describe("CoderService", () => {
562547 // drain
563548 }
564549
565- expect ( mockSpawn ) . toHaveBeenCalledWith (
550+ expect ( spawnSpy ) . toHaveBeenCalledWith (
566551 "coder" ,
567552 [
568553 "create" ,
@@ -587,7 +572,7 @@ describe("CoderService", () => {
587572 const stderr = Readable . from ( [ ] ) ;
588573 const events = new EventEmitter ( ) ;
589574
590- mockSpawn . mockReturnValue ( {
575+ spawnSpy ! . mockReturnValue ( {
591576 stdout,
592577 stderr,
593578 kill : vi . fn ( ) ,
0 commit comments