Initial import of the CBDDC codebase with docs and tests. Add a .NET-focused gitignore to keep generated artifacts out of source control.
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
Joseph Doherty
2026-02-20 13:03:21 -05:00
commit 08bfc17218
218 changed files with 33910 additions and 0 deletions

View File

@@ -0,0 +1,267 @@
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using ZB.MOM.WW.CBDDC.Core;
namespace ZB.MOM.WW.CBDDC.Core.Tests;
public class ArchitectureFitnessTests
{
/// <summary>
/// Verifies that the core assembly does not reference outer-layer assemblies.
/// </summary>
[Fact]
public void CoreAssembly_ShouldNotReferenceOuterAssemblies()
{
var references = typeof(OplogEntry).Assembly
.GetReferencedAssemblies()
.Select(a => a.Name)
.Where(a => !string.IsNullOrWhiteSpace(a))
.ToHashSet(StringComparer.Ordinal);
references.ShouldNotContain("ZB.MOM.WW.CBDDC.Network");
references.ShouldNotContain("ZB.MOM.WW.CBDDC.Persistence");
references.ShouldNotContain("ZB.MOM.WW.CBDDC.Hosting");
}
/// <summary>
/// Verifies that project references under src form an acyclic graph.
/// </summary>
[Fact]
public void SourceProjectGraph_ShouldBeAcyclic()
{
var repoRoot = FindRepoRoot();
var srcRoot = Path.Combine(repoRoot, "src");
var projectFiles = Directory
.EnumerateFiles(srcRoot, "*.csproj", SearchOption.AllDirectories)
.Where(p => !p.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.Ordinal)
&& !p.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.Ordinal))
.ToList();
var nodes = projectFiles.ToDictionary(
p => Path.GetFileNameWithoutExtension(p),
p => new HashSet<string>(StringComparer.Ordinal));
foreach (var projectFile in projectFiles)
{
var projectName = Path.GetFileNameWithoutExtension(projectFile);
var doc = XDocument.Load(projectFile);
var refs = doc.Descendants("ProjectReference")
.Select(x => x.Attribute("Include")?.Value)
.Where(v => !string.IsNullOrWhiteSpace(v))
.Select(v => Path.GetFileNameWithoutExtension(v!.Replace('\\', '/')));
foreach (var reference in refs)
{
if (nodes.ContainsKey(reference))
{
nodes[projectName].Add(reference);
}
}
}
HasCycle(nodes).ShouldBeFalse();
}
/// <summary>
/// Verifies the allowed dependency graph between source projects.
/// </summary>
[Fact]
public void SourceProjectReferences_ShouldMatchAllowedDependencyGraph()
{
var repoRoot = FindRepoRoot();
var srcRoot = Path.Combine(repoRoot, "src");
var projectFiles = Directory
.EnumerateFiles(srcRoot, "*.csproj", SearchOption.AllDirectories)
.Where(p => !p.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.Ordinal)
&& !p.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.Ordinal))
.ToList();
var allowedDependencies = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal)
{
["ZB.MOM.WW.CBDDC.Core"] = new HashSet<string>(StringComparer.Ordinal),
["ZB.MOM.WW.CBDDC.Network"] = new HashSet<string>(StringComparer.Ordinal) { "ZB.MOM.WW.CBDDC.Core" },
["ZB.MOM.WW.CBDDC.Persistence"] = new HashSet<string>(StringComparer.Ordinal) { "ZB.MOM.WW.CBDDC.Core" },
["ZB.MOM.WW.CBDDC.Hosting"] = new HashSet<string>(StringComparer.Ordinal) { "ZB.MOM.WW.CBDDC.Network" }
};
foreach (var projectFile in projectFiles)
{
var projectName = Path.GetFileNameWithoutExtension(projectFile);
allowedDependencies.ContainsKey(projectName)
.ShouldBeTrue($"Unexpected source project found: {projectName}");
var doc = XDocument.Load(projectFile);
var references = doc.Descendants("ProjectReference")
.Select(x => x.Attribute("Include")?.Value)
.Where(v => !string.IsNullOrWhiteSpace(v))
.Select(v => Path.GetFileNameWithoutExtension(v!.Replace('\\', '/')))
.ToHashSet(StringComparer.Ordinal);
var expected = allowedDependencies[projectName];
var extra = references.Where(r => !expected.Contains(r)).ToList();
var missing = expected.Where(e => !references.Contains(e)).ToList();
extra.ShouldBeEmpty($"Project {projectName} has disallowed references: {string.Join(", ", extra)}");
missing.ShouldBeEmpty($"Project {projectName} is missing required references: {string.Join(", ", missing)}");
}
}
/// <summary>
/// Verifies non-generic ILogger usage is restricted to explicit compatibility shims.
/// </summary>
[Fact]
public void SourceCode_ShouldRestrictNonGenericILoggerUsage()
{
var repoRoot = FindRepoRoot();
var srcRoot = Path.Combine(repoRoot, "src");
var loggerPattern = new Regex(@"\bILogger\b(?!\s*<|\s*Factory\b)", RegexOptions.Compiled);
var allowedSnippets = new[]
{
"private readonly ILogger _inner;",
"internal ProtocolHandler(ILogger logger",
"ILogger? logger = null)",
"CreateTypedLogger(ILogger? logger)",
"public ForwardingLogger(ILogger inner)"
};
var violations = new List<string>();
var sourceFiles = Directory.EnumerateFiles(srcRoot, "*.cs", SearchOption.AllDirectories)
.Where(p => !p.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}", StringComparison.Ordinal)
&& !p.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.Ordinal));
foreach (var file in sourceFiles)
{
var lines = File.ReadAllLines(file);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//", StringComparison.Ordinal))
{
continue;
}
if (!loggerPattern.IsMatch(line))
{
continue;
}
if (allowedSnippets.Any(line.Contains))
{
continue;
}
var relativePath = Path.GetRelativePath(repoRoot, file).Replace('\\', '/');
violations.Add($"{relativePath}:{i + 1} -> {line}");
}
}
violations.ShouldBeEmpty($"Unexpected non-generic ILogger usage:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
}
/// <summary>
/// Verifies log boundaries push operation context for hosted/background entry points.
/// </summary>
[Fact]
public void BoundaryServices_ShouldPushOperationLogContext()
{
var repoRoot = FindRepoRoot();
var boundaryFiles = new[]
{
"src/ZB.MOM.WW.CBDDC.Network/CBDDCNodeService.cs",
"src/ZB.MOM.WW.CBDDC.Network/SyncOrchestrator.cs",
"src/ZB.MOM.WW.CBDDC.Network/TcpSyncServer.cs",
"src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/DiscoveryServiceHostedService.cs",
"src/ZB.MOM.WW.CBDDC.Hosting/HostedServices/TcpSyncServerHostedService.cs",
"src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpDiscoveryService.cs",
"src/ZB.MOM.WW.CBDDC.Hosting/Services/NoOpSyncOrchestrator.cs"
};
foreach (var relativePath in boundaryFiles)
{
var filePath = Path.Combine(repoRoot, relativePath.Replace('/', Path.DirectorySeparatorChar));
File.Exists(filePath).ShouldBeTrue($"Missing expected boundary file: {relativePath}");
var contents = File.ReadAllText(filePath);
contents.Contains("LogContext.PushProperty(\"OperationId\"", StringComparison.Ordinal)
.ShouldBeTrue($"Boundary file is missing OperationId log enrichment: {relativePath}");
}
}
/// <summary>
/// Verifies boundary projects include Serilog for LogContext support.
/// </summary>
[Fact]
public void BoundaryProjects_ShouldReferenceSerilog()
{
var repoRoot = FindRepoRoot();
var projects = new[]
{
"src/ZB.MOM.WW.CBDDC.Network/ZB.MOM.WW.CBDDC.Network.csproj",
"src/ZB.MOM.WW.CBDDC.Hosting/ZB.MOM.WW.CBDDC.Hosting.csproj",
"samples/ZB.MOM.WW.CBDDC.Sample.Console/ZB.MOM.WW.CBDDC.Sample.Console.csproj"
};
foreach (var relativePath in projects)
{
var filePath = Path.Combine(repoRoot, relativePath.Replace('/', Path.DirectorySeparatorChar));
File.Exists(filePath).ShouldBeTrue($"Missing project file: {relativePath}");
var contents = File.ReadAllText(filePath);
contents.Contains("<PackageReference Include=\"Serilog\"", StringComparison.Ordinal)
.ShouldBeTrue($"Serilog package reference is required for logging boundary enrichment: {relativePath}");
}
}
private static string FindRepoRoot()
{
var dir = AppContext.BaseDirectory;
for (var i = 0; i < 10 && !string.IsNullOrWhiteSpace(dir); i++)
{
if (File.Exists(Path.Combine(dir, "CBDDC.slnx")))
{
return dir;
}
dir = Directory.GetParent(dir)?.FullName ?? string.Empty;
}
throw new InvalidOperationException("Could not locate repository root containing CBDDC.slnx.");
}
private static bool HasCycle(Dictionary<string, HashSet<string>> graph)
{
var visiting = new HashSet<string>(StringComparer.Ordinal);
var visited = new HashSet<string>(StringComparer.Ordinal);
bool Dfs(string node)
{
if (visiting.Contains(node))
{
return true;
}
if (!visited.Add(node))
{
return false;
}
visiting.Add(node);
foreach (var next in graph[node])
{
if (Dfs(next))
{
return true;
}
}
visiting.Remove(node);
return false;
}
return graph.Keys.Any(Dfs);
}
}