Files
egress-proxies-operator/internal/provider/config_test.go
Jan Novak ff859ebd84 Add the Kubernetes pod provider (replaces the removed mock)
Create/Get/Delete/ListByTag against real corev1.Pod objects in the same
cluster the operator runs in, running an ubuntu/squid container -- picked
by actually checking Docker Hub metadata (Canonical-published, rebuilt
the same day this was decided, 50M+ pulls) rather than guessing an image
reference. It's a public image, so kind nodes pull it directly with no
build/load step.

providerID is "<namespace>/<podName>", parsed via
cache.SplitMetaNamespaceKey -- the same self-contained-providerID
reasoning the plan already calls for on the GCP provider's zone-qualified
IDs. Pod state maps to InstanceState with Succeeded/Failed/Unknown all
collapsing to Terminated, since the reconciler already treats Stopped and
Terminated identically; Running-without-PodIP maps to Provisioning so an
empty IP is never published.

The client is built internally via ctrl.GetConfig() (in-cluster or local
kubeconfig, whichever applies), not threaded through the registry
Constructor signature -- this is what lets `make run` against a local
kind cluster and running in-cluster share the exact same code path with
no provider-specific wiring in cmd/main.go. New() is deliberately
untested (0% coverage): it's the one function that must never run under
`go test`, since it would happily connect to whatever cluster the
developer's kubeconfig points at. Tests construct Provider via an
unexported newWithClient(client, cfg) instead.

ListByTag lists Pods across every namespace (orphan GC needs to find
every tagged Pod regardless of where it landed), which means this
provider's RBAC has to be a ClusterRole rather than namespace-scoped --
flagged now, wired in Step 10.

provider.Config gains KubernetesConfig (replacing MockConfig) and drops
the FailWith*/fault-injection surface entirely, since that need is now
served by a small in-test stub Provider for reconciler tests (Step 4),
not a config-driven mechanism on a real provider package.

Tests use sigs.k8s.io/controller-runtime/pkg/client/fake -- real Pod
objects, the real client.Client interface -- at 77.6% coverage.
make test green across the whole repo.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-08 00:17:50 +02:00

164 lines
3.5 KiB
Go

package provider
import (
"strings"
"testing"
)
func TestLoadConfig_valid(t *testing.T) {
t.Parallel()
data := []byte(`
providers:
- name: kubernetes
type: kubernetes
kubernetes:
image: ubuntu/squid:6.6-24.04_edge
- name: gcp-eu
type: gcp
gcp:
project: my-project
network: custom-net
`)
cfg, err := LoadConfig(data)
if err != nil {
t.Fatalf("LoadConfig() error = %v, want nil", err)
}
if len(cfg.Providers) != 2 {
t.Fatalf("len(cfg.Providers) = %d, want 2", len(cfg.Providers))
}
if cfg.Providers[0].Kubernetes == nil || cfg.Providers[0].Kubernetes.Image != "ubuntu/squid:6.6-24.04_edge" {
t.Errorf("providers[0].kubernetes = %+v, want Image=ubuntu/squid:6.6-24.04_edge", cfg.Providers[0].Kubernetes)
}
if cfg.Providers[1].GCP == nil || cfg.Providers[1].GCP.Project != "my-project" {
t.Errorf("providers[1].gcp = %+v, want Project=my-project", cfg.Providers[1].GCP)
}
}
func TestLoadConfig_invalid(t *testing.T) {
t.Parallel()
tests := []struct {
name string
yaml string
wantErrSub string
}{
{
name: "empty providers list",
yaml: `providers: []`,
wantErrSub: "at least one provider",
},
{
name: "missing name",
yaml: `
providers:
- type: kubernetes`,
wantErrSub: "name is required",
},
{
name: "duplicate name",
yaml: `
providers:
- name: kubernetes
type: kubernetes
- name: kubernetes
type: kubernetes`,
wantErrSub: "duplicate provider name",
},
{
name: "unknown type",
yaml: `
providers:
- name: p1
type: azure`,
wantErrSub: `unknown provider type "azure"`,
},
{
name: "missing type",
yaml: `
providers:
- name: p1`,
wantErrSub: "type is required",
},
{
name: "gcp missing project",
yaml: `
providers:
- name: gcp-eu
type: gcp`,
wantErrSub: "gcp.project is required",
},
{
name: "gcp with empty project",
yaml: `
providers:
- name: gcp-eu
type: gcp
gcp:
project: ""`,
wantErrSub: "gcp.project is required",
},
{
name: "kubernetes type with gcp block",
yaml: `
providers:
- name: p1
type: kubernetes
gcp:
project: my-project`,
wantErrSub: "type is kubernetes but a gcp block is set",
},
{
name: "gcp type with kubernetes block",
yaml: `
providers:
- name: p1
type: gcp
gcp:
project: my-project
kubernetes:
image: custom-image`,
wantErrSub: "type is gcp but a kubernetes block is set",
},
{
name: "strict mode rejects unknown top-level key",
yaml: `
providers:
- name: p1
type: kubernetes
extraneous: true`,
wantErrSub: "parsing providers config",
},
{
name: "strict mode rejects unknown provider key",
yaml: `
providers:
- name: p1
type: kubernetes
bogus: true`,
wantErrSub: "parsing providers config",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
_, err := LoadConfig([]byte(tc.yaml))
if err == nil {
t.Fatalf("LoadConfig() error = nil, want error containing %q", tc.wantErrSub)
}
if !strings.Contains(err.Error(), tc.wantErrSub) {
t.Errorf("LoadConfig() error = %q, want substring %q", err.Error(), tc.wantErrSub)
}
})
}
}
func TestLoadConfigFile_missingFile(t *testing.T) {
t.Parallel()
_, err := LoadConfigFile("/nonexistent/providers.yaml")
if err == nil {
t.Fatal("LoadConfigFile() error = nil, want error")
}
if !strings.Contains(err.Error(), "reading providers config") {
t.Errorf("LoadConfigFile() error = %q, want substring %q", err.Error(), "reading providers config")
}
}