using NUnit.Framework;
using ProjectM.Server;
using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Tests
{
///
/// Regression pin for the RPC-leak class the 2026-08-06 audit reproduced live: every RPC receiver in this
/// project gates itself behind RequireForUpdate on a scene-baked singleton, so in a scene missing that
/// singleton the receiver never runs and the request entity is never destroyed. Netcode's own stale-RPC
/// warning Consume()s but never destroys, and is compiled out of player builds — so the entities accumulated
/// forever, worst in a shipped build.
///
/// is the terminal drain. These tests pin the two halves of its contract:
/// an aged UNCONSUMED request is destroyed, and a CONSUMED one is left alone for its owning receiver.
///
public class StaleRpcReaperSystemTests
{
[Test]
public void Unconsumed_Request_Is_Destroyed_Once_It_Has_Aged()
{
var (world, group) = TestWorld.Make("ReaperAged", tick: 100, server: true);
using (world)
{
var em = world.EntityManager;
var e = em.CreateEntity();
// Age 2 == "survived a full frame unhandled" (the reaper's k_MaxAgeFrames).
em.AddComponentData(e, new ReceiveRpcCommandRequest { SourceConnection = Entity.Null, Age = 2 });
group.Update();
Assert.IsFalse(em.Exists(e),
"an unconsumed RPC request that outlived its receiving frame must be reaped, not leaked forever.");
}
}
[Test]
public void Fresh_Request_Survives_So_A_Late_Receiver_Still_Sees_It()
{
var (world, group) = TestWorld.Make("ReaperFresh", tick: 100, server: true);
using (world)
{
var em = world.EntityManager;
var e = em.CreateEntity();
em.AddComponentData(e, new ReceiveRpcCommandRequest { SourceConnection = Entity.Null, Age = 0 });
group.Update();
Assert.IsTrue(em.Exists(e),
"a request that arrived this frame must NOT be reaped — a receiver ordered later still owns it.");
}
}
[Test]
public void Consumed_Request_Is_Left_To_Its_Receiver()
{
var (world, group) = TestWorld.Make("ReaperConsumed", tick: 100, server: true);
using (world)
{
var em = world.EntityManager;
var e = em.CreateEntity();
var recv = new ReceiveRpcCommandRequest { SourceConnection = Entity.Null, Age = 250 };
recv.Consume();
em.AddComponentData(e, recv);
group.Update();
Assert.IsTrue(em.Exists(e),
"a CONSUMED request belongs to the receiver that consumed it; the reaper must not race it.");
}
}
}
}