1package traffic
2
3import (
4 "testing"
5 "time"
6
7 "github.com/stretchr/testify/require"
8
9 "github.com/bnema/gordon/internal/domain"
10)
11
12func TestBuildSmartTCPRoutesDefaultToEdge(t *testing.T) {
13 graph, err := Build(Input{
14 EntryPoints: map[string]EntryPointConfig{"edge": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}},
15 Routes: []domain.Route{{Domain: "app.example.com"}},
16 })
17 require.NoError(t, err)
18 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:app.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "app.example.com"}, Service: "route:app.example.com"})
19}
20
21func TestBuildManagedRoutesUseOnlyCustomSmartTCPEntrypoint(t *testing.T) {
22 graph, err := Build(Input{
23 EntryPoints: map[string]EntryPointConfig{"public": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}},
24 Routes: []domain.Route{{Domain: "app.example.com", HTTPS: true}},
25 })
26 require.NoError(t, err)
27 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:app.example.com", EntryPoint: "public", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "app.example.com"}, Service: "route:app.example.com"})
28}
29
30func TestBuildRouteEntrypointPrefersEdge(t *testing.T) {
31 graph, err := Build(Input{
32 EntryPoints: map[string]EntryPointConfig{
33 "edge": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP},
34 "public": {Address: ":8443", Protocol: domain.EntryPointProtocolSmartTCP},
35 },
36 Routes: []domain.Route{{Domain: "app.example.com", HTTPS: true}},
37 })
38 require.NoError(t, err)
39 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:app.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "app.example.com"}, Service: "route:app.example.com"})
40}
41
42func TestBuildRoutesRejectAmbiguousRouteEntrypoints(t *testing.T) {
43 _, err := Build(Input{
44 EntryPoints: map[string]EntryPointConfig{
45 "public-a": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP},
46 "public-b": {Address: ":8443", Protocol: domain.EntryPointProtocolTLSMux},
47 },
48 Routes: []domain.Route{{Domain: "app.example.com", HTTPS: true}},
49 })
50 require.ErrorContains(t, err, "multiple route-capable entrypoints")
51}
52
53func TestBuildExternalRoutesUseResolvedRouteEntryPoint(t *testing.T) {
54 graph, err := Build(Input{
55 EntryPoints: map[string]EntryPointConfig{"public": {Address: ":443", Protocol: domain.EntryPointProtocolTLSMux}},
56 ExternalRoutes: map[string]string{"ext.example.com": "backend.local:8080"},
57 })
58 require.NoError(t, err)
59 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "external_route:ext.example.com", EntryPoint: "public", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "ext.example.com"}, Service: "external_route:ext.example.com"})
60}
61
62func TestBuildRawFallbackConfigMapsToEntryPoint(t *testing.T) {
63 graph, err := Build(Input{
64 EntryPoints: map[string]EntryPointConfig{"edge": {
65 Address: ":443",
66 Protocol: domain.EntryPointProtocolSmartTCP,
67 RawFallback: "ssh-fallback",
68 RawFallbackTrustedCIDRs: []string{"100.64.0.0/10"},
69 AllowPublicRawFallback: true,
70 }},
71 Traffic: Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "ssh-fallback", EntryPoint: "edge", Service: "network_service:ssh:ssh"}}}},
72 NetworkServices: []NetworkServiceConfig{{Name: "ssh", Ports: []PortConfig{{Name: "ssh", Container: 22, Protocol: domain.NetworkProtocolTCP}}}},
73 })
74 require.NoError(t, err)
75 require.Equal(t, []domain.EntryPoint{{
76 Name: "edge",
77 Address: ":443",
78 Protocol: domain.EntryPointProtocolSmartTCP,
79 RawFallback: "ssh-fallback",
80 RawFallbackTrustedCIDRs: []string{"100.64.0.0/10"},
81 AllowPublicRawFallback: true,
82 }}, graph.EntryPoints)
83}
84
85func TestBuildSmartTCPRoutesRequireCompatibleEdge(t *testing.T) {
86 _, err := Build(Input{Routes: []domain.Route{{Domain: "app.example.com"}}})
87 require.ErrorContains(t, err, "entrypoint")
88}
89
90func TestBuildDefaultsAndHTTPRoutes(t *testing.T) {
91 graph, err := Build(Input{
92 EntryPoints: map[string]EntryPointConfig{"edge": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}},
93 Routes: []domain.Route{{Domain: "app.example.com"}, {Domain: "secure.example.com", HTTPS: true}},
94 ExternalRoutes: map[string]string{"ext.example.com": "backend.local:8080"},
95 })
96 require.NoError(t, err)
97
98 require.Equal(t, []domain.EntryPoint{{Name: "edge", Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}}, graph.EntryPoints)
99 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:app.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "app.example.com"}, Service: "route:app.example.com"})
100 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "route:secure.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "secure.example.com"}, Service: "route:secure.example.com"})
101 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "external_route:ext.example.com", EntryPoint: "edge", Protocol: domain.RouterProtocolHTTP, Rule: domain.TrafficRule{Host: "ext.example.com"}, Service: "external_route:ext.example.com"})
102 require.Contains(t, graph.Services, domain.TrafficService{Name: "external_route:ext.example.com", Backends: []domain.TrafficBackend{{Name: "ext.example.com", Host: "backend.local", Port: 8080, Protocol: domain.NetworkProtocolTCP}}})
103}
104
105func TestBuildCustomEntryPointTrustedCIDRs(t *testing.T) {
106 graph, err := Build(Input{EntryPoints: map[string]EntryPointConfig{"tcp-public": {Address: ":5432", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"10.0.0.0/8"}}}})
107 require.NoError(t, err)
108 require.Equal(t, []domain.EntryPoint{{Name: "tcp-public", Address: ":5432", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"10.0.0.0/8"}}}, graph.EntryPoints)
109}
110
111func TestBuildExplicitTCPUDPTLSRouterResolution(t *testing.T) {
112 graph, err := Build(Input{
113 EntryPoints: map[string]EntryPointConfig{
114 "tcp-db": {Address: ":5432", Protocol: domain.EntryPointProtocolTCP},
115 "udp-dns": {Address: ":5353", Protocol: domain.EntryPointProtocolUDP},
116 "tls-db": {Address: ":15432", Protocol: domain.EntryPointProtocolTLSMux},
117 },
118 Traffic: Config{
119 TCP: TCPConfig{Routers: []RouterConfig{{Name: "db", EntryPoint: "tcp-db", Service: "network_service:postgres:sql"}}},
120 UDP: UDPConfig{Routers: []RouterConfig{{Name: "dns", EntryPoint: "udp-dns", Service: "network_service:coredns:dns"}}},
121 TLS: TLSConfig{Routers: []RouterConfig{{Name: "tlsdb", EntryPoint: "tls-db", SNI: "db.example.com", Service: "network_service:postgres:sql"}}},
122 },
123 NetworkServices: []NetworkServiceConfig{
124 {Name: "postgres", Ports: []PortConfig{{Name: "sql", Container: 5432, Protocol: domain.NetworkProtocolTCP}}},
125 {Name: "coredns", Ports: []PortConfig{{Name: "dns", Container: 53, Protocol: domain.NetworkProtocolUDP}}},
126 },
127 })
128 require.NoError(t, err)
129 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "db", EntryPoint: "tcp-db", Protocol: domain.RouterProtocolTCP, Service: "network_service:postgres:sql"})
130 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "dns", EntryPoint: "udp-dns", Protocol: domain.RouterProtocolUDP, Service: "network_service:coredns:dns"})
131 require.Contains(t, graph.Routers, domain.TrafficRouter{Name: "tlsdb", EntryPoint: "tls-db", Protocol: domain.RouterProtocolTLSPassthrough, Rule: domain.TrafficRule{SNI: "db.example.com"}, Service: "network_service:postgres:sql"})
132 require.Contains(t, graph.Services, domain.TrafficService{Name: "network_service:postgres:sql", Backends: []domain.TrafficBackend{{Name: "postgres:sql", Host: "postgres", Port: 5432, Protocol: domain.NetworkProtocolTCP}}})
133}
134
135func TestBuildRejectsStaticServiceRef(t *testing.T) {
136 _, err := Build(Input{
137 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
138 Traffic: Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "static:legacy"}}}},
139 })
140 require.ErrorContains(t, err, "static")
141}
142
143func TestBuildRejectsTLSHTTPRouteConflict(t *testing.T) {
144 _, err := Build(Input{
145 EntryPoints: map[string]EntryPointConfig{"edge": {Address: ":443", Protocol: domain.EntryPointProtocolSmartTCP}},
146 Routes: []domain.Route{{Domain: "app.example.com", HTTPS: true}},
147 Traffic: Config{TLS: TLSConfig{Routers: []RouterConfig{{Name: "passthrough", EntryPoint: "edge", SNI: "app.example.com", Service: "network_service:app:https"}}}},
148 NetworkServices: []NetworkServiceConfig{{Name: "app", Ports: []PortConfig{{Name: "https", Container: 443, Protocol: domain.NetworkProtocolTCP}}}},
149 })
150 require.ErrorContains(t, err, "http host conflicts")
151}
152
153func TestBuildOptionsParsing(t *testing.T) {
154 graph, err := Build(Input{Traffic: Config{TCP: TCPConfig{DialTimeout: "1s", IdleTimeout: "2m", DrainTimeout: "3s", MaxConnections: 12}, UDP: UDPConfig{IdleTimeout: "4s", DrainTimeout: "5s", MaxSessions: 34}}})
155 require.NoError(t, err)
156 require.Equal(t, time.Second, graph.Options.TCP.DialTimeout)
157 require.Equal(t, 2*time.Minute, graph.Options.TCP.IdleTimeout)
158 require.Equal(t, 3*time.Second, graph.Options.TCP.DrainTimeout)
159 require.Equal(t, 12, graph.Options.TCP.MaxConnections)
160 require.Equal(t, 4*time.Second, graph.Options.UDP.IdleTimeout)
161 require.Equal(t, 5*time.Second, graph.Options.UDP.DrainTimeout)
162 require.Equal(t, 34, graph.Options.UDP.MaxSessions)
163
164 defaults, err := Build(Input{})
165 require.NoError(t, err)
166 require.Equal(t, 10*time.Second, defaults.Options.TCP.DialTimeout)
167 require.Equal(t, 5*time.Minute, defaults.Options.TCP.IdleTimeout)
168 require.Equal(t, 30*time.Second, defaults.Options.TCP.DrainTimeout)
169 require.Equal(t, 30*time.Second, defaults.Options.UDP.IdleTimeout)
170 require.Equal(t, 30*time.Second, defaults.Options.UDP.DrainTimeout)
171
172 _, err = Build(Input{Traffic: Config{TCP: TCPConfig{DialTimeout: "bogus"}}})
173 require.ErrorContains(t, err, "traffic.tcp.dial_timeout")
174}
175
176func TestBuildHTTPOptions(t *testing.T) {
177 defaults, err := Build(Input{})
178 require.NoError(t, err)
179 require.Equal(t, domain.DefaultHTTPOptions(), defaults.Options.HTTP)
180
181 graph, err := Build(Input{Traffic: Config{HTTP: HTTPConfig{ReadHeaderTimeout: "1s", IdleTimeout: "2s", TransferIdleTimeout: "3m"}}})
182 require.NoError(t, err)
183 require.Equal(t, domain.HTTPOptions{ReadHeaderTimeout: time.Second, IdleTimeout: 2 * time.Second, TransferIdleTimeout: 3 * time.Minute}, graph.Options.HTTP)
184
185 for _, value := range []string{"bogus", "-1s", "0s"} {
186 _, err = Build(Input{Traffic: Config{HTTP: HTTPConfig{TransferIdleTimeout: value}}})
187 require.ErrorContains(t, err, "traffic.http.transfer_idle_timeout", value)
188 }
189}
190
191func TestBuildRejectsAmbiguousNetworkServices(t *testing.T) {
192 t.Run("duplicate service name", func(t *testing.T) {
193 _, err := Build(Input{NetworkServices: []NetworkServiceConfig{
194 {Name: "db", Ports: []PortConfig{{Name: "sql", Container: 5432, Protocol: domain.NetworkProtocolTCP}}},
195 {Name: "db", Ports: []PortConfig{{Name: "sql", Container: 5433, Protocol: domain.NetworkProtocolTCP}}},
196 }})
197 require.ErrorContains(t, err, "duplicate network service")
198 })
199
200 t.Run("duplicate port name", func(t *testing.T) {
201 _, err := Build(Input{NetworkServices: []NetworkServiceConfig{
202 {Name: "db", Ports: []PortConfig{
203 {Name: "sql", Container: 5432, Protocol: domain.NetworkProtocolTCP},
204 {Name: "sql", Container: 5433, Protocol: domain.NetworkProtocolTCP},
205 }},
206 }})
207 require.ErrorContains(t, err, "duplicate port")
208 })
209
210 t.Run("unused invalid port rejected", func(t *testing.T) {
211 _, err := Build(Input{NetworkServices: []NetworkServiceConfig{
212 {Name: "db", Ports: []PortConfig{{Name: "unused", Container: 0, Protocol: domain.NetworkProtocolTCP}}},
213 }})
214 require.ErrorContains(t, err, "invalid container port")
215 })
216
217 t.Run("unused invalid protocol rejected", func(t *testing.T) {
218 _, err := Build(Input{NetworkServices: []NetworkServiceConfig{
219 {Name: "db", Ports: []PortConfig{{Name: "unused", Container: 5432, Protocol: domain.NetworkProtocol("sctp")}}},
220 }})
221 require.ErrorContains(t, err, "invalid protocol")
222 })
223}
224
225func TestBuildResolvesStandaloneServiceBackend(t *testing.T) {
226 graph, err := Build(Input{
227 EntryPoints: map[string]EntryPointConfig{"udp": {Address: ":28015", Protocol: domain.EntryPointProtocolUDP}},
228 Traffic: Config{UDP: UDPConfig{Routers: []RouterConfig{{Name: "rust-game", EntryPoint: "udp", Service: "service:rust:game"}}}},
229 Services: []domain.StandaloneService{{
230 Name: "rust",
231 Image: "localhost/rust:latest",
232 Enabled: true,
233 Ports: []domain.StandaloneServicePort{{Name: "game", Container: 28015, Protocol: domain.NetworkProtocolUDP, Publish: "127.0.0.1:38015"}},
234 }},
235 })
236 require.NoError(t, err)
237 require.Contains(t, graph.Services, domain.TrafficService{Name: "service:rust:game", Backends: []domain.TrafficBackend{{Name: "rust:game", Host: "127.0.0.1", Port: 38015, Protocol: domain.NetworkProtocolUDP}}})
238}
239
240func TestBuildRejectsInvalidStandaloneServiceRefs(t *testing.T) {
241 base := Input{
242 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
243 Services: []domain.StandaloneService{{
244 Name: "rust",
245 Image: "localhost/rust:latest",
246 Enabled: true,
247 Ports: []domain.StandaloneServicePort{{Name: "game", Container: 28015, Protocol: domain.NetworkProtocolUDP, Publish: "127.0.0.1:38015"}, {Name: "rcon", Container: 28016, Protocol: domain.NetworkProtocolTCP, Publish: "127.0.0.1:38016", Private: true, TrustedCIDRs: []string{"100.64.0.0/10"}}},
248 }},
249 }
250
251 t.Run("unknown service", func(t *testing.T) {
252 input := base
253 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:missing:rcon"}}}}
254 _, err := Build(input)
255 require.ErrorContains(t, err, "router \"bad\": unknown service \"missing\"")
256 })
257
258 t.Run("unknown port", func(t *testing.T) {
259 input := base
260 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:missing"}}}}
261 _, err := Build(input)
262 require.ErrorContains(t, err, "unknown port \"missing\" on service \"rust\"")
263 })
264
265 t.Run("disabled service", func(t *testing.T) {
266 input := base
267 input.Services = append([]domain.StandaloneService(nil), base.Services...)
268 input.Services[0].Enabled = false
269 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
270 _, err := Build(input)
271 require.ErrorContains(t, err, "unknown service \"rust\"")
272 })
273
274 t.Run("protocol mismatch", func(t *testing.T) {
275 input := base
276 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:game"}}}}
277 _, err := Build(input)
278 require.ErrorContains(t, err, "service \"rust\" port \"game\" protocol udp does not match router protocol tcp")
279 })
280
281 t.Run("missing publish", func(t *testing.T) {
282 input := base
283 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}}
284 input.Services = append([]domain.StandaloneService(nil), base.Services...)
285 input.Services[0].Ports = append([]domain.StandaloneServicePort(nil), base.Services[0].Ports...)
286 input.Services[0].Ports[1].Publish = ""
287 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
288 _, err := Build(input)
289 require.ErrorContains(t, err, "service \"rust\" port \"rcon\" publish address")
290 })
291
292 t.Run("invalid publish", func(t *testing.T) {
293 input := base
294 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}}
295 input.Services = append([]domain.StandaloneService(nil), base.Services...)
296 input.Services[0].Ports = append([]domain.StandaloneServicePort(nil), base.Services[0].Ports...)
297 input.Services[0].Ports[1].Publish = "127.0.0.1:notaport"
298 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
299 _, err := Build(input)
300 require.ErrorContains(t, err, "publish address")
301 })
302}
303
304func TestBuildWrapsStandaloneServiceValidationErrorsWithServiceName(t *testing.T) {
305 _, err := Build(Input{
306 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
307 Services: []domain.StandaloneService{{
308 Name: "rust",
309 Image: "localhost/rust:latest",
310 Enabled: true,
311 Ports: []domain.StandaloneServicePort{{Name: " admin ", Container: 28016, Protocol: domain.NetworkProtocolTCP}},
312 }},
313 })
314
315 require.ErrorContains(t, err, "standalone service \"rust\"")
316 require.ErrorContains(t, err, "leading or trailing whitespace")
317}
318
319func TestBuildEnforcesPrivateStandaloneServicePortRouting(t *testing.T) {
320 base := func() Input {
321 return Input{
322 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}},
323 Traffic: Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "rust-rcon", EntryPoint: "tcp", Service: "service:rust:rcon"}}}},
324 Services: []domain.StandaloneService{{
325 Name: "rust",
326 Image: "localhost/rust:latest",
327 Enabled: true,
328 Ports: []domain.StandaloneServicePort{{Name: "rcon", Container: 28016, Protocol: domain.NetworkProtocolTCP, Publish: "127.0.0.1:38016", TrustedCIDRs: []string{"100.64.0.0/10"}}},
329 }},
330 }
331 }
332
333 t.Run("rcon defaults private and allows exact trusted cidrs", func(t *testing.T) {
334 _, err := Build(base())
335 require.NoError(t, err)
336 })
337
338 t.Run("private port rejects service port without trusted cidrs", func(t *testing.T) {
339 input := base()
340 input.Services[0].Ports[0].TrustedCIDRs = nil
341 _, err := Build(input)
342 require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
343 })
344
345 t.Run("rcon rejects both service and entrypoint without trusted cidrs", func(t *testing.T) {
346 input := base()
347 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
348 input.Services[0].Ports[0].TrustedCIDRs = nil
349 _, err := Build(input)
350 require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
351 })
352
353 t.Run("explicit private non-rcon rejects service port without trusted cidrs", func(t *testing.T) {
354 input := base()
355 input.Services[0].Ports[0].Name = "admin"
356 input.Services[0].Ports[0].Private = true
357 input.Services[0].Ports[0].TrustedCIDRs = nil
358 input.Traffic.TCP.Routers[0].Service = "service:rust:admin"
359 _, err := Build(input)
360 require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
361 })
362
363 t.Run("private port rejects entrypoint without trusted cidrs", func(t *testing.T) {
364 input := base()
365 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
366 _, err := Build(input)
367 require.ErrorContains(t, err, "entrypoint \"tcp\" requires non-empty trusted_cidrs")
368 })
369
370 t.Run("private port rejects non-matching trusted cidrs", func(t *testing.T) {
371 input := base()
372 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"10.0.0.0/8"}}}
373 _, err := Build(input)
374 require.ErrorContains(t, err, "must exactly match")
375 })
376
377 t.Run("private port compares canonical trusted cidrs", func(t *testing.T) {
378 input := base()
379 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.1/10"}}}
380 _, err := Build(input)
381 require.NoError(t, err)
382 })
383
384 t.Run("explicit public rcon is allowed without trusted cidrs", func(t *testing.T) {
385 input := base()
386 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
387 input.Services[0].Ports[0].Public = true
388 input.Services[0].Ports[0].TrustedCIDRs = nil
389 _, err := Build(input)
390 require.NoError(t, err)
391 })
392
393 t.Run("explicit public non-rcon is allowed without trusted cidrs", func(t *testing.T) {
394 input := base()
395 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
396 input.Services[0].Ports[0].Name = "admin"
397 input.Services[0].Ports[0].Private = false
398 input.Services[0].Ports[0].Public = true
399 input.Services[0].Ports[0].TrustedCIDRs = nil
400 input.Traffic.TCP.Routers[0].Service = "service:rust:admin"
401 _, err := Build(input)
402 require.NoError(t, err)
403 })
404}
405
406func TestBuildRejectsInvalidServiceRefs(t *testing.T) {
407 base := Input{
408 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
409 NetworkServices: []NetworkServiceConfig{{Name: "svc", Ports: []PortConfig{{Name: "udp", Container: 53, Protocol: domain.NetworkProtocolUDP}}}},
410 }
411
412 t.Run("unknown service", func(t *testing.T) {
413 input := base
414 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:missing:tcp"}}}}
415 _, err := Build(input)
416 require.ErrorContains(t, err, "unknown network service")
417 })
418
419 t.Run("unknown port", func(t *testing.T) {
420 input := base
421 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:svc:missing"}}}}
422 _, err := Build(input)
423 require.ErrorContains(t, err, "unknown port")
424 })
425
426 t.Run("protocol mismatch", func(t *testing.T) {
427 input := base
428 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:svc:udp"}}}}
429 _, err := Build(input)
430 require.ErrorContains(t, err, "does not match")
431 })
432}