diff --git a/.cspell.yaml b/.cspell.yaml index 6189e92..7881c28 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -19,7 +19,7 @@ words: - Dema - FileAssert - fileassert - - mstest + - xunit - Pandoc - Qube - reqstream diff --git a/.github/agents/repo-consistency.agent.md b/.github/agents/repo-consistency.agent.md deleted file mode 100644 index 0b66277..0000000 --- a/.github/agents/repo-consistency.agent.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -name: repo-consistency -description: > - Ensures downstream repositories remain consistent with the TemplateDotNetLibrary - template patterns and best practices. -user-invocable: true ---- - -# Repo Consistency Agent - -Maintain consistency between downstream projects and the TemplateDotNetLibrary template, ensuring repositories -benefit from template evolution while respecting project-specific customizations. - -# Consistency Workflow (MANDATORY) - -**CRITICAL**: This agent MUST follow these steps systematically to ensure proper template consistency analysis: - -1. **Fetch Recent Template Changes**: Use GitHub search to fetch the 20 most recently merged PRs - (`is:pr is:merged sort:updated-desc`) from -2. **Analyze Template Evolution**: For each relevant PR, determine the intent and scope of changes - (what files were modified, what improvements were made) -3. **Assess Downstream Applicability**: Evaluate which template changes would benefit this repository - while respecting project-specific customizations -4. **Apply Appropriate Updates**: Implement applicable template improvements with proper translation for project context -5. **Validate Consistency**: Verify that applied changes maintain functionality and follow project patterns -6. **Generate completion report** per the AGENTS.md reporting requirements - save to - `.agent-logs/{agent-name}-{subject}-{unique-id}.md` and return the summary to the caller - -## Key Principles - -- **Evolutionary Consistency**: Template improvements should enhance downstream projects systematically -- **Intelligent Customization Respect**: Distinguish valid customizations from unintentional drift -- **Incremental Template Adoption**: Support phased adoption of template improvements based on project capacity - -# Don't Do These Things - -- **Never recommend changes without understanding project context** (some differences are intentional) -- **Never flag valid project-specific customizations** as consistency problems -- **Never apply template changes blindly** without assessing downstream project impact -- **Never ignore template evolution benefits** when they clearly improve downstream projects -- **Never recommend breaking changes** without migration guidance and impact assessment -- **Never skip validation** of preserved functionality after template alignment -- **Never assume all template patterns apply universally** (assess project-specific needs) - -# Report Template - -```markdown -# Repo Consistency Report - -**Result**: (SUCCEEDED|FAILED) - -## Consistency Analysis - -- **Template PRs Analyzed**: {Number and timeframe of PRs reviewed} -- **Template Changes Identified**: {Count and types of template improvements} -- **Applicable Updates**: {Changes determined suitable for this repository} -- **Project Customizations Preserved**: {Valid differences maintained} - -## Template Evolution Applied - -- **Files Modified**: {List of files updated for template consistency} -- **Improvements Adopted**: {Specific template enhancements implemented} -- **Configuration Updates**: {Tool configurations, workflows, or standards updated} - -## Consistency Status - -- **Template Alignment**: {Overall consistency rating with template} -- **Customization Respect**: {How project-specific needs were preserved} -- **Functionality Validation**: {Verification that changes don't break existing features} -- **Future Consistency**: {Recommendations for ongoing template alignment} - -## Issues Resolved - -- **Drift Corrections**: {Template drift issues addressed} -- **Enhancement Adoptions**: {Template improvements successfully integrated} -- **Validation Results**: {Testing and validation outcomes} -``` diff --git a/.github/standards/coding-principles.md b/.github/standards/coding-principles.md index 8470677..937f39c 100644 --- a/.github/standards/coding-principles.md +++ b/.github/standards/coding-principles.md @@ -20,9 +20,9 @@ All code MUST follow literate programming principles: matches design intent without reading the full codebase - **Logical Separation**: Complex functions use block comments to separate and describe logical steps within the implementation -- **Public Documentation**: All public interfaces have comprehensive documentation - because consumers and auditors rely on interface contracts for integration - and compliance verification +- **Full Symbol Documentation**: ALL symbols (public, protected, and private) + have comprehensive documentation because reviewers and auditors must verify + every implementation detail, not just the public interface - **Clarity Over Cleverness**: Code should be immediately understandable by team members ## API Documentation diff --git a/.github/standards/csharp-language.md b/.github/standards/csharp-language.md index a580a39..9ada3f1 100644 --- a/.github/standards/csharp-language.md +++ b/.github/standards/csharp-language.md @@ -4,18 +4,12 @@ description: Follow these standards when developing C# source code. globs: ["**/*.cs"] --- -# C# Language Development Standard - -## Required Standards +# Required Standards Read these standards first before applying this standard: - **`coding-principles.md`** - Universal coding principles and quality gates -# File Patterns - -- **Source Files**: `**/*.cs` - # API Documentation and Literate Coding Example The example below demonstrates good XmlDoc API documentation combined with diff --git a/.github/standards/csharp-testing.md b/.github/standards/csharp-testing.md index 1591eeb..b8fd426 100644 --- a/.github/standards/csharp-testing.md +++ b/.github/standards/csharp-testing.md @@ -4,115 +4,71 @@ description: Follow these standards when developing C# tests. globs: ["**/test/**/*.cs", "**/tests/**/*.cs", "**/*Tests.cs", "**/*Test.cs"] --- -# C# Testing Standards (MSTest) - -This document defines standards for C# test development using -MSTest within Continuous Compliance environments. - -## Required Standards +# Required Standards Read these standards first before applying this standard: - **`testing-principles.md`** - Universal testing principles and dependency boundaries - **`csharp-language.md`** - C# language development standards -# C# AAA Pattern Implementation +# Package Reference -```csharp -[TestMethod] -public void ServiceName_MethodName_Scenario_ExpectedBehavior() -{ - // Arrange: description of setup (omit if nothing to set up) +Every xUnit v3 test project requires the following package references for +`dotnet test` to discover and execute tests: - // Act: description of action (can combine with Assert when action occurs within assertion) +| Package | Purpose | +| ------- | ------- | +| `xunit.v3` | xUnit v3 framework (monolithic — includes assertions and fixtures) | +| `Microsoft.NET.Test.Sdk` | Required by the VSTest/`dotnet test` host for test discovery | +| `xunit.runner.visualstudio` | VSTest adapter that bridges xUnit v3 to `dotnet test` | +| `NSubstitute` | Mocking library | - // Assert: description of verification -} -``` +Omitting `Microsoft.NET.Test.Sdk` or `xunit.runner.visualstudio` causes tests +to be silently undiscoverable by `dotnet test`. -# Test Naming Standards +# Test Style -Use descriptive test names because test names appear in requirements traceability matrices and compliance reports. +Test names appear in requirements traceability matrices — use the hierarchical +naming pattern, and follow AAA with labeled comments: - **System tests**: `{SystemName}_{Functionality}_{Scenario}_{ExpectedBehavior}` - **Subsystem tests**: `{SubsystemName}_{Functionality}_{Scenario}_{ExpectedBehavior}` - **Unit tests**: `{ClassName}_{MethodUnderTest}_{Scenario}_{ExpectedBehavior}` -- **Descriptive Scenarios**: Clearly describe the input condition being tested -- **Expected Behavior**: State the expected outcome or exception - -## Examples - -- `UserValidator_ValidateEmail_ValidFormat_ReturnsTrue` -- `UserValidator_ValidateEmail_InvalidFormat_ThrowsArgumentException` -- `PaymentProcessor_ProcessPayment_InsufficientFunds_ReturnsFailureResult` - -# Mock Dependencies - -Mock external dependencies using NSubstitute (preferred) because tests must run in isolation to generate -reliable evidence. - -- **Isolate System Under Test**: Mock all external dependencies (databases, web services, file systems) -- **Verify Interactions**: Assert that expected method calls occurred with correct parameters -- **Predictable Behavior**: Set up mocks to return known values for consistent test results - -# MSTest V4 Anti-patterns - -Avoid these common MSTest V4 patterns because they produce poor error messages or cause tests to be silently ignored. - -# Avoid Assertions in Catch Blocks (MSTEST0058) - -Instead of wrapping code in try/catch and asserting in the catch block, use `Assert.ThrowsExactly()`: ```csharp -var ex = Assert.ThrowsExactly(() => SomeWork()); -Assert.Contains("Some message", ex.Message); -``` - -# Avoid Assert.IsTrue/IsFalse for Equality Checks - -Use `Assert.AreEqual`/`Assert.AreNotEqual` instead, as they provide better failure messages: - -```csharp -// ❌ Bad: Assert.IsTrue(result == expected); -// ✅ Good: Assert.AreEqual(expected, result); -``` - -# Avoid Non-Public Test Classes and Methods - -Test classes and `[TestMethod]` methods must be `public` or they will be silently ignored: - -```csharp -// ❌ Bad: internal class MyTests -// ✅ Good: public class MyTests -``` - -# Avoid Assert.IsTrue for Collection Count - -Use `Assert.HasCount` for count assertions: +/// +/// Validates that an invalid email format throws an ArgumentException. +/// +[Fact] +public void UserValidator_ValidateEmail_InvalidFormat_ThrowsArgumentException() +{ + // Arrange: create a validator with default configuration + var validator = new UserValidator(); -```csharp -// ❌ Bad: Assert.IsTrue(collection.Count == 3); -// ✅ Good: Assert.HasCount(3, collection); + // Act / Assert: email with no domain throws + Assert.Throws(() => validator.ValidateEmail("not-an-email")); +} ``` -# Avoid Assert.IsTrue for String Prefix Checks +# xUnit v3 Specifics -Use `Assert.StartsWith` instead, as it produces clearer failure messages: +These are non-obvious v3 behaviors that differ from v2 or common assumptions: -```csharp -// ❌ Bad: Assert.IsTrue(value.StartsWith("prefix")); -// ✅ Good: Assert.StartsWith("prefix", value); -``` +- **`IAsyncLifetime`**: Both `InitializeAsync` and `DisposeAsync` return `ValueTask` + in v3, not `Task` — using `Task` compiles but does not satisfy the v3 interface +- **`Assert.Multiple`**: Use to collect all assertion failures in a single test + rather than stopping at the first +- **`[Collection]` without `[CollectionDefinition]`**: Silently disables parallelism + without providing any shared fixture — always pair them or remove `[Collection]` # Quality Checks Before submitting C# tests, verify: - [ ] All tests follow AAA pattern with clear section comments -- [ ] Test names follow hierarchical patterns defined in Test Naming Standards section -- [ ] Each test verifies single, specific behavior (no shared state) +- [ ] Test names follow hierarchical naming pattern above +- [ ] Each test verifies single, specific behavior (no shared state between tests) - [ ] Both success and failure scenarios covered including edge cases -- [ ] External dependencies mocked with NSubstitute or equivalent +- [ ] External dependencies mocked with NSubstitute - [ ] Tests linked to requirements with source filters where needed -- [ ] Test results generate TRX format for ReqStream compatibility -- [ ] MSTest V4 anti-patterns avoided (proper assertions, public visibility, etc.) +- [ ] Test results generated in TRX format for ReqStream compatibility (`dotnet test --logger trx`) diff --git a/AGENTS.md b/AGENTS.md index 3611fee..ac816d2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,18 @@ +# Project Overview + +> **Downstream customization required**: Replace the `TODO` values below with +> values specific to the target repository. + +- **name**: TODO +- **description**: TODO +- **languages**: TODO +- **technologies**: TODO + # Project Structure +> **Downstream customization required**: Replace `{project}` and +> `{test-project}` with the actual source and test project folder names. + ```text ├── docs/ │ ├── build_notes/ @@ -69,26 +82,10 @@ Delegate to specialized agents only for specific scenarios: - **Formal feature implementation** (complex, multi-step) → Call the implementation agent - **Formal bug resolution** (complex debugging, systematic fixes) → Call the implementation agent - **Formal reviews** (compliance verification, detailed analysis) → Call the formal-review agent -- **Template consistency** (downstream repository alignment) → Call the repo-consistency agent - -## Available Specialized Agents - -- **lint-fix** - Pre-PR lint sweep agent that loops running `pwsh ./lint.ps1`, - fixing issues until the repository is lint-clean -- **developer** - General-purpose software development agent that applies appropriate - standards based on the work being performed -- **formal-review** - Agent for performing formal reviews using standardized review processes -- **implementation** - Orchestrator agent that manages quality implementations - through a formal state machine workflow -- **quality** - Quality assurance agent that grades developer work against project - standards and Continuous Compliance practices -- **repo-consistency** - Ensures downstream repositories remain consistent with - the TemplateDotNetLibrary template patterns and best practices # Agent Reporting (Specialized Agents Must Follow) -Specialized agents (lint-fix, developer, quality, implementation, -formal-review, repo-consistency) MUST generate a completion report: +Specialized agents MUST generate a completion report: 1. Save to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` where `{subject}` is a kebab-case task summary (max 5 words) and diff --git a/docs/reqstream/ots/mstest.yaml b/docs/reqstream/ots/xunit.yaml similarity index 74% rename from docs/reqstream/ots/mstest.yaml rename to docs/reqstream/ots/xunit.yaml index 5faa16a..c17f0cc 100644 --- a/docs/reqstream/ots/mstest.yaml +++ b/docs/reqstream/ots/xunit.yaml @@ -1,17 +1,17 @@ --- -# MSTest OTS Software Requirements +# xUnit v3 OTS Software Requirements # -# Requirements for the MSTest testing framework functionality. +# Requirements for the xUnit v3 testing framework functionality. sections: - title: OTS Software Requirements sections: - - title: MSTest Requirements + - title: xUnit v3 Requirements requirements: - - id: Template-OTS-MSTest - title: MSTest shall execute unit tests and report results. + - id: Template-OTS-XUnit + title: xUnit v3 shall execute unit tests and report results. justification: | - MSTest (MSTest.TestFramework and MSTest.TestAdapter) is the unit-testing framework used + xUnit v3 (xunit.v3 and xunit.runner.visualstudio) is the unit-testing framework used by the project. It discovers and runs all test methods and writes TRX result files that feed into coverage reporting and requirements traceability. Passing tests confirm the framework is functioning correctly. diff --git a/requirements.yaml b/requirements.yaml index f70a60b..eddf147 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -4,7 +4,7 @@ includes: - docs/reqstream/template-dot-net-library/template-dot-net-library.yaml - docs/reqstream/template-dot-net-library/demo.yaml - docs/reqstream/template-dot-net-library/platform-requirements.yaml - - docs/reqstream/ots/mstest.yaml + - docs/reqstream/ots/xunit.yaml - docs/reqstream/ots/reqstream.yaml - docs/reqstream/ots/buildmark.yaml - docs/reqstream/ots/versionmark.yaml diff --git a/test/DemaConsulting.TemplateDotNetLibrary.Tests/DemaConsulting.TemplateDotNetLibrary.Tests.csproj b/test/DemaConsulting.TemplateDotNetLibrary.Tests/DemaConsulting.TemplateDotNetLibrary.Tests.csproj index 2413e9c..1efd0a0 100644 --- a/test/DemaConsulting.TemplateDotNetLibrary.Tests/DemaConsulting.TemplateDotNetLibrary.Tests.csproj +++ b/test/DemaConsulting.TemplateDotNetLibrary.Tests/DemaConsulting.TemplateDotNetLibrary.Tests.csproj @@ -8,6 +8,7 @@ enable enable + Exe false true true @@ -22,6 +23,7 @@ + @@ -35,8 +37,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/test/DemaConsulting.TemplateDotNetLibrary.Tests/DemoTests.cs b/test/DemaConsulting.TemplateDotNetLibrary.Tests/DemoTests.cs index b2569e6..8d4a003 100644 --- a/test/DemaConsulting.TemplateDotNetLibrary.Tests/DemoTests.cs +++ b/test/DemaConsulting.TemplateDotNetLibrary.Tests/DemoTests.cs @@ -3,14 +3,13 @@ namespace TemplateDotNetLibrary.Tests; /// /// Unit tests for the Demo class. /// -[TestClass] public class DemoTests { /// /// Proves that DemoMethod produces the expected "{prefix}, {name}!" format /// when the default constructor is used. /// - [TestMethod] + [Fact] public void Demo_DemoMethod_DefaultPrefix_ReturnsGreeting() { // Arrange: set up Demo with default constructor and test name @@ -21,14 +20,14 @@ public void Demo_DemoMethod_DefaultPrefix_ReturnsGreeting() var result = demo.DemoMethod(name); // Assert: greeting must be exactly "Hello, World!" - Assert.AreEqual("Hello, World!", result); + Assert.Equal("Hello, World!", result); } /// /// Proves that DemoMethod uses the custom prefix supplied at construction /// time instead of the default. /// - [TestMethod] + [Fact] public void Demo_DemoMethod_CustomPrefix_ReturnsGreeting() { // Arrange: set up Demo with custom prefix and test name @@ -39,64 +38,64 @@ public void Demo_DemoMethod_CustomPrefix_ReturnsGreeting() var result = demo.DemoMethod(name); // Assert: greeting must use the custom prefix "Hi" - Assert.AreEqual("Hi, Alice!", result); + Assert.Equal("Hi, Alice!", result); } /// /// Proves that DemoMethod throws ArgumentNullException (not a base /// ArgumentException) when a null name is supplied. /// - [TestMethod] + [Fact] public void Demo_DemoMethod_NullInput_ThrowsArgumentNullException() { // Arrange: set up Demo with default constructor var demo = new Demo(); // Act & Assert: exact exception type proves null is explicitly rejected - Assert.ThrowsExactly(() => demo.DemoMethod(null!)); + Assert.Throws(() => demo.DemoMethod(null!)); } /// /// Proves that DemoMethod throws ArgumentException (not ArgumentNullException) /// when an empty string name is supplied. /// - [TestMethod] + [Fact] public void Demo_DemoMethod_EmptyInput_ThrowsArgumentException() { // Arrange: set up Demo with default constructor var demo = new Demo(); // Act & Assert: ArgumentException (not the null sub-type) must be thrown - Assert.ThrowsExactly(() => demo.DemoMethod(string.Empty)); + Assert.Throws(() => demo.DemoMethod(string.Empty)); } /// /// Proves that the custom-prefix constructor throws ArgumentNullException /// (not a base ArgumentException) when a null prefix is supplied. /// - [TestMethod] + [Fact] public void Demo_Constructor_NullPrefix_ThrowsArgumentNullException() { // Act & Assert: exact exception type proves null is explicitly rejected - Assert.ThrowsExactly(() => new Demo(null!)); + Assert.Throws(() => new Demo(null!)); } /// /// Proves that the custom-prefix constructor throws ArgumentException /// (not ArgumentNullException) when an empty string prefix is supplied. /// - [TestMethod] + [Fact] public void Demo_Constructor_EmptyPrefix_ThrowsArgumentException() { // Act & Assert: ArgumentException (not the null sub-type) must be thrown - Assert.ThrowsExactly(() => new Demo(string.Empty)); + Assert.Throws(() => new Demo(string.Empty)); } /// /// Proves that the DefaultPrefix constant exposes the value "Hello", /// which is the expected default greeting prefix. /// - [TestMethod] + [Fact] public void Demo_DefaultPrefix_IsHello() { // Arrange: set expected value @@ -106,14 +105,14 @@ public void Demo_DefaultPrefix_IsHello() var actual = Demo.DefaultPrefix; // Assert: constant value must not silently change - Assert.AreEqual(expected, actual); + Assert.Equal(expected, actual); } /// /// Proves that the Prefix property returns the custom value provided to /// the constructor, confirming the property reflects what was stored. /// - [TestMethod] + [Fact] public void Demo_Prefix_WithCustomConstruction_ReturnsCustomPrefix() { // Arrange: set up Demo with custom prefix @@ -124,14 +123,14 @@ public void Demo_Prefix_WithCustomConstruction_ReturnsCustomPrefix() var actual = demo.Prefix; // Assert: Prefix must exactly match the value passed at construction - Assert.AreEqual(customPrefix, actual); + Assert.Equal(customPrefix, actual); } /// /// Proves that the default constructor stores DefaultPrefix in the Prefix /// property, tying the constant and the property together explicitly. /// - [TestMethod] + [Fact] public void Demo_DefaultConstructor_WithNoArgs_SetsDefaultPrefix() { // Arrange: set up Demo with default constructor @@ -141,6 +140,6 @@ public void Demo_DefaultConstructor_WithNoArgs_SetsDefaultPrefix() var actual = demo.Prefix; // Assert: default constructor must yield exactly Demo.DefaultPrefix - Assert.AreEqual(Demo.DefaultPrefix, actual); + Assert.Equal(Demo.DefaultPrefix, actual); } } diff --git a/test/DemaConsulting.TemplateDotNetLibrary.Tests/TemplateDotNetLibraryTests.cs b/test/DemaConsulting.TemplateDotNetLibrary.Tests/TemplateDotNetLibraryTests.cs index 73f3d8a..9428b47 100644 --- a/test/DemaConsulting.TemplateDotNetLibrary.Tests/TemplateDotNetLibraryTests.cs +++ b/test/DemaConsulting.TemplateDotNetLibrary.Tests/TemplateDotNetLibraryTests.cs @@ -3,14 +3,13 @@ namespace TemplateDotNetLibrary.Tests; /// /// System-level integration tests for the TemplateDotNetLibrary system. /// -[TestClass] public class TemplateDotNetLibraryTests { /// /// Proves that the system can be instantiated and provides expected functionality /// when integrated with all components. /// - [TestMethod] + [Fact] public void TemplateDotNetLibrary_SystemIntegration_ProvidesExpectedFunctionality() { // Arrange: set up system-level integration test @@ -21,14 +20,14 @@ public void TemplateDotNetLibrary_SystemIntegration_ProvidesExpectedFunctionalit var result = demo.DemoMethod(testName); // Assert: system produces expected integrated behavior - Assert.AreEqual("Hello, System!", result); + Assert.Equal("Hello, System!", result); } /// /// Proves that the system handles configuration and customization properly /// across all integrated components. /// - [TestMethod] + [Fact] public void TemplateDotNetLibrary_SystemCustomization_HandlesConfigurationProperly() { // Arrange: set up system with custom configuration @@ -40,56 +39,56 @@ public void TemplateDotNetLibrary_SystemCustomization_HandlesConfigurationProper var result = demo.DemoMethod(testName); // Assert: system respects configuration across components - Assert.AreEqual("Welcome, Integration!", result); + Assert.Equal("Welcome, Integration!", result); } /// /// Proves that the system rejects null input passed to DemoMethod /// with the expected exception at the system level. /// - [TestMethod] + [Fact] public void TemplateDotNetLibrary_SystemValidation_DemoMethodRejectsNullInput() { // Arrange: set up system components var demo = new Demo(); // Act & Assert: system validates DemoMethod null input properly - Assert.ThrowsExactly(() => demo.DemoMethod(null!)); + Assert.Throws(() => demo.DemoMethod(null!)); } /// /// Proves that the system rejects empty input passed to DemoMethod /// with the expected exception at the system level. /// - [TestMethod] + [Fact] public void TemplateDotNetLibrary_SystemValidation_DemoMethodRejectsEmptyInput() { // Arrange: set up system components var demo = new Demo(); // Act & Assert: system validates DemoMethod empty input properly - Assert.ThrowsExactly(() => demo.DemoMethod(string.Empty)); + Assert.Throws(() => demo.DemoMethod(string.Empty)); } /// /// Proves that the system rejects a null constructor prefix /// with the expected exception at the system level. /// - [TestMethod] + [Fact] public void TemplateDotNetLibrary_SystemValidation_ConstructorRejectsNullPrefix() { // Act & Assert: system validates constructor null prefix properly - Assert.ThrowsExactly(() => new Demo(null!)); + Assert.Throws(() => new Demo(null!)); } /// /// Proves that the system rejects an empty constructor prefix /// with the expected exception at the system level. /// - [TestMethod] + [Fact] public void TemplateDotNetLibrary_SystemValidation_ConstructorRejectsEmptyPrefix() { // Act & Assert: system validates constructor empty prefix properly - Assert.ThrowsExactly(() => new Demo(string.Empty)); + Assert.Throws(() => new Demo(string.Empty)); } }