blob: f43055934a01c02a5648e11c4fd0757a7eeaedd4 [file] [log] [blame]
Sean McCullough138ec242025-06-02 22:42:06 +00001package dockerimg
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 "sketch.dev/loop"
9)
10
11// TestTunnelManagerMaxLimit tests that the tunnel manager respects the max tunnels limit
12func TestTunnelManagerMaxLimit(t *testing.T) {
13 tm := NewTunnelManager("http://localhost:8080", "test-container", 2) // Max 2 tunnels
14
15 if tm.maxActiveTunnels != 2 {
16 t.Errorf("Expected maxActiveTunnels to be 2, got %d", tm.maxActiveTunnels)
17 }
18
19 // Test that GetActiveTunnels returns empty initially
20 activeTunnels := tm.GetActiveTunnels()
21 if len(activeTunnels) != 0 {
22 t.Errorf("Expected 0 active tunnels initially, got %d", len(activeTunnels))
23 }
24
25 // Simulate adding tunnels beyond the limit by directly manipulating the internal map
26 // This tests the limit logic without actually starting SSH processes
27 // Add 2 tunnels (up to the limit)
28 tm.activeTunnels["8080"] = &sshTunnel{containerPort: "8080", hostPort: "8080"}
29 tm.activeTunnels["9090"] = &sshTunnel{containerPort: "9090", hostPort: "9090"}
30
31 // Verify we have 2 active tunnels
32 activeTunnels = tm.GetActiveTunnels()
33 if len(activeTunnels) != 2 {
34 t.Errorf("Expected 2 active tunnels, got %d", len(activeTunnels))
35 }
36
37 // Now test that the limit check works - attempt to add a third tunnel
38 // Check if we've reached the maximum (this simulates the check in createTunnel)
39 shouldBlock := len(tm.activeTunnels) >= tm.maxActiveTunnels
40
41 if !shouldBlock {
42 t.Error("Expected tunnel creation to be blocked when at max limit, but limit check failed")
43 }
44
45 // Verify that attempting to add beyond the limit doesn't actually add more
46 if len(tm.activeTunnels) < tm.maxActiveTunnels {
47 // This should not happen since we're at the limit
48 tm.activeTunnels["3000"] = &sshTunnel{containerPort: "3000", hostPort: "3000"}
49 }
50
51 // Verify we still have only 2 active tunnels (didn't exceed limit)
52 activeTunnels = tm.GetActiveTunnels()
53 if len(activeTunnels) != 2 {
54 t.Errorf("Expected exactly 2 active tunnels after limit enforcement, got %d", len(activeTunnels))
55 }
56}
57
58// TestNewTunnelManagerParams tests that NewTunnelManager correctly sets all parameters
59func TestNewTunnelManagerParams(t *testing.T) {
60 containerURL := "http://localhost:9090"
61 containerSSHHost := "test-ssh-host"
62 maxTunnels := 5
63
64 tm := NewTunnelManager(containerURL, containerSSHHost, maxTunnels)
65
66 if tm.containerURL != containerURL {
67 t.Errorf("Expected containerURL %s, got %s", containerURL, tm.containerURL)
68 }
69 if tm.containerSSHHost != containerSSHHost {
70 t.Errorf("Expected containerSSHHost %s, got %s", containerSSHHost, tm.containerSSHHost)
71 }
72 if tm.maxActiveTunnels != maxTunnels {
73 t.Errorf("Expected maxActiveTunnels %d, got %d", maxTunnels, tm.maxActiveTunnels)
74 }
75 if tm.activeTunnels == nil {
76 t.Error("Expected activeTunnels map to be initialized")
77 }
78 if tm.lastPollTime.IsZero() {
79 t.Error("Expected lastPollTime to be initialized")
80 }
81}
82
83// TestShouldSkipPort tests the port skipping logic
84func TestShouldSkipPort(t *testing.T) {
85 tm := NewTunnelManager("http://localhost:8080", "test-container", 10)
86
87 // Test that system ports are skipped
88 systemPorts := []string{"22", "80", "443", "25", "53"}
89 for _, port := range systemPorts {
90 if !tm.shouldSkipPort(port) {
91 t.Errorf("Expected port %s to be skipped", port)
92 }
93 }
94
95 // Test that application ports are not skipped
96 appPorts := []string{"8080", "3000", "9090", "8000"}
97 for _, port := range appPorts {
98 if tm.shouldSkipPort(port) {
99 t.Errorf("Expected port %s to NOT be skipped", port)
100 }
101 }
102}
103
104// TestExtractPortNumber tests port number extraction from ss format
105func TestExtractPortNumber(t *testing.T) {
106 tm := NewTunnelManager("http://localhost:8080", "test-container", 10)
107
108 tests := []struct {
109 input string
110 expected string
111 }{
112 {"tcp:0.0.0.0:8080", "8080"},
113 {"tcp:127.0.0.1:3000", "3000"},
114 {"tcp:[::]:9090", "9090"},
115 {"udp:0.0.0.0:53", "53"},
116 {"invalid", ""},
117 {"", ""},
118 }
119
120 for _, test := range tests {
121 result := tm.extractPortNumber(test.input)
122 if result != test.expected {
123 t.Errorf("extractPortNumber(%q) = %q, expected %q", test.input, result, test.expected)
124 }
125 }
126}
127
128// TestTunnelManagerLimitEnforcement tests that createTunnel enforces the max limit
129func TestTunnelManagerLimitEnforcement(t *testing.T) {
130 tm := NewTunnelManager("http://localhost:8080", "test-container", 2) // Max 2 tunnels
131 ctx := context.Background()
132
133 // Add tunnels manually to reach the limit (simulating successful SSH setup)
134 // In real usage, these would be added by createTunnel after successful SSH
135 tm.activeTunnels["8080"] = &sshTunnel{containerPort: "8080", hostPort: "8080"}
136 tm.activeTunnels["9090"] = &sshTunnel{containerPort: "9090", hostPort: "9090"}
137
138 // Verify we're now at the limit
139 if len(tm.GetActiveTunnels()) != 2 {
140 t.Fatalf("Setup failed: expected 2 active tunnels, got %d", len(tm.GetActiveTunnels()))
141 }
142
143 // Now test that createTunnel respects the limit by calling it directly
144 // This should hit the limit check and return early without attempting SSH
145 tm.createTunnel(ctx, "3000")
146 tm.createTunnel(ctx, "4000")
147
148 // Verify no additional tunnels were added (limit enforcement worked)
149 if len(tm.GetActiveTunnels()) != 2 {
150 t.Errorf("createTunnel should have been blocked by limit, but tunnel count changed from 2 to %d", len(tm.GetActiveTunnels()))
151 }
152
153 // Verify we're at the limit
154 if len(tm.GetActiveTunnels()) != 2 {
155 t.Fatalf("Expected 2 active tunnels, got %d", len(tm.GetActiveTunnels()))
156 }
157
158 // Now try to process more port events that would create additional tunnels
159 // These should be blocked by the limit check in createTunnel
160 portEvent1 := loop.PortEvent{
161 Type: "opened",
162 Port: "tcp:0.0.0.0:3000",
163 Timestamp: time.Now(),
164 }
165 portEvent2 := loop.PortEvent{
166 Type: "opened",
167 Port: "tcp:0.0.0.0:4000",
168 Timestamp: time.Now(),
169 }
170
171 // Process these events - they should be blocked by the limit
172 tm.processPortEvent(ctx, portEvent1)
173 tm.processPortEvent(ctx, portEvent2)
174
175 // Verify that no additional tunnels were created
176 activeTunnels := tm.GetActiveTunnels()
177 if len(activeTunnels) != 2 {
178 t.Errorf("Expected exactly 2 active tunnels after limit enforcement, got %d", len(activeTunnels))
179 }
180
181 // Verify the original tunnels are still there
182 if _, exists := activeTunnels["8080"]; !exists {
183 t.Error("Expected original tunnel for port 8080 to still exist")
184 }
185 if _, exists := activeTunnels["9090"]; !exists {
186 t.Error("Expected original tunnel for port 9090 to still exist")
187 }
188
189 // Verify the new tunnels were NOT created
190 if _, exists := activeTunnels["3000"]; exists {
191 t.Error("Expected tunnel for port 3000 to NOT be created due to limit")
192 }
193 if _, exists := activeTunnels["4000"]; exists {
194 t.Error("Expected tunnel for port 4000 to NOT be created due to limit")
195 }
196}