Phase 06 complete: - Namespace renamed in 62 .cs files (namespace + using directives) - AssemblyName + RootNamespace updated in csproj files - FanucProgramManager.csproj → NcProgramManager.csproj - FanucProgramManager.sln → NcProgramManager.sln - FanucProgramManager.Tests/ → NcProgramManager.Tests/ - FANUCMachine.cs deleted (dead code, zero references) Fanuc-prefixed class names kept (FanucMachine, FanucProgram, IFanucMachine) — manufacturer identifiers, not project names. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
221 lines
9.2 KiB
C#
221 lines
9.2 KiB
C#
using System;
|
|
using NUnit.Framework;
|
|
using Moq;
|
|
using NcProgramManager.Cnc.Fanuc;
|
|
using NcProgramManager.Cnc.Models;
|
|
|
|
namespace NcProgramManager.Tests.Unit
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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<T>(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<T>(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<IFocasDialect> 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<IFocasDialect>();
|
|
dialect.Setup(d => d.UploadStart(It.IsAny<ushort>(), It.IsAny<string>(), It.IsAny<long>(), It.IsAny<short>()))
|
|
.Returns(Focas1.EW_OK);
|
|
dialect.Setup(d => d.UploadData(It.IsAny<ushort>(), ref It.Ref<int>.IsAny, It.IsAny<char[]>()))
|
|
.Returns(Focas1.EW_OK);
|
|
dialect.Setup(d => d.UploadEnd(It.IsAny<ushort>())).Returns(Focas1.EW_OK);
|
|
|
|
using (var machine = CreateConnectedWithMockDialect(dialect))
|
|
{
|
|
machine.ReadProgramAsync("O1234").GetAwaiter().GetResult();
|
|
}
|
|
|
|
dialect.Verify(d => d.UploadStart(It.IsAny<ushort>(), "O1234", It.IsAny<long>(), 0), Times.Once);
|
|
dialect.Verify(d => d.UploadEnd(It.IsAny<ushort>()), Times.Once);
|
|
}
|
|
|
|
[Test]
|
|
public void ReadProgramAsync_UploadDataReturnsContent_ContentInResult()
|
|
{
|
|
var programContent = "O1234\nG0 X0\nM30\n%";
|
|
var chars = programContent.ToCharArray();
|
|
|
|
var dialect = new Mock<IFocasDialect>();
|
|
dialect.Setup(d => d.UploadStart(It.IsAny<ushort>(), It.IsAny<string>(), It.IsAny<long>(), It.IsAny<short>()))
|
|
.Returns(Focas1.EW_OK);
|
|
dialect.Setup(d => d.UploadData(It.IsAny<ushort>(), ref It.Ref<int>.IsAny, It.IsAny<char[]>()))
|
|
.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<ushort>())).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<IFocasDialect>();
|
|
dialect.Setup(d => d.UploadStart(It.IsAny<ushort>(), It.IsAny<string>(), It.IsAny<long>(), It.IsAny<short>()))
|
|
.Returns(Focas1.EW_OK);
|
|
dialect.Setup(d => d.UploadData(It.IsAny<ushort>(), ref It.Ref<int>.IsAny, It.IsAny<char[]>()))
|
|
.Returns(Focas1.EW_OK);
|
|
dialect.Setup(d => d.UploadEnd(It.IsAny<ushort>())).Returns(Focas1.EW_OK);
|
|
|
|
using (var machine = CreateConnectedWithMockDialect(dialect))
|
|
{
|
|
machine.ReadToolDataAsync("O1234").GetAwaiter().GetResult();
|
|
}
|
|
|
|
dialect.Verify(d => d.UploadStart(It.IsAny<ushort>(), "O1234", It.IsAny<long>(), (short)1), Times.Once);
|
|
}
|
|
|
|
[Test]
|
|
public void ReadWorkZeroDataAsync_CallsUploadStartWithFileType5()
|
|
{
|
|
var dialect = new Mock<IFocasDialect>();
|
|
dialect.Setup(d => d.UploadStart(It.IsAny<ushort>(), It.IsAny<string>(), It.IsAny<long>(), It.IsAny<short>()))
|
|
.Returns(Focas1.EW_OK);
|
|
dialect.Setup(d => d.UploadData(It.IsAny<ushort>(), ref It.Ref<int>.IsAny, It.IsAny<char[]>()))
|
|
.Returns(Focas1.EW_OK);
|
|
dialect.Setup(d => d.UploadEnd(It.IsAny<ushort>())).Returns(Focas1.EW_OK);
|
|
|
|
using (var machine = CreateConnectedWithMockDialect(dialect))
|
|
{
|
|
machine.ReadWorkZeroDataAsync("O1234").GetAwaiter().GetResult();
|
|
}
|
|
|
|
dialect.Verify(d => d.UploadStart(It.IsAny<ushort>(), "O1234", It.IsAny<long>(), (short)5), Times.Once);
|
|
}
|
|
|
|
[Test]
|
|
public void ReadProgramAsync_UploadStartFails_ResultIsFail()
|
|
{
|
|
var dialect = new Mock<IFocasDialect>();
|
|
dialect.Setup(d => d.UploadStart(It.IsAny<ushort>(), It.IsAny<string>(), It.IsAny<long>(), It.IsAny<short>()))
|
|
.Returns(-3); // EW_SOCKET or any error
|
|
dialect.Setup(d => d.UploadEnd(It.IsAny<ushort>())).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<IFocasDialect>();
|
|
|
|
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<IFocasDialect>();
|
|
|
|
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"));
|
|
}
|
|
}
|
|
}
|
|
}
|