From 7848a3fe7c9f6885474f6beb33ebe401b94d368a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 9 Nov 2026 14:29:37 -0500 Subject: [PATCH] fix(transport): case-insensitive remaining library lookups; base-only template reads for import targets (ScadaBridge#95) - Preview matches template folders, shared scripts, external systems, SMTP (by Host) and SMS (by AccountSid) with the #93 ToNameLookup comparer (OrdinalIgnoreCase, first wins, never throws), matching SQL Server's case-insensitive collation, so a differently-cased target row is reported as a conflict instead of New. - Apply-side in-memory lookups do the same: ApplyTemplateFoldersAsync, the template folder-id map, ResolveFolderParentEdgesAsync, ApplySmtpConfigsAsync and ApplySmsConfigsAsync. An Overwrite now updates the existing row instead of adding a duplicate (folder names are only unique per parent, so the old ToDictionary could also throw). - New ITemplateEngineRepository.GetBaseTemplatesWithChildrenAsync filters IsDerived in SQL. Preview and the Overwrite load in ApplyTemplatesAsync use it, so a derived slot template that shares a bundled name is never the import target. GetTemplatesWithChildrenAsync is unchanged for the id-keyed callers (export closure, rewire passes), which can need derived rows. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV --- .../Repositories/ITemplateEngineRepository.cs | 10 ++ .../Repositories/TemplateEngineRepository.cs | 13 +- .../Import/BundleImporter.cs | 37 +++-- .../TemplateEngineRepositoryTests.cs | 21 +++ .../Import/ImportApplyScalingTests.cs | 3 +- .../Import/ImportNameLookupTests.cs | 157 ++++++++++++++++++ 6 files changed, 222 insertions(+), 19 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs index 091f5396..c78677e6 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs @@ -31,6 +31,16 @@ public interface ITemplateEngineRepository /// Cancellation token. /// A task that resolves to a read-only list of matched templates with children loaded. Task> GetTemplatesWithChildrenAsync(IEnumerable names, CancellationToken cancellationToken = default); + /// + /// restricted to + /// BASE templates ( is false). Derived templates carry their + /// composition slot name, so a by-name read meant for the user-authored template the name + /// identifies (a bundle import's target) would otherwise also match derived rows (ScadaBridge#95). + /// + /// Template names to load. Duplicate / null / empty names are filtered out. + /// Cancellation token. + /// A task that resolves to the matched base templates with children loaded. + Task> GetBaseTemplatesWithChildrenAsync(IEnumerable names, CancellationToken cancellationToken = default); /// Retrieves all templates. /// Cancellation token. /// A task that resolves to a read-only list of all templates. diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs index ba088f47..5bef7c5a 100644 --- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs +++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs @@ -53,8 +53,17 @@ public class TemplateEngineRepository : ITemplateEngineRepository } /// - public async Task> GetTemplatesWithChildrenAsync( + public Task> GetTemplatesWithChildrenAsync( IEnumerable names, CancellationToken cancellationToken = default) + => GetTemplatesWithChildrenAsync(names, baseOnly: false, cancellationToken); + + /// + public Task> GetBaseTemplatesWithChildrenAsync( + IEnumerable names, CancellationToken cancellationToken = default) + => GetTemplatesWithChildrenAsync(names, baseOnly: true, cancellationToken); + + private async Task> GetTemplatesWithChildrenAsync( + IEnumerable names, bool baseOnly, CancellationToken cancellationToken) { // Bulk lookup replaces the per-name N+1 in // BundleImporter.PreviewAsync. Filter out null / empty / duplicate @@ -67,7 +76,7 @@ public class TemplateEngineRepository : ITemplateEngineRepository if (distinct.Length == 0) return Array.Empty