Make installer runtime acceptance transactional

This commit is contained in:
2026-08-10 18:28:49 +02:00
parent 8fe1b57173
commit 448be8dc49
20 changed files with 874 additions and 65 deletions
+267 -6
View File
@@ -37,14 +37,22 @@ namespace BizTalkCheckmkPulse.Tests
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
Run("EndpointOutputExplainsOnlyRealResolutionGaps", EndpointOutputExplainsOnlyRealResolutionGaps);
Run("EndpointProbeBudgetFitsMinuteInterval", EndpointProbeBudgetFitsMinuteInterval);
<<<<<<< HEAD
=======
Run("ForcedEndpointRefreshRewritesFreshCatalog", ForcedEndpointRefreshRewritesFreshCatalog);
Run("EnvironmentLabelDoesNotRenameServicesByDefault", EnvironmentLabelDoesNotRenameServicesByDefault);
Run("DefaultServiceContractIsExact", DefaultServiceContractIsExact);
Run("InstallerRejectsUnconfirmedServiceRename", InstallerRejectsUnconfirmedServiceRename);
Run("InstallerAllowsConfirmedServiceRename", InstallerAllowsConfirmedServiceRename);
>>>>>>> e694e4c (Guard Checkmk service renames during updates)
Run("InstallerUpdatePreservesExistingSettings", InstallerUpdatePreservesExistingSettings);
Run("RuntimeValidationAcceptsFreshConsistentArtifacts", RuntimeValidationAcceptsFreshConsistentArtifacts);
Run("RuntimeValidationRejectsPreInstallSnapshot", RuntimeValidationRejectsPreInstallSnapshot);
Run("RuntimeValidationRejectsWrongIdentity", RuntimeValidationRejectsWrongIdentity);
Run("RuntimeValidationRejectsStaleCatalog", RuntimeValidationRejectsStaleCatalog);
Run("RuntimeValidationRejectsChangedServiceContract", RuntimeValidationRejectsChangedServiceContract);
Run("RuntimeValidationRejectsUnknownStableService", RuntimeValidationRejectsUnknownStableService);
Run("RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog", RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog);
Run("ForceEndpointRefreshRequiresProviderMode", ForceEndpointRefreshRequiresProviderMode);
Run("RuntimeValidationArgumentsAreParsedStrictly", RuntimeValidationArgumentsAreParsedStrictly);
Run("TaskCompletionRequiresFreshSuccessfulRun", TaskCompletionRequiresFreshSuccessfulRun);
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
@@ -337,6 +345,7 @@ namespace BizTalkCheckmkPulse.Tests
Assert(result.IsSuccess, "snapshot should be readable: " + result.Error);
AssertEqual(lines.Length, result.Lines.Count, "snapshot line count");
Assert(lines.SequenceEqual(result.Lines), "snapshot payload changed");
AssertEqual("DOMAIN\\collector$", result.Identity, "snapshot identity");
var replacement = lines.Select(x => x.Replace("Self test OK", "Replacement OK")).ToArray();
store.Write(replacement, generated.AddSeconds(30), "DOMAIN\\collector$");
@@ -690,8 +699,51 @@ namespace BizTalkCheckmkPulse.Tests
Assert(configuredMaximum <= 30000, "configured endpoint worst-case must leave headroom in the minute interval");
}
<<<<<<< HEAD
=======
private static void ForcedEndpointRefreshRewritesFreshCatalog()
{
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.ForcedCatalogTests." + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
try
{
var options = new MonitoringOptions
{
EnvironmentName = "ACC",
EndpointCatalogPath = Path.Combine(directory, "endpoints.xml"),
LogDirectory = Path.Combine(directory, "logs"),
ForceEndpointCatalogRefresh = true
};
var oldTimestamp = DateTime.UtcNow.AddHours(-1);
new EndpointCatalogStore(
options.EndpointCatalogPath,
options.EndpointCatalogMaxBytes,
options.EndpointCatalogMaxEntries)
.Write(new EndpointCatalog
{
EnvironmentName = options.EnvironmentName,
SynchronizedUtc = oldTimestamp
});
var probeResult = new ProbeResult();
probeResult.Platform.ReceiveLocationsDataAvailable = true;
probeResult.Platform.SendPortsDataAvailable = true;
new EndpointConnectivityProbe(options, new FileLogger(options.LogDirectory, 1, "test"))
.Query(probeResult);
var refreshed = new EndpointCatalogStore(
options.EndpointCatalogPath,
options.EndpointCatalogMaxBytes,
options.EndpointCatalogMaxEntries)
.Read(options.EnvironmentName);
Assert(probeResult.EndpointConnectivity.RefreshRequired, "forced catalog refresh was not requested");
Assert(probeResult.EndpointConnectivity.RefreshSucceeded, "forced catalog refresh failed");
Assert(refreshed.SynchronizedUtc > oldTimestamp, "forced catalog refresh did not replace timestamp");
}
finally
{
if (Directory.Exists(directory)) Directory.Delete(directory, true);
}
}
private static void EnvironmentLabelDoesNotRenameServicesByDefault()
{
var options = new MonitoringOptions { EnvironmentName = "ACC" };
@@ -765,7 +817,6 @@ namespace BizTalkCheckmkPulse.Tests
return line.Substring(start, end - start);
}
>>>>>>> e694e4c (Guard Checkmk service renames during updates)
private static void InstallerUpdatePreservesExistingSettings()
{
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.InstallerTests." + Guid.NewGuid().ToString("N"));
@@ -779,6 +830,7 @@ namespace BizTalkCheckmkPulse.Tests
source,
"<configuration><appSettings>"
+ "<add key=\"EnvironmentName\" value=\"\" />"
+ "<add key=\"IncludeEnvironmentInServiceName\" value=\"false\" />"
+ "<add key=\"EndpointProbeMaxConcurrency\" value=\"16\" />"
+ "<add key=\"EndpointMaxCount\" value=\"100\" />"
+ "<add key=\"EndpointCatalogMaxEntries\" value=\"1000\" />"
@@ -802,6 +854,7 @@ namespace BizTalkCheckmkPulse.Tests
string.Empty);
var merged = File.ReadAllText(staged);
AssertEqual("ACC", effectiveEnvironment, "preserved installer environment");
Assert(merged.Contains("key=\"IncludeEnvironmentInServiceName\" value=\"false\""), "stable service-name default must be added on update");
Assert(merged.Contains("key=\"EndpointProbeMaxConcurrency\" value=\"7\""), "existing operational value must be preserved");
Assert(merged.Contains("key=\"EndpointMaxCount\" value=\"100\""), "superseded old default must migrate to new bounded default");
Assert(merged.Contains("key=\"EndpointCatalogMaxEntries\" value=\"1000\""), "new catalog entry limit must be added");
@@ -821,6 +874,214 @@ namespace BizTalkCheckmkPulse.Tests
}
}
private static void RuntimeValidationAcceptsFreshConsistentArtifacts()
{
var boundary = DateTime.UtcNow.AddMinutes(-1);
var result = ValidateRuntimeArtifacts(
boundary,
boundary.AddSeconds(10),
boundary.AddSeconds(5),
"BEW\\t231bizmon",
"BEW\\t231bizmon",
true,
null);
Assert(result.IsSuccess, "fresh consistent runtime artifacts must pass: " + result.Error);
AssertEqual(9, result.StableServiceCount, "validated stable service count");
}
private static void RuntimeValidationRejectsPreInstallSnapshot()
{
var boundary = DateTime.UtcNow.AddMinutes(-1);
var result = ValidateRuntimeArtifacts(
boundary,
boundary.AddSeconds(-1),
boundary.AddSeconds(5),
"BEW\\t231bizmon",
"BEW\\t231bizmon",
true,
null);
Assert(!result.IsSuccess, "snapshot from before installation must fail");
Assert(result.Error.Contains("nicht aus dem gestarteten Providerlauf"), "old snapshot reason missing");
}
private static void RuntimeValidationRejectsWrongIdentity()
{
var boundary = DateTime.UtcNow.AddMinutes(-1);
var result = ValidateRuntimeArtifacts(
boundary,
boundary.AddSeconds(10),
boundary.AddSeconds(5),
"BEW\\other-account",
"BEW\\t231bizmon",
true,
null);
Assert(!result.IsSuccess, "unexpected provider identity must fail");
Assert(result.Error.Contains("unerwarteten Identitaet"), "identity mismatch reason missing");
}
private static void RuntimeValidationRejectsStaleCatalog()
{
var boundary = DateTime.UtcNow.AddMinutes(-1);
var result = ValidateRuntimeArtifacts(
boundary,
boundary.AddSeconds(10),
boundary.AddSeconds(-1),
"BEW\\t231bizmon",
"BEW\\t231bizmon",
true,
null);
Assert(!result.IsSuccess, "catalog from before installation must fail");
Assert(result.Error.Contains("nicht aktualisiert"), "old catalog reason missing");
}
private static void RuntimeValidationRejectsChangedServiceContract()
{
var boundary = DateTime.UtcNow.AddMinutes(-1);
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
lines[0] = lines[0].Replace("BizTalk Platform", "BizTalk Renamed Platform");
var result = ValidateRuntimeArtifacts(
boundary,
boundary.AddSeconds(10),
boundary.AddSeconds(5),
"BEW\\t231bizmon",
"BEW\\t231bizmon",
true,
lines);
Assert(!result.IsSuccess, "changed runtime service contract must fail");
Assert(result.Error.Contains("Fehlend=[BizTalk Platform]"), "missing stable service reason missing");
}
private static void RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog()
{
var boundary = DateTime.UtcNow.AddMinutes(-1);
var result = ValidateRuntimeArtifacts(
boundary,
boundary.AddSeconds(10),
null,
"BEW\\t231bizmon",
"BEW\\t231bizmon",
false,
null);
Assert(result.IsSuccess, "disabled endpoint probe must not require catalog: " + result.Error);
Assert(!result.CatalogSynchronizedUtc.HasValue, "disabled endpoint probe must report no catalog timestamp");
}
private static void RuntimeValidationRejectsUnknownStableService()
{
var boundary = DateTime.UtcNow.AddMinutes(-1);
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
lines[0] = "3" + lines[0].Substring(1);
var result = ValidateRuntimeArtifacts(
boundary,
boundary.AddSeconds(10),
boundary.AddSeconds(5),
"BEW\\t231bizmon",
"BEW\\t231bizmon",
true,
lines);
Assert(!result.IsSuccess, "UNKNOWN stable service must fail runtime acceptance");
Assert(result.Error.Contains("UNKNOWN=[BizTalk Platform]"), "UNKNOWN stable service reason missing");
}
private static void ForceEndpointRefreshRequiresProviderMode()
{
var options = MonitoringOptions.Load(new[] { "--collect", "--force-endpoint-refresh" });
Assert(options.Collect, "forced refresh must retain provider mode");
Assert(options.ForceEndpointCatalogRefresh, "forced refresh option missing");
try
{
MonitoringOptions.Load(new[] { "--force-endpoint-refresh" });
throw new InvalidOperationException("forced refresh without provider mode was accepted");
}
catch (Exception ex)
{
Assert(ex.Message.Contains("valid only in provider mode"), "forced refresh rejection reason missing");
}
}
private static void RuntimeValidationArgumentsAreParsedStrictly()
{
var timestamp = DateTime.UtcNow;
var identity = "BEW\\t231bizmon";
var options = MonitoringOptions.Load(new[]
{
"--validate-runtime",
"--validation-not-before-utc",
timestamp.ToString("o"),
"--expected-identity-base64",
Convert.ToBase64String(Encoding.UTF8.GetBytes(identity))
});
Assert(options.RuntimeValidation, "runtime validation mode missing");
AssertEqual(timestamp.ToString("o"), options.RuntimeValidationNotBeforeUtc.Value.ToString("o"), "runtime validation timestamp");
AssertEqual(identity, options.ExpectedRuntimeIdentity, "runtime validation identity");
}
private static void TaskCompletionRequiresFreshSuccessfulRun()
{
var boundary = DateTime.UtcNow;
Assert(BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsSuccessfulCompletion(
3, 0, boundary.AddSeconds(1), boundary), "fresh successful ready task must pass");
Assert(!BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsSuccessfulCompletion(
4, 0, boundary.AddSeconds(1), boundary), "running task must not pass");
Assert(!BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsSuccessfulCompletion(
3, 1, boundary.AddSeconds(1), boundary), "failed task result must not pass");
Assert(!BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsSuccessfulCompletion(
3, 0, boundary.AddSeconds(-3), boundary), "old successful task must not pass");
}
private static RuntimeValidationResult ValidateRuntimeArtifacts(
DateTime boundaryUtc,
DateTime snapshotUtc,
DateTime? catalogUtc,
string snapshotIdentity,
string expectedIdentity,
bool probeEndpoints,
string[] snapshotLines)
{
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.RuntimeValidationTests." + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
try
{
var options = new MonitoringOptions
{
EnvironmentName = "ACC",
SnapshotPath = Path.Combine(directory, "snapshot.txt"),
EndpointCatalogPath = Path.Combine(directory, "endpoints.xml"),
ProbeEndpointConnectivity = probeEndpoints
};
var formatter = new CheckmkLocalFormatter(options);
new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes).Write(
snapshotLines ?? formatter.FormatSelfTest().ToArray(),
snapshotUtc,
snapshotIdentity);
if (catalogUtc.HasValue)
{
new EndpointCatalogStore(
options.EndpointCatalogPath,
options.EndpointCatalogMaxBytes,
options.EndpointCatalogMaxEntries)
.Write(new EndpointCatalog
{
EnvironmentName = options.EnvironmentName,
SynchronizedUtc = catalogUtc.Value
});
}
return RuntimeValidator.Validate(
options,
formatter,
boundaryUtc,
expectedIdentity,
boundaryUtc.AddMinutes(1));
}
finally
{
if (Directory.Exists(directory)) Directory.Delete(directory, true);
}
}
private static void SnapshotRejectsTampering()
{
WithTemporarySnapshot((path, store) =>