Reduce catalog to Phase 1 ACC/PROD view
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Verhindert, dass versehentlich Kennwörter oder Token aus WMI-Textwerten in den Bericht gelangen.
|
||||
/// </summary>
|
||||
internal static class SensitiveDataSanitizer
|
||||
{
|
||||
private static readonly Regex NamedSecret = new Regex(
|
||||
@"(?i)(password|passwd|pwd|secret|token|clientsecret|accesskey)\s*([=:])\s*([^;&\s""']+)",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
public static string Sanitize(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return NamedSecret.Replace(value, match =>
|
||||
match.Groups[1].Value + match.Groups[2].Value + "[REDACTED]");
|
||||
}
|
||||
|
||||
public static string RedactProperty(string name, string value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(name)
|
||||
&& (name.IndexOf("password", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| name.IndexOf("secret", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| name.IndexOf("token", StringComparison.OrdinalIgnoreCase) >= 0))
|
||||
{
|
||||
return "[REDACTED]";
|
||||
}
|
||||
|
||||
return Sanitize(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user