1
1
import * as childProcess from 'child_process' ;
2
+ import * as fs from 'fs' ;
3
+ import * as path from 'path' ;
2
4
3
- import { determineCdsCommand , resetCdsCommandCache } from '../../../../src/cds/compiler' ;
5
+ import { determineCdsCommand , resetCdsCommandCache } from '../../../../src/cds/compiler/command' ;
6
+ import { fileExists } from '../../../../src/filesystem' ;
7
+ import { cdsExtractorLog } from '../../../../src/logging' ;
4
8
5
9
// Mock dependencies
6
10
jest . mock ( 'child_process' , ( ) => ( {
7
11
execFileSync : jest . fn ( ) ,
8
12
spawnSync : jest . fn ( ) ,
9
13
} ) ) ;
10
14
15
+ jest . mock ( 'fs' , ( ) => ( {
16
+ existsSync : jest . fn ( ) ,
17
+ readdirSync : jest . fn ( ) ,
18
+ } ) ) ;
19
+
11
20
jest . mock ( 'path' , ( ) => {
12
21
const original = jest . requireActual ( 'path' ) ;
13
22
return {
@@ -25,6 +34,10 @@ jest.mock('../../../../src/filesystem', () => ({
25
34
recursivelyRenameJsonFiles : jest . fn ( ) ,
26
35
} ) ) ;
27
36
37
+ jest . mock ( '../../../../src/logging' , ( ) => ( {
38
+ cdsExtractorLog : jest . fn ( ) ,
39
+ } ) ) ;
40
+
28
41
describe ( 'cds compiler command' , ( ) => {
29
42
beforeEach ( ( ) => {
30
43
jest . clearAllMocks ( ) ;
@@ -117,5 +130,247 @@ describe('cds compiler command', () => {
117
130
// Verify execFileSync was called minimal times (once for cds during cache initialization)
118
131
expect ( childProcess . execFileSync ) . toHaveBeenCalledTimes ( 1 ) ;
119
132
} ) ;
133
+
134
+ it ( 'should return fallback command when all commands fail' , ( ) => {
135
+ // Mock all commands to fail
136
+ ( childProcess . execFileSync as jest . Mock ) . mockImplementation ( ( ) => {
137
+ throw new Error ( 'Command not found' ) ;
138
+ } ) ;
139
+
140
+ // Execute
141
+ const result = determineCdsCommand ( undefined , '/mock/source/root' ) ;
142
+
143
+ // Verify - should return the fallback command even if it doesn't work
144
+ expect ( result ) . toBe ( 'npx -y --package @sap/cds-dk cds' ) ;
145
+ } ) ;
146
+
147
+ it ( 'should handle cache directory discovery with available directories' , ( ) => {
148
+ const mockFileExists = fileExists as jest . MockedFunction < typeof fileExists > ;
149
+
150
+ // Mock file system operations
151
+ ( fs . existsSync as jest . Mock ) . mockReturnValue ( true ) ;
152
+ ( fs . readdirSync as jest . Mock ) . mockReturnValue ( [
153
+ { name : 'cds-v6.1.3' , isDirectory : ( ) => true } ,
154
+ { name : 'cds-v6.2.0' , isDirectory : ( ) => true } ,
155
+ { name : 'other-dir' , isDirectory : ( ) => true } ,
156
+ ] ) ;
157
+
158
+ ( path . join as jest . Mock ) . mockImplementation ( ( ...args : string [ ] ) => args . join ( '/' ) ) ;
159
+
160
+ // Mock fileExists to return true for cache directories with cds binary
161
+ mockFileExists . mockImplementation ( ( filePath : string ) => {
162
+ return filePath . includes ( 'cds-v6' ) && filePath . endsWith ( 'node_modules/.bin/cds' ) ;
163
+ } ) ;
164
+
165
+ // Mock successful execution for cache directory commands
166
+ ( childProcess . execFileSync as jest . Mock ) . mockReturnValue ( Buffer . from ( '6.1.3' ) ) ;
167
+
168
+ // Execute
169
+ const result = determineCdsCommand ( undefined , '/mock/source/root' ) ;
170
+
171
+ // Verify - should use the first available cache directory
172
+ expect ( result ) . toBe (
173
+ '/mock/source/root/.cds-extractor-cache/cds-v6.1.3/node_modules/.bin/cds' ,
174
+ ) ;
175
+ } ) ;
176
+
177
+ it ( 'should handle cache directory discovery with filesystem errors' , ( ) => {
178
+ const mockCdsExtractorLog = cdsExtractorLog as jest . MockedFunction < typeof cdsExtractorLog > ;
179
+
180
+ // Mock existsSync to return true, but readdirSync to throw an error
181
+ ( fs . existsSync as jest . Mock ) . mockReturnValue ( true ) ;
182
+ ( fs . readdirSync as jest . Mock ) . mockImplementation ( ( ) => {
183
+ throw new Error ( 'Permission denied' ) ;
184
+ } ) ;
185
+
186
+ // Mock execFileSync to succeed for fallback commands
187
+ ( childProcess . execFileSync as jest . Mock ) . mockReturnValue ( Buffer . from ( '4.6.0' ) ) ;
188
+
189
+ // Execute
190
+ const result = determineCdsCommand ( undefined , '/mock/source/root' ) ;
191
+
192
+ // Verify - should fall back to global command
193
+ expect ( result ) . toBe ( 'cds' ) ;
194
+ expect ( mockCdsExtractorLog ) . toHaveBeenCalledWith (
195
+ 'debug' ,
196
+ 'Failed to discover cache directories: Error: Permission denied' ,
197
+ ) ;
198
+ } ) ;
199
+
200
+ it ( 'should prefer provided cache directory over discovered ones' , ( ) => {
201
+ const mockFileExists = fileExists as jest . MockedFunction < typeof fileExists > ;
202
+
203
+ ( path . join as jest . Mock ) . mockImplementation ( ( ...args : string [ ] ) => args . join ( '/' ) ) ;
204
+
205
+ // Mock fileExists to return true for the provided cache directory
206
+ mockFileExists . mockImplementation ( ( filePath : string ) => {
207
+ return filePath === '/custom/cache/node_modules/.bin/cds' ;
208
+ } ) ;
209
+
210
+ // Mock successful execution for the provided cache directory
211
+ ( childProcess . execFileSync as jest . Mock ) . mockReturnValue ( Buffer . from ( '6.2.0' ) ) ;
212
+
213
+ // Execute with custom cache directory
214
+ const result = determineCdsCommand ( '/custom/cache' , '/mock/source/root' ) ;
215
+
216
+ // Verify - should use the provided cache directory
217
+ expect ( result ) . toBe ( '/custom/cache/node_modules/.bin/cds' ) ;
218
+ } ) ;
219
+
220
+ it ( 'should handle node command format correctly' , ( ) => {
221
+ const mockFileExists = fileExists as jest . MockedFunction < typeof fileExists > ;
222
+
223
+ // Mock cache directory discovery with node command - but no cache directories exist
224
+ ( fs . existsSync as jest . Mock ) . mockReturnValue ( false ) ;
225
+ ( path . join as jest . Mock ) . mockImplementation ( ( ...args : string [ ] ) => args . join ( '/' ) ) ;
226
+
227
+ // Mock fileExists to return false (no cache directories)
228
+ mockFileExists . mockReturnValue ( false ) ;
229
+
230
+ // Mock execFileSync to handle node command execution
231
+ ( childProcess . execFileSync as jest . Mock ) . mockImplementation (
232
+ ( command : string , args : string [ ] ) => {
233
+ if ( command === 'node' ) {
234
+ return Buffer . from ( '6.1.3' ) ;
235
+ }
236
+ if ( command === 'sh' && args . join ( ' ' ) . includes ( 'cds --version' ) ) {
237
+ throw new Error ( 'Command not found' ) ;
238
+ }
239
+ if ( command === 'sh' && args . join ( ' ' ) . includes ( 'npx -y --package @sap/cds-dk cds' ) ) {
240
+ return Buffer . from ( '6.1.3' ) ;
241
+ }
242
+ throw new Error ( 'Command not found' ) ;
243
+ } ,
244
+ ) ;
245
+
246
+ // Execute
247
+ const result = determineCdsCommand ( undefined , '/mock/source/root' ) ;
248
+
249
+ // Should fall back to npx command since cache directories don't exist
250
+ expect ( result ) . toBe ( 'npx -y --package @sap/cds-dk cds' ) ;
251
+ } ) ;
252
+
253
+ it ( 'should handle version parsing failures gracefully' , ( ) => {
254
+ // Mock cache directory discovery - no cache directories exist
255
+ ( fs . existsSync as jest . Mock ) . mockReturnValue ( false ) ;
256
+
257
+ // Mock command to return output without version number for global command
258
+ ( childProcess . execFileSync as jest . Mock ) . mockImplementation (
259
+ ( command : string , args : string [ ] ) => {
260
+ if ( command === 'sh' && args . join ( ' ' ) . includes ( 'cds --version' ) ) {
261
+ return Buffer . from ( 'No version info' ) ;
262
+ }
263
+ throw new Error ( 'Command not found' ) ;
264
+ } ,
265
+ ) ;
266
+
267
+ // Execute
268
+ const result = determineCdsCommand ( undefined , '/mock/source/root' ) ;
269
+
270
+ // Verify - should still return the command even without version info
271
+ expect ( result ) . toBe ( 'cds' ) ;
272
+ } ) ;
273
+
274
+ it ( 'should try fallback npx commands when global command fails' , ( ) => {
275
+ // Mock all commands to fail except one fallback
276
+ ( childProcess . execFileSync as jest . Mock ) . mockImplementation (
277
+ ( _command : string , args : string [ ] ) => {
278
+ const fullCommand = args . join ( ' ' ) ;
279
+ if ( fullCommand === "-c 'npx -y --package @sap/cds cds' --version" ) {
280
+ return Buffer . from ( '6.1.3' ) ;
281
+ }
282
+ throw new Error ( 'Command not found' ) ;
283
+ } ,
284
+ ) ;
285
+
286
+ // Execute
287
+ const result = determineCdsCommand ( undefined , '/mock/source/root' ) ;
288
+
289
+ // Verify - should use the fallback command
290
+ expect ( result ) . toBe ( 'npx -y --package @sap/cds cds' ) ;
291
+ } ) ;
292
+
293
+ it ( 'should log discovery of multiple cache directories' , ( ) => {
294
+ const mockFileExists = fileExists as jest . MockedFunction < typeof fileExists > ;
295
+ const mockCdsExtractorLog = cdsExtractorLog as jest . MockedFunction < typeof cdsExtractorLog > ;
296
+
297
+ // Mock file system operations to discover multiple cache directories
298
+ ( fs . existsSync as jest . Mock ) . mockReturnValue ( true ) ;
299
+ ( fs . readdirSync as jest . Mock ) . mockReturnValue ( [
300
+ { name : 'cds-v6.1.3' , isDirectory : ( ) => true } ,
301
+ { name : 'cds-v6.2.0' , isDirectory : ( ) => true } ,
302
+ { name : 'cds-v7.0.0' , isDirectory : ( ) => true } ,
303
+ ] ) ;
304
+
305
+ ( path . join as jest . Mock ) . mockImplementation ( ( ...args : string [ ] ) => args . join ( '/' ) ) ;
306
+
307
+ // Mock fileExists to return true for all cache directories
308
+ mockFileExists . mockImplementation ( ( filePath : string ) => {
309
+ return filePath . includes ( 'cds-v' ) && filePath . endsWith ( 'node_modules/.bin/cds' ) ;
310
+ } ) ;
311
+
312
+ // Mock successful execution
313
+ ( childProcess . execFileSync as jest . Mock ) . mockReturnValue ( Buffer . from ( '6.1.3' ) ) ;
314
+
315
+ // Execute
316
+ determineCdsCommand ( undefined , '/mock/source/root' ) ;
317
+
318
+ // Verify - should log the discovery of multiple directories
319
+ expect ( mockCdsExtractorLog ) . toHaveBeenCalledWith (
320
+ 'info' ,
321
+ 'Discovered 3 CDS cache directories' ,
322
+ ) ;
323
+ } ) ;
324
+
325
+ it ( 'should log discovery of single cache directory' , ( ) => {
326
+ const mockFileExists = fileExists as jest . MockedFunction < typeof fileExists > ;
327
+ const mockCdsExtractorLog = cdsExtractorLog as jest . MockedFunction < typeof cdsExtractorLog > ;
328
+
329
+ // Mock file system operations to discover one cache directory
330
+ ( fs . existsSync as jest . Mock ) . mockReturnValue ( true ) ;
331
+ ( fs . readdirSync as jest . Mock ) . mockReturnValue ( [
332
+ { name : 'cds-v6.1.3' , isDirectory : ( ) => true } ,
333
+ ] ) ;
334
+
335
+ ( path . join as jest . Mock ) . mockImplementation ( ( ...args : string [ ] ) => args . join ( '/' ) ) ;
336
+
337
+ // Mock fileExists to return true for the cache directory
338
+ mockFileExists . mockImplementation ( ( filePath : string ) => {
339
+ return filePath . includes ( 'cds-v6' ) && filePath . endsWith ( 'node_modules/.bin/cds' ) ;
340
+ } ) ;
341
+
342
+ // Mock successful execution
343
+ ( childProcess . execFileSync as jest . Mock ) . mockReturnValue ( Buffer . from ( '6.1.3' ) ) ;
344
+
345
+ // Execute
346
+ determineCdsCommand ( undefined , '/mock/source/root' ) ;
347
+
348
+ // Verify - should log the discovery of single directory
349
+ expect ( mockCdsExtractorLog ) . toHaveBeenCalledWith ( 'info' , 'Discovered 1 CDS cache directory' ) ;
350
+ } ) ;
351
+
352
+ it ( 'should handle cache directory without valid cds binary' , ( ) => {
353
+ const mockFileExists = fileExists as jest . MockedFunction < typeof fileExists > ;
354
+
355
+ // Mock file system operations
356
+ ( fs . existsSync as jest . Mock ) . mockReturnValue ( true ) ;
357
+ ( fs . readdirSync as jest . Mock ) . mockReturnValue ( [
358
+ { name : 'cds-v6.1.3' , isDirectory : ( ) => true } ,
359
+ ] ) ;
360
+
361
+ ( path . join as jest . Mock ) . mockImplementation ( ( ...args : string [ ] ) => args . join ( '/' ) ) ;
362
+
363
+ // Mock fileExists to return false for the cache directory (no cds binary)
364
+ mockFileExists . mockReturnValue ( false ) ;
365
+
366
+ // Mock successful execution for fallback
367
+ ( childProcess . execFileSync as jest . Mock ) . mockReturnValue ( Buffer . from ( '4.6.0' ) ) ;
368
+
369
+ // Execute
370
+ const result = determineCdsCommand ( undefined , '/mock/source/root' ) ;
371
+
372
+ // Verify - should fall back to global command
373
+ expect ( result ) . toBe ( 'cds' ) ;
374
+ } ) ;
120
375
} ) ;
121
376
} ) ;
0 commit comments