From 1d4f64726bb2e47fa636f3fef4218b29c10b9d51 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 30 Jun 2026 15:00:22 -0400 Subject: [PATCH] fix: resolve 500s on nest-details endpoints under Docker deployment NestReader opened .pep files with FileStream(path, FileMode.Open), which defaults to FileAccess.ReadWrite. The forge deployment bind-mounts /mnt/pep-nest read-only, so every read-only open was rejected with "Read-only file system" (EROFS) even though the code never writes. Pass FileAccess.Read/FileShare.Read explicitly, matching DrawingReader and ZipHelper elsewhere in PepLib.Core. Once that was fixed, a second pre-existing bug surfaced: GetNestDetailsAsync matched DB rows by NestName AND a literal Path comparison built from the container's local mount path. NestHeader.Path stores the original Windows UNC share path (e.g. \REMCOSRV0\pep nest\) from when PepApi ran directly against the network share, so the comparison can never match post-Docker. Match by NestName alone (ordered by most recent), consistent with the fallback lookup already used elsewhere in NestsController. --- PepApi.Core/Controllers/NestsController.cs | 5 ++-- PepLib.Core.Tests/IO/NestReaderTests.cs | 33 ++++++++++++++++++++++ PepLib.Core/IO/NestReader.cs | 2 +- 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 PepLib.Core.Tests/IO/NestReaderTests.cs diff --git a/PepApi.Core/Controllers/NestsController.cs b/PepApi.Core/Controllers/NestsController.cs index 5d6b93e..f61c376 100644 --- a/PepApi.Core/Controllers/NestsController.cs +++ b/PepApi.Core/Controllers/NestsController.cs @@ -409,11 +409,12 @@ public class NestsController : ControllerBase private async Task GetNestDetailsAsync(string nestFilePath) { var nest = Nest.Load(nestFilePath); - var dir = Path.GetDirectoryName(nestFilePath) + "\\"; var name = Path.GetFileNameWithoutExtension(nestFilePath).ToUpper(); var info = await _db.NestHeaders - .FirstOrDefaultAsync(n => n.NestName.ToUpper() == name && dir == n.Path); + .Where(n => n.NestName.ToUpper() == name) + .OrderByDescending(n => n.DateProgrammed) + .FirstOrDefaultAsync(); if (info == null) throw new Exception("Nest header not found in database"); diff --git a/PepLib.Core.Tests/IO/NestReaderTests.cs b/PepLib.Core.Tests/IO/NestReaderTests.cs new file mode 100644 index 0000000..839cce3 --- /dev/null +++ b/PepLib.Core.Tests/IO/NestReaderTests.cs @@ -0,0 +1,33 @@ +using PepLib.IO; +using Xunit; + +namespace PepLib.Core.Tests.IO; + +public class NestReaderTests +{ + [Fact] + public void Read_ReadOnlyFile_DoesNotRequestWriteAccess() + { + // Arrange: a file on a read-only mount denies write-access opens (EROFS on Linux, + // UnauthorizedAccessException on Windows when the read-only attribute is set). + // NestReader only ever reads nest files, so it must never request FileAccess.Write. + var path = Path.GetTempFileName(); + try + { + File.SetAttributes(path, FileAttributes.ReadOnly); + + var reader = new NestReader(); + + // Act / Assert: opening must succeed (i.e. not throw because of the read-only + // attribute). Parsing the empty/invalid content is expected to fail separately. + var ex = Record.Exception(() => reader.Read(path)); + + Assert.IsNotType(ex); + } + finally + { + File.SetAttributes(path, FileAttributes.Normal); + File.Delete(path); + } + } +} diff --git a/PepLib.Core/IO/NestReader.cs b/PepLib.Core/IO/NestReader.cs index 162f478..e927489 100644 --- a/PepLib.Core/IO/NestReader.cs +++ b/PepLib.Core/IO/NestReader.cs @@ -86,7 +86,7 @@ namespace PepLib.IO try { - stream = new FileStream(nestFile, FileMode.Open); + stream = new FileStream(nestFile, FileMode.Open, FileAccess.Read, FileShare.Read); Read(stream); } finally