Files
egress-proxies-operator/api/v1alpha1/helpers_test.go
Jan Novak f28766fce3 Add Proxy API types with CEL validation (Step 1)
Full ProxySpec/ProxyStatus/Proxy types per the plan: PlacementSpec,
CloudInitSpec, EndpointSpec, HealthCheckSpec, SecretKeySelector, all
defaults, and 7 CEL XValidation rules enforcing mode/provider
immutability, provider/endpoint required-iff-Managed/External, and
cloud-init exactly-one-of inline/secretRef.

Applies the four corrections identified during planning that would
otherwise be silent bugs: MaxLeases as *int32 (so an explicit 0 survives
Go round-trips instead of re-defaulting to 5), HealthCheck's
default={} marker (so nested defaults apply even when the field is
omitted entirely), MinLength=1 on Provider/CloudInit.Inline (so the CEL
has() checks stay simple), and listType=map on Conditions.

Adds pure helpers (EffectivePort, EffectiveHost, HealthCheckOrDefault,
MaxLeasesOrDefault) with table-driven tests, for use by the health
engine, discovery API, and spec-hash computation in later steps.

Patches the scaffolded placeholder controller test's resource literal to
a schema-valid spec so it survives the new CRD validation — the test
itself is rewritten wholesale in Step 4 alongside the real reconciler.

Regenerated deepcopy and the CRD; make test green (envtest confirmed all
7 CEL rules enforced by a real apiserver).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 21:01:56 +02:00

191 lines
5.2 KiB
Go

/*
Copyright 2026.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
"reflect"
"testing"
)
func TestEffectivePort(t *testing.T) {
t.Parallel()
tests := []struct {
name string
spec ProxySpec
want int32
}{
{
name: "managed with explicit port",
spec: ProxySpec{Mode: ModeManaged, Port: 8080},
want: 8080,
},
{
name: "managed with unset port falls back to default",
spec: ProxySpec{Mode: ModeManaged},
want: DefaultPort,
},
{
name: "external with explicit endpoint port",
spec: ProxySpec{Mode: ModeExternal, Endpoint: &EndpointSpec{Host: "1.2.3.4", Port: 9999}},
want: 9999,
},
{
name: "external with unset endpoint port falls back to default",
spec: ProxySpec{Mode: ModeExternal, Endpoint: &EndpointSpec{Host: "1.2.3.4"}},
want: DefaultPort,
},
{
name: "external with nil endpoint falls back to spec.port",
spec: ProxySpec{Mode: ModeExternal, Port: 3000},
want: 3000,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: tc.spec}
if got := p.EffectivePort(); got != tc.want {
t.Errorf("EffectivePort() = %d, want %d", got, tc.want)
}
})
}
}
func TestEffectiveHost(t *testing.T) {
t.Parallel()
tests := []struct {
name string
spec ProxySpec
status ProxyStatus
want string
}{
{
name: "managed uses status.ip",
spec: ProxySpec{Mode: ModeManaged},
status: ProxyStatus{IP: "10.0.0.5"},
want: "10.0.0.5",
},
{
name: "external uses endpoint.host",
spec: ProxySpec{Mode: ModeExternal, Endpoint: &EndpointSpec{Host: "proxy.example.com"}},
want: "proxy.example.com",
},
{
name: "external with nil endpoint falls back to status.ip",
spec: ProxySpec{Mode: ModeExternal},
status: ProxyStatus{IP: "10.0.0.6"},
want: "10.0.0.6",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: tc.spec, Status: tc.status}
if got := p.EffectiveHost(); got != tc.want {
t.Errorf("EffectiveHost() = %q, want %q", got, tc.want)
}
})
}
}
func TestHealthCheckOrDefault(t *testing.T) {
t.Parallel()
fullDefault := HealthCheckSpec{
ProbeURL: DefaultProbeURL,
IntervalSeconds: DefaultHealthCheckIntervalSeconds,
TimeoutSeconds: DefaultHealthCheckTimeoutSeconds,
FailureThreshold: DefaultFailureThreshold,
SuccessThreshold: DefaultSuccessThreshold,
ExpectedStatusCodes: []int32{200, 204},
}
t.Run("nil healthCheck returns full default", func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: ProxySpec{}}
got := p.HealthCheckOrDefault()
if !reflect.DeepEqual(got, fullDefault) {
t.Errorf("HealthCheckOrDefault() = %+v, want %+v", got, fullDefault)
}
})
t.Run("partial healthCheck fills only unset fields", func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: ProxySpec{HealthCheck: &HealthCheckSpec{
ProbeURL: "http://internal/probe",
FailureThreshold: 7,
}}}
got := p.HealthCheckOrDefault()
want := fullDefault
want.ProbeURL = "http://internal/probe"
want.FailureThreshold = 7
if !reflect.DeepEqual(got, want) {
t.Errorf("HealthCheckOrDefault() = %+v, want %+v", got, want)
}
})
t.Run("fully set healthCheck passes through unchanged", func(t *testing.T) {
t.Parallel()
custom := HealthCheckSpec{
ProbeURL: "http://internal/probe",
IntervalSeconds: 10,
TimeoutSeconds: 2,
FailureThreshold: 5,
SuccessThreshold: 2,
ExpectedStatusCodes: []int32{200},
}
p := &Proxy{Spec: ProxySpec{HealthCheck: &custom}}
got := p.HealthCheckOrDefault()
if !reflect.DeepEqual(got, custom) {
t.Errorf("HealthCheckOrDefault() = %+v, want %+v", got, custom)
}
})
t.Run("does not mutate the original spec", func(t *testing.T) {
t.Parallel()
hc := &HealthCheckSpec{ProbeURL: "http://internal/probe"}
p := &Proxy{Spec: ProxySpec{HealthCheck: hc}}
_ = p.HealthCheckOrDefault()
if hc.IntervalSeconds != 0 {
t.Errorf("original HealthCheckSpec was mutated: IntervalSeconds = %d, want 0", hc.IntervalSeconds)
}
})
}
func TestMaxLeasesOrDefault(t *testing.T) {
t.Parallel()
zero := int32(0)
seven := int32(7)
tests := []struct {
name string
spec ProxySpec
want int32
}{
{name: "unset falls back to default", spec: ProxySpec{}, want: DefaultMaxLeases},
{name: "explicit zero is preserved (unleasable)", spec: ProxySpec{MaxLeases: &zero}, want: 0},
{name: "explicit non-zero is preserved", spec: ProxySpec{MaxLeases: &seven}, want: 7},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
p := &Proxy{Spec: tc.spec}
if got := p.MaxLeasesOrDefault(); got != tc.want {
t.Errorf("MaxLeasesOrDefault() = %d, want %d", got, tc.want)
}
})
}
}