using System; using System.IO; using System.Linq; using System.Text; using BizTalkPlatformManagementTool.Models; using BizTalkPlatformManagementTool.Services; using BizTalkPlatformManagementTool.Setup; namespace BizTalkPlatformManagementTool.Tests { internal static class Program { private static int failures; private static int Main() { Run("JsonRoundTripIsBomTolerantAndAtomic", JsonRoundTripIsBomTolerantAndAtomic); Run("DiffUsesApplicationAndNameIdentity", DiffUsesApplicationAndNameIdentity); Run("RestoreRejectsDifferentServer", RestoreRejectsDifferentServer); Run("RestorePlanUsesSafeOrder", RestorePlanUsesSafeOrder); Run("CsvNeutralizesFormulaValues", CsvNeutralizesFormulaValues); Run("PackageManifestRejectsTampering", PackageManifestRejectsTampering); Run("PackageManifestRejectsUndeclaredAndTraversalFiles", PackageManifestRejectsUndeclaredAndTraversalFiles); Run("InstallerActivatesValidatedPayload", InstallerActivatesValidatedPayload); Run("InstallerDoesNotMutateOnStagingFailure", InstallerDoesNotMutateOnStagingFailure); Run("InstallerRollsBackFailedActivatedSelfTest", InstallerRollsBackFailedActivatedSelfTest); Run("InstallerUninstallRemovesProgramDirectory", InstallerUninstallRemovesProgramDirectory); Console.WriteLine(failures == 0 ? "ALL TESTS PASSED" : failures + " TEST(S) FAILED"); return failures == 0 ? 0 : 1; } private static void Run(string name, Action test) { try { test(); Console.WriteLine("PASS " + name); } catch (Exception ex) { failures++; Console.Error.WriteLine("FAIL " + name + ": " + ex); } } private static void JsonRoundTripIsBomTolerantAndAtomic() { InTemp(directory => { var path = Path.Combine(directory, "snapshot.json"); var snapshot = Snapshot("APP", "PORT", ArtifactStates.SendPortStarted); JsonFileStore.Save(path, snapshot); JsonFileStore.Save(path, snapshot); var original = File.ReadAllBytes(path); var withBom = new byte[original.Length + 3]; withBom[0] = 0xef; withBom[1] = 0xbb; withBom[2] = 0xbf; Buffer.BlockCopy(original, 0, withBom, 3, original.Length); File.WriteAllBytes(path, withBom); Assert(JsonFileStore.Load(path).Applications.Count == 1, "BOM JSON did not load"); Assert(Directory.GetFiles(directory, "*.tmp.*").Length == 0, "temporary files remained"); Assert(Directory.GetFiles(directory, "*.bak.*").Length == 0, "backup files remained"); }); } private static void DiffUsesApplicationAndNameIdentity() { var before = Snapshot("APP-A", "SHARED", ArtifactStates.SendPortStarted); before.Applications.Add(Snapshot("APP-B", "SHARED", ArtifactStates.SendPortStarted).Applications[0]); var after = Snapshot("APP-A", "SHARED", ArtifactStates.SendPortStopped); after.Applications.Add(Snapshot("APP-B", "SHARED", ArtifactStates.SendPortStarted).Applications[0]); var diff = SnapshotComparer.Compare(before, after); Assert(diff.ArtifactDifferences.Count == 1, "expected one application-scoped difference"); Assert(diff.ArtifactDifferences[0].Application == "APP-A", "wrong application was compared"); } private static void RestoreRejectsDifferentServer() { var snapshot = Snapshot("APP", "PORT", ArtifactStates.SendPortStarted); snapshot.Server = "BIZTALK-A.example.local"; SnapshotValidator.EnsureServerMatches(snapshot, "BIZTALK-A"); Expect(() => SnapshotValidator.EnsureServerMatches(snapshot, "BIZTALK-B")); } private static void RestorePlanUsesSafeOrder() { var snapshot = Snapshot("APP", "PORT", ArtifactStates.SendPortStarted); snapshot.HostInstances.Add(new HostInstanceState { InstanceName = "HOST:SERVER", HostName = "HOST", Server = snapshot.Server, RawState = ArtifactStates.HostStarted }); snapshot.Applications[0].Orchestrations.Add(new OrchestrationState { Application = "APP", Name = "ORCH", OrchestrationStatus = ArtifactStates.OrchestrationBound }); snapshot.Applications[0].ReceiveLocations.Add(new ReceiveLocationState { Application = "APP", Name = "RL", Enabled = true }); var plan = new BizTalkOperationService(null).CreateRestorePlan(snapshot, snapshot.Server); Assert(plan.Steps.First().Kind == "HostInstance", "host instance must start first"); Assert(plan.Steps.Last().Kind == "ReceiveLocation", "receive location must be restored last"); var bound = plan.Steps.Single(x => x.Name == "ORCH"); Assert(!bound.Execute && bound.Kind == "Note", "bound orchestration must remain unchanged"); } private static void CsvNeutralizesFormulaValues() { InTemp(directory => { var path = Path.Combine(directory, "diff.csv"); var diff = new SnapshotDiff(); diff.ArtifactDifferences.Add(new ArtifactDiffEntry { Application = "=cmd|' /C calc'!A0", ArtifactType = "SendPort", Name = "PORT", Before = "Started", After = "Stopped" }); CsvWriter.WriteDiff(path, diff); Assert(File.ReadAllText(path).Contains("'=cmd"), "formula-like CSV field was not neutralized"); }); } private static void PackageManifestRejectsTampering() { InTemp(directory => { var app = Path.Combine(directory, "application"); Directory.CreateDirectory(app); File.WriteAllText(Path.Combine(app, InstallerEngine.ApplicationExeName), "payload"); File.WriteAllText(Path.Combine(app, InstallerEngine.ApplicationExeName + ".config"), "config"); var manifest = Path.Combine(directory, "application.manifest"); PackageManifest.Write(app, manifest); PackageManifest.ValidateAndRead(app, manifest); File.AppendAllText(Path.Combine(app, InstallerEngine.ApplicationExeName), "tampered"); Expect(() => PackageManifest.ValidateAndRead(app, manifest)); }); } private static void InstallerActivatesValidatedPayload() { InTemp(directory => { var package = CreatePackage(directory, "new"); var install = Path.Combine(directory, "install"); var data = Path.Combine(directory, "data"); var engine = new InstallerEngine(package, install, data, false, path => File.Exists(path)); engine.Install(false, null); Assert(File.ReadAllText(Path.Combine(install, InstallerEngine.ApplicationExeName)) == "new", "new payload not activated"); Assert(File.Exists(Path.Combine(install, "install-state.txt")), "install state missing"); }); } private static void PackageManifestRejectsUndeclaredAndTraversalFiles() { InTemp(directory => { var app = Path.Combine(directory, "application"); Directory.CreateDirectory(app); var exe = Path.Combine(app, InstallerEngine.ApplicationExeName); File.WriteAllText(exe, "payload"); File.WriteAllText(Path.Combine(app, InstallerEngine.ApplicationExeName + ".config"), "config"); var manifest = Path.Combine(directory, "application.manifest"); PackageManifest.Write(app, manifest); File.WriteAllText(Path.Combine(app, "undeclared.dll"), "extra"); Expect(() => PackageManifest.ValidateAndRead(app, manifest)); File.Delete(Path.Combine(app, "undeclared.dll")); File.WriteAllText(manifest, PackageManifest.Sha256(exe) + "|" + new FileInfo(exe).Length + "|../escape.exe" + Environment.NewLine); Expect(() => PackageManifest.ValidateAndRead(app, manifest)); }); } private static void InstallerDoesNotMutateOnStagingFailure() { InTemp(directory => { var package = CreatePackage(directory, "new"); var install = Path.Combine(directory, "install"); Directory.CreateDirectory(install); File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName), "old"); File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName + ".config"), "old-config"); var engine = new InstallerEngine(package, install, Path.Combine(directory, "data"), false, path => false); Expect(() => engine.Install(false, null)); Assert(File.ReadAllText(Path.Combine(install, InstallerEngine.ApplicationExeName)) == "old", "staging failure modified the installed payload"); Assert(!Directory.GetDirectories(directory, "install.backup.*").Any(), "backup was created before staging passed"); }); } private static void InstallerRollsBackFailedActivatedSelfTest() { InTemp(directory => { var package = CreatePackage(directory, "new"); var install = Path.Combine(directory, "install"); Directory.CreateDirectory(install); File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName), "old"); File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName + ".config"), "old-config"); var calls = 0; var engine = new InstallerEngine(package, install, Path.Combine(directory, "data"), false, path => ++calls == 1); Expect(() => engine.Install(false, null)); Assert(File.ReadAllText(Path.Combine(install, InstallerEngine.ApplicationExeName)) == "old", "previous payload was not restored"); Assert(!Directory.GetDirectories(directory, "install.staging.*").Any(), "staging directory remained"); Assert(!Directory.GetDirectories(directory, "install.backup.*").Any(), "backup directory remained"); }); } private static void InstallerUninstallRemovesProgramDirectory() { InTemp(directory => { var package = CreatePackage(directory, "new"); var install = Path.Combine(directory, "install"); Directory.CreateDirectory(install); File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName), "installed"); var engine = new InstallerEngine(package, install, Path.Combine(directory, "data"), false, path => true); engine.Uninstall(null); Assert(!Directory.Exists(install), "program directory still exists after uninstall"); Assert(!Directory.GetDirectories(directory, "install.removed.*").Any(), "uninstall quarantine directory remained"); }); } private static BizTalkSnapshot Snapshot(string application, string port, int state) { var result = new BizTalkSnapshot { ToolVersion = "test", CreatedAt = DateTimeOffset.Now.ToString("o"), Server = Environment.MachineName }; var app = new ApplicationSnapshot { Application = application }; app.SendPorts.Add(new SendPortState { Application = application, Name = port, Status = state }); result.Applications.Add(app); return result; } private static string CreatePackage(string root, string payload) { var package = Path.Combine(root, "package"); var app = Path.Combine(package, "application"); Directory.CreateDirectory(app); File.WriteAllText(Path.Combine(app, InstallerEngine.ApplicationExeName), payload); File.WriteAllText(Path.Combine(app, InstallerEngine.ApplicationExeName + ".config"), "config"); PackageManifest.Write(app, Path.Combine(package, "application.manifest")); return package; } private static void InTemp(Action action) { var directory = Path.Combine(Path.GetTempPath(), "BizTalkPlatformManagementTool.Tests." + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(directory); try { action(directory); } finally { if (Directory.Exists(directory)) Directory.Delete(directory, true); } } private static void Assert(bool condition, string message) { if (!condition) throw new InvalidOperationException(message); } private static void Expect(Action action) where T : Exception { try { action(); } catch (T) { return; } throw new InvalidOperationException("Expected exception " + typeof(T).Name); } } }