From 31cca72d7aa6ef1bff9bbf18e626679f4ddadfcf Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:23:17 +0000 Subject: [PATCH 1/2] refactor: consolidate test source code to eliminate DRY violations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract duplicated test source code patterns across analyzer and serialization tests: - Create TestSourceConstants.cs with shared module header strings for analyzer tests - Create SerializationTestModels.cs with shared test model and values for JSON/XML/YAML tests - Update 7 analyzer test files to use shared TestSourceConstants - Update JsonTests, XmlTests, YamlTests to use shared SerializationTestModels Fixes #1634, fixes #1627 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- ...PipelinesAnalyzersAsyncModulesUnitTests.cs | 183 ++++++------------ ...larPipelinesAnalyzersAwaitThisUnitTests.cs | 116 ++++------- ...nesAnalyzersBaseClassAttributeUnitTests.cs | 64 +++--- ...sConflictingDependsOnAttributeUnitTests.cs | 97 ++-------- ...dularPipelinesAnalyzersConsoleUnitTests.cs | 165 ++-------------- ...rPipelinesAnalyzersIEnumerableUnitTests.cs | 48 ++--- ...dularPipelinesAnalyzersILoggerUnitTests.cs | 172 +++------------- .../TestSourceConstants.cs | 109 +++++++++++ .../Helpers/JsonTests.cs | 32 ++- .../Helpers/SerializationTestModels.cs | 45 +++++ .../Helpers/XmlTests.cs | 82 ++++---- .../Helpers/YamlTests.cs | 61 +++--- 12 files changed, 423 insertions(+), 751 deletions(-) create mode 100644 src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/TestSourceConstants.cs create mode 100644 test/ModularPipelines.UnitTests/Helpers/SerializationTestModels.cs diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAsyncModulesUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAsyncModulesUnitTests.cs index 741c671114..ac412ddfe3 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAsyncModulesUnitTests.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAsyncModulesUnitTests.cs @@ -7,181 +7,108 @@ namespace ModularPipelines.Analyzers.Test; [TestClass] public class ModularPipelinesAnalyzersAsyncModulesUnitTests { - private const string BadModuleSource = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private const string BadModuleSource = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ - {|#0:protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { +{{ + {{|#0:protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + {{ return ExecuteCommand(context); - }|} + }}|}} private async Task ExecuteCommand(IPipelineContext context) - { + {{ return await context.Command.ExecuteCommandLineTool(new CommandLineToolOptions(""git"")); - } -} + }} +}} "; - - private const string BadModuleSource2 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + + private const string BadModuleSource2 = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ - {|#0:protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { +{{ + {{|#0:protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + {{ if (1 + ""n"" == ""1n"") - { + {{ return ExecuteCommand(context); - } + }} return Task.FromResult(""Foo!""); - }|} + }}|}} private async Task ExecuteCommand(IPipelineContext context) - { + {{ await Task.Yield(); return ""Foo!""; - } -} + }} +}} "; - private const string GoodModuleSource = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private const string GoodModuleSource = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ +{{ protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ return await ExecuteCommand(context); - } + }} private async Task ExecuteCommand(IPipelineContext context) - { + {{ return await context.Command.ExecuteCommandLineTool(new CommandLineToolOptions(""git"")); - } -} + }} +}} "; - - private const string GoodModuleSource2 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + + private const string GoodModuleSource2 = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ +{{ protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ return Task.FromResult(""Foo""); - } -} + }} +}} "; - - private const string GoodModuleSource3 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Extensions; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + + private const string GoodModuleSource3 = $@" +{TestSourceConstants.StandardModuleHeaderWithExtensions} public class Module1 : Module -{ +{{ protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ return ""Foo"".AsTask(); - } -} + }} +}} "; - - private const string BadModuleSource2Fixed = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + + private const string BadModuleSource2Fixed = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ - {|#0:protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { +{{ + {{|#0:protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + {{ if (1 + ""n"" == ""1n"") - { + {{ return await ExecuteCommand(context); - } + }} return ""Foo!""; - }|} + }}|}} private async Task ExecuteCommand(IPipelineContext context) - { + {{ await Task.Yield(); return ""Foo!""; - } -} + }} +}} "; [TestMethod] diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAwaitThisUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAwaitThisUnitTests.cs index 9f1274865b..56d45ac0a4 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAwaitThisUnitTests.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAwaitThisUnitTests.cs @@ -7,96 +7,60 @@ namespace ModularPipelines.Analyzers.Test; [TestClass] public class ModularPipelinesAnalyzersAwaitThisUnitTests { - private const string BadModuleSourceAwaitThis = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private const string BadModuleSourceAwaitThis = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ +{{ protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ // This should trigger the analyzer - {|#0:await this|}; + {{|#0:await this|}}; return null; - } -} + }} +}} "; - private const string BadModuleSourceAwaitThisInMethod = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private const string BadModuleSourceAwaitThisInMethod = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ +{{ protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ return await ExecuteCommand(context); - } + }} private async Task ExecuteCommand(IPipelineContext context) - { + {{ // This should also trigger the analyzer - {|#0:await this|}; + {{|#0:await this|}}; return null; - } -} + }} +}} "; - private const string GoodModuleSourceNoAwaitThis = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private const string GoodModuleSourceNoAwaitThis = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ +{{ protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ // This is fine - awaiting something else var otherModule = GetModule(); await otherModule; return null; - } -} + }} +}} public class Module2 : Module -{ +{{ protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ return Task.FromResult(""Test""); - } -} + }} +}} "; private const string NonModuleClassAwaitThis = @" @@ -122,34 +86,22 @@ public TaskAwaiter GetAwaiter() } "; - private const string GoodModuleSourceAwaitThisInOnAfterExecute = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Options; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private const string GoodModuleSourceAwaitThisInOnAfterExecute = $@" +{TestSourceConstants.StandardModuleHeaderWithOptions} public class Module1 : Module -{ +{{ protected override Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ return Task.FromResult(null); - } + }} protected override async Task OnAfterExecute(IPipelineContext context) - { + {{ // This should NOT trigger the analyzer - await this is allowed in OnAfterExecute var result = await this; - } -} + }} +}} "; [TestMethod] diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersBaseClassAttributeUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersBaseClassAttributeUnitTests.cs index af9dd04140..7720c7155c 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersBaseClassAttributeUnitTests.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersBaseClassAttributeUnitTests.cs @@ -8,7 +8,8 @@ namespace ModularPipelines.Analyzers.Test; [TestClass] public class ModularPipelinesAnalyzersBaseClassAttributeUnitTests { - private const string BaseTypeWithAttribute = @" + // These tests use a simpler header without Linq and Options - different from StandardModuleHeader + private const string SimplerModuleHeader = @" #nullable enable using System; using System.Threading; @@ -19,67 +20,58 @@ public class ModularPipelinesAnalyzersBaseClassAttributeUnitTests using ModularPipelines.Modules; using ModularPipelines.Attributes; -namespace ModularPipelines.Examples.Modules; +namespace ModularPipelines.Examples.Modules;"; + + private const string BaseTypeWithAttribute = $@"{SimplerModuleHeader} public class Module1 : Module -{ +{{ protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ await Task.Delay(1, cancellationToken); return null; - } -} + }} +}} public class Module2 : DependsOnModule1 -{ +{{ protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - var module1 = await {|#0:GetModule()|}; + {{ + var module1 = await {{|#0:GetModule()|}}; return null; - } -} + }} +}} [DependsOn] public abstract class DependsOnModule1 : Module -{ -} +{{ +}} "; - private const string InterfaceWithAttribute = @" -#nullable enable -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private const string InterfaceWithAttribute = $@"{SimplerModuleHeader} public class Module1 : Module -{ +{{ protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ await Task.Delay(1, cancellationToken); return null; - } -} + }} +}} public class Module2 : Module, IDependsOnModule1 -{ +{{ protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - var module1 = await {|#0:GetModule()|}; + {{ + var module1 = await {{|#0:GetModule()|}}; return null; - } -} + }} +}} [DependsOn] public interface IDependsOnModule1 -{ -} +{{ +}} "; [TestMethod] diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests.cs index fb4cffc71b..b33a88cbe5 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests.cs @@ -6,101 +6,44 @@ namespace ModularPipelines.Analyzers.Test; [TestClass] public class ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests { - private const string BadModuleSource = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; - -namespace ModularPipelines.Examples.Modules; - -[{|#0:DependsOn|}] -public class Module1 : Module> + private const string SimpleModuleBody = @" { protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) { await Task.Delay(1, cancellationToken); return new List(); } -} +}"; + + private const string BadModuleSource = $@" +{TestSourceConstants.StandardModuleHeaderWithLogging} + +[{{|#0:DependsOn|}}] +public class Module1 : Module> +{SimpleModuleBody} -[{|#1:DependsOn|}] +[{{|#1:DependsOn|}}] public class Module2 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - return new List(); - } -} +{SimpleModuleBody} "; - private const string BadModuleSource2 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; - -namespace ModularPipelines.Examples.Modules; - -[{|#0:DependsOn|}] + private const string BadModuleSource2 = $@" +{TestSourceConstants.StandardModuleHeaderWithLogging} + +[{{|#0:DependsOn|}}] public class Module1 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - return new List(); - } -} +{SimpleModuleBody} "; - private const string GoodModuleSource = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; - -namespace ModularPipelines.Examples.Modules; + private const string GoodModuleSource = $@" +{TestSourceConstants.StandardModuleHeaderWithLogging} public class Module1 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - return new List(); - } -} +{SimpleModuleBody} [DependsOn] public class Module2 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - return new List(); - } -} +{SimpleModuleBody} "; [TestMethod] diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConsoleUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConsoleUnitTests.cs index ac67c3ca4b..e18f9c76ca 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConsoleUnitTests.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConsoleUnitTests.cs @@ -6,167 +6,28 @@ namespace ModularPipelines.Analyzers.Test; [TestClass] public class ModularPipelinesAnalyzersConsoleUnitTests { - private const string BadModuleSource = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private static string CreateBadModuleSource(string consoleCall, bool isAsync = false) => $@" +{TestSourceConstants.StandardModuleHeader} public class Module1 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - - {|#0:Console.WriteLine(""Done!"")|}; - - return new List(); - } -} -"; - - private const string BadModuleSource2 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; - -public class Module1 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - - {|#0:Console.Write(""Done!"")|}; - - return new List(); - } -} -"; - - private const string BadModuleSource3 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; - -public class Module1 : Module> -{ +{{ protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ await Task.Delay(1, cancellationToken); - {|#0:Console.Out.Write(""Done!"")|}; + {(isAsync ? "await " : "")}{{|#0:{consoleCall}|}}; return new List(); - } -} + }} +}} "; - private const string BadModuleSource4 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; - -public class Module1 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - - {|#0:Console.Out.WriteLine(""Done!"")|}; - - return new List(); - } -} -"; - - private const string BadModuleSource5 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; - -public class Module1 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - - await {|#0:Console.Out.WriteLineAsync(""Done!"")|}; - - return new List(); - } -} -"; - - private const string BadModuleSource6 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; - -public class Module1 : Module> -{ - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - - {|#0:Console.Out.Dispose()|}; - - return new List(); - } -} -"; + private static readonly string BadModuleSource = CreateBadModuleSource(@"Console.WriteLine(""Done!"")"); + private static readonly string BadModuleSource2 = CreateBadModuleSource(@"Console.Write(""Done!"")"); + private static readonly string BadModuleSource3 = CreateBadModuleSource(@"Console.Out.Write(""Done!"")"); + private static readonly string BadModuleSource4 = CreateBadModuleSource(@"Console.Out.WriteLine(""Done!"")"); + private static readonly string BadModuleSource5 = CreateBadModuleSource(@"Console.Out.WriteLineAsync(""Done!"")", isAsync: true); + private static readonly string BadModuleSource6 = CreateBadModuleSource(@"Console.Out.Dispose()"); [TestMethod] public async Task AnalyzerIsTriggered_When_Using_Console() diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersIEnumerableUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersIEnumerableUnitTests.cs index 43b88ebe08..368184fb3c 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersIEnumerableUnitTests.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersIEnumerableUnitTests.cs @@ -6,52 +6,30 @@ namespace ModularPipelines.Analyzers.Test; [TestClass] public class ModularPipelinesAnalyzersIEnumerableUnitTests { - private const string BadModuleSource = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; + private const string BadModuleSource = $@" +{TestSourceConstants.StandardModuleHeader} -namespace ModularPipelines.Examples.Modules; - -public class Module1 : {|#0:Module>|} -{ +public class Module1 : {{|#0:Module>|}} +{{ protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ await Task.Delay(1, cancellationToken); return new ModuleResult>(Array.Empty().Select(x => x)); - } -} + }} +}} "; - private const string GoodModuleSource = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; - -namespace ModularPipelines.Examples.Modules; + private const string GoodModuleSource = $@" +{TestSourceConstants.StandardModuleHeader} public class Module1 : Module> -{ +{{ protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ await Task.Delay(1, cancellationToken); return new List(); - } -} + }} +}} "; [TestMethod] diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersILoggerUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersILoggerUnitTests.cs index fc4bc21f44..a29236132c 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersILoggerUnitTests.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersILoggerUnitTests.cs @@ -6,175 +6,57 @@ namespace ModularPipelines.Analyzers.Test; [TestClass] public class ModularPipelinesAnalyzersILoggerUnitTests { - private const string BadModuleSourceILogger = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; - -namespace ModularPipelines.Examples.Modules; + private static string CreateModuleWithLoggerConstructor(string constructorParam) => $@" +{TestSourceConstants.StandardModuleHeaderWithLogging} public class Module1 : Module> -{ - public Module1({|#0:ILogger logger|}) - { - } - - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - return new List(); - } -} -"; - - private const string BadModuleSourceILoggerProvider = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; - -namespace ModularPipelines.Examples.Modules; - -public class Module1 : Module> -{ - public Module1({|#0:ILoggerProvider loggerProvider|}) - { - } - - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - return new List(); - } -} -"; - - private const string BadModuleSourceILoggerFactory = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; - -namespace ModularPipelines.Examples.Modules; - -public class Module1 : Module> -{ - public Module1({|#0:ILoggerFactory loggerFactory|}) - { - } +{{ + public Module1({{|#0:{constructorParam}|}}) + {{ + }} protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ await Task.Delay(1, cancellationToken); return new List(); - } -} + }} +}} "; - private const string BadModuleSourceILoggerGeneric = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; - -namespace ModularPipelines.Examples.Modules; - -public class Module1 : Module> -{ - public Module1({|#0:ILogger logger|}) - { - } - - protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - await Task.Delay(1, cancellationToken); - return new List(); - } -} -"; + private static readonly string BadModuleSourceILogger = CreateModuleWithLoggerConstructor("ILogger logger"); + private static readonly string BadModuleSourceILoggerProvider = CreateModuleWithLoggerConstructor("ILoggerProvider loggerProvider"); + private static readonly string BadModuleSourceILoggerFactory = CreateModuleWithLoggerConstructor("ILoggerFactory loggerFactory"); + private static readonly string BadModuleSourceILoggerGeneric = CreateModuleWithLoggerConstructor("ILogger logger"); - private const string GoodModuleSource = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; - -namespace ModularPipelines.Examples.Modules; + private const string GoodModuleSource = $@" +{TestSourceConstants.StandardModuleHeaderWithLogging} public class Module1 : Module> -{ +{{ protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ await Task.Delay(1, cancellationToken); return new List(); - } -} + }} +}} "; - private const string GoodModuleSource2 = @" -#nullable enable -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using ModularPipelines.Attributes; -using Microsoft.Extensions.Logging; + private const string GoodModuleSource2 = $@" +{TestSourceConstants.StandardModuleHeaderWithLogging} using ModularPipelines.Logging; -namespace ModularPipelines.Examples.Modules; - public class Module1 : Module> -{ +{{ public Module1(IModuleLoggerProvider moduleLoggerProvider) - { - } + {{ + }} protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { + {{ await Task.Delay(1, cancellationToken); return new List(); - } -} + }} +}} "; [TestMethod] diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/TestSourceConstants.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/TestSourceConstants.cs new file mode 100644 index 0000000000..898fdb80f2 --- /dev/null +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/TestSourceConstants.cs @@ -0,0 +1,109 @@ +namespace ModularPipelines.Analyzers.Test; + +/// +/// Shared test source code constants to reduce duplication across analyzer tests. +/// +internal static class TestSourceConstants +{ + /// + /// Standard using statements for module test source code. + /// + public const string StandardUsings = @"#nullable enable +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using ModularPipelines.Context; +using ModularPipelines.Models; +using ModularPipelines.Modules; +using ModularPipelines.Attributes;"; + + /// + /// Standard using statements including Options namespace. + /// + public const string StandardUsingsWithOptions = @"#nullable enable +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using ModularPipelines.Context; +using ModularPipelines.Models; +using ModularPipelines.Options; +using ModularPipelines.Modules; +using ModularPipelines.Attributes;"; + + /// + /// Standard using statements including Microsoft.Extensions.Logging. + /// + public const string StandardUsingsWithLogging = @"#nullable enable +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using ModularPipelines.Context; +using ModularPipelines.Models; +using ModularPipelines.Modules; +using ModularPipelines.Attributes; +using Microsoft.Extensions.Logging;"; + + /// + /// Standard using statements including Extensions namespace. + /// + public const string StandardUsingsWithExtensions = @"#nullable enable +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using ModularPipelines.Context; +using ModularPipelines.Extensions; +using ModularPipelines.Models; +using ModularPipelines.Options; +using ModularPipelines.Modules; +using ModularPipelines.Attributes;"; + + /// + /// Standard namespace declaration for example modules. + /// + public const string ExamplesNamespace = @"namespace ModularPipelines.Examples.Modules;"; + + /// + /// Combines standard usings with the examples namespace. + /// + public const string StandardModuleHeader = StandardUsings + @" + +" + ExamplesNamespace; + + /// + /// Combines standard usings (with options) with the examples namespace. + /// + public const string StandardModuleHeaderWithOptions = StandardUsingsWithOptions + @" + +" + ExamplesNamespace; + + /// + /// Combines standard usings (with logging) with the examples namespace. + /// + public const string StandardModuleHeaderWithLogging = StandardUsingsWithLogging + @" + +" + ExamplesNamespace; + + /// + /// Combines standard usings (with extensions) with the examples namespace. + /// + public const string StandardModuleHeaderWithExtensions = StandardUsingsWithExtensions + @" + +" + ExamplesNamespace; + + /// + /// A simple async ExecuteAsync method body with Task.Delay. + /// + public const string SimpleAsyncExecuteBody = @"protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) + { + await Task.Delay(1, cancellationToken); + return new List(); + }"; +} diff --git a/test/ModularPipelines.UnitTests/Helpers/JsonTests.cs b/test/ModularPipelines.UnitTests/Helpers/JsonTests.cs index db2e0f75ea..c03a619ce6 100644 --- a/test/ModularPipelines.UnitTests/Helpers/JsonTests.cs +++ b/test/ModularPipelines.UnitTests/Helpers/JsonTests.cs @@ -1,6 +1,7 @@ using System.Text.Json; using ModularPipelines.Context; using ModularPipelines.TestHelpers; +using static ModularPipelines.UnitTests.Helpers.SerializationTestModels; namespace ModularPipelines.UnitTests.Helpers; @@ -11,10 +12,8 @@ public async Task Can_Serialize() { var json = await GetService(); - var result = json.ToJson(new JsonModel { Foo = "Bar!", Hello = "World!" }); - await Assert.That(result).IsEqualTo(""" - {"Foo":"Bar!","Hello":"World!"} - """); + var result = json.ToJson(SerializationTestModel.CreateDefault()); + await Assert.That(result).IsEqualTo($@"{{""Foo"":""{TestValues.FooValue}"",""Hello"":""{TestValues.HelloValue}""}}"); } [Test] @@ -22,12 +21,12 @@ public async Task Can_Serialize_With_Options() { var json = await GetService(); - var result = json.ToJson(new JsonModel { Foo = "Bar!", Hello = "World!" }, new JsonSerializerOptions + var result = json.ToJson(SerializationTestModel.CreateDefault(), new JsonSerializerOptions { WriteIndented = true, }); // Normalize line endings to handle differences between platforms - var expected = "{\n \"Foo\": \"Bar!\",\n \"Hello\": \"World!\"\n}"; + var expected = $"{{\n \"Foo\": \"{TestValues.FooValue}\",\n \"Hello\": \"{TestValues.HelloValue}\"\n}}"; await Assert.That(result.ReplaceLineEndings("\n")).IsEqualTo(expected); } @@ -36,10 +35,8 @@ public async Task Can_Deserialize() { var json = await GetService(); - var result = json.FromJson(""" - {"Foo":"Bar!","Hello":"World!"} - """); - await Assert.That(result).IsEqualTo(new JsonModel { Foo = "Bar!", Hello = "World!" }); + var result = json.FromJson($@"{{""Foo"":""{TestValues.FooValue}"",""Hello"":""{TestValues.HelloValue}""}}"); + await Assert.That(result).IsEqualTo(SerializationTestModel.CreateDefault()); } [Test] @@ -47,22 +44,15 @@ public async Task Can_Deserialize_With_Options() { var json = await GetService(); - var result = json.FromJson(""" + var result = json.FromJson($$""" { - "foo": "Bar!", - "hello": "World!" + "foo": "{{TestValues.FooValue}}", + "hello": "{{TestValues.HelloValue}}" } """, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }); - await Assert.That(result).IsEqualTo(new JsonModel { Foo = "Bar!", Hello = "World!" }); - } - - private record JsonModel - { - public string? Foo { get; set; } - - public string? Hello { get; set; } + await Assert.That(result).IsEqualTo(SerializationTestModel.CreateDefault()); } } \ No newline at end of file diff --git a/test/ModularPipelines.UnitTests/Helpers/SerializationTestModels.cs b/test/ModularPipelines.UnitTests/Helpers/SerializationTestModels.cs new file mode 100644 index 0000000000..7da7f5c480 --- /dev/null +++ b/test/ModularPipelines.UnitTests/Helpers/SerializationTestModels.cs @@ -0,0 +1,45 @@ +namespace ModularPipelines.UnitTests.Helpers; + +/// +/// Shared test models for serialization tests (JSON, XML, YAML). +/// Reduces duplication of similar model definitions across test files. +/// +public static class SerializationTestModels +{ + /// + /// Standard test values used across serialization tests. + /// + public static class TestValues + { + public const string FooValue = "Bar!"; + public const string HelloValue = "World!"; + public static readonly string[] ItemsValue = ["One", "Two", "3"]; + } + + /// + /// Base test model with common properties for serialization testing. + /// + public record SerializationTestModel + { + public string? Foo { get; set; } + public string? Hello { get; set; } + public List? Items { get; set; } + + /// + /// Creates a new model with default test values. + /// + public static SerializationTestModel CreateDefault() => + new() { Foo = TestValues.FooValue, Hello = TestValues.HelloValue }; + + /// + /// Creates a new model with default test values including items. + /// + public static SerializationTestModel CreateWithItems() => + new() + { + Foo = TestValues.FooValue, + Hello = TestValues.HelloValue, + Items = [..TestValues.ItemsValue], + }; + } +} diff --git a/test/ModularPipelines.UnitTests/Helpers/XmlTests.cs b/test/ModularPipelines.UnitTests/Helpers/XmlTests.cs index e6a4e6bbd4..0eae87a886 100644 --- a/test/ModularPipelines.UnitTests/Helpers/XmlTests.cs +++ b/test/ModularPipelines.UnitTests/Helpers/XmlTests.cs @@ -1,21 +1,42 @@ using System.Xml.Linq; using ModularPipelines.Context; using ModularPipelines.TestHelpers; +using static ModularPipelines.UnitTests.Helpers.SerializationTestModels; namespace ModularPipelines.UnitTests.Helpers; public class XmlTests : TestBase { + // XML serializer requires a concrete type, and the model name affects the XML output + // Keep XmlModel as a separate record that mirrors SerializationTestModel + public record XmlModel + { + public string? Foo { get; set; } + public string? Hello { get; set; } + public string[]? Items { get; set; } + + public static XmlModel CreateDefault() => + new() { Foo = TestValues.FooValue, Hello = TestValues.HelloValue }; + + public static XmlModel CreateWithItems() => + new() + { + Foo = TestValues.FooValue, + Hello = TestValues.HelloValue, + Items = TestValues.ItemsValue, + }; + } + [Test] public async Task Can_Serialize_With_Null() { var xml = await GetService(); - var result = xml.ToXml(new XmlModel { Foo = "Bar!", Hello = "World!" }); - await Assert.That(result.Trim()).IsEqualTo(""" + var result = xml.ToXml(XmlModel.CreateDefault()); + await Assert.That(result.Trim()).IsEqualTo($""" - Bar! - World! + {TestValues.FooValue} + {TestValues.HelloValue} """); } @@ -25,20 +46,15 @@ public async Task Can_Serialize_With_Array() { var xml = await GetService(); - var result = xml.ToXml(new XmlModel - { - Foo = "Bar!", - Hello = "World!", - Items = ["One", "Two", "3"], - }); - await Assert.That(result.Trim()).IsEqualTo(""" + var result = xml.ToXml(XmlModel.CreateWithItems()); + await Assert.That(result.Trim()).IsEqualTo($""" - Bar! - World! + {TestValues.FooValue} + {TestValues.HelloValue} - One - Two - 3 + {TestValues.ItemsValue[0]} + {TestValues.ItemsValue[1]} + {TestValues.ItemsValue[2]} """); @@ -49,12 +65,11 @@ public async Task Can_Serialize_With_Options() { var xml = await GetService(); - var result = xml.ToXml(new XmlModel { Foo = "Bar!", Hello = "World!" }, - SaveOptions.DisableFormatting); - await Assert.That(result.Trim()).IsEqualTo(""" + var result = xml.ToXml(XmlModel.CreateDefault(), SaveOptions.DisableFormatting); + await Assert.That(result.Trim()).IsEqualTo($""" - Bar! - World! + {TestValues.FooValue} + {TestValues.HelloValue} """); } @@ -64,13 +79,13 @@ public async Task Can_Deserialize() { var xml = await GetService(); - var result = xml.FromXml(""" + var result = xml.FromXml($""" - Bar! - World! + {TestValues.FooValue} + {TestValues.HelloValue} """); - await Assert.That(result).IsEqualTo(new XmlModel { Foo = "Bar!", Hello = "World!" }); + await Assert.That(result).IsEqualTo(XmlModel.CreateDefault()); } [Test] @@ -78,21 +93,12 @@ public async Task Can_Deserialize_With_Options() { var xml = await GetService(); - var result = xml.FromXml(""" + var result = xml.FromXml($""" - Bar! - World! + {TestValues.FooValue} + {TestValues.HelloValue} """, LoadOptions.None); - await Assert.That(result).IsEqualTo(new XmlModel { Foo = "Bar!", Hello = "World!" }); - } - - public record XmlModel - { - public string? Foo { get; set; } - - public string? Hello { get; set; } - - public string[]? Items { get; set; } + await Assert.That(result).IsEqualTo(XmlModel.CreateDefault()); } } \ No newline at end of file diff --git a/test/ModularPipelines.UnitTests/Helpers/YamlTests.cs b/test/ModularPipelines.UnitTests/Helpers/YamlTests.cs index 84c7b7dd14..3c0990f72e 100644 --- a/test/ModularPipelines.UnitTests/Helpers/YamlTests.cs +++ b/test/ModularPipelines.UnitTests/Helpers/YamlTests.cs @@ -1,6 +1,7 @@ using ModularPipelines.Context; using ModularPipelines.TestHelpers; using YamlDotNet.Serialization.NamingConventions; +using static ModularPipelines.UnitTests.Helpers.SerializationTestModels; namespace ModularPipelines.UnitTests.Helpers; @@ -11,10 +12,10 @@ public async Task Can_Serialize_With_Null() { var yaml = await GetService(); - var result = yaml.ToYaml(new YamlModel { Foo = "Bar!", Hello = "World!" }); - await Assert.That(result.Trim()).IsEqualTo(""" - foo: Bar! - hello: World! + var result = yaml.ToYaml(SerializationTestModel.CreateDefault()); + await Assert.That(result.Trim()).IsEqualTo($""" + foo: {TestValues.FooValue} + hello: {TestValues.HelloValue} """); } @@ -23,19 +24,14 @@ public async Task Can_Serialize_With_Array() { var yaml = await GetService(); - var result = yaml.ToYaml(new YamlModel - { - Foo = "Bar!", - Hello = "World!", - Items = ["One", "Two", "3"], - }); - await Assert.That(result.Trim()).IsEqualTo(""" - foo: Bar! - hello: World! + var result = yaml.ToYaml(SerializationTestModel.CreateWithItems()); + await Assert.That(result.Trim()).IsEqualTo($""" + foo: {TestValues.FooValue} + hello: {TestValues.HelloValue} items: - - One - - Two - - 3 + - {TestValues.ItemsValue[0]} + - {TestValues.ItemsValue[1]} + - {TestValues.ItemsValue[2]} """); } @@ -44,11 +40,11 @@ public async Task Can_Serialize_With_Options() { var yaml = await GetService(); - var result = yaml.ToYaml(new YamlModel { Foo = "Bar!", Hello = "World!" }, + var result = yaml.ToYaml(SerializationTestModel.CreateDefault(), PascalCaseNamingConvention.Instance); - await Assert.That(result.Trim()).IsEqualTo(""" - Foo: Bar! - Hello: World! + await Assert.That(result.Trim()).IsEqualTo($""" + Foo: {TestValues.FooValue} + Hello: {TestValues.HelloValue} """); } @@ -57,11 +53,11 @@ public async Task Can_Deserialize() { var yaml = await GetService(); - var result = yaml.FromYaml(""" - foo: Bar! - hello: World! + var result = yaml.FromYaml($""" + foo: {TestValues.FooValue} + hello: {TestValues.HelloValue} """); - await Assert.That(result).IsEqualTo(new YamlModel { Foo = "Bar!", Hello = "World!" }); + await Assert.That(result).IsEqualTo(SerializationTestModel.CreateDefault()); } [Test] @@ -69,19 +65,10 @@ public async Task Can_Deserialize_With_Options() { var yaml = await GetService(); - var result = yaml.FromYaml(""" - foo: Bar! - hello: World! + var result = yaml.FromYaml($""" + foo: {TestValues.FooValue} + hello: {TestValues.HelloValue} """, CamelCaseNamingConvention.Instance); - await Assert.That(result).IsEqualTo(new YamlModel { Foo = "Bar!", Hello = "World!" }); - } - - private record YamlModel - { - public string? Foo { get; set; } - - public string? Hello { get; set; } - - public List? Items { get; set; } + await Assert.That(result).IsEqualTo(SerializationTestModel.CreateDefault()); } } \ No newline at end of file From 4b03fd96a12a828b848090a8ed9910d1d27008bc Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:36:20 +0000 Subject: [PATCH 2/2] fix: Exclude null Items from JSON serialization in test model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add JsonIgnore attribute with WhenWritingNull condition to prevent the Items property from appearing as null in serialized JSON output. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../Helpers/SerializationTestModels.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/ModularPipelines.UnitTests/Helpers/SerializationTestModels.cs b/test/ModularPipelines.UnitTests/Helpers/SerializationTestModels.cs index 7da7f5c480..6ecf186210 100644 --- a/test/ModularPipelines.UnitTests/Helpers/SerializationTestModels.cs +++ b/test/ModularPipelines.UnitTests/Helpers/SerializationTestModels.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace ModularPipelines.UnitTests.Helpers; /// @@ -23,6 +25,7 @@ public record SerializationTestModel { public string? Foo { get; set; } public string? Hello { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public List? Items { get; set; } ///