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"));
}
}
}
}