test(host): a mesh gRPC map added outside MeshGrpcEndpoints fails the build
MeshGrpcMapConventionTests - source-scan of Program.cs (solution-root walk, following FlushLoggingConventionTests.cs's idiom): zero direct MapGrpcService</MapZbLocalDbSync( occurrences, exactly one MapOtOpcUaMeshGrpcServices( call. Complements the endpoint-metadata census (T1-T3): the census catches a map added to MeshGrpcEndpoints without the AllowAnonymous convention, but cannot see a map that bypasses the helper and goes straight into Program.cs - that shape never reaches MapOtOpcUaMeshGrpcServices at all. Verified red against the pre-T1 Program.cs (five inline map sites, zero mapper calls) and green against the current extracted form.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
|
||||
|
||||
/// <summary>
|
||||
/// R3-01 T7 — a source-scan guard, following the solution-root-walk idiom
|
||||
/// <c>ZB.MOM.WW.OtOpcUa.Driver.S7.Cli.Tests.FlushLoggingConventionTests</c> uses. Complements the
|
||||
/// endpoint-metadata census (<c>MeshGrpcAnonymousMetadataTests</c>): the census catches a map
|
||||
/// added to <c>MeshGrpcEndpoints</c> without the <c>AllowAnonymous</c> convention, but it cannot
|
||||
/// catch a map added to <c>Program.cs</c> that bypasses the helper entirely — that shape never
|
||||
/// goes through <c>MapOtOpcUaMeshGrpcServices</c> at all, so the census would simply never see it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Would have FAILED at HEAD before T1: <c>Program.cs</c> used to carry the five map sites
|
||||
/// inline (<c>MapZbLocalDbSync()</c>, four <c>MapGrpcService<T>()</c> calls) with no
|
||||
/// <c>MapOtOpcUaMeshGrpcServices</c> call at all.
|
||||
/// </remarks>
|
||||
public sealed class MeshGrpcMapConventionTests
|
||||
{
|
||||
private static readonly string ProgramCsPath = LocateProgramCs();
|
||||
|
||||
[Fact]
|
||||
public void ProgramCs_ContainsNoDirectMeshGrpcMapSites_AndExactlyOneMapperCall()
|
||||
{
|
||||
var source = File.ReadAllText(ProgramCsPath);
|
||||
|
||||
// Guard-of-the-guard: a bad path (an empty file, or one that never had the mapper in the
|
||||
// first place) must not pass this test by finding nothing to count.
|
||||
source.ShouldNotBeNullOrWhiteSpace();
|
||||
source.ShouldContain("MapOtOpcUaMeshGrpcServices(");
|
||||
|
||||
CountOccurrences(source, "MapGrpcService<").ShouldBe(0,
|
||||
"every MapGrpcService<T>() call must live inside MeshGrpcEndpoints.MapOtOpcUaMeshGrpcServices, " +
|
||||
"not inline in Program.cs (03/S14 — a map added outside the helper is invisible to the " +
|
||||
"endpoint-metadata census).");
|
||||
CountOccurrences(source, "MapZbLocalDbSync(").ShouldBe(0,
|
||||
"the LocalDb sync map must live inside MeshGrpcEndpoints.MapOtOpcUaMeshGrpcServices, not inline " +
|
||||
"in Program.cs, for the same reason as MapGrpcService<T>() above.");
|
||||
CountOccurrences(source, "MapOtOpcUaMeshGrpcServices(").ShouldBe(1,
|
||||
"Program.cs must call the mapper exactly once; more than one call would map the mesh gRPC " +
|
||||
"services twice (a route conflict) and zero calls would drop them entirely.");
|
||||
}
|
||||
|
||||
private static int CountOccurrences(string source, string needle)
|
||||
{
|
||||
var count = 0;
|
||||
var index = 0;
|
||||
while ((index = source.IndexOf(needle, index, StringComparison.Ordinal)) >= 0)
|
||||
{
|
||||
count++;
|
||||
index += needle.Length;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static string LocateProgramCs()
|
||||
{
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "ZB.MOM.WW.OtOpcUa.slnx")))
|
||||
dir = dir.Parent;
|
||||
dir.ShouldNotBeNull("Could not find solution root (ZB.MOM.WW.OtOpcUa.slnx).");
|
||||
return Path.Combine(dir!.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.Host", "Program.cs");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user