using System.IO; using System.Text.Json; using CliFx.Exceptions; using CliFx.Infrastructure; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.S7.Cli.Commands; namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Cli.Tests; /// /// Coverage for the import-symbols CLI command. The command is intentionally /// thin (open file, hand to TiaCsvImporter / AwlImporter, serialise) — /// these tests focus on the I/O + flag-handling shape rather than re-running the /// parser. /// [Trait("Category", "Unit")] public sealed class ImportSymbolsCommandTests { private const string CanonicalTiaCsv = """ Name,Path,Data type,Logical address,Comment,Hmi accessible MotorSpeed,Default,Int,%MW0,Motor speed,True TankLevel,Default,Real,%MD4,Tank level,True RunFlag,Default,Bool,%M0.0,Run flag,True """; private const string CanonicalAwl = """ VAR_GLOBAL Speed : INT; Pressure : REAL; END_VAR """; [Fact] public async Task Execute_with_tia_csv_emits_json_fragment_with_three_tags() { var path = Path.Combine(Path.GetTempPath(), $"s7-tia-{Guid.NewGuid():N}.csv"); File.WriteAllText(path, CanonicalTiaCsv); try { using var console = new FakeInMemoryConsole(); var cmd = new ImportSymbolsCommand { File = path, Format = "tia", Emit = "appsettings-fragment", }; await cmd.ExecuteAsync(console); var output = console.ReadOutputString(); output.ShouldContain("\"Tags\""); using var doc = JsonDocument.Parse(output); var tags = doc.RootElement.GetProperty("Tags"); tags.GetArrayLength().ShouldBe(3); tags[0].GetProperty("Name").GetString().ShouldBe("MotorSpeed"); tags[0].GetProperty("Address").GetString().ShouldBe("MW0"); tags[0].GetProperty("DataType").GetString().ShouldBe("Int16"); tags[2].GetProperty("DataType").GetString().ShouldBe("Bool"); } finally { try { File.Delete(path); } catch { /* best-effort */ } } } [Fact] public async Task Execute_with_summary_emit_prints_counters() { var path = Path.Combine(Path.GetTempPath(), $"s7-tia-{Guid.NewGuid():N}.csv"); File.WriteAllText(path, CanonicalTiaCsv); try { using var console = new FakeInMemoryConsole(); var cmd = new ImportSymbolsCommand { File = path, Format = "tia", Emit = "summary", }; await cmd.ExecuteAsync(console); var output = console.ReadOutputString(); output.ShouldContain("Imported 3"); output.ShouldContain("udt-placeholders 0"); } finally { try { File.Delete(path); } catch { /* best-effort */ } } } [Fact] public async Task Execute_with_awl_format_emits_var_global_tags() { var path = Path.Combine(Path.GetTempPath(), $"s7-awl-{Guid.NewGuid():N}.awl"); File.WriteAllText(path, CanonicalAwl); try { using var console = new FakeInMemoryConsole(); var cmd = new ImportSymbolsCommand { File = path, Format = "awl", Emit = "appsettings-fragment", }; await cmd.ExecuteAsync(console); var output = console.ReadOutputString(); using var doc = JsonDocument.Parse(output); var tags = doc.RootElement.GetProperty("Tags"); tags.GetArrayLength().ShouldBe(2); tags[0].GetProperty("Address").GetString().ShouldBe("MW0"); tags[1].GetProperty("Address").GetString().ShouldBe("MD2"); } finally { try { File.Delete(path); } catch { /* best-effort */ } } } [Fact] public async Task Execute_with_output_path_writes_file_and_prints_summary() { var inputPath = Path.Combine(Path.GetTempPath(), $"s7-tia-{Guid.NewGuid():N}.csv"); var outputPath = Path.Combine(Path.GetTempPath(), $"s7-tia-{Guid.NewGuid():N}.json"); File.WriteAllText(inputPath, CanonicalTiaCsv); try { using var console = new FakeInMemoryConsole(); var cmd = new ImportSymbolsCommand { File = inputPath, Format = "tia", Emit = "appsettings-fragment", Output = outputPath, }; await cmd.ExecuteAsync(console); File.Exists(outputPath).ShouldBeTrue(); var fileBody = File.ReadAllText(outputPath); using var doc = JsonDocument.Parse(fileBody); doc.RootElement.GetProperty("Tags").GetArrayLength().ShouldBe(3); console.ReadOutputString().ShouldContain("Wrote 3"); } finally { try { File.Delete(inputPath); } catch { /* best-effort */ } try { File.Delete(outputPath); } catch { /* best-effort */ } } } [Fact] public async Task Execute_with_missing_file_throws_command_exception() { var missing = Path.Combine(Path.GetTempPath(), $"does-not-exist-{Guid.NewGuid():N}.csv"); using var console = new FakeInMemoryConsole(); var cmd = new ImportSymbolsCommand { File = missing, }; var ex = await Should.ThrowAsync(async () => await cmd.ExecuteAsync(console)); ex.ExitCode.ShouldBe(1); } [Fact] public async Task Execute_with_unknown_format_throws_command_exception() { var path = Path.Combine(Path.GetTempPath(), $"s7-tia-{Guid.NewGuid():N}.csv"); File.WriteAllText(path, CanonicalTiaCsv); try { using var console = new FakeInMemoryConsole(); var cmd = new ImportSymbolsCommand { File = path, Format = "yaml", }; var ex = await Should.ThrowAsync(async () => await cmd.ExecuteAsync(console)); ex.ExitCode.ShouldBe(2); } finally { try { File.Delete(path); } catch { /* best-effort */ } } } [Fact] public async Task Execute_with_unknown_emit_throws_command_exception() { var path = Path.Combine(Path.GetTempPath(), $"s7-tia-{Guid.NewGuid():N}.csv"); File.WriteAllText(path, CanonicalTiaCsv); try { using var console = new FakeInMemoryConsole(); var cmd = new ImportSymbolsCommand { File = path, Format = "tia", Emit = "xml", }; var ex = await Should.ThrowAsync(async () => await cmd.ExecuteAsync(console)); ex.ExitCode.ShouldBe(2); } finally { try { File.Delete(path); } catch { /* best-effort */ } } } }