using System; using NUnit.Framework; using Moq; using FanucProgramManager.Cnc.Fanuc; using FanucProgramManager.Cnc.Models; namespace FanucProgramManager.Tests.Unit { /// /// FanucMachine calls real FOCAS DLL in ConnectCore and most methods guard /// via EnsureConnected. Tests that require an active FOCAS handle are marked Ignore. /// The tests here verify dialect call patterns by setting machine state via /// reflection (bypass EnsureConnected) after injecting a mock dialect. /// [TestFixture] public class FanucMachineTests { // Used in Returns() to modify the ref int len parameter — lambdas cannot have ref params. private delegate short UploadDataDelegate(ushort hndl, ref int len, char[] data); private FanucConnectionConfig EthernetConfig() { return new FanucConnectionConfig { Name = "TestFanuc", Transport = FanucTransport.Ethernet, IpAddress = "127.0.0.1", Port = 8193, ConnectTimeout = TimeSpan.FromSeconds(1) }; } private static T GetRequiredField(object target, string name) { var f = target.GetType().GetField(name, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) ?? throw new InvalidOperationException( string.Format("Field '{0}' not found on {1}. Field may have been renamed.", name, target.GetType().Name)); return (T)f.GetValue(target); } private static void SetRequiredField(object target, string name, T value) { var f = target.GetType().GetField(name, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) ?? throw new InvalidOperationException( string.Format("Field '{0}' not found on {1}. Field may have been renamed.", name, target.GetType().Name)); f.SetValue(target, value); } private FanucMachine CreateConnectedWithMockDialect(Mock dialectMock) { var machine = new FanucMachine(EthernetConfig()); SetRequiredField(machine, "_state", ConnectionState.Connected); SetRequiredField(machine, "_dialect", dialectMock.Object); return machine; } [Test] [Ignore("Requires Fanuc FOCAS DLL — no mock injection path for ConnectCore")] public void ConnectAsync_FocasReturnsOk_ResultSuccess() { // Would need real FOCAS DLL or a way to intercept Focas1.cnc_allclibhndl3 } [Test] [Ignore("Requires Fanuc FOCAS DLL")] public void ConnectAsync_FocasReturnsError_ResultFail() { // Same — Focas1 static calls cannot be intercepted without DLL } [Test] // Moq default: ref int len stays 0 after UploadData call, loop exits via else-branch (len == 0). // This test verifies call sequence only, not data content. // See ReadProgramAsync_UploadDataReturnsContent_ContentInResult for data-reading coverage. public void ReadProgramAsync_CallsUploadStartThenDataThenEnd() { var dialect = new Mock(); dialect.Setup(d => d.UploadStart(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Focas1.EW_OK); dialect.Setup(d => d.UploadData(It.IsAny(), ref It.Ref.IsAny, It.IsAny())) .Returns(Focas1.EW_OK); dialect.Setup(d => d.UploadEnd(It.IsAny())).Returns(Focas1.EW_OK); using (var machine = CreateConnectedWithMockDialect(dialect)) { machine.ReadProgramAsync("O1234").GetAwaiter().GetResult(); } dialect.Verify(d => d.UploadStart(It.IsAny(), "O1234", It.IsAny(), 0), Times.Once); dialect.Verify(d => d.UploadEnd(It.IsAny()), Times.Once); } [Test] public void ReadProgramAsync_UploadDataReturnsContent_ContentInResult() { var programContent = "O1234\nG0 X0\nM30\n%"; var chars = programContent.ToCharArray(); var dialect = new Mock(); dialect.Setup(d => d.UploadStart(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Focas1.EW_OK); dialect.Setup(d => d.UploadData(It.IsAny(), ref It.Ref.IsAny, It.IsAny())) .Returns(new UploadDataDelegate(delegate(ushort h, ref int len, char[] buf) { Array.Copy(chars, buf, chars.Length); len = chars.Length; return Focas1.EW_OK; })); dialect.Setup(d => d.UploadEnd(It.IsAny())).Returns(Focas1.EW_OK); using (var machine = CreateConnectedWithMockDialect(dialect)) { var result = machine.ReadProgramAsync("O1234").GetAwaiter().GetResult(); Assert.That(result.Success, Is.True); Assert.That(result.Value, Is.EqualTo(programContent)); } } [Test] public void ReadToolDataAsync_CallsUploadStartWithFileType1() { var dialect = new Mock(); dialect.Setup(d => d.UploadStart(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Focas1.EW_OK); dialect.Setup(d => d.UploadData(It.IsAny(), ref It.Ref.IsAny, It.IsAny())) .Returns(Focas1.EW_OK); dialect.Setup(d => d.UploadEnd(It.IsAny())).Returns(Focas1.EW_OK); using (var machine = CreateConnectedWithMockDialect(dialect)) { machine.ReadToolDataAsync("O1234").GetAwaiter().GetResult(); } dialect.Verify(d => d.UploadStart(It.IsAny(), "O1234", It.IsAny(), (short)1), Times.Once); } [Test] public void ReadWorkZeroDataAsync_CallsUploadStartWithFileType5() { var dialect = new Mock(); dialect.Setup(d => d.UploadStart(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Focas1.EW_OK); dialect.Setup(d => d.UploadData(It.IsAny(), ref It.Ref.IsAny, It.IsAny())) .Returns(Focas1.EW_OK); dialect.Setup(d => d.UploadEnd(It.IsAny())).Returns(Focas1.EW_OK); using (var machine = CreateConnectedWithMockDialect(dialect)) { machine.ReadWorkZeroDataAsync("O1234").GetAwaiter().GetResult(); } dialect.Verify(d => d.UploadStart(It.IsAny(), "O1234", It.IsAny(), (short)5), Times.Once); } [Test] public void ReadProgramAsync_UploadStartFails_ResultIsFail() { var dialect = new Mock(); dialect.Setup(d => d.UploadStart(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(-3); // EW_SOCKET or any error dialect.Setup(d => d.UploadEnd(It.IsAny())).Returns(Focas1.EW_OK); using (var machine = CreateConnectedWithMockDialect(dialect)) { var result = machine.ReadProgramAsync("O1234").GetAwaiter().GetResult(); Assert.That(result.Success, Is.False); } } [Test] public void WriteProgramAsync_NullContent_ResultFail() { var dialect = new Mock(); using (var machine = CreateConnectedWithMockDialect(dialect)) { var prog = new CncProgram { Name = "O1234", Content = null }; var result = machine.WriteProgramAsync("//CNC_MEM/USER/PATH1/", prog).GetAwaiter().GetResult(); Assert.That(result.Success, Is.False); } } [Test] public void WriteProgramAsync_InvalidProgramName_ValidationFails() { var dialect = new Mock(); using (var machine = CreateConnectedWithMockDialect(dialect)) { // "PROG" does not match Fanuc O#### name rule — validator rejects before any FOCAS call var prog = new CncProgram { Name = "PROG", Content = "O1234\nG00 X0\nM30" }; var result = machine.WriteProgramAsync("//CNC_MEM/USER/PATH1/", prog).GetAwaiter().GetResult(); Assert.That(result.Success, Is.False); } } [Test] public void State_InitialState_IsDisconnected() { using (var machine = new FanucMachine(EthernetConfig())) { Assert.That(machine.State, Is.EqualTo(ConnectionState.Disconnected)); } } [Test] public void Name_ReturnsConfigName() { using (var machine = new FanucMachine(EthernetConfig())) { Assert.That(machine.Name, Is.EqualTo("TestFanuc")); } } } }