From 4af64d8b41ac199ae8f303cbc8d3a8667cfa3c89 Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Wed, 19 Aug 2026 18:51:33 +0200 Subject: [PATCH] Harden shutdown against logging exceptions --- AI-README.md | 3 +- CHANGELOG.md | 7 ++ Installation.md | 2 +- ...C-Emergency-Restore-before-json-Runbook.md | 12 ++-- ...C-Runtime-Shutdown-Exception-2026-08-19.md | 8 ++- .../InstallerEngine.cs | 2 +- .../MainForm.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../app.manifest | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../Services/BizTalkOperationService.cs | 2 +- .../Services/OperationLogger.cs | 17 ++++- .../app.manifest | 2 +- .../Program.cs | 66 +++++++++++++++++++ 14 files changed, 113 insertions(+), 20 deletions(-) diff --git a/AI-README.md b/AI-README.md index 4a2d519..e1cc3d4 100644 --- a/AI-README.md +++ b/AI-README.md @@ -2,7 +2,7 @@ ## Projekt und Sicherheitsziel -Das Repository enthält ein .NET-Framework-4.6.1-WinForms-Tool für kontrollierte BizTalk-2020-Wartungsoperationen. Änderungen müssen Dry-run, explizite Freigabe realer Aktionen, sichere Reihenfolgen und wiederherstellbare Installergrenzen erhalten. Die aktuelle Produktversion ist 2.2.1. +Das Repository enthält ein .NET-Framework-4.6.1-WinForms-Tool für kontrollierte BizTalk-2020-Wartungsoperationen. Änderungen müssen Dry-run, explizite Freigabe realer Aktionen, sichere Reihenfolgen und wiederherstellbare Installergrenzen erhalten. Die aktuelle Produktversion ist 2.2.2. ## Installerinvarianten @@ -31,6 +31,7 @@ Die zentrale Implementierung liegt in `src/BizTalkPlatformManagementTool.Setup/I - Ein älterer kompatibler Snapshot, insbesondere aus 2.1.3, darf nicht allein anhand seines `ToolVersion`-Werts abgelehnt werden. - Der echte Emergency Restore erzeugt nach Möglichkeit automatisch einen timestamp-basierten Soll/Ist-Diff aus Recovery-Quelle und Nachher-Snapshot. - Die zentrale best-effort Orchestrierung liegt in `OperationPlanExecutor`; die WMI-/Service-Zustandsprüfung bleibt im produktiven Runtime-Adapter. +- Datei-Logging und optionale GUI-Logweiterleitung sind Diagnosekanäle und dürfen niemals einen fachlichen Planschritt oder dessen Fehlerbehandlung unterbrechen. ## Versionierung diff --git a/CHANGELOG.md b/CHANGELOG.md index 2670f76..7faae44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog +## [2.2.2] - 2026-08-19 +### Fixed +- A failure in the optional GUI log sink can no longer escape step-level error handling or stop later independent shutdown/restore steps. + +### Added +- Failure-injection coverage for multiple consecutive unexpected exception types and a permanently failing log-display sink. + ## [2.2.1] - 2026-08-19 ### Added - WMI-free **Validate State** preflight with snapshot metadata, server matching and artifact counts. diff --git a/Installation.md b/Installation.md index a5a4315..a10f901 100644 --- a/Installation.md +++ b/Installation.md @@ -89,7 +89,7 @@ Ein erfolgreicher Installer-Self-Test bestätigt Paket, Programmstart und lokale ## Emergency Restore nach einem Teilabbruch -Version 2.2.1 kann einen Wiederanlauf allein aus einer erhaltenen `before.json` vorbereiten und ausführen. Eine mit Version 2.1.3 erzeugte Datei ist kompatibel; zusätzliche Plan- oder Nachher-Dateien des fehlgeschlagenen Laufs sind nicht erforderlich. +Version 2.2.2 kann einen Wiederanlauf allein aus einer erhaltenen `before.json` vorbereiten und ausführen. Eine mit Version 2.1.3 erzeugte Datei ist kompatibel; zusätzliche Plan- oder Nachher-Dateien des fehlgeschlagenen Laufs sind nicht erforderlich. Auch ein Fehler der GUI-Logweiterleitung ist vom fachlichen Executor isoliert und kann die späteren Planschritte nicht abbrechen. 1. Die erhaltene `before.json` außerhalb des Arbeitsverzeichnisses zusätzlich sichern. 2. Anwendung als Administrator starten und denselben Zielserver wählen, der im Snapshot gespeichert ist. diff --git a/docs/ACC-Emergency-Restore-before-json-Runbook.md b/docs/ACC-Emergency-Restore-before-json-Runbook.md index 718118a..b1f6fa2 100644 --- a/docs/ACC-Emergency-Restore-before-json-Runbook.md +++ b/docs/ACC-Emergency-Restore-before-json-Runbook.md @@ -3,11 +3,11 @@ Stand: 19.08.2026 Ausführung: Donnerstag, 20.08.2026, oder Montag, 24.08.2026 Zielserver: `AV23AGPWBI01` -Benötigte Toolversion: `2.2.1` +Benötigte Toolversion: `2.2.2` ## Kurzantwort -Ja. Die vorhandene, mit Version 2.1.3 erzeugte `before.json` kann von Version 2.2.1 direkt als Recovery-Quelle verwendet werden. Zusätzliche Dateien des abgebrochenen Shutdowns sind nicht erforderlich. Die Kompatibilität wird durch einen Regressionstest abgedeckt; die echte BizTalk-/ACC-Ausführung muss noch vor Ort bestätigt werden. +Ja. Die vorhandene, mit Version 2.1.3 erzeugte `before.json` kann von Version 2.2.2 direkt als Recovery-Quelle verwendet werden. Zusätzliche Dateien des abgebrochenen Shutdowns sind nicht erforderlich. Die Kompatibilität wird durch einen Regressionstest abgedeckt; die echte BizTalk-/ACC-Ausführung muss noch vor Ort bestätigt werden. Für diesen Teilzustand ausschließlich **Emergency Restore** verwenden. Dieser Modus startet zuerst `ENTSSO`, arbeitet danach den gespeicherten Sollzustand idempotent ab, setzt nach Einzelfehlern fort und überschreibt die Quell-`before.json` nicht. @@ -31,7 +31,7 @@ type BizTalkPlatformManagementTool-Setup.zip.sha256.txt - [ ] Nur fortfahren, wenn beide ZIP-Hashes exakt gleich sind. - [ ] ZIP in einen neuen Ordner entpacken, die laufende Toolinstanz schließen und `Setup.exe` als Administrator starten. -- [ ] Installation/Update auf Version `2.2.1` vollständig abschließen. +- [ ] Installation/Update auf Version `2.2.2` vollständig abschließen. - [ ] Ein neues, leeres Ausgabeverzeichnis anlegen, zum Beispiel `C:\BizTalk-Recovery\2026-08-20` oder `C:\BizTalk-Recovery\2026-08-24`. Wichtig: Die Recovery-Quelle außerhalb dieses Ausgabeverzeichnisses lassen. Während der Recovery nicht **Snapshot Before** anklicken, damit kein neuer Teilzustand als vermeintlicher Sollzustand gespeichert wird. @@ -86,7 +86,7 @@ Die reale Reihenfolge ist: 4. Orchestrations abgleichen; im Snapshot nur `Bound` gebliebene Orchestrations werden bewusst zur manuellen Kontrolle markiert. 5. Receive Locations zuletzt abgleichen. -Vor jeder Mutation liest Version 2.2.1 den Istzustand. Ein bereits korrekter Zustand wird als `AlreadySatisfied` protokolliert und nicht erneut per WMI geändert. Ein einzelner Fehler wird als `Failed` gespeichert; alle späteren unabhängigen Schritte werden trotzdem versucht. +Vor jeder Mutation liest Version 2.2.2 den Istzustand. Ein bereits korrekter Zustand wird als `AlreadySatisfied` protokolliert und nicht erneut per WMI geändert. Ein einzelner Fehler wird als `Failed` gespeichert; alle späteren unabhängigen Schritte werden trotzdem versucht. Auch ein Fehler der optionalen GUI-Loganzeige kann die fachliche Ausführung nicht unterbrechen. ## 5. Ergebnis abnehmen @@ -112,8 +112,8 @@ Wenn sie deaktiviert ist, obwohl `before.json` sie aktiviert erwartet, versucht ## Wenn der Lauf erneut unterbrochen wird -Die ursprüngliche `before.json` bleibt unverändert. Version 2.2.1 kann mit derselben Recovery-Quelle erneut gestartet werden, weil bereits erreichte Zustände als `AlreadySatisfied` übersprungen werden. Vor dem Wiederholen immer den jüngsten Ergebnisreport sichern und prüfen, welcher Schritt tatsächlich fehlgeschlagen ist. +Die ursprüngliche `before.json` bleibt unverändert. Version 2.2.2 kann mit derselben Recovery-Quelle erneut gestartet werden, weil bereits erreichte Zustände als `AlreadySatisfied` übersprungen werden. Vor dem Wiederholen immer den jüngsten Ergebnisreport sichern und prüfen, welcher Schritt tatsächlich fehlgeschlagen ist. ## Go/No-Go für PROD -ACC ist erst ein belastbarer PROD-Nachweis, wenn der echte Emergency Restore abgeschlossen wurde, der Ergebnisreport vollständig ist, der automatische Diff geprüft wurde und jeder verbliebene `Failed`-Schritt fachlich behandelt wurde. Die lokalen 24 Regressionstests und der Self-Test ersetzen diesen realen BizTalk-/Windows-Nachweis nicht. +ACC ist erst ein belastbarer PROD-Nachweis, wenn der echte Emergency Restore abgeschlossen wurde, der Ergebnisreport vollständig ist, der automatische Diff geprüft wurde und jeder verbliebene `Failed`-Schritt fachlich behandelt wurde. Die lokalen 27 Regressionstests und der Self-Test ersetzen diesen realen BizTalk-/Windows-Nachweis nicht. diff --git a/docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md b/docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md index ff691bd..57c25a4 100644 --- a/docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md +++ b/docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md @@ -55,6 +55,10 @@ Der neue Button **Emergency Restore** benötigt außer der erhaltenen `before.js - Ein 2.1.3-Snapshot wird ausdrücklich als kompatible Recovery-Quelle regressionsgetestet; `ToolVersion` ist keine Versionssperre. - Nach einem echten Emergency Restore wird bei erfolgreichem Nachher-Snapshot automatisch `emergency-restore-diff-*` als JSON, CSV und HTML geschrieben. +## Zusätzliche Exception-Grenze in Version 2.2.2 + +Ein erneuter Audit des gesamten Shutdown-Kontrollflusses bestätigte, dass WMI-Lookup, Istzustandsprüfung, Methodenaufruf, Zustandswartezeit und Objektfreigabe innerhalb der Per-Step-Exception-Grenze liegen. Zusätzlich ist nun auch die optionale GUI-Weiterleitung des Loggers vollständig isoliert: Selbst wenn die Anzeige eines Logeintrags eine Exception wirft, laufen der aktuelle fachliche Schritt und alle späteren unabhängigen Schritte weiter. Failure-Injection-Tests decken mehrere aufeinanderfolgende `InvalidOperationException`-, `IOException`- und `TimeoutException`-Fehler sowie einen dauerhaft werfenden Log-Sink ab. + ### Globale Reihenfolge Shutdown: @@ -86,11 +90,11 @@ Die portable Regressionstestsuite enthält ab Version 2.2.0 unter anderem folgen - globale Shutdown-/Restore-Reihenfolge mit mehreren Anwendungen, - Kurzname/FQDN-Gleichheit für Host-Instance-Schritte. -Die lokale Suite umfasst ab Version 2.2.1 insgesamt 24 Tests einschließlich der expliziten 2.1.3-Snapshot-Kompatibilität. Die abschließende Freigabe für PROD benötigt weiterhin einen Windows-/BizTalk-Test auf ACC: echter Scheduler-Fehlerpfad, vollständige Fortsetzung, Ergebnisreport, Emergency-Dry-run und realer Wiederanlauf aus einer Kopie der vorhandenen `before.json`. +Die lokale Suite umfasst ab Version 2.2.2 insgesamt 27 Tests einschließlich der expliziten 2.1.3-Snapshot-Kompatibilität, zusätzlicher Failure-Injection für unerwartete Exceptions und eines vollständigen Shutdown-Fortsetzungstests über Receive Location, Orchestration, Send Port und Host Instance. Die abschließende Freigabe für PROD benötigt weiterhin einen Windows-/BizTalk-Test auf ACC: echter Scheduler-Fehlerpfad, vollständige Fortsetzung, Ergebnisreport, Emergency-Dry-run und realer Wiederanlauf aus einer Kopie der vorhandenen `before.json`. ## PROD-Freigabekriterien -- Version 2.2.1 installieren und Installer-Self-Test bestätigen. +- Version 2.2.2 installieren und Installer-Self-Test bestätigen. - `Diagnose` und einen vollständigen Shutdown-Dry-run ausführen. - Kontrolliert nachweisen, dass eine isolierte Scheduler-Exception spätere Schritte nicht blockiert. - `shutdown-result.json` und Nachher-Snapshot prüfen. diff --git a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs index fd577a6..a4d51bb 100644 --- a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs +++ b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs @@ -23,7 +23,7 @@ namespace BizTalkPlatformManagementTool.Setup private const string ProductName = "BizTalk Platform Management Tool"; /// Aktuelle Produktversion des Installers und Uninstall-Eintrags. - private const string ProductVersion = "2.2.1"; + private const string ProductVersion = "2.2.2"; /// /// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen. diff --git a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs index e97d547..184593e 100644 --- a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs +++ b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs @@ -65,7 +65,7 @@ namespace BizTalkPlatformManagementTool.Setup { AutoSize = true, Font = new Font(Font.FontFamily, 14, FontStyle.Bold), - Text = "BizTalk Platform Management Tool 2.2.1" + Text = "BizTalk Platform Management Tool 2.2.2" }); root.Controls.Add(new Label { diff --git a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs index 873a0e8..ae7ad8f 100644 --- a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs @@ -8,6 +8,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyProduct("BizTalk Platform Management Tool")] [assembly: ComVisible(false)] [assembly: Guid("675b68a9-bd80-46a5-b8c5-3b11b0b374e2")] -[assembly: AssemblyVersion("2.2.1.0")] -[assembly: AssemblyFileVersion("2.2.1.0")] +[assembly: AssemblyVersion("2.2.2.0")] +[assembly: AssemblyFileVersion("2.2.2.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool.Setup/app.manifest b/src/BizTalkPlatformManagementTool.Setup/app.manifest index ef6a3f0..a8fde31 100644 --- a/src/BizTalkPlatformManagementTool.Setup/app.manifest +++ b/src/BizTalkPlatformManagementTool.Setup/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs index bbe9b00..292520c 100644 --- a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs @@ -9,6 +9,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: ComVisible(false)] [assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")] -[assembly: AssemblyVersion("2.2.1.0")] -[assembly: AssemblyFileVersion("2.2.1.0")] +[assembly: AssemblyVersion("2.2.2.0")] +[assembly: AssemblyFileVersion("2.2.2.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs index 1f09073..a48489b 100644 --- a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs +++ b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs @@ -16,7 +16,7 @@ namespace BizTalkPlatformManagementTool.Services /// /// Current tool version written into generated snapshots. /// - public const string Version = "2.2.1-net461"; + public const string Version = "2.2.2-net461"; /// /// Fallback application name used when WMI does not expose an application property. diff --git a/src/BizTalkPlatformManagementTool/Services/OperationLogger.cs b/src/BizTalkPlatformManagementTool/Services/OperationLogger.cs index cab86b0..f45869d 100644 --- a/src/BizTalkPlatformManagementTool/Services/OperationLogger.cs +++ b/src/BizTalkPlatformManagementTool/Services/OperationLogger.cs @@ -171,7 +171,22 @@ namespace BizTalkPlatformManagementTool.Services return; } - _sink(entry); + try + { + _sink(entry); + } + catch (Exception ex) + { + // Auch die optionale GUI-Weiterleitung ist nur ein Diagnosekanal. Ein Fehler + // dort darf insbesondere nicht die Exception-Behandlung eines BizTalk-Schritts + // erneut unterbrechen und dadurch alle späteren Planschritte verhindern. + WriteToFile(new LogEntry + { + Timestamp = DateTime.Now, + Level = LogLevel.Warning, + Message = "Log display sink failed; runtime operation continues. Error: " + ex.Message + }); + } } /// diff --git a/src/BizTalkPlatformManagementTool/app.manifest b/src/BizTalkPlatformManagementTool/app.manifest index 80e6843..c5c815a 100644 --- a/src/BizTalkPlatformManagementTool/app.manifest +++ b/src/BizTalkPlatformManagementTool/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/tests/BizTalkPlatformManagementTool.Tests/Program.cs b/tests/BizTalkPlatformManagementTool.Tests/Program.cs index f2a43c4..9f5df3e 100644 --- a/tests/BizTalkPlatformManagementTool.Tests/Program.cs +++ b/tests/BizTalkPlatformManagementTool.Tests/Program.cs @@ -27,9 +27,12 @@ namespace BizTalkPlatformManagementTool.Tests Run("Legacy213SnapshotIsAcceptedForRecovery", Legacy213SnapshotIsAcceptedForRecovery); Run("RestorePlanUsesSafeOrder", RestorePlanUsesSafeOrder); Run("ShutdownPlanUsesGlobalSafeOrder", ShutdownPlanUsesGlobalSafeOrder); + Run("ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure", ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure); Run("EmergencyRestorePlanStartsSsoFirst", EmergencyRestorePlanStartsSsoFirst); Run("HostInstancePlanAcceptsShortAndFqdnServer", HostInstancePlanAcceptsShortAndFqdnServer); Run("PlanExecutionContinuesAfterSchedulerFailure", PlanExecutionContinuesAfterSchedulerFailure); + Run("PlanExecutionContinuesAfterMultipleUnexpectedExceptions", PlanExecutionContinuesAfterMultipleUnexpectedExceptions); + Run("LoggerSinkFailureCannotAbortPlanExecution", LoggerSinkFailureCannotAbortPlanExecution); Run("PlanExecutionSkipsAlreadySatisfiedState", PlanExecutionSkipsAlreadySatisfiedState); Run("ExecutionReportRoundTripPreservesFailure", ExecutionReportRoundTripPreservesFailure); Run("CsvNeutralizesFormulaValues", CsvNeutralizesFormulaValues); @@ -163,6 +166,22 @@ namespace BizTalkPlatformManagementTool.Tests Assert(lastSendPort < firstHost, "host instances were not globally last"); } + /// Prüft den vollständigen Shutdown-Fortgang von Receive Location bis Host Instance nach einem frühen Fehler. + private static void ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure() + { + var snapshot = Snapshot("APP", "SEND", ArtifactStates.SendPortStarted); + snapshot.Applications[0].ReceiveLocations.Add(new ReceiveLocationState { Application = "APP", Name = "RV_PMP_Trigger_Schedule", Enabled = true }); + snapshot.Applications[0].Orchestrations.Add(new OrchestrationState { Application = "APP", Name = "ORCHESTRATION", OrchestrationStatus = ArtifactStates.OrchestrationStarted }); + snapshot.HostInstances.Add(new HostInstanceState { InstanceName = "HOST:SERVER", HostName = "HOST", Server = snapshot.Server, RawState = ArtifactStates.HostStarted }); + + var plan = new BizTalkOperationService(null).CreateShutdownPlan(snapshot, snapshot.Server); + var runtime = new FakeOperationStepRuntime { FailingName = "RV_PMP_Trigger_Schedule" }; + var report = new OperationPlanExecutor(null).Execute(plan, TestOptions(false), runtime); + + Assert(runtime.Calls.SequenceEqual(new[] { "RV_PMP_Trigger_Schedule", "ORCHESTRATION", "SEND", "HOST:SERVER" }), "shutdown did not continue through every later artifact category"); + Assert(report.FailedCount == 1 && report.SucceededCount == 3, "cross-category shutdown outcome is incomplete"); + } + /// Prüft ENTSSO als erste Voraussetzung des Emergency Restore. private static void EmergencyRestorePlanStartsSsoFirst() { @@ -202,6 +221,27 @@ namespace BizTalkPlatformManagementTool.Tests Assert(report.Steps[1].Error.Contains("Microsoft.BizTalk.Scheduler"), "nested Scheduler exception missing from report"); } + /// Prüft die Fortsetzung auch bei mehreren unterschiedlichen, nicht adapterspezifischen Exceptions. + private static void PlanExecutionContinuesAfterMultipleUnexpectedExceptions() + { + var runtime = new MultipleFailureOperationStepRuntime(); + var report = new OperationPlanExecutor(null).Execute(TestPlan("INVALID", "IO", "TIMEOUT", "CONTINUE"), TestOptions(false), runtime); + Assert(runtime.Calls.SequenceEqual(new[] { "INVALID", "IO", "TIMEOUT", "CONTINUE" }), "an unexpected exception stopped later independent steps"); + Assert(report.FailedCount == 3 && report.SucceededCount == 1, "multiple failure summary is incomplete"); + Assert(report.Steps.All(x => !string.IsNullOrWhiteSpace(x.FinishedAt)), "a failure result has no completion timestamp"); + } + + /// Prüft, dass selbst eine defekte GUI-Logweiterleitung keine fachlichen Schritte verhindert. + private static void LoggerSinkFailureCannotAbortPlanExecution() + { + var logger = new OperationLogger(entry => { throw new InvalidOperationException("simulated UI sink failure"); }); + var runtime = new FakeOperationStepRuntime { FailingName = "TWO" }; + var report = new OperationPlanExecutor(logger).Execute(TestPlan("ONE", "TWO", "THREE"), TestOptions(false), runtime); + Assert(runtime.Calls.SequenceEqual(new[] { "ONE", "TWO", "THREE" }), "logging failure stopped plan execution"); + Assert(report.SucceededCount == 2 && report.FailedCount == 1, "logging failure changed business outcomes"); + Assert(report.Steps[1].Error.Contains("Microsoft.BizTalk.Scheduler"), "business failure was lost while the log sink was failing"); + } + /// Prüft idempotentes Überspringen eines bereits erreichten Sollzustands. private static void PlanExecutionSkipsAlreadySatisfiedState() { @@ -610,6 +650,32 @@ namespace BizTalkPlatformManagementTool.Tests } } + /// Wirft verschiedene Exception-Typen und liefert danach wieder ein erfolgreiches Ergebnis. + private sealed class MultipleFailureOperationStepRuntime : IOperationStepRuntime + { + /// Initialisiert die vollständige Aufrufliste. + public MultipleFailureOperationStepRuntime() + { + Calls = new List(); + } + + /// Gets all attempted step names. + public List Calls { get; private set; } + + /// Wirft je nach Testschritt eine andere unerwartete Ausnahme. + /// Der auszuführende Testschritt. + /// Die ungenutzten Testoptionen. + /// Erfolg für den abschließenden Fortsetzungsschritt. + public RuntimeStepOutcome Execute(OperationStep step, OperationOptions options) + { + Calls.Add(step.Name); + if (step.Name == "INVALID") throw new InvalidOperationException("unexpected state error"); + if (step.Name == "IO") throw new IOException("unexpected provider I/O error"); + if (step.Name == "TIMEOUT") throw new TimeoutException("unexpected provider timeout"); + return RuntimeStepOutcome.Succeeded; + } + } + /// Erstellt einen minimalen Snapshot für Vergleiche und Planprüfungen. /// Der Name der Testanwendung. /// Der Name des Test-Send-Ports.