using System; using NUnit.Framework; using Moq; using NcProgramManager.Cnc; using NcProgramManager.Cnc.Models; using NcProgramManager.Cnc.Mitsubishi; namespace NcProgramManager.Tests.Unit { [TestFixture] public class MitsubishiMachineTests { private MitsubishiConnectionConfig _config; private Mock _ftpMock; private Mock _validatorMock; private MitsubishiMachine _machine; [SetUp] public void SetUp() { _config = new MitsubishiConnectionConfig { Name = "TestMitsubishi", IpAddress = "192.168.1.3", Port = 21, ProgramDirectory = "/PRG/" }; _ftpMock = new Mock(MockBehavior.Strict); // Always-ok validator so existing write tests are not affected by validation rules _validatorMock = new Mock(); _validatorMock.Setup(v => v.Validate(It.IsAny(), It.IsAny())) .Returns(ValidationResult.Ok()); _machine = new MitsubishiMachine(_config, _ftpMock.Object, _validatorMock.Object); } [TearDown] public void TearDown() { _machine?.Dispose(); } // ConnectAsync [Test] public void ConnectAsync_PingTrue_StateConnectedAndResultOk() { _ftpMock.Setup(f => f.Ping()).Returns(true); var result = _machine.ConnectAsync().GetAwaiter().GetResult(); Assert.That(result.Success, Is.True); Assert.That(_machine.State, Is.EqualTo(ConnectionState.Connected)); } [Test] public void ConnectAsync_PingFalse_StateFaultedAndResultFail() { _ftpMock.Setup(f => f.Ping()).Returns(false); var result = _machine.ConnectAsync().GetAwaiter().GetResult(); Assert.That(result.Success, Is.False); Assert.That(_machine.State, Is.EqualTo(ConnectionState.Faulted)); } // ReadProgramAsync — no .MPF extension for Mitsubishi [Test] public void ReadProgramAsync_FullPath_CallsDownloadFileNoExtension() { _ftpMock.Setup(f => f.Ping()).Returns(true); _ftpMock.Setup(f => f.DownloadFile("/PRG/O1234")).Returns("NC_DATA"); _machine.ConnectAsync().GetAwaiter().GetResult(); var result = _machine.ReadProgramAsync("/PRG/O1234").GetAwaiter().GetResult(); Assert.That(result.Success, Is.True); Assert.That(result.Value, Is.EqualTo("NC_DATA")); _ftpMock.Verify(f => f.DownloadFile("/PRG/O1234"), Times.Once); } [Test] public void ReadProgramAsync_ShortName_ResolvesWithDirectoryNoExtension() { _ftpMock.Setup(f => f.Ping()).Returns(true); _ftpMock.Setup(f => f.DownloadFile("/PRG/O1234")).Returns("DATA"); _machine.ConnectAsync().GetAwaiter().GetResult(); _machine.ReadProgramAsync("O1234").GetAwaiter().GetResult(); // Must NOT append .MPF _ftpMock.Verify(f => f.DownloadFile("/PRG/O1234"), Times.Once); _ftpMock.Verify(f => f.DownloadFile(It.Is(s => s.EndsWith(".MPF"))), Times.Never); } // WriteProgramAsync [Test] public void WriteProgramAsync_FullPath_CallsUploadFileNoExtension() { // ISS-020: write resolves the path identically to read (path = program // identifier / full path, not a directory combined with program.Name). _ftpMock.Setup(f => f.Ping()).Returns(true); _ftpMock.Setup(f => f.UploadFile("/PRG/O1234", "CONTENT")); _machine.ConnectAsync().GetAwaiter().GetResult(); var prog = new CncProgram { Name = "O1234", Content = "CONTENT" }; var result = _machine.WriteProgramAsync("/PRG/O1234", prog).GetAwaiter().GetResult(); Assert.That(result.Success, Is.True); _ftpMock.Verify(f => f.UploadFile("/PRG/O1234", "CONTENT"), Times.Once); _ftpMock.Verify(f => f.UploadFile(It.Is(s => s.EndsWith(".MPF")), It.IsAny()), Times.Never); } [Test] public void WriteProgramAsync_ShortName_ResolvesWithDirectory_SymmetricWithRead() { // ISS-020 regression: -path "1" must write to ProgramDirectory + "1" // (the same location ReadProgramAsync("1") reads), so roundtrip works. _ftpMock.Setup(f => f.Ping()).Returns(true); _ftpMock.Setup(f => f.UploadFile("/PRG/1", "CONTENT")); _machine.ConnectAsync().GetAwaiter().GetResult(); var prog = new CncProgram { Name = "O0001", Content = "CONTENT" }; var result = _machine.WriteProgramAsync("1", prog).GetAwaiter().GetResult(); Assert.That(result.Success, Is.True); _ftpMock.Verify(f => f.UploadFile("/PRG/1", "CONTENT"), Times.Once); } [Test] public void WriteProgramAsync_EmptyContent_FailsWithoutCallingUploadFile() { _ftpMock.Setup(f => f.Ping()).Returns(true); _machine.ConnectAsync().GetAwaiter().GetResult(); var prog = new CncProgram { Name = "O1234", Content = "" }; var result = _machine.WriteProgramAsync("/PRG/", prog).GetAwaiter().GetResult(); Assert.That(result.Success, Is.False); _ftpMock.Verify(f => f.UploadFile(It.IsAny(), It.IsAny()), Times.Never); } [Test] public void WriteProgramAsync_NullProgram_FailsWithoutCallingUploadFile() { _ftpMock.Setup(f => f.Ping()).Returns(true); _machine.ConnectAsync().GetAwaiter().GetResult(); var result = _machine.WriteProgramAsync("/PRG/", null).GetAwaiter().GetResult(); Assert.That(result.Success, Is.False); _ftpMock.Verify(f => f.UploadFile(It.IsAny(), It.IsAny()), Times.Never); } // DeleteProgramAsync [Test] public void DeleteProgramAsync_ValidPath_CallsDeleteFile() { _ftpMock.Setup(f => f.Ping()).Returns(true); _ftpMock.Setup(f => f.DeleteFile("/PRG/O1234")); _machine.ConnectAsync().GetAwaiter().GetResult(); var result = _machine.DeleteProgramAsync("/PRG/O1234").GetAwaiter().GetResult(); Assert.That(result.Success, Is.True); _ftpMock.Verify(f => f.DeleteFile("/PRG/O1234"), Times.Once); } // ReadActiveProgramAsync [Test] public void ReadActiveProgramAsync_ReturnsFailNotSupported() { var result = _machine.ReadActiveProgramAsync().GetAwaiter().GetResult(); Assert.That(result.Success, Is.False); } } }