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 TestBuildRejectsAmbiguousNetworkServices(t *testing.T) {
177 t.Run("duplicate service name", func(t *testing.T) {
178 _, err := Build(Input{NetworkServices: []NetworkServiceConfig{
179 {Name: "db", Ports: []PortConfig{{Name: "sql", Container: 5432, Protocol: domain.NetworkProtocolTCP}}},
180 {Name: "db", Ports: []PortConfig{{Name: "sql", Container: 5433, Protocol: domain.NetworkProtocolTCP}}},
181 }})
182 require.ErrorContains(t, err, "duplicate network service")
183 })
184
185 t.Run("duplicate port name", func(t *testing.T) {
186 _, err := Build(Input{NetworkServices: []NetworkServiceConfig{
187 {Name: "db", Ports: []PortConfig{
188 {Name: "sql", Container: 5432, Protocol: domain.NetworkProtocolTCP},
189 {Name: "sql", Container: 5433, Protocol: domain.NetworkProtocolTCP},
190 }},
191 }})
192 require.ErrorContains(t, err, "duplicate port")
193 })
194
195 t.Run("unused invalid port rejected", func(t *testing.T) {
196 _, err := Build(Input{NetworkServices: []NetworkServiceConfig{
197 {Name: "db", Ports: []PortConfig{{Name: "unused", Container: 0, Protocol: domain.NetworkProtocolTCP}}},
198 }})
199 require.ErrorContains(t, err, "invalid container port")
200 })
201
202 t.Run("unused invalid protocol rejected", func(t *testing.T) {
203 _, err := Build(Input{NetworkServices: []NetworkServiceConfig{
204 {Name: "db", Ports: []PortConfig{{Name: "unused", Container: 5432, Protocol: domain.NetworkProtocol("sctp")}}},
205 }})
206 require.ErrorContains(t, err, "invalid protocol")
207 })
208}
209
210func TestBuildResolvesStandaloneServiceBackend(t *testing.T) {
211 graph, err := Build(Input{
212 EntryPoints: map[string]EntryPointConfig{"udp": {Address: ":28015", Protocol: domain.EntryPointProtocolUDP}},
213 Traffic: Config{UDP: UDPConfig{Routers: []RouterConfig{{Name: "rust-game", EntryPoint: "udp", Service: "service:rust:game"}}}},
214 Services: []domain.StandaloneService{{
215 Name: "rust",
216 Image: "localhost/rust:latest",
217 Enabled: true,
218 Ports: []domain.StandaloneServicePort{{Name: "game", Container: 28015, Protocol: domain.NetworkProtocolUDP, Publish: "127.0.0.1:38015"}},
219 }},
220 })
221 require.NoError(t, err)
222 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}}})
223}
224
225func TestBuildRejectsInvalidStandaloneServiceRefs(t *testing.T) {
226 base := Input{
227 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
228 Services: []domain.StandaloneService{{
229 Name: "rust",
230 Image: "localhost/rust:latest",
231 Enabled: true,
232 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"}}},
233 }},
234 }
235
236 t.Run("unknown service", func(t *testing.T) {
237 input := base
238 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:missing:rcon"}}}}
239 _, err := Build(input)
240 require.ErrorContains(t, err, "router \"bad\": unknown service \"missing\"")
241 })
242
243 t.Run("unknown port", func(t *testing.T) {
244 input := base
245 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:missing"}}}}
246 _, err := Build(input)
247 require.ErrorContains(t, err, "unknown port \"missing\" on service \"rust\"")
248 })
249
250 t.Run("disabled service", func(t *testing.T) {
251 input := base
252 input.Services = append([]domain.StandaloneService(nil), base.Services...)
253 input.Services[0].Enabled = false
254 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
255 _, err := Build(input)
256 require.ErrorContains(t, err, "unknown service \"rust\"")
257 })
258
259 t.Run("protocol mismatch", func(t *testing.T) {
260 input := base
261 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:game"}}}}
262 _, err := Build(input)
263 require.ErrorContains(t, err, "service \"rust\" port \"game\" protocol udp does not match router protocol tcp")
264 })
265
266 t.Run("missing publish", func(t *testing.T) {
267 input := base
268 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}}
269 input.Services = append([]domain.StandaloneService(nil), base.Services...)
270 input.Services[0].Ports = append([]domain.StandaloneServicePort(nil), base.Services[0].Ports...)
271 input.Services[0].Ports[1].Publish = ""
272 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
273 _, err := Build(input)
274 require.ErrorContains(t, err, "service \"rust\" port \"rcon\" publish address")
275 })
276
277 t.Run("invalid publish", func(t *testing.T) {
278 input := base
279 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}}
280 input.Services = append([]domain.StandaloneService(nil), base.Services...)
281 input.Services[0].Ports = append([]domain.StandaloneServicePort(nil), base.Services[0].Ports...)
282 input.Services[0].Ports[1].Publish = "127.0.0.1:notaport"
283 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "service:rust:rcon"}}}}
284 _, err := Build(input)
285 require.ErrorContains(t, err, "publish address")
286 })
287}
288
289func TestBuildWrapsStandaloneServiceValidationErrorsWithServiceName(t *testing.T) {
290 _, err := Build(Input{
291 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
292 Services: []domain.StandaloneService{{
293 Name: "rust",
294 Image: "localhost/rust:latest",
295 Enabled: true,
296 Ports: []domain.StandaloneServicePort{{Name: " admin ", Container: 28016, Protocol: domain.NetworkProtocolTCP}},
297 }},
298 })
299
300 require.ErrorContains(t, err, "standalone service \"rust\"")
301 require.ErrorContains(t, err, "leading or trailing whitespace")
302}
303
304func TestBuildEnforcesPrivateStandaloneServicePortRouting(t *testing.T) {
305 base := func() Input {
306 return Input{
307 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.0/10"}}},
308 Traffic: Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "rust-rcon", EntryPoint: "tcp", Service: "service:rust:rcon"}}}},
309 Services: []domain.StandaloneService{{
310 Name: "rust",
311 Image: "localhost/rust:latest",
312 Enabled: true,
313 Ports: []domain.StandaloneServicePort{{Name: "rcon", Container: 28016, Protocol: domain.NetworkProtocolTCP, Publish: "127.0.0.1:38016", TrustedCIDRs: []string{"100.64.0.0/10"}}},
314 }},
315 }
316 }
317
318 t.Run("rcon defaults private and allows exact trusted cidrs", func(t *testing.T) {
319 _, err := Build(base())
320 require.NoError(t, err)
321 })
322
323 t.Run("private port rejects service port without trusted cidrs", func(t *testing.T) {
324 input := base()
325 input.Services[0].Ports[0].TrustedCIDRs = nil
326 _, err := Build(input)
327 require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
328 })
329
330 t.Run("rcon rejects both service and entrypoint without trusted cidrs", func(t *testing.T) {
331 input := base()
332 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
333 input.Services[0].Ports[0].TrustedCIDRs = nil
334 _, err := Build(input)
335 require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
336 })
337
338 t.Run("explicit private non-rcon rejects service port without trusted cidrs", func(t *testing.T) {
339 input := base()
340 input.Services[0].Ports[0].Name = "admin"
341 input.Services[0].Ports[0].Private = true
342 input.Services[0].Ports[0].TrustedCIDRs = nil
343 input.Traffic.TCP.Routers[0].Service = "service:rust:admin"
344 _, err := Build(input)
345 require.ErrorContains(t, err, "requires non-empty service port trusted_cidrs")
346 })
347
348 t.Run("private port rejects entrypoint without trusted cidrs", func(t *testing.T) {
349 input := base()
350 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
351 _, err := Build(input)
352 require.ErrorContains(t, err, "entrypoint \"tcp\" requires non-empty trusted_cidrs")
353 })
354
355 t.Run("private port rejects non-matching trusted cidrs", func(t *testing.T) {
356 input := base()
357 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"10.0.0.0/8"}}}
358 _, err := Build(input)
359 require.ErrorContains(t, err, "must exactly match")
360 })
361
362 t.Run("private port compares canonical trusted cidrs", func(t *testing.T) {
363 input := base()
364 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP, TrustedCIDRs: []string{"100.64.0.1/10"}}}
365 _, err := Build(input)
366 require.NoError(t, err)
367 })
368
369 t.Run("explicit public rcon is allowed without trusted cidrs", func(t *testing.T) {
370 input := base()
371 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
372 input.Services[0].Ports[0].Public = true
373 input.Services[0].Ports[0].TrustedCIDRs = nil
374 _, err := Build(input)
375 require.NoError(t, err)
376 })
377
378 t.Run("explicit public non-rcon is allowed without trusted cidrs", func(t *testing.T) {
379 input := base()
380 input.EntryPoints = map[string]EntryPointConfig{"tcp": {Address: ":28016", Protocol: domain.EntryPointProtocolTCP}}
381 input.Services[0].Ports[0].Name = "admin"
382 input.Services[0].Ports[0].Private = false
383 input.Services[0].Ports[0].Public = true
384 input.Services[0].Ports[0].TrustedCIDRs = nil
385 input.Traffic.TCP.Routers[0].Service = "service:rust:admin"
386 _, err := Build(input)
387 require.NoError(t, err)
388 })
389}
390
391func TestBuildRejectsInvalidServiceRefs(t *testing.T) {
392 base := Input{
393 EntryPoints: map[string]EntryPointConfig{"tcp": {Address: ":1234", Protocol: domain.EntryPointProtocolTCP}},
394 NetworkServices: []NetworkServiceConfig{{Name: "svc", Ports: []PortConfig{{Name: "udp", Container: 53, Protocol: domain.NetworkProtocolUDP}}}},
395 }
396
397 t.Run("unknown service", func(t *testing.T) {
398 input := base
399 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:missing:tcp"}}}}
400 _, err := Build(input)
401 require.ErrorContains(t, err, "unknown network service")
402 })
403
404 t.Run("unknown port", func(t *testing.T) {
405 input := base
406 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:svc:missing"}}}}
407 _, err := Build(input)
408 require.ErrorContains(t, err, "unknown port")
409 })
410
411 t.Run("protocol mismatch", func(t *testing.T) {
412 input := base
413 input.Traffic = Config{TCP: TCPConfig{Routers: []RouterConfig{{Name: "bad", EntryPoint: "tcp", Service: "network_service:svc:udp"}}}}
414 _, err := Build(input)
415 require.ErrorContains(t, err, "does not match")
416 })
417}