1package domain_test
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7 "github.com/stretchr/testify/require"
8
9 "github.com/bnema/gordon/internal/domain"
10)
11
12func reservationSpec() domain.AppSpec {
13 return domain.AppSpec{
14 Name: "blog",
15 Services: []domain.AppService{
16 {
17 Name: "web", Image: "img:1",
18 HTTP: []domain.AppHTTPInterface{{Host: "b.example.com", Port: 8080, TLS: "auto"}},
19 TCP: []domain.AppTCPInterface{{Entrypoint: "tcp", Port: 9000, Publish: "9000"}},
20 },
21 },
22 }
23}
24
25func TestReservationsFor_SortedAndNamespaced(t *testing.T) {
26 reservations := domain.ReservationsFor(reservationSpec())
27 require.Len(t, reservations, 2)
28 assert.Equal(t, "http", reservations[0].Proto)
29 assert.Equal(t, "tcp", reservations[1].Proto)
30 assert.Equal(t, "dual", reservations[1].IP)
31 assert.Equal(t, "web", reservations[0].Service)
32 assert.Equal(t, "blog", reservations[0].App)
33}
34
35func TestCheckReservations_RejectsDuplicateInsideCandidate(t *testing.T) {
36 spec := reservationSpec()
37 dup := spec.Services[0]
38 dup.Name = "web2"
39 spec.Services = append(spec.Services, dup)
40
41 err := domain.CheckReservations(nil, domain.ReservationsFor(spec), "blog")
42 require.ErrorIs(t, err, domain.ErrAppReservationConflict)
43 assert.Contains(t, err.Error(), "claims")
44}
45
46func TestCheckReservations_RejectsWildcardOverlapInsideCandidate(t *testing.T) {
47 candidate := []domain.AppListenerReservation{
48 {Proto: "tcp", IP: "0.0.0.0", Port: 9000, Service: "one", App: "tcp-overlap"},
49 {Proto: "tcp", IP: "127.0.0.1", Port: 9000, Service: "two", App: "tcp-overlap"},
50 }
51 err := domain.CheckReservations(nil, candidate, "tcp-overlap")
52 require.ErrorIs(t, err, domain.ErrAppReservationConflict)
53}
54
55func TestCheckReservations_RejectsCrossAppWildcardConflict(t *testing.T) {
56 existing := []domain.AppListenerReservation{
57 {Proto: "tcp", IP: "dual", Port: 9000, Service: "s", App: "other"},
58 }
59 candidate := []domain.AppListenerReservation{
60 {Proto: "tcp", IP: "127.0.0.1", Port: 9000, Service: "s", App: "shop"},
61 }
62 err := domain.CheckReservations(existing, candidate, "shop")
63 require.ErrorIs(t, err, domain.ErrAppReservationConflict)
64 assert.Contains(t, err.Error(), "conflicts with")
65}
66
67func TestCheckReservations_SameAppNeverSelfConflicts(t *testing.T) {
68 existing := []domain.AppListenerReservation{
69 {Proto: "http", Host: "blog.example.com", Service: "web", App: "blog"},
70 }
71 candidate := []domain.AppListenerReservation{
72 {Proto: "http", Host: "blog.example.com", Service: "web", App: "blog"},
73 }
74 require.NoError(t, domain.CheckReservations(existing, candidate, "blog"))
75}
76
77func TestCheckReservations_SamePortDifferentProtoCoexists(t *testing.T) {
78 candidate := []domain.AppListenerReservation{
79 {Proto: "tcp", IP: "0.0.0.0", Port: 9000, Service: "s", App: "a"},
80 {Proto: "udp", IP: "0.0.0.0", Port: 9000, Service: "s", App: "a"},
81 }
82 require.NoError(t, domain.CheckReservations(nil, candidate, "a"))
83}