@@ -41,6 +41,8 @@ type mockControlServer struct {
4141 deletedActors []string
4242 actorTemplates map [string ]bool
4343 deletedTemplates []string
44+ crashedActor string
45+ revertedActors []string
4446}
4547
4648// noSecrets is a SecretResolver for tests: it never finds a key and never touches a cluster.
@@ -79,6 +81,9 @@ func (m *mockControlServer) CreateActor(ctx context.Context, req *ateapipb.Creat
7981 if req .Actor != nil && req .Actor .Metadata != nil {
8082 name = req .Actor .Metadata .Name
8183 }
84+ if name == m .crashedActor {
85+ return nil , status .Error (codes .AlreadyExists , "actor exists" )
86+ }
8287 m .createdActors = append (m .createdActors , name )
8388 return & ateapipb.Actor {
8489 Metadata : & ateapipb.ResourceMetadata {Name : name },
@@ -123,6 +128,22 @@ func (m *mockControlServer) SuspendActor(ctx context.Context, req *ateapipb.Susp
123128}
124129
125130
131+ func (m * mockControlServer ) GetActor (_ context.Context , req * ateapipb.GetActorRequest ) (* ateapipb.Actor , error ) {
132+ return & ateapipb.Actor {
133+ Metadata : & ateapipb.ResourceMetadata {Name : req .GetActor ().GetName ()},
134+ Status : & ateapipb.ActorStatus {State : ateapipb .ActorState_ACTOR_STATE_CRASHED },
135+ }, nil
136+ }
137+
138+ func (m * mockControlServer ) RevertActor (_ context.Context , req * ateapipb.RevertActorRequest ) (* ateapipb.RevertActorResponse , error ) {
139+ name := req .GetActor ().GetName ()
140+ m .revertedActors = append (m .revertedActors , name )
141+ return & ateapipb.RevertActorResponse {Actor : & ateapipb.Actor {
142+ Metadata : & ateapipb.ResourceMetadata {Name : name },
143+ Status : & ateapipb.ActorStatus {State : ateapipb .ActorState_ACTOR_STATE_SUSPENDED },
144+ }}, nil
145+ }
146+
126147func (m * mockControlServer ) DeleteActor (ctx context.Context , req * ateapipb.DeleteActorRequest ) (* ateapipb.Actor , error ) {
127148 name := req .GetActor ().GetName ()
128149 m .deletedActors = append (m .deletedActors , name )
@@ -459,3 +480,34 @@ func TestReconcileDelete_RemovesActorAndTemplates(t *testing.T) {
459480 }
460481 }
461482}
483+
484+ func TestEnsureActor_RevertsCrashedActor (t * testing.T ) {
485+ lis , err := net .Listen ("tcp" , "127.0.0.1:0" )
486+ if err != nil {
487+ t .Fatalf ("failed to listen: %v" , err )
488+ }
489+ defer lis .Close ()
490+
491+ mockSrv := & mockControlServer {crashedActor : "job" }
492+ grpcServer := grpc .NewServer ()
493+ ateapipb .RegisterControlServer (grpcServer , mockSrv )
494+ go grpcServer .Serve (lis )
495+ defer grpcServer .Stop ()
496+
497+ client , err := substrate .NewClient (lis .Addr ().String (), grpc .WithTransportCredentials (insecure .NewCredentials ()))
498+ if err != nil {
499+ t .Fatalf ("failed to create substrate client: %v" , err )
500+ }
501+ defer client .Close ()
502+
503+ actor , err := client .EnsureActor (context .Background (), "default" , "job" , "default" , "job-tmpl-0a1b2c3d" )
504+ if err != nil {
505+ t .Fatalf ("EnsureActor failed: %v" , err )
506+ }
507+ if got := actor .GetStatus ().GetState (); got != ateapipb .ActorState_ACTOR_STATE_SUSPENDED {
508+ t .Errorf ("actor state = %v, want SUSPENDED" , got )
509+ }
510+ if len (mockSrv .revertedActors ) != 1 || len (mockSrv .deletedActors ) != 0 {
511+ t .Errorf ("crashed actor: reverted %v, deleted %v; want reverted only" , mockSrv .revertedActors , mockSrv .deletedActors )
512+ }
513+ }
0 commit comments