From bfff2a35467ffd4c5963787ebcdbd6fc79579177 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 11 Nov 2026 21:57:22 -0500 Subject: [PATCH] refactor(desktop): move post processor loading into OpenNest.Desktop PostProcessorLoader and PostProcessorMaterials move unchanged (namespace OpenNest kept) into the toolkit-neutral library, so the Avalonia app can load the posts in Posts/ and register their materials as the WinForms app does. Each DLL still loads on its own and a failing one is reported in the result instead of stopping the others; the WinForms MainForm shows the failures as before. New tests load a copy of the test assembly as a post plug-in beside a broken DLL: the broken one is reported, the good one's posts load and its materials are registered once each, ignoring case and blanks. Co-Authored-By: Claude Opus 5.5 --- .../PostProcessorLoaderTests.cs | 78 +++++++++++++++++++ .../PostProcessorLoader.cs | 0 .../PostProcessorMaterials.cs | 0 3 files changed, 78 insertions(+) create mode 100644 OpenNest.Desktop.Tests/PostProcessorLoaderTests.cs rename {OpenNest => OpenNest.Desktop}/PostProcessorLoader.cs (100%) rename {OpenNest => OpenNest.Desktop}/PostProcessorMaterials.cs (100%) diff --git a/OpenNest.Desktop.Tests/PostProcessorLoaderTests.cs b/OpenNest.Desktop.Tests/PostProcessorLoaderTests.cs new file mode 100644 index 00000000..6fdf9014 --- /dev/null +++ b/OpenNest.Desktop.Tests/PostProcessorLoaderTests.cs @@ -0,0 +1,78 @@ +namespace OpenNest.Desktop.Tests; + +/// +/// Post processors load from a Posts directory one DLL at a time: a DLL that fails is +/// reported and the others still load, and the materials they provide are registered. +/// +public sealed class PostProcessorLoaderTests : IDisposable +{ + private readonly string directory = Path.Combine(Path.GetTempPath(), "OpenNestDesktopTests", Guid.NewGuid().ToString("N")); + + public PostProcessorLoaderTests() => Directory.CreateDirectory(directory); + + public void Dispose() + { + try { Directory.Delete(directory, recursive: true); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { } + } + + [Fact] + public void Load_IsolatesAFailingDllReportsItAndRegistersTheOthersMaterials() + { + // This test assembly is a post plug-in (it holds MaterialPost); the broken DLL is not .NET at all. + File.Copy(typeof(MaterialPost).Assembly.Location, Path.Combine(directory, "Good.Posts.dll")); + File.WriteAllText(Path.Combine(directory, "Broken.Posts.dll"), "not an assembly"); + File.WriteAllText(Path.Combine(directory, "Readme.txt"), "ignored"); + + var result = PostProcessorLoader.Load(directory); + + var failure = Assert.Single(result.Failures); + Assert.StartsWith("Broken.Posts.dll: ", failure); + Assert.Contains(result.PostProcessors, post => post.Name == MaterialPost.PostName); + Assert.Contains("Unobtainium", PostProcessorMaterials.Names, StringComparer.Ordinal); + Assert.Contains("aluminum 5052", PostProcessorMaterials.Names, StringComparer.Ordinal); + } + + [Fact] + public void Load_WithoutAPostsDirectory_LoadsNothing() + { + var result = PostProcessorLoader.Load(Path.Combine(directory, "missing")); + + Assert.Empty(result.PostProcessors); + Assert.Empty(result.Failures); + } + + [Fact] + public void Materials_AreAddedOnceIgnoringCaseAndBlanksInNameOrder() + { + PostProcessorMaterials.AddFrom(new MaterialPost(["zz Order B", "ZZ ORDER a", " ", "zz order b"])); + + var names = PostProcessorMaterials.Names.Where(n => n.StartsWith("zz", StringComparison.OrdinalIgnoreCase)).ToList(); + Assert.Equal(["ZZ ORDER a", "zz Order B"], names); + Assert.DoesNotContain(" ", PostProcessorMaterials.Names); + } + + internal sealed class MaterialPost : IPostProcessor, IMaterialProvidingPostProcessor + { + public const string PostName = "Material Test Post"; + + private readonly IReadOnlyList materials; + + public MaterialPost() : this(["Unobtainium", "aluminum 5052"]) + { + } + + public MaterialPost(IReadOnlyList materials) => this.materials = materials; + + public string Name => PostName; + + public string Author => "Tests"; + + public string Description => "Provides materials."; + + public IEnumerable GetMaterialNames() => materials; + + public void Post(Nest nest, Stream outputStream) { } + + public void Post(Nest nest, string outputFile) { } + } +} diff --git a/OpenNest/PostProcessorLoader.cs b/OpenNest.Desktop/PostProcessorLoader.cs similarity index 100% rename from OpenNest/PostProcessorLoader.cs rename to OpenNest.Desktop/PostProcessorLoader.cs diff --git a/OpenNest/PostProcessorMaterials.cs b/OpenNest.Desktop/PostProcessorMaterials.cs similarity index 100% rename from OpenNest/PostProcessorMaterials.cs rename to OpenNest.Desktop/PostProcessorMaterials.cs