blob: a1dad57830cf48dacc3ec8f93e51f748730e7eb0 [file] [log] [blame]
Sean McCullough364f7412025-06-02 00:55:44 +00001package loop
2
3import (
4 "context"
5 "testing"
Sean McCullough138ec242025-06-02 22:42:06 +00006 "time"
Sean McCullough364f7412025-06-02 00:55:44 +00007)
8
9// TestPortMonitoring tests the port monitoring functionality
10func TestPortMonitoring(t *testing.T) {
11 // Test with ss output format
12 ssOutput := `Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
13tcp LISTEN 0 1024 127.0.0.1:40975 0.0.0.0:*
14tcp LISTEN 0 4096 *:22 *:*
15tcp LISTEN 0 4096 *:80 *:*
16udp UNCONN 0 0 127.0.0.1:123 0.0.0.0:*
17`
18
19 expected := map[string]bool{
20 "tcp:127.0.0.1:40975": true,
21 "tcp:*:22": true,
22 "tcp:*:80": true,
23 }
24
25 result := parseSSPorts(ssOutput)
26
27 // Check that all expected ports are found
28 for port := range expected {
29 if !result[port] {
30 t.Errorf("Expected port %s not found in ss parsed output", port)
31 }
32 }
33
34 // Check that UDP port is not included (since it's UNCONN, not LISTEN)
35 if result["udp:127.0.0.1:123"] {
36 t.Errorf("UDP UNCONN port should not be included in listening ports")
37 }
38
39 // Check that no extra ports are found
40 for port := range result {
41 if !expected[port] {
42 t.Errorf("Unexpected port %s found in parsed output", port)
43 }
44 }
45}
46
47// TestPortMonitoringLogDifferences tests the port difference logging
48func TestPortMonitoringLogDifferences(t *testing.T) {
49 ctx := context.Background()
50
51 oldPorts := `Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
52tcp LISTEN 0 4096 *:22 *:*
53tcp LISTEN 0 1024 127.0.0.1:8080 0.0.0.0:*
54`
55
56 newPorts := `Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
57tcp LISTEN 0 4096 *:22 *:*
58tcp LISTEN 0 1024 127.0.0.1:9090 0.0.0.0:*
59`
60
61 // Create a port monitor to test the logPortDifferences method
62 pm := NewPortMonitor()
63
64 // This test mainly ensures the method doesn't panic and processes the differences
65 // The actual logging output would need to be captured via a test logger to verify fully
66 pm.logPortDifferences(ctx, oldPorts, newPorts)
67
68 // Test with no differences
69 pm.logPortDifferences(ctx, oldPorts, oldPorts)
70}
71
72// TestPortMonitorCreation tests creating a new port monitor
73func TestPortMonitorCreation(t *testing.T) {
74 pm := NewPortMonitor()
75 if pm == nil {
76 t.Error("NewPortMonitor() returned nil")
77 }
78
79 // Verify initial state
80 pm.mu.Lock()
81 defer pm.mu.Unlock()
82 if pm.lastPorts != "" {
83 t.Error("NewPortMonitor() should have empty lastPorts initially")
84 }
85}
86
87// TestParseSSPortsEdgeCases tests edge cases in ss output parsing
88func TestParseSSPortsEdgeCases(t *testing.T) {
89 tests := []struct {
90 name string
91 output string
92 expected map[string]bool
93 }{
94 {
95 name: "empty output",
96 output: "",
97 expected: map[string]bool{},
98 },
99 {
100 name: "header only",
101 output: "Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess",
102 expected: map[string]bool{},
103 },
104 {
105 name: "non-listen states filtered out",
106 output: "tcp ESTAB 0 0 127.0.0.1:8080 127.0.0.1:45678\nudp UNCONN 0 0 127.0.0.1:123 0.0.0.0:*",
107 expected: map[string]bool{},
108 },
109 {
110 name: "insufficient fields",
111 output: "tcp LISTEN 0",
112 expected: map[string]bool{},
113 },
114 }
115
116 for _, test := range tests {
117 t.Run(test.name, func(t *testing.T) {
118 result := parseSSPorts(test.output)
119 if len(result) != len(test.expected) {
120 t.Errorf("Expected %d ports, got %d", len(test.expected), len(result))
121 }
122 for port := range test.expected {
123 if !result[port] {
124 t.Errorf("Expected port %s not found", port)
125 }
126 }
127 for port := range result {
128 if !test.expected[port] {
129 t.Errorf("Unexpected port %s found", port)
130 }
131 }
132 })
133 }
134}
Sean McCullough138ec242025-06-02 22:42:06 +0000135
136// TestPortEventStorage tests the new event storage functionality
137func TestPortEventStorage(t *testing.T) {
138 pm := NewPortMonitor()
139
140 // Initially should have no events
141 allEvents := pm.GetAllRecentEvents()
142 if len(allEvents) != 0 {
143 t.Errorf("Expected 0 events initially, got %d", len(allEvents))
144 }
145
146 // Simulate port changes that would add events
147 ctx := context.Background()
148 oldPorts := "Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port\ntcp LISTEN 0 128 0.0.0.0:8080 0.0.0.0:*"
149 newPorts := "Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port\ntcp LISTEN 0 128 0.0.0.0:9090 0.0.0.0:*"
150
151 pm.logPortDifferences(ctx, oldPorts, newPorts)
152
153 // Should now have events
154 allEvents = pm.GetAllRecentEvents()
155 if len(allEvents) != 2 {
156 t.Errorf("Expected 2 events (1 opened, 1 closed), got %d", len(allEvents))
157 }
158
159 // Check event types
160 foundOpened := false
161 foundClosed := false
162 for _, event := range allEvents {
163 if event.Type == "opened" && event.Port == "tcp:0.0.0.0:9090" {
164 foundOpened = true
165 }
166 if event.Type == "closed" && event.Port == "tcp:0.0.0.0:8080" {
167 foundClosed = true
168 }
169 }
170
171 if !foundOpened {
172 t.Error("Expected to find 'opened' event for port tcp:0.0.0.0:9090")
173 }
174 if !foundClosed {
175 t.Error("Expected to find 'closed' event for port tcp:0.0.0.0:8080")
176 }
177}
178
179// TestPortEventFiltering tests the time-based filtering
180func TestPortEventFiltering(t *testing.T) {
181 pm := NewPortMonitor()
182 ctx := context.Background()
183
184 // Record time before adding events
185 beforeTime := time.Now()
186 time.Sleep(1 * time.Millisecond) // Small delay to ensure timestamp difference
187
188 // Add some events
189 oldPorts := "Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port\ntcp LISTEN 0 128 0.0.0.0:8080 0.0.0.0:*"
190 newPorts := "Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port\ntcp LISTEN 0 128 0.0.0.0:9090 0.0.0.0:*"
191 pm.logPortDifferences(ctx, oldPorts, newPorts)
192
193 // Get events since beforeTime - should get all events
194 recentEvents := pm.GetRecentEvents(beforeTime)
195 if len(recentEvents) != 2 {
196 t.Errorf("Expected 2 recent events, got %d", len(recentEvents))
197 }
198
199 // Get events since now - should get no events
200 nowTime := time.Now()
201 recentEvents = pm.GetRecentEvents(nowTime)
202 if len(recentEvents) != 0 {
203 t.Errorf("Expected 0 recent events since now, got %d", len(recentEvents))
204 }
205}