package controller import ( "regexp" "testing" crawlv1alpha1 "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/api/v1alpha1" ) func hashProxy(mut ...func(*crawlv1alpha1.Proxy)) *crawlv1alpha1.Proxy { p := &crawlv1alpha1.Proxy{ Spec: crawlv1alpha1.ProxySpec{ Mode: crawlv1alpha1.ModeManaged, Provider: "stub", Port: 3128, Placement: &crawlv1alpha1.PlacementSpec{ Zone: "europe-west1-b", MachineType: "e2-micro", }, }, } for _, m := range mut { m(p) } return p } func TestSpecHash_stability(t *testing.T) { t.Parallel() p := hashProxy() h1 := specHash(p, "cloud-init-content") h2 := specHash(p.DeepCopy(), "cloud-init-content") if h1 != h2 { t.Errorf("same input hashed differently: %s vs %s", h1, h2) } if !regexp.MustCompile(`^[0-9a-f]{64}$`).MatchString(h1) { t.Errorf("hash %q is not hex SHA-256", h1) } // Fields outside the replacement set must not affect the hash — that is // the whole point of an explicit hash-input struct. q := hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Attributes = map[string]string{"geo": "eu"} five := int32(5) p.Spec.MaxLeases = &five p.Spec.HealthCheck = &crawlv1alpha1.HealthCheckSpec{IntervalSeconds: 60} }) if specHash(q, "cloud-init-content") != h1 { t.Error("non-replacement spec fields changed the hash") } } func TestSpecHash_normalization(t *testing.T) { t.Parallel() nilPlacement := hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Placement = nil }) emptyPlacement := hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Placement = &crawlv1alpha1.PlacementSpec{} }) if specHash(nilPlacement, "") != specHash(emptyPlacement, "") { t.Error("nil and empty placement hashed differently") } // An unset port and an explicit default port mean the same instance. unsetPort := hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Port = 0 }) defaultPort := hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Port = crawlv1alpha1.DefaultPort }) if specHash(unsetPort, "") != specHash(defaultPort, "") { t.Error("unset port and explicit default port hashed differently") } } func TestSpecHash_sensitivity(t *testing.T) { t.Parallel() base := specHash(hashProxy(), "cloud-init") tests := []struct { name string proxy *crawlv1alpha1.Proxy cloudInit string }{ { name: "port change", proxy: hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Port = 8080 }), cloudInit: "cloud-init", }, { name: "zone change", proxy: hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Placement.Zone = "us-east1-c" }), cloudInit: "cloud-init", }, { name: "machine type change", proxy: hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Placement.MachineType = "e2-small" }), cloudInit: "cloud-init", }, { name: "image change", proxy: hashProxy(func(p *crawlv1alpha1.Proxy) { p.Spec.Placement.Image = "debian-13" }), cloudInit: "cloud-init", }, { name: "resolved cloud-init change (secret rotation)", proxy: hashProxy(), cloudInit: "rotated-cloud-init", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() if specHash(tc.proxy, tc.cloudInit) == base { t.Error("hash did not change") } }) } }