New Cnc/Mazak/ machine integration mirroring the Fagor/Mitsubishi FTP pattern: MazakConnectionConfig, IMazakFtpClient, MazakFtpClient, MazakMachine (ICncMachine). Transfers EIA/ISO G-code text over FTP to Smooth-family controllers. Mazatrol CMT (proprietary binary) out of scope. Design (verified against Mazak Matrix EIA manual + field reports): - Filenames preserved verbatim; no O#### rename, no extension append. - Symmetric ResolvePath for Read/Write/Delete (avoids ISS-020 class; Delete resolved too, unlike copied Mitsubishi source). - Validator = full passthrough for Mazak (EIA permits ';' EOB, '[ ]', '#'; restrictive Fanuc checks would false-reject valid programs). - Port 21 default, -porta=23 for Smooth Ai IIS variant. - ProgramDirectory site-specific, no default. Wiring: CncManufacturer.Mazak, ArgParser 'mazak' token, CncMachineFactory. Tests: MazakMachineTests (unit), MazakMachineIntegrationTests (FTP-stub roundtrip + symmetry guard), Mazak cases in validator/factory tests. Docs: README Controller Mazak section + table row; ISS-021 (constraints, EIA-option requirement), ISS-022 (Mitsubishi delete asymmetry logged). NOT build-verified (no .NET toolchain in build env) — verify on Windows. Projects: NcProgramManager, NcProgramManager.Tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
132 lines
5.2 KiB
C#
132 lines
5.2 KiB
C#
using System;
|
|
using NUnit.Framework;
|
|
using NcProgramManager.Cnc.Models;
|
|
using NcProgramManager.Cnc.Mazak;
|
|
using NcProgramManager.Tests.Integration.Stubs;
|
|
|
|
namespace NcProgramManager.Tests.Integration
|
|
{
|
|
[TestFixture]
|
|
[Category("Integration")]
|
|
public class MazakMachineIntegrationTests
|
|
{
|
|
private FtpServerStub _stub;
|
|
private MazakMachine _machine;
|
|
private const string ProgramDir = "/PRG/";
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_stub = new FtpServerStub();
|
|
var config = new MazakConnectionConfig
|
|
{
|
|
Name = "MazakIntegration",
|
|
IpAddress = "127.0.0.1",
|
|
Port = _stub.Port,
|
|
Username = "user",
|
|
Password = "pass",
|
|
ProgramDirectory = ProgramDir,
|
|
RequestTimeout = TimeSpan.FromSeconds(5)
|
|
};
|
|
_machine = new MazakMachine(config);
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
_machine?.Dispose();
|
|
_stub?.Dispose();
|
|
}
|
|
|
|
[Test]
|
|
public void ConnectAsync_StubListens_ResultOk()
|
|
{
|
|
var result = _machine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.True);
|
|
Assert.That(_machine.State, Is.EqualTo(ConnectionState.Connected));
|
|
}
|
|
|
|
[Test]
|
|
public void WriteThenRead_ProgramRoundTripsSamePathNoExtension()
|
|
{
|
|
_machine.ConnectAsync().GetAwaiter().GetResult();
|
|
// Bare identifier "1234"; Mazak validator is passthrough but use clean EIA text.
|
|
const string identifier = "1234";
|
|
string content = "O1234\nG00 X10\nM30";
|
|
var prog = new CncProgram { Name = identifier, Content = content };
|
|
|
|
// Write target == ResolvePath(identifier) == ProgramDir + identifier (verbatim, no extension)
|
|
var writeResult = _machine.WriteProgramAsync(identifier, prog).GetAwaiter().GetResult();
|
|
Assert.That(writeResult.Success, Is.True);
|
|
|
|
string storedPath = ProgramDir + identifier;
|
|
Assert.That(_stub.Files.ContainsKey(storedPath), Is.True,
|
|
"File should be stored verbatim at " + storedPath);
|
|
Assert.That(_stub.Files.ContainsKey(storedPath + ".EIA"), Is.False,
|
|
"File must NOT be stored with an appended extension");
|
|
|
|
// Read SAME bare identifier -> ResolvePath resolves to the same target (ISS-020 symmetry)
|
|
var readResult = _machine.ReadProgramAsync(identifier).GetAwaiter().GetResult();
|
|
Assert.That(readResult.Success, Is.True);
|
|
Assert.That(readResult.Value, Is.EqualTo(content),
|
|
"Read content must match written content for the same identifier (ResolvePath symmetry)");
|
|
}
|
|
|
|
[Test]
|
|
public void WriteThenDelete_ProgramRemoved_SubsequentReadFails()
|
|
{
|
|
_machine.ConnectAsync().GetAwaiter().GetResult();
|
|
const string identifier = "1234";
|
|
string content = "O1234\nG00 X10\nM30";
|
|
var prog = new CncProgram { Name = identifier, Content = content };
|
|
|
|
var writeResult = _machine.WriteProgramAsync(identifier, prog).GetAwaiter().GetResult();
|
|
Assert.That(writeResult.Success, Is.True);
|
|
|
|
string storedPath = ProgramDir + identifier;
|
|
Assert.That(_stub.Files.ContainsKey(storedPath), Is.True);
|
|
|
|
// ISS-020 symmetry: delete the SAME bare identifier used for write.
|
|
// ResolvePath(identifier) -> storedPath, so write/delete/read all align.
|
|
var deleteResult = _machine.DeleteProgramAsync(identifier).GetAwaiter().GetResult();
|
|
Assert.That(deleteResult.Success, Is.True);
|
|
Assert.That(_stub.Files.ContainsKey(storedPath), Is.False);
|
|
|
|
var readResult = _machine.ReadProgramAsync(identifier).GetAwaiter().GetResult();
|
|
Assert.That(readResult.Success, Is.False, "Read after delete must fail / not found");
|
|
}
|
|
|
|
[Test]
|
|
public void ReadProgramAsync_FileNotInStub_ResultFail()
|
|
{
|
|
_machine.ConnectAsync().GetAwaiter().GetResult();
|
|
|
|
var result = _machine.ReadProgramAsync("9999").GetAwaiter().GetResult();
|
|
|
|
Assert.That(result.Success, Is.False);
|
|
}
|
|
|
|
[Test]
|
|
[Category("Hardware")]
|
|
public void Hardware_ConnectAsync_RealMachine()
|
|
{
|
|
string ip = HardwareTestHelper.GetIpOrSkip("MAZAK_TEST_IP");
|
|
var config = new MazakConnectionConfig
|
|
{
|
|
Name = "MazakHardware",
|
|
IpAddress = ip,
|
|
Port = 21,
|
|
Username = Environment.GetEnvironmentVariable("MAZAK_USER") ?? "user",
|
|
Password = Environment.GetEnvironmentVariable("MAZAK_PASS") ?? "pass",
|
|
ProgramDirectory = "/PRG/",
|
|
RequestTimeout = TimeSpan.FromSeconds(10)
|
|
};
|
|
using (var machine = new MazakMachine(config))
|
|
{
|
|
var result = machine.ConnectAsync().GetAwaiter().GetResult();
|
|
Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors));
|
|
}
|
|
}
|
|
}
|
|
}
|