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