Phase 16 (v0.6 milestone) — supporto Fagor CNC via FTP, riuso pattern Siemens/Mitsubishi (FtpWebRequest + ASCII). Modelli supportati: 8060/8065/8070 (FTP nativo), 8055 (con opzione Ethernet). Modelli 8025-8050 esclusi (solo RS232). Code: - Cnc/Fagor/* — FagorConnectionConfig, IFagorFtpClient, FagorFtpClient, FagorMachine. ProgramExtension configurabile (default .nc), ProgramDirectory /Users/Prg/. - Cnc/Models/CncManufacturer.cs — enum + Fagor. - Cnc/CncMachineFactory.cs — case Fagor, porta default 21. - Cnc/NcProgramValidator.cs — refactor IsRestrictiveDialect(): block-80/no-tab/no-lowercase/nested-parens/comment-32 ora applicati solo a Fanuc+Mitsubishi (Heidenhain/Siemens/Fagor esenti). Fagor name rules: max 24 char, no ñ (escape ñ per safety codepage), stem alphanumerico + spazi (estensione separata). - ArgParser.cs — case "fagor". - Program.cs — help text manufacturer/username/password. Tests: - FagorMachineTests — 12 unit (10 mock + 2 validator reale). - FagorMachineIntegrationTests — 5 stub-based (riusa FtpServerStub) + 1 hardware skip via FAGOR_TEST_IP env var. - NcProgramValidatorTests — 12 nuovi Fagor (8 + 4 extension coverage), 6 lock Siemens/Heidenhain exemption, 3 esistenti Siemens→Fanuc. - CncMachineFactoryTests — Create_Fagor_ReturnsFagorMachine. Docs: - README.md — sezione "Controller Fagor" italiano: modelli supportati/ non supportati, regole nome, env var test. Build: - *.csproj — 4 Compile Include main + 2 test. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
212 lines
7.7 KiB
C#
212 lines
7.7 KiB
C#
using System;
|
|
using NUnit.Framework;
|
|
using Moq;
|
|
using NcProgramManager.Cnc;
|
|
using NcProgramManager.Cnc.Models;
|
|
using NcProgramManager.Cnc.Fagor;
|
|
|
|
namespace NcProgramManager.Tests.Unit
|
|
{
|
|
[TestFixture]
|
|
public class FagorMachineTests
|
|
{
|
|
private FagorConnectionConfig _config;
|
|
private Mock<IFagorFtpClient> _ftpMock;
|
|
private Mock<INcProgramValidator> _validatorMock;
|
|
private FagorMachine _machine;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_config = new FagorConnectionConfig
|
|
{
|
|
Name = "TestFagor",
|
|
IpAddress = "192.168.1.2",
|
|
Port = 21,
|
|
ProgramDirectory = "/Users/Prg/",
|
|
ProgramExtension = ".nc"
|
|
};
|
|
_ftpMock = new Mock<IFagorFtpClient>(MockBehavior.Strict);
|
|
// Always-ok validator so existing write tests are not affected by validation rules
|
|
_validatorMock = new Mock<INcProgramValidator>();
|
|
_validatorMock.Setup(v => v.Validate(It.IsAny<string>(), It.IsAny<string>()))
|
|
.Returns(ValidationResult.Ok());
|
|
_machine = new FagorMachine(_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(result.Value, 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
|
|
|
|
[Test]
|
|
public void ReadProgramAsync_FullPath_CallsDownloadFileWithPath()
|
|
{
|
|
_ftpMock.Setup(f => f.Ping()).Returns(true);
|
|
_ftpMock.Setup(f => f.DownloadFile("/Users/Prg/PROG1.nc")).Returns("CONTENT");
|
|
_machine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
var result = _machine.ReadProgramAsync("/Users/Prg/PROG1.nc").GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.True);
|
|
Assert.That(result.Value, Is.EqualTo("CONTENT"));
|
|
_ftpMock.Verify(f => f.DownloadFile("/Users/Prg/PROG1.nc"), Times.Once);
|
|
}
|
|
|
|
[Test]
|
|
public void ReadProgramAsync_ShortName_ResolvesWithDirectoryAndExtension()
|
|
{
|
|
_ftpMock.Setup(f => f.Ping()).Returns(true);
|
|
_ftpMock.Setup(f => f.DownloadFile("/Users/Prg/PROG1.nc")).Returns("DATA");
|
|
_machine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
_machine.ReadProgramAsync("PROG1").GetAwaiter().GetResult();
|
|
|
|
_ftpMock.Verify(f => f.DownloadFile("/Users/Prg/PROG1.nc"), Times.Once);
|
|
}
|
|
|
|
// WriteProgramAsync
|
|
|
|
[Test]
|
|
public void WriteProgramAsync_ValidContent_CallsUploadFile()
|
|
{
|
|
_ftpMock.Setup(f => f.Ping()).Returns(true);
|
|
_ftpMock.Setup(f => f.UploadFile(It.IsAny<string>(), "CONTENT"));
|
|
_machine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
var prog = new CncProgram { Name = "PROG1", Content = "CONTENT" };
|
|
var result = _machine.WriteProgramAsync("/Users/Prg/", prog).GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.True);
|
|
_ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), "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 = "PROG1", Content = "" };
|
|
var result = _machine.WriteProgramAsync("/Users/Prg/", prog).GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.False);
|
|
_ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
|
}
|
|
|
|
[Test]
|
|
public void WriteProgramAsync_NullProgram_FailsWithoutCallingUploadFile()
|
|
{
|
|
_ftpMock.Setup(f => f.Ping()).Returns(true);
|
|
_machine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
var result = _machine.WriteProgramAsync("/Users/Prg/", null).GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.False);
|
|
_ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
|
}
|
|
|
|
// DeleteProgramAsync
|
|
|
|
[Test]
|
|
public void DeleteProgramAsync_ValidPath_CallsDeleteFile()
|
|
{
|
|
_ftpMock.Setup(f => f.Ping()).Returns(true);
|
|
_ftpMock.Setup(f => f.DeleteFile("/Users/Prg/PROG1.nc"));
|
|
_machine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
var result = _machine.DeleteProgramAsync("/Users/Prg/PROG1.nc").GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.True);
|
|
_ftpMock.Verify(f => f.DeleteFile("/Users/Prg/PROG1.nc"), Times.Once);
|
|
}
|
|
|
|
// ReadActiveProgramAsync
|
|
|
|
[Test]
|
|
public void ReadActiveProgramAsync_ReturnsFailNotSupported()
|
|
{
|
|
var result = _machine.ReadActiveProgramAsync().GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.False);
|
|
}
|
|
|
|
// SelectMainProgramAsync
|
|
|
|
[Test]
|
|
public void SelectMainProgramAsync_ReturnsFailNotSupported()
|
|
{
|
|
var result = _machine.SelectMainProgramAsync("/Users/Prg/PROG1.nc").GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.False);
|
|
}
|
|
|
|
// Validator-real tests (NcProgramValidator with CncManufacturer.Fagor)
|
|
|
|
[Test]
|
|
public void WriteProgramAsync_NameTooLong_ValidationFails()
|
|
{
|
|
var realMachine = new FagorMachine(_config, _ftpMock.Object,
|
|
new NcProgramValidator(CncManufacturer.Fagor));
|
|
try
|
|
{
|
|
_ftpMock.Setup(f => f.Ping()).Returns(true);
|
|
realMachine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
var prog = new CncProgram { Name = new string('A', 25), Content = "G00 X10" };
|
|
var result = realMachine.WriteProgramAsync("/Users/Prg/", prog).GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.False);
|
|
_ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
|
}
|
|
finally { realMachine.Dispose(); }
|
|
}
|
|
|
|
[Test]
|
|
public void WriteProgramAsync_NameContainsTilde_ValidationFails()
|
|
{
|
|
var realMachine = new FagorMachine(_config, _ftpMock.Object,
|
|
new NcProgramValidator(CncManufacturer.Fagor));
|
|
try
|
|
{
|
|
_ftpMock.Setup(f => f.Ping()).Returns(true);
|
|
realMachine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
var prog = new CncProgram { Name = "MAñANA", Content = "G00 X10" };
|
|
var result = realMachine.WriteProgramAsync("/Users/Prg/", prog).GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.False);
|
|
_ftpMock.Verify(f => f.UploadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
|
}
|
|
finally { realMachine.Dispose(); }
|
|
}
|
|
}
|
|
}
|