package signedjobs import ( "context" "encoding/json" "testing" ) type fakeDecommissioner struct { got string err error } func (f *fakeDecommissioner) SetDecommissioned(durableID string) error { f.got = durableID return f.err } func TestDecommissionExecutor_HappyPath(t *testing.T) { fd := &fakeDecommissioner{} ex := NewDecommissionExecutor(fd, nil) err := ex.Execute(context.Background(), opDecommission, json.RawMessage(`{"durable_id":"uuid:abc-123"}`)) if err != nil { t.Fatalf("unexpected error: %v", err) } if fd.got != "uuid:abc-123" { t.Errorf("SetDecommissioned got %q, want uuid:abc-123", fd.got) } } func TestDecommissionExecutor_NotOurOp(t *testing.T) { fd := &fakeDecommissioner{} ex := NewDecommissionExecutor(fd, nil) if err := ex.Execute(context.Background(), "storage_wipe", json.RawMessage(`{}`)); err != ErrNoExecutor { t.Errorf("want ErrNoExecutor for a foreign op, got %v", err) } if fd.got != "" { t.Errorf("SetDecommissioned must not be called for a foreign op (got %q)", fd.got) } } func TestDecommissionExecutor_RefusesUnbound(t *testing.T) { fd := &fakeDecommissioner{} ex := NewDecommissionExecutor(fd, nil) if err := ex.Execute(context.Background(), opDecommission, json.RawMessage(`{"durable_id":""}`)); err == nil { t.Error("want error for an empty durable_id (unbound decommission)") } if fd.got != "" { t.Errorf("SetDecommissioned must not be called for an unbound op (got %q)", fd.got) } } func TestDecommissionExecutor_NoIntentStore(t *testing.T) { ex := NewDecommissionExecutor(nil, nil) if err := ex.Execute(context.Background(), opDecommission, json.RawMessage(`{"durable_id":"uuid:x"}`)); err == nil { t.Error("want a hard error (not silent success) when no intent store is wired") } } // chainProbe records whether it was reached and what it returns. type chainProbe struct { owns string called bool ret error } func (c *chainProbe) Execute(_ context.Context, op string, _ json.RawMessage) error { c.called = true if op == c.owns { return c.ret } return ErrNoExecutor } func TestExecutorChain_DispatchesToOwner(t *testing.T) { a := &chainProbe{owns: "storage_wipe"} b := &chainProbe{owns: "decommission"} chain := ExecutorChain{a, b} if err := chain.Execute(context.Background(), "decommission", nil); err != nil { t.Fatalf("unexpected error: %v", err) } if !b.called { t.Error("owner (decommission) executor was not reached") } // An op no sub-executor owns → ErrNoExecutor (left queued). if err := chain.Execute(context.Background(), "guest_destroy", nil); err != ErrNoExecutor { t.Errorf("want ErrNoExecutor for an unowned op, got %v", err) } }