diff --git a/.reviewmark.yaml b/.reviewmark.yaml index c02eba2..a17a717 100644 --- a/.reviewmark.yaml +++ b/.reviewmark.yaml @@ -11,6 +11,7 @@ needs-review: - "**/*.cs" # All C# source and test files - "docs/reqstream/**/*.yaml" # Requirements files - "docs/design/**/*.md" # Design documentation files + - "docs/verification/**/*.md" # Verification documentation files - "docs/user_guide/**/*.md" # User guide documentation - "!**/obj/**" # Exclude build output - "!**/bin/**" # Exclude build output @@ -45,6 +46,7 @@ reviews: - "docs/reqstream/build-mark/build-mark.yaml" - "docs/design/introduction.md" - "docs/design/build-mark/build-mark.md" + - "docs/verification/build-mark/build-mark.md" - "test/**/IntegrationTests.cs" - "test/**/Runner.cs" - "test/**/AssemblyInfo.cs" @@ -56,6 +58,7 @@ reviews: - "docs/reqstream/build-mark/platform-requirements.yaml" - "docs/design/introduction.md" - "docs/design/build-mark/**/*.md" + - "docs/verification/introduction.md" - id: BuildMark-AllRequirements title: Review of All BuildMark requirements quality and traceability @@ -70,6 +73,7 @@ reviews: paths: - "docs/reqstream/build-mark/program.yaml" - "docs/design/build-mark/program.md" + - "docs/verification/build-mark/program.md" - "src/**/Program.cs" - "test/**/ProgramTests.cs" @@ -79,6 +83,7 @@ reviews: paths: - "docs/reqstream/build-mark/cli/cli.yaml" - "docs/design/build-mark/cli/cli.md" + - "docs/verification/build-mark/cli/cli.md" - "test/**/Cli/CliTests.cs" - id: BuildMark-Cli-Context @@ -86,6 +91,7 @@ reviews: paths: - "docs/reqstream/build-mark/cli/context.yaml" - "docs/design/build-mark/cli/context.md" + - "docs/verification/build-mark/cli/context.md" - "src/**/Cli/Context.cs" - "test/**/Cli/ContextTests.cs" @@ -95,6 +101,7 @@ reviews: paths: - "docs/reqstream/build-mark/self-test/self-test.yaml" - "docs/design/build-mark/self-test/self-test.md" + - "docs/verification/build-mark/self-test/self-test.md" - "test/**/SelfTest/SelfTestTests.cs" - id: BuildMark-SelfTest-Validation @@ -102,6 +109,7 @@ reviews: paths: - "docs/reqstream/build-mark/self-test/validation.yaml" - "docs/design/build-mark/self-test/validation.md" + - "docs/verification/build-mark/self-test/validation.md" - "src/**/SelfTest/Validation.cs" - "test/**/SelfTest/ValidationTests.cs" @@ -111,6 +119,7 @@ reviews: paths: - "docs/reqstream/build-mark/utilities/utilities.yaml" - "docs/design/build-mark/utilities/utilities.md" + - "docs/verification/build-mark/utilities/utilities.md" - "test/**/Utilities/UtilitiesTests.cs" - id: BuildMark-Utilities-PathHelpers @@ -118,6 +127,7 @@ reviews: paths: - "docs/reqstream/build-mark/utilities/path-helpers.yaml" - "docs/design/build-mark/utilities/path-helpers.md" + - "docs/verification/build-mark/utilities/path-helpers.md" - "src/**/Utilities/PathHelpers.cs" - "test/**/Utilities/PathHelpersTests.cs" @@ -126,6 +136,7 @@ reviews: paths: - "docs/reqstream/build-mark/utilities/process-runner.yaml" - "docs/design/build-mark/utilities/process-runner.md" + - "docs/verification/build-mark/utilities/process-runner.md" - "src/**/Utilities/ProcessRunner.cs" - "test/**/Utilities/ProcessRunnerTests.cs" @@ -135,6 +146,7 @@ reviews: paths: - "docs/reqstream/build-mark/version/version.yaml" - "docs/design/build-mark/version/version.md" + - "docs/verification/build-mark/version/version.md" - "test/**/Version/VersionTests.cs" - id: BuildMark-Version-VersionComparable @@ -142,6 +154,7 @@ reviews: paths: - "docs/reqstream/build-mark/version/version-comparable.yaml" - "docs/design/build-mark/version/version-comparable.md" + - "docs/verification/build-mark/version/version-comparable.md" - "src/**/Version/VersionComparable.cs" - "test/**/Version/VersionComparableTests.cs" @@ -150,6 +163,7 @@ reviews: paths: - "docs/reqstream/build-mark/version/version-semantic.yaml" - "docs/design/build-mark/version/version-semantic.md" + - "docs/verification/build-mark/version/version-semantic.md" - "src/**/Version/VersionSemantic.cs" - "test/**/Version/VersionSemanticTests.cs" @@ -158,6 +172,7 @@ reviews: paths: - "docs/reqstream/build-mark/version/version-tag.yaml" - "docs/design/build-mark/version/version-tag.md" + - "docs/verification/build-mark/version/version-tag.md" - "src/**/Version/VersionTag.cs" - "test/**/Version/VersionTagTests.cs" @@ -166,6 +181,8 @@ reviews: paths: - "docs/reqstream/build-mark/version/version-interval.yaml" - "docs/design/build-mark/version/version-interval.md" + - "docs/verification/build-mark/version/version-interval.md" + - "docs/verification/build-mark/version/version-interval-set.md" - "src/**/Version/VersionInterval.cs" - "src/**/Version/VersionIntervalSet.cs" - "test/**/Version/VersionIntervalTests.cs" @@ -176,6 +193,7 @@ reviews: paths: - "docs/reqstream/build-mark/version/version-commit-tag.yaml" - "docs/design/build-mark/version/version-commit-tag.md" + - "docs/verification/build-mark/version/version-commit-tag.md" - "src/**/Version/VersionCommitTag.cs" - "test/**/BuildNotes/BuildInformationTests.cs" @@ -185,6 +203,7 @@ reviews: paths: - "docs/reqstream/build-mark/build-notes/build-notes.yaml" - "docs/design/build-mark/build-notes/build-notes.md" + - "docs/verification/build-mark/build-notes/build-notes.md" - "test/**/BuildNotes/BuildNotesTests.cs" - id: BuildMark-BuildNotes-BuildInformation @@ -192,6 +211,7 @@ reviews: paths: - "docs/reqstream/build-mark/build-notes/build-information.yaml" - "docs/design/build-mark/build-notes/build-information.md" + - "docs/verification/build-mark/build-notes/build-information.md" - "src/**/BuildNotes/BuildInformation.cs" - "test/**/BuildNotes/BuildInformationTests.cs" @@ -200,6 +220,7 @@ reviews: paths: - "docs/reqstream/build-mark/build-notes/item-info.yaml" - "docs/design/build-mark/build-notes/item-info.md" + - "docs/verification/build-mark/build-notes/item-info.md" - "src/**/BuildNotes/ItemInfo.cs" - id: BuildMark-BuildNotes-WebLink @@ -207,6 +228,7 @@ reviews: paths: - "docs/reqstream/build-mark/build-notes/web-link.yaml" - "docs/design/build-mark/build-notes/web-link.md" + - "docs/verification/build-mark/build-notes/web-link.md" - "src/**/BuildNotes/WebLink.cs" - "test/**/BuildNotes/BuildInformationTests.cs" @@ -216,6 +238,7 @@ reviews: paths: - "docs/reqstream/build-mark/configuration/configuration.yaml" - "docs/design/build-mark/configuration/configuration.md" + - "docs/verification/build-mark/configuration/configuration.md" - "test/**/Configuration/ConfigurationSubsystemTests.cs" - id: BuildMark-Configuration-BuildMarkConfigReader @@ -223,6 +246,7 @@ reviews: paths: - "docs/reqstream/build-mark/configuration/configuration.yaml" - "docs/design/build-mark/configuration/configuration.md" + - "docs/verification/build-mark/configuration/build-mark-config-reader.md" - "src/**/Configuration/BuildMarkConfigReader.cs" - "test/**/Configuration/ConfigurationTests.cs" @@ -231,6 +255,8 @@ reviews: paths: - "docs/reqstream/build-mark/configuration/configuration.yaml" - "docs/design/build-mark/configuration/configuration.md" + - "docs/verification/build-mark/configuration/configuration-load-result.md" + - "docs/verification/build-mark/configuration/configuration-issue.md" - "src/**/Configuration/ConfigurationLoadResult.cs" - "src/**/Configuration/ConfigurationIssue.cs" - "test/**/Configuration/ConfigurationTests.cs" @@ -240,6 +266,14 @@ reviews: paths: - "docs/reqstream/build-mark/configuration/configuration.yaml" - "docs/design/build-mark/configuration/configuration.md" + - "docs/verification/build-mark/configuration/build-mark-config.md" + - "docs/verification/build-mark/configuration/connector-config.md" + - "docs/verification/build-mark/configuration/git-hub-connector-config.md" + - "docs/verification/build-mark/configuration/azure-dev-ops-connector-config.md" + - "docs/verification/build-mark/configuration/report-config.md" + - "docs/verification/build-mark/configuration/section-config.md" + - "docs/verification/build-mark/configuration/rule-config.md" + - "docs/verification/build-mark/configuration/rule-match-config.md" - "src/**/Configuration/BuildMarkConfig.cs" - "src/**/Configuration/ConnectorConfig.cs" - "src/**/Configuration/GitHubConnectorConfig.cs" @@ -255,6 +289,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/repo-connectors.yaml" - "docs/design/build-mark/repo-connectors/repo-connectors.md" + - "docs/verification/build-mark/repo-connectors/repo-connectors.md" - "test/**/RepoConnectors/RepoConnectorsTests.cs" - id: BuildMark-RepoConnectors-RepoConnectorBase @@ -262,6 +297,8 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/repo-connector-base.yaml" - "docs/design/build-mark/repo-connectors/repo-connector-base.md" + - "docs/verification/build-mark/repo-connectors/repo-connector-base.md" + - "docs/verification/build-mark/repo-connectors/i-repo-connector.md" - "src/**/RepoConnectors/IRepoConnector.cs" - "src/**/RepoConnectors/RepoConnectorBase.cs" - "test/**/RepoConnectors/RepoConnectorBaseTests.cs" @@ -271,6 +308,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/repo-connector-factory.yaml" - "docs/design/build-mark/repo-connectors/repo-connector-factory.md" + - "docs/verification/build-mark/repo-connectors/repo-connector-factory.md" - "src/**/RepoConnectors/RepoConnectorFactory.cs" - "test/**/RepoConnectors/RepoConnectorFactoryTests.cs" @@ -280,6 +318,8 @@ reviews: - "docs/reqstream/build-mark/repo-connectors/item-controls-parser.yaml" - "docs/design/build-mark/repo-connectors/item-controls-info.md" - "docs/design/build-mark/repo-connectors/item-controls-parser.md" + - "docs/verification/build-mark/repo-connectors/item-controls-parser.md" + - "docs/verification/build-mark/repo-connectors/item-controls-info.md" - "src/**/RepoConnectors/ItemControlsInfo.cs" - "src/**/RepoConnectors/ItemControlsParser.cs" - "test/**/ItemControls/ItemControlsParserTests.cs" @@ -290,6 +330,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/item-router.yaml" - "docs/design/build-mark/repo-connectors/item-router.md" + - "docs/verification/build-mark/repo-connectors/item-router.md" - "src/**/RepoConnectors/ItemRouter.cs" - "test/**/RepoConnectors/ItemRouterTests.cs" @@ -298,6 +339,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/mock/mock.yaml" - "docs/design/build-mark/repo-connectors/mock/mock.md" + - "docs/verification/build-mark/repo-connectors/mock/mock.md" - "test/**/RepoConnectors/Mock/MockTests.cs" - id: BuildMark-RepoConnectors-Mock-MockRepoConnector @@ -305,6 +347,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/mock/mock-repo-connector.yaml" - "docs/design/build-mark/repo-connectors/mock/mock-repo-connector.md" + - "docs/verification/build-mark/repo-connectors/mock/mock-repo-connector.md" - "src/**/RepoConnectors/Mock/MockRepoConnector.cs" - "test/**/RepoConnectors/Mock/MockRepoConnectorTests.cs" @@ -313,6 +356,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/github/github.yaml" - "docs/design/build-mark/repo-connectors/github/github.md" + - "docs/verification/build-mark/repo-connectors/github/github.md" - "test/**/RepoConnectors/GitHub/GitHubTests.cs" - id: BuildMark-RepoConnectors-GitHub-GitHubGraphQLClient @@ -321,6 +365,8 @@ reviews: - "docs/reqstream/build-mark/repo-connectors/github/github-graphql-client.yaml" - "docs/design/build-mark/repo-connectors/github/github-graphql-client.md" - "docs/design/build-mark/repo-connectors/github/github-graphql-types.md" + - "docs/verification/build-mark/repo-connectors/github/git-hub-graph-ql-client.md" + - "docs/verification/build-mark/repo-connectors/github/git-hub-graph-ql-types.md" - "src/**/RepoConnectors/GitHub/GitHubGraphQLClient.cs" - "src/**/RepoConnectors/GitHub/GitHubGraphQLTypes.cs" - "test/**/RepoConnectors/GitHub/GitHubGraphQLClientFindIssueIdsTests.cs" @@ -337,6 +383,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/github/github-repo-connector.yaml" - "docs/design/build-mark/repo-connectors/github/github-repo-connector.md" + - "docs/verification/build-mark/repo-connectors/github/git-hub-repo-connector.md" - "src/**/RepoConnectors/GitHub/GitHubRepoConnector.cs" - "test/**/RepoConnectors/GitHub/GitHubRepoConnectorTests.cs" - "test/**/RepoConnectors/GitHub/MockableGitHubRepoConnector.cs" @@ -346,6 +393,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops.yaml" - "docs/design/build-mark/repo-connectors/azure-devops/azure-devops.md" + - "docs/verification/build-mark/repo-connectors/azure-dev-ops/azure-dev-ops.md" - "test/**/RepoConnectors/AzureDevOps/AzureDevOpsTests.cs" - id: BuildMark-RepoConnectors-AzureDevOps-AzureDevOpsRestClient @@ -354,6 +402,8 @@ reviews: - "docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops-rest-client.yaml" - "docs/design/build-mark/repo-connectors/azure-devops/azure-devops-rest-client.md" - "docs/design/build-mark/repo-connectors/azure-devops/azure-devops-api-types.md" + - "docs/verification/build-mark/repo-connectors/azure-dev-ops/azure-dev-ops-rest-client.md" + - "docs/verification/build-mark/repo-connectors/azure-dev-ops/azure-dev-ops-api-types.md" - "src/**/RepoConnectors/AzureDevOps/AzureDevOpsRestClient.cs" - "src/**/RepoConnectors/AzureDevOps/AzureDevOpsApiTypes.cs" - "test/**/RepoConnectors/AzureDevOps/MockAzureDevOpsHttpMessageHandler.cs" @@ -364,6 +414,7 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/azure-devops/work-item-mapper.yaml" - "docs/design/build-mark/repo-connectors/azure-devops/work-item-mapper.md" + - "docs/verification/build-mark/repo-connectors/azure-dev-ops/work-item-mapper.md" - "src/**/RepoConnectors/AzureDevOps/WorkItemMapper.cs" - "test/**/RepoConnectors/AzureDevOps/WorkItemMapperTests.cs" @@ -372,6 +423,68 @@ reviews: paths: - "docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops-repo-connector.yaml" - "docs/design/build-mark/repo-connectors/azure-devops/azure-devops-repo-connector.md" + - "docs/verification/build-mark/repo-connectors/azure-dev-ops/azure-dev-ops-repo-connector.md" - "src/**/RepoConnectors/AzureDevOps/AzureDevOpsRepoConnector.cs" - "test/**/RepoConnectors/AzureDevOps/AzureDevOpsRepoConnectorTests.cs" - "test/**/RepoConnectors/AzureDevOps/MockableAzureDevOpsRepoConnector.cs" + + # OTS Items + - id: OTS-BuildMark + title: Review of BuildMark OTS verification evidence + paths: + - "docs/reqstream/ots/buildmark.yaml" + - "docs/verification/ots/buildmark.md" + + - id: OTS-FileAssert + title: Review of FileAssert OTS verification evidence + paths: + - "docs/reqstream/ots/fileassert.yaml" + - "docs/verification/ots/fileassert.md" + + - id: OTS-Pandoc + title: Review of Pandoc OTS verification evidence + paths: + - "docs/reqstream/ots/pandoc.yaml" + - "docs/verification/ots/pandoc.md" + + - id: OTS-ReqStream + title: Review of ReqStream OTS verification evidence + paths: + - "docs/reqstream/ots/reqstream.yaml" + - "docs/verification/ots/reqstream.md" + + - id: OTS-ReviewMark + title: Review of ReviewMark OTS verification evidence + paths: + - "docs/reqstream/ots/reviewmark.yaml" + - "docs/verification/ots/reviewmark.md" + + - id: OTS-SarifMark + title: Review of SarifMark OTS verification evidence + paths: + - "docs/reqstream/ots/sarifmark.yaml" + - "docs/verification/ots/sarifmark.md" + + - id: OTS-SonarMark + title: Review of SonarMark OTS verification evidence + paths: + - "docs/reqstream/ots/sonarmark.yaml" + - "docs/verification/ots/sonarmark.md" + + - id: OTS-VersionMark + title: Review of VersionMark OTS verification evidence + paths: + - "docs/reqstream/ots/versionmark.yaml" + - "docs/verification/ots/versionmark.md" + + - id: OTS-WeasyPrint + title: Review of WeasyPrint OTS verification evidence + paths: + - "docs/reqstream/ots/weasyprint.yaml" + - "docs/verification/ots/weasyprint.md" + + - id: OTS-xUnit + title: Review of xUnit OTS verification evidence + paths: + - "docs/reqstream/ots/xunit.yaml" + - "docs/verification/ots/xunit.md" diff --git a/docs/design/build-mark/program.md b/docs/design/build-mark/program.md index 5451cc2..c3c0ead 100644 --- a/docs/design/build-mark/program.md +++ b/docs/design/build-mark/program.md @@ -43,7 +43,7 @@ method applies the following priority order: 2. Print the application banner (version and copyright). 3. If `context.Help` is set, print the usage message and return. 4. If `context.Validate` is set, delegate to `Validation.Run(context)` and return. -5. If `context.Lint` is set, call `BuildMarkConfigReader.ReadAsync`, call +5. If `context.Lint` is set, call `LoadConfiguration()`, call `result.ReportTo(context)`, and return. 6. Otherwise, call `ProcessBuildNotes(context)` to generate the build report. diff --git a/docs/design/build-mark/version/version.md b/docs/design/build-mark/version/version.md index fa6afb8..b204c4b 100644 --- a/docs/design/build-mark/version/version.md +++ b/docs/design/build-mark/version/version.md @@ -22,12 +22,12 @@ The Version subsystem is composed of six units: ```text VersionTag (raw repository tag) ↓ (extraction) -VersionSemantic (parsed semantic version) +VersionSemantic (parsed semantic version) ↓ (normalization) VersionComparable (optimized comparison) ↓ (range operations) VersionInterval / VersionIntervalSet (version ranges) - ↓ (build association) + ↓ (build association) VersionCommitTag (version + commit hash) ``` @@ -35,7 +35,7 @@ VersionCommitTag (version + commit hash) ### Semantic Versioning Compliance -All version processing strictly adheres to [Semantic Versioning 2.0.0](https://semver.org/) specification +All version processing strictly adheres to Semantic Versioning 2.0.0 () specification to ensure predictable and industry-standard behavior. ### Performance Optimization @@ -48,7 +48,7 @@ through construction-time parsing and cached segment arrays. Each version type serves a specific purpose with clear boundaries: - **VersionTag**: Raw string from repository -- **VersionSemantic**: Validated SemVer structure +- **VersionSemantic**: Validated SemVer structure - **VersionComparable**: Optimized for comparison operations - **VersionInterval**: Range queries and filtering - **VersionCommitTag**: Build metadata association @@ -69,7 +69,7 @@ Each version type serves a specific purpose with clear boundaries: Version subsystem processes raw repository tags from GitHub and other sources, extracting semantic versions for build boundary determination. -### Build Notes +### Build Notes Provides VersionCommitTag associations that link semantic versions to specific commit hashes for build information generation. diff --git a/docs/design/introduction.md b/docs/design/introduction.md index f8a6000..b8009e9 100644 --- a/docs/design/introduction.md +++ b/docs/design/introduction.md @@ -45,7 +45,7 @@ BuildMark (System) │ └── ProcessRunner (Unit) ├── Version (Subsystem) │ ├── VersionComparable (Unit) -│ ├── VersionSemantic (Unit) +│ ├── VersionSemantic (Unit) │ ├── VersionTag (Unit) │ ├── VersionInterval (Unit) │ ├── VersionIntervalSet (Unit) @@ -156,6 +156,4 @@ Throughout this document: ## References - See the BuildMark User Guide for user-facing documentation. -- [BuildMark Repository][repo] - -[repo]: https://github.com/demaconsulting/BuildMark +- See the BuildMark repository at . diff --git a/docs/reqstream/build-mark/build-mark.yaml b/docs/reqstream/build-mark/build-mark.yaml index 28fd32a..6b24105 100644 --- a/docs/reqstream/build-mark/build-mark.yaml +++ b/docs/reqstream/build-mark/build-mark.yaml @@ -365,6 +365,48 @@ sections: - BuildMark-BuildNotes-ReportModel - BuildMark-Program-Report + - id: BuildMark-Report-BaselinePreRelease + title: >- + The tool shall select the previous tag with a different commit hash as the + baseline when generating build notes for a pre-release version. + justification: | + When two pre-release tags point to the same commit, using the immediately + preceding tag as the baseline would produce an empty changelog. Selecting + the most recent predecessor with a different commit hash ensures a meaningful + changelog is always generated for pre-release builds. + tests: + - BuildMark_Report_BaselinePreRelease_SkipsSameCommitPredecessor + children: + - BuildMark-RepoConnectorBase-BaselinePreRelease + + - id: BuildMark-Report-BaselineRelease + title: >- + The tool shall select the previous release tag as the baseline when + generating build notes for a release version, skipping all pre-release tags. + justification: | + Release notes should cover all changes since the last stable release, not + just changes since the most recent pre-release. Skipping pre-release + predecessors when selecting the baseline ensures completeness and correctness + of the generated release notes. + tests: + - BuildMark_Report_ShowsVersionRangeFromPreviousRelease + children: + - BuildMark-RepoConnectorBase-BaselineRelease + + - id: BuildMark-Report-BaselineFirstRelease + title: >- + The tool shall generate build notes from the beginning of repository history + when no previous version tag qualifies as a baseline. + justification: | + When a project publishes its first release there is no predecessor tag. + Treating the absence of a qualifying baseline as "generate from the start + of history" ensures that the first release still receives meaningful, complete + build notes rather than an error or an empty report. + tests: + - BuildMark_Report_BaselineFirstRelease_GeneratesFromBeginningOfHistory + children: + - BuildMark-RepoConnectorBase-BaselineNoHistory + - id: BuildMark-Report-Hyperlinks title: The tool shall include hyperlinks to issues and pull requests. justification: | diff --git a/docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops-rest-client.yaml b/docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops-rest-client.yaml index 1bf013c..2ba73b5 100644 --- a/docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops-rest-client.yaml +++ b/docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops-rest-client.yaml @@ -10,9 +10,8 @@ sections: requirements: - id: BuildMark-AzureDevOps-RestClient title: >- - The AzureDevOpsRestClient shall retrieve repository details, commits, tags, - pull requests, and work items from the Azure DevOps API, including support - for paginated responses. + The AzureDevOpsRestClient shall retrieve repository data using the Azure DevOps + REST API, including support for paginated responses. justification: | The Azure DevOps REST API returns large result sets in pages. The client must transparently follow pagination to retrieve complete repository data so that diff --git a/docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops.yaml b/docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops.yaml index 6ab7593..ca3d2ab 100644 --- a/docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops.yaml +++ b/docs/reqstream/build-mark/repo-connectors/azure-devops/azure-devops.yaml @@ -13,10 +13,8 @@ sections: requirements: - id: BuildMark-AzureDevOps-SubSystem title: >- - The AzureDevOps sub-subsystem shall provide a repository connector that implements - the IRepoConnector interface, retrieves build information from Azure DevOps, supports - URL parsing, item controls, configurable routing rules, REST API access, and work - item mapping. + The AzureDevOps sub-subsystem shall implement the IRepoConnector interface + for Azure DevOps repositories. justification: | Generating build notes from Azure DevOps repositories requires a dedicated sub-subsystem that can authenticate to the Azure DevOps REST API, retrieve commits, tags, pull diff --git a/docs/reqstream/build-mark/repo-connectors/github/github-graphql-client.yaml b/docs/reqstream/build-mark/repo-connectors/github/github-graphql-client.yaml index 5666515..c9620bc 100644 --- a/docs/reqstream/build-mark/repo-connectors/github/github-graphql-client.yaml +++ b/docs/reqstream/build-mark/repo-connectors/github/github-graphql-client.yaml @@ -11,8 +11,8 @@ sections: requirements: - id: BuildMark-GitHub-GraphQLClient title: >- - The GitHubGraphQLClient shall retrieve commits, releases, tags, pull requests, - and issues from the GitHub repository, including support for paginated responses. + The GitHubGraphQLClient shall retrieve repository data using the GitHub GraphQL API, + including support for paginated responses. justification: | The GitHub API returns large result sets in pages. The client must transparently follow pagination cursors to retrieve all commits, releases, tags, pull requests, diff --git a/docs/reqstream/build-mark/repo-connectors/github/github.yaml b/docs/reqstream/build-mark/repo-connectors/github/github.yaml index 9ca3ca1..a4efb5c 100644 --- a/docs/reqstream/build-mark/repo-connectors/github/github.yaml +++ b/docs/reqstream/build-mark/repo-connectors/github/github.yaml @@ -13,9 +13,8 @@ sections: requirements: - id: BuildMark-GitHub-SubSystem title: >- - The GitHub sub-subsystem shall provide a repository connector that implements - the IRepoConnector interface, retrieves build information from GitHub, supports - item controls, description body retrieval, and configurable routing rules. + The GitHub sub-subsystem shall implement the IRepoConnector interface + for GitHub repositories. justification: | Generating build notes from GitHub repositories requires a dedicated sub-subsystem that can authenticate to the GitHub API, retrieve commits, releases, tags, pull diff --git a/docs/reqstream/build-mark/repo-connectors/repo-connector-base.yaml b/docs/reqstream/build-mark/repo-connectors/repo-connector-base.yaml index 8bc8f71..6036504 100644 --- a/docs/reqstream/build-mark/repo-connectors/repo-connector-base.yaml +++ b/docs/reqstream/build-mark/repo-connectors/repo-connector-base.yaml @@ -63,3 +63,45 @@ sections: tests: - RepoConnectorBase_FindVersionIndex_DifferentPrefixSameVersion_ReturnsCorrectIndex - RepoConnectorBase_FindVersionIndex_VersionNotInList_ReturnsMinusOne + + - id: BuildMark-RepoConnectorBase-BaselinePreRelease + title: >- + For a pre-release target version, RepoConnectorBase shall select the most + recent preceding tag with a different commit hash as the baseline, skipping + tags that share the same commit hash to avoid generating empty changelogs. + justification: | + When multiple pre-release tags are created from the same commit (e.g., a tag + is renamed or recreated), using such a tag as the baseline would produce an + empty changelog because the commit range would be empty. By skipping tags with + the same commit hash, BuildMark always produces a meaningful changelog for + pre-release builds. + tests: + - RepoConnectorBase_FindBaselineForPreRelease_SameCommitSkipped_ReturnsPreviousDistinctCommit + - RepoConnectorBase_FindBaselineForPreRelease_AllSameCommit_ReturnsNull + + - id: BuildMark-RepoConnectorBase-BaselineRelease + title: >- + For a release target version, RepoConnectorBase shall select the most recent + preceding release tag as the baseline, skipping all pre-release tags. + justification: | + Release notes should describe changes since the previous stable release, not + since the most recent pre-release candidate. Skipping pre-release tags when + selecting the baseline ensures that the generated changelog covers the complete + set of changes that went into the release. + tests: + - RepoConnectorBase_FindBaselineForRelease_SkipsPreReleaseTags_ReturnsPreviousReleaseTag + - RepoConnectorBase_FindBaselineForRelease_NoPreviousRelease_ReturnsNull + + - id: BuildMark-RepoConnectorBase-BaselineNoHistory + title: >- + When no qualifying baseline tag exists in the version history, + RepoConnectorBase shall return null to indicate that build notes should be + generated from the beginning of repository history. + justification: | + The first release in a repository has no predecessor tag, so there is no + meaningful baseline from which to compute a changelog. Returning null allows + the connector to generate build notes covering the complete repository history + up to the first release, rather than producing an error or an empty report. + tests: + - RepoConnectorBase_FindBaselineForRelease_NoPreviousVersion_ReturnsNull + - RepoConnectorBase_FindBaselineForPreRelease_NoPreviousVersion_ReturnsNull diff --git a/docs/user_guide/cli-reference.md b/docs/user_guide/cli-reference.md index 32ef036..ca43d44 100644 --- a/docs/user_guide/cli-reference.md +++ b/docs/user_guide/cli-reference.md @@ -14,7 +14,7 @@ | `--build-version ` | Specify the build version for report generation | | `--report ` | Export build notes to a markdown file | | `--depth ` | Set markdown heading depth (default: 1) | -| `--include-known-issues` | Include open bugs as known issues | +| `--include-known-issues` | Include known issues (open bugs, plus closed bugs with matching affected-versions). | ## Display Options @@ -89,7 +89,8 @@ buildmark --build-version v1.2.3 --report build-notes.md --depth 2 ### `--include-known-issues` -Include known issues in the generated report. Known issues are open bugs in the GitHub repository. +Include known issues in the generated report. Known issues are open bugs in the repository, +plus closed bugs whose `affected-versions` field intersects the current build version. ```bash buildmark --build-version v1.2.3 --report build-notes.md --include-known-issues diff --git a/docs/verification/build-mark/build-notes/build-information.md b/docs/verification/build-mark/build-notes/build-information.md index eef2cc9..bea5183 100644 --- a/docs/verification/build-mark/build-notes/build-information.md +++ b/docs/verification/build-mark/build-notes/build-information.md @@ -2,57 +2,267 @@ ## Verification Approach -`BuildInformation` is a data model with no dedicated test class. It is verified -indirectly through integration tests in `ProgramTests.cs` and `RepoConnectorsTests.cs` -that exercise the full pipeline and assert on the structure and content of -`BuildInformation` instances returned by connectors. +`BuildInformation` is verified with dedicated unit tests in `BuildInformationTests.cs`. Tests +construct `BuildInformation` instances either directly or via `MockRepoConnector`, invoke +`ToMarkdown`, and assert on the rendered output. `NSubstitute` is used in a small number of tests +to simulate connector error conditions; `MockRepoConnector` is used for the remaining tests. No +further mocking is needed. ## Dependencies -| Mock / Stub | Reason | -| ------------------- | -------------------------------------------------- | -| `MockRepoConnector` | Returns deterministic `BuildInformation` instances | +| Mock / Stub | Reason | +| --- | --- | +| `MockRepoConnector` | Supplies deterministic `BuildInformation` instances for rendering tests. | +| `NSubstitute` (`IRepoConnector`) | Simulates error conditions that `MockRepoConnector` cannot reproduce. | -## Test Scenarios (Integration) +## Test Scenarios -### Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues +### BuildInformation_GetBuildInformationAsync_ThrowsWhenNoVersionAndNoTags -**Scenario**: `Program.Run` generates a report using a mock connector. +**Scenario**: A substitute connector is configured to throw `InvalidOperationException` with the +message "No tags found"; `GetBuildInformationAsync()` is called without a version argument. -**Expected**: `BuildInformation.ToMarkdown` produces valid markdown output including -known issues section. +**Expected**: `InvalidOperationException` is thrown; message contains "No tags found". -**Requirement coverage**: `BuildMark-BuildNotes-BuildInformation` +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. -### RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation +### BuildInformation_GetBuildInformationAsync_ThrowsWhenNoVersionAndCommitDoesNotMatchTag -**Scenario**: Mock connector's `GetBuildInformationAsync` returns a complete result. +**Scenario**: A substitute connector is configured to throw `InvalidOperationException` with the +message "does not match any tag"; `GetBuildInformationAsync()` is called. -**Expected**: `BuildInformation` instance contains version, baseline, changes, -known issues, and routed sections. +**Expected**: `InvalidOperationException` is thrown; message contains "does not match any tag". -**Requirement coverage**: `BuildMark-BuildNotes-BuildInformation` +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. -### RepoConnectors_GitHubConnector_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation +### BuildInformation_GetBuildInformationAsync_WorksWithExplicitVersion -**Scenario**: GitHub connector returns `BuildInformation` from mocked API responses. +**Scenario**: `MockRepoConnector.GetBuildInformationAsync(VersionTag.Create("v2.1.0"))` is called. -**Expected**: `BuildInformation` fields are correctly populated from the mock data. +**Expected**: `CurrentVersionTag.VersionTag.Tag` equals `"v2.1.0"`; commit hash is +`"current123hash456"`; `BaselineVersionTag.VersionTag.Tag` equals `"v2.0.0"`. -**Requirement coverage**: `BuildMark-BuildNotes-BuildInformation` +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. -### RepoConnectors_AzureDevOps_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation +### BuildInformation_GetBuildInformationAsync_WorksWhenCurrentCommitMatchesLatestTag -**Scenario**: Azure DevOps connector returns `BuildInformation` from mocked API responses. +**Scenario**: A substitute connector returns a `BuildInformation` where `CurrentVersionTag` is +`v2.0.0`; `GetBuildInformationAsync()` is called. -**Expected**: `BuildInformation` fields are correctly populated from the mock data. +**Expected**: Returned `BuildInformation.CurrentVersionTag.VersionTag.Tag` equals `"v2.0.0"`; +`BaselineVersionTag.VersionTag.Tag` equals `"ver-1.1.0"`. -**Requirement coverage**: `BuildMark-BuildNotes-BuildInformation` +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_GetBuildInformationAsync_PreReleaseUsesPreviousTag + +**Scenario**: `MockRepoConnector.GetBuildInformationAsync(VersionTag.Create("v2.0.0-beta.1"))` is +called. + +**Expected**: `CurrentVersionTag.VersionTag.Tag` equals `"v2.0.0-beta.1"`; +`BaselineVersionTag.VersionTag.Tag` equals `"v2.0.0"`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_GetBuildInformationAsync_ReleaseSkipsPreReleases + +**Scenario**: `MockRepoConnector.GetBuildInformationAsync(VersionTag.Create("v2.0.0"))` is called. + +**Expected**: `CurrentVersionTag.VersionTag.Tag` equals `"v2.0.0"`; +`BaselineVersionTag.VersionTag.Tag` equals `"ver-1.1.0"` (pre-releases are skipped). + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_GetBuildInformationAsync_CollectsIssuesCorrectly + +**Scenario**: `MockRepoConnector.GetBuildInformationAsync(VersionTag.Create("ver-1.1.0"))` is +called. + +**Expected**: `Changes` contains 2 items (ids `"1"` and `"#13"`); `Bugs` is empty; `KnownIssues` +contains 2 items (ids `"4"` and `"6"`). + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_GetBuildInformationAsync_OrdersChangesByIndex + +**Scenario**: `MockRepoConnector.GetBuildInformationAsync(VersionTag.Create("ver-1.1.0"))` is +called. + +**Expected**: `Changes` are ordered by `Index`; the item with `Index` 10 precedes the item with +`Index` 13. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_GetBuildInformationAsync_SeparatesBugAndChangeIssues + +**Scenario**: `MockRepoConnector.GetBuildInformationAsync(VersionTag.Create("v2.0.0"))` is called. + +**Expected**: `Changes` contains 1 item; `Bugs` contains 1 item (id `"2"`, title `"Fix bug in Y"`). + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_GetBuildInformationAsync_HandlesFirstReleaseCorrectly + +**Scenario**: `MockRepoConnector.GetBuildInformationAsync(VersionTag.Create("v1.0.0"))` is called. + +**Expected**: `BaselineVersionTag` is null; `CurrentVersionTag.VersionTag.Tag` equals `"v1.0.0"`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_GeneratesCorrectMarkdownWithDefaults + +**Scenario**: `ToMarkdown()` is called on `BuildInformation` for `v2.0.0` without any optional +arguments. + +**Expected**: Markdown contains `# Build Report`, `## Version Information`, `## Changes`, +`## Bugs Fixed`, `v2.0.0`, and `ver-1.1.0`; `## Known Issues` is absent. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_IncludesKnownIssuesWhenRequested + +**Scenario**: `ToMarkdown(includeKnownIssues: true)` is called on `BuildInformation` for `v2.0.0`. + +**Expected**: Markdown contains `## Known Issues`, `Known bug A`, and `Known bug C`; `Known bug B` +is absent. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_RespectsCustomHeadingDepth + +**Scenario**: `ToMarkdown(headingDepth: 3)` is called on `BuildInformation` for `v2.0.0`. + +**Expected**: Top-level heading is `### Build Report`; sub-headings are `#### Version Information`, +`#### Changes`, and `#### Bugs Fixed`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_DisplaysNAForEmptyChanges + +**Scenario**: A `BuildInformation` with an empty `Changes` list is rendered via `ToMarkdown()`. + +**Expected**: The Changes section contains `- N/A`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_DisplaysNAForEmptyBugs + +**Scenario**: A `BuildInformation` with an empty `Bugs` list is rendered via `ToMarkdown()`. + +**Expected**: The Bugs Fixed section contains `- N/A`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_IncludesIssueLinks + +**Scenario**: `ToMarkdown()` is called on `BuildInformation` for `v2.0.0` which has items with +GitHub URLs. + +**Expected**: Markdown contains formatted links such as +`- [3](https://github.com/example/repo/issues/3)` and +`- [2](https://github.com/example/repo/issues/2)`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_HandlesFirstReleaseWithNA + +**Scenario**: `ToMarkdown()` is called on `BuildInformation` for `v1.0.0` (no baseline). + +**Expected**: Version Information section contains `| **Previous Version** | N/A |` and +`| **Previous Commit Hash** | N/A |`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_IncludesFullChangelogWhenLinkPresent + +**Scenario**: `ToMarkdown()` is called on `BuildInformation` for `v2.0.0` which has a changelog +link. + +**Expected**: Markdown contains `## Full Changelog`, the text `See the full changelog at`, and the +URL containing `ver-1.1.0...v2.0.0`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_ExcludesFullChangelogWhenNoBaseline + +**Scenario**: `ToMarkdown()` is called on `BuildInformation` for `v1.0.0` (no baseline version). + +**Expected**: Markdown does not contain `## Full Changelog`. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### BuildInformation_ToMarkdown_UsesBulletLists + +**Scenario**: `ToMarkdown(includeKnownIssues: true)` is called on `BuildInformation` for `v2.0.0`. + +**Expected**: Changes, Bugs Fixed, and Known Issues sections each use `- [` bullet format; no +markdown table notation (`| :-: | :---------- |`) is present in those sections. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### VersionCommitTag_Constructor_StoresVersionAndHash + +**Scenario**: A `VersionCommitTag` is constructed with a `VersionTag` and a commit hash string. + +**Expected**: `VersionTag` property equals the supplied tag; `CommitHash` equals the supplied hash +string. + +**Requirement coverage**: `BuildMark-BuildInformation-Markdown`. + +### WebLink_Constructor_StoresTextAndUrl + +**Scenario**: A `WebLink` is constructed with display text and a target URL. + +**Expected**: `LinkText` property equals the supplied text; `TargetUrl` property equals the +supplied URL. + +**Requirement coverage**: `BuildMark-WebLink-Record`. + +### BuildInformation_ToMarkdown_WithRoutedSections_RendersCustomSections + +**Scenario**: A `BuildInformation` is constructed with a populated `RoutedSections` list containing +two custom sections (Features, Bugs); `ToMarkdown()` is called. + +**Expected**: Markdown contains `## Features` and `## Bugs` headings with their respective items; +`## Changes` and `## Bugs Fixed` are absent. + +**Requirement coverage**: `BuildMark-BuildInformation-RoutedSections`. + +### BuildInformation_ToMarkdown_WithoutRoutedSections_RendersDefaultSections + +**Scenario**: A `BuildInformation` is constructed with `RoutedSections` left as null; +`ToMarkdown()` is called. + +**Expected**: Markdown contains the default `## Changes` and `## Bugs Fixed` headings. + +**Requirement coverage**: `BuildMark-BuildInformation-RoutedSections`. ## Requirements Coverage -- **BuildMark-BuildNotes-BuildInformation**: - Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues, - RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation, - RepoConnectors_GitHubConnector_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation, - RepoConnectors_AzureDevOps_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation +- **`BuildMark-BuildInformation-Markdown`**: + - BuildInformation_GetBuildInformationAsync_ThrowsWhenNoVersionAndNoTags + - BuildInformation_GetBuildInformationAsync_ThrowsWhenNoVersionAndCommitDoesNotMatchTag + - BuildInformation_GetBuildInformationAsync_WorksWithExplicitVersion + - BuildInformation_GetBuildInformationAsync_WorksWhenCurrentCommitMatchesLatestTag + - BuildInformation_GetBuildInformationAsync_PreReleaseUsesPreviousTag + - BuildInformation_GetBuildInformationAsync_ReleaseSkipsPreReleases + - BuildInformation_GetBuildInformationAsync_CollectsIssuesCorrectly + - BuildInformation_GetBuildInformationAsync_OrdersChangesByIndex + - BuildInformation_GetBuildInformationAsync_SeparatesBugAndChangeIssues + - BuildInformation_GetBuildInformationAsync_HandlesFirstReleaseCorrectly + - BuildInformation_ToMarkdown_GeneratesCorrectMarkdownWithDefaults + - BuildInformation_ToMarkdown_IncludesKnownIssuesWhenRequested + - BuildInformation_ToMarkdown_RespectsCustomHeadingDepth + - BuildInformation_ToMarkdown_DisplaysNAForEmptyChanges + - BuildInformation_ToMarkdown_DisplaysNAForEmptyBugs + - BuildInformation_ToMarkdown_IncludesIssueLinks + - BuildInformation_ToMarkdown_HandlesFirstReleaseWithNA + - BuildInformation_ToMarkdown_IncludesFullChangelogWhenLinkPresent + - BuildInformation_ToMarkdown_ExcludesFullChangelogWhenNoBaseline + - BuildInformation_ToMarkdown_UsesBulletLists + - VersionCommitTag_Constructor_StoresVersionAndHash +- **`BuildMark-WebLink-Record`**: + - WebLink_Constructor_StoresTextAndUrl +- **`BuildMark-BuildInformation-RoutedSections`**: + - BuildInformation_ToMarkdown_WithRoutedSections_RendersCustomSections + - BuildInformation_ToMarkdown_WithoutRoutedSections_RendersDefaultSections diff --git a/docs/verification/build-mark/build-notes/build-notes.md b/docs/verification/build-mark/build-notes/build-notes.md index 7f91309..6ae4050 100644 --- a/docs/verification/build-mark/build-notes/build-notes.md +++ b/docs/verification/build-mark/build-notes/build-notes.md @@ -2,51 +2,52 @@ ## Verification Approach -The BuildNotes subsystem is verified at the integration level through `ProgramTests.cs` -and `RepoConnectorsTests.cs`. There is no dedicated `BuildNotesTests.cs` file; the -subsystem is exercised indirectly whenever `Program.Run` generates a report or when -a connector's `GetBuildInformationAsync` is exercised with mock data. - -`BuildInformation.ToMarkdown` is exercised by report generation tests. -`ItemInfo` and `WebLink` data models are exercised through the connector tests that -populate `BuildInformation` instances with change items and web links. +`BuildNotes` is the subsystem encompassing `BuildInformation`, `ItemInfo`, and `WebLink`. It is +verified with dedicated subsystem tests in `BuildNotesTests.cs`. The subsystem uses +`MockRepoConnector` to supply deterministic `BuildInformation` instances; no other mocking or test +doubles are required. ## Dependencies -| Mock / Stub | Reason | -| ------------------- | --------------------------------------------------------- | -| `MockRepoConnector` | Returns deterministic `BuildInformation` with known items | -| `Context` | Provides output capture for markdown assertion | +| Mock / Stub | Reason | +| ------------------- | -------------------------------------------------------------------- | +| `MockRepoConnector` | Provides deterministic `BuildInformation` for subsystem-level tests. | + +## Test Scenarios + +### BuildNotes_ReportModel_GeneratesCorrectMarkdown -## Test Scenarios (Integration) +**Scenario**: `MockRepoConnector.GetBuildInformationAsync` is called with version `v2.0.0`; the +resulting `BuildInformation` is rendered via `ToMarkdown()`. -The following integration tests in `ProgramTests.cs` exercise the BuildNotes subsystem: +**Expected**: Markdown contains `# Build Report`, `## Version Information`, `## Changes`, +`## Bugs Fixed`, `v2.0.0`, and `ver-1.1.0`. -### Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues +**Requirement coverage**: `BuildMark-BuildNotes-ReportModel`. -**Scenario**: A full pipeline run generates a report with known issues included. +### BuildNotes_ReportModel_IncludesKnownIssues -**Expected**: `BuildInformation` is populated; markdown report is written to file. +**Scenario**: `ToMarkdown(includeKnownIssues: true)` is called on a `BuildInformation` for `v2.0.0` +that has known issues. -**Requirement coverage**: `BuildMark-BuildNotes-BuildInformation`, -`BuildMark-BuildNotes-ItemInfo` +**Expected**: Markdown contains `## Known Issues`, `Known bug A`, and `Known bug C`; `Known bug B` +(which has `affected-versions [5.0.0,)`) does not appear. -The following tests in `RepoConnectorsTests.cs` exercise the subsystem through -connector output: +**Requirement coverage**: `BuildMark-BuildNotes-ReportModel`. -### RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation +### BuildNotes_ReportModel_IncludesFullChangelog -**Scenario**: Mock connector returns a fully populated `BuildInformation` instance. +**Scenario**: `ToMarkdown()` is called on a `BuildInformation` for `v2.0.0` that has a changelog +link. -**Expected**: All fields including changes, known issues, and web links are present. +**Expected**: Markdown contains `## Full Changelog` and the comparison link text +`ver-1.1.0...v2.0.0`. -**Requirement coverage**: `BuildMark-BuildNotes-BuildInformation`, -`BuildMark-BuildNotes-ItemInfo`, `BuildMark-BuildNotes-WebLink` +**Requirement coverage**: `BuildMark-BuildNotes-ReportModel`. ## Requirements Coverage -- **BuildMark-BuildNotes-BuildInformation**: Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues, - RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation -- **BuildMark-BuildNotes-ItemInfo**: Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues, - RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation -- **BuildMark-BuildNotes-WebLink**: RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation +- **`BuildMark-BuildNotes-ReportModel`**: + - BuildNotes_ReportModel_GeneratesCorrectMarkdown + - BuildNotes_ReportModel_IncludesKnownIssues + - BuildNotes_ReportModel_IncludesFullChangelog diff --git a/docs/verification/build-mark/build-notes/item-info.md b/docs/verification/build-mark/build-notes/item-info.md index d45ea3d..f19ad36 100644 --- a/docs/verification/build-mark/build-notes/item-info.md +++ b/docs/verification/build-mark/build-notes/item-info.md @@ -2,47 +2,41 @@ ## Verification Approach -`ItemInfo` is a data model with no dedicated test class. It is verified indirectly -through connector tests in `RepoConnectorsTests.cs`, `GitHubRepoConnectorTests.cs`, -`AzureDevOpsRepoConnectorTests.cs`, and `MockRepoConnectorTests.cs` that assert on -the items contained in `BuildInformation` instances returned by connectors. +`ItemInfo` is a record type that carries no logic of its own. It is verified through +`BuildInformationTests.cs`, which asserts on the ordering, identity, and link properties of +`ItemInfo` entries returned by `MockRepoConnector`. No mocking beyond `MockRepoConnector` is +needed. ## Dependencies -| Mock / Stub | Reason | -| ------------------- | -------------------------------------------------------- | -| `MockRepoConnector` | Returns `BuildInformation` with known `ItemInfo` entries | +| Mock / Stub | Reason | +| ------------------- | -------------------------------------------------------------------- | +| `MockRepoConnector` | Provides `BuildInformation` instances with known `ItemInfo` entries. | -## Test Scenarios (Integration) +## Test Scenarios -### RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation +### BuildInformation_GetBuildInformationAsync_OrdersChangesByIndex -**Scenario**: Mock connector returns complete build information with change items. +**Scenario**: `MockRepoConnector.GetBuildInformationAsync(VersionTag.Create("ver-1.1.0"))` is +called; the `Changes` collection is inspected. -**Expected**: `BuildInformation.Changes` contains `ItemInfo` entries with correct -title, type, visibility, and web link. +**Expected**: `ItemInfo` entries in `Changes` are ordered by `Index` in ascending order; the first +entry has `Index` 10 and `Id` `"1"`; the second has `Index` 13. -**Requirement coverage**: `BuildMark-BuildNotes-ItemInfo` +**Requirement coverage**: `BuildMark-ItemInfo-Record`. -### RepoConnectors_GitHubConnector_GetBuildInformation_WithPullRequests_GathersChanges +### BuildInformation_ToMarkdown_UsesBulletLists -**Scenario**: GitHub connector processes pull requests into change items. +**Scenario**: `ToMarkdown(includeKnownIssues: true)` is called on `BuildInformation` for `v2.0.0`; +the rendered bullet list items are inspected. -**Expected**: Each pull request is represented as an `ItemInfo` in `BuildInformation.Changes`. +**Expected**: Each `ItemInfo` entry is rendered as a `- [id](url)` bullet; no table-row format is +present. -**Requirement coverage**: `BuildMark-BuildNotes-ItemInfo` - -### RepoConnectors_AzureDevOps_GetBuildInformation_WithPullRequests_GathersChanges - -**Scenario**: Azure DevOps connector processes pull requests into change items. - -**Expected**: Each pull request is represented as an `ItemInfo` in `BuildInformation.Changes`. - -**Requirement coverage**: `BuildMark-BuildNotes-ItemInfo` +**Requirement coverage**: `BuildMark-ItemInfo-Record`. ## Requirements Coverage -- **BuildMark-BuildNotes-ItemInfo**: - RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation, - RepoConnectors_GitHubConnector_GetBuildInformation_WithPullRequests_GathersChanges, - RepoConnectors_AzureDevOps_GetBuildInformation_WithPullRequests_GathersChanges +- **`BuildMark-ItemInfo-Record`**: + - BuildInformation_GetBuildInformationAsync_OrdersChangesByIndex + - BuildInformation_ToMarkdown_UsesBulletLists diff --git a/docs/verification/build-mark/build-notes/web-link.md b/docs/verification/build-mark/build-notes/web-link.md index 0856603..399cb2d 100644 --- a/docs/verification/build-mark/build-notes/web-link.md +++ b/docs/verification/build-mark/build-notes/web-link.md @@ -2,46 +2,28 @@ ## Verification Approach -`WebLink` is a utility data model with no dedicated test class. It is verified -indirectly through connector tests that assert on web link properties within -`ItemInfo` and `BuildInformation` instances. The `WebLink` type is used to attach -URLs to items, releases, and changelog links. +`WebLink` is a record type with no logic. It is verified through a dedicated test in +`BuildInformationTests.cs` that constructs a `WebLink` directly and asserts on its properties. +No mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ------------------- | ---------------------------------------------------------- | -| `MockRepoConnector` | Returns `BuildInformation` with `WebLink` entries on items | +| Mock / Stub | Reason | +| ----------- | --------------- | +| None | No mocks needed | -## Test Scenarios (Integration) +## Test Scenarios -### RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation +### WebLink_Constructor_StoresTextAndUrl -**Scenario**: Mock connector returns complete build information including items with web links. +**Scenario**: A `WebLink` is constructed with display text `"v1.0.0...v2.0.0"` and a GitHub +compare URL. -**Expected**: `ItemInfo` entries contain `WebLink` instances with non-null URL and label. +**Expected**: `LinkText` equals `"v1.0.0...v2.0.0"`; `TargetUrl` equals the supplied URL. -**Requirement coverage**: `BuildMark-BuildNotes-WebLink` - -### RepoConnectors_GitHubConnector_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation - -**Scenario**: GitHub connector populates change items with links to GitHub pull request URLs. - -**Expected**: `ItemInfo.Link` is a `WebLink` with a valid GitHub URL. - -**Requirement coverage**: `BuildMark-BuildNotes-WebLink` - -### RepoConnectors_AzureDevOps_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation - -**Scenario**: Azure DevOps connector populates change items with links to Azure DevOps URLs. - -**Expected**: `ItemInfo.Link` is a `WebLink` with a valid Azure DevOps URL. - -**Requirement coverage**: `BuildMark-BuildNotes-WebLink` +**Requirement coverage**: `BuildMark-WebLink-Record`. ## Requirements Coverage -- **BuildMark-BuildNotes-WebLink**: - RepoConnectors_MockConnector_GetBuildInformation_ReturnsCompleteInformation, - RepoConnectors_GitHubConnector_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation, - RepoConnectors_AzureDevOps_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation +- **`BuildMark-WebLink-Record`**: + - WebLink_Constructor_StoresTextAndUrl diff --git a/docs/verification/build-mark/cli/cli.md b/docs/verification/build-mark/cli/cli.md index d69f9de..d84a3a9 100644 --- a/docs/verification/build-mark/cli/cli.md +++ b/docs/verification/build-mark/cli/cli.md @@ -67,9 +67,10 @@ and output behavior. ### Cli_ReportFlags_SetProperties -**Scenario**: Context is created with `--report path.md` and `--report-depth 2` arguments. +**Scenario**: Context is created with `["--report", "output.md", "--depth", "3", +"--include-known-issues"]`. -**Expected**: `ReportFile` and `Depth` properties are set to the provided values. +**Expected**: `ReportFile` equals `"output.md"`; `Depth` equals 3; `IncludeKnownIssues` is true. **Requirement coverage**: `BuildMark-Program-Report`, `BuildMark-Program-Depth` @@ -91,17 +92,17 @@ and output behavior. ### Cli_ResultsFlag_SetsProperty -**Scenario**: Context is created with `--results path.trx` argument. +**Scenario**: Context is created with `["--results", "results.trx"]`. -**Expected**: `Results` property equals the provided path. +**Expected**: `ResultsFile` property equals `"results.trx"`. **Requirement coverage**: `BuildMark-Program-Results` ### Cli_ResultFlag_SetsProperty -**Scenario**: Context is created with `--result path.trx` argument (alias). +**Scenario**: Context is created with `["--result", "results.trx"]` (alias). -**Expected**: `Results` property equals the provided path. +**Expected**: `ResultsFile` property equals `"results.trx"`. **Requirement coverage**: `BuildMark-Program-Results` @@ -109,23 +110,25 @@ and output behavior. **Scenario**: `context.WriteError` is called with an error message. -**Expected**: Message is written to the error stream; exit code is set. +**Expected**: The message `"Subsystem error test"` appears in the standard error stream. **Requirement coverage**: `BuildMark-Program-ErrorHandling` ### Cli_InvalidArgument_ThrowsException -**Scenario**: Context is created with an unrecognized flag. +**Scenario**: Context is created with `["--unsupported"]`. -**Expected**: `ArgumentException` is thrown. +**Expected**: `ArgumentException` is thrown with a message containing +`"Unsupported argument '--unsupported'"`. **Requirement coverage**: `BuildMark-Cli-Context` ### Cli_MissingArgumentValue_ThrowsException -**Scenario**: Context is created with a flag that requires a value but no value is provided. +**Scenario**: Context is created with `["--build-version"]` (value missing). -**Expected**: `ArgumentException` is thrown. +**Expected**: `ArgumentException` is thrown with a message containing +`"--build-version requires a version argument"`. **Requirement coverage**: `BuildMark-Cli-Context` diff --git a/docs/verification/build-mark/cli/context.md b/docs/verification/build-mark/cli/context.md index 8c0305b..edeed44 100644 --- a/docs/verification/build-mark/cli/context.md +++ b/docs/verification/build-mark/cli/context.md @@ -21,7 +21,7 @@ no mocking is needed at this level. **Scenario**: `Context.Create` is called with an empty argument array. -**Expected**: All boolean flags are false; `ResultsFile` is null; `HeadingDepth` is 1; +**Expected**: All boolean flags are false; `ResultsFile` is null; `Depth` is null; exit code is 0. **Requirement coverage**: `BuildMark-Context-DefaultConstruction`. @@ -110,15 +110,16 @@ exit code is 0. **Scenario**: `Context.Create` is called with `["--depth", "3"]`. -**Expected**: `HeadingDepth` property equals 3. +**Expected**: `Depth` property equals 3. **Requirement coverage**: `BuildMark-Context-ArgumentParsing`. ### Context_Create_LegacyReportDepthArgument_SetsDepthProperty -**Scenario**: `Context.Create` is called with the legacy depth argument form. +**Scenario**: `Context.Create` is called with `["--report-depth", "3"]` (legacy alias for +`--depth`). -**Expected**: `HeadingDepth` property is set to the specified value. +**Expected**: `Depth` property equals 3. **Requirement coverage**: `BuildMark-Context-ArgumentParsing`. @@ -157,15 +158,19 @@ with a test message. ### Context_Create_MultipleArguments_SetsAllPropertiesCorrectly -**Scenario**: `Context.Create` is called with multiple flags together. +**Scenario**: `Context.Create` is called with `["--silent", "--validate", "--lint", +"--build-version", "1.2.3", "--report", "report.md", "--depth", "2", +"--include-known-issues", "--results", "results.trx"]`. -**Expected**: All corresponding properties are set correctly; no exception is thrown. +**Expected**: `Silent`, `Validate`, `Lint`, and `IncludeKnownIssues` are true; `BuildVersion` +equals `"1.2.3"`; `ReportFile` equals `"report.md"`; `Depth` equals 2; `ResultsFile` equals +`"results.trx"`; no exception is thrown. **Requirement coverage**: `BuildMark-Context-FlagParsing`, `BuildMark-Context-ArgumentParsing`. ### Context_Create_UnsupportedArgument_ThrowsArgumentException -**Scenario**: `Context.Create` is called with an unrecognized argument (e.g., `["--unknown"]`). +**Scenario**: `Context.Create` is called with `["--unsupported"]`. **Expected**: An `ArgumentException` is thrown containing the text "Unsupported argument". diff --git a/docs/verification/build-mark/configuration/azure-dev-ops-connector-config.md b/docs/verification/build-mark/configuration/azure-dev-ops-connector-config.md index 00ddb8f..941c889 100644 --- a/docs/verification/build-mark/configuration/azure-dev-ops-connector-config.md +++ b/docs/verification/build-mark/configuration/azure-dev-ops-connector-config.md @@ -2,30 +2,42 @@ ## Verification Approach -`AzureDevOpsConnectorConfig` is a data model class verified indirectly through -`RepoConnectorFactoryTests.cs`. Tests that supply Azure DevOps-specific configuration -within a `ConnectorConfig` confirm that the settings are forwarded to the created -`AzureDevOpsRepoConnector`. +`AzureDevOpsConnectorConfig` is verified through `ConfigurationTests.cs`. Tests write +`.buildmark.yaml` files with Azure DevOps connector blocks and assert that `OrganizationUrl`, +`Organization`, `Project`, and `Repository` properties are correctly parsed, including alias key +support. No mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------- | -| None | Data class | +| Mock / Stub | Reason | +| ----------- | -------------------------------------------------------------------- | +| File system | Tests create temporary `.buildmark.yaml` files in `Path.GetTempPath` | -## Test Scenarios (Integration) +## Test Scenarios -### RepoConnectorFactory_Create_WithAzureDevOpsConnectorConfig_ForwardsAzureDevOpsConfiguration +### BuildMarkConfigReader_ReadAsync_ValidAzureDevOpsConnector_ReturnsParsedConfiguration -**Scenario**: A `ConnectorConfig` with an `AzureDevOpsConnectorConfig` is passed to -the factory. +**Scenario**: A `.buildmark.yaml` with an Azure DevOps connector block using canonical keys +(`organization`, `repository`) is written; `BuildMarkConfigReader.ReadAsync` is called; +`Config.Connector.AzureDevOps` is inspected. -**Expected**: The resulting connector incorporates the Azure DevOps-specific -configuration. +**Expected**: `OrganizationUrl` equals `"https://dev.azure.com/myorg"`; `Organization` equals +`"myorg"`; `Project` equals `"myproject"`; `Repository` equals `"myrepo"`. -**Requirement coverage**: `BuildMark-Configuration-AzureDevOpsConnectorConfig` +**Requirement coverage**: `BuildMark-AzureDevOpsConnectorConfig-Properties`. + +### BuildMarkConfigReader_ReadAsync_AzureDevOpsConnectorAliases_ReturnsParsedConfiguration + +**Scenario**: A `.buildmark.yaml` with Azure DevOps connector using alias keys (`org`, `repo`) is +written; `BuildMarkConfigReader.ReadAsync` is called; `Config.Connector.AzureDevOps` is inspected. + +**Expected**: `OrganizationUrl`, `Organization`, `Project`, and `Repository` are all populated +correctly from the alias keys. + +**Requirement coverage**: `BuildMark-AzureDevOpsConnectorConfig-Properties`. ## Requirements Coverage -- **BuildMark-Configuration-AzureDevOpsConnectorConfig**: - RepoConnectorFactory_Create_WithAzureDevOpsConnectorConfig_ForwardsAzureDevOpsConfiguration +- **`BuildMark-AzureDevOpsConnectorConfig-Properties`**: + - BuildMarkConfigReader_ReadAsync_ValidAzureDevOpsConnector_ReturnsParsedConfiguration + - BuildMarkConfigReader_ReadAsync_AzureDevOpsConnectorAliases_ReturnsParsedConfiguration diff --git a/docs/verification/build-mark/configuration/build-mark-config-reader.md b/docs/verification/build-mark/configuration/build-mark-config-reader.md index 8ba1b2d..3af72ef 100644 --- a/docs/verification/build-mark/configuration/build-mark-config-reader.md +++ b/docs/verification/build-mark/configuration/build-mark-config-reader.md @@ -2,41 +2,105 @@ ## Verification Approach -`BuildMarkConfigReader` is verified indirectly through `ProgramTests.cs`. The lint -and report tests exercise `BuildMarkConfigReader.ReadAsync` as part of the program -execution pipeline. When `Lint = true`, `ReadAsync` is called and the result is -reported to the context. When a report is generated, `ReadAsync` is called to load -optional configuration before connector creation. +`BuildMarkConfigReader` is verified with dedicated unit tests in `ConfigurationTests.cs`. Tests +write `.buildmark.yaml` files to temporary directories and call `BuildMarkConfigReader.ReadAsync`, +asserting on the returned `ConfigurationLoadResult`. No mocking is required; the real file system +is used. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ------------------------------------------------------- | -| File system | `ReadAsync` attempts to read `.buildmark.yaml` from the | -| | current directory; tests control file presence | +| Mock / Stub | Reason | +| ----------- | -------------------------------------------------------------------- | +| File system | Tests create temporary `.buildmark.yaml` files in `Path.GetTempPath` | -## Test Scenarios (Integration) +## Test Scenarios -### Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero +### BuildMarkConfigReader_ReadAsync_MissingFile_ReturnsEmptyResult -**Scenario**: `Program.Run` calls `BuildMarkConfigReader.ReadAsync` when no -`.buildmark.yaml` is present. +**Scenario**: `BuildMarkConfigReader.ReadAsync` is called on a temp directory containing no +`.buildmark.yaml`. -**Expected**: Reader returns a result with no errors; exit code is 0. +**Expected**: `Config` is null; `HasErrors` is false; `Issues` is empty. -**Requirement coverage**: `BuildMark-Configuration-BuildMarkConfigReader` +**Requirement coverage**: `BuildMark-ConfigReader-MissingFile`. -### Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues +### BuildMarkConfigReader_ReadAsync_ValidFile_ReturnsParsedConfiguration -**Scenario**: `Program.Run` calls `BuildMarkConfigReader.ReadAsync` before report -generation; configuration may or may not be present. +**Scenario**: A `.buildmark.yaml` with a GitHub connector, sections, and rules is written; +`BuildMarkConfigReader.ReadAsync` is called. -**Expected**: Reader returns without error; report generation proceeds. +**Expected**: `Config` is non-null; `HasErrors` is false; connector type, owner, repo, base-url, +sections, and rules are parsed correctly. -**Requirement coverage**: `BuildMark-Configuration-BuildMarkConfigReader` +**Requirement coverage**: `BuildMark-ConfigReader-ReadAsync`. + +### BuildMarkConfigReader_ReadAsync_InvalidRepositoryValue_ReturnsErrorIssue + +**Scenario**: A `.buildmark.yaml` with `repository: invalid` (not in `owner/repo` format) is +written; `BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: `Config` is null; `HasErrors` is true; issue description contains `"owner/repo"`. + +**Requirement coverage**: `BuildMark-ConfigReader-MalformedFile`. + +### BuildMarkConfigReader_ReadAsync_MalformedFile_ReturnsErrorIssue + +**Scenario**: A `.buildmark.yaml` containing a tab character (invalid YAML) is written; +`BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: `Config` is null; `HasErrors` is true; issue description contains `"tab"` +(case-insensitive). + +**Requirement coverage**: `BuildMark-ConfigReader-MalformedFile`. + +### BuildMarkConfigReader_ReadAsync_ValidAzureDevOpsConnector_ReturnsParsedConfiguration + +**Scenario**: A `.buildmark.yaml` with an Azure DevOps connector block (url, organization, +project, repository) is written; `BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: `Config.Connector.Type` is `"azure-devops"`; all Azure DevOps fields are parsed +correctly. + +**Requirement coverage**: `BuildMark-ConfigReader-ReadAsync`. + +### BuildMarkConfigReader_ReadAsync_AzureDevOpsConnectorAliases_ReturnsParsedConfiguration + +**Scenario**: A `.buildmark.yaml` with Azure DevOps connector using alias keys (`org`, `repo`) is +written; `BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: All Azure DevOps fields are parsed correctly using the alias keys. + +**Requirement coverage**: `BuildMark-ConfigReader-ReadAsync`. + +### BuildMarkConfigReader_ReadAsync_AzureDevOpsUnsupportedKey_ReturnsErrorIssue + +**Scenario**: A `.buildmark.yaml` with an unknown key inside the Azure DevOps connector block is +written; `BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: `Config` is null; `HasErrors` is true; issue description contains +`"Unsupported Azure DevOps connector key"`. + +**Requirement coverage**: `BuildMark-ConfigReader-MalformedFile`. + +### BuildMarkConfigReader_ReadAsync_AzureDevOpsNonMapping_ReturnsErrorIssue + +**Scenario**: A `.buildmark.yaml` where `azure-devops:` is a scalar value instead of a mapping is +written; `BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: `Config` is null; `HasErrors` is true; issue description contains `"YAML mapping"`. + +**Requirement coverage**: `BuildMark-ConfigReader-MalformedFile`. ## Requirements Coverage -- **BuildMark-Configuration-BuildMarkConfigReader**: - Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero, - Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues +- **`BuildMark-ConfigReader-ReadAsync`**: + - BuildMarkConfigReader_ReadAsync_ValidFile_ReturnsParsedConfiguration + - BuildMarkConfigReader_ReadAsync_ValidAzureDevOpsConnector_ReturnsParsedConfiguration + - BuildMarkConfigReader_ReadAsync_AzureDevOpsConnectorAliases_ReturnsParsedConfiguration +- **`BuildMark-ConfigReader-MissingFile`**: + - BuildMarkConfigReader_ReadAsync_MissingFile_ReturnsEmptyResult +- **`BuildMark-ConfigReader-MalformedFile`**: + - BuildMarkConfigReader_ReadAsync_InvalidRepositoryValue_ReturnsErrorIssue + - BuildMarkConfigReader_ReadAsync_MalformedFile_ReturnsErrorIssue + - BuildMarkConfigReader_ReadAsync_AzureDevOpsUnsupportedKey_ReturnsErrorIssue + - BuildMarkConfigReader_ReadAsync_AzureDevOpsNonMapping_ReturnsErrorIssue diff --git a/docs/verification/build-mark/configuration/build-mark-config.md b/docs/verification/build-mark/configuration/build-mark-config.md index 359f550..f0f30c7 100644 --- a/docs/verification/build-mark/configuration/build-mark-config.md +++ b/docs/verification/build-mark/configuration/build-mark-config.md @@ -2,40 +2,33 @@ ## Verification Approach -`BuildMarkConfig` is a data model class with no dedicated test class. It is verified -indirectly through program-level and connector factory tests that exercise the -configuration pipeline. When `BuildMarkConfigReader.ReadAsync` returns a -`ConfigurationLoadResult`, the embedded `BuildMarkConfig` instance drives connector -creation and report configuration. +`BuildMarkConfig` is verified with dedicated unit tests in `ConfigurationTests.cs`. The test +`BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection` calls +`BuildMarkConfig.CreateDefault()` and asserts on the returned sections and routing rules. No +mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------------------------------------------------------- | -| File system | Integration tests may create temporary configuration files | +| Mock / Stub | Reason | +| ----------- | --------------- | +| None | No mocks needed | -## Test Scenarios (Integration) +## Test Scenarios -### Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero +### BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection -**Scenario**: `Program.Run` processes a lint request; `BuildMarkConfig` is loaded -as an empty/default instance when no file is present. +**Scenario**: `BuildMarkConfig.CreateDefault()` is called. -**Expected**: Default `BuildMarkConfig` is handled gracefully; exit code is 0. +**Expected**: The result contains 3 sections (`changes`, `bugs-fixed`, `dependency-updates`) with +correct titles; 6 rules are present with correct routes and match conditions; the final catch-all +rule has a null match. -**Requirement coverage**: `BuildMark-Configuration-BuildMarkConfig` - -### RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration - -**Scenario**: `BuildMarkConfig.Connector` field is used by the factory to create -a connector with GitHub settings. - -**Expected**: Connector reflects the `BuildMarkConfig` connector settings. - -**Requirement coverage**: `BuildMark-Configuration-BuildMarkConfig` +**Requirement coverage**: `BuildMark-SectionConfig-Properties`, +`BuildMark-RuleConfig-Properties`. ## Requirements Coverage -- **BuildMark-Configuration-BuildMarkConfig**: - Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero, - RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration +- **`BuildMark-SectionConfig-Properties`**: + - BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection +- **`BuildMark-RuleConfig-Properties`**: + - BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection diff --git a/docs/verification/build-mark/configuration/configuration-issue.md b/docs/verification/build-mark/configuration/configuration-issue.md index 045b399..30b234f 100644 --- a/docs/verification/build-mark/configuration/configuration-issue.md +++ b/docs/verification/build-mark/configuration/configuration-issue.md @@ -2,30 +2,50 @@ ## Verification Approach -`ConfigurationIssue` is a data class with no dedicated test class. It is verified -indirectly through `ProgramTests.cs` via the `ConfigurationLoadResult` pipeline. -When configuration parsing produces issues, they are stored as `ConfigurationIssue` -instances and reported to the context. No dedicated test exercises this path in -isolation; it is covered by integration paths where configuration errors surface. +`ConfigurationIssue` is a record type with no logic. It is verified through +`ConfigurationTests.cs`, which constructs `ConfigurationIssue` instances directly and asserts on +their `FilePath`, `Line`, `Severity`, and `Description` properties. No mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------- | -| None | Data class | +| Mock / Stub | Reason | +| ----------- | --------------- | +| None | No mocks needed | -## Test Scenarios (Integration) +## Test Scenarios -### Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero +### ConfigurationLoadResult_ReportTo_ErrorIssue_SetsExitCode -**Scenario**: `ConfigurationLoadResult` contains no `ConfigurationIssue` entries -when no config file is present; `ReportTo` is a no-op. +**Scenario**: A `ConfigurationIssue` with `Error` severity is constructed and placed in a +`ConfigurationLoadResult`; `ReportTo` is called. -**Expected**: No issues reported; exit code is 0. +**Expected**: `context.ExitCode` equals 1, confirming the issue record carries severity correctly. -**Requirement coverage**: `BuildMark-Configuration-ConfigurationIssue` +**Requirement coverage**: `BuildMark-ConfigurationIssue-Record`. + +### ConfigurationLoadResult_ReportTo_WarningIssue_DoesNotSetExitCode + +**Scenario**: A `ConfigurationIssue` with `Warning` severity is constructed and placed in a +`ConfigurationLoadResult`; `ReportTo` is called. + +**Expected**: `context.ExitCode` remains 0, confirming severity is preserved and evaluated +correctly. + +**Requirement coverage**: `BuildMark-ConfigurationIssue-Record`. + +### ConfigurationLoadResult_ReportTo_IssueMessage_IncludesLineNumber + +**Scenario**: A `ConfigurationIssue` is constructed with `FilePath` `"/repo/.buildmark.yaml"`, +`Line` 7, `Error` severity, and description `"Unexpected value"`; properties are inspected. + +**Expected**: `FilePath` equals `"/repo/.buildmark.yaml"`; `Line` equals 7; `Severity` equals +`Error`; `Description` equals `"Unexpected value"`. + +**Requirement coverage**: `BuildMark-ConfigurationIssue-Record`. ## Requirements Coverage -- **BuildMark-Configuration-ConfigurationIssue**: - Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero +- **`BuildMark-ConfigurationIssue-Record`**: + - ConfigurationLoadResult_ReportTo_ErrorIssue_SetsExitCode + - ConfigurationLoadResult_ReportTo_WarningIssue_DoesNotSetExitCode + - ConfigurationLoadResult_ReportTo_IssueMessage_IncludesLineNumber diff --git a/docs/verification/build-mark/configuration/configuration-load-result.md b/docs/verification/build-mark/configuration/configuration-load-result.md index 057ae83..688b1a9 100644 --- a/docs/verification/build-mark/configuration/configuration-load-result.md +++ b/docs/verification/build-mark/configuration/configuration-load-result.md @@ -2,30 +2,51 @@ ## Verification Approach -`ConfigurationLoadResult` is a data class with no dedicated test class. It is verified -indirectly through `ProgramTests.cs`, where the result of `BuildMarkConfigReader.ReadAsync` -is used to report issues and extract the active configuration. Tests that exercise -the lint path confirm that `ConfigurationLoadResult` is handled correctly when no -issues are present. +`ConfigurationLoadResult` is verified with dedicated unit tests in `ConfigurationTests.cs`. Tests +construct `ConfigurationLoadResult` instances directly with controlled `ConfigurationIssue` entries +and assert on the behavior of `ReportTo(context)`. No mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------- | -| None | Data class | +| Mock / Stub | Reason | +| ----------- | --------------- | +| None | No mocks needed | -## Test Scenarios (Integration) +## Test Scenarios -### Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero +### ConfigurationLoadResult_ReportTo_ErrorIssue_SetsExitCode -**Scenario**: `ConfigurationLoadResult` is returned from `BuildMarkConfigReader` with -no issues when no configuration file is present. +**Scenario**: A `ConfigurationLoadResult` containing one `Error`-severity issue is created; +`ReportTo` is called on a silent `Context`. -**Expected**: `result.ReportTo(context)` completes without errors; exit code is 0. +**Expected**: `context.ExitCode` equals 1. -**Requirement coverage**: `BuildMark-Configuration-ConfigurationLoadResult` +**Requirement coverage**: `BuildMark-ConfigLoadResult-ReportTo`. + +### ConfigurationLoadResult_ReportTo_WarningIssue_DoesNotSetExitCode + +**Scenario**: A `ConfigurationLoadResult` containing one `Warning`-severity issue is created; +`ReportTo` is called on a silent `Context`. + +**Expected**: `context.ExitCode` remains 0. + +**Requirement coverage**: `BuildMark-ConfigLoadResult-ReportTo`. + +### ConfigurationLoadResult_ReportTo_IssueMessage_IncludesLineNumber + +**Scenario**: A `ConfigurationLoadResult` containing an `Error`-severity issue at `FilePath` +`"/repo/.buildmark.yaml"`, `Line` 7, with description `"Unexpected value"` is created; `ReportTo` +is called. + +**Expected**: `result.HasErrors` is true; `context.ExitCode` is 1; +`result.Issues[0].FilePath` equals `"/repo/.buildmark.yaml"`; `result.Issues[0].Line` equals 7; +`result.Issues[0].Description` equals `"Unexpected value"`. + +**Requirement coverage**: `BuildMark-ConfigLoadResult-ReportTo`. ## Requirements Coverage -- **BuildMark-Configuration-ConfigurationLoadResult**: - Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero +- **`BuildMark-ConfigLoadResult-ReportTo`**: + - ConfigurationLoadResult_ReportTo_ErrorIssue_SetsExitCode + - ConfigurationLoadResult_ReportTo_WarningIssue_DoesNotSetExitCode + - ConfigurationLoadResult_ReportTo_IssueMessage_IncludesLineNumber diff --git a/docs/verification/build-mark/configuration/configuration.md b/docs/verification/build-mark/configuration/configuration.md index e6a2197..d8673c2 100644 --- a/docs/verification/build-mark/configuration/configuration.md +++ b/docs/verification/build-mark/configuration/configuration.md @@ -2,57 +2,106 @@ ## Verification Approach -The Configuration subsystem is verified at the integration level through -`ProgramTests.cs` and `RepoConnectorFactoryTests.cs`. There is no dedicated -`ConfigurationTests.cs` file. The lint-related tests in `ProgramTests.cs` exercise -`BuildMarkConfigReader` indirectly when `Program.Run` with `Lint = true` calls -`BuildMarkConfigReader.ReadAsync`. The factory tests in `RepoConnectorFactoryTests.cs` -exercise connector configuration forwarding. +The Configuration subsystem is verified with dedicated subsystem tests in +`ConfigurationSubsystemTests.cs`. Tests create temporary `.buildmark.yaml` files, call +`BuildMarkConfigReader.ReadAsync`, and assert on the returned `ConfigurationLoadResult`. No mocking +is required; the real file system is used with temporary directories. ## Dependencies -| Mock / Stub | Reason | -| ----------- | --------------------------------------------------------- | -| File system | Some tests create temporary `.buildmark.yaml` files | -| `Context` | Provides output capture for configuration issue reporting | +| Mock / Stub | Reason | +| ----------- | -------------------------------------------------------------------- | +| File system | Tests create temporary `.buildmark.yaml` files in `Path.GetTempPath` | -## Test Scenarios (Integration) +## Test Scenarios -### Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero +### Configuration_ReadAsync_ValidFile_ReturnsConfiguration -**Scenario**: `Program.Run` is called with `Lint = true` and no `.buildmark.yaml` present. +**Scenario**: A valid `.buildmark.yaml` containing a GitHub connector, one section, and one rule +is written to a temp directory; `BuildMarkConfigReader.ReadAsync` is called. -**Expected**: `BuildMarkConfigReader` returns a default/empty result; exit code is 0. +**Expected**: Result has no errors; `Config.Connector.Type` is `"github"`; +`Config.Connector.GitHub.Owner` is `"test-owner"`; one section and one rule are parsed. -**Requirement coverage**: `BuildMark-Configuration-BuildMarkConfigReader` +**Requirement coverage**: `BuildMark-Configuration-Read`. -### RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration +### Configuration_ReadAsync_MissingFile_ReturnsEmptyResult -**Scenario**: `RepoConnectorFactory.Create` is called with a `ConnectorConfig` that -specifies GitHub settings. +**Scenario**: `BuildMarkConfigReader.ReadAsync` is called on a temp directory containing no +`.buildmark.yaml`. -**Expected**: The returned connector has the GitHub configuration applied. +**Expected**: `Config` is null; `HasErrors` is false; `Issues` is empty. -**Requirement coverage**: `BuildMark-Configuration-ConnectorConfig`, -`BuildMark-Configuration-GitHubConnectorConfig` +**Requirement coverage**: `BuildMark-Configuration-Read`. -### RepoConnectorFactory_Create_WithAzureDevOpsConnectorConfig_ForwardsAzureDevOpsConfiguration +### Configuration_ReadAsync_MalformedFile_ReportsError -**Scenario**: `RepoConnectorFactory.Create` is called with Azure DevOps connector config. +**Scenario**: A `.buildmark.yaml` with a tab character (malformed YAML) is written; +`BuildMarkConfigReader.ReadAsync` is called. -**Expected**: The returned connector has the Azure DevOps configuration applied. +**Expected**: `Config` is null; `HasErrors` is true; `Issues` contains at least one entry with +`Error` severity. -**Requirement coverage**: `BuildMark-Configuration-ConnectorConfig`, -`BuildMark-Configuration-AzureDevOpsConnectorConfig` +**Requirement coverage**: `BuildMark-Configuration-Read`. + +### Configuration_Issues_ErrorIssue_SetsExitCode + +**Scenario**: A `ConfigurationLoadResult` containing one `Error`-severity `ConfigurationIssue` is +created; `ReportTo(context)` is called on a silent context. + +**Expected**: `context.ExitCode` equals 1. + +**Requirement coverage**: `BuildMark-Configuration-Issues`. + +### Configuration_Issues_WarningIssue_DoesNotSetExitCode + +**Scenario**: A `ConfigurationLoadResult` containing one `Warning`-severity `ConfigurationIssue` +is created; `ReportTo(context)` is called on a silent context. + +**Expected**: `context.ExitCode` remains 0. + +**Requirement coverage**: `BuildMark-Configuration-Issues`. + +### Configuration_Issues_ValidationError_ReportsAccurateLine + +**Scenario**: A `.buildmark.yaml` with an unsupported key on line 3 is written; +`BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: The resulting issue reports `Line` equal to 3. + +**Requirement coverage**: `BuildMark-Configuration-Issues`. + +### Configuration_ConnectorConfig_ValidFile_ParsesConnectorSettings + +**Scenario**: A `.buildmark.yaml` with a GitHub connector block (owner, repo, base-url) is +written; `BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: `Config.Connector.Type` is `"github"`; `GitHub.Owner`, `GitHub.Repo`, and +`GitHub.BaseUrl` match the file values. + +**Requirement coverage**: `BuildMark-Configuration-ConnectorConfig`. + +### Configuration_ConnectorConfig_ValidFile_ParsesAzureDevOpsSettings + +**Scenario**: A `.buildmark.yaml` with an Azure DevOps connector block (url, organization, +project, repository) is written; `BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: `Config.Connector.Type` is `"azure-devops"`; `AzureDevOps.OrganizationUrl`, +`AzureDevOps.Organization`, `AzureDevOps.Project`, and `AzureDevOps.Repository` match the file +values. + +**Requirement coverage**: `BuildMark-Configuration-ConnectorConfig`. ## Requirements Coverage -- **BuildMark-Configuration-BuildMarkConfigReader**: - Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero -- **BuildMark-Configuration-ConnectorConfig**: - RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration, - RepoConnectorFactory_Create_WithAzureDevOpsConnectorConfig_ForwardsAzureDevOpsConfiguration -- **BuildMark-Configuration-GitHubConnectorConfig**: - RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration -- **BuildMark-Configuration-AzureDevOpsConnectorConfig**: - RepoConnectorFactory_Create_WithAzureDevOpsConnectorConfig_ForwardsAzureDevOpsConfiguration +- **`BuildMark-Configuration-Read`**: + - Configuration_ReadAsync_ValidFile_ReturnsConfiguration + - Configuration_ReadAsync_MissingFile_ReturnsEmptyResult + - Configuration_ReadAsync_MalformedFile_ReportsError +- **`BuildMark-Configuration-Issues`**: + - Configuration_Issues_ErrorIssue_SetsExitCode + - Configuration_Issues_WarningIssue_DoesNotSetExitCode + - Configuration_Issues_ValidationError_ReportsAccurateLine +- **`BuildMark-Configuration-ConnectorConfig`**: + - Configuration_ConnectorConfig_ValidFile_ParsesConnectorSettings + - Configuration_ConnectorConfig_ValidFile_ParsesAzureDevOpsSettings diff --git a/docs/verification/build-mark/configuration/connector-config.md b/docs/verification/build-mark/configuration/connector-config.md index 24f6fc2..aa0953d 100644 --- a/docs/verification/build-mark/configuration/connector-config.md +++ b/docs/verification/build-mark/configuration/connector-config.md @@ -2,46 +2,41 @@ ## Verification Approach -`ConnectorConfig` is a data model class verified indirectly through -`RepoConnectorFactoryTests.cs`. Tests that pass a `ConnectorConfig` to -`RepoConnectorFactory.Create` exercise the configuration forwarding path and confirm -that the connector type and sub-configuration are interpreted correctly. +`ConnectorConfig` is verified through `ConfigurationTests.cs`. Tests write `.buildmark.yaml` files +with connector blocks and assert that `ConnectorConfig.Type`, `ConnectorConfig.GitHub`, and +`ConnectorConfig.AzureDevOps` are correctly populated by `BuildMarkConfigReader.ReadAsync`. No +mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------- | -| None | Data class | +| Mock / Stub | Reason | +| ----------- | -------------------------------------------------------------------- | +| File system | Tests create temporary `.buildmark.yaml` files in `Path.GetTempPath` | -## Test Scenarios (Integration) +## Test Scenarios -### RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration +### BuildMarkConfigReader_ReadAsync_ValidFile_ReturnsParsedConfiguration -**Scenario**: `ConnectorConfig` with GitHub settings is passed to the factory. +**Scenario**: A `.buildmark.yaml` with a GitHub connector block is written; +`BuildMarkConfigReader.ReadAsync` is called; `Config.Connector` is inspected. -**Expected**: Factory creates a GitHub connector with the supplied settings applied. +**Expected**: `Config.Connector.Type` equals `"github"`; `Config.Connector.GitHub` is non-null; +`Config.Connector.AzureDevOps` is null. -**Requirement coverage**: `BuildMark-Configuration-ConnectorConfig` +**Requirement coverage**: `BuildMark-ConnectorConfig-Properties`. -### RepoConnectorFactory_Create_WithAzureDevOpsType_CreatesAzureDevOpsConnector +### BuildMarkConfigReader_ReadAsync_ValidAzureDevOpsConnector_ReturnsParsedConfiguration -**Scenario**: `ConnectorConfig` with Azure DevOps type is passed to the factory. +**Scenario**: A `.buildmark.yaml` with an Azure DevOps connector block is written; +`BuildMarkConfigReader.ReadAsync` is called; `Config.Connector` is inspected. -**Expected**: Factory creates an Azure DevOps connector. +**Expected**: `Config.Connector.Type` equals `"azure-devops"`; +`Config.Connector.AzureDevOps` is non-null with all fields set; `Config.Connector.GitHub` is null. -**Requirement coverage**: `BuildMark-Configuration-ConnectorConfig` - -### RepoConnectorFactory_Create_WithAzureDevOpsConnectorConfig_ForwardsAzureDevOpsConfiguration - -**Scenario**: `ConnectorConfig` with Azure DevOps settings is passed to the factory. - -**Expected**: Factory creates Azure DevOps connector with the supplied settings applied. - -**Requirement coverage**: `BuildMark-Configuration-ConnectorConfig` +**Requirement coverage**: `BuildMark-ConnectorConfig-Properties`. ## Requirements Coverage -- **BuildMark-Configuration-ConnectorConfig**: - RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration, - RepoConnectorFactory_Create_WithAzureDevOpsType_CreatesAzureDevOpsConnector, - RepoConnectorFactory_Create_WithAzureDevOpsConnectorConfig_ForwardsAzureDevOpsConfiguration +- **`BuildMark-ConnectorConfig-Properties`**: + - BuildMarkConfigReader_ReadAsync_ValidFile_ReturnsParsedConfiguration + - BuildMarkConfigReader_ReadAsync_ValidAzureDevOpsConnector_ReturnsParsedConfiguration diff --git a/docs/verification/build-mark/configuration/git-hub-connector-config.md b/docs/verification/build-mark/configuration/git-hub-connector-config.md index 64541b7..36af597 100644 --- a/docs/verification/build-mark/configuration/git-hub-connector-config.md +++ b/docs/verification/build-mark/configuration/git-hub-connector-config.md @@ -2,28 +2,29 @@ ## Verification Approach -`GitHubConnectorConfig` is a data model class verified indirectly through -`RepoConnectorFactoryTests.cs`. Tests that supply GitHub-specific configuration -within a `ConnectorConfig` confirm that the settings are forwarded to the created -`GitHubRepoConnector`. +`GitHubConnectorConfig` is verified through `ConfigurationTests.cs`. Tests write `.buildmark.yaml` +files with a GitHub connector block and assert that `Owner`, `Repo`, and `BaseUrl` properties are +correctly parsed. No mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------- | -| None | Data class | +| Mock / Stub | Reason | +| ----------- | -------------------------------------------------------------------- | +| File system | Tests create temporary `.buildmark.yaml` files in `Path.GetTempPath` | -## Test Scenarios (Integration) +## Test Scenarios -### RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration +### BuildMarkConfigReader_ReadAsync_ValidFile_ReturnsParsedConfiguration -**Scenario**: A `ConnectorConfig` with a `GitHubConnectorConfig` is passed to the factory. +**Scenario**: A `.buildmark.yaml` with `github.owner`, `github.repo`, and `github.base-url` fields +is written; `BuildMarkConfigReader.ReadAsync` is called; `Config.Connector.GitHub` is inspected. -**Expected**: The resulting connector incorporates the GitHub-specific configuration. +**Expected**: `Owner` equals `"example-owner"`; `Repo` equals `"hello-world"`; `BaseUrl` equals +`"https://api.github.com"`. -**Requirement coverage**: `BuildMark-Configuration-GitHubConnectorConfig` +**Requirement coverage**: `BuildMark-GitHubConnectorConfig-Properties`. ## Requirements Coverage -- **BuildMark-Configuration-GitHubConnectorConfig**: - RepoConnectorFactory_Create_WithConnectorConfig_ForwardsGitHubConfiguration +- **`BuildMark-GitHubConnectorConfig-Properties`**: + - BuildMarkConfigReader_ReadAsync_ValidFile_ReturnsParsedConfiguration diff --git a/docs/verification/build-mark/configuration/report-config.md b/docs/verification/build-mark/configuration/report-config.md index e11a51e..0f84b13 100644 --- a/docs/verification/build-mark/configuration/report-config.md +++ b/docs/verification/build-mark/configuration/report-config.md @@ -2,30 +2,41 @@ ## Verification Approach -`ReportConfig` is a data model class verified indirectly through `ProgramTests.cs`. -Report configuration fields (`File`, `Depth`, `IncludeKnownIssues`) influence the -behavior of `Program.ProcessBuildNotes`. The test -`Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues` -exercises the `IncludeKnownIssues` field path. +`ReportConfig` is verified through `ConfigurationTests.cs`. Tests write `.buildmark.yaml` files +with a `report:` block and assert that `File`, `Depth`, and `IncludeKnownIssues` properties are +correctly parsed or that invalid values produce error issues. No mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------- | -| None | Data class | +| Mock / Stub | Reason | +| ----------- | -------------------------------------------------------------------- | +| File system | Tests create temporary `.buildmark.yaml` files in `Path.GetTempPath` | -## Test Scenarios (Integration) +## Test Scenarios -### Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues +### BuildMarkConfigReader_ReadAsync_ValidReportSection_ReturnsParsedReportConfig -**Scenario**: `ReportConfig.IncludeKnownIssues` is set; report generation includes -known issues. +**Scenario**: A `.buildmark.yaml` with `report.file`, `report.depth: 2`, and +`report.include-known-issues: true` is written; `BuildMarkConfigReader.ReadAsync` is called; +`Config.Report` is inspected. -**Expected**: Generated report contains a known issues section. +**Expected**: `Config.Report.File` equals `"build-notes.md"`; `Config.Report.Depth` equals 2; +`Config.Report.IncludeKnownIssues` is true. -**Requirement coverage**: `BuildMark-Configuration-ReportConfig` +**Requirement coverage**: `BuildMark-ReportConfig-Properties`. + +### BuildMarkConfigReader_ReadAsync_InvalidReportDepth_ReturnsErrorIssue + +**Scenario**: A `.buildmark.yaml` with `report.depth: -1` is written; +`BuildMarkConfigReader.ReadAsync` is called. + +**Expected**: `Config` is null; `HasErrors` is true; issue description contains +`"positive integer"`. + +**Requirement coverage**: `BuildMark-ReportConfig-Properties`. ## Requirements Coverage -- **BuildMark-Configuration-ReportConfig**: - Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues +- **`BuildMark-ReportConfig-Properties`**: + - BuildMarkConfigReader_ReadAsync_ValidReportSection_ReturnsParsedReportConfig + - BuildMarkConfigReader_ReadAsync_InvalidReportDepth_ReturnsErrorIssue diff --git a/docs/verification/build-mark/configuration/rule-config.md b/docs/verification/build-mark/configuration/rule-config.md index e6b3b69..89e97a1 100644 --- a/docs/verification/build-mark/configuration/rule-config.md +++ b/docs/verification/build-mark/configuration/rule-config.md @@ -2,47 +2,32 @@ ## Verification Approach -`RuleConfig` is a data model class verified indirectly through `ItemRouterTests.cs` -and connector configuration tests. Tests that provide `RuleConfig` instances to -connectors or to `ItemRouter` exercise the routing logic and confirm that items -are routed to the correct sections. +`RuleConfig` is verified through `ConfigurationTests.cs`. The test +`BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection` calls +`BuildMarkConfig.CreateDefault()` and asserts on the `Route` and `Match` properties of the +returned rules. No mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------- | -| None | Data class | +| Mock / Stub | Reason | +| ----------- | --------------- | +| None | No mocks needed | -## Test Scenarios (Integration) +## Test Scenarios -### ItemRouter_Route_MatchingRuleRoutesItemToConfiguredSection +### BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection -**Scenario**: A `RuleConfig` with a matching condition routes an item to the -configured section. +**Scenario**: `BuildMarkConfig.CreateDefault()` is called; the returned `Rules` collection is +inspected. -**Expected**: Item appears in the correct section of the output. +**Expected**: Six rules are present; the first three rules have `dependency-updates`, +`bugs-fixed`, and `bugs-fixed` as routes with appropriate label and work-item-type match +conditions; the fourth and fifth rules have `suppressed` routes; the sixth rule has `changes` as +route with a null match. -**Requirement coverage**: `BuildMark-Configuration-RuleConfig` - -### ItemRouter_Route_SuppressedRouteOmitsMatchingItem - -**Scenario**: A `RuleConfig` with suppress set routes a matching item to be omitted. - -**Expected**: Item does not appear in any section of the output. - -**Requirement coverage**: `BuildMark-Configuration-RuleConfig` - -### MockRepoConnector_Configure_StoresRulesAndSections - -**Scenario**: `MockRepoConnector.Configure` is called with `RuleConfig` entries. - -**Expected**: Rules are stored; `HasRules` returns `true`. - -**Requirement coverage**: `BuildMark-Configuration-RuleConfig` +**Requirement coverage**: `BuildMark-RuleConfig-Properties`. ## Requirements Coverage -- **BuildMark-Configuration-RuleConfig**: - ItemRouter_Route_MatchingRuleRoutesItemToConfiguredSection, - ItemRouter_Route_SuppressedRouteOmitsMatchingItem, - MockRepoConnector_Configure_StoresRulesAndSections +- **`BuildMark-RuleConfig-Properties`**: + - BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection diff --git a/docs/verification/build-mark/configuration/section-config.md b/docs/verification/build-mark/configuration/section-config.md index c1b49a2..36afae8 100644 --- a/docs/verification/build-mark/configuration/section-config.md +++ b/docs/verification/build-mark/configuration/section-config.md @@ -2,40 +2,31 @@ ## Verification Approach -`SectionConfig` is a data model class verified indirectly through -`RepoConnectorsTests.cs` and connector-specific tests. When `Configure` is called -on a connector with `SectionConfig` entries, the connector creates named sections in -the `BuildInformation.RoutedSections` output. Tests that assert on routed sections -exercise `SectionConfig` indirectly. +`SectionConfig` is verified through `ConfigurationTests.cs`. The test +`BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection` calls +`BuildMarkConfig.CreateDefault()` and asserts on the `Id` and `Title` properties of the returned +sections. No mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ---------- | -| None | Data class | +| Mock / Stub | Reason | +| ----------- | --------------- | +| None | No mocks needed | -## Test Scenarios (Integration) +## Test Scenarios -### RepoConnectors_GitHubConnector_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation +### BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection -**Scenario**: A connector configured with `SectionConfig` entries processes items and -populates `RoutedSections`. +**Scenario**: `BuildMarkConfig.CreateDefault()` is called; the returned `Sections` collection is +inspected. -**Expected**: Routed sections are present in the result as configured. +**Expected**: Three sections are present with ids `"changes"`, `"bugs-fixed"`, and +`"dependency-updates"` and corresponding titles `"Changes"`, `"Bugs Fixed"`, and +`"Dependency Updates"`. -**Requirement coverage**: `BuildMark-Configuration-SectionConfig` - -### MockRepoConnector_GetBuildInformationAsync_WithRules_ReturnsRoutedSections - -**Scenario**: `MockRepoConnector` is configured with sections; `GetBuildInformationAsync` -populates routed sections. - -**Expected**: `RoutedSections` contains the configured section names. - -**Requirement coverage**: `BuildMark-Configuration-SectionConfig` +**Requirement coverage**: `BuildMark-SectionConfig-Properties`. ## Requirements Coverage -- **BuildMark-Configuration-SectionConfig**: - RepoConnectors_GitHubConnector_GetBuildInformation_WithMockedData_ReturnsValidBuildInformation, - MockRepoConnector_GetBuildInformationAsync_WithRules_ReturnsRoutedSections +- **`BuildMark-SectionConfig-Properties`**: + - BuildMarkConfig_CreateDefault_ContainsDependencyUpdatesSection diff --git a/docs/verification/build-mark/program.md b/docs/verification/build-mark/program.md index 8322304..c3d7f78 100644 --- a/docs/verification/build-mark/program.md +++ b/docs/verification/build-mark/program.md @@ -86,15 +86,53 @@ to avoid live API calls where needed. **Expected**: Error is written to stderr; exit code is 1. -**Requirement coverage**: `BuildMark-Program-ErrorHandling` - -### Program_Run_ConnectorThrowsInvalidOperationException_WritesErrorAndSetsExitCode +**Requirement coverage**: `BuildMark-Program-ErrorHandling-InvalidBuildVersion` **Scenario**: `Program.Run` is called but connector factory throws `InvalidOperationException`. **Expected**: Error is written to stderr; exit code is 1. -**Requirement coverage**: `BuildMark-Program-ErrorHandling` +**Requirement coverage**: `BuildMark-Program-ErrorHandling-ConnectorFailure` + +### Program_Run_WithSilentFlag_SuppressesOutput + +**Scenario**: `Program.Run` is called with `--silent` and `--help` flags in context. + +**Expected**: Banner text is suppressed from console output; exit code is 0. + +**Requirement coverage**: `BuildMark-Program-Silent` + +### Program_Run_WithLogFlag_WritesToLogFile + +**Scenario**: `Program.Run` is called with `--log ` flag pointing to a temporary file. + +**Expected**: Log file is created and contains non-empty output; exit code is 0. + +**Requirement coverage**: `BuildMark-Program-Log` + +### Program_Run_WithResultsFlag_WritesResultsFile + +**Scenario**: `Program.Run` is called with `--validate` and `--results ` flags. + +**Expected**: Results file is created; exit code is 0. + +**Requirement coverage**: `BuildMark-Program-Results` + +### Program_Run_WithBuildVersionFlag_AcceptsBuildVersion + +**Scenario**: `Program.Run` is called with `--build-version 3.2.1` and a mock connector. + +**Expected**: Report is generated containing the specified version string; exit code is 0. + +**Requirement coverage**: `BuildMark-Program-BuildVersion` + +### Program_Run_WithDepthFlag_SetsHeadingDepth + +**Scenario**: `Program.Run` is called with `--depth 3` and a mock connector. + +**Expected**: Report uses level-three headings (`###`) for the title; exit code is 0. + +**Requirement coverage**: `BuildMark-Program-Depth` ## Requirements Coverage @@ -105,5 +143,10 @@ to avoid live API calls where needed. - **BuildMark-Program-Validate**: Program_Run_ValidateFlag_OutputsValidationMessage - **BuildMark-Program-Report**: Program_Run_ReportWithIncludeKnownIssuesFlag_GeneratesReportWithKnownIssues - **BuildMark-Program-Lint**: Program_Run_LintFlagWithoutConfiguration_LeavesExitCodeAtZero -- **BuildMark-Program-ErrorHandling**: Program_Run_InvalidBuildVersion_WritesErrorAndSetsExitCode, - Program_Run_ConnectorThrowsInvalidOperationException_WritesErrorAndSetsExitCode +- **BuildMark-Program-Silent**: Program_Run_WithSilentFlag_SuppressesOutput +- **BuildMark-Program-Log**: Program_Run_WithLogFlag_WritesToLogFile +- **BuildMark-Program-Results**: Program_Run_WithResultsFlag_WritesResultsFile +- **BuildMark-Program-BuildVersion**: Program_Run_WithBuildVersionFlag_AcceptsBuildVersion +- **BuildMark-Program-Depth**: Program_Run_WithDepthFlag_SetsHeadingDepth +- **BuildMark-Program-ErrorHandling-InvalidBuildVersion**: Program_Run_InvalidBuildVersion_WritesErrorAndSetsExitCode +- **BuildMark-Program-ErrorHandling-ConnectorFailure**: Program_Run_ConnectorThrowsInvalidOperationException_WritesErrorAndSetsExitCode diff --git a/docs/verification/build-mark/repo-connectors/github/git-hub-repo-connector.md b/docs/verification/build-mark/repo-connectors/github/git-hub-repo-connector.md index 0ac83ea..71e819e 100644 --- a/docs/verification/build-mark/repo-connectors/github/git-hub-repo-connector.md +++ b/docs/verification/build-mark/repo-connectors/github/git-hub-repo-connector.md @@ -28,7 +28,9 @@ and edge cases such as duplicate commit SHAs and substring label matching. **Scenario**: `GitHubRepoConnector` is constructed with a `GitHubConnectorConfig`. -**Expected**: Configuration overrides (e.g., owner, repo) are stored. +**Expected**: `ConfigurationOverrides.Owner` equals `"example-owner"`; +`ConfigurationOverrides.Repo` equals `"example-repo"`; `ConfigurationOverrides.BaseUrl` +equals `"https://api.github.com"`. **Requirement coverage**: `BuildMark-RepoConnectors-GitHub` diff --git a/docs/verification/build-mark/self-test/self-test.md b/docs/verification/build-mark/self-test/self-test.md index b4165e3..a097310 100644 --- a/docs/verification/build-mark/self-test/self-test.md +++ b/docs/verification/build-mark/self-test/self-test.md @@ -2,29 +2,71 @@ ## Verification Approach -The SelfTest subsystem is verified through the `Program_Run_ValidateFlag_OutputsValidationMessage` -test in `ProgramTests.cs` and through the CI pipeline integration test step -`Run self-validation`. Both confirm that the `Validation.Run` method can be invoked -without errors and produces output, providing evidence that all internal self-checks -pass in the test environment. +The SelfTest subsystem is verified with dedicated subsystem tests in `SelfTestTests.cs`. Tests +invoke `Validation.Run` through a `Context` constructed with controlled argument arrays, then +inspect created results files and log output. `MockRepoConnector` is used so that no real +repository access is needed. ## Dependencies -| Mock / Stub | Reason | -| ----------- | -------------------------------------------------------- | -| `Context` | Provides output capture for validation message assertion | +| Mock / Stub | Reason | +| --- | --- | +| `MockRepoConnector` | Provides a real connector factory that does not require external network access. | -## Test Scenarios (Integration) +## Test Scenarios -### Program_Run_ValidateFlag_OutputsValidationMessage +### SelfTest_Validation_WithTrxFile_WritesResults -**Scenario**: `Program.Run` is called with `Validate = true`. +**Scenario**: `Validation.Run` is called with `--validate --results .trx --silent`; the +results file path ends in `.trx`. -**Expected**: Validation framework runs; output is written; exit code is 0. +**Expected**: A `.trx` file is created; its content contains `TestRun` and +`BuildMark Self-Validation`. -**Requirement coverage**: `BuildMark-SelfTest-Validation`, `BuildMark-Program-Validate` +**Requirement coverage**: `BuildMark-SelfTest-Qualification`. + +### SelfTest_Validation_WithXmlFile_WritesResults + +**Scenario**: `Validation.Run` is called with `--validate --results .xml --silent`; the +results file path ends in `.xml`. + +**Expected**: An `.xml` file is created; its content contains `testsuites` and +`BuildMark Self-Validation`. + +**Requirement coverage**: `BuildMark-SelfTest-Qualification`. + +### SelfTest_ResultsOutput_WithTrxFile_CreatesFile + +**Scenario**: `Validation.Run` is called with `--validate --results output.trx --silent`. + +**Expected**: The `.trx` results file is created and has a non-zero file size. + +**Requirement coverage**: `BuildMark-SelfTest-ResultsOutput`. + +### SelfTest_ResultsOutput_WithXmlFile_CreatesFile + +**Scenario**: `Validation.Run` is called with `--validate --results output.xml --silent`. + +**Expected**: The `.xml` results file is created and has a non-zero file size. + +**Requirement coverage**: `BuildMark-SelfTest-ResultsOutput`. + +### SelfTest_Qualification_WithoutResultsFile_Succeeds + +**Scenario**: `Validation.Run` is called with `--validate --log /validation.log --silent`; no +`--results` argument is supplied. + +**Expected**: Validation completes without error; the log file is created; its content contains +`BuildMark Self-validation` and `Total Tests:`. + +**Requirement coverage**: `BuildMark-SelfTest-Qualification`. ## Requirements Coverage -- **BuildMark-SelfTest-Validation**: Program_Run_ValidateFlag_OutputsValidationMessage -- **BuildMark-Program-Validate**: Program_Run_ValidateFlag_OutputsValidationMessage +- **`BuildMark-SelfTest-Qualification`**: + - SelfTest_Validation_WithTrxFile_WritesResults + - SelfTest_Validation_WithXmlFile_WritesResults + - SelfTest_Qualification_WithoutResultsFile_Succeeds +- **`BuildMark-SelfTest-ResultsOutput`**: + - SelfTest_ResultsOutput_WithTrxFile_CreatesFile + - SelfTest_ResultsOutput_WithXmlFile_CreatesFile diff --git a/docs/verification/build-mark/self-test/validation.md b/docs/verification/build-mark/self-test/validation.md index c6f53bd..677cc98 100644 --- a/docs/verification/build-mark/self-test/validation.md +++ b/docs/verification/build-mark/self-test/validation.md @@ -2,33 +2,76 @@ ## Verification Approach -`Validation` is tested indirectly through `ProgramTests.cs`. The test -`Program_Run_ValidateFlag_OutputsValidationMessage` invokes `Program.Run` with -`Validate = true`, which delegates to `Validation.Run`. Successful completion without -exception or non-zero exit code constitutes evidence that the validation framework -is operational. - -The CI pipeline integration test `Run self-validation` also runs -`buildmark --validate --results artifacts/validation-*.trx` on each operating -system and .NET runtime combination, providing platform-level evidence. +`Validation` is verified with dedicated unit tests in `ValidationTests.cs`. Tests construct a +`Context` with controlled arguments, call `Validation.Run`, and assert on the created results +files, console error output, and exit code. `MockRepoConnector` is provided as the connector +factory; no further mocking is required. ## Dependencies -| Mock / Stub | Reason | -| ----------- | ----------------------------------------------------- | -| `Context` | Provides output stream for validation result messages | +| Mock / Stub | Reason | +| ------------------- | --------------------------------------------------------------------- | +| `MockRepoConnector` | Supplies connector factory so self-check runs without network access. | ## Test Scenarios -### Program_Run_ValidateFlag_OutputsValidationMessage +### Validation_Run_WithTrxResultsFile_WritesTrxFile + +**Scenario**: `Validation.Run` is called with `--validate --results /results.trx`; console +output is captured. + +**Expected**: `results.trx` is created; its content contains `TestRun` and +`BuildMark Self-Validation`. + +**Requirement coverage**: `BuildMark-Validation-Run`, `BuildMark-Validation-TrxOutput`. + +### Validation_Run_WithXmlResultsFile_WritesJUnitFile + +**Scenario**: `Validation.Run` is called with `--validate --results /results.xml`; console +output is captured. + +**Expected**: `results.xml` is created; its content contains `testsuites` and +`BuildMark Self-Validation`. + +**Requirement coverage**: `BuildMark-Validation-Run`, `BuildMark-Validation-JUnitOutput`. + +### Validation_Run_WithUnsupportedResultsFileExtension_ShowsError + +**Scenario**: `Validation.Run` is called with `--validate --results /results.json` +(unsupported extension); console error is captured. + +**Expected**: Console error contains `"Unsupported results file format"`; exit code is 1. + +**Requirement coverage**: `BuildMark-Validation-TrxOutput`, `BuildMark-Validation-JUnitOutput`. + +### Validation_Run_WithInvalidResultsFilePath_ShowsError + +**Scenario**: `Validation.Run` is called with +`--validate --results /invalid_path_that_does_not_exist/results.trx`; console error is captured. + +**Expected**: Console error contains `"Failed to write results file"`; exit code is 1. + +**Requirement coverage**: `BuildMark-Validation-TrxOutput`. + +### Validation_Run_WithoutResultsFile_CompletesSuccessfully -**Scenario**: `Program.Run` is called with `Validate = true`; control reaches -`Validation.Run`. +**Scenario**: `Validation.Run` is called with `--validate --silent`; no `--results` argument is +supplied. -**Expected**: Validation output is written to the context output; exit code is 0. +**Expected**: Validation completes without error; exit code is 0. -**Requirement coverage**: `BuildMark-SelfTest-Validation` +**Requirement coverage**: `BuildMark-Validation-Run`. ## Requirements Coverage -- **BuildMark-SelfTest-Validation**: Program_Run_ValidateFlag_OutputsValidationMessage +- **`BuildMark-Validation-Run`**: + - Validation_Run_WithTrxResultsFile_WritesTrxFile + - Validation_Run_WithXmlResultsFile_WritesJUnitFile + - Validation_Run_WithoutResultsFile_CompletesSuccessfully +- **`BuildMark-Validation-TrxOutput`**: + - Validation_Run_WithTrxResultsFile_WritesTrxFile + - Validation_Run_WithUnsupportedResultsFileExtension_ShowsError + - Validation_Run_WithInvalidResultsFilePath_ShowsError +- **`BuildMark-Validation-JUnitOutput`**: + - Validation_Run_WithXmlResultsFile_WritesJUnitFile + - Validation_Run_WithUnsupportedResultsFileExtension_ShowsError diff --git a/docs/verification/build-mark/version/version-semantic.md b/docs/verification/build-mark/version/version-semantic.md index 62bd147..5cb37ef 100644 --- a/docs/verification/build-mark/version/version-semantic.md +++ b/docs/verification/build-mark/version/version-semantic.md @@ -16,36 +16,36 @@ to the underlying `VersionComparable`, string formatting, and comparison. ### VersionSemantic_Create_WithBuildMetadata_ReturnsInstance -**Scenario**: `VersionSemantic.Create` is called with a version string that includes -build metadata (the `+` suffix). +**Scenario**: `VersionSemantic.Create` is called with `"1.2.3+build.123"`. -**Expected**: Returns a non-null instance with metadata set. +**Expected**: `Metadata` equals `"build.123"`; `FullVersion` equals `"1.2.3+build.123"`. **Requirement coverage**: `BuildMark-Version-VersionSemantic` ### VersionSemantic_Create_WithoutBuildMetadata_ReturnsInstance -**Scenario**: `VersionSemantic.Create` is called with a version string that has no -build metadata. +**Scenario**: `VersionSemantic.Create` is called with `"1.2.3"`. -**Expected**: Returns a non-null instance with metadata as empty string. +**Expected**: `Metadata` is `null`; `FullVersion` equals `"1.2.3"`. **Requirement coverage**: `BuildMark-Version-VersionSemantic` ### VersionSemantic_Properties_DelegateToComparable_Correctly -**Scenario**: Major, minor, patch, and pre-release properties are accessed on a -`VersionSemantic` instance. +**Scenario**: `VersionSemantic.Create` is called with `"1.2.3-alpha"` and the `Major`, +`Minor`, `Patch`, `PreRelease`, and `CompareVersion` properties are read. -**Expected**: Values match those of the underlying `VersionComparable`. +**Expected**: `Major` equals 1; `Minor` equals 2; `Patch` equals 3; `PreRelease` equals +`"alpha"`; `CompareVersion` equals `"1.2.3-alpha"`. **Requirement coverage**: `BuildMark-Version-VersionSemantic` ### VersionSemantic_ToString_FormatsCompletely_WithAllComponents -**Scenario**: `ToString` is called on a `VersionSemantic` with pre-release and metadata. +**Scenario**: `VersionSemantic.Create` is called with `"1.2.3-alpha+build.123"` and +`FullVersion` is read. -**Expected**: Returns a string in `major.minor.patch-pre+meta` format. +**Expected**: `FullVersion` equals `"1.2.3-alpha+build.123"`. **Requirement coverage**: `BuildMark-Version-VersionSemantic` @@ -67,31 +67,37 @@ build metadata. ### VersionSemantic_Create_SimpleVersion_ParsesVersion -**Scenario**: `VersionSemantic.Create` is called with a simple `major.minor.patch`. +**Scenario**: `VersionSemantic.Create` is called with `"1.2.3"`. -**Expected**: Instance created with zero pre-release and metadata. +**Expected**: `Major` equals 1; `Minor` equals 2; `Patch` equals 3; `Numbers` equals +`"1.2.3"`; `PreRelease` equals `""`; `Metadata` is `null`; `CompareVersion` equals +`"1.2.3"`; `FullVersion` equals `"1.2.3"`; `IsPreRelease` is false. **Requirement coverage**: `BuildMark-Version-VersionSemantic` ### VersionSemantic_Create_VersionWithMetadata_ParsesVersion -**Scenario**: `VersionSemantic.Create` is called with version plus build metadata. +**Scenario**: `VersionSemantic.Create` is called with `"1.2.3+build.5"`. -**Expected**: Metadata property returns the metadata string. +**Expected**: `Numbers` equals `"1.2.3"`; `PreRelease` equals `""`; `Metadata` equals +`"build.5"`; `CompareVersion` equals `"1.2.3"`; `FullVersion` equals `"1.2.3+build.5"`; +`IsPreRelease` is false. **Requirement coverage**: `BuildMark-Version-VersionSemantic` ### VersionSemantic_Create_PreReleaseWithMetadata_ParsesVersion -**Scenario**: `VersionSemantic.Create` is called with pre-release and metadata. +**Scenario**: `VersionSemantic.Create` is called with `"2.0.0-alpha.1+linux.x64"`. -**Expected**: Both pre-release and metadata properties are set correctly. +**Expected**: `Numbers` equals `"2.0.0"`; `PreRelease` equals `"alpha.1"`; `Metadata` +equals `"linux.x64"`; `CompareVersion` equals `"2.0.0-alpha.1"`; `FullVersion` equals +`"2.0.0-alpha.1+linux.x64"`; `IsPreRelease` is true. **Requirement coverage**: `BuildMark-Version-VersionSemantic` ### VersionSemantic_TryCreate_InvalidVersion_ReturnsNull -**Scenario**: `VersionSemantic.TryCreate` is called with an invalid string. +**Scenario**: `VersionSemantic.TryCreate` is called with `"not-a-version"`. **Expected**: Returns `null`. @@ -99,17 +105,19 @@ build metadata. ### VersionSemantic_Create_InvalidVersion_ThrowsArgumentException -**Scenario**: `VersionSemantic.Create` is called with an invalid string. +**Scenario**: `VersionSemantic.Create` is called with `"not-a-version"`. -**Expected**: `ArgumentException` is thrown. +**Expected**: `ArgumentException` is thrown with a message containing +`"does not match semantic version format"`. **Requirement coverage**: `BuildMark-Version-VersionSemantic` ### VersionSemantic_Comparable_AllowsComparison -**Scenario**: Two `VersionSemantic` instances are compared using comparison operators. +**Scenario**: `Comparable` is compared for `"1.2.3+build1"` and `"1.2.4+build2"`. -**Expected**: Comparison delegates to the underlying `VersionComparable` correctly. +**Expected**: `version1.Comparable < version2.Comparable` is true (i.e., `"1.2.3"` sorts +before `"1.2.4"`). **Requirement coverage**: `BuildMark-Version-VersionSemantic` diff --git a/docs/verification/build-mark/version/version-tag.md b/docs/verification/build-mark/version/version-tag.md index 5491980..8264678 100644 --- a/docs/verification/build-mark/version/version-tag.md +++ b/docs/verification/build-mark/version/version-tag.md @@ -25,49 +25,52 @@ and error handling for invalid tags. ### VersionTag_Create_StandardTag_ParsesCorrectly -**Scenario**: `VersionTag.Create` is called with a standard `v1.2.3` tag. +**Scenario**: `VersionTag.Create` is called with `"1.2.3"` (no prefix). -**Expected**: Version components are parsed correctly. +**Expected**: `Tag` equals `"1.2.3"`; `FullVersion` equals `"1.2.3"`; `Numbers` equals `"1.2.3"`. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_PrefixedTag_ParsesCorrectly -**Scenario**: `VersionTag.Create` is called with a prefixed tag like `v1.2.3`. +**Scenario**: `VersionTag.Create` is called with `"v1.2.3"`. -**Expected**: `v` prefix is stripped; version parses correctly. +**Expected**: `Tag` equals `"v1.2.3"`; `FullVersion` equals `"1.2.3"`; `Numbers` equals `"1.2.3"`. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_DotSeparatedPreRelease_NormalizesToHyphen -**Scenario**: `VersionTag.Create` is called with a tag using dots in the pre-release -segment. +**Scenario**: `VersionTag.Create` is called with `"v1.2.3.alpha.1"`. -**Expected**: Dots in the pre-release are normalized to hyphens. +**Expected**: `Tag` equals `"v1.2.3.alpha.1"`; `FullVersion` equals `"1.2.3-alpha.1"`; +`PreRelease` equals `"alpha.1"`. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_ComplexTag_ExtractsVersionCorrectly -**Scenario**: `VersionTag.Create` is called with a complex tag containing prefix, -version, pre-release, and build metadata. +**Scenario**: `VersionTag.Create` is called with `"Release_1.2.3.beta.5+build.123"`. -**Expected**: All components are extracted correctly. +**Expected**: `Tag` equals `"Release_1.2.3.beta.5+build.123"`; `FullVersion` equals +`"1.2.3-beta.5+build.123"`; `Numbers` equals `"1.2.3"`; `PreRelease` equals `"beta.5"`; +`Metadata` equals `"build.123"`. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Properties_ExposeOriginalAndParsed_Correctly -**Scenario**: `Tag` and `Semantic` properties are accessed after creating a tag. +**Scenario**: `VersionTag.Create` is called with `"v1.2.3-alpha"` and the `Tag`, +`FullVersion`, `Numbers`, and `PreRelease` properties are read. -**Expected**: `Tag` returns the original string; `Semantic` returns the parsed version. +**Expected**: `Tag` equals `"v1.2.3-alpha"`; `FullVersion` equals `"1.2.3-alpha"`; +`Numbers` equals `"1.2.3"`; `PreRelease` equals `"alpha"`. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_ToString_ReturnsOriginalTag -**Scenario**: `ToString` is called on a `VersionTag` instance. +**Scenario**: `ToString` is called on a `VersionTag` created from `"v1.2.3-alpha+build.123"`. **Expected**: Returns the original tag string unchanged. @@ -75,23 +78,28 @@ version, pre-release, and build metadata. ### VersionTag_Create_SimpleVPrefix_ParsesVersion -**Scenario**: `VersionTag.Create` is called with `v1.0.0`. +**Scenario**: `VersionTag.Create` is called with `"v1.2.3"`. -**Expected**: Instance created with version `1.0.0`. +**Expected**: `Tag` equals `"v1.2.3"`; `FullVersion` equals `"1.2.3"`; `Numbers` equals +`"1.2.3"`; `PreRelease` equals `""`; `CompareVersion` equals `"1.2.3"`; `Metadata` equals +`""`; `IsPreRelease` is false. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_ComplexVersionWithMetadata_ParsesVersion -**Scenario**: `VersionTag.Create` is called with a tag including build metadata. +**Scenario**: `VersionTag.Create` is called with `"Rel_1.2.3.rc.4+build.5"`. -**Expected**: Metadata is preserved in the `Semantic` property. +**Expected**: `Tag` equals `"Rel_1.2.3.rc.4+build.5"`; `FullVersion` equals +`"1.2.3-rc.4+build.5"`; `Numbers` equals `"1.2.3"`; `PreRelease` equals `"rc.4"`; +`CompareVersion` equals `"1.2.3-rc.4"`; `Metadata` equals `"build.5"`; `IsPreRelease` +is true. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_TryCreate_InvalidTag_ReturnsNull -**Scenario**: `VersionTag.TryCreate` is called with an unparseable tag string. +**Scenario**: `VersionTag.TryCreate` is called with `"not-a-version"`. **Expected**: Returns `null`. @@ -99,41 +107,45 @@ version, pre-release, and build metadata. ### VersionTag_Create_InvalidTag_ThrowsArgumentException -**Scenario**: `VersionTag.Create` is called with an unparseable tag string. +**Scenario**: `VersionTag.Create` is called with `"not-a-version"`. -**Expected**: `ArgumentException` is thrown. +**Expected**: `ArgumentException` is thrown with a message containing +`"does not match version format"`. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_NoPrefix_ParsesVersion -**Scenario**: `VersionTag.Create` is called with a tag that has no alphabetic prefix. +**Scenario**: `VersionTag.Create` is called with `"1.0.0"`. -**Expected**: Version is parsed directly from the string. +**Expected**: `Tag` equals `"1.0.0"`; `FullVersion` equals `"1.0.0"`; `IsPreRelease` is false. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_HyphenPreReleaseWithMetadata_ParsesVersion -**Scenario**: `VersionTag.Create` is called with a tag using hyphen pre-release and -build metadata. +**Scenario**: `VersionTag.Create` is called with `"Rel_1.2.3-rc.4+build.5"`. -**Expected**: Both pre-release and metadata are parsed correctly. +**Expected**: `Tag` equals `"Rel_1.2.3-rc.4+build.5"`; `FullVersion` equals +`"1.2.3-rc.4+build.5"`; `Numbers` equals `"1.2.3"`; `PreRelease` equals `"rc.4"`; +`CompareVersion` equals `"1.2.3-rc.4"`; `Metadata` equals `"build.5"`; `IsPreRelease` +is true. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Semantic_AllowsComparison -**Scenario**: `Semantic` property is used to compare two tags. +**Scenario**: `Semantic.Comparable` is compared for tags `"v1.2.3"` and `"v1.11.2"`. -**Expected**: Comparison behaves correctly via the underlying `VersionSemantic`. +**Expected**: `tag1.Semantic.Comparable < tag2.Semantic.Comparable` is true (i.e., +`"v1.2.3"` sorts before `"v1.11.2"`). **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionComparable_Equals_DifferentPrefixesSameVersion_ReturnsTrue -**Scenario**: Two tags with different prefixes but the same version are compared -via `VersionComparable`. +**Scenario**: `Semantic.Comparable` is extracted from `"v1.2.3"`, `"VER1.2.3"`, +`"Release_1.2.3"`, and `"release/1.2.3"` and the four values are compared for equality. **Expected**: Comparable values are equal. @@ -141,34 +153,40 @@ via `VersionComparable`. ### VersionTag_GetVersionComparable_SemanticTags_ReturnsCorrectComparison -**Scenario**: `VersionComparable` extracted from two semantic tags is compared. +**Scenario**: `Semantic.Comparable` is compared for `"v1.0.0-alpha"`, `"v1.0.0-beta"`, +and `"v1.0.0"`. -**Expected**: Returns the correct ordering. +**Expected**: alpha < beta < release: `"v1.0.0-alpha"` sorts before `"v1.0.0-beta"`, +which sorts before `"v1.0.0"`. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_PathSeparatorPrefix_ParsesCorrectly -**Scenario**: `VersionTag.Create` is called with a tag in `prefix/1.2.3` format. +**Scenario**: `VersionTag.Create` is called with `"release/1.2.3"`. -**Expected**: Path prefix is stripped; version is parsed correctly. +**Expected**: `Tag` equals `"release/1.2.3"`; `FullVersion` equals `"1.2.3"`; `Numbers` +equals `"1.2.3"`; `PreRelease` equals `""`; `IsPreRelease` is false. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_PathSeparatorPrefixWithPreRelease_ParsesCorrectly -**Scenario**: `VersionTag.Create` is called with `prefix/1.2.3-alpha`. +**Scenario**: `VersionTag.Create` is called with `"release/1.2.3-rc.4"`. -**Expected**: Path prefix is stripped; pre-release is parsed correctly. +**Expected**: `Tag` equals `"release/1.2.3-rc.4"`; `FullVersion` equals `"1.2.3-rc.4"`; +`Numbers` equals `"1.2.3"`; `PreRelease` equals `"rc.4"`; `CompareVersion` equals +`"1.2.3-rc.4"`; `IsPreRelease` is true. **Requirement coverage**: `BuildMark-Version-VersionTag` ### VersionTag_Create_MultiLevelPathPrefix_ParsesCorrectly -**Scenario**: `VersionTag.Create` is called with a multi-level path prefix like -`a/b/1.2.3`. +**Scenario**: `VersionTag.Create` is called with `"builds/release/1.2.3-beta.1+build.99"`. -**Expected**: All prefix segments are stripped; version parses correctly. +**Expected**: `Tag` equals `"builds/release/1.2.3-beta.1+build.99"`; `FullVersion` equals +`"1.2.3-beta.1+build.99"`; `Numbers` equals `"1.2.3"`; `PreRelease` equals `"beta.1"`; +`Metadata` equals `"build.99"`; `IsPreRelease` is true. **Requirement coverage**: `BuildMark-Version-VersionTag` diff --git a/docs/verification/introduction.md b/docs/verification/introduction.md index ddc07be..ee69353 100644 --- a/docs/verification/introduction.md +++ b/docs/verification/introduction.md @@ -139,4 +139,4 @@ match identifiers defined in the ReqStream YAML files under `docs/reqstream/`. - See the *BuildMark Software Design* document for implementation details of each unit. - See the *BuildMark Requirements* document for the full requirements specification. -- [BuildMark Repository](https://github.com/demaconsulting/BuildMark) +- See the BuildMark repository at . diff --git a/src/DemaConsulting.BuildMark/Program.cs b/src/DemaConsulting.BuildMark/Program.cs index 71d493d..0244ba3 100644 --- a/src/DemaConsulting.BuildMark/Program.cs +++ b/src/DemaConsulting.BuildMark/Program.cs @@ -36,6 +36,17 @@ internal static class Program /// /// Gets the application version string. /// + /// + /// The version is resolved using the following fallback chain: + /// + /// — preferred because it includes + /// pre-release labels and build metadata (e.g., 1.2.3-beta.1+abc123). + /// assembly.GetName().Version — numeric fallback when the informational + /// attribute is absent. + /// "0.0.0" — final safety net for test assemblies or stripped builds that + /// carry neither attribute. + /// + /// public static string Version { get @@ -99,6 +110,19 @@ private static int Main(string[] args) /// Runs the program logic based on the provided context. /// /// The context containing command line arguments and program state. + /// + /// + /// This method is to serve as the test injection point. + /// Unit and integration tests construct a pre-configured and + /// call Run directly, bypassing the argument-parsing step in Main and + /// allowing individual code paths to be exercised in isolation. + /// + /// + /// As a side effect, the method mutates — callers + /// must read context.ExitCode after Run returns to determine success + /// or failure; the method itself has no return value. + /// + /// public static void Run(Context context) { // Priority 1: Version query @@ -141,6 +165,12 @@ public static void Run(Context context) /// Prints the application banner. /// /// The context for output. + /// + /// Called unconditionally after the version-flag check in the dispatch + /// sequence, so the banner is visible for every execution path except --version. + /// Factored out from to keep the dispatch sequence readable and to + /// allow the banner format to change without modifying the dispatch logic. + /// private static void PrintBanner(Context context) { context.WriteLine($"BuildMark version {Version}"); @@ -152,6 +182,12 @@ private static void PrintBanner(Context context) /// Prints usage information. /// /// The context for output. + /// + /// Called for all three help-flag variants (-?, -h, --help) from + /// the dispatch sequence. Factored out from so that + /// the help text can evolve independently of the dispatch logic and so that a single + /// method is the authoritative source of the usage message. + /// private static void PrintHelp(Context context) { context.WriteLine("Usage: buildmark [options]"); diff --git a/src/DemaConsulting.BuildMark/RepoConnectors/AzureDevOps/AzureDevOpsRepoConnector.cs b/src/DemaConsulting.BuildMark/RepoConnectors/AzureDevOps/AzureDevOpsRepoConnector.cs index d6f51f5..c4b53b5 100644 --- a/src/DemaConsulting.BuildMark/RepoConnectors/AzureDevOps/AzureDevOpsRepoConnector.cs +++ b/src/DemaConsulting.BuildMark/RepoConnectors/AzureDevOps/AzureDevOpsRepoConnector.cs @@ -297,92 +297,27 @@ private static (VersionTag? fromVersion, string? fromHash) DetermineBaselineVers return (null, null); } + // Find the position of target version in the newest-first tag list var toIndex = FindVersionIndex(lookupData.TagVersions, toVersion); - var fromVersion = toVersion.IsPreRelease - ? DetermineBaselineForPreRelease(toIndex, toHash, lookupData) - : DetermineBaselineForRelease(toIndex, lookupData.TagVersions); - - if (fromVersion != null && - lookupData.TagToCommitHash.TryGetValue(fromVersion.Tag, out var fromHash)) - { - return (fromVersion, fromHash); - } - - return (fromVersion, null); - } - - /// - /// Determines the baseline version for a pre-release. - /// - /// Index of target version in version list. - /// Commit hash of target version. - /// Lookup data structures. - /// Baseline version or null. - private static VersionTag? DetermineBaselineForPreRelease(int toIndex, string toHash, LookupData lookupData) - { - var versions = lookupData.TagVersions; - int startIndex; - - if (toIndex >= 0 && toIndex < versions.Count - 1) - { - startIndex = toIndex + 1; - } - else if (toIndex == -1 && versions.Count > 0) - { - startIndex = 0; - } - else - { - return null; - } - - // Search for previous version with different commit hash - for (var i = startIndex; i < versions.Count; i++) + // Build preceding versions list (oldest-first) for the base class selection methods. + // TagVersions is newest-first; preceding entries start at toIndex+1 (or 0 if target + // not found) and span to the end of the list. Iterating from Count-1 down gives oldest-first. + var startIndex = toIndex >= 0 ? toIndex + 1 : 0; + var preceding = new List(); + for (var i = lookupData.TagVersions.Count - 1; i >= startIndex; i--) { - if (lookupData.TagToCommitHash.TryGetValue(versions[i].Tag, out var candidateHash) && - candidateHash != toHash) - { - return versions[i]; - } - } - - return null; - } - - /// - /// Determines the baseline version for a release (non-pre-release). - /// - /// Index of target version in version list. - /// List of version tags. - /// Baseline version or null. - private static VersionTag? DetermineBaselineForRelease(int toIndex, List versions) - { - int startIndex; - - if (toIndex >= 0 && toIndex < versions.Count - 1) - { - startIndex = toIndex + 1; - } - else if (toIndex == -1 && versions.Count > 0) - { - startIndex = 0; - } - else - { - return null; + var tag = lookupData.TagVersions[i]; + var hash = lookupData.TagToCommitHash.TryGetValue(tag.Tag, out var h) ? h : string.Empty; + preceding.Add(new VersionCommitTag(tag, hash)); } - // Search for previous non-pre-release version - for (var i = startIndex; i < versions.Count; i++) - { - if (!versions[i].IsPreRelease) - { - return versions[i]; - } - } + // Delegate selection to the base class algorithm + var baseline = toVersion.IsPreRelease + ? FindBaselineForPreRelease(preceding, toHash) + : FindBaselineForRelease(preceding); - return null; + return (baseline?.VersionTag, baseline?.CommitHash); } /// diff --git a/src/DemaConsulting.BuildMark/RepoConnectors/GitHub/GitHubRepoConnector.cs b/src/DemaConsulting.BuildMark/RepoConnectors/GitHub/GitHubRepoConnector.cs index 2e93434..be39f83 100644 --- a/src/DemaConsulting.BuildMark/RepoConnectors/GitHub/GitHubRepoConnector.cs +++ b/src/DemaConsulting.BuildMark/RepoConnectors/GitHub/GitHubRepoConnector.cs @@ -464,131 +464,30 @@ private static (VersionTag? fromVersion, string? fromHash) DetermineBaselineVers return (null, null); } - // Find the position of target version in release history + // Find the position of target version in the newest-first release list var toIndex = FindVersionIndex(lookupData.ReleaseVersions, toVersion); - // Determine baseline version based on whether target is pre-release - var fromVersion = toVersion.IsPreRelease - ? DetermineBaselineForPreRelease(toIndex, toHash, lookupData) - : DetermineBaselineForRelease(toIndex, lookupData.ReleaseVersions); - - // Get commit hash for baseline version if one was found - if (fromVersion != null && - lookupData.TagToRelease.TryGetValue(fromVersion.Tag, out var fromRelease) && - lookupData.TagsByName.TryGetValue(fromRelease.TagName!, out var fromTagCommit)) + // Build preceding versions list (oldest-first) for the base class selection methods. + // ReleaseVersions is newest-first; preceding entries start at toIndex+1 (or 0 if target + // not found) and span to the end of the list. Iterating from Count-1 down gives oldest-first. + var startIndex = toIndex >= 0 ? toIndex + 1 : 0; + var preceding = new List(); + for (var i = lookupData.ReleaseVersions.Count - 1; i >= startIndex; i--) { - return (fromVersion, fromTagCommit.Commit.Sha); + var tag = lookupData.ReleaseVersions[i]; + var hash = lookupData.TagToRelease.TryGetValue(tag.Tag, out var rel) && + lookupData.TagsByName.TryGetValue(rel.TagName!, out var tagCommit) + ? tagCommit.Commit.Sha + : string.Empty; + preceding.Add(new VersionCommitTag(tag, hash)); } - // Return baseline version with null hash if commit not found - return (fromVersion, null); - } - - /// - /// Determines the baseline version for a pre-release. - /// Pre-release versions pick the previous tag (release or pre-release) that isn't the same commit-hash. - /// - /// Index of target version in release history. - /// Commit hash of target version. - /// Lookup data structures. - /// Baseline version or null. - private static VersionTag? DetermineBaselineForPreRelease(int toIndex, string toHash, LookupData lookupData) - { - var releaseVersions = lookupData.ReleaseVersions; - - // Determine starting index for search - int startIndex; - if (toIndex >= 0 && toIndex < releaseVersions.Count - 1) - { - // Target exists, start from next older release - startIndex = toIndex + 1; - } - else if (toIndex == -1 && releaseVersions.Count > 0) - { - // Target not in history, start from most recent - startIndex = 0; - } - else - { - // No valid starting point - startIndex = -1; - } - - // If no valid starting point, return null - if (startIndex < 0) - { - return null; - } - - // Search forward through older releases (incrementing index) for previous version with different commit hash - for (var i = startIndex; i < releaseVersions.Count; i++) - { - var candidateVersion = releaseVersions[i]; - - // Get commit hash for candidate version - if (lookupData.TagToRelease.TryGetValue(candidateVersion.Tag, out var candidateRelease) && - lookupData.TagsByName.TryGetValue(candidateRelease.TagName!, out var candidateTag) && - candidateTag.Commit.Sha != toHash) - { - // Found a version with a different commit hash - use it - return candidateVersion; - } - } - - // No version with different commit hash found - return null; - } - - /// - /// Determines the baseline version for a release (non-pre-release). - /// - /// Index of target version in release history. - /// List of release versions. - /// Baseline version or null. - private static VersionTag? DetermineBaselineForRelease(int toIndex, List releaseVersions) - { - // Release versions skip pre-releases and use previous non-pre-release as baseline - var startIndex = DetermineSearchStartIndex(toIndex, releaseVersions.Count); - - // Search forward through older releases (incrementing index) for previous non-pre-release version - if (startIndex >= 0) - { - for (var i = startIndex; i < releaseVersions.Count; i++) - { - if (!releaseVersions[i].IsPreRelease) - { - return releaseVersions[i]; - } - } - } - - return null; - } - - /// - /// Determines the starting index for searching for previous releases. - /// - /// Index of target version in release history. - /// Total number of releases. - /// Starting index for search, or -1 if no search needed. - private static int DetermineSearchStartIndex(int toIndex, int releaseCount) - { - // Target version exists in history, start search from next older release - if (toIndex >= 0 && toIndex < releaseCount - 1) - { - return toIndex + 1; - } - - // Target version not in history, start from most recent release - if (toIndex == -1 && releaseCount > 0) - { - // Target version not in history, start from most recent release - // The target is newer than all existing releases, so use the most recent as baseline - return 0; - } + // Delegate selection to the base class algorithm + var baseline = toVersion.IsPreRelease + ? FindBaselineForPreRelease(preceding, toHash) + : FindBaselineForRelease(preceding); - // Target is the oldest release or no releases exist, no previous release exists - return -1; + return (baseline?.VersionTag, baseline?.CommitHash); } /// diff --git a/src/DemaConsulting.BuildMark/RepoConnectors/Mock/MockRepoConnector.cs b/src/DemaConsulting.BuildMark/RepoConnectors/Mock/MockRepoConnector.cs index 2b6a83a..73d0542 100644 --- a/src/DemaConsulting.BuildMark/RepoConnectors/Mock/MockRepoConnector.cs +++ b/src/DemaConsulting.BuildMark/RepoConnectors/Mock/MockRepoConnector.cs @@ -231,7 +231,7 @@ public override async Task GetBuildInformationAsync(VersionTag /// Determines the baseline version for comparing changes. /// /// Target version. - /// List of tag history. + /// List of tag history, ordered oldest first. /// Tuple of (fromTagInfo, fromHash). private async Task<(VersionTag? fromTagInfo, string? fromHash)> DetermineBaselineVersionAsync( VersionTag toTagInfo, @@ -243,95 +243,28 @@ public override async Task GetBuildInformationAsync(VersionTag return (null, null); } - // Find the position of target version in tag history + // Find the position of target version in the tag history var toIndex = FindVersionIndex(tags, toTagInfo); - // Determine baseline version based on whether target is pre-release - var fromTagInfo = toTagInfo.IsPreRelease - ? DetermineBaselineForPreRelease(toIndex, tags) - : DetermineBaselineForRelease(toIndex, tags); + // Get the commit hash of the target version for same-commit detection + var toHash = await GetHashForTagAsync(toTagInfo.Tag) ?? CurrentHash; - // Get commit hash for baseline version if one was found - if (fromTagInfo != null) - { - var fromHash = await GetHashForTagAsync(fromTagInfo.Tag); - return (fromTagInfo, fromHash); - } - - // Return baseline version with null hash - return (fromTagInfo, null); - } - - /// - /// Determines the baseline version for a pre-release. - /// - /// Index of target version in tag history. - /// List of tags. - /// Baseline version or null. - private static VersionTag? DetermineBaselineForPreRelease(int toIndex, List tags) - { - // Pre-release versions use the immediately previous tag as baseline - if (toIndex > 0) + // Build list of preceding VersionCommitTags (oldest first, excluding target) + var precedingCount = toIndex >= 0 ? toIndex : tags.Count; + var precedingVersions = new List(precedingCount); + for (var i = 0; i < precedingCount; i++) { - // Target version exists in history, use previous tag - return tags[toIndex - 1]; + var hash = await GetHashForTagAsync(tags[i].Tag) ?? string.Empty; + precedingVersions.Add(new VersionCommitTag(tags[i], hash)); } - // Target version not in history, use most recent tag as baseline - if (toIndex == -1) - { - return tags[^1]; - } - - // If toIndex == 0, this is the first tag, no baseline - return null; - } - - /// - /// Determines the baseline version for a release (non-pre-release). - /// - /// Index of target version in tag history. - /// List of tags. - /// Baseline version or null. - private static VersionTag? DetermineBaselineForRelease(int toIndex, List tags) - { - // Release versions skip pre-releases and use previous release as baseline - var startIndex = DetermineSearchStartIndex(toIndex, tags.Count); - - // Search backward for previous non-pre-release version - for (var i = startIndex; i >= 0; i--) - { - if (!tags[i].IsPreRelease) - { - return tags[i]; - } - } - - return null; - } - - /// - /// Determines the starting index for searching for previous releases. - /// - /// Index of target version in tag history. - /// Total number of tags. - /// Starting index for search, or -1 if no search needed. - private static int DetermineSearchStartIndex(int toIndex, int tagCount) - { - // Target version exists in history, start search from previous position - if (toIndex > 0) - { - return toIndex - 1; - } - - // Target version not in history, start from most recent tag - if (toIndex == -1) - { - return tagCount - 1; - } + // Find baseline using base class methods + var baseline = toTagInfo.IsPreRelease + ? FindBaselineForPreRelease(precedingVersions, toHash) + : FindBaselineForRelease(precedingVersions); - // Target is first tag, no previous release exists - return -1; + // Return baseline version and hash if one was found + return (baseline?.VersionTag, baseline?.CommitHash); } /// diff --git a/src/DemaConsulting.BuildMark/RepoConnectors/RepoConnectorBase.cs b/src/DemaConsulting.BuildMark/RepoConnectors/RepoConnectorBase.cs index 914c593..df00eec 100644 --- a/src/DemaConsulting.BuildMark/RepoConnectors/RepoConnectorBase.cs +++ b/src/DemaConsulting.BuildMark/RepoConnectors/RepoConnectorBase.cs @@ -146,4 +146,63 @@ protected static int FindVersionIndex(List versions, VersionTag targ // Version not found in list return -1; } + + /// + /// Finds the baseline version for a pre-release target. + /// Returns the most recent entry from whose commit hash + /// differs from , skipping any entries that share the + /// same commit hash as the target (which would produce an empty changelog). + /// + /// + /// Version-commit tags that precede the target, ordered oldest first. + /// + /// Commit hash of the target version. + /// + /// The most recent preceding entry with a different commit hash, or null if none exists. + /// + internal static VersionCommitTag? FindBaselineForPreRelease( + List precedingVersions, + string targetCommitHash) + { + // Search from newest to oldest, skipping entries with the same commit hash + for (var i = precedingVersions.Count - 1; i >= 0; i--) + { + // Return the first entry that has a different commit hash + if (precedingVersions[i].CommitHash != targetCommitHash) + { + return precedingVersions[i]; + } + } + + // No entry with a different commit hash found + return null; + } + + /// + /// Finds the baseline version for a release target. + /// Returns the most recent entry from that is not a + /// pre-release, skipping any pre-release entries. + /// + /// + /// Version-commit tags that precede the target, ordered oldest first. + /// + /// + /// The most recent preceding non-pre-release entry, or null if none exists. + /// + internal static VersionCommitTag? FindBaselineForRelease( + List precedingVersions) + { + // Search from newest to oldest, skipping pre-release entries + for (var i = precedingVersions.Count - 1; i >= 0; i--) + { + // Return the first non-pre-release entry found + if (!precedingVersions[i].VersionTag.IsPreRelease) + { + return precedingVersions[i]; + } + } + + // No non-pre-release entry found + return null; + } } diff --git a/test/DemaConsulting.BuildMark.Tests/IntegrationTests.cs b/test/DemaConsulting.BuildMark.Tests/IntegrationTests.cs index 786178d..d35200c 100644 --- a/test/DemaConsulting.BuildMark.Tests/IntegrationTests.cs +++ b/test/DemaConsulting.BuildMark.Tests/IntegrationTests.cs @@ -399,6 +399,75 @@ public void BuildMark_Report_ShowsVersionRangeFromPreviousRelease() } } + /// + /// Test that the report selects the nearest predecessor with a different commit hash + /// as the baseline when generating build notes for a pre-release version. + /// + [Fact] + public void BuildMark_Report_BaselinePreRelease_SkipsSameCommitPredecessor() + { + // Arrange: create a temporary report file path + var reportFile = Path.GetTempFileName(); + try + { + // Create context with a connector that provides a pre-release where the immediate + // predecessor shares the same commit hash (should be skipped), leaving v1.1.0 as baseline + using var context = Context.Create( + ["--build-version", "1.2.0-beta.2", "--report", reportFile, "--silent"], + () => new PreReleaseSameCommitConnector()); + + // Act: run the program + Program.Run(context); + + // Assert: report uses v1.1.0 as the baseline (same-commit predecessor was skipped) + Assert.Equal(0, context.ExitCode); + var content = File.ReadAllText(reportFile); + Assert.Contains("v1.1.0", content); + Assert.Contains("v1.1.0...1.2.0-beta.2", content); + } + finally + { + if (File.Exists(reportFile)) + { + File.Delete(reportFile); + } + } + } + + /// + /// Test that the report generates from the beginning of history when no previous + /// version tag qualifies as a baseline. + /// + [Fact] + public void BuildMark_Report_BaselineFirstRelease_GeneratesFromBeginningOfHistory() + { + // Arrange: create a temporary report file path + var reportFile = Path.GetTempFileName(); + try + { + // Create context with a connector that provides a first release with no baseline + using var context = Context.Create( + ["--build-version", "1.0.0", "--report", reportFile, "--silent"], + () => new FirstReleaseConnector()); + + // Act: run the program + Program.Run(context); + + // Assert: report generates successfully and shows N/A for the missing baseline + Assert.Equal(0, context.ExitCode); + var content = File.ReadAllText(reportFile); + Assert.Contains("1.0.0", content); + Assert.Contains("N/A", content); + } + finally + { + if (File.Exists(reportFile)) + { + File.Delete(reportFile); + } + } + } + /// /// Test that the report includes known issues when the flag is set. /// @@ -1215,6 +1284,37 @@ public Task GetBuildInformationAsync(VersionTag? version = nul null)); } } + + /// + /// Mock connector that simulates a pre-release where the immediate predecessor + /// shares the same commit hash (skipped), leaving v1.1.0 as the baseline. + /// + private sealed class PreReleaseSameCommitConnector : IRepoConnector + { + /// + public Task GetBuildInformationAsync(VersionTag? version = null) + { + // Target is 1.2.0-beta.2; its immediate predecessor 1.2.0-beta.1 shares the same + // commit hash and must be skipped, so v1.1.0 is chosen as the baseline instead. + var currentTag = new VersionCommitTag(VersionTag.Create("1.2.0-beta.2"), "target-hash"); + var baselineTag = new VersionCommitTag(VersionTag.Create("v1.1.0"), "prev-hash"); + var changelogLink = new WebLink("Full Changelog", "https://example.com/compare/v1.1.0...1.2.0-beta.2"); + return Task.FromResult(new BuildInformation(baselineTag, currentTag, [], [], [], changelogLink)); + } + } + + /// + /// Mock connector that simulates a first release with no baseline (no preceding tags). + /// + private sealed class FirstReleaseConnector : IRepoConnector + { + /// + public Task GetBuildInformationAsync(VersionTag? version = null) + { + var currentTag = new VersionCommitTag(VersionTag.Create("v1.0.0"), "first-hash"); + return Task.FromResult(new BuildInformation(null, currentTag, [], [], [], null)); + } + } } diff --git a/test/DemaConsulting.BuildMark.Tests/ProgramTests.cs b/test/DemaConsulting.BuildMark.Tests/ProgramTests.cs index da4505b..e7a4851 100644 --- a/test/DemaConsulting.BuildMark.Tests/ProgramTests.cs +++ b/test/DemaConsulting.BuildMark.Tests/ProgramTests.cs @@ -29,6 +29,7 @@ namespace DemaConsulting.BuildMark.Tests; /// /// Tests for the Program class. /// +[Collection("ProgramTests")] public class ProgramTests { /// @@ -465,3 +466,11 @@ public Task GetBuildInformationAsync(VersionTag? version = nul } } } + +/// +/// Disables parallel execution for the collection to prevent +/// race conditions when tests redirect the shared and +/// streams via Console.SetOut / Console.SetError. +/// +[CollectionDefinition("ProgramTests", DisableParallelization = true)] +public sealed class ProgramTestsCollection; diff --git a/test/DemaConsulting.BuildMark.Tests/RepoConnectors/RepoConnectorBaseTests.cs b/test/DemaConsulting.BuildMark.Tests/RepoConnectors/RepoConnectorBaseTests.cs index 6af56b6..d16454e 100644 --- a/test/DemaConsulting.BuildMark.Tests/RepoConnectors/RepoConnectorBaseTests.cs +++ b/test/DemaConsulting.BuildMark.Tests/RepoConnectors/RepoConnectorBaseTests.cs @@ -199,4 +199,132 @@ public void RepoConnectorBase_FindVersionIndex_VersionNotInList_ReturnsMinusOne( // Assert - Should return -1 when version is not found Assert.True(foundIndex == -1, "Should return -1 when the target version is not in the list"); } + + /// + /// Test that FindBaselineForPreRelease skips same-commit tags and returns the most recent + /// preceding tag that has a different commit hash. + /// + [Fact] + public void RepoConnectorBase_FindBaselineForPreRelease_SameCommitSkipped_ReturnsPreviousDistinctCommit() + { + // Arrange - three preceding tags; first two share the target's commit hash, third differs + const string targetHash = "aaaa"; + List precedingVersions = + [ + new(VersionTag.Create("v1.0.0"), "bbbb"), // different hash - oldest + new(VersionTag.Create("v2.0.0-beta.1"), targetHash), // same hash + new(VersionTag.Create("v2.0.0-beta.2"), targetHash) // same hash - newest + ]; + + // Act - find baseline for a pre-release with the shared hash + var baseline = RepoConnectorBase.FindBaselineForPreRelease(precedingVersions, targetHash); + + // Assert - returns the first tag with a different commit hash (v1.0.0), skipping same-hash entries + Assert.True(baseline != null, "Baseline should not be null when a different-commit predecessor exists"); + Assert.True( + baseline!.VersionTag.Tag == "v1.0.0", + $"Expected baseline 'v1.0.0' but got '{baseline.VersionTag.Tag}'"); + } + + /// + /// Test that FindBaselineForPreRelease returns null when all preceding tags share + /// the same commit hash as the target. + /// + [Fact] + public void RepoConnectorBase_FindBaselineForPreRelease_AllSameCommit_ReturnsNull() + { + // Arrange - all preceding tags share the same commit hash as the target + const string targetHash = "aaaa"; + List precedingVersions = + [ + new(VersionTag.Create("v1.0.0-rc.1"), targetHash), + new(VersionTag.Create("v1.0.0-rc.2"), targetHash) + ]; + + // Act - find baseline when all predecessors share the target hash + var baseline = RepoConnectorBase.FindBaselineForPreRelease(precedingVersions, targetHash); + + // Assert - returns null because no different-commit predecessor exists + Assert.True(baseline == null, "Baseline should be null when all predecessors share the same commit hash"); + } + + /// + /// Test that FindBaselineForRelease skips pre-release tags and returns the most recent + /// preceding release tag. + /// + [Fact] + public void RepoConnectorBase_FindBaselineForRelease_SkipsPreReleaseTags_ReturnsPreviousReleaseTag() + { + // Arrange - preceding tags include several pre-releases before the previous release + List precedingVersions = + [ + new(VersionTag.Create("v1.0.0"), "hash1"), // release - oldest + new(VersionTag.Create("v2.0.0-alpha.1"), "hash2"), // pre-release + new(VersionTag.Create("v2.0.0-beta.1"), "hash3"), // pre-release - newest + ]; + + // Act - find baseline for a release target (e.g., v2.0.0) + var baseline = RepoConnectorBase.FindBaselineForRelease(precedingVersions); + + // Assert - returns v1.0.0, the most recent non-pre-release tag + Assert.True(baseline != null, "Baseline should not be null when a preceding release tag exists"); + Assert.True( + baseline!.VersionTag.Tag == "v1.0.0", + $"Expected baseline 'v1.0.0' but got '{baseline.VersionTag.Tag}'"); + } + + /// + /// Test that FindBaselineForRelease returns null when only pre-release tags precede + /// the release target. + /// + [Fact] + public void RepoConnectorBase_FindBaselineForRelease_NoPreviousRelease_ReturnsNull() + { + // Arrange - only pre-release tags exist before the target release + List precedingVersions = + [ + new(VersionTag.Create("v1.0.0-alpha.1"), "hash1"), + new(VersionTag.Create("v1.0.0-beta.1"), "hash2") + ]; + + // Act - find baseline when no release tag precedes the target + var baseline = RepoConnectorBase.FindBaselineForRelease(precedingVersions); + + // Assert - returns null because no preceding release tag exists + Assert.True(baseline == null, "Baseline should be null when only pre-release tags precede the target"); + } + + /// + /// Test that FindBaselineForRelease returns null when the version list is empty + /// (first release scenario). + /// + [Fact] + public void RepoConnectorBase_FindBaselineForRelease_NoPreviousVersion_ReturnsNull() + { + // Arrange - empty preceding version list (first release in repository) + List precedingVersions = []; + + // Act - find baseline when no preceding versions exist + var baseline = RepoConnectorBase.FindBaselineForRelease(precedingVersions); + + // Assert - returns null because no preceding versions exist + Assert.True(baseline == null, "Baseline should be null when no preceding versions exist"); + } + + /// + /// Test that FindBaselineForPreRelease returns null when the version list is empty + /// (first pre-release scenario). + /// + [Fact] + public void RepoConnectorBase_FindBaselineForPreRelease_NoPreviousVersion_ReturnsNull() + { + // Arrange - empty preceding version list (first tag in repository) + List precedingVersions = []; + + // Act - find baseline when no preceding versions exist + var baseline = RepoConnectorBase.FindBaselineForPreRelease(precedingVersions, "aaaa"); + + // Assert - returns null because no preceding versions exist + Assert.True(baseline == null, "Baseline should be null when no preceding versions exist"); + } }