package companion import "testing" func retirementInstance(t *testing.T, withActor bool) (*Instance, RunKey) { t.Helper() key := runKey() i, err := NewInstance(key, prepared(t), 1, 1) if err != nil { t.Fatal(err) } if err := i.Apply(Event{Run: key, Sequence: 1, Kind: "room_ready", RoomGeneration: 1}, nil); err != nil { t.Fatal(err) } if withActor { for n, kind := range []string{"native_created", "owner_bound", "activate"} { if err := i.Apply(Event{Run: key, Sequence: uint64(n + 2), Kind: kind, RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}, map[uint16]bool{}); err != nil { t.Fatal(err) } } } seq := i.Status().LastSequence + 1 if err := i.Apply(Event{Run: key, Sequence: seq, Kind: "retire", RoomGeneration: 1}, nil); err != nil { t.Fatal(err) } return i, key } func TestCompanionDisposedRequiresExactActorAndRoom(t *testing.T) { i, key := retirementInstance(t, true) before := i.Status() valid := Event{Run: key, Sequence: before.LastSequence + 1, Kind: "disposed", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7} for _, field := range []string{"key", "generation", "room", "empty"} { e := valid switch field { case "key": e.ObjectKey++ case "generation": e.ObjectGeneration++ case "room": e.RoomGeneration++ case "empty": e.ObjectKey = 0 e.ObjectGeneration = 0 } if err := i.Apply(e, nil); err == nil { t.Fatalf("accepted mismatched %s disposal", field) } if i.Status() != before || !i.Owns(key, 24, 7) { t.Fatalf("invalid disposal revoked owner: %s", field) } } if err := i.Apply(valid, nil); err != nil { t.Fatal(err) } if i.Owns(key, 24, 7) || i.Status().Phase != Disposed { t.Fatal("valid disposal did not close") } } func TestCompanionLateCreationTracksCleanupWithoutAuthorization(t *testing.T) { i, key := retirementInstance(t, false) e := Event{Run: key, Sequence: 3, Kind: "native_created", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7} if err := i.Apply(e, map[uint16]bool{}); err != nil { t.Fatal("late creation lost:", err) } if i.Status().Phase != Retiring || i.Owns(key, 24, 7) { t.Fatal("late creation resurrected cancelled run") } if err := i.Apply(e, map[uint16]bool{}); err != nil { t.Fatal("exact duplicate should be idempotent", err) } if err := i.Apply(Event{Run: key, Sequence: 4, Kind: "owner_bound", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}, nil); err == nil { t.Fatal("late actor authorized") } if err := i.Apply(Event{Run: key, Sequence: 4, Kind: "disposed", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7}, nil); err != nil { t.Fatal(err) } if i.Status().Phase != Disposed { t.Fatal(i.Status()) } } func TestCompanionLateCreationCannotReplaceExistingActor(t *testing.T) { i, key := retirementInstance(t, true) before := i.Status() e := Event{Run: key, Sequence: before.LastSequence + 1, Kind: "native_created", RoomGeneration: 1, ObjectKey: 25, ObjectGeneration: 8} if err := i.Apply(e, map[uint16]bool{}); err == nil { t.Fatal("replaced retiring actor") } if i.Status() != before { t.Fatal("rejected late creation changed state") } } func TestCompanionCancellationWithoutActorRequiresEmptyDisposal(t *testing.T) { i, key := retirementInstance(t, false) before := i.Status() e := Event{Run: key, Sequence: 3, Kind: "disposed", RoomGeneration: 1, ObjectKey: 24, ObjectGeneration: 7} if err := i.Apply(e, nil); err == nil { t.Fatal("undisclosed actor disposal accepted") } if i.Status() != before { t.Fatal("bad disposal changed state") } e.ObjectKey = 0 e.ObjectGeneration = 0 if err := i.Apply(e, nil); err != nil { t.Fatal(err) } if i.Status().Phase != Disposed { t.Fatal(i.Status()) } }