@@ -23,6 +23,7 @@ import (
2323
2424 "github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
2525 "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest"
26+ "github.com/agent-substrate/substrate/pkg/proto/ateapipb"
2627 corev1 "k8s.io/api/core/v1"
2728 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829 "k8s.io/apimachinery/pkg/util/wait"
@@ -154,3 +155,62 @@ func TestSyncer_Lifecycle(t *testing.T) {
154155 t .Fatalf ("Worker still found in Redis after deletion: %v" , err )
155156 }
156157}
158+
159+ func TestSyncer_DeleteBoundWorker_ClearsActor (t * testing.T ) {
160+ ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
161+ defer cancel ()
162+
163+ persistence , fakeK8s , cleanup := setupSyncerTest (t , ctx )
164+ defer cleanup ()
165+
166+ ns , pool , pod , ip := "ns-orphan" , "pool1" , "worker-orphan" , "10.0.0.1"
167+ if _ , err := fakeK8s .CoreV1 ().Pods (ns ).Create (ctx , & corev1.Pod {
168+ ObjectMeta : metav1.ObjectMeta {Name : pod , Namespace : ns ,
169+ Labels : map [string ]string {workerPodLabel : pool }},
170+ Status : corev1.PodStatus {Phase : corev1 .PodRunning , PodIP : ip ,
171+ PodIPs : []corev1.PodIP {{IP : ip }}},
172+ }, metav1.CreateOptions {}); err != nil {
173+ t .Fatalf ("create pod: %v" , err )
174+ }
175+ if err := wait .PollUntilContextTimeout (ctx , 50 * time .Millisecond , 2 * time .Second , true , func (c context.Context ) (bool , error ) {
176+ _ , gerr := persistence .GetWorker (c , ns , pool , pod )
177+ return gerr == nil , nil
178+ }); err != nil {
179+ t .Fatalf ("worker row not materialised: %v" , err )
180+ }
181+ actorID := "actor-orphan"
182+ if err := persistence .CreateActor (ctx , & ateapipb.Actor {
183+ ActorId : actorID , ActorTemplateNamespace : ns , ActorTemplateName : "tmpl" ,
184+ Status : ateapipb .Actor_STATUS_RUNNING ,
185+ AteomPodNamespace : ns , AteomPodName : pod , AteomPodIp : ip ,
186+ LastSnapshot : "gs://snapshots/last" , InProgressSnapshot : "gs://snapshots/partial" ,
187+ }); err != nil {
188+ t .Fatalf ("create actor: %v" , err )
189+ }
190+ w , _ := persistence .GetWorker (ctx , ns , pool , pod )
191+ w .ActorId , w .ActorNamespace , w .ActorTemplate = actorID , ns , "tmpl"
192+ if err := persistence .UpdateWorker (ctx , w , w .Version ); err != nil {
193+ t .Fatalf ("update worker: %v" , err )
194+ }
195+
196+ if err := fakeK8s .CoreV1 ().Pods (ns ).Delete (ctx , pod , metav1.DeleteOptions {}); err != nil {
197+ t .Fatalf ("delete pod: %v" , err )
198+ }
199+ var got * ateapipb.Actor
200+ if err := wait .PollUntilContextTimeout (ctx , 50 * time .Millisecond , 2 * time .Second , true , func (c context.Context ) (bool , error ) {
201+ a , gerr := persistence .GetActor (c , actorID )
202+ if gerr != nil {
203+ return false , gerr
204+ }
205+ got = a
206+ return a .GetStatus () == ateapipb .Actor_STATUS_SUSPENDED , nil
207+ }); err != nil {
208+ t .Fatalf ("actor not reset to SUSPENDED: %v" , err )
209+ }
210+ if got .AteomPodName != "" || got .AteomPodNamespace != "" || got .AteomPodIp != "" || got .InProgressSnapshot != "" {
211+ t .Errorf ("bind fields not cleared: %+v" , got )
212+ }
213+ if got .LastSnapshot == "" {
214+ t .Errorf ("LastSnapshot must be preserved" )
215+ }
216+ }
0 commit comments