Reduce catalog to Phase 1 ACC/PROD view

This commit is contained in:
2026-07-27 18:03:39 +02:00
parent 2b5b97c869
commit 53fcfb84a4
17 changed files with 1208 additions and 1555 deletions
@@ -1,27 +1,22 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using BizTalkApplicationCatalog.Models;
using BizTalkApplicationCatalog.Persistence;
using BizTalkApplicationCatalog.Reporting;
namespace BizTalkApplicationCatalog.Infrastructure
{
/// <summary>
/// Prüft die zentrale Berichtslogik ohne Zugriff auf Windows- oder BizTalk-WMI.
/// Prüft Snapshot, Merge und Excel-Ausgabe ohne BizTalk-Zugriff.
/// </summary>
internal static class SelfTestRunner
{
public static void Run(string outputPath = null)
{
Assert(SensitiveDataSanitizer.Sanitize(
"https://host/path?password=secret&client=100")
== "https://host/path?password=[REDACTED]&client=100",
"Secret-Redaktion ist fehlerhaft.");
var keepOutput = !string.IsNullOrWhiteSpace(outputPath);
var directory = keepOutput
? Path.GetDirectoryName(Path.GetFullPath(outputPath))
@@ -29,11 +24,26 @@ namespace BizTalkApplicationCatalog.Infrastructure
Path.GetTempPath(),
"BizTalkApplicationCatalogTests-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
var path = keepOutput ? Path.GetFullPath(outputPath) : Path.Combine(directory, "test.xlsx");
var path = keepOutput
? Path.GetFullPath(outputPath)
: Path.Combine(directory, "phase1-test.xlsx");
var jsonPath = Path.Combine(
directory,
"phase1-test-" + Guid.NewGuid().ToString("N") + ".json");
try
{
var document = SampleDocument();
new XlsxReportWriter().Write(document, path);
var acc = AccSnapshot();
SnapshotStore.Save(acc, jsonPath);
var loadedAcc = SnapshotStore.Load(jsonPath);
SnapshotStore.ValidateForMerge(loadedAcc, "ACC");
Assert(
loadedAcc.Applications.Count == 2,
"JSON-Roundtrip hat Anwendungen verloren.");
var prod = ProdSnapshot();
SnapshotStore.ValidateForMerge(prod, "PROD");
new XlsxReportWriter().Write(new[] { loadedAcc, prod }, path);
Assert(File.Exists(path), "XLSX-Datei wurde nicht erzeugt.");
using (var archive = ZipFile.OpenRead(path))
@@ -46,19 +56,18 @@ namespace BizTalkApplicationCatalog.Infrastructure
"docProps/app.xml",
"xl/workbook.xml",
"xl/_rels/workbook.xml.rels",
"xl/styles.xml"
"xl/styles.xml",
"xl/worksheets/sheet1.xml"
};
foreach (var name in required)
{
Assert(archive.GetEntry(name) != null, "XLSX-Part fehlt: " + name);
}
for (var index = 1; index <= 10; index++)
{
Assert(
archive.GetEntry("xl/worksheets/sheet" + index + ".xml") != null,
"Arbeitsblatt fehlt: " + index);
archive.GetEntry(name) != null,
"XLSX-Part fehlt: " + name);
}
Assert(
archive.GetEntry("xl/worksheets/sheet2.xml") == null,
"Die Arbeitsmappe enthält mehr als ein Datenblatt.");
foreach (var entry in archive.Entries.Where(item =>
item.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)
@@ -74,42 +83,60 @@ namespace BizTalkApplicationCatalog.Infrastructure
XNamespace spreadsheet =
"http://schemas.openxmlformats.org/spreadsheetml/2006/main";
var names = workbook.Descendants(spreadsheet + "sheet")
.Select(item => (string)item.Attribute("name")).ToList();
Assert(names.Contains("Anwendungen"), "Anwendungsblatt fehlt.");
Assert(names.Contains("Abdeckung"), "Abdeckungsblatt fehlt.");
.Select(item => (string)item.Attribute("name"))
.ToList();
Assert(
names.SequenceEqual(new[] { "Phase 1" }),
"Es wird nicht exakt das Blatt 'Phase 1' erzeugt.");
var contentTypes = LoadXml(archive, "[Content_Types].xml");
XNamespace contentTypeNamespace =
"http://schemas.openxmlformats.org/package/2006/content-types";
Assert(
contentTypes.Root.Elements(contentTypeNamespace + "Override").Count() == 14,
"Content-Type-Overrides sind unvollständig oder im falschen Namespace.");
contentTypes.Root
.Elements(contentTypeNamespace + "Override")
.Count() == 5,
"Content-Type-Overrides sind unvollständig.");
var relationships = LoadXml(archive, "_rels/.rels");
XNamespace relationshipNamespace =
"http://schemas.openxmlformats.org/package/2006/relationships";
var worksheet = LoadXml(
archive,
"xl/worksheets/sheet1.xml");
Assert(
relationships.Root.Elements(relationshipNamespace + "Relationship").Count() == 3,
"Paketbeziehungen sind unvollständig oder im falschen Namespace.");
worksheet.Descendants(spreadsheet + "mergeCell")
.Select(item => (string)item.Attribute("ref"))
.Contains("A1:G1"),
"Titelzeile ist nicht zusammengeführt.");
Assert(
(string)worksheet
.Descendants(spreadsheet + "autoFilter")
.Single()
.Attribute("ref") == "A2:G5",
"Filterbereich ist falsch.");
var combinedText = new StringBuilder();
foreach (var entry in archive.Entries.Where(item =>
item.FullName.StartsWith("xl/worksheets/", StringComparison.Ordinal)))
string text;
using (var reader = new StreamReader(
archive.GetEntry("xl/worksheets/sheet1.xml").Open(),
Encoding.UTF8))
{
using (var reader = new StreamReader(entry.Open(), Encoding.UTF8))
{
combinedText.Append(reader.ReadToEnd());
}
text = reader.ReadToEnd();
}
Assert(!combinedText.ToString().Contains("supersecret"), "XLSX enthält Testkennwort.");
Assert(combinedText.ToString().Contains("[REDACTED]"), "Redaktionsmarker fehlt.");
Assert(text.Contains("BizTalk-Anwendung in ACC"), "ACC-Spalte fehlt.");
Assert(text.Contains("BizTalk-Anwendung in PRD"), "PRD-Spalte fehlt.");
Assert(text.Contains("FILE (1x) / SFTP (1x)"), "Adapteraggregation ist falsch.");
Assert(text.Contains("Nur in ACC vorhanden"), "ACC-Hinweis fehlt.");
Assert(text.Contains("Nur in PRD vorhanden"), "PRD-Hinweis fehlt.");
Assert(text.Contains("Gesamt: 3 BizTalk-Anwendungen"), "Summenzeile ist falsch.");
}
}
finally
{
try
{
if (!keepOutput && Directory.Exists(directory)) Directory.Delete(directory, true);
if (File.Exists(jsonPath)) File.Delete(jsonPath);
if (!keepOutput && Directory.Exists(directory))
{
Directory.Delete(directory, true);
}
}
catch (IOException)
{
@@ -120,58 +147,56 @@ namespace BizTalkApplicationCatalog.Infrastructure
}
}
private static InventoryDocument SampleDocument()
private static CatalogSnapshot AccSnapshot()
{
var document = new InventoryDocument
var snapshot = Snapshot("ACC", "BIZTALK-ACC");
snapshot.Applications.Add(Application(
"OrderProcessing",
new AdapterCount { AdapterType = "FILE", Count = 1 },
new AdapterCount { AdapterType = "SFTP", Count = 1 }));
snapshot.Applications.Add(Application(
"AccOnly",
new AdapterCount { AdapterType = "FILE", Count = 1 }));
return snapshot;
}
private static CatalogSnapshot ProdSnapshot()
{
var snapshot = Snapshot("PROD", "BIZTALK-PROD");
snapshot.Applications.Add(Application(
"OrderProcessing",
new AdapterCount { AdapterType = "FILE", Count = 1 },
new AdapterCount { AdapterType = "SFTP", Count = 1 }));
snapshot.Applications.Add(Application(
"ProdOnly",
new AdapterCount { AdapterType = "SFTP", Count = 1 }));
return snapshot;
}
private static CatalogSnapshot Snapshot(
string environment,
string computer)
{
return new CatalogSnapshot
{
EnvironmentName = "ACC",
ComputerName = "BIZTALK-ACC",
ToolVersion = "1.0.0.0",
ManagementServer = "SQL-ACC",
ManagementDatabase = "BizTalkMgmtDb",
StartedUtc = DateTime.UtcNow.AddSeconds(-1),
CompletedUtc = DateTime.UtcNow
EnvironmentName = environment,
ComputerName = computer,
CreatedUtc = DateTime.UtcNow.ToString("o"),
IsComplete = true
};
document.SystemProperties.Add(new NameValueRecord(
"Betriebssystem", "Windows Server 2019", "Self-Test"));
document.Applications.Add(new ApplicationRecord
}
private static CatalogApplication Application(
string name,
params AdapterCount[] adapters)
{
var result = new CatalogApplication
{
Name = "OrderProcessing",
Description = "Aufträge & Sonderzeichen <Test>",
Status = "Gestartet (2)",
IsDefault = "Nein",
Source = "Self-Test"
});
var port = new ArtifactRecord
{
Type = "Send Port",
ApplicationName = "OrderProcessing",
Name = "Send_Orders",
Status = "Gestartet (2)",
HostName = "SendHost",
AdapterName = "WCF-Custom",
Address = SensitiveDataSanitizer.Sanitize(
"https://service/orders?password=supersecret")
Name = name,
EndpointCount = adapters.Sum(item => item.Count)
};
port.Properties.Add(new NameValueRecord("IsTwoWay", "True", "Self-Test"));
document.Artifacts.Add(port);
document.Coverage.Add(new CoverageRecord
{
DataSource = "MSBTS_Application",
Status = "Vollständig",
RowCount = 1,
Required = "Ja",
Message = "Self-Test"
});
document.SectionStatuses.Add(new SectionStatus
{
Name = "Self-Test",
Status = "Erfolgreich",
Message = "OK",
Required = true,
DurationMilliseconds = 1
});
return document;
result.AdapterCounts.AddRange(adapters);
return result;
}
private static XDocument LoadXml(ZipArchive archive, string name)