@@ -19,7 +19,17 @@ vi.mock("vscode", () => ({
19
19
window :{
20
20
showInformationMessage :vi . fn ( ) ,
21
21
showErrorMessage :vi . fn ( ) ,
22
+ createTerminal :vi . fn ( ) ,
22
23
} ,
24
+ EventEmitter :vi . fn ( ) . mockImplementation ( ( ) => ( {
25
+ event :vi . fn ( ) ,
26
+ fire :vi . fn ( ) ,
27
+ dispose :vi . fn ( ) ,
28
+ } ) ) ,
29
+ TerminalLocation :{
30
+ Panel :1 ,
31
+ } ,
32
+ ThemeIcon :vi . fn ( ) ,
23
33
} ) )
24
34
25
35
vi . mock ( "fs/promises" , ( ) => ( {
@@ -62,6 +72,15 @@ vi.mock("./featureSet", () => ({
62
72
63
73
vi . mock ( "./util" , ( ) => ( {
64
74
parseRemoteAuthority :vi . fn ( ) ,
75
+ findPort :vi . fn ( ) ,
76
+ } ) )
77
+
78
+ vi . mock ( "find-process" , ( ) => ( {
79
+ default :vi . fn ( ) ,
80
+ } ) )
81
+
82
+ vi . mock ( "pretty-bytes" , ( ) => ( {
83
+ default :vi . fn ( ( bytes ) => `${ bytes } B` ) ,
65
84
} ) )
66
85
67
86
// Create a testable Remote class that exposes protected methods
@@ -85,6 +104,18 @@ class TestableRemote extends Remote {
85
104
public fetchWorkspace ( workspaceRestClient :Api , parts :any , baseUrlRaw :string , remoteAuthority :string ) {
86
105
return super . fetchWorkspace ( workspaceRestClient , parts , baseUrlRaw , remoteAuthority )
87
106
}
107
+
108
+ public createBuildLogTerminal ( writeEmitter :vscode . EventEmitter < string > ) {
109
+ return super . createBuildLogTerminal ( writeEmitter )
110
+ }
111
+
112
+ public searchSSHLogForPID ( logPath :string ) {
113
+ return super . searchSSHLogForPID ( logPath )
114
+ }
115
+
116
+ public updateNetworkStatus ( networkStatus :vscode . StatusBarItem , network :any ) {
117
+ return super . updateNetworkStatus ( networkStatus , network )
118
+ }
88
119
}
89
120
90
121
describe ( "Remote" , ( ) => {
@@ -478,4 +509,106 @@ describe("Remote", () => {
478
509
)
479
510
} )
480
511
} )
512
+
513
+ describe ( "createBuildLogTerminal" , ( ) => {
514
+ it ( "should create terminal with correct configuration" , ( ) => {
515
+ const mockWriteEmitter = new vscode . EventEmitter < string > ( )
516
+ mockWriteEmitter . event = vi . fn ( )
517
+
518
+ const mockTerminal = { name :"Build Log" }
519
+ vscode . window . createTerminal . mockReturnValue ( mockTerminal )
520
+
521
+ const result = remote . createBuildLogTerminal ( mockWriteEmitter )
522
+
523
+ expect ( result ) . toBe ( mockTerminal )
524
+ expect ( vscode . window . createTerminal ) . toHaveBeenCalledWith ( {
525
+ name :"Build Log" ,
526
+ location :vscode . TerminalLocation . Panel ,
527
+ iconPath :expect . any ( vscode . ThemeIcon ) ,
528
+ pty :expect . objectContaining ( {
529
+ onDidWrite :mockWriteEmitter . event ,
530
+ close :expect . any ( Function ) ,
531
+ open :expect . any ( Function ) ,
532
+ } ) ,
533
+ } )
534
+ } )
535
+ } )
536
+
537
+ describe ( "searchSSHLogForPID" , ( ) => {
538
+ it ( "should find SSH process ID from log file" , async ( ) => {
539
+ const logPath = "/path/to/ssh.log"
540
+
541
+ const fs = await import ( "fs/promises" )
542
+ vi . mocked ( fs . readFile ) . mockResolvedValue ( "Forwarding port 12345..." )
543
+
544
+ const { findPort} = await import ( "./util" )
545
+ vi . mocked ( findPort ) . mockResolvedValue ( 12345 )
546
+
547
+ const find = ( await import ( "find-process" ) ) . default
548
+ vi . mocked ( find ) . mockResolvedValue ( [ { pid :54321 , name :"ssh" } ] )
549
+
550
+ const result = await remote . searchSSHLogForPID ( logPath )
551
+
552
+ expect ( result ) . toBe ( 54321 )
553
+ expect ( fs . readFile ) . toHaveBeenCalledWith ( logPath , "utf8" )
554
+ expect ( findPort ) . toHaveBeenCalled ( )
555
+ expect ( find ) . toHaveBeenCalledWith ( "port" , 12345 )
556
+ } )
557
+
558
+ it ( "should return undefined when no port found" , async ( ) => {
559
+ const logPath = "/path/to/ssh.log"
560
+
561
+ const fs = await import ( "fs/promises" )
562
+ vi . mocked ( fs . readFile ) . mockResolvedValue ( "No port info here" )
563
+
564
+ const { findPort} = await import ( "./util" )
565
+ vi . mocked ( findPort ) . mockResolvedValue ( undefined )
566
+
567
+ const result = await remote . searchSSHLogForPID ( logPath )
568
+
569
+ expect ( result ) . toBeUndefined ( )
570
+ } )
571
+ } )
572
+
573
+ describe ( "updateNetworkStatus" , ( ) => {
574
+ let mockStatusBar :any
575
+
576
+ beforeEach ( ( ) => {
577
+ mockStatusBar = {
578
+ text :"" ,
579
+ tooltip :"" ,
580
+ show :vi . fn ( ) ,
581
+ hide :vi . fn ( ) ,
582
+ dispose :vi . fn ( ) ,
583
+ }
584
+ } )
585
+
586
+ it ( "should update status for peer-to-peer connection" , ( ) => {
587
+ const network = {
588
+ using_coder_connect :false ,
589
+ p2p :true ,
590
+ latency :15.5 ,
591
+ download_bytes_sec :1000000 ,
592
+ upload_bytes_sec :500000 ,
593
+ }
594
+
595
+ remote . updateNetworkStatus ( mockStatusBar , network )
596
+
597
+ expect ( mockStatusBar . text ) . toBe ( "$(globe) Direct (15.50ms)" )
598
+ expect ( mockStatusBar . tooltip ) . toContain ( "You're connected peer-to-peer" )
599
+ expect ( mockStatusBar . show ) . toHaveBeenCalled ( )
600
+ } )
601
+
602
+ it ( "should update status for Coder Connect" , ( ) => {
603
+ const network = {
604
+ using_coder_connect :true ,
605
+ }
606
+
607
+ remote . updateNetworkStatus ( mockStatusBar , network )
608
+
609
+ expect ( mockStatusBar . text ) . toBe ( "$(globe) Coder Connect " )
610
+ expect ( mockStatusBar . tooltip ) . toBe ( "You're connected using Coder Connect." )
611
+ expect ( mockStatusBar . show ) . toHaveBeenCalled ( )
612
+ } )
613
+ } )
481
614
} )