using System.Reflection;
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Tests;
///
/// Pins the MQTT project layering that Gitea #512 restored: .Browser depends on the shared
/// .Contracts + .Transport leaves, never on the runtime .Driver.
///
///
///
/// Why this is worth a test. .Browser used to reference the runtime driver, and the
/// AdminUI references .Browser — so the AdminUI's build/deploy graph transitively contained
/// the whole MQTT driver runtime plus Core, Polly.Core and Serilog. Nothing failed; the cost was
/// invisible. A reference re-added out of convenience would be invisible in exactly the same way,
/// which is precisely what a guard is for.
///
///
/// What it can and cannot catch. It asserts on emitted assembly references, so it fires when
/// the browser actually uses a driver-runtime type — the case that matters. A
/// ProjectReference re-added but never used would not trip it (the compiler emits no
/// reference), so the csproj comment remains the primary record of the decision.
///
///
/// That gap is now covered by DriverBrowserLayeringTests (Commons.Tests), which
/// scans the csproj graph of every *.Browser project in the tree — added for Gitea #526,
/// after the identical violation was found sitting in Driver.Sql.Browser with no guard
/// watching it, because this file was written for the one driver that had an issue filed. What
/// stays here is the MQTT-specific half: the .Transport leaf shape and the
/// MQTTnet-free-ness of .Contracts.
///
///
[Trait("Category", "Unit")]
public sealed class MqttBrowserLayeringTests
{
private const string BrowserAssembly = "ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Browser";
private const string DriverRuntimeAssembly = "ZB.MOM.WW.OtOpcUa.Driver.Mqtt";
private const string ContractsAssembly = "ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Contracts";
private const string TransportAssembly = "ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Transport";
private static string[] BrowserReferences()
{
var browser = Assembly.Load(BrowserAssembly);
return [.. browser.GetReferencedAssemblies().Select(a => a.Name!)];
}
[Fact]
public void Browser_does_not_reference_the_runtime_driver()
=> BrowserReferences().ShouldNotContain(
DriverRuntimeAssembly,
"the MQTT browser is back to referencing the runtime driver — every other bespoke browser " +
"in this repo (OpcUaClient, Galaxy) depends on its .Contracts leaf only, and this reference " +
"reaches the AdminUI. If shared connection policy is needed, put it in .Transport; if shared " +
"protocol/codec is needed, put it in .Contracts. Do NOT copy the TLS/CA-pin logic.");
[Fact]
public void Browser_reaches_the_shared_leaves_instead()
{
// The positive control. Asserting only the absence above would also pass if the browser stopped
// referencing anything MQTT at all — e.g. if this test loaded the wrong assembly, or the type
// names drifted — so pin what it SHOULD depend on in the same breath.
var references = BrowserReferences();
references.ShouldContain(ContractsAssembly);
references.ShouldContain(TransportAssembly);
}
[Fact]
public void Transport_stays_a_leaf_that_does_not_reach_back_into_the_runtime_driver()
{
// .Transport holds connection POLICY only. If it ever referenced the runtime driver, routing the
// browser through it would restore the coupling this whole change removed, one hop further out.
var references = Assembly.Load(TransportAssembly)
.GetReferencedAssemblies()
.Select(a => a.Name!)
.ToArray();
references.ShouldNotContain(DriverRuntimeAssembly);
references.ShouldNotContain(BrowserAssembly);
}
[Fact]
public void Contracts_stays_transport_free()
=> Assembly.Load(ContractsAssembly)
.GetReferencedAssemblies()
.Select(a => a.Name!)
.ShouldNotContain(
"MQTTnet",
"Driver.Mqtt.Contracts is deliberately transport-free — it carries the Sparkplug wire " +
"schema and codec, and is referenced by ControlPlane, which has no business with a " +
"broker client. MQTTnet-typed connection policy belongs in .Transport.");
}