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; /// /// PR-S7-D3 / #301 — golden-fixture integration test for instance-DB / FB parameter /// resolution. Loads Fixtures/sample_tia_export_with_fb_instance.csv, materialises /// a driver-options object via , /// and exercises the runtime read path against the python-snap7 simulator using the /// resolved instance-DB addresses. /// /// /// /// The fixture mixes three categories so a single import covers the full /// DB type column matrix: /// /// Two global-DB tags (DB type = Global DB) — pre-existing D1 path. /// Five instance-DB tags (DB type = Instance DB) — new D3 path. /// One HMI-hidden row — must be filtered. /// One M-area probe (no DB type) — global by default. /// /// /// /// The instance-DB tag addresses deliberately overlap with the snap7 seed offsets /// baked into 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 importer recognised it and the driver /// accepted the resolved address verbatim. /// /// /// Auto-skips when no simulator is reachable (build-only on hosts without snap7). /// /// [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(); } }