44 "testing"
55
66 "github.com/stretchr/testify/assert"
7+ "github.com/stretchr/testify/require"
78 corev1 "k8s.io/api/core/v1"
89 "k8s.io/apimachinery/pkg/api/resource"
910 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -12,7 +13,7 @@ import (
1213)
1314
1415func TestBuildStatefulSet_NameNamespaceLabels (t * testing.T ) {
15- sts := BuildStatefulSet (minimalInstance ())
16+ sts := BuildStatefulSet (minimalInstance (), nil )
1617 assert .Equal (t , "demo" , sts .Name )
1718 assert .Equal (t , "agents" , sts .Namespace )
1819 assert .Equal (t , "hermes-agent" , sts .Labels ["app.kubernetes.io/name" ])
@@ -23,15 +24,15 @@ func TestBuildStatefulSet_ContainerImage(t *testing.T) {
2324 inst := minimalInstance ()
2425 inst .Spec .Image .Repository = "ghcr.io/stubbi/hermes-agent"
2526 inst .Spec .Image .Tag = "v1.0.0"
26- sts := BuildStatefulSet (inst )
27+ sts := BuildStatefulSet (inst , nil )
2728 require := sts .Spec .Template .Spec .Containers
2829 assert .Len (t , require , 1 )
2930 assert .Equal (t , "ghcr.io/stubbi/hermes-agent:v1.0.0" , require [0 ].Image )
3031 assert .Equal (t , corev1 .PullIfNotPresent , require [0 ].ImagePullPolicy , "explicit default" )
3132}
3233
3334func TestBuildStatefulSet_ExplicitK8sDefaults (t * testing.T ) {
34- sts := BuildStatefulSet (minimalInstance ())
35+ sts := BuildStatefulSet (minimalInstance (), nil )
3536 podSpec := sts .Spec .Template .Spec
3637
3738 assert .NotNil (t , sts .Spec .RevisionHistoryLimit )
@@ -48,7 +49,7 @@ func TestBuildStatefulSet_ExplicitK8sDefaults(t *testing.T) {
4849}
4950
5051func TestBuildStatefulSet_HardenedPodSecurity (t * testing.T ) {
51- sts := BuildStatefulSet (minimalInstance ())
52+ sts := BuildStatefulSet (minimalInstance (), nil )
5253 pc := sts .Spec .Template .Spec .SecurityContext
5354 require := sts .Spec .Template .Spec .Containers [0 ].SecurityContext
5455 assert .NotNil (t , pc .RunAsNonRoot )
@@ -61,7 +62,7 @@ func TestBuildStatefulSet_HardenedPodSecurity(t *testing.T) {
6162}
6263
6364func TestBuildStatefulSet_VolumesAndMounts (t * testing.T ) {
64- sts := BuildStatefulSet (minimalInstance ())
65+ sts := BuildStatefulSet (minimalInstance (), nil )
6566 c := sts .Spec .Template .Spec .Containers [0 ]
6667
6768 mountNames := map [string ]string {}
@@ -92,7 +93,7 @@ func TestBuildStatefulSet_HonorsResources(t *testing.T) {
9293 corev1 .ResourceMemory : resource .MustParse ("512Mi" ),
9394 },
9495 }
95- sts := BuildStatefulSet (inst )
96+ sts := BuildStatefulSet (inst , nil )
9697 c := sts .Spec .Template .Spec .Containers [0 ]
9798 assert .Equal (t , resource .MustParse ("100m" ), c .Resources .Requests [corev1 .ResourceCPU ])
9899 assert .Equal (t , resource .MustParse ("512Mi" ), c .Resources .Limits [corev1 .ResourceMemory ])
@@ -107,7 +108,7 @@ func TestBuildStatefulSet_OverridesSecurityContexts(t *testing.T) {
107108 inst .Spec .Security .ContainerSecurityContext = & corev1.SecurityContext {
108109 ReadOnlyRootFilesystem : Ptr (false ),
109110 }
110- sts := BuildStatefulSet (inst )
111+ sts := BuildStatefulSet (inst , nil )
111112 assert .Equal (t , int64 (2000 ), * sts .Spec .Template .Spec .SecurityContext .RunAsUser )
112113 assert .False (t , * sts .Spec .Template .Spec .Containers [0 ].SecurityContext .ReadOnlyRootFilesystem )
113114}
@@ -122,7 +123,7 @@ func TestBuildStatefulSet_ProbeOverrides(t *testing.T) {
122123 FailureThreshold : 5 ,
123124 TimeoutSeconds : 2 ,
124125 }
125- sts := BuildStatefulSet (inst )
126+ sts := BuildStatefulSet (inst , nil )
126127 c := sts .Spec .Template .Spec .Containers [0 ]
127128 assert .NotNil (t , c .LivenessProbe )
128129 assert .Equal (t , int32 (30 ), c .LivenessProbe .InitialDelaySeconds )
@@ -143,7 +144,7 @@ func TestBuildStatefulSet_Scheduling(t *testing.T) {
143144 },
144145 },
145146 }
146- sts := BuildStatefulSet (inst )
147+ sts := BuildStatefulSet (inst , nil )
147148 podSpec := sts .Spec .Template .Spec
148149 assert .Equal (t , "ssd" , podSpec .NodeSelector ["disktype" ])
149150 assert .Len (t , podSpec .Tolerations , 1 )
@@ -158,7 +159,7 @@ func TestBuildStatefulSet_TopologySpread(t *testing.T) {
158159 {TopologyKey : "topology.kubernetes.io/zone" , WhenUnsatisfiable : corev1 .ScheduleAnyway , MaxSkew : 1 ,
159160 LabelSelector : & metav1.LabelSelector {MatchLabels : map [string ]string {"app" : "x" }}},
160161 }
161- sts := BuildStatefulSet (inst )
162+ sts := BuildStatefulSet (inst , nil )
162163 assert .Len (t , sts .Spec .Template .Spec .TopologySpreadConstraints , 1 )
163164}
164165
@@ -167,7 +168,7 @@ func TestBuildStatefulSet_InitContainersAndSidecars(t *testing.T) {
167168 inst := minimalInstance ()
168169 inst .Spec .InitContainers = []corev1.Container {{Name : "user-init" , Image : "alpine" }}
169170 inst .Spec .Sidecars = []corev1.Container {{Name : "user-side" , Image : "alpine" }}
170- sts := BuildStatefulSet (inst )
171+ sts := BuildStatefulSet (inst , nil )
171172 var sawInit , sawSide bool
172173 for _ , c := range sts .Spec .Template .Spec .InitContainers {
173174 if c .Name == "user-init" {
@@ -188,7 +189,7 @@ func TestBuildStatefulSet_ExtraVolumesAndMounts(t *testing.T) {
188189 inst := minimalInstance ()
189190 inst .Spec .ExtraVolumes = []corev1.Volume {{Name : "user-vol" , VolumeSource : corev1.VolumeSource {EmptyDir : & corev1.EmptyDirVolumeSource {}}}}
190191 inst .Spec .ExtraVolumeMounts = []corev1.VolumeMount {{Name : "user-vol" , MountPath : "/user" }}
191- sts := BuildStatefulSet (inst )
192+ sts := BuildStatefulSet (inst , nil )
192193 var sawVol , sawMount bool
193194 for _ , v := range sts .Spec .Template .Spec .Volumes {
194195 if v .Name == "user-vol" {
@@ -211,7 +212,7 @@ func TestBuildStatefulSet_EnvAndEnvFrom(t *testing.T) {
211212 inst .Spec .EnvFrom = []corev1.EnvFromSource {
212213 {SecretRef : & corev1.SecretEnvSource {LocalObjectReference : corev1.LocalObjectReference {Name : "user-secret" }}},
213214 }
214- sts := BuildStatefulSet (inst )
215+ sts := BuildStatefulSet (inst , nil )
215216 c := sts .Spec .Template .Spec .Containers [0 ]
216217 var sawEnv , sawEnvFrom bool
217218 for _ , e := range c .Env {
@@ -231,19 +232,19 @@ func TestBuildStatefulSet_EnvAndEnvFrom(t *testing.T) {
231232func TestBuildStatefulSet_ServiceAccountName (t * testing.T ) {
232233 t .Parallel ()
233234 inst := minimalInstance ()
234- sts := BuildStatefulSet (inst )
235+ sts := BuildStatefulSet (inst , nil )
235236 assert .Equal (t , "demo" , sts .Spec .Template .Spec .ServiceAccountName )
236237
237238 inst .Spec .Security .RBAC .ServiceAccountName = "byo-sa"
238- sts2 := BuildStatefulSet (inst )
239+ sts2 := BuildStatefulSet (inst , nil )
239240 assert .Equal (t , "byo-sa" , sts2 .Spec .Template .Spec .ServiceAccountName )
240241}
241242
242243func TestBuildStatefulSet_WorkspaceVolumeMounted (t * testing.T ) {
243244 t .Parallel ()
244245 inst := minimalInstance ()
245246 inst .Spec .Workspace .InitialFiles = []hermesv1.WorkspaceFile {{Path : "a.md" , Content : "x" }}
246- sts := BuildStatefulSet (inst )
247+ sts := BuildStatefulSet (inst , nil )
247248 var sawVol bool
248249 for _ , v := range sts .Spec .Template .Spec .Volumes {
249250 if v .Name == "workspace" && v .ConfigMap != nil && v .ConfigMap .Name == "demo-workspace" {
@@ -257,7 +258,7 @@ func TestBuildStatefulSet_CABundleConfigMapMounted(t *testing.T) {
257258 t .Parallel ()
258259 inst := minimalInstance ()
259260 inst .Spec .Security .CABundle = hermesv1.CABundleSpec {ConfigMapName : "corp-ca" , Key : "ca.crt" }
260- sts := BuildStatefulSet (inst )
261+ sts := BuildStatefulSet (inst , nil )
261262 var sawCA bool
262263 for _ , v := range sts .Spec .Template .Spec .Volumes {
263264 if v .Name == "ca-bundle" {
@@ -279,15 +280,15 @@ func TestBuildStatefulSet_Suspended(t *testing.T) {
279280 t .Parallel ()
280281 inst := minimalInstance ()
281282 inst .Spec .Suspended = true
282- sts := BuildStatefulSet (inst )
283+ sts := BuildStatefulSet (inst , nil )
283284 assert .NotNil (t , sts .Spec .Replicas )
284285 assert .Equal (t , int32 (0 ), * sts .Spec .Replicas )
285286}
286287
287288func TestBuildStatefulSet_NotSuspendedDefaultReplica (t * testing.T ) {
288289 t .Parallel ()
289290 inst := minimalInstance ()
290- sts := BuildStatefulSet (inst )
291+ sts := BuildStatefulSet (inst , nil )
291292 assert .NotNil (t , sts .Spec .Replicas )
292293 assert .Equal (t , int32 (1 ), * sts .Spec .Replicas )
293294}
@@ -299,7 +300,7 @@ func TestBuildStatefulSet_RuntimeInitContainersAppended(t *testing.T) {
299300 UV : hermesv1.UVSpec {Enabled : Ptr (true )},
300301 ExtraPipPackages : []string {"polars" },
301302 }
302- sts := BuildStatefulSet (inst )
303+ sts := BuildStatefulSet (inst , nil )
303304 names := []string {}
304305 for _ , c := range sts .Spec .Template .Spec .InitContainers {
305306 names = append (names , c .Name )
@@ -320,7 +321,7 @@ func TestBuildStatefulSet_GatewayEnvWired(t *testing.T) {
320321 },
321322 },
322323 }
323- sts := BuildStatefulSet (inst )
324+ sts := BuildStatefulSet (inst , nil )
324325 c := sts .Spec .Template .Spec .Containers [0 ]
325326 hasToken := false
326327 for _ , e := range c .Env {
@@ -343,7 +344,7 @@ func TestBuildStatefulSet_HonchoEnvWired(t *testing.T) {
343344 },
344345 },
345346 }
346- sts := BuildStatefulSet (inst )
347+ sts := BuildStatefulSet (inst , nil )
347348 c := sts .Spec .Template .Spec .Containers [0 ]
348349 byName := map [string ]corev1.EnvVar {}
349350 for _ , e := range c .Env {
@@ -357,7 +358,7 @@ func TestBuildStatefulSet_UVCacheVolume(t *testing.T) {
357358 t .Parallel ()
358359 inst := minimalInstance ()
359360 inst .Spec .Runtime = hermesv1.RuntimeSpec {UV : hermesv1.UVSpec {Enabled : Ptr (true )}}
360- sts := BuildStatefulSet (inst )
361+ sts := BuildStatefulSet (inst , nil )
361362 found := false
362363 for _ , v := range sts .Spec .Template .Spec .Volumes {
363364 if v .Name == "uv-cache" {
@@ -393,7 +394,18 @@ func TestBuildStatefulSet_IdempotentWithRuntimeGatewaysHoncho(t *testing.T) {
393394 APIKeySecretRef : & corev1.SecretKeySelector {LocalObjectReference : corev1.LocalObjectReference {Name : "honcho" }, Key : "api-key" },
394395 },
395396 }
396- a := BuildStatefulSet (inst )
397- b := BuildStatefulSet (inst )
397+ a := BuildStatefulSet (inst , nil )
398+ b := BuildStatefulSet (inst , nil )
398399 assert .Equal (t , a , b , "pure builder must be deterministic" )
399400}
401+
402+ func TestBuildStatefulSet_AcceptsInitContainers (t * testing.T ) {
403+ inst := minimalInstance ()
404+ initC := corev1.Container {Name : "init-restore" , Image : "restic/restic:0.16.4" }
405+ sts := BuildStatefulSet (inst , []corev1.Container {initC })
406+ require .NotNil (t , sts )
407+ // extraInits must come BEFORE operator-managed inits — restore writes to PVC
408+ // before runtime-init starts touching it.
409+ require .NotEmpty (t , sts .Spec .Template .Spec .InitContainers )
410+ assert .Equal (t , "init-restore" , sts .Spec .Template .Spec .InitContainers [0 ].Name )
411+ }
0 commit comments