@@ -21,73 +21,85 @@ import * as assert from 'assert';
2121import * as sinon from 'sinon' ;
2222import { afterEach , beforeEach } from 'mocha' ;
2323import { DdevUtils } from '../shared/utils/ddev-utils' ;
24+ import * as cp from 'child_process' ;
25+ import * as fs from 'fs' ;
2426
2527suite ( 'DdevUtils Test Suite' , ( ) => {
2628 let sandbox : sinon . SinonSandbox ;
27- let execSyncStub : sinon . SinonStub ;
29+ let spawnSyncStub : sinon . SinonStub ;
30+ let existsSyncStub : sinon . SinonStub ;
2831
2932 beforeEach ( ( ) => {
3033 sandbox = sinon . createSandbox ( ) ;
31- // Mock the entire child_process module
32- const childProcess = require ( 'child_process' ) ;
33- execSyncStub = sandbox . stub ( childProcess , 'execSync' ) ;
34+ // Use require to get the module to ensure we can stub it
35+ const cp = require ( 'child_process' ) ;
36+ const fs = require ( 'fs' ) ;
37+
38+ // Try to stub, but handle if it fails (basic check)
39+ try {
40+ spawnSyncStub = sandbox . stub ( cp , 'spawnSync' ) ;
41+ existsSyncStub = sandbox . stub ( fs , 'existsSync' ) ;
42+ } catch ( e ) {
43+ console . error ( 'Failed to stub modules:' , e ) ;
44+ throw e ;
45+ }
3446 } ) ;
3547
3648 afterEach ( ( ) => {
3749 sandbox . restore ( ) ;
3850 } ) ;
3951
4052 test ( 'hasDdevProject returns true when .ddev/config.yaml exists' , ( ) => {
41- execSyncStub . returns ( 'exists\n' ) ;
53+ existsSyncStub . returns ( true ) ;
4254
4355 const result = DdevUtils . hasDdevProject ( '/test/workspace' ) ;
4456
4557 assert . strictEqual ( result , true ) ;
46- assert . strictEqual ( execSyncStub . calledOnce , true ) ;
58+ assert . strictEqual ( existsSyncStub . calledOnce , true ) ;
4759 } ) ;
4860
4961 test ( 'hasDdevProject returns false when .ddev/config.yaml does not exist' , ( ) => {
50- execSyncStub . throws ( new Error ( 'File not found' ) ) ;
62+ existsSyncStub . returns ( false ) ;
5163
5264 const result = DdevUtils . hasDdevProject ( '/test/workspace' ) ;
5365
5466 assert . strictEqual ( result , false ) ;
5567 } ) ;
5668
5769 test ( 'isDdevRunning returns true when DDEV container is running' , ( ) => {
58- execSyncStub . returns ( '' ) ;
70+ spawnSyncStub . returns ( { status : 0 , stdout : 'test\n' } ) ;
5971
6072 const result = DdevUtils . isDdevRunning ( '/test/workspace' ) ;
6173
6274 assert . strictEqual ( result , true ) ;
6375 } ) ;
6476
6577 test ( 'isDdevRunning returns false when DDEV container is not running' , ( ) => {
66- execSyncStub . throws ( new Error ( 'Container not running' ) ) ;
78+ spawnSyncStub . returns ( { status : 1 , stderr : 'Container not running' } ) ;
6779
6880 const result = DdevUtils . isDdevRunning ( '/test/workspace' ) ;
6981
7082 assert . strictEqual ( result , false ) ;
7183 } ) ;
7284
7385 test ( 'isToolInstalled returns true when tool is available' , ( ) => {
74- execSyncStub . returns ( 'PHPStan 1.10.0\n' ) ;
86+ spawnSyncStub . returns ( { status : 0 , stdout : 'PHPStan 1.10.0\n' } ) ;
7587
7688 const result = DdevUtils . isToolInstalled ( 'phpstan' , '/test/workspace' ) ;
7789
7890 assert . strictEqual ( result , true ) ;
7991 } ) ;
8092
8193 test ( 'isToolInstalled returns false when tool is not available' , ( ) => {
82- execSyncStub . throws ( new Error ( 'Command not found' ) ) ;
94+ spawnSyncStub . returns ( { status : 1 , stderr : 'Command not found' } ) ;
8395
8496 const result = DdevUtils . isToolInstalled ( 'phpstan' , '/test/workspace' ) ;
8597
8698 assert . strictEqual ( result , false ) ;
8799 } ) ;
88100
89101 test ( 'validateDdevTool returns invalid result when no DDEV project found' , ( ) => {
90- execSyncStub . throws ( new Error ( 'File not found' ) ) ;
102+ existsSyncStub . returns ( false ) ;
91103
92104 const result = DdevUtils . validateDdevTool ( 'phpstan' , '/test/workspace' ) ;
93105
@@ -97,10 +109,10 @@ suite('DdevUtils Test Suite', () => {
97109 } ) ;
98110
99111 test ( 'validateDdevTool returns valid result when tool is available' , ( ) => {
100- // First call ( hasDdevProject) succeeds
101- execSyncStub . onFirstCall ( ) . returns ( 'exists\n' ) ;
102- // Second call (tool version check) succeeds
103- execSyncStub . onSecondCall ( ) . returns ( 'PHPStan 1.10.0\n' ) ;
112+ // hasDdevProject
113+ existsSyncStub . returns ( true ) ;
114+ // execDdev (tool check)
115+ spawnSyncStub . returns ( { status : 0 , stdout : 'PHPStan 1.10.0\n' } ) ;
104116
105117 const result = DdevUtils . validateDdevTool ( 'phpstan' , '/test/workspace' ) ;
106118
@@ -109,12 +121,12 @@ suite('DdevUtils Test Suite', () => {
109121 } ) ;
110122
111123 test ( 'validateDdevTool returns error message for DDEV issues' , ( ) => {
112- // First call ( hasDdevProject) succeeds
113- execSyncStub . onFirstCall ( ) . returns ( 'exists\n' ) ;
114- // Second call (tool version check) fails
115- execSyncStub . onSecondCall ( ) . throws ( new Error ( 'Container not running' ) ) ;
116- // Third call ( isDdevRunning) fails
117- execSyncStub . onThirdCall ( ) . throws ( new Error ( 'DDEV not running' ) ) ;
124+ // hasDdevProject
125+ existsSyncStub . returns ( true ) ;
126+ // execDdev (tool check) fails
127+ spawnSyncStub . onFirstCall ( ) . returns ( { status : 1 , stderr : 'Container not running' } ) ;
128+ // isDdevRunning fails
129+ spawnSyncStub . onSecondCall ( ) . returns ( { status : 1 , stderr : 'DDEV not running' } ) ;
118130
119131 const result = DdevUtils . validateDdevTool ( 'phpstan' , '/test/workspace' ) ;
120132
@@ -124,12 +136,12 @@ suite('DdevUtils Test Suite', () => {
124136 } ) ;
125137
126138 test ( 'validateDdevTool returns tool not found message when tool is missing' , ( ) => {
127- // First call ( hasDdevProject) succeeds
128- execSyncStub . onFirstCall ( ) . returns ( 'exists\n' ) ;
129- // Second call (tool version check) fails
130- execSyncStub . onSecondCall ( ) . throws ( new Error ( 'Command not found' ) ) ;
131- // Third call ( isDdevRunning) succeeds
132- execSyncStub . onThirdCall ( ) . returns ( '' ) ;
139+ // hasDdevProject
140+ existsSyncStub . returns ( true ) ;
141+ // execDdev (tool check) fails
142+ spawnSyncStub . onFirstCall ( ) . returns ( { status : 127 , stderr : 'Command not found' } ) ;
143+ // isDdevRunning succeeds
144+ spawnSyncStub . onSecondCall ( ) . returns ( { status : 0 , stdout : 'test\n' } ) ;
133145
134146 const result = DdevUtils . validateDdevTool ( 'phpstan' , '/test/workspace' ) ;
135147
@@ -139,27 +151,35 @@ suite('DdevUtils Test Suite', () => {
139151 assert . ok ( result . userMessage ?. includes ( 'phpstan/phpstan' ) ) ;
140152 } ) ;
141153
142- test ( 'execDdev wraps command with XDEBUG_MODE=off ' , ( ) => {
143- execSyncStub . returns ( 'output' ) ;
154+ test ( 'execDdev passes args array correctly ' , ( ) => {
155+ spawnSyncStub . returns ( { status : 0 , stdout : 'output' } ) ;
144156
145- const result = DdevUtils . execDdev ( 'phpstan analyze' , '/test/workspace' ) ;
157+ const result = DdevUtils . execDdev ( [ 'phpstan' , ' analyze'] , '/test/workspace' ) ;
146158
147159 assert . strictEqual ( result , 'output' ) ;
148- assert . strictEqual ( execSyncStub . calledOnce , true ) ;
149- const callArgs = execSyncStub . firstCall . args ;
150- assert . ok ( callArgs [ 0 ] . includes ( "XDEBUG_MODE=off" ) ) ;
151- assert . ok ( callArgs [ 0 ] . includes ( "bash -c" ) ) ;
152- assert . ok ( callArgs [ 0 ] . includes ( "'XDEBUG_MODE=off phpstan analyze'" ) ) ;
160+ assert . strictEqual ( spawnSyncStub . calledOnce , true ) ;
161+ const callArgs = spawnSyncStub . firstCall . args ;
162+ assert . strictEqual ( callArgs [ 0 ] , 'ddev' ) ;
163+ assert . deepStrictEqual ( callArgs [ 1 ] , [ 'exec' , 'env' , 'XDEBUG_MODE=off' , 'phpstan' , 'analyze' ] ) ;
164+ assert . strictEqual ( callArgs [ 2 ] . cwd , '/test/workspace' ) ;
153165 } ) ;
154166
155- test ( 'execDdev escapes single quotes in command ' , ( ) => {
156- execSyncStub . returns ( 'output' ) ;
167+ test ( 'execDdev throws on non-zero exit code ' , ( ) => {
168+ spawnSyncStub . returns ( { status : 1 , stderr : 'error' , stdout : '' } ) ;
157169
158- const result = DdevUtils . execDdev ( "echo 'hello'" , '/test/workspace' ) ;
170+ assert . throws ( ( ) => {
171+ DdevUtils . execDdev ( [ 'ls' ] , '/test/workspace' ) ;
172+ } , ( err : any ) => {
173+ return err . status === 1 && err . stderr === 'error' ;
174+ } ) ;
175+ } ) ;
159176
160- assert . strictEqual ( result , 'output' ) ;
161- const callArgs = execSyncStub . firstCall . args ;
162- // Should be: ddev exec bash -c 'XDEBUG_MODE=off echo '\''hello'\'''
163- assert . ok ( callArgs [ 0 ] . includes ( "'XDEBUG_MODE=off echo '\\''hello'\\'''" ) ) ;
177+ test ( 'execDdev returns stdout on allowed non-zero exit code' , ( ) => {
178+ spawnSyncStub . returns ( { status : 1 , stdout : 'partial output' } ) ;
179+
180+ const result = DdevUtils . execDdev ( [ 'ls' ] , '/test/workspace' , [ 0 , 1 ] ) ;
181+
182+ assert . strictEqual ( result , 'partial output' ) ;
164183 } ) ;
165184} ) ;
185+
0 commit comments