nc_program_manager/NcProgramManager.Tests/Unit/FtpListParserTests.cs
dtrentin 89487256d7 fix(mazak): harden FTP list parsing + surface root-list failure
- FtpListParser: tolerant DOS/IIS regex (optional AM/PM, 2-or-4-digit
  year, 1-or-2-digit hour) for 24h/locale-dependent Mazak servers;
  NLST fallback now only accepts single-token lines (multi-token
  garbage/header -> skip) to avoid whole-line-as-filename.
- MazakMachine.BuildTreeAsync: root-level LIST failure now propagates
  -> Fail (no more silent exit 0 on a diagnostic probe). Children keep
  best-effort inline "errore lista" swallow. Depth semantics preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 15:14:57 +02:00

189 lines
6 KiB
C#

using NUnit.Framework;
using NcProgramManager.Cnc.Mazak;
namespace NcProgramManager.Tests.Unit
{
[TestFixture]
public class FtpListParserTests
{
// NormalizeDir
[Test]
public void NormalizeDir_Empty_ReturnsRoot()
{
Assert.That(FtpListParser.NormalizeDir(""), Is.EqualTo("/"));
}
[Test]
public void NormalizeDir_Null_ReturnsRoot()
{
Assert.That(FtpListParser.NormalizeDir(null), Is.EqualTo("/"));
}
[Test]
public void NormalizeDir_NoSlashes_AddsBoth()
{
Assert.That(FtpListParser.NormalizeDir("PRG"), Is.EqualTo("/PRG/"));
}
[Test]
public void NormalizeDir_NoTrailingSlash_AddsTrailing()
{
Assert.That(FtpListParser.NormalizeDir("/a/b"), Is.EqualTo("/a/b/"));
}
[Test]
public void NormalizeDir_AlreadyNormalized_Unchanged()
{
Assert.That(FtpListParser.NormalizeDir("/a/b/"), Is.EqualTo("/a/b/"));
}
// Parse - DOS/IIS format
[Test]
public void Parse_DosDir_DirectoryEntry()
{
var e = FtpListParser.Parse("07-18-26 10:00AM <DIR> MYDIR", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.Name, Is.EqualTo("MYDIR"));
Assert.That(e.IsDirectory, Is.True);
Assert.That(e.Size, Is.EqualTo(-1));
Assert.That(e.FullPath, Is.EqualTo("/PRG/MYDIR"));
}
[Test]
public void Parse_DosFile_FileEntryWithSize()
{
var e = FtpListParser.Parse("07-18-26 10:00AM 1234 O1000.EIA", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.Name, Is.EqualTo("O1000.EIA"));
Assert.That(e.IsDirectory, Is.False);
Assert.That(e.Size, Is.EqualTo(1234));
}
// Parse - DOS/IIS format, T4 tolerances (24h time, 4-digit year, spaces)
[Test]
public void Parse_Dos24hTime_DirectoryEntry()
{
// 24h time, no AM/PM: _dosLine \s*(AM|PM)? optional absorbs this.
var e = FtpListParser.Parse("07-18-26 22:00 <DIR> NIGHTDIR", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.Name, Is.EqualTo("NIGHTDIR"));
Assert.That(e.IsDirectory, Is.True);
Assert.That(e.Size, Is.EqualTo(-1));
}
[Test]
public void Parse_Dos4DigitYear_FileEntryWithSize()
{
// 4-digit year: date group is \d{2}-\d{2}-\d{2,4}.
var e = FtpListParser.Parse("07-18-2026 10:00AM 2048 O4000.EIA", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.Name, Is.EqualTo("O4000.EIA"));
Assert.That(e.IsDirectory, Is.False);
Assert.That(e.Size, Is.EqualTo(2048));
}
[Test]
public void Parse_DosFileNameWithSpaces_KeepsSpaces()
{
// name group is .+ (greedy to $) -> internal spaces preserved.
var e = FtpListParser.Parse("07-18-26 10:00AM 128 MY PROG.EIA", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.Name, Is.EqualTo("MY PROG.EIA"));
Assert.That(e.IsDirectory, Is.False);
Assert.That(e.Size, Is.EqualTo(128));
}
// Parse - Unix format
[Test]
public void Parse_UnixFileNameWithSpaces_KeepsSpaces()
{
// Unix name group is .+ -> internal spaces preserved.
var e = FtpListParser.Parse("-rw-r--r-- 1 own grp 64 Jul 18 10:00 MY PROG", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.Name, Is.EqualTo("MY PROG"));
Assert.That(e.IsDirectory, Is.False);
Assert.That(e.Size, Is.EqualTo(64));
}
[Test]
public void Parse_UnixDir_DirectoryEntry()
{
var e = FtpListParser.Parse("drwxr-xr-x 1 own grp 4096 Jul 18 10:00 sub", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.IsDirectory, Is.True);
Assert.That(e.Name, Is.EqualTo("sub"));
}
[Test]
public void Parse_UnixFile_FileEntryWithSize()
{
var e = FtpListParser.Parse("-rw-r--r-- 1 own grp 512 Jul 18 10:00 O2000", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.IsDirectory, Is.False);
Assert.That(e.Size, Is.EqualTo(512));
Assert.That(e.Name, Is.EqualTo("O2000"));
}
// Parse - skip '.' and '..'
[Test]
public void Parse_DotDir_ReturnsNull()
{
var e = FtpListParser.Parse("07-18-26 10:00AM <DIR> .", "/PRG/");
Assert.That(e, Is.Null);
}
[Test]
public void Parse_DotDotDir_ReturnsNull()
{
var e = FtpListParser.Parse("07-18-26 10:00AM <DIR> ..", "/PRG/");
Assert.That(e, Is.Null);
}
// Parse - fallback (NLST bare name)
[Test]
public void Parse_BareName_FallbackFileEntry()
{
var e = FtpListParser.Parse("O3000", "/PRG/");
Assert.That(e, Is.Not.Null);
Assert.That(e.Name, Is.EqualTo("O3000"));
Assert.That(e.IsDirectory, Is.False);
Assert.That(e.Size, Is.EqualTo(-1));
}
// Parse - garbage/header multi-token line matches neither regex -> skip
[Test]
public void Parse_GarbageMultiToken_ReturnsNull()
{
// "total 8": DOS regex needs leading MM-DD-YY date (no); Unix perm group is
// [dl-][rwxsStT-]{9} and 't' is not in [dl-] (no). Trimmed line has internal
// whitespace -> T4 fallback returns null (not treated as bare NLST name).
Assert.That(FtpListParser.Parse("total 8", "/PRG/"), Is.Null);
}
// Parse - whitespace/empty
[Test]
public void Parse_Whitespace_ReturnsNull()
{
Assert.That(FtpListParser.Parse(" ", "/PRG/"), Is.Null);
}
}
}