Harden BizTalk maintenance workflow and add installer

This commit is contained in:
2026-08-06 18:46:28 +02:00
parent e8df6042d2
commit 0cb3fda151
32 changed files with 1782 additions and 565 deletions
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.props" Condition="Exists('$(MSBuildToolsPath)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B2AD340B-859F-413B-8811-7FBA4BD2BC02}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>BizTalkPlatformManagementTool.Tests</RootNamespace>
<AssemblyName>BizTalkPlatformManagementTool.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\BizTalkPlatformManagementTool\BizTalkPlatformManagementTool.csproj">
<Project>{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}</Project>
<Name>BizTalkPlatformManagementTool</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,136 @@
using System;
using System.IO;
using System.Linq;
using BizTalkPlatformManagementTool.Models;
using BizTalkPlatformManagementTool.Services;
namespace BizTalkPlatformManagementTool.Tests
{
internal static class Program
{
private static int _failed;
private static int Main()
{
Run("RestoreStartsOnlyPreviouslyActiveArtifacts", RestoreStartsOnlyPreviouslyActiveArtifacts);
Run("ShutdownStopsOnlyPreviouslyActiveArtifacts", ShutdownStopsOnlyPreviouslyActiveArtifacts);
Run("SnapshotComparisonUsesCompositeIdentity", SnapshotComparisonUsesCompositeIdentity);
Run("SnapshotServerValidationIsSafe", SnapshotServerValidationIsSafe);
Run("JsonStoreReplacesAtomically", JsonStoreReplacesAtomically);
Console.WriteLine(_failed == 0 ? "PASS: 5 tests" : "FAIL: " + _failed + " test(s)");
return _failed == 0 ? 0 : 1;
}
private static void RestoreStartsOnlyPreviouslyActiveArtifacts()
{
var snapshot = FullSnapshot();
var plan = new BizTalkOperationService(null).CreateRestorePlan(snapshot, "BIZ01");
AssertEqual(4, plan.Steps.Count(x => x.Execute), "restore executable step count");
Assert(plan.Steps.Any(x => x.Kind == "HostInstance" && x.Name == "HostStarted"), "started host missing");
Assert(plan.Steps.Any(x => x.Kind == "SendPort" && x.Name == "SendStarted"), "started send port missing");
Assert(plan.Steps.Any(x => x.Kind == "Orchestration" && x.Name == "OrchStarted"), "started orchestration missing");
AssertEqual("AssemblyName", plan.Steps.Single(x => x.Kind == "Orchestration").QualifierProperty, "orchestration qualifier property");
var receive = plan.Steps.Single(x => x.Kind == "ReceiveLocation");
AssertEqual("ReceivePortName", receive.QualifierProperty, "receive qualifier property");
AssertEqual("RP1", receive.QualifierValue, "receive qualifier value");
Assert(!plan.Steps.Any(x => x.Name == "SendStopped" || x.Name == "SendBound" || x.Name == "ReceiveDisabled"), "inactive artifacts must remain untouched");
}
private static void ShutdownStopsOnlyPreviouslyActiveArtifacts()
{
var plan = new BizTalkOperationService(null).CreateShutdownPlan(FullSnapshot(), "BIZ01");
AssertEqual(4, plan.Steps.Count(x => x.Execute), "shutdown executable step count");
Assert(!plan.Steps.Any(x => x.Name == "SendStopped" || x.Name == "SendBound" || x.Name == "ReceiveDisabled"), "inactive artifacts must not be stopped");
}
private static void SnapshotComparisonUsesCompositeIdentity()
{
var before = EmptySnapshot("BIZ01");
var after = EmptySnapshot("BIZ01");
before.Applications.Add(App("AppA", ArtifactStates.SendPortStarted));
before.Applications.Add(App("AppB", ArtifactStates.SendPortStopped));
after.Applications.Add(App("AppA", ArtifactStates.SendPortStopped));
after.Applications.Add(App("AppB", ArtifactStates.SendPortStopped));
var diff = SnapshotComparer.Compare(before, after);
AssertEqual(1, diff.ArtifactDifferences.Count, "composite send port comparison");
AssertEqual("AppA", diff.ArtifactDifferences[0].Application, "changed application");
}
private static void SnapshotServerValidationIsSafe()
{
BizTalkOperationService.ValidateSnapshot(EmptySnapshot("biz01.domain.local"), "BIZ01");
AssertThrows<InvalidDataException>(() => BizTalkOperationService.ValidateSnapshot(EmptySnapshot("BIZ02"), "BIZ01"), "foreign server must be rejected");
}
private static void JsonStoreReplacesAtomically()
{
var directory = Path.Combine(Path.GetTempPath(), "BizTalkPlatformManagementTool.Tests." + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
try
{
var path = Path.Combine(directory, "state.json");
JsonFileStore.Save(path, EmptySnapshot("BIZ01"));
JsonFileStore.Save(path, EmptySnapshot("BIZ02"));
var loaded = JsonFileStore.Load<BizTalkSnapshot>(path);
AssertEqual("BIZ02", loaded.Server, "atomic replacement payload");
AssertEqual(0, Directory.GetFiles(directory, "*.tmp.*").Length, "temporary files");
}
finally
{
Directory.Delete(directory, true);
}
}
private static BizTalkSnapshot FullSnapshot()
{
var snapshot = EmptySnapshot("BIZ01");
var app = new ApplicationSnapshot { Application = "Orders" };
app.ReceiveLocations.Add(new ReceiveLocationState { Application = "Orders", Name = "ReceiveEnabled", ReceivePortName = "RP1", Enabled = true });
app.ReceiveLocations.Add(new ReceiveLocationState { Application = "Orders", Name = "ReceiveDisabled", ReceivePortName = "RP1", Enabled = false });
app.SendPorts.Add(new SendPortState { Application = "Orders", Name = "SendStarted", Status = ArtifactStates.SendPortStarted });
app.SendPorts.Add(new SendPortState { Application = "Orders", Name = "SendStopped", Status = ArtifactStates.SendPortStopped });
app.SendPorts.Add(new SendPortState { Application = "Orders", Name = "SendBound", Status = ArtifactStates.SendPortBound });
app.Orchestrations.Add(new OrchestrationState { Application = "Orders", Name = "OrchStarted", OrchestrationStatus = ArtifactStates.OrchestrationStarted, AssemblyName = "Orders.Processes" });
app.Orchestrations.Add(new OrchestrationState { Application = "Orders", Name = "OrchStopped", OrchestrationStatus = ArtifactStates.OrchestrationStopped });
snapshot.Applications.Add(app);
snapshot.HostInstances.Add(new HostInstanceState { InstanceName = "HostStarted", Server = "BIZ01", RawState = ArtifactStates.HostStarted });
snapshot.HostInstances.Add(new HostInstanceState { InstanceName = "HostStopped", Server = "BIZ01", RawState = ArtifactStates.HostStopped });
return snapshot;
}
private static BizTalkSnapshot EmptySnapshot(string server)
{
return new BizTalkSnapshot { Server = server, CreatedAt = DateTime.Now.ToString("s"), ToolVersion = BizTalkOperationService.Version };
}
private static ApplicationSnapshot App(string name, int sendStatus)
{
var app = new ApplicationSnapshot { Application = name };
app.SendPorts.Add(new SendPortState { Application = name, Name = "SharedName", Status = sendStatus });
return app;
}
private static void Run(string name, Action test)
{
try { test(); Console.WriteLine("PASS: " + name); }
catch (Exception ex) { _failed++; Console.WriteLine("FAIL: " + name + " - " + ex.Message); }
}
private static void Assert(bool condition, string message)
{
if (!condition) throw new InvalidOperationException(message);
}
private static void AssertEqual<T>(T expected, T actual, string label)
{
if (!object.Equals(expected, actual)) throw new InvalidOperationException(label + ": expected=" + expected + ", actual=" + actual);
}
private static void AssertThrows<T>(Action action, string label) where T : Exception
{
try { action(); }
catch (T) { return; }
throw new InvalidOperationException(label);
}
}
}