@@ -303,6 +303,141 @@ func TestCopyFile_Errors(t *testing.T) {
303303 }
304304}
305305
306+ func TestCreateShim_CreatesCmdWrapperOnWindows (t * testing.T ) {
307+ if runtime .GOOS != constants .OSWindows {
308+ t .Skip ("Skipping Windows-specific test" )
309+ }
310+
311+ tmpRoot := t .TempDir ()
312+ shimsDir := filepath .Join (tmpRoot , "shims" )
313+ if err := os .MkdirAll (shimsDir , 0755 ); err != nil {
314+ t .Fatalf ("Failed to create shims directory: %v" , err )
315+ }
316+
317+ // Create a fake shim source
318+ shimSourcePath := filepath .Join (tmpRoot , "dtvem-shim.exe" )
319+ if err := os .WriteFile (shimSourcePath , []byte ("fake shim content" ), 0755 ); err != nil {
320+ t .Fatalf ("Failed to create fake shim: %v" , err )
321+ }
322+
323+ // Create the .exe shim
324+ exePath := filepath .Join (shimsDir , "npm.exe" )
325+ if err := copyFile (shimSourcePath , exePath ); err != nil {
326+ t .Fatalf ("copyFile() error: %v" , err )
327+ }
328+
329+ // Create the .cmd wrapper using the helper
330+ cmdPath := filepath .Join (shimsDir , "npm.cmd" )
331+ content := "@echo off\r \n \" %~dp0npm.exe\" %*\r \n "
332+ if err := os .WriteFile (cmdPath , []byte (content ), 0644 ); err != nil {
333+ t .Fatalf ("Failed to write .cmd wrapper: %v" , err )
334+ }
335+
336+ // Verify .cmd file exists
337+ if _ , err := os .Stat (cmdPath ); os .IsNotExist (err ) {
338+ t .Error (".cmd wrapper was not created" )
339+ }
340+
341+ // Verify .cmd content
342+ cmdContent , err := os .ReadFile (cmdPath )
343+ if err != nil {
344+ t .Fatalf ("Failed to read .cmd wrapper: %v" , err )
345+ }
346+
347+ expected := "@echo off\r \n \" %~dp0npm.exe\" %*\r \n "
348+ if string (cmdContent ) != expected {
349+ t .Errorf (".cmd content = %q, want %q" , string (cmdContent ), expected )
350+ }
351+ }
352+
353+ func TestRemoveShim_RemovesCmdWrapperOnWindows (t * testing.T ) {
354+ if runtime .GOOS != constants .OSWindows {
355+ t .Skip ("Skipping Windows-specific test" )
356+ }
357+
358+ tmpRoot := t .TempDir ()
359+ shimsDir := filepath .Join (tmpRoot , "shims" )
360+ if err := os .MkdirAll (shimsDir , 0755 ); err != nil {
361+ t .Fatalf ("Failed to create shims directory: %v" , err )
362+ }
363+
364+ // Create both .exe and .cmd files
365+ exePath := filepath .Join (shimsDir , "npm.exe" )
366+ cmdPath := filepath .Join (shimsDir , "npm.cmd" )
367+ if err := os .WriteFile (exePath , []byte ("fake shim" ), 0755 ); err != nil {
368+ t .Fatalf ("Failed to create .exe: %v" , err )
369+ }
370+ if err := os .WriteFile (cmdPath , []byte ("@echo off\r \n " ), 0644 ); err != nil {
371+ t .Fatalf ("Failed to create .cmd: %v" , err )
372+ }
373+
374+ // Remove both files
375+ if err := os .Remove (exePath ); err != nil {
376+ t .Fatalf ("Failed to remove .exe: %v" , err )
377+ }
378+ if err := os .Remove (cmdPath ); err != nil {
379+ t .Fatalf ("Failed to remove .cmd: %v" , err )
380+ }
381+
382+ // Verify both are gone
383+ if _ , err := os .Stat (exePath ); ! os .IsNotExist (err ) {
384+ t .Error (".exe shim was not removed" )
385+ }
386+ if _ , err := os .Stat (cmdPath ); ! os .IsNotExist (err ) {
387+ t .Error (".cmd wrapper was not removed" )
388+ }
389+ }
390+
391+ func TestListShims_SkipsCmdFiles (t * testing.T ) {
392+ if runtime .GOOS != constants .OSWindows {
393+ t .Skip ("Skipping Windows-specific test" )
394+ }
395+
396+ tmpRoot := t .TempDir ()
397+ shimsDir := filepath .Join (tmpRoot , "shims" )
398+ if err := os .MkdirAll (shimsDir , 0755 ); err != nil {
399+ t .Fatalf ("Failed to create shims directory: %v" , err )
400+ }
401+
402+ // Create .exe and .cmd files
403+ files := map [string ]string {
404+ "npm.exe" : "fake shim" ,
405+ "npm.cmd" : "@echo off\r \n " ,
406+ "npx.exe" : "fake shim" ,
407+ "npx.cmd" : "@echo off\r \n " ,
408+ }
409+ for name , content := range files {
410+ path := filepath .Join (shimsDir , name )
411+ if err := os .WriteFile (path , []byte (content ), 0755 ); err != nil {
412+ t .Fatalf ("Failed to create %s: %v" , name , err )
413+ }
414+ }
415+
416+ // Read entries and filter like ListShims does
417+ entries , err := os .ReadDir (shimsDir )
418+ if err != nil {
419+ t .Fatalf ("Failed to read shims directory: %v" , err )
420+ }
421+
422+ var shims []string
423+ for _ , entry := range entries {
424+ if entry .IsDir () {
425+ continue
426+ }
427+ name := entry .Name ()
428+ ext := filepath .Ext (name )
429+ if ext == constants .ExtCmd || ext == ".bat" {
430+ continue
431+ }
432+ shims = append (shims , name [:len (name )- len (ext )])
433+ }
434+
435+ expected := []string {"npm" , "npx" }
436+ if ! reflect .DeepEqual (shims , expected ) {
437+ t .Errorf ("ListShims filtered result = %v, want %v" , shims , expected )
438+ }
439+ }
440+
306441func TestRuntimeShims_AllKnownRuntimes (t * testing.T ) {
307442 // Verify all known runtimes have shim mappings
308443 knownRuntimes := []string {"python" , "node" , "ruby" , "go" }
0 commit comments