@@ -2,9 +2,11 @@ package main
22
33import (
44 "bufio"
5+ "context"
56 "crypto/tls"
67 "encoding/json"
78 "fmt"
9+ "io"
810 "net"
911 "os"
1012 "os/exec"
@@ -17,6 +19,40 @@ import (
1719 "github.com/RewstApp/agent-smith-go/internal/version"
1820)
1921
22+ // tlsDialer abstracts TLS connectivity checks so tests can inject fakes.
23+ type tlsDialer interface {
24+ Dial (host , port string ) bool
25+ }
26+
27+ type defaultTLSDialer struct {}
28+
29+ func (d * defaultTLSDialer ) Dial (host , port string ) bool {
30+ return testTLSConnection (host , port )
31+ }
32+
33+ // serviceStatusQuerier abstracts per-platform service status so tests can
34+ // inject fakes into scanAgentsFrom.
35+ type serviceStatusQuerier interface {
36+ QueryStatus (name string ) (installed , running bool )
37+ }
38+
39+ type osServiceQuerier struct {}
40+
41+ func (q * osServiceQuerier ) QueryStatus (name string ) (bool , bool ) {
42+ return queryServiceStatus (name )
43+ }
44+
45+ // logFileOpener abstracts os.Open so tests can inject an in-memory reader.
46+ type logFileOpener interface {
47+ Open (name string ) (io.ReadCloser , error )
48+ }
49+
50+ type osLogFileOpener struct {}
51+
52+ func (o * osLogFileOpener ) Open (name string ) (io.ReadCloser , error ) {
53+ return os .Open (name ) // #nosec G304 - path comes from internal config
54+ }
55+
2056type agentInfo struct {
2157 OrgId string
2258 ConfigFile string
@@ -27,12 +63,21 @@ type agentInfo struct {
2763}
2864
2965func runDiagnostic (params * diagnosticContext ) {
30- reader := bufio .NewReader (os .Stdin )
66+ runDiagnosticFull (context .Background (), params , os .Stdin , & defaultTLSDialer {}, & osLogFileOpener {}, getAgentDataRoot ())
67+ }
68+
69+ // runDiagnosticWith is the testable entry point with all dependencies injected.
70+ func runDiagnosticWith (params * diagnosticContext , input io.Reader , dialer tlsDialer , opener logFileOpener ) {
71+ runDiagnosticFull (context .Background (), params , input , dialer , opener , getAgentDataRoot ())
72+ }
73+
74+ func runDiagnosticFull (ctx context.Context , params * diagnosticContext , input io.Reader , dialer tlsDialer , opener logFileOpener , agentRoot string ) {
75+ reader := bufio .NewReader (input )
3176
3277 printHeader ()
3378
3479 // Scan for installed agents
35- agents := scanAgents ( )
80+ agents := scanAgentsFrom ( agentRoot )
3681
3782 if len (agents ) == 0 && params .OrgId == "" {
3883 fmt .Println ("\n No installed agents found." )
@@ -77,13 +122,13 @@ func runDiagnostic(params *diagnosticContext) {
77122 case "2" :
78123 runCommandTest ()
79124 case "3" :
80- runConnectivityTest (target )
125+ runConnectivityTestWith (target , dialer )
81126 case "4" :
82127 runTempDirTest (target )
83128 case "5" :
84- runLiveLogs ( target )
129+ runLiveLogsWith ( ctx , target , opener )
85130 case "6" :
86- runAllChecks (params , agents , target )
131+ runAllChecksWith (params , agents , target , dialer , opener )
87132 case "0" , "q" , "quit" , "exit" :
88133 fmt .Println ("\n Exiting diagnostic mode." )
89134 return
@@ -145,9 +190,13 @@ func selectAgent(reader *bufio.Reader, agents []agentInfo) agentInfo {
145190 }
146191}
147192
148- // scanAgents discovers installed agents by scanning the data directory
193+ // scanAgents discovers installed agents by scanning the platform data directory.
149194func scanAgents () []agentInfo {
150- root := getAgentDataRoot ()
195+ return scanAgentsFrom (getAgentDataRoot ())
196+ }
197+
198+ // scanAgentsFrom is the testable core of scanAgents.
199+ func scanAgentsFrom (root string ) []agentInfo {
151200 entries , err := os .ReadDir (root )
152201 if err != nil {
153202 return nil
@@ -258,7 +307,7 @@ func runCommandTest() {
258307
259308// ── Check 3: MQTT/WebSocket connectivity ──
260309
261- func runConnectivityTest (target agentInfo ) {
310+ func runConnectivityTestWith (target agentInfo , dialer tlsDialer ) {
262311 printSection ("MQTT/WebSocket Connectivity" )
263312
264313 if target .Device == nil {
@@ -277,7 +326,7 @@ func runConnectivityTest(target agentInfo) {
277326
278327 // Test MQTT port (8883)
279328 fmt .Printf (" Testing MQTT (TLS port 8883)... " )
280- mqttOk := testTLSConnection (host , "8883" )
329+ mqttOk := dialer . Dial (host , "8883" )
281330 if mqttOk {
282331 fmt .Println ("OK" )
283332 } else {
@@ -287,7 +336,7 @@ func runConnectivityTest(target agentInfo) {
287336
288337 // Test WebSocket port (443)
289338 fmt .Printf (" Testing WebSocket (port 443)... " )
290- wsOk := testTLSConnection (host , "443" )
339+ wsOk := dialer . Dial (host , "443" )
291340 if wsOk {
292341 fmt .Println ("OK" )
293342 } else {
@@ -374,76 +423,58 @@ func runTempDirTest(target agentInfo) {
374423
375424// ── Check 5: Live log viewer ──
376425
377- func runLiveLogs ( target agentInfo ) {
426+ func runLiveLogsWith ( ctx context. Context , target agentInfo , opener logFileOpener ) {
378427 printSection ("Live Log Viewer" )
379428
380429 logFile := target .LogFile
381430 fmt .Printf (" Log file: %s\n " , logFile )
382431 fmt .Println (" Press Ctrl+C to stop watching." )
383432 fmt .Println ()
384433
385- file , err := os .Open (logFile )
434+ rc , err := opener .Open (logFile )
386435 if err != nil {
387436 printResult (false , fmt .Sprintf ("Cannot open log file: %v" , err ))
388437 fmt .Println (" The agent may not have been started yet, or the log file path is incorrect." )
389438 return
390439 }
391- defer func () { _ = file .Close () }()
440+ defer func () { _ = rc .Close () }()
392441
393- // Seek to the last 4KB to show recent entries
394- info , err := file .Stat ()
395- if err == nil && info .Size () > 4096 {
396- _ , _ = file .Seek (- 4096 , 2 )
397- // Discard partial line
398- reader := bufio .NewReader (file )
399- _ , _ = reader .ReadString ('\n' )
400-
401- fmt .Println (" ... (showing last entries)" )
402- fmt .Println ()
403-
404- // Print remaining buffered content
405- for {
406- line , err := reader .ReadString ('\n' )
407- if line != "" {
408- fmt .Print (" " , line )
409- }
410- if err != nil {
411- break
412- }
442+ // Read all buffered content line by line
443+ reader := bufio .NewReader (rc )
444+ for {
445+ line , err := reader .ReadString ('\n' )
446+ if line != "" {
447+ fmt .Print (" " , line )
413448 }
414- } else {
415- // Small file, read from beginning
416- reader := bufio .NewReader (file )
417- for {
418- line , err := reader .ReadString ('\n' )
419- if line != "" {
420- fmt .Print (" " , line )
421- }
422- if err != nil {
423- break
424- }
449+ if err != nil {
450+ break
425451 }
426452 }
427453
428- // Tail the file for new entries
454+ // Tail for new entries until context is cancelled
455+ buf := make ([]byte , 4096 )
429456 for {
430- line := make ([]byte , 4096 )
431- n , err := file .Read (line )
432- if n > 0 {
433- fmt .Print (" " , string (line [:n ]))
434- }
435- if err != nil {
436- time .Sleep (500 * time .Millisecond )
457+ select {
458+ case <- ctx .Done ():
459+ return
460+ default :
461+ n , err := rc .Read (buf )
462+ if n > 0 {
463+ fmt .Print (" " , string (buf [:n ]))
464+ }
465+ if err != nil {
466+ time .Sleep (500 * time .Millisecond )
467+ }
437468 }
438469 }
439470}
440471
441472// ── Check 6: Run all checks ──
442473
443- func runAllChecks (params * diagnosticContext , agents []agentInfo , target agentInfo ) {
474+ func runAllChecksWith (params * diagnosticContext , agents []agentInfo , target agentInfo , dialer tlsDialer , opener logFileOpener ) {
444475 runCheckAgents (params , agents )
445476 runCommandTest ()
446- runConnectivityTest (target )
477+ runConnectivityTestWith (target , dialer )
447478 runTempDirTest (target )
448479}
449480
0 commit comments