Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/SymbolImport/InstanceDbImportIntegrationTests.cs
2026-04-26 07:04:40 -04:00

97 lines
4.4 KiB
C#

using System.IO;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests.S7_1500;
using ZB.MOM.WW.OtOpcUa.Driver.S7.SymbolImport;
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests.SymbolImport;
/// <summary>
/// PR-S7-D3 / #301 — golden-fixture integration test for instance-DB / FB parameter
/// resolution. Loads <c>Fixtures/sample_tia_export_with_fb_instance.csv</c>, materialises
/// a driver-options object via <see cref="S7DriverFactoryExtensions.AddTiaCsvImport"/>,
/// and exercises the runtime read path against the python-snap7 simulator using the
/// resolved instance-DB addresses.
/// </summary>
/// <remarks>
/// <para>
/// The fixture mixes three categories so a single import covers the full
/// <c>DB type</c> column matrix:
/// <list type="bullet">
/// <item>Two global-DB tags (<c>DB type = Global DB</c>) — pre-existing D1 path.</item>
/// <item>Five instance-DB tags (<c>DB type = Instance DB</c>) — new D3 path.</item>
/// <item>One HMI-hidden row — must be filtered.</item>
/// <item>One M-area probe (no <c>DB type</c>) — global by default.</item>
/// </list>
/// </para>
/// <para>
/// The instance-DB tag addresses deliberately overlap with the snap7 seed offsets
/// baked into <see cref="S7_1500Profile"/> so a successful round-trip proves the
/// resolver normalises addresses identically to the global-DB path. The simulator
/// doesn't know (or care) that a tag came from an FB-instance row — what we're
/// testing is that the <em>importer</em> recognised it and the <em>driver</em>
/// accepted the resolved address verbatim.
/// </para>
/// <para>
/// Auto-skips when no simulator is reachable (build-only on hosts without snap7).
/// </para>
/// </remarks>
[Collection(Snap7ServerCollection.Name)]
[Trait("Category", "Integration")]
[Trait("Device", "S7_1500")]
public sealed class InstanceDbImportIntegrationTests(Snap7ServerFixture sim)
{
private static string FixturePath(string name) =>
Path.Combine(AppContext.BaseDirectory, "Fixtures", name);
[Fact]
public async Task Driver_resolves_fb_instance_then_reads_seeded_member()
{
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
var baseOptions = new S7DriverOptions
{
Host = sim.Host,
Port = sim.Port,
CpuType = global::S7.Net.CpuType.S71500,
Timeout = TimeSpan.FromSeconds(5),
Probe = new S7ProbeOptions { Enabled = false },
Tags = [],
};
var options = baseOptions.AddTiaCsvImport(
FixturePath("sample_tia_export_with_fb_instance.csv"),
out var importResult);
// Fixture: 9 rows total = 1 probe + 2 global-DB + 5 instance-DB + 1 hidden.
// Hidden filtered → SkippedCount = 1; everything else lands.
importResult.ParsedCount.ShouldBe(8);
importResult.SkippedCount.ShouldBe(1);
importResult.InstanceDbCount.ShouldBe(5);
importResult.UdtPlaceholderCount.ShouldBe(0);
importResult.ErrorCount.ShouldBe(0);
options.Tags.Count.ShouldBe(8);
await using var drv = new S7Driver(options, driverInstanceId: "s7-tia-import-fb");
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
// Read three resolved instance-DB members. Each address overlaps a snap7 seed in
// S7_1500Profile, so a successful round-trip proves the resolver pointed the
// driver at the right absolute byte offset.
var snapshots = await drv.ReadAsync(
["MotorFB_Inst.Speed", "MotorFB_Inst.Setpoint", "MotorFB_Inst.Enabled"],
TestContext.Current.CancellationToken);
snapshots.Count.ShouldBe(3);
foreach (var s in snapshots)
s.StatusCode.ShouldBe(0u, "instance-DB resolved address must read end-to-end");
// Speed sits at DB1.DBW10 → SmokeI16Seed; Setpoint at DB1.DBD30 → SmokeF32Seed;
// Enabled at DB1.DBX50.3 → SmokeBool.
Convert.ToInt32(snapshots[0].Value).ShouldBe((int)S7_1500Profile.SmokeI16SeedValue);
Convert.ToSingle(snapshots[1].Value).ShouldBe(S7_1500Profile.SmokeF32SeedValue, tolerance: 0.0001f);
Convert.ToBoolean(snapshots[2].Value).ShouldBeTrue();
}
}