test: add baseline table-driven tests for listener build, merge and compare

Covers current correct behaviour of the pure functions in internal/gateway.
Known-buggy inputs (wildcards, collisions, truncation) are left out on purpose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 20:36:11 +02:00
co-authored by Claude Opus 5.5
parent 3766e94b35
commit ab408403bc
2 changed files with 297 additions and 1 deletions
+279
View File
@@ -0,0 +1,279 @@
package gateway
import (
"testing"
"time"
cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
)
const testPrefix = "auto-"
func cert(namespace, name, secret string, dnsNames ...string) cmv1.Certificate {
return cmv1.Certificate{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
Spec: cmv1.CertificateSpec{SecretName: secret, DNSNames: dnsNames},
}
}
func listener(name, hostname, secretNamespace, secret string, port gwapiv1.PortNumber) gwapiv1.Listener {
h := gwapiv1.Hostname(hostname)
ns := gwapiv1.Namespace(secretNamespace)
return gwapiv1.Listener{
Name: gwapiv1.SectionName(name),
Hostname: &h,
Port: port,
Protocol: gwapiv1.HTTPSProtocolType,
TLS: &gwapiv1.ListenerTLSConfig{
CertificateRefs: []gwapiv1.SecretObjectReference{
{Name: gwapiv1.ObjectName(secret), Namespace: &ns},
},
},
}
}
func names(listeners []gwapiv1.Listener) []string {
out := make([]string, 0, len(listeners))
for _, l := range listeners {
out = append(out, string(l.Name))
}
return out
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func TestBuildListenersFromCertificates_names(t *testing.T) {
t.Parallel()
deleted := cert("default", "gone", "gone-tls", "gone.example.com")
now := metav1.NewTime(time.Unix(0, 0))
deleted.DeletionTimestamp = &now
tests := []struct {
name string
certs []cmv1.Certificate
want []string
}{
{
name: "no certificates",
},
{
name: "one listener per DNS name",
certs: []cmv1.Certificate{cert("default", "web", "web-tls", "a.example.com", "b.example.com")},
want: []string{"auto-web-a-example-com", "auto-web-b-example-com"},
},
{
name: "sorted by listener name across certificates",
certs: []cmv1.Certificate{
cert("default", "zeta", "zeta-tls", "z.example.com"),
cert("default", "alpha", "alpha-tls", "a.example.com"),
},
want: []string{"auto-alpha-a-example-com", "auto-zeta-z-example-com"},
},
{
name: "certificate being deleted is skipped",
certs: []cmv1.Certificate{deleted, cert("default", "web", "web-tls", "a.example.com")},
want: []string{"auto-web-a-example-com"},
},
{
name: "certificate without secret name is skipped",
certs: []cmv1.Certificate{cert("default", "web", "", "a.example.com")},
},
{
name: "certificate without DNS names yields nothing",
certs: []cmv1.Certificate{cert("default", "web", "web-tls")},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := names(BuildListenersFromCertificates(tc.certs, testPrefix))
if !equalStrings(got, tc.want) {
t.Errorf("listener names = %v, want %v", got, tc.want)
}
})
}
}
func TestBuildListenersFromCertificates_listenerFields(t *testing.T) {
t.Parallel()
got := BuildListenersFromCertificates(
[]cmv1.Certificate{cert("apps", "web", "web-tls", "app.example.com")}, testPrefix)
if len(got) != 1 {
t.Fatalf("got %d listeners, want 1", len(got))
}
l := got[0]
if l.Hostname == nil || *l.Hostname != "app.example.com" {
t.Errorf("hostname = %v, want app.example.com", l.Hostname)
}
if l.Port != 443 {
t.Errorf("port = %d, want 443", l.Port)
}
if l.Protocol != gwapiv1.HTTPSProtocolType {
t.Errorf("protocol = %q, want HTTPS", l.Protocol)
}
if l.TLS == nil || l.TLS.Mode == nil || *l.TLS.Mode != gwapiv1.TLSModeTerminate {
t.Fatalf("TLS mode is not Terminate: %+v", l.TLS)
}
if len(l.TLS.CertificateRefs) != 1 {
t.Fatalf("got %d certificate refs, want 1", len(l.TLS.CertificateRefs))
}
ref := l.TLS.CertificateRefs[0]
if ref.Name != "web-tls" {
t.Errorf("secret name = %q, want web-tls", ref.Name)
}
if ref.Namespace == nil || *ref.Namespace != "apps" {
t.Errorf("secret namespace = %v, want the certificate's namespace apps", ref.Namespace)
}
if ref.Kind == nil || *ref.Kind != "Secret" {
t.Errorf("ref kind = %v, want Secret", ref.Kind)
}
if l.AllowedRoutes == nil || l.AllowedRoutes.Namespaces == nil ||
l.AllowedRoutes.Namespaces.From == nil ||
*l.AllowedRoutes.Namespaces.From != gwapiv1.NamespacesFromAll {
t.Errorf("allowed routes = %+v, want namespaces from All", l.AllowedRoutes)
}
}
func TestMergeListeners_ownership(t *testing.T) {
t.Parallel()
manual := listener("https", "manual.example.com", "default", "manual-tls", 443)
stale := listener("auto-old-old-example-com", "old.example.com", "default", "old-tls", 443)
desired := listener("auto-web-a-example-com", "a.example.com", "default", "web-tls", 443)
tests := []struct {
name string
existing []gwapiv1.Listener
desired []gwapiv1.Listener
want []string
}{
{
name: "both empty",
},
{
name: "manual listeners are kept",
existing: []gwapiv1.Listener{manual},
desired: []gwapiv1.Listener{desired},
want: []string{"https", "auto-web-a-example-com"},
},
{
name: "stale managed listeners are dropped",
existing: []gwapiv1.Listener{manual, stale},
desired: []gwapiv1.Listener{desired},
want: []string{"https", "auto-web-a-example-com"},
},
{
name: "no desired listeners removes every managed one",
existing: []gwapiv1.Listener{stale, manual},
want: []string{"https"},
},
{
name: "existing managed listener is not duplicated",
existing: []gwapiv1.Listener{desired},
desired: []gwapiv1.Listener{desired},
want: []string{"auto-web-a-example-com"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := names(MergeListeners(tc.existing, tc.desired, testPrefix))
if !equalStrings(got, tc.want) {
t.Errorf("merged names = %v, want %v", got, tc.want)
}
})
}
}
func TestListenersEqual_managedFields(t *testing.T) {
t.Parallel()
a := listener("auto-web-a-example-com", "a.example.com", "default", "web-tls", 443)
b := listener("auto-web-b-example-com", "b.example.com", "default", "web-tls", 443)
tests := []struct {
name string
x, y []gwapiv1.Listener
want bool
}{
{name: "both empty", want: true},
{name: "identical", x: []gwapiv1.Listener{a, b}, y: []gwapiv1.Listener{a, b}, want: true},
{name: "order is ignored", x: []gwapiv1.Listener{a, b}, y: []gwapiv1.Listener{b, a}, want: true},
{name: "different length", x: []gwapiv1.Listener{a, b}, y: []gwapiv1.Listener{a}, want: false},
{name: "different name", x: []gwapiv1.Listener{a}, y: []gwapiv1.Listener{b}, want: false},
{
name: "different hostname",
x: []gwapiv1.Listener{a},
y: []gwapiv1.Listener{listener("auto-web-a-example-com", "other.example.com", "default", "web-tls", 443)},
want: false,
},
{
name: "different port",
x: []gwapiv1.Listener{a},
y: []gwapiv1.Listener{listener("auto-web-a-example-com", "a.example.com", "default", "web-tls", 8443)},
want: false,
},
{
name: "different secret name",
x: []gwapiv1.Listener{a},
y: []gwapiv1.Listener{listener("auto-web-a-example-com", "a.example.com", "default", "other-tls", 443)},
want: false,
},
{
name: "different secret namespace",
x: []gwapiv1.Listener{a},
y: []gwapiv1.Listener{listener("auto-web-a-example-com", "a.example.com", "other", "web-tls", 443)},
want: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := ListenersEqual(tc.x, tc.y); got != tc.want {
t.Errorf("ListenersEqual() = %v, want %v", got, tc.want)
}
})
}
}
func TestListenerNameFor_sanitizesDots(t *testing.T) {
t.Parallel()
tests := []struct {
name string
certName string
dnsName string
want string
}{
{name: "dots become dashes", certName: "web", dnsName: "app.example.com", want: "auto-web-app-example-com"},
{name: "single label", certName: "web", dnsName: "localhost", want: "auto-web-localhost"},
{name: "existing dashes are kept", certName: "my-cert", dnsName: "my-app.example.com", want: "auto-my-cert-my-app-example-com"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := listenerNameFor(testPrefix, tc.certName, tc.dnsName); got != tc.want {
t.Errorf("listenerNameFor() = %q, want %q", got, tc.want)
}
})
}
}