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) } } type fakeBinds struct{ m map[int][]string } func (f *fakeBinds) Guests() map[int][]string { return f.m } func (f *fakeBinds) Remove(vmid int, id string) error { var kept []string for _, x := range f.m[vmid] { if x != id { kept = append(kept, x) } } f.m[vmid] = kept return nil } // v0.159.1: the signed decommission drops the drive from EVERY guest's bind record, as the self-serve path does — and // only that drive. RED-PROOF (felhom.eu audits/agent-allowlist-rotation-2026-10-17/P1/redproof-bindprune.txt): with // the prune loop removed, the record still holds the drive and this test fails. func TestDecommissionExecutor_PrunesTheGuestBindRecord(t *testing.T) { fd := &fakeDecommissioner{} fb := &fakeBinds{m: map[int][]string{9201: {"uuid:keep", "uuid:gone"}, 9202: {"uuid:gone"}}} ex := NewDecommissionExecutor(fd, nil) ex.SetGuestBinds(fb) params, _ := json.Marshal(decommissionParams{DurableID: "uuid:gone"}) if err := ex.Execute(context.Background(), opDecommission, params); err != nil { t.Fatal(err) } if got := fb.m[9201]; len(got) != 1 || got[0] != "uuid:keep" { t.Fatalf("9201 record = %v, want [uuid:keep]", got) } if got := fb.m[9202]; len(got) != 0 { t.Fatalf("9202 record = %v, want empty", got) } }