From f8b914acf692f30fa4de0d0e55ba285bc4e33177 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:35:32 +0000 Subject: [PATCH 1/5] Initial plan From 2c8380054068bed8e2470e88451f3886261d98b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:02:19 +0000 Subject: [PATCH 2/5] Improve template: API, tests, docs, and fix workflow/doc inconsistencies Code improvements: - Add DefaultPrefix constant and Prefix property to DemoClass - Strengthen argument validation to reject empty strings (ThrowIfNullOrEmpty) - Add XML documentation for all new members with literate comments - Add tests for DefaultPrefix, Prefix property, and empty-string validation - Update requirements.yaml with TMPL-REQ-008 and TMPL-REQ-009 - Update user guide with DefaultPrefix, Prefix, and exception documentation - Fix README to mention .NET Standard 2.0 target framework Consistency fixes (repo-consistency-agent review): - Upgrade actions/upload-artifact from v7 to v8 (matches download-artifact@v8) - Fix CONTRIBUTING.md: encoding is UTF-8 (not UTF-8 with BOM) - Fix CONTRIBUTING.md: Assert.HasCount(expectedCount, collection) argument order - Fix AGENTS.md: encoding is UTF-8 (not UTF-8+BOM) --- .github/workflows/build.yaml | 12 +- AGENTS.md | 2 +- CONTRIBUTING.md | 4 +- README.md | 2 +- docs/guide/guide.md | 26 +++- requirements.yaml | 24 ++++ src/TemplateDotNetLibrary/DemoClass.cs | 49 ++++++- .../DemoClassTests.cs | 121 ++++++++++++++++-- 8 files changed, 211 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0d94e19..2e03ee9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -38,7 +38,7 @@ jobs: echo "✓ Tool versions captured" - name: Upload version capture - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v8 with: name: version-capture-quality path: versionmark-quality.json @@ -155,19 +155,19 @@ jobs: echo "✓ Tool versions captured" - name: Upload version capture - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v8 with: name: version-capture-${{ matrix.os }} path: versionmark-build-*.json - name: Upload Test Results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v8 with: name: test-results-${{ matrix.os }} path: test-results/*.trx - name: Upload Artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v8 with: name: artifacts-${{ matrix.os }} path: | @@ -229,7 +229,7 @@ jobs: upload: false - name: Upload CodeQL SARIF - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v8 with: name: codeql-sarif path: sarif-results/csharp.sarif @@ -495,7 +495,7 @@ jobs: # Downstream projects: Add any additional artifact uploads here. - name: Upload documentation - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v8 with: name: documents path: | diff --git a/AGENTS.md b/AGENTS.md index 4506302..f54c4f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,7 @@ implementation demonstrating best practices for DEMA Consulting .NET libraries. ## Key Files - **`requirements.yaml`** - All requirements with test linkage (enforced via `dotnet reqstream --enforce`) -- **`.editorconfig`** - Code style (file-scoped namespaces, 4-space indent, UTF-8+BOM, LF endings) +- **`.editorconfig`** - Code style (file-scoped namespaces, 4-space indent, UTF-8, LF endings) - **`.cspell.json`, `.markdownlint-cli2.jsonc`, `.yamllint.yaml`** - Linting configs ## Requirements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef6e8f2..6062608 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,7 +95,7 @@ This project enforces code style through `.editorconfig`. Key requirements: - **Indentation**: 4 spaces for C#, 2 spaces for YAML/JSON/XML - **Line Endings**: LF (Unix-style) -- **Encoding**: UTF-8 with BOM +- **Encoding**: UTF-8 - **Namespaces**: Use file-scoped namespace declarations - **Braces**: Required for all control statements - **Naming Conventions**: @@ -142,7 +142,7 @@ Examples: - Write tests that are clear and focused - Use modern MSTest v4 assertions: - - `Assert.HasCount(collection, expectedCount)` + - `Assert.HasCount(expectedCount, collection)` - `Assert.IsEmpty(collection)` - `Assert.DoesNotContain(item, collection)` - Always clean up resources (use `try/finally` for console redirection) diff --git a/README.md b/README.md index 92e91c7..51c802e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This template demonstrates: - **Simple Library Structure**: Demo class with example methods - **Multi-Platform Support**: Builds and runs on Windows and Linux -- **Multi-Runtime Support**: Targets .NET 8, 9, and 10 +- **Multi-Runtime Support**: Targets .NET Standard 2.0, .NET 8, 9, and 10 - **MSTest V4**: Modern unit testing with MSTest framework version 4 - **Comprehensive CI/CD**: GitHub Actions workflows with quality checks and builds - **Documentation Generation**: Automated build notes, user guide, code quality reports, diff --git a/docs/guide/guide.md b/docs/guide/guide.md index 71dbf6d..cb199f8 100644 --- a/docs/guide/guide.md +++ b/docs/guide/guide.md @@ -39,6 +39,16 @@ Console.WriteLine(result); // Output: Hello, World! The `DemoClass` provides demonstration functionality for the template library. +#### Constants + +##### DefaultPrefix + +```csharp +public const string DefaultPrefix = "Hello"; +``` + +The greeting prefix used when no custom prefix is specified. + #### Constructors ##### DemoClass() @@ -59,11 +69,22 @@ Initializes a new instance of the `DemoClass` class with a custom prefix. **Parameters:** -- `prefix` (string): The prefix to use in greetings. Must not be null. +- `prefix` (string): The prefix to use in greetings. Must not be null or empty. **Exceptions:** - `ArgumentNullException`: Thrown when `prefix` is null. +- `ArgumentException`: Thrown when `prefix` is an empty string. + +#### Properties + +##### Prefix + +```csharp +public string Prefix { get; } +``` + +Gets the greeting prefix used by this instance. #### Methods @@ -77,7 +98,7 @@ Returns a greeting message for the specified name. **Parameters:** -- `name` (string): The name to greet. Must not be null. +- `name` (string): The name to greet. Must not be null or empty. **Returns:** @@ -86,6 +107,7 @@ A string containing the greeting message in the format "{prefix}, {name}!". **Exceptions:** - `ArgumentNullException`: Thrown when `name` is null. +- `ArgumentException`: Thrown when `name` is an empty string. **Example:** diff --git a/requirements.yaml b/requirements.yaml index c205cce..1ed0922 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -21,6 +21,30 @@ sections: .NET library best practices for DEMA Consulting libraries. tests: - DemoMethod_ReturnsGreeting_WithDefaultPrefix + - DemoMethod_ReturnsGreeting_WithCustomPrefix + + - id: TMPL-REQ-008 + title: The library shall support a customizable greeting prefix. + justification: | + Consumers may need to produce greetings with a prefix other than the + default, so the library must allow the prefix to be specified at + construction time. + tests: + - DemoMethod_ReturnsGreeting_WithCustomPrefix + + - id: TMPL-REQ-009 + title: The library shall reject null and empty arguments with an appropriate ArgumentException. + justification: | + Null-safety and non-empty validation are fundamental API contracts: passing null or an + empty string to the constructor or to DemoMethod is a programming error. Throwing + ArgumentNullException for null arguments and ArgumentException for empty-string arguments + immediately surfaces the mistake to callers and prevents malformed output (such as + ", World!" or "Hello, !") and obscure failures deeper in the call stack. + tests: + - DemoMethod_ThrowsArgumentNullException_ForNullInput + - DemoMethod_ThrowsArgumentException_ForEmptyInput + - Constructor_ThrowsArgumentNullException_ForNullPrefix + - Constructor_ThrowsArgumentException_ForEmptyPrefix - title: Platform Support requirements: diff --git a/src/TemplateDotNetLibrary/DemoClass.cs b/src/TemplateDotNetLibrary/DemoClass.cs index 0035a85..3dd0524 100644 --- a/src/TemplateDotNetLibrary/DemoClass.cs +++ b/src/TemplateDotNetLibrary/DemoClass.cs @@ -5,13 +5,24 @@ namespace TemplateDotNetLibrary; /// public class DemoClass { + /// + /// The greeting prefix used when no custom prefix is specified. + /// + public const string DefaultPrefix = "Hello"; + + /// + /// The prefix prepended to every greeting produced by this instance. + /// private readonly string _prefix; /// - /// Initializes a new instance of the class with default prefix. + /// Initializes a new instance of the class with the default prefix. /// + /// + /// The prefix is set to . + /// public DemoClass() - : this("Hello") + : this(DefaultPrefix) { } @@ -19,20 +30,44 @@ public DemoClass() /// Initializes a new instance of the class with a custom prefix. /// /// The prefix to use in greetings. + /// + /// Thrown when is . + /// + /// + /// Thrown when is an empty string. + /// public DemoClass(string prefix) { - ArgumentNullException.ThrowIfNull(prefix); + // Validate that the prefix is non-null and non-empty before storing it + ArgumentException.ThrowIfNullOrEmpty(prefix); _prefix = prefix; } /// - /// Demo method that returns a greeting message. + /// Gets the greeting prefix used by this instance. /// - /// The name to greet. - /// A greeting message. + public string Prefix => _prefix; + + /// + /// Returns a greeting message that combines the instance prefix with the given name. + /// + /// The name to include in the greeting. + /// + /// A greeting string in the format {prefix}, {name}!, + /// for example Hello, World!. + /// + /// + /// Thrown when is . + /// + /// + /// Thrown when is an empty string. + /// public string DemoMethod(string name) { - ArgumentNullException.ThrowIfNull(name); + // Validate that the name is non-null and non-empty before building the greeting + ArgumentException.ThrowIfNullOrEmpty(name); + + // Combine the prefix and name into the standard greeting format return $"{_prefix}, {name}!"; } } diff --git a/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs b/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs index 389b915..0a00c64 100644 --- a/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs +++ b/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs @@ -6,8 +6,13 @@ namespace TemplateDotNetLibrary.Tests; [TestClass] public class DemoClassTests { + // ------------------------------------------------------------------------- + // DemoMethod – happy-path greeting output + // ------------------------------------------------------------------------- + /// - /// Test that DemoMethod returns the expected greeting with default prefix. + /// Proves that DemoMethod produces the expected "{prefix}, {name}!" format + /// when the default constructor is used. /// [TestMethod] public void DemoMethod_ReturnsGreeting_WithDefaultPrefix() @@ -19,12 +24,13 @@ public void DemoMethod_ReturnsGreeting_WithDefaultPrefix() // Act var result = demo.DemoMethod(name); - // Assert + // Assert – greeting must be exactly "Hello, World!" Assert.AreEqual("Hello, World!", result); } /// - /// Test that DemoMethod returns the expected greeting with custom prefix. + /// Proves that DemoMethod uses the custom prefix supplied at construction + /// time instead of the default. /// [TestMethod] public void DemoMethod_ReturnsGreeting_WithCustomPrefix() @@ -36,12 +42,17 @@ public void DemoMethod_ReturnsGreeting_WithCustomPrefix() // Act var result = demo.DemoMethod(name); - // Assert + // Assert – greeting must use the custom prefix "Hi" Assert.AreEqual("Hi, Alice!", result); } + // ------------------------------------------------------------------------- + // DemoMethod – argument validation + // ------------------------------------------------------------------------- + /// - /// Test that DemoMethod throws ArgumentNullException for null input. + /// Proves that DemoMethod throws ArgumentNullException (not a base + /// ArgumentException) when a null name is supplied. /// [TestMethod] public void DemoMethod_ThrowsArgumentNullException_ForNullInput() @@ -49,17 +60,107 @@ public void DemoMethod_ThrowsArgumentNullException_ForNullInput() // Arrange var demo = new DemoClass(); - // Act & Assert - _ = Assert.Throws(() => demo.DemoMethod(null!)); + // Act & Assert – exact exception type proves null is explicitly rejected + Assert.ThrowsExactly(() => demo.DemoMethod(null!)); } /// - /// Test that constructor throws ArgumentNullException for null prefix. + /// Proves that DemoMethod throws ArgumentException (not ArgumentNullException) + /// when an empty string name is supplied. + /// + [TestMethod] + public void DemoMethod_ThrowsArgumentException_ForEmptyInput() + { + // Arrange + var demo = new DemoClass(); + + // Act & Assert – ArgumentException (not the null sub-type) must be thrown + Assert.ThrowsExactly(() => demo.DemoMethod(string.Empty)); + } + + // ------------------------------------------------------------------------- + // Constructor – argument validation + // ------------------------------------------------------------------------- + + /// + /// Proves that the custom-prefix constructor throws ArgumentNullException + /// (not a base ArgumentException) when a null prefix is supplied. /// [TestMethod] public void Constructor_ThrowsArgumentNullException_ForNullPrefix() { - // Act & Assert - _ = Assert.Throws(() => new DemoClass(null!)); + // Act & Assert – exact exception type proves null is explicitly rejected + Assert.ThrowsExactly(() => new DemoClass(null!)); + } + + /// + /// Proves that the custom-prefix constructor throws ArgumentException + /// (not ArgumentNullException) when an empty string prefix is supplied. + /// + [TestMethod] + public void Constructor_ThrowsArgumentException_ForEmptyPrefix() + { + // Act & Assert – ArgumentException (not the null sub-type) must be thrown + Assert.ThrowsExactly(() => new DemoClass(string.Empty)); + } + + // ------------------------------------------------------------------------- + // DefaultPrefix constant + // ------------------------------------------------------------------------- + + /// + /// Proves that the DefaultPrefix constant exposes the value "Hello", + /// which is the expected default greeting prefix. + /// + [TestMethod] + public void DemoClass_DefaultPrefix_IsHello() + { + // Arrange + const string expected = "Hello"; + + // Act – read the public constant directly + var actual = DemoClass.DefaultPrefix; + + // Assert – constant value must not silently change + Assert.AreEqual(expected, actual); + } + + // ------------------------------------------------------------------------- + // Prefix property + // ------------------------------------------------------------------------- + + /// + /// Proves that the Prefix property returns the custom value provided to + /// the constructor, confirming the property reflects what was stored. + /// + [TestMethod] + public void DemoClass_Prefix_ReturnsCustomPrefix() + { + // Arrange + const string customPrefix = "Greetings"; + var demo = new DemoClass(customPrefix); + + // Act + var actual = demo.Prefix; + + // Assert – Prefix must exactly match the value passed at construction + Assert.AreEqual(customPrefix, actual); + } + + /// + /// Proves that the default constructor stores DefaultPrefix in the Prefix + /// property, tying the constant and the property together explicitly. + /// + [TestMethod] + public void DemoClass_DefaultConstructor_SetsDefaultPrefix() + { + // Arrange + var demo = new DemoClass(); + + // Act + var actual = demo.Prefix; + + // Assert – default constructor must yield exactly DemoClass.DefaultPrefix + Assert.AreEqual(DemoClass.DefaultPrefix, actual); } } From 628e6577396628b7ef6f9fbca0539c685009ac06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:31:37 +0000 Subject: [PATCH 3/5] Remove non-XML-doc section separator comments from test file Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> --- .../DemoClassTests.cs | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs b/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs index 0a00c64..c70659b 100644 --- a/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs +++ b/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs @@ -6,10 +6,6 @@ namespace TemplateDotNetLibrary.Tests; [TestClass] public class DemoClassTests { - // ------------------------------------------------------------------------- - // DemoMethod – happy-path greeting output - // ------------------------------------------------------------------------- - /// /// Proves that DemoMethod produces the expected "{prefix}, {name}!" format /// when the default constructor is used. @@ -46,10 +42,6 @@ public void DemoMethod_ReturnsGreeting_WithCustomPrefix() Assert.AreEqual("Hi, Alice!", result); } - // ------------------------------------------------------------------------- - // DemoMethod – argument validation - // ------------------------------------------------------------------------- - /// /// Proves that DemoMethod throws ArgumentNullException (not a base /// ArgumentException) when a null name is supplied. @@ -78,10 +70,6 @@ public void DemoMethod_ThrowsArgumentException_ForEmptyInput() Assert.ThrowsExactly(() => demo.DemoMethod(string.Empty)); } - // ------------------------------------------------------------------------- - // Constructor – argument validation - // ------------------------------------------------------------------------- - /// /// Proves that the custom-prefix constructor throws ArgumentNullException /// (not a base ArgumentException) when a null prefix is supplied. @@ -104,10 +92,6 @@ public void Constructor_ThrowsArgumentException_ForEmptyPrefix() Assert.ThrowsExactly(() => new DemoClass(string.Empty)); } - // ------------------------------------------------------------------------- - // DefaultPrefix constant - // ------------------------------------------------------------------------- - /// /// Proves that the DefaultPrefix constant exposes the value "Hello", /// which is the expected default greeting prefix. @@ -125,10 +109,6 @@ public void DemoClass_DefaultPrefix_IsHello() Assert.AreEqual(expected, actual); } - // ------------------------------------------------------------------------- - // Prefix property - // ------------------------------------------------------------------------- - /// /// Proves that the Prefix property returns the custom value provided to /// the constructor, confirming the property reflects what was stored. From dc34fef698c5c03a01afcc66b28e3481c1366b6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:41:55 +0000 Subject: [PATCH 4/5] Fix upload-artifact action version: v8 does not exist, use v4 Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> --- .github/workflows/build.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2e03ee9..11897b8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -38,7 +38,7 @@ jobs: echo "✓ Tool versions captured" - name: Upload version capture - uses: actions/upload-artifact@v8 + uses: actions/upload-artifact@v4 with: name: version-capture-quality path: versionmark-quality.json @@ -155,19 +155,19 @@ jobs: echo "✓ Tool versions captured" - name: Upload version capture - uses: actions/upload-artifact@v8 + uses: actions/upload-artifact@v4 with: name: version-capture-${{ matrix.os }} path: versionmark-build-*.json - name: Upload Test Results - uses: actions/upload-artifact@v8 + uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.os }} path: test-results/*.trx - name: Upload Artifacts - uses: actions/upload-artifact@v8 + uses: actions/upload-artifact@v4 with: name: artifacts-${{ matrix.os }} path: | @@ -229,7 +229,7 @@ jobs: upload: false - name: Upload CodeQL SARIF - uses: actions/upload-artifact@v8 + uses: actions/upload-artifact@v4 with: name: codeql-sarif path: sarif-results/csharp.sarif @@ -495,7 +495,7 @@ jobs: # Downstream projects: Add any additional artifact uploads here. - name: Upload documentation - uses: actions/upload-artifact@v8 + uses: actions/upload-artifact@v4 with: name: documents path: | From 95c946e51d731edf9730e57cb28d8d30a8f6a506 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:46:53 +0000 Subject: [PATCH 5/5] Revert upload-artifact to v7 (original version) Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com> --- .github/workflows/build.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 11897b8..0d94e19 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -38,7 +38,7 @@ jobs: echo "✓ Tool versions captured" - name: Upload version capture - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: version-capture-quality path: versionmark-quality.json @@ -155,19 +155,19 @@ jobs: echo "✓ Tool versions captured" - name: Upload version capture - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: version-capture-${{ matrix.os }} path: versionmark-build-*.json - name: Upload Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-results-${{ matrix.os }} path: test-results/*.trx - name: Upload Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: artifacts-${{ matrix.os }} path: | @@ -229,7 +229,7 @@ jobs: upload: false - name: Upload CodeQL SARIF - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: codeql-sarif path: sarif-results/csharp.sarif @@ -495,7 +495,7 @@ jobs: # Downstream projects: Add any additional artifact uploads here. - name: Upload documentation - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: documents path: |