@@ -18,47 +18,75 @@ import (
1818 "context"
1919 "errors"
2020 "fmt"
21+ "maps"
2122 "testing"
2223 "time"
2324
2425 "github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
2526 "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest"
27+ atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
28+ atefake "github.com/agent-substrate/substrate/pkg/client/clientset/versioned/fake"
29+ "github.com/agent-substrate/substrate/pkg/client/informers/externalversions"
2630 "github.com/agent-substrate/substrate/pkg/proto/ateapipb"
2731 corev1 "k8s.io/api/core/v1"
2832 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33+ "k8s.io/apimachinery/pkg/runtime"
2934 "k8s.io/apimachinery/pkg/util/wait"
3035 "k8s.io/client-go/kubernetes/fake"
3136)
3237
3338// setupSyncerTest sets up a real store with fake Redis and a fake K8s client with informer.
34- func setupSyncerTest (t * testing.T , ctx context.Context ) (store.Interface , * fake.Clientset , func ()) {
39+ func setupSyncerTest (t * testing.T , ctx context.Context , initPools ... * atev1alpha1. WorkerPool ) (store.Interface , * fake. Clientset , * atefake .Clientset , func ()) {
3540 t .Helper ()
3641
3742 persistence , cleanup := storetest .SetupTestStore (t )
3843
3944 fakeK8s := fake .NewSimpleClientset ()
4045 workerFactory , workerInformer := WorkerPodInformer (fakeK8s )
4146
42- syncer := NewWorkerPoolSyncer (persistence , workerInformer )
47+ objects := make ([]runtime.Object , len (initPools ))
48+ for i , pool := range initPools {
49+ objects [i ] = pool
50+ }
51+ //nolint:staticcheck // NewSimpleClientset is the only available fake clientset for versioned CRDs.
52+ fakeAte := atefake .NewSimpleClientset (objects ... )
53+ ateInformerFactory := externalversions .NewSharedInformerFactory (fakeAte , 0 )
54+ workerPoolLister := ateInformerFactory .Api ().V1alpha1 ().WorkerPools ().Lister ()
55+
56+ syncer := NewWorkerPoolSyncer (persistence , workerInformer , workerPoolLister )
4357 syncer .Start (ctx )
4458
4559 workerFactory .Start (ctx .Done ())
60+ ateInformerFactory .Start (ctx .Done ())
61+
4662 workerFactory .WaitForCacheSync (ctx .Done ())
63+ ateInformerFactory .WaitForCacheSync (ctx .Done ())
4764
48- return persistence , fakeK8s , cleanup
65+ return persistence , fakeK8s , fakeAte , cleanup
4966}
5067
5168func TestSyncer_Lifecycle (t * testing.T ) {
5269 ctx , cancel := context .WithCancel (context .Background ())
5370 defer cancel ()
5471
55- persistence , fakeK8s , cleanup := setupSyncerTest (t , ctx )
56- defer cleanup ()
57-
5872 ns := "ns-syncer-lifecycle"
5973 podName := "worker-unit-1"
6074 poolName := "pool1"
6175
76+ pool := & atev1alpha1.WorkerPool {
77+ ObjectMeta : metav1.ObjectMeta {
78+ Name : poolName ,
79+ Namespace : ns ,
80+ Labels : map [string ]string {"foo" : "bar" },
81+ },
82+ Spec : atev1alpha1.WorkerPoolSpec {
83+ SandboxClass : "gvisor" ,
84+ },
85+ }
86+
87+ persistence , fakeK8s , _ , cleanup := setupSyncerTest (t , ctx , pool )
88+ defer cleanup ()
89+
6290 // 1. Verify no workers in Redis initially
6391 workers , err := persistence .ListWorkers (context .Background ())
6492 if err != nil {
@@ -127,7 +155,16 @@ func TestSyncer_Lifecycle(t *testing.T) {
127155 }
128156 return false , err
129157 }
130- return w .Ip == "127.0.0.1" , nil
158+ if w .Ip != "127.0.0.1" {
159+ return false , nil
160+ }
161+ if w .SandboxClass != "gvisor" {
162+ return false , fmt .Errorf ("expected SandboxClass gvisor, got %q" , w .SandboxClass )
163+ }
164+ if ! maps .Equal (w .Labels , map [string ]string {"foo" : "bar" }) {
165+ return false , fmt .Errorf ("expected labels map[foo:bar], got %v" , w .Labels )
166+ }
167+ return true , nil
131168 })
132169 if err != nil {
133170 t .Fatalf ("Worker not found in Redis after update: %v" , err )
@@ -159,10 +196,20 @@ func TestSyncer_DeleteBoundWorker_ClearsActor(t *testing.T) {
159196 ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
160197 defer cancel ()
161198
162- persistence , fakeK8s , cleanup := setupSyncerTest (t , ctx )
163- defer cleanup ()
164-
165199 ns , pool , pod , ip := "ns-orphan" , "pool1" , "worker-orphan" , "10.0.0.1"
200+ workerPool := & atev1alpha1.WorkerPool {
201+ ObjectMeta : metav1.ObjectMeta {
202+ Name : pool ,
203+ Namespace : ns ,
204+ Labels : map [string ]string {"foo" : "bar" },
205+ },
206+ Spec : atev1alpha1.WorkerPoolSpec {
207+ SandboxClass : "gvisor" ,
208+ },
209+ }
210+
211+ persistence , fakeK8s , _ , cleanup := setupSyncerTest (t , ctx , workerPool )
212+ defer cleanup ()
166213 if _ , err := fakeK8s .CoreV1 ().Pods (ns ).Create (ctx ,
167214 & corev1.Pod {
168215 ObjectMeta : metav1.ObjectMeta {
@@ -240,3 +287,76 @@ func TestSyncer_DeleteBoundWorker_ClearsActor(t *testing.T) {
240287 t .Errorf ("External SnapshotUriPrefix must be preserved" )
241288 }
242289}
290+
291+ func TestSyncer_OmittedFields (t * testing.T ) {
292+ ctx , cancel := context .WithCancel (context .Background ())
293+ defer cancel ()
294+
295+ ns := "ns-syncer-omitted"
296+ podName := "worker-unit-1"
297+ poolName := "pool1"
298+
299+ // Create a pool with omitted sandbox class and no labels
300+ pool := & atev1alpha1.WorkerPool {
301+ ObjectMeta : metav1.ObjectMeta {
302+ Name : poolName ,
303+ Namespace : ns ,
304+ },
305+ Spec : atev1alpha1.WorkerPoolSpec {
306+ // Spec has no SandboxClass and no Labels
307+ },
308+ }
309+
310+ persistence , fakeK8s , _ , cleanup := setupSyncerTest (t , ctx , pool )
311+ defer cleanup ()
312+
313+ // Create a pod
314+ pod := & corev1.Pod {
315+ ObjectMeta : metav1.ObjectMeta {
316+ Name : podName ,
317+ Namespace : ns ,
318+ UID : "08675309-4a65-6e6e-7973-6e756d626572" ,
319+ Labels : map [string ]string {
320+ workerPodLabel : poolName ,
321+ },
322+ },
323+ Spec : corev1.PodSpec {
324+ NodeName : "node1" ,
325+ Containers : []corev1.Container {{Name : "main" , Image : "nginx" }},
326+ },
327+ Status : corev1.PodStatus {
328+ Phase : corev1 .PodRunning ,
329+ PodIP : "127.0.0.1" ,
330+ PodIPs : []corev1.PodIP {{IP : "127.0.0.1" }},
331+ },
332+ }
333+
334+ _ , err := fakeK8s .CoreV1 ().Pods (ns ).Create (context .Background (), pod , metav1.CreateOptions {})
335+ if err != nil {
336+ t .Fatalf ("failed to create pod: %v" , err )
337+ }
338+
339+ // Verify that it is created in Redis with empty SandboxClass and empty Labels
340+ err = wait .PollUntilContextTimeout (context .Background (), 100 * time .Millisecond , 2 * time .Second , true , func (ctx context.Context ) (bool , error ) {
341+ w , err := persistence .GetWorker (ctx , ns , poolName , podName )
342+ if err != nil {
343+ if errors .Is (err , store .ErrNotFound ) {
344+ return false , nil
345+ }
346+ return false , err
347+ }
348+ if w .Ip != "127.0.0.1" {
349+ return false , nil
350+ }
351+ if w .SandboxClass != "" {
352+ return false , fmt .Errorf ("expected SandboxClass to be empty, got %q" , w .SandboxClass )
353+ }
354+ if len (w .Labels ) != 0 {
355+ return false , fmt .Errorf ("expected labels to be empty, got %v" , w .Labels )
356+ }
357+ return true , nil
358+ })
359+ if err != nil {
360+ t .Fatalf ("Worker state check failed: %v" , err )
361+ }
362+ }
0 commit comments