blob: 133972039b896178477a26307b6c236b7bec2626 [file] [log] [blame]
Philip Zeyliger5f26a342025-07-04 01:30:29 +00001package loop
2
3import (
4 "context"
Philip Zeyliger5f26a342025-07-04 01:30:29 +00005 "testing"
6 "time"
7
8 "tailscale.com/portlist"
9)
10
11// TestPortMonitor_NewPortMonitor tests the creation of a new PortMonitor.
12func TestPortMonitor_NewPortMonitor(t *testing.T) {
13 agent := createTestAgent(t)
14 interval := 2 * time.Second
15
16 pm := NewPortMonitor(agent, interval)
17
18 if pm == nil {
19 t.Fatal("NewPortMonitor returned nil")
20 }
21
22 if pm.agent != agent {
23 t.Errorf("expected agent %v, got %v", agent, pm.agent)
24 }
25
26 if pm.interval != interval {
27 t.Errorf("expected interval %v, got %v", interval, pm.interval)
28 }
29
30 if pm.running {
31 t.Error("expected monitor to not be running initially")
32 }
33
34 if pm.poller == nil {
35 t.Error("expected poller to be initialized")
36 }
37
38 if !pm.poller.IncludeLocalhost {
39 t.Error("expected IncludeLocalhost to be true")
40 }
41}
42
43// TestPortMonitor_DefaultInterval tests that a default interval is set when invalid.
44func TestPortMonitor_DefaultInterval(t *testing.T) {
45 agent := createTestAgent(t)
46
47 pm := NewPortMonitor(agent, 0)
48 if pm.interval != 5*time.Second {
49 t.Errorf("expected default interval 5s, got %v", pm.interval)
50 }
51
52 pm2 := NewPortMonitor(agent, -1*time.Second)
53 if pm2.interval != 5*time.Second {
54 t.Errorf("expected default interval 5s, got %v", pm2.interval)
55 }
56}
57
58// TestPortMonitor_StartStop tests starting and stopping the monitor.
59func TestPortMonitor_StartStop(t *testing.T) {
60 agent := createTestAgent(t)
61 pm := NewPortMonitor(agent, 100*time.Millisecond)
62
63 // Test starting
64 ctx := context.Background()
65 err := pm.Start(ctx)
66 if err != nil {
67 t.Fatalf("failed to start port monitor: %v", err)
68 }
69
70 if !pm.running {
71 t.Error("expected monitor to be running after start")
72 }
73
74 // Test double start fails
75 err = pm.Start(ctx)
76 if err == nil {
77 t.Error("expected error when starting already running monitor")
78 }
79
80 // Test stopping
81 pm.Stop()
82 if pm.running {
83 t.Error("expected monitor to not be running after stop")
84 }
85
86 // Test double stop is safe
87 pm.Stop() // should not panic
88}
89
90// TestPortMonitor_GetPorts tests getting the cached port list.
91func TestPortMonitor_GetPorts(t *testing.T) {
92 agent := createTestAgent(t)
93 pm := NewPortMonitor(agent, 100*time.Millisecond)
94
95 // Initially should be empty
96 ports := pm.GetPorts()
97 if len(ports) != 0 {
98 t.Errorf("expected empty ports initially, got %d", len(ports))
99 }
100
101 // Start monitoring to populate ports
102 ctx := context.Background()
103 err := pm.Start(ctx)
104 if err != nil {
105 t.Fatalf("failed to start port monitor: %v", err)
106 }
107 defer pm.Stop()
108
109 // Allow some time for initial scan
110 time.Sleep(200 * time.Millisecond)
111
112 // Should have some ports now (at least system ports)
113 ports = pm.GetPorts()
114 // We can't guarantee specific ports, but there should be at least some TCP ports
115 // on most systems (like SSH, etc.)
116 t.Logf("Found %d TCP ports", len(ports))
117
118 // Verify all returned ports are TCP
119 for _, port := range ports {
120 if port.Proto != "tcp" {
121 t.Errorf("expected TCP port, got %s", port.Proto)
122 }
123 }
124}
125
Philip Zeyliger5f26a342025-07-04 01:30:29 +0000126// TestPortMonitor_FilterTCPPorts tests the TCP port filtering.
127func TestPortMonitor_FilterTCPPorts(t *testing.T) {
128 ports := []portlist.Port{
129 {Proto: "tcp", Port: 80},
130 {Proto: "udp", Port: 53},
131 {Proto: "tcp", Port: 443},
132 {Proto: "udp", Port: 123},
133 }
134
135 tcpPorts := filterTCPPorts(ports)
136
137 if len(tcpPorts) != 2 {
138 t.Errorf("expected 2 TCP ports, got %d", len(tcpPorts))
139 }
140
141 for _, port := range tcpPorts {
142 if port.Proto != "tcp" {
143 t.Errorf("expected TCP port, got %s", port.Proto)
144 }
145 }
146}
147
148// TestPortMonitor_SortPorts tests the port sorting.
149func TestPortMonitor_SortPorts(t *testing.T) {
150 ports := []portlist.Port{
151 {Proto: "tcp", Port: 443},
152 {Proto: "tcp", Port: 80},
153 {Proto: "tcp", Port: 8080},
154 {Proto: "tcp", Port: 22},
155 }
156
157 sortPorts(ports)
158
159 expected := []uint16{22, 80, 443, 8080}
160 for i, port := range ports {
161 if port.Port != expected[i] {
162 t.Errorf("expected port %d at index %d, got %d", expected[i], i, port.Port)
163 }
164 }
165}
166
167// TestPortMonitor_FindAddedPorts tests finding added ports.
168func TestPortMonitor_FindAddedPorts(t *testing.T) {
169 previous := []portlist.Port{
170 {Proto: "tcp", Port: 80},
171 {Proto: "tcp", Port: 443},
172 }
173
174 current := []portlist.Port{
175 {Proto: "tcp", Port: 80},
176 {Proto: "tcp", Port: 443},
177 {Proto: "tcp", Port: 8080},
178 {Proto: "tcp", Port: 22},
179 }
180
181 added := findAddedPorts(previous, current)
182
183 if len(added) != 2 {
184 t.Errorf("expected 2 added ports, got %d", len(added))
185 }
186
187 addedPorts := make(map[uint16]bool)
188 for _, port := range added {
189 addedPorts[port.Port] = true
190 }
191
192 if !addedPorts[8080] || !addedPorts[22] {
193 t.Errorf("expected ports 8080 and 22 to be added, got %v", added)
194 }
195}
196
197// TestPortMonitor_FindRemovedPorts tests finding removed ports.
198func TestPortMonitor_FindRemovedPorts(t *testing.T) {
199 previous := []portlist.Port{
200 {Proto: "tcp", Port: 80},
201 {Proto: "tcp", Port: 443},
202 {Proto: "tcp", Port: 8080},
203 {Proto: "tcp", Port: 22},
204 }
205
206 current := []portlist.Port{
207 {Proto: "tcp", Port: 80},
208 {Proto: "tcp", Port: 443},
209 }
210
211 removed := findRemovedPorts(previous, current)
212
213 if len(removed) != 2 {
214 t.Errorf("expected 2 removed ports, got %d", len(removed))
215 }
216
217 removedPorts := make(map[uint16]bool)
218 for _, port := range removed {
219 removedPorts[port.Port] = true
220 }
221
222 if !removedPorts[8080] || !removedPorts[22] {
223 t.Errorf("expected ports 8080 and 22 to be removed, got %v", removed)
224 }
225}
226
227// createTestAgent creates a minimal test agent for testing.
228func createTestAgent(t *testing.T) *Agent {
229 // Create a minimal agent for testing
230 // We need to initialize the required fields for the PortMonitor to work
231 agent := &Agent{
232 subscribers: make([]chan *AgentMessage, 0),
233 }
234 return agent
235}