using System; using System.Collections.Generic; using System.IO; using Tesla.Net; using Xunit; namespace TeslaConsole.DiffTests { /// /// Exercises the framed JSON RPC protocol (PodRpc) that replaced the OFB + /// BinaryFormatter wire. Both the Console client (PodManagerConnection) and the /// Launcher Service share this exact code, so an in-process round-trip /// (encode request → decode → encode response → decode) validates the wire /// contract both ends depend on. The live console↔pod path still needs a pod; /// this covers the serialization, which is the part that can silently break. /// public class PodRpcProtocolTests { private static readonly Guid Key = new Guid("7D241B1F-AB6D-4e08-9C20-12294E743D94"); [Fact] public void Request_RoundTrips_Method_And_Args() { var ms = new MemoryStream(); PodRpc.WriteRequest(ms, "KillApp", new object[] { Key, 4242 }); ms.Position = 0; var req = PodRpc.ReadRequest(ms); Assert.Equal("KillApp", req.Method); Assert.Equal(2, req.Args.Count); Assert.Equal(Key, req.Args[0].ToObject(PodRpc.JsonOptions)); Assert.Equal(4242, req.Args[1].ToObject(PodRpc.JsonOptions)); } [Fact] public void Request_With_No_Args_RoundTrips() { var ms = new MemoryStream(); PodRpc.WriteRequest(ms, "FullUpdate", null); ms.Position = 0; var req = PodRpc.ReadRequest(ms); Assert.Equal("FullUpdate", req.Method); Assert.Empty(req.Args); } [Fact] public void Two_Frames_Read_Back_To_Back() { var ms = new MemoryStream(); PodRpc.WriteRequest(ms, "Ping", new object[] { new DateTime(2026, 6, 30, 12, 0, 0, DateTimeKind.Utc) }); PodRpc.WriteRequest(ms, "KillAllApps", null); ms.Position = 0; Assert.Equal("Ping", PodRpc.ReadRequest(ms).Method); Assert.Equal("KillAllApps", PodRpc.ReadRequest(ms).Method); } [Fact] public void Response_Error_RoundTrips() { var ms = new MemoryStream(); PodRpc.WriteResponse(ms, null, "boom on the pod"); ms.Position = 0; var resp = PodRpc.ReadResponse(ms); Assert.Equal("boom on the pod", resp.Error); } [Fact] public void Response_Primitive_Results_RoundTrip() { Assert.Equal(99, RoundTripResult(99)); Assert.Equal(Key, RoundTripResult(Key)); Assert.Equal(0.5f, RoundTripResult(0.5f)); Assert.Equal(new DateTime(2026, 6, 30), RoundTripResult(new DateTime(2026, 6, 30))); } [Fact] public void Response_LaunchData_Array_RoundTrips_With_Fields() { var apps = new[] { new LaunchData { LaunchPair = new LaunchPair { LaunchKey = Key, DisplayName = "Red Planet" }, WorkingDirectory = @"C:\Games\RedPlanet", ExeFile = "RedPlanet.exe", Arguments = "-res 1024 768", AutoRestart = true, }, }; var got = RoundTripResult(apps); Assert.Single(got); Assert.Equal(Key, got[0].LaunchPair.LaunchKey); Assert.Equal("Red Planet", got[0].LaunchPair.DisplayName); Assert.Equal(@"C:\Games\RedPlanet", got[0].WorkingDirectory); Assert.Equal("RedPlanet.exe", got[0].ExeFile); Assert.Equal("-res 1024 768", got[0].Arguments); Assert.True(got[0].AutoRestart); } [Fact] public void Response_FullUpdateData_RoundTrips() { var full = new FullUpdateData { InstalledApps = new[] { new LaunchData { LaunchPair = new LaunchPair { LaunchKey = Key, DisplayName = "RP" } }, }, LaunchedApps = new[] { new LaunchedAppData { LaunchKey = Key, ProcessId = 1234 } }, VolumeLevel = 0.75f, }; var got = RoundTripResult(full); Assert.Equal(0.75f, got.VolumeLevel); Assert.Equal("RP", got.InstalledApps[0].LaunchPair.DisplayName); Assert.Equal(1234, got.LaunchedApps[0].ProcessId); Assert.Equal(Key, got.LaunchedApps[0].LaunchKey); } [Fact] public void Response_OutOfBandProgress_RoundTrips() { var p = new OutOfBandProgress { PercentComplete = 73, Status = "Extracting...", IsCompleted = false }; var got = RoundTripResult(p); Assert.Equal(73, got.PercentComplete); Assert.Equal("Extracting...", got.Status); Assert.False(got.IsCompleted); } [Fact] public void Oversized_Frame_Length_Is_Rejected() { var ms = new MemoryStream(); // Craft a frame header claiming more than MaxFrameBytes. ms.Write(BitConverter.GetBytes(PodRpc.MaxFrameBytes + 1), 0, 4); ms.Position = 0; Assert.Throws(() => PodRpc.ReadFrame(ms)); } // Serialize a result the way the Launcher does, read it the way the Console does. private static T RoundTripResult(object result) { var ms = new MemoryStream(); PodRpc.WriteResponse(ms, result, null); ms.Position = 0; var resp = PodRpc.ReadResponse(ms); Assert.Null(resp.Error); return resp.Result.ToObject(PodRpc.JsonOptions); } } }