using System.IO; using System.Security.Cryptography; using Xunit; using XWorld.PublishTool; namespace XWorld.PublishTool.Tests { public class PublishLayoutTests { private static GameSpec MakeSpec() => new GameSpec { GameId = "rps", Version = 1, MinFrameworkVersion = 1, PlayerCount = 2, TickRateHz = 10, ServerAssembly = "RPS.Server.dll", ServerEntryType = "RPS.Server.RpsServerRoom", CoreDll = "RPS.Core.dll", ClientDll = "RPS.Client.dll", ClientEntryType = "RPS.Client.RpsGameClient", CodeAb = "code.unity3d", Assets = new System.Collections.Generic.List { "res.unity3d" }, }; private static string TempDir() => Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "pub_" + System.Guid.NewGuid().ToString("N"))).FullName; [Fact] public void PublishServer_ProducesDlls_GameJson_Manifest_Signature() { string src = TempDir(); string outDir = Path.Combine(TempDir(), "s"); File.WriteAllBytes(Path.Combine(src, "RPS.Core.dll"), new byte[] { 1, 2, 3 }); File.WriteAllBytes(Path.Combine(src, "RPS.Server.dll"), new byte[] { 4, 5 }); using var rsa = RSA.Create(2048); new PublishLayout().PublishServer(src, outDir, MakeSpec(), rsa.ExportRSAPrivateKeyPem()); Assert.True(File.Exists(Path.Combine(outDir, "RPS.Server.dll"))); Assert.True(File.Exists(Path.Combine(outDir, "RPS.Core.dll"))); Assert.True(File.Exists(Path.Combine(outDir, "game.json"))); var fm = FilesManifest.Parse(File.ReadAllText(Path.Combine(outDir, "files.txt"))); Assert.True(fm.Entries.ContainsKey("RPS.Server.dll")); Assert.True(fm.Entries.ContainsKey("RPS.Core.dll")); Assert.True(fm.Entries.ContainsKey("game.json")); byte[] sig = File.ReadAllBytes(Path.Combine(outDir, "files.txt.sig")); Assert.True(ManifestSigner.Verify(fm.Digest(), sig, rsa.ExportRSAPublicKeyPem())); } [Fact] public void PublishClient_Md5NamedAbs_CodeAbInManifest_NoLooseBytes() { string src = TempDir(); string outDir = Path.Combine(TempDir(), "c"); File.WriteAllBytes(Path.Combine(src, "code.unity3d"), new byte[] { 6, 7, 8 }); File.WriteAllBytes(Path.Combine(src, "res.unity3d"), new byte[] { 9 }); using var rsa = RSA.Create(2048); new PublishLayout().PublishClient(src, outDir, MakeSpec(), rsa.ExportRSAPrivateKeyPem()); var fm = FilesManifest.Parse(File.ReadAllText(Path.Combine(outDir, "files.txt"))); // files.txt 登记逻辑名;落盘 md5 命名(与 MiniGameDownloader.Md5Name 一致) Assert.True(fm.Entries.ContainsKey("code.unity3d")); Assert.True(fm.Entries.ContainsKey("res.unity3d")); string codeMd5 = Md5Util.OfFile(Path.Combine(src, "code.unity3d")); string resMd5 = Md5Util.OfFile(Path.Combine(src, "res.unity3d")); Assert.True(File.Exists(Path.Combine(outDir, $"code_{codeMd5}.unity3d"))); Assert.True(File.Exists(Path.Combine(outDir, $"res_{resMd5}.unity3d"))); // 统一 AB:不再有散 .bytes 文件 Assert.Empty(Directory.GetFiles(outDir, "*.bytes")); // game.json 带 codeAb Assert.Contains("\"codeAb\": \"code.unity3d\"", File.ReadAllText(Path.Combine(outDir, "game.json"))); byte[] sig = File.ReadAllBytes(Path.Combine(outDir, "files.txt.sig")); Assert.True(ManifestSigner.Verify(fm.Digest(), sig, rsa.ExportRSAPublicKeyPem())); } [Fact] public void NullKey_WritesEmptySig_BothTrees() { string src = TempDir(); string outS = Path.Combine(TempDir(), "s"); string outC = Path.Combine(TempDir(), "c"); File.WriteAllBytes(Path.Combine(src, "RPS.Core.dll"), new byte[] { 1 }); File.WriteAllBytes(Path.Combine(src, "RPS.Server.dll"), new byte[] { 2 }); File.WriteAllBytes(Path.Combine(src, "code.unity3d"), new byte[] { 3 }); File.WriteAllBytes(Path.Combine(src, "res.unity3d"), new byte[] { 4 }); var layout = new PublishLayout(); layout.PublishServer(src, outS, MakeSpec(), null); layout.PublishClient(src, outC, MakeSpec(), null); Assert.Equal(0, new FileInfo(Path.Combine(outS, "files.txt.sig")).Length); Assert.Equal(0, new FileInfo(Path.Combine(outC, "files.txt.sig")).Length); } [Fact] public void PublishClient_EmptyCodeAb_Throws() { var spec = MakeSpec(); spec.CodeAb = null; Assert.Throws( () => new PublishLayout().PublishClient(TempDir(), Path.Combine(TempDir(), "c"), spec, null)); } } }