From 4b503a9e96e724adb6aa2624a385ac79afc187dd Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Tue, 26 May 2026 18:05:39 -0400 Subject: [PATCH 01/12] Apply formal review fixes from second review cycle (all 24 review-sets) - Add missing tags across all production and test source files - Fix ordering (must follow , before /) - Rename test methods to 4-part {Class}_{Method}_{Scenario}_{Behavior} convention - Update YAML tests: entries and verification doc scenarios to match renamed tests - Fix ||->&& logic bug in Spdx2JsonSerializer SerializeSnippet line-range guard - Decompose compound requirements into atomic requirements across multiple YAML files - Add missing tests: entries and justification: fields to requirements YAML files - Fix copy-paste errors in XML doc summaries and inline comments - Fix design/verification doc parameter name mismatches and inaccuracies - Add SpdxAnnotationType design section to spdx-annotation.md - Add structural deviation note to design introduction.md - Add SpdxHelpersTests.cs to folder inventory in introduction.md - Add atomicity guarantee documentation and test to SpdxRelationships - Add missing test coverage (ToText(Missing), unknown algorithm Validate branch, null boundary assertions, line-range boundary tests, annotation tests) - Fix verification scenario descriptions (FromText_Invalid throws, not returns Unknown) - Split multi-cycle SpdxDocument GetElement test into 4 focused tests - Add SpdxModel-Data-Helpers and SpdxModel-Data-Elements test links - Add spdx-constants.yaml to requirements.yaml includes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .cspell.yaml | 2 + .github/agents/developer.agent.md | 6 +- .github/agents/formal-review.agent.md | 2 + .github/agents/lint-fix.agent.md | 7 +- .github/agents/quality.agent.md | 59 +- .github/agents/repo-consistency.agent.md | 77 -- .github/agents/software-architect.agent.md | 2 +- .github/agents/template-sync.agent.md | 109 +++ .github/standards/coding-principles.md | 40 +- .github/standards/csharp-language.md | 61 +- .github/standards/csharp-testing.md | 129 ++-- .github/standards/design-documentation.md | 223 ++---- .github/standards/reqstream-usage.md | 120 +-- .github/standards/requirements-principles.md | 4 + .github/standards/reviewmark-usage.md | 132 +++- .github/standards/software-items.md | 86 ++- .github/standards/technical-documentation.md | 170 ++--- .github/standards/testing-principles.md | 5 - .../standards/verification-documentation.md | 101 +++ .markdownlint-cli2.yaml | 1 + .reviewmark.yaml | 27 + AGENTS.md | 72 +- README.md | 9 + docs/build_notes/definition.yaml | 8 +- docs/build_notes/introduction.md | 30 +- docs/build_notes/title.txt | 12 +- docs/code_quality/introduction.md | 32 +- docs/code_quality/title.txt | 8 +- docs/code_review_plan/definition.yaml | 6 +- docs/code_review_plan/introduction.md | 29 +- docs/code_review_plan/title.txt | 14 +- docs/code_review_report/definition.yaml | 6 +- docs/code_review_report/introduction.md | 29 +- docs/code_review_report/title.txt | 14 +- docs/design/definition.yaml | 2 +- docs/design/introduction.md | 151 ++-- docs/design/spdx-model.md | 123 ++++ docs/design/spdx-model/io/io.md | 79 +- .../spdx-model/io/spdx-2-json-deserializer.md | 85 ++- .../spdx-model/io/spdx-2-json-serializer.md | 85 ++- docs/design/spdx-model/io/spdx-constants.md | 35 +- docs/design/spdx-model/spdx-annotation.md | 133 +++- docs/design/spdx-model/spdx-checksum.md | 129 +++- .../spdx-model/spdx-creation-information.md | 78 +- docs/design/spdx-model/spdx-document.md | 157 ++-- docs/design/spdx-model/spdx-element.md | 50 +- .../spdx-external-document-reference.md | 74 +- .../spdx-model/spdx-external-reference.md | 114 ++- .../spdx-extracted-licensing-info.md | 75 +- docs/design/spdx-model/spdx-file.md | 141 +++- docs/design/spdx-model/spdx-helpers.md | 60 +- .../design/spdx-model/spdx-license-element.md | 74 +- docs/design/spdx-model/spdx-model.md | 192 +++-- .../spdx-package-verification-code.md | 59 +- docs/design/spdx-model/spdx-package.md | 150 +++- docs/design/spdx-model/spdx-relationship.md | 136 +++- docs/design/spdx-model/spdx-snippet.md | 93 ++- .../transform/spdx-relationships.md | 64 +- docs/design/spdx-model/transform/transform.md | 59 +- docs/design/title.txt | 11 +- docs/reqstream/ots/buildmark.yaml | 4 +- docs/reqstream/ots/fileassert.yaml | 4 +- docs/reqstream/ots/mstest.yaml | 4 +- docs/reqstream/ots/pandoc.yaml | 4 +- docs/reqstream/ots/reqstream.yaml | 4 +- docs/reqstream/ots/reviewmark.yaml | 4 +- docs/reqstream/ots/sarifmark.yaml | 4 +- docs/reqstream/ots/sonarmark.yaml | 4 +- docs/reqstream/ots/versionmark.yaml | 4 +- docs/reqstream/ots/weasyprint.yaml | 4 +- docs/reqstream/spdx-model/io/io.yaml | 63 +- .../io/spdx-2-json-deserializer.yaml | 117 +-- .../spdx-model/io/spdx-2-json-serializer.yaml | 95 +-- .../spdx-model/io/spdx-constants.yaml | 24 + .../spdx-model/platform-requirements.yaml | 146 ++-- .../reqstream/spdx-model/spdx-annotation.yaml | 50 +- docs/reqstream/spdx-model/spdx-checksum.yaml | 92 ++- .../spdx-model/spdx-creation-information.yaml | 43 +- docs/reqstream/spdx-model/spdx-document.yaml | 131 ++-- docs/reqstream/spdx-model/spdx-element.yaml | 33 +- .../spdx-external-document-reference.yaml | 39 +- .../spdx-model/spdx-external-reference.yaml | 50 +- .../spdx-extracted-licensing-info.yaml | 39 +- docs/reqstream/spdx-model/spdx-file.yaml | 48 +- docs/reqstream/spdx-model/spdx-helpers.yaml | 70 +- .../spdx-model/spdx-license-element.yaml | 90 ++- docs/reqstream/spdx-model/spdx-model.yaml | 71 +- .../spdx-package-verification-code.yaml | 61 +- docs/reqstream/spdx-model/spdx-package.yaml | 88 ++- .../spdx-model/spdx-relationship.yaml | 81 ++- docs/reqstream/spdx-model/spdx-snippet.yaml | 69 +- .../transform/spdx-relationships.yaml | 75 +- .../spdx-model/transform/transform.yaml | 36 +- docs/requirements_doc/definition.yaml | 8 +- docs/requirements_doc/introduction.md | 26 +- docs/requirements_doc/title.txt | 7 +- docs/requirements_report/definition.yaml | 6 +- docs/requirements_report/introduction.md | 26 +- docs/requirements_report/title.txt | 12 +- docs/user_guide/definition.yaml | 10 +- docs/user_guide/installation.md | 31 + docs/user_guide/introduction.md | 687 +----------------- docs/user_guide/title.txt | 15 +- docs/user_guide/troubleshooting.md | 59 ++ docs/user_guide/usage.md | 569 +++++++++++++++ docs/verification/definition.yaml | 32 + docs/verification/introduction.md | 38 + docs/verification/spdx-model.md | 56 ++ docs/verification/spdx-model/io/io.md | 34 + .../spdx-model/io/spdx-2-json-deserializer.md | 142 ++++ .../spdx-model/io/spdx-2-json-serializer.md | 120 +++ .../spdx-model/io/spdx-constants.md | 22 + .../spdx-model/spdx-annotation.md | 72 ++ docs/verification/spdx-model/spdx-checksum.md | 88 +++ .../spdx-model/spdx-creation-information.md | 63 ++ docs/verification/spdx-model/spdx-document.md | 92 +++ docs/verification/spdx-model/spdx-element.md | 27 + .../spdx-external-document-reference.md | 50 ++ .../spdx-model/spdx-external-reference.md | 68 ++ .../spdx-extracted-licensing-info.md | 48 ++ docs/verification/spdx-model/spdx-file.md | 65 ++ docs/verification/spdx-model/spdx-helpers.md | 48 ++ .../spdx-model/spdx-license-element.md | 21 + docs/verification/spdx-model/spdx-model.md | 56 ++ .../spdx-package-verification-code.md | 51 ++ docs/verification/spdx-model/spdx-package.md | 87 +++ .../spdx-model/spdx-relationship.md | 68 ++ docs/verification/spdx-model/spdx-snippet.md | 65 ++ .../transform/spdx-relationships.md | 38 + .../spdx-model/transform/transform.md | 50 ++ docs/verification/title.txt | 14 + requirements.yaml | 1 + .../IO/Spdx2JsonDeserializer.cs | 110 ++- .../IO/Spdx2JsonSerializer.cs | 120 ++- .../IO/SpdxConstants.cs | 100 +++ .../SpdxAnnotation.cs | 52 +- .../SpdxAnnotationType.cs | 27 +- src/DemaConsulting.SpdxModel/SpdxChecksum.cs | 30 + .../SpdxChecksumAlgorithm.cs | 56 +- .../SpdxCreationInformation.cs | 27 + src/DemaConsulting.SpdxModel/SpdxDocument.cs | 87 ++- src/DemaConsulting.SpdxModel/SpdxElement.cs | 26 +- .../SpdxExternalDocumentReference.cs | 43 +- .../SpdxExternalReference.cs | 32 + .../SpdxExtractedLicensingInfo.cs | 43 +- src/DemaConsulting.SpdxModel/SpdxFile.cs | 34 + src/DemaConsulting.SpdxModel/SpdxFileType.cs | 52 +- src/DemaConsulting.SpdxModel/SpdxHelpers.cs | 32 +- .../SpdxLicenseElement.cs | 13 + src/DemaConsulting.SpdxModel/SpdxPackage.cs | 49 +- .../SpdxPackageVerificationCode.cs | 22 +- .../SpdxReferenceCategory.cs | 52 +- .../SpdxRelationship.cs | 34 + .../SpdxRelationshipType.cs | 168 ++++- src/DemaConsulting.SpdxModel/SpdxSnippet.cs | 42 ++ .../Transform/SpdxRelationships.cs | 121 ++- .../IO/Spdx2JsonDeserialize22.cs | 8 +- .../IO/Spdx2JsonDeserialize23.cs | 8 +- .../IO/Spdx2JsonDeserializerTests.cs | 60 ++ .../IO/Spdx2JsonSerializeAnnotation.cs | 27 + .../IO/Spdx2JsonSerializeDocument.cs | 14 + .../IO/Spdx2JsonSerializeFile.cs | 6 +- .../IO/Spdx2JsonSerializeSnippet.cs | 105 +++ .../IO/SpdxModelIOTests.cs | 23 + .../SpdxAnnotationTests.cs | 33 +- .../SpdxChecksumTests.cs | 148 +++- .../SpdxCreationInformationTests.cs | 122 ++++ .../SpdxDocumentTests.cs | 70 +- .../SpdxElementTests.cs | 68 ++ .../SpdxExternalDocumentReferenceTests.cs | 30 +- .../SpdxExternalReferenceTests.cs | 11 +- .../SpdxExtractedLicensingInfoTests.cs | 54 ++ .../SpdxFileTests.cs | 114 ++- .../SpdxHelpersTests.cs | 123 ++++ .../SpdxModelTests.cs | 86 +++ .../SpdxPackageTests.cs | 160 +++- .../SpdxPackageVerificationCodeTests.cs | 86 ++- .../SpdxRelationshipTests.cs | 96 ++- .../SpdxSnippetTests.cs | 192 ++++- .../Transforms/SpdxModelTransformTests.cs | 227 ++++++ .../Transforms/SpdxRelationshipsTests.cs | 127 +++- 181 files changed, 9122 insertions(+), 2912 deletions(-) delete mode 100644 .github/agents/repo-consistency.agent.md create mode 100644 .github/agents/template-sync.agent.md create mode 100644 .github/standards/verification-documentation.md create mode 100644 docs/design/spdx-model.md create mode 100644 docs/reqstream/spdx-model/io/spdx-constants.yaml create mode 100644 docs/user_guide/installation.md create mode 100644 docs/user_guide/troubleshooting.md create mode 100644 docs/user_guide/usage.md create mode 100644 docs/verification/definition.yaml create mode 100644 docs/verification/introduction.md create mode 100644 docs/verification/spdx-model.md create mode 100644 docs/verification/spdx-model/io/io.md create mode 100644 docs/verification/spdx-model/io/spdx-2-json-deserializer.md create mode 100644 docs/verification/spdx-model/io/spdx-2-json-serializer.md create mode 100644 docs/verification/spdx-model/io/spdx-constants.md create mode 100644 docs/verification/spdx-model/spdx-annotation.md create mode 100644 docs/verification/spdx-model/spdx-checksum.md create mode 100644 docs/verification/spdx-model/spdx-creation-information.md create mode 100644 docs/verification/spdx-model/spdx-document.md create mode 100644 docs/verification/spdx-model/spdx-element.md create mode 100644 docs/verification/spdx-model/spdx-external-document-reference.md create mode 100644 docs/verification/spdx-model/spdx-external-reference.md create mode 100644 docs/verification/spdx-model/spdx-extracted-licensing-info.md create mode 100644 docs/verification/spdx-model/spdx-file.md create mode 100644 docs/verification/spdx-model/spdx-helpers.md create mode 100644 docs/verification/spdx-model/spdx-license-element.md create mode 100644 docs/verification/spdx-model/spdx-model.md create mode 100644 docs/verification/spdx-model/spdx-package-verification-code.md create mode 100644 docs/verification/spdx-model/spdx-package.md create mode 100644 docs/verification/spdx-model/spdx-relationship.md create mode 100644 docs/verification/spdx-model/spdx-snippet.md create mode 100644 docs/verification/spdx-model/transform/spdx-relationships.md create mode 100644 docs/verification/spdx-model/transform/transform.md create mode 100644 docs/verification/title.txt create mode 100644 test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs create mode 100644 test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs create mode 100644 test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs diff --git a/.cspell.yaml b/.cspell.yaml index cdf4235..8e90699 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -31,6 +31,8 @@ words: - NTIA - opencover - pandoc + - Postconditions + - Preconditions - Protecode - reqstream - reviewmark diff --git a/.github/agents/developer.agent.md b/.github/agents/developer.agent.md index 35f5dda..5f208eb 100644 --- a/.github/agents/developer.agent.md +++ b/.github/agents/developer.agent.md @@ -14,6 +14,10 @@ Perform software development tasks by determining and applying appropriate stand 2. **Read relevant standards** using the selection matrix in AGENTS.md 3. **Pre-flight verification** before making any changes: - List files that will be created, modified, or deleted + - For each file to be **created**, check whether a counterpart exists in the + template (URL in the `# Reference Template` section of `AGENTS.md`). + If one exists, fetch it as the starting point; adjust placeholder names and heading + depth to match the target path before writing the file - For each modified file, identify which companion artifacts need updating (requirements, design docs, tests, review-sets) - Include companion artifact updates in the work plan @@ -21,7 +25,7 @@ Perform software development tasks by determining and applying appropriate stand 5. **Formatting**: Run `pwsh ./fix.ps1` to silently apply all available auto-fixers (dotnet format, markdown, YAML) before committing 6. **Build and test** (code changes only): Run `pwsh ./build.ps1` and confirm it - passes — report FAILED if the build or any tests fail + passes - report FAILED if the build or any tests fail 7. **Generate completion report** per the AGENTS.md reporting requirements - save to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` and return the summary to the caller diff --git a/.github/agents/formal-review.agent.md b/.github/agents/formal-review.agent.md index 88b0691..7dd8e84 100644 --- a/.github/agents/formal-review.agent.md +++ b/.github/agents/formal-review.agent.md @@ -20,6 +20,8 @@ Before reviewing, read these standards to inform review judgments: hierarchy and categorization review judgments - **`design-documentation.md`** - defines mandatory sections, structural conventions, and coverage expected at each level; informs all design documentation review judgments +- **`verification-documentation.md`** - defines mandatory sections, structural conventions, + and coverage expected at each level; informs all verification design review judgments For review sets that include source code or tests, also consult the relevant standards from the selection matrix in AGENTS.md. diff --git a/.github/agents/lint-fix.agent.md b/.github/agents/lint-fix.agent.md index 83ad8cb..549e751 100644 --- a/.github/agents/lint-fix.agent.md +++ b/.github/agents/lint-fix.agent.md @@ -36,7 +36,12 @@ submission, not during normal development. - **markdownlint MD013 (line length)**: Wrap long lines at natural break points, after commas, before conjunctions, or at sentence boundaries. Do not break - in the middle of a code span or URL. + in the middle of a code span or URL. **Pipe-tables that cannot be wrapped + without breaking structure** are a special case - convert them to a bullet + list if the data reads naturally that way, or rewrite as a + [grid table](https://pandoc.org/MANUAL.html#tables) if a tabular layout is + essential. Do not get stuck trying to squeeze a wide pipe-table into 120 + characters. - **markdownlint other rules**: Apply the specific fix indicated in the output (e.g., missing blank lines, heading levels, code fence languages). diff --git a/.github/agents/quality.agent.md b/.github/agents/quality.agent.md index da467d4..380d11f 100644 --- a/.github/agents/quality.agent.md +++ b/.github/agents/quality.agent.md @@ -54,21 +54,13 @@ Priority-ordered list of issues that MUST be resolved for the next retry: ## Requirements Compliance: (PASS|FAIL|N/A) -- Were requirements updated to reflect functional changes? -- Were new requirements created for new features? -- Do requirement IDs follow semantic naming standards? -- Do requirement files follow kebab-case naming convention? -- Are requirement files organized under `docs/reqstream/` with proper folder structure? -- Are OTS requirements properly placed in `docs/reqstream/ots/` subfolder? -- Were source filters applied appropriately for platform-specific requirements? +- Were requirements created/updated for all functional changes? +- Were source filters applied for platform-specific requirements? - Is requirements traceability maintained to tests? ## Design Documentation Compliance: (PASS|FAIL|N/A) -- Were design documents updated for architectural changes? -- Were new design artifacts created for new components? -- Do design folder names use kebab-case convention matching source structure? -- Are design files properly named ({subsystem-name}.md, {unit-name}.md patterns)? +- Were design artifacts created/updated for all new or changed components? - Is `docs/design/introduction.md` present with required Software Structure section? - Are design decisions documented with rationale? - Is system/subsystem/unit categorization maintained? @@ -76,55 +68,50 @@ Priority-ordered list of issues that MUST be resolved for the next retry: ## Code Quality Compliance: (PASS|FAIL|N/A) -- Are language-specific standards followed (from applicable standards files)? -- Are quality checks from standards files satisfied? -- Is code properly categorized (system/subsystem/unit/OTS)? -- Is appropriate separation of concerns maintained? -- Was language-specific build tooling executed and passing? +- Do language-specific quality checks from loaded standards pass? +- Is code properly categorized (system/subsystem/unit/OTS/Shared Package)? +- Does the build pass? ## Testing Compliance: (PASS|FAIL|N/A) - Were tests created/updated for all functional changes? - Is test coverage maintained for all requirements? -- Are testing standards followed (AAA pattern, etc.)? -- Do tests respect software item hierarchy boundaries (System/Subsystem/Unit scope)? +- Do tests respect software item hierarchy boundaries? - Are cross-hierarchy test dependencies documented in design docs? -- Does test categorization align with code structure? -- Do all tests pass without failures? +- Do all tests pass? ## Review Management Compliance: (PASS|FAIL|N/A) -- Were review-sets updated for structural changes (new/deleted systems, subsystems, or units)? -- Do file patterns follow include-then-exclude approach? +- Were review-sets updated for structural changes? - Is review scope appropriate for change magnitude? -- Was ReviewMark tooling executed and passing? -- Were review artifacts generated correctly? +- Does ReviewMark pass? ## Documentation Compliance: (PASS|FAIL|N/A) -- Was README.md updated for user-facing changes? -- Were user guides updated for feature changes? +- Were README.md and user guides updated for user-facing changes? - Does API documentation reflect code changes? - Was compliance documentation generated? -- Does documentation follow standards formatting? -- Is documentation organized under `docs/` following standard folder structure? -- Do Pandoc collections include proper `introduction.md` with Purpose and Scope sections? - Are auto-generated markdown files left unmodified? -- Do README.md files use absolute URLs and include concrete examples? -- Is documentation integrated into ReviewMark review-sets for formal review? +- Is documentation integrated into ReviewMark review-sets? ## Software Item Completeness: (PASS|FAIL|N/A) +- Load `software-items.md` before evaluating this section. + - Does every identified software unit have its own requirements file? - Does every identified software unit have its own design document? - Does every identified subsystem have its own requirements file? - Does every identified subsystem have its own design document? +## Repository Structure Compliance: (PASS|FAIL|N/A) + +- Load `repository-map.md` from the template URL in the `# Reference Template` + section of `AGENTS.md` before evaluating this section. + +- Are parallel artifact trees in sync (reqstream/design/verification/src/test)? +- Does the repository conform to the template `repository-map.md`? + ## Process Compliance: (PASS|FAIL|N/A) -- Was Continuous Compliance workflow followed? -- Did all quality gates execute successfully? -- Were appropriate tools used for validation? -- Were standards consistently applied across work? -- Was compliance evidence generated and preserved? +- Was compliance evidence (test results, review artifacts, generated docs) generated and preserved? ``` diff --git a/.github/agents/repo-consistency.agent.md b/.github/agents/repo-consistency.agent.md deleted file mode 100644 index 0b66277..0000000 --- a/.github/agents/repo-consistency.agent.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -name: repo-consistency -description: > - Ensures downstream repositories remain consistent with the TemplateDotNetLibrary - template patterns and best practices. -user-invocable: true ---- - -# Repo Consistency Agent - -Maintain consistency between downstream projects and the TemplateDotNetLibrary template, ensuring repositories -benefit from template evolution while respecting project-specific customizations. - -# Consistency Workflow (MANDATORY) - -**CRITICAL**: This agent MUST follow these steps systematically to ensure proper template consistency analysis: - -1. **Fetch Recent Template Changes**: Use GitHub search to fetch the 20 most recently merged PRs - (`is:pr is:merged sort:updated-desc`) from -2. **Analyze Template Evolution**: For each relevant PR, determine the intent and scope of changes - (what files were modified, what improvements were made) -3. **Assess Downstream Applicability**: Evaluate which template changes would benefit this repository - while respecting project-specific customizations -4. **Apply Appropriate Updates**: Implement applicable template improvements with proper translation for project context -5. **Validate Consistency**: Verify that applied changes maintain functionality and follow project patterns -6. **Generate completion report** per the AGENTS.md reporting requirements - save to - `.agent-logs/{agent-name}-{subject}-{unique-id}.md` and return the summary to the caller - -## Key Principles - -- **Evolutionary Consistency**: Template improvements should enhance downstream projects systematically -- **Intelligent Customization Respect**: Distinguish valid customizations from unintentional drift -- **Incremental Template Adoption**: Support phased adoption of template improvements based on project capacity - -# Don't Do These Things - -- **Never recommend changes without understanding project context** (some differences are intentional) -- **Never flag valid project-specific customizations** as consistency problems -- **Never apply template changes blindly** without assessing downstream project impact -- **Never ignore template evolution benefits** when they clearly improve downstream projects -- **Never recommend breaking changes** without migration guidance and impact assessment -- **Never skip validation** of preserved functionality after template alignment -- **Never assume all template patterns apply universally** (assess project-specific needs) - -# Report Template - -```markdown -# Repo Consistency Report - -**Result**: (SUCCEEDED|FAILED) - -## Consistency Analysis - -- **Template PRs Analyzed**: {Number and timeframe of PRs reviewed} -- **Template Changes Identified**: {Count and types of template improvements} -- **Applicable Updates**: {Changes determined suitable for this repository} -- **Project Customizations Preserved**: {Valid differences maintained} - -## Template Evolution Applied - -- **Files Modified**: {List of files updated for template consistency} -- **Improvements Adopted**: {Specific template enhancements implemented} -- **Configuration Updates**: {Tool configurations, workflows, or standards updated} - -## Consistency Status - -- **Template Alignment**: {Overall consistency rating with template} -- **Customization Respect**: {How project-specific needs were preserved} -- **Functionality Validation**: {Verification that changes don't break existing features} -- **Future Consistency**: {Recommendations for ongoing template alignment} - -## Issues Resolved - -- **Drift Corrections**: {Template drift issues addressed} -- **Enhancement Adoptions**: {Template improvements successfully integrated} -- **Validation Results**: {Testing and validation outcomes} -``` diff --git a/.github/agents/software-architect.agent.md b/.github/agents/software-architect.agent.md index 494568d..de5efa2 100644 --- a/.github/agents/software-architect.agent.md +++ b/.github/agents/software-architect.agent.md @@ -13,7 +13,7 @@ Interview the user and produce evolving architecture documentation with prioriti # Standards Read `.github/standards/software-items.md` before starting. Use its definitions -(Software Package, System, Subsystem, Unit, OTS) as vocabulary throughout. +(Software Package, System, Subsystem, Unit, OTS, Shared Package) as vocabulary throughout. # Approach diff --git a/.github/agents/template-sync.agent.md b/.github/agents/template-sync.agent.md new file mode 100644 index 0000000..c013503 --- /dev/null +++ b/.github/agents/template-sync.agent.md @@ -0,0 +1,109 @@ +--- +name: template-sync +description: Audits or synchronizes repository files against the canonical template. + Supports four modes - Audit, Sync, Scaffold, and Recreate. +user-invocable: true +--- + +# Template Sync Agent + +This agent is an orchestrator supporting four modes: + +- **Audit** - report structural deviations; no changes +- **Sync** - patch missing sections into existing files +- **Scaffold** - create files that do not yet exist; skip existing files +- **Recreate** - rebuild existing files from the template, migrating old content + +Read the template URL and `repository-map.md` from the `# Reference Template` +section in `AGENTS.md`, then map the requested scope onto the work groups below. +Delegate each group to a sub-agent. + +# Work Groups + +- **Root config files** - all non-collection files at the repository root +- **One group per flat `docs/` folder** - e.g. `docs/build_notes/`, `docs/user_guide/` +- **One group per system subtree** in `docs/design/`, `docs/verification/`, `docs/reqstream/` - + each subtree and all its descendants is one group + +# Orchestration + +For each group intersecting the requested scope, call a sub-agent with: + +- **context**: + - Group scope and template URL from the `# Reference Template` section in `AGENTS.md` + - Applicable standards from the `# Standards Application` matrix in `AGENTS.md` + for the file types in the group scope + - Project-specific names substitute for placeholders at matching path depth + (e.g. `MySystem` → `{SystemName}`, `my-system` → `{system-name}`) + - For files within `{system-name}/` subtrees in `docs/design/`, `docs/verification/`, + and `docs/reqstream/`: consult `docs/design/introduction.md` to determine whether + each item is a subsystem or unit, then select the appropriate template + (`subsystem-name.*` or `unit-name.*`) regardless of the item's folder depth — + do not infer item type from path depth alone + - If a template counterpart cannot be fetched, skip the file and report it +- **goal**: + - Based on the given mode: + - **Audit** - fetch each template counterpart; compare headings; report missing + sections and depth mismatches; no changes + - **Sync** - as Audit, then insert each missing section; run `pwsh ./fix.ps1` + - **Scaffold** - fetch `repository-map.md` from the template URL in `AGENTS.md` + to identify files that should exist but don't; for each, fetch the template, + populate all sections, write the file; run `pwsh ./fix.ps1` + - **Recreate** - fetch the template and use it as the blueprint for a + freshly authored document: + - Work through the template section by section; for each section, find + any `TEMPLATE-DIRECTIVE` blocks (both `` + in markdown and `# ` in YAML) — execute + each directive (read specified standards, apply structural guidance, + substitute content), then **remove the directive block entirely** from + the output; gather the relevant technical details from all available + sources — the old file, README, related docs, sibling files, and any + other repo context — to populate that section correctly; the old file's + structure and headings are irrelevant; only its factual content is mined + as a source + - **Gap-check**: after all template sections are filled, scan the old + file once more for any technical information not yet captured; if + found, preserve it by appending new relevant sections at the end + - **Before writing**: do a mandatory self-check — for every section that + has a `TEMPLATE-DIRECTIVE` block in the template, explicitly state what + format the directive requires, then verify the drafted content matches + that format exactly (e.g. if the directive says "no sub-headings", + confirm there are no `###` headings inside that section; if it says + "bold-name paragraph blocks", confirm each entry is `**Name**: prose` + with no sub-heading); fix any mismatches before writing the file + - Write the rebuilt file; run `pwsh ./fix.ps1` + - When writing any section: `TEMPLATE-DIRECTIVE` blocks are directives — + execute them (read specified standards, apply structural guidance, substitute + content) and **remove the block entirely** from the written file; inline + `TODO:` placeholders in YAML string values (e.g. `title:`, `justification:`) + are content placeholders — always resolve them to real content; infer from + README, related files, sibling docs, and path; if confident write directly; + if ambiguous offer 2–3 concrete options and ask the user; keep asking until + they answer - never leave a TODO or TEMPLATE-DIRECTIVE in the output unless + the user explicitly requests it + +Collect sub-agent results and assemble the final report. + +# Report Template + +```markdown +# Template Sync Report + +**Result**: (SUCCEEDED|FAILED) +**Mode**: (Audit|Sync|Scaffold|Recreate) + +## Files + +### {file-path} + +- **Template**: {template path} +- **Missing sections**: {list or "none"} +- **Heading depth issues**: {list or "none"} +- **Content format issues**: {list of sections where intra-section content did not + match the template comment's prescribed format, or "none"} *(Recreate only)* +- **Action**: (Reported | Sections added | Created | Rebuilt | No template found) + +## Summary + +- **Conformant**: {count} | **Deviations**: {count} | **Updated**: {count} +``` diff --git a/.github/standards/coding-principles.md b/.github/standards/coding-principles.md index 213c031..6797c61 100644 --- a/.github/standards/coding-principles.md +++ b/.github/standards/coding-principles.md @@ -3,11 +3,6 @@ name: Coding Principles description: Follow these standards when developing any software code. --- -# Coding Principles Standards - -This document defines universal coding principles and quality standards for software development within -Continuous Compliance environments. - # Core Principles ## Literate Coding @@ -20,11 +15,34 @@ All code MUST follow literate programming principles: matches design intent without reading the full codebase - **Logical Separation**: Complex functions use block comments to separate and describe logical steps within the implementation -- **Public Documentation**: All public interfaces have comprehensive documentation - because consumers and auditors rely on interface contracts for integration - and compliance verification +- **Full Symbol Documentation**: ALL symbols have comprehensive documentation — + not just the public interface, because reviewers and auditors must verify every + implementation detail. Access-level specifics vary by language; see the language-specific standard. - **Clarity Over Cleverness**: Code should be immediately understandable by team members +## API Documentation + +Good API documentation enables consumers, reviewers, and agents to use an +interface correctly without reading the implementation: + +- **Self-Contained**: Each member's documentation must be fully understandable + in isolation - consumers must not need to read the implementation to call it + correctly +- **Intent-Focused**: Explain WHY the member exists and WHAT problem it solves, + not just restate the name - this lets reviewers verify the implementation + matches design intent +- **Parameter and Return Contracts**: Document valid ranges, null handling, and + boundary cases - agents and consumers rely on these contracts to call the API + correctly +- **Error Conditions**: Document every exception or error code, the condition + that triggers it, and how the caller should respond - undocumented errors + cannot be handled correctly +- **Side Effects**: Document I/O, state mutation, resource allocation, or + network calls - hidden side effects cause integration bugs that are hard to + diagnose +- **Thread Safety**: State whether the API is safe for concurrent use - missing + this forces consumers to read the implementation or risk data races + ## Universal Code Architecture Principles ### Design Patterns @@ -55,13 +73,13 @@ All code MUST follow literate programming principles: ## Universal Anti-Patterns -- **Skip Literate Coding**: Don't skip literate programming comments - they are required for maintainability -- **Ignore Compiler Warnings**: Don't ignore compiler warnings - they exist for quality enforcement +- **Skip Literate Coding**: Don't skip literate programming comments +- **Ignore Compiler Warnings**: Don't ignore compiler warnings - **Hidden Dependencies**: Don't create untestable code with hidden dependencies - **Hidden Functionality**: Don't implement functionality without requirement traceability because untraced functionality cannot be validated during audits - **Monolithic Functions**: Don't write monolithic functions with multiple responsibilities -- **Overcomplicated Solutions**: Don't make solutions more complex than necessary - favor simplicity and clarity +- **Overcomplicated Solutions**: Don't make solutions more complex than necessary - **Premature Optimization**: Don't optimize for performance before establishing correctness - **Copy-Paste Programming**: Don't duplicate logic - extract common functionality into reusable components - **Magic Numbers**: Don't use unexplained constants - either name them or add clear comments diff --git a/.github/standards/csharp-language.md b/.github/standards/csharp-language.md index 707b0f9..ec05a25 100644 --- a/.github/standards/csharp-language.md +++ b/.github/standards/csharp-language.md @@ -4,37 +4,60 @@ description: Follow these standards when developing C# source code. globs: ["**/*.cs"] --- -# C# Language Development Standard - -## Required Standards +# Required Standards Read these standards first before applying this standard: - **`coding-principles.md`** - Universal coding principles and quality gates -# File Patterns - -- **Source Files**: `**/*.cs` - -# Literate Coding Example +# API Documentation and Literate Coding Example ```csharp -// Validate input parameters to prevent downstream errors -if (string.IsNullOrEmpty(input)) +/// +/// Converts a raw sensor reading into a validated measurement ready for downstream consumers. +/// +/// +/// Clamping is preferred over throwing on out-of-range values because sensor drift at +/// range boundaries is expected; clamping produces a usable result where rejection would +/// discard valid near-boundary readings. Stateless and thread-safe; the calibration +/// profile is read but never modified. +/// +/// Raw sensor value. Must be finite (NaN and infinities are rejected). +/// Calibration profile providing offset and range. Must not be null. +/// Corrected value clamped to [calibration.Minimum, calibration.Maximum]. +/// Thrown when is NaN or infinite. +/// Thrown when is null. +public double ProcessReading(double reading, CalibrationProfile calibration) { - throw new ArgumentException("Input cannot be null or empty", nameof(input)); -} + // Reject invalid inputs before any calculation - non-finite readings cannot be + // corrected, and a null calibration profile provides no offset or range to apply + if (!double.IsFinite(reading)) + throw new ArgumentException("Reading must be a finite number.", nameof(reading)); + ArgumentNullException.ThrowIfNull(calibration); -// Transform input data using the configured processing pipeline -var processedData = ProcessingPipeline.Transform(input); + // Apply the calibration offset to convert raw counts to physical units + var corrected = reading + calibration.Offset; -// Apply business rules and validation logic -var validatedResults = BusinessRuleEngine.ValidateAndProcess(processedData); - -// Return formatted results matching the expected output contract -return OutputFormatter.Format(validatedResults); + // Clamp to the operational range so consumers can rely on the documented contract + return Math.Clamp(corrected, calibration.Minimum, calibration.Maximum); +} ``` +Key qualities demonstrated above: + +- **``** is a brief one-liner explaining *what* the method does +- **``** sits directly after summary and carries the extended intent - + *why* it exists, design decisions, thread-safety, and side-effect disclosures +- **`` tags** state constraints (finite, non-null) so callers know what + is valid without reading the body +- **``** documents the boundary guarantee so consumers can rely on the + contract +- **`` tags** name every thrown exception and the condition that + triggers each one +- **Inline block comments** follow the Literate Coding principles from + `coding-principles.md`, separating logical steps so reviewers can verify each + step against design intent + # Code Formatting - **Format entire solution**: `dotnet format` diff --git a/.github/standards/csharp-testing.md b/.github/standards/csharp-testing.md index 1591eeb..e6970ab 100644 --- a/.github/standards/csharp-testing.md +++ b/.github/standards/csharp-testing.md @@ -4,115 +4,82 @@ description: Follow these standards when developing C# tests. globs: ["**/test/**/*.cs", "**/tests/**/*.cs", "**/*Tests.cs", "**/*Test.cs"] --- -# C# Testing Standards (MSTest) - -This document defines standards for C# test development using -MSTest within Continuous Compliance environments. - -## Required Standards +# Required Standards Read these standards first before applying this standard: - **`testing-principles.md`** - Universal testing principles and dependency boundaries - **`csharp-language.md`** - C# language development standards -# C# AAA Pattern Implementation +# Package Reference -```csharp -[TestMethod] -public void ServiceName_MethodName_Scenario_ExpectedBehavior() -{ - // Arrange: description of setup (omit if nothing to set up) +Every xUnit v3 test project requires the following package references for +`dotnet test` to discover and execute tests: - // Act: description of action (can combine with Assert when action occurs within assertion) +| Package | Purpose | +| ------- | ------- | +| `xunit.v3` | xUnit v3 framework (monolithic - includes assertions and fixtures) | +| `Microsoft.NET.Test.Sdk` | Required by the VSTest/`dotnet test` host for test discovery | +| `xunit.runner.visualstudio` | VSTest adapter that bridges xUnit v3 to `dotnet test` | - // Assert: description of verification -} -``` +Omitting `Microsoft.NET.Test.Sdk` or `xunit.runner.visualstudio` causes tests +to be silently undiscoverable by `dotnet test`. -# Test Naming Standards +If tests require mocking of dependencies, add `NSubstitute` as a package +reference - it is recommended when mocking is needed but is not required for +every test project. -Use descriptive test names because test names appear in requirements traceability matrices and compliance reports. +# Test Style + +Test names appear in requirements traceability matrices - use the hierarchical +naming pattern, and follow AAA with labeled comments: - **System tests**: `{SystemName}_{Functionality}_{Scenario}_{ExpectedBehavior}` - **Subsystem tests**: `{SubsystemName}_{Functionality}_{Scenario}_{ExpectedBehavior}` - **Unit tests**: `{ClassName}_{MethodUnderTest}_{Scenario}_{ExpectedBehavior}` -- **Descriptive Scenarios**: Clearly describe the input condition being tested -- **Expected Behavior**: State the expected outcome or exception - -## Examples - -- `UserValidator_ValidateEmail_ValidFormat_ReturnsTrue` -- `UserValidator_ValidateEmail_InvalidFormat_ThrowsArgumentException` -- `PaymentProcessor_ProcessPayment_InsufficientFunds_ReturnsFailureResult` - -# Mock Dependencies - -Mock external dependencies using NSubstitute (preferred) because tests must run in isolation to generate -reliable evidence. - -- **Isolate System Under Test**: Mock all external dependencies (databases, web services, file systems) -- **Verify Interactions**: Assert that expected method calls occurred with correct parameters -- **Predictable Behavior**: Set up mocks to return known values for consistent test results - -# MSTest V4 Anti-patterns - -Avoid these common MSTest V4 patterns because they produce poor error messages or cause tests to be silently ignored. - -# Avoid Assertions in Catch Blocks (MSTEST0058) - -Instead of wrapping code in try/catch and asserting in the catch block, use `Assert.ThrowsExactly()`: ```csharp -var ex = Assert.ThrowsExactly(() => SomeWork()); -Assert.Contains("Some message", ex.Message); -``` - -# Avoid Assert.IsTrue/IsFalse for Equality Checks - -Use `Assert.AreEqual`/`Assert.AreNotEqual` instead, as they provide better failure messages: - -```csharp -// ❌ Bad: Assert.IsTrue(result == expected); -// ✅ Good: Assert.AreEqual(expected, result); -``` - -# Avoid Non-Public Test Classes and Methods - -Test classes and `[TestMethod]` methods must be `public` or they will be silently ignored: +/// +/// Validates that an invalid email format throws an ArgumentException. +/// +[Fact] +public void UserValidator_ValidateEmail_InvalidFormat_ThrowsArgumentException() +{ + // Arrange: create a validator with default configuration + var validator = new UserValidator(); -```csharp -// ❌ Bad: internal class MyTests -// ✅ Good: public class MyTests + // Act / Assert: email with no domain throws + Assert.Throws(() => validator.ValidateEmail("not-an-email")); +} ``` -# Avoid Assert.IsTrue for Collection Count +# xUnit v3 Specifics -Use `Assert.HasCount` for count assertions: +These are non-obvious v3 behaviors that differ from v2 or common assumptions: -```csharp -// ❌ Bad: Assert.IsTrue(collection.Count == 3); -// ✅ Good: Assert.HasCount(3, collection); -``` +- **`IAsyncLifetime`**: Both `InitializeAsync` and `DisposeAsync` return `ValueTask` + in v3, not `Task` - using `Task` compiles but does not satisfy the v3 interface +- **`Assert.Multiple`**: Use to collect all assertion failures in a single test + rather than stopping at the first +- **`[Collection]` without `[CollectionDefinition]`**: Silently disables parallelism + without providing any shared fixture - always pair them or remove `[Collection]` -# Avoid Assert.IsTrue for String Prefix Checks +# Repository-Specific Exceptions -Use `Assert.StartsWith` instead, as it produces clearer failure messages: +## MSTest Approval (SpdxModel) -```csharp -// ❌ Bad: Assert.IsTrue(value.StartsWith("prefix")); -// ✅ Good: Assert.StartsWith("prefix", value); -``` +The `DemaConsulting.SpdxModel.Tests` project uses **MSTest** instead of xUnit v3. This is +an approved exception for this repository: the test project predates this standard and a +full migration to xUnit v3 would be high risk with low value. MSTest remains the approved +test framework for this repository's existing test suite. New test projects in this repository +should follow the xUnit v3 standard unless this section is updated. # Quality Checks -Before submitting C# tests, verify: - - [ ] All tests follow AAA pattern with clear section comments -- [ ] Test names follow hierarchical patterns defined in Test Naming Standards section -- [ ] Each test verifies single, specific behavior (no shared state) +- [ ] Test names follow hierarchical naming pattern above +- [ ] Each test verifies single, specific behavior (no shared state between tests) - [ ] Both success and failure scenarios covered including edge cases -- [ ] External dependencies mocked with NSubstitute or equivalent +- [ ] External dependencies mocked with NSubstitute (when mocking is needed) - [ ] Tests linked to requirements with source filters where needed -- [ ] Test results generate TRX format for ReqStream compatibility -- [ ] MSTest V4 anti-patterns avoided (proper assertions, public visibility, etc.) +- [ ] Test results generated in TRX format for ReqStream compatibility (`dotnet test --logger trx`) diff --git a/.github/standards/design-documentation.md b/.github/standards/design-documentation.md index 30becb5..e5b7bf9 100644 --- a/.github/standards/design-documentation.md +++ b/.github/standards/design-documentation.md @@ -4,185 +4,112 @@ description: Follow these standards when creating design documentation. globs: ["docs/design/**/*.md"] --- -# Design Documentation Standards - -This document defines standards for design documentation within Continuous -Compliance environments, extending the general technical documentation -standards with specific requirements for software design artifacts. - -## Required Standards - -Read these standards first before applying this standard: +# Required Standards - **`technical-documentation.md`** - General technical documentation standards -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) - -# Core Principles - -Design documentation serves as the bridge between requirements and -implementation, providing detailed technical specifications that enable: - -- **Formal Code Review**: Reviewers can verify implementation matches design -- **Compliance Evidence**: Auditors can trace requirements through design to code -- **Maintenance Support**: Developers can understand system structure and interactions -- **Quality Assurance**: Testing teams can validate against detailed specifications +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) -# Required Structure and Documents - -Design documentation must be organized under `docs/design/` with folder structure -mirroring source code organization because reviewers need clear navigation from -design to implementation: +# Folder Structure ```text docs/design/ -├── introduction.md # Design overview with software structure -└── {system-name}/ # System-level design folder (one per system) - ├── {system-name}.md # System-level design documentation - ├── {subsystem-name}/ # Subsystem (kebab-case); may nest recursively - │ ├── {subsystem-name}.md # Subsystem overview and design - │ ├── {child-subsystem}/ # Child subsystem (same structure as parent) - │ └── {unit-name}.md # Unit-level design documents - └── {unit-name}.md # Top-level unit design documents (if not in subsystem) +├── introduction.md # heading depth # +├── {system-name}.md # heading depth # +├── {system-name}/ +│ ├── {subsystem-name}.md # heading depth ## +│ ├── {subsystem-name}/ +│ │ └── {unit-name}.md # heading depth ### +│ └── {unit-name}.md # heading depth ## +├── ots.md # heading depth # (if OTS items exist) +├── ots/ +│ └── {ots-name}.md # heading depth ## +├── shared.md # heading depth # (if Shared Packages exist) +└── shared/ + └── {package-name}.md # heading depth ## ``` -## introduction.md (MANDATORY) +All sections in every file are mandatory; write "N/A - {justification}" rather than removing any. +Determine subsystem vs. unit classification from `docs/design/introduction.md` — folder depth does not determine classification. +Do not record version numbers anywhere in design documentation — version information is managed in SBOMs. -The `introduction.md` file serves as the design entry point and MUST include -these sections because auditors need clear scope boundaries and architectural -overview: +# introduction.md (MANDATORY) -### Purpose Section +Must include: -Clear statement of the design document's purpose, audience, and regulatory -or compliance drivers. +- **Purpose**: audience and compliance drivers +- **Scope**: items covered and explicitly excluded (no test projects) +- **Software Structure**: text tree showing all Systems/Subsystems/Units/OTS/Shared items +- **Folder Layout**: text tree showing source folder structure +- **Companion Artifact Structure**: parallel paths for requirements, design, verification, source, tests +- **References** _(if applicable)_: external standards or specifications - only in `introduction.md` -### Scope Section +# System Design (MANDATORY) -Define what software items are covered and what is explicitly excluded. -Design documentation must NOT include test projects, test classes, or test -infrastructure because design documentation documents the architecture of -shipping product code, not ancillary content used to validate it. +Create `{system-name}.md` (`#` heading) and `{system-name}/` folder: -### Software Structure Section (MANDATORY) +- **Architecture**: software items, relationships, and collaboration +- **External Interfaces**: name, direction, format, constraints +- **Dependencies**: OTS and Shared Packages used; cross-reference their design docs +- **Risk Control Measures**: segregation required for risk control (IEC 62304 §5.3.3) +- **Data Flow**: inputs to outputs +- **Design Constraints**: platform, performance, security, regulatory -Include a text-based tree diagram showing the software organization across -System, Subsystem, and Unit levels. Agents MUST read `software-items.md` -to understand these classifications before creating this section. +# Subsystem Design (MANDATORY) -Example format: +Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children: -```text -Project1Name (System) -├── ComponentA (Subsystem) -│ ├── SubComponentP (Subsystem) -│ │ └── ClassW (Unit) -│ ├── ClassX (Unit) -│ └── ClassY (Unit) -├── ComponentB (Subsystem) -│ └── ClassZ (Unit) -└── UtilityClass (Unit) - -Project2Name (System) -└── HelperClass (Unit) -``` +- **Overview**: responsibility, boundaries, contained units +- **Interfaces**: what it exposes and consumes +- **Design**: how internal units collaborate -### Folder Layout Section (MANDATORY) +# Unit Design (MANDATORY) -Include a text-based tree diagram showing how the source code folders -mirror the software structure, with file paths and brief descriptions. +Place `{unit-name}.md` in the **parent** folder: -Example format: +- **Purpose**: single responsibility +- **Data Model**: fields, properties, types, invariants (IEC 62304 §5.4.2) +- **Key Methods**: name, purpose, algorithm, preconditions, postconditions, parameter types +- **Error Handling**: detection and handling; what is propagated vs. handled locally +- **Dependencies**: other units, subsystems, OTS items, and shared packages used +- **Callers**: units or subsystems that call or consume this unit -```text -src/Project1Name/ -├── ComponentA/ -│ ├── SubComponentP/ -│ │ └── ClassW.cs - Specialized processing engine -│ ├── ClassX.cs - Core business logic handler -│ └── ClassY.cs - Data validation service -├── ComponentB/ -│ └── ClassZ.cs - Integration interface -└── UtilityClass.cs - Common utility functions - -src/Project2Name/ -└── HelperClass.cs - Helper functions -``` +# OTS Integration Design (when OTS items exist) -### Companion Artifact Structure (RECOMMENDED) +Create `docs/design/ots.md` (`#` heading) covering the overall OTS integration strategy. -Include a brief note explaining that each software item has parallel artifacts -across the repository, so agents and reviewers can navigate from any one -artifact to all related files: +For each OTS item, create `docs/design/ots/{ots-name}.md` (`##` heading) with sections: -Example format: +- **Purpose**: why chosen and what it provides to the local system +- **Features Used**: which specific features, APIs, or capabilities are consumed +- **Integration Pattern**: how it is consumed; initialization, configuration, disposal requirements -```text -Each software item in the structure above has corresponding artifacts in -parallel directory trees: - -- Requirements: `docs/reqstream/{system}/.../{item}.yaml` (kebab-case) -- Design docs: `docs/design/{system}/.../{item}.md` (kebab-case) -- Source code: `src/{System}/.../{Item}.{ext}` (cased per language - see `software-items.md`) -- Tests: `test/{System}.Tests/.../{Item}Tests.{ext}` (cased per language - see `software-items.md`) -- Review-sets: defined in `.reviewmark.yaml` -``` - -## System Design Documentation (MANDATORY) - -For each system identified in the repository: - -- Create a kebab-case folder matching the system name -- Include `{system-name}.md` with system-level design documentation such as: - - System architecture and major components - - External interfaces and dependencies - - Data flow and control flow - - System-wide design constraints and decisions - - Integration patterns and communication protocols - -## Subsystem and Unit Design Documents - -For each subsystem identified in the software structure: +# Shared Package Integration Design (when Shared Packages exist) -- Create a kebab-case folder matching the subsystem name (enables automated tooling) -- Include `{subsystem-name}.md` with subsystem overview and design -- Include unit design documents for ALL units within the subsystem +Create `docs/design/shared.md` (`#` heading) covering the overall consumption strategy. -For every unit identified in the software structure: +For each Shared Package, create `docs/design/shared/{package-name}.md` (`##` heading) with sections: -- Document data models, algorithms, and key methods -- Describe interactions with other units -- Include sufficient detail for formal code review -- Place in appropriate subsystem folder or at design root level - -# Software Items Integration (CRITICAL) - -Read `software-items.md` before creating design documentation - correct -System/Subsystem/Unit categorization is required for software structure -diagrams and folder layout. +- **Advertised Features Consumed**: which features the local system relies on +- **Integration Pattern**: how the package is referenced, initialized, and consumed +- **Assumptions**: any assumptions the local system makes about the package's behavior # Writing Guidelines -Design documentation must be technical and specific because it serves as the -implementation specification for formal code review: - -- **Implementation Detail**: Provide sufficient detail for code review and implementation -- **Architectural Clarity**: Clearly define component boundaries and interfaces -- **Traceability**: Link to requirements where applicable using ReqStream patterns - -# Mermaid Diagram Integration - -Use Mermaid diagrams to supplement text descriptions (diagrams must not replace text content). +- Use Mermaid diagrams to supplement (not replace) text +- Use verbal cross-references ("see _Parser Design_") - not markdown hyperlinks (break in PDF) +- Provide sufficient detail for formal code review # Quality Checks -Before submitting design documentation, verify: - -- [ ] `introduction.md` includes both Software Structure and Folder Layout sections -- [ ] Software structure correctly categorizes items as System/Subsystem/Unit per `software-items.md` -- [ ] Folder layout mirrors software structure organization -- [ ] Design documents provide sufficient detail for code review -- [ ] System documentation provides comprehensive system-level design -- [ ] Subsystem documentation folders use kebab-case names while mirroring source subsystem names and structure -- [ ] All documents follow technical documentation formatting standards -- [ ] Content is current with implementation and requirements -- [ ] Documents are integrated into ReviewMark review-sets for formal review +- [ ] `introduction.md` includes Software Structure, Folder Layout, and Companion Artifact Structure +- [ ] Software structure correctly categorizes items per `software-items.md` +- [ ] Each file's heading depth matches its folder depth +- [ ] All folders use kebab-case mirroring source structure +- [ ] System design includes all mandatory sections (Architecture, External Interfaces, Dependencies, + Risk Control Measures, Data Flow, Design Constraints) +- [ ] Subsystem design includes all mandatory sections (Overview, Interfaces, Design) +- [ ] Unit design includes all mandatory sections (Purpose, Data Model, Key Methods, Error Handling, Dependencies, Callers) +- [ ] Non-applicable mandatory sections contain "N/A - {justification}" +- [ ] `docs/design/ots.md` and `docs/design/ots/{ots-name}.md` exist when OTS items are present +- [ ] `docs/design/shared.md` and `docs/design/shared/{package-name}.md` exist when Shared Packages are present +- [ ] Documents are integrated into ReviewMark review-sets diff --git a/.github/standards/reqstream-usage.md b/.github/standards/reqstream-usage.md index ae5e565..2371164 100644 --- a/.github/standards/reqstream-usage.md +++ b/.github/standards/reqstream-usage.md @@ -9,7 +9,7 @@ globs: ["requirements.yaml", "docs/reqstream/**/*.yaml"] Read these standards first before applying this standard: - **`requirements-principles.md`** - Requirements principles and unidirectionality -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) # Requirements Organization @@ -18,54 +18,83 @@ because ReqStream discovers files via the includes chain in `requirements.yaml` and organizes report output by this hierarchy: ```text -requirements.yaml # Root file (includes only) +requirements.yaml # Root file (includes only) docs/reqstream/ -├── {system-name}/ # System-level requirements folder (one per system) -│ ├── {system-name}.yaml # System-level requirements +├── {system-name}.yaml # System-level requirements +├── {system-name}/ # System folder (one per system) │ ├── platform-requirements.yaml # Platform support requirements -│ ├── {subsystem-name}/ # Subsystem (kebab-case); may nest recursively -│ │ ├── {subsystem-name}.yaml # Requirements for this subsystem -│ │ ├── {child-subsystem}/ # Child subsystem (same structure as parent) -│ │ └── {unit-name}.yaml # Requirements for units within this subsystem -│ └── {unit-name}.yaml # Requirements for top-level units (outside subsystems) -└── ots/ # OTS items appear as a distinct section in reports - └── {ots-name}.yaml # Requirements for OTS components +│ ├── {subsystem-name}.yaml # Subsystem requirements +│ ├── {subsystem-name}/ # Subsystem folder (kebab-case); may nest recursively +│ │ ├── {subsystem-name}.yaml # Child subsystem requirements +│ │ ├── {subsystem-name}/ # Child subsystem folder +│ │ └── {unit-name}.yaml # Unit requirements +│ └── {unit-name}.yaml # System-level unit requirements +├── ots/ # OTS items appear as a distinct section in reports +│ └── {ots-name}.yaml # Requirements for OTS components +└── shared/ # Shared Packages appear as a distinct section in reports + └── {package-name}.yaml # Requirements for Shared Package dependencies ``` +Local items have matching relative paths across `docs/reqstream/`, `docs/design/`, and `docs/verification/`: + +- Requirements: `{system-name}[/{subsystem-name}...]/{item-name}.yaml` +- Design: `{system-name}[/{subsystem-name}...]/{item-name}.md` +- Verification: `{system-name}[/{subsystem-name}...]/{item-name}.md` + # Requirements File Format -```yaml -sections: - - title: Functional Requirements - requirements: - - id: System-Component-Feature # Used as-is in all reports - make it readable - title: The system shall perform the required function. - justification: | - Business rationale and any regulatory references. - # ReqStream extracts this field into the justifications report (--justifications) - children: # ReqStream validates this decomposition chain - - ChildSystem-Feature-Behavior # Downward links only (see requirements-principles.md) - tests: # ReqStream matches these by method name in test results - - TestMethodName - - windows@PlatformSpecificTest # Only test runs on Windows count as evidence -``` +Each file adds requirements at exactly one level of the hierarchy. The file spells out +its full ancestry as nested `{ItemName} Requirements` sections down to that level, then +places requirements there. ReqStream merges identical section title paths across included +files automatically. Always determine item classification from `docs/design/introduction.md` - +folder depth does not determine whether an item is a subsystem or unit. + +Valid section nestings (names in `{braces}` are placeholders): -# OTS Software Requirements +```text +{SystemName} Requirements # system-level requirements +├── {SubsystemName} Requirements # root subsystem requirements +│ ├── {SubsystemName} Requirements # nested subsystem (may recurse) +│ │ └── {UnitName} Requirements # unit under a nested subsystem +│ └── {UnitName} Requirements # unit under a root subsystem +└── {UnitName} Requirements # unit directly under the system +OTS Software Requirements # OTS root section (fixed title) +└── {OtsName} Requirements # requirements for one OTS item +Shared Package Requirements # shared package root section (fixed title) +└── {PackageName} Requirements # requirements for one shared package +``` -Use nested sections in `docs/reqstream/ots/` because ReqStream renders the `ots/` -subtree as a distinct section in generated reports, separate from in-house -system requirements: +Each file implements one path through this tree: ```yaml sections: - - title: OTS Software Requirements + - title: '{SystemName} Requirements' sections: - - title: System.Text.Json + - title: '{SubsystemName} Requirements' requirements: - - id: TemplateTool-SystemTextJson-ReadJson - title: System.Text.Json shall be able to read JSON files. - tests: - - JsonReaderTests.TestReadValidJson + - id: System-Subsystem-Feature # Used as-is in all reports - make it readable + title: The subsystem shall perform the required function. + justification: | # ReqStream extracts this into the justifications report (--justifications) + Business rationale and any regulatory references. + tags: # Optional: categorize for filtering with --filter + - security + children: # Optional: ReqStream validates this decomposition chain + - System-Subsystem-Unit-Feat # Downward links only (see requirements-principles.md) + tests: # ReqStream matches these by method name in test results + - TestMethodName + - windows@PlatformSpecificTest # Only test runs on Windows count as evidence +``` + +# Tags (OPTIONAL) + +Tags are free-form - no mandatory vocabulary. Common tags: `security`, `safety`, `performance`, +`compliance`, `reliability`, `critical`. Use `--filter` to selectively export or enforce subsets +(OR logic across comma-separated tags): + +```bash +dotnet reqstream --requirements requirements.yaml \ + --filter security,critical \ + --report docs/requirements_doc/generated/security_requirements.md ``` # Semantic IDs (MANDATORY) @@ -104,28 +133,25 @@ dotnet reqstream --requirements requirements.yaml --lint # Generate requirements document for compliance record dotnet reqstream --requirements requirements.yaml \ - --report docs/requirements_doc/requirements.md + --report docs/requirements_doc/generated/requirements.md # Generate justifications document for compliance record dotnet reqstream --requirements requirements.yaml \ - --justifications docs/requirements_doc/justifications.md + --justifications docs/requirements_doc/generated/justifications.md # Generate trace matrix proving each requirement is covered by passing tests dotnet reqstream --requirements requirements.yaml \ --tests "artifacts/**/*.trx" \ - --matrix docs/requirements_report/trace_matrix.md + --matrix docs/requirements_report/generated/trace_matrix.md ``` # Quality Checks Before submitting requirements, verify: -- [ ] All requirements have semantic IDs (`System-Section-Feature` pattern) -- [ ] Every requirement links to at least one passing test +- [ ] All requirements have semantic IDs (`System-Component-Feature` pattern) +- [ ] Every requirement has a justification explaining business/regulatory need +- [ ] Every requirement links to at least one test - [ ] Platform-specific requirements use source filters (`platform@TestName`) -- [ ] Comprehensive justification explains business/regulatory need -- [ ] Files organized under `docs/reqstream/` following folder structure patterns -- [ ] Subsystem folders use kebab-case naming matching source code -- [ ] OTS requirements placed in `ots/` subfolder -- [ ] Valid YAML syntax passes yamllint validation -- [ ] Test result formats compatible (TRX, JUnit XML) +- [ ] All files and folders use kebab-case names matching source code structure +- [ ] All files are organized under `docs/reqstream/` following the folder structure above diff --git a/.github/standards/requirements-principles.md b/.github/standards/requirements-principles.md index 7d2d572..b6cf136 100644 --- a/.github/standards/requirements-principles.md +++ b/.github/standards/requirements-principles.md @@ -29,6 +29,10 @@ implementation code. - **Valid**: "The parser shall report the line number of the first syntax error." - **Not a requirement (design decision)**: "The parser shall use a `TokenStream` class." +A unit may use its own name freely - that is identity, not HOW. What is +forbidden is describing *internal construction*: class names, method signatures, +algorithms, or data structures. + # Requirements at Every Level (MANDATORY) Every identified subsystem and unit MUST have its own requirements file because diff --git a/.github/standards/reviewmark-usage.md b/.github/standards/reviewmark-usage.md index 5d6219e..b521433 100644 --- a/.github/standards/reviewmark-usage.md +++ b/.github/standards/reviewmark-usage.md @@ -9,7 +9,7 @@ description: Follow these standards when configuring file reviews with ReviewMar Read these standards first before applying this standard: -- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS) +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) ## Purpose @@ -20,7 +20,7 @@ review, organizes them into review-sets, and generates review plans and reports. - **Lint Configuration**: `dotnet reviewmark --lint` - **Elaborate Review-Set**: `dotnet reviewmark --elaborate {review-set}` -- **Generate Plan**: `dotnet reviewmark --plan docs/code_review_plan/plan.md --enforce` +- **Generate Plan**: `dotnet reviewmark --plan docs/code_review_plan/generated/plan.md --enforce` > **Note**: `--enforce` causes the plan to fail with a non-zero exit code if any repository > files are not covered by a review-set. Uncovered files indicate a gap in review-set @@ -31,7 +31,8 @@ review, organizes them into review-sets, and generates review plans and reports. Required repository items for ReviewMark operation: - `.reviewmark.yaml` - Configuration for review-sets, file-patterns, and review evidence-source. -- `docs/code_review_plan/` - Review planning artifacts +- `docs/code_review_plan/generated/` - Generated review plan (build output, do not edit) +- `docs/code_review_report/generated/` - Generated review report (build output, do not edit) # Review Definition Structure @@ -55,10 +56,22 @@ needs-review: - "README.md" # Root level README - "docs/user_guide/**/*.md" # User guide - "docs/design/**/*.md" # Design documentation + - "docs/verification/**/*.md" # Verification design documentation # Source of review evidence evidence-source: type: none + +# Review-sets (each focuses on a single compliance question) +reviews: + - id: Purpose + title: Review of user-facing capabilities and system promises + paths: + - "README.md" + - "docs/user_guide/**/*.md" + - "docs/reqstream/{system-name}.yaml" + - "docs/design/introduction.md" + - "docs/design/{system-name}.md" ``` # Review-Set Design Principles @@ -75,12 +88,11 @@ When constructing review-sets, follow these principles to maintain manageable sc # Review-Set Organization -Organize review-sets using these standard patterns to ensure comprehensive coverage -while keeping each review manageable in scope: - -**Naming conventions**: See `software-items.md` - kebab-case placeholders -(e.g., `{system-name}`) are always kebab-case; cased placeholders -(e.g., `{SystemName}`) follow your language's convention. +**Naming conventions**: Placeholders in documentation, requirements, design, and +verification file paths are kebab-case (e.g., `{system-name}`). Placeholders in +source and test file paths may use the casing conventional for the project's +source language or repository layout (e.g., `{SystemName}`). Review-set name +placeholders are always PascalCase (e.g., `{SystemName}`). ## `Purpose` Review (only one per repository) @@ -93,74 +105,124 @@ Reviews user-facing capabilities and system promises: - **File Path Patterns**: - README: `README.md` - User guide: `docs/user_guide/**/*.md` - - System requirements: `docs/reqstream/{system-name}/{system-name}.yaml` + - System requirements: `docs/reqstream/{system-name}.yaml` - Design introduction: `docs/design/introduction.md` - - System design: `docs/design/{system-name}/{system-name}.md` + - System design: `docs/design/{system-name}.md` -## `{System}-Architecture` Review (one per system) +## `{SystemName}-Architecture` Review (one per system) Reviews system architecture and operational validation: - **Purpose**: Proves that the system is designed and tested to satisfy its requirements -- **Title**: "Review that {System} Architecture Satisfies Requirements" +- **Title**: "Review that {SystemName} Architecture Satisfies Requirements" - **Scope**: Excludes subsystem and unit files, relying on system-level design to describe what subsystems and units it uses - **File Path Patterns**: - - System requirements: `docs/reqstream/{system-name}/{system-name}.yaml` + - System requirements: `docs/reqstream/{system-name}.yaml` - Design introduction: `docs/design/introduction.md` - - System design: `docs/design/{system-name}/{system-name}.md` + - System design: `docs/design/{system-name}.md` + - Verification introduction: `docs/verification/introduction.md` + - System verification design: `docs/verification/{system-name}.md` - System integration tests: `test/{SystemName}.Tests/{SystemName}Tests.{ext}` -## `{System}-Design` Review (one per system) +## `{SystemName}-Design` Review (one per system) Reviews architectural and design consistency: - **Purpose**: Proves the system design is consistent and complete -- **Title**: "Review that {System} Design is Consistent and Complete" +- **Title**: "Review that {SystemName} Design is Consistent and Complete" - **Scope**: Only brings in top-level requirements and relies on brevity of design documentation - **File Path Patterns**: - - System requirements: `docs/reqstream/{system-name}/{system-name}.yaml` + - System requirements: `docs/reqstream/{system-name}.yaml` - Platform requirements: `docs/reqstream/{system-name}/platform-requirements.yaml` - Design introduction: `docs/design/introduction.md` + - System design: `docs/design/{system-name}.md` - System design files: `docs/design/{system-name}/**/*.md` + - OTS overview: `docs/design/ots.md` _(only if OTS items exist)_ + - Shared Package overview: `docs/design/shared.md` _(only if Shared Package items exist)_ -## `{System}-AllRequirements` Review (one per system) +## `{SystemName}-Verification` Review (one per system) + +Reviews verification completeness and consistency: + +- **Purpose**: Proves the system verification design is consistent and covers all requirements +- **Title**: "Review that {SystemName} Verification is Consistent and Complete" +- **Scope**: Only brings in top-level requirements and all verification docs for the system +- **File Path Patterns**: + - System requirements: `docs/reqstream/{system-name}.yaml` + - Verification introduction: `docs/verification/introduction.md` + - System verification: `docs/verification/{system-name}.md` + - System verification files: `docs/verification/{system-name}/**/*.md` + - OTS overview: `docs/verification/ots.md` _(only if OTS items exist)_ + - Shared Package overview: `docs/verification/shared.md` _(only if Shared Package items exist)_ + +## `{SystemName}-AllRequirements` Review (one per system) Reviews requirements quality and traceability: - **Purpose**: Proves the requirements are consistent and complete -- **Title**: "Review that All {System} Requirements are Complete" +- **Title**: "Review that All {SystemName} Requirements are Complete" - **Scope**: Only brings in requirements files to keep review manageable - **File Path Patterns**: - Root requirements: `requirements.yaml` - - System requirements: `docs/reqstream/{system-name}/**/*.yaml` - - OTS requirements: `docs/reqstream/ots/**/*.yaml` (if applicable) + - System requirements: `docs/reqstream/{system-name}.yaml` + - Subsystem/unit requirements: `docs/reqstream/{system-name}/**/*.yaml` -## `{System}-{Subsystem[-Child...]}` Review (one per subsystem at any depth) +## `{SystemName}-{SubsystemName}[-{SubsystemName}...]` Review (one per subsystem at any depth) Reviews subsystem architecture and interfaces: - **Purpose**: Proves that the subsystem is designed and tested to satisfy its requirements -- **Title**: "Review that {System} {Subsystem} Satisfies Subsystem Requirements" +- **Title**: "Review that {SystemName} {SubsystemName} Satisfies Subsystem Requirements" - **Scope**: Excludes units under the subsystem, relying on subsystem design to describe what units it uses - **File Path Patterns**: - - Requirements: `docs/reqstream/{system-name}/.../{subsystem-name}/{subsystem-name}.yaml` - - Design: `docs/design/{system-name}/.../{subsystem-name}/{subsystem-name}.md` - - Tests: `test/{SystemName}.Tests/.../{SubsystemName}/{SubsystemName}Tests.{ext}` + - Requirements: `docs/reqstream/{system-name}[/{subsystem-name}...]/{subsystem-name}.yaml` + - Design: `docs/design/{system-name}[/{subsystem-name}...]/{subsystem-name}.md` + - Verification design: `docs/verification/{system-name}[/{subsystem-name}...]/{subsystem-name}.md` + - Tests: `test/{SystemName}.Tests[/{SubsystemName}...]/{SubsystemName}Tests.{ext}` -## `{System}-{Subsystem[-Child...]}-{Unit}` Review (one per unit) +## `{SystemName}-{SubsystemName}[-{SubsystemName}...]-{UnitName}` Review (one per unit) Reviews individual software unit implementation: - **Purpose**: Proves the unit is designed, implemented, and tested to satisfy its requirements -- **Title**: "Review that {System} {Subsystem} {Unit} Implementation is Correct" +- **Title**: "Review that {SystemName} {SubsystemName} {UnitName} Implementation is Correct" - **Scope**: Complete unit review including all artifacts - **File Path Patterns**: - - Requirements: `docs/reqstream/{system-name}/.../{unit-name}.yaml` - - Design: `docs/design/{system-name}/.../{unit-name}.md` - - Source: `src/{SystemName}/.../{UnitName}.{ext}` - - Tests: `test/{SystemName}.Tests/.../{UnitName}Tests.{ext}` + - Requirements: `docs/reqstream/{system-name}[/{subsystem-name}...]/{unit-name}.yaml` + - Design: `docs/design/{system-name}[/{subsystem-name}...]/{unit-name}.md` + - Verification design: `docs/verification/{system-name}[/{subsystem-name}...]/{unit-name}.md` + - Source (C# example): `src/{SystemName}[/{SubsystemName}...]/{UnitName}.cs` + - Tests (C# example): `test/{SystemName}.Tests[/{SubsystemName}...]/{UnitName}Tests.cs` + - Source (snake_case C++ example): `src/{system_name}[/{subsystem_name}...]/{unit_name}.cpp` + - Tests (snake_case C++ example): `test/{system_name}_tests[/{subsystem_name}...]/{unit_name}_tests.cpp` + +## `OTS-{OtsName}` Review (one per OTS item) + +Reviews OTS item integration design, requirements, and verification evidence: + +- **Purpose**: Proves that the OTS item provides the required functionality and is correctly integrated +- **Title**: "Review that {OtsName} Provides Required Functionality" +- **Scope**: No local source code; review covers integration design, requirements, and verification evidence +- **File Path Patterns**: + - OTS requirements: `docs/reqstream/ots/{ots-name}.yaml` + - OTS integration design: `docs/design/ots/{ots-name}.md` + - OTS verification: `docs/verification/ots/{ots-name}.md` + - Tests (if applicable): `test/OtsSoftwareTests/...` (C#) or `test/ots_software_tests/...` + (Python/other) — fixed repo-level name, no system prefix + +## `Shared-{PackageName}` Review (one per Shared Package) + +Reviews Shared Package integration design, requirements, and verification evidence: + +- **Purpose**: Proves that the Shared Package provides the required advertised features and is correctly integrated +- **Title**: "Review that {PackageName} Provides Required Features" +- **Scope**: No local source code; review covers integration design, requirements, and verification evidence +- **File Path Patterns**: + - Shared Package requirements: `docs/reqstream/shared/{package-name}.yaml` + - Shared Package integration design: `docs/design/shared/{package-name}.md` + - Shared Package verification: `docs/verification/shared/{package-name}.md` **Note**: File path patterns use `{ext}` as a placeholder for language-specific extensions (`.cs`, `.cpp`/`.hpp`, `.py`, etc.). Adapt to your repository's languages. @@ -171,10 +233,6 @@ Before submitting ReviewMark configuration, verify: - [ ] `.reviewmark.yaml` exists at repository root with proper structure - [ ] Review-set organization follows the standard hierarchy patterns -- [ ] Purpose review-set includes README.md, user guide, system requirements, design introduction, and system design files -- [ ] System-level reviews follow hierarchical scope principle (exclude subsystem/unit details) -- [ ] Subsystem reviews follow hierarchical scope principle (exclude unit source code) -- [ ] Only unit reviews include actual source code files - [ ] Each review-set focuses on a single compliance question (single focus principle) - [ ] File patterns use correct glob syntax and match intended files - [ ] Review-set file counts remain manageable (context management principle) diff --git a/.github/standards/software-items.md b/.github/standards/software-items.md index bb67b1d..6c29525 100644 --- a/.github/standards/software-items.md +++ b/.github/standards/software-items.md @@ -3,20 +3,16 @@ name: Software Items description: Follow these standards when categorizing software components. --- -# Software Items Definition Standards - -This document defines standards for categorizing software items within -Continuous Compliance environments because proper categorization determines -requirements management approach, testing strategy, and review scope. - # Software Item Categories -Categorize all software into five primary groups: +Categorize all software into six primary groups: - **Software Package**: Distributable unit delivered to end users or dependent systems, containing one software system with all its components. All software - systems are delivered as a software package. When consumed by another system, - our software package is treated as an OTS Software Item by that system. + systems are delivered as a software package. When consumed by a system outside + the producing program, our software package is treated as an OTS Software Item + by that system. When consumed by another repository within the same program, + it is treated as a Shared Package. - **Software System**: Complete deliverable product including all components and external interfaces, contained within a software package - **Software Subsystem**: Major architectural component with well-defined @@ -24,7 +20,11 @@ Categorize all software into five primary groups: - **Software Unit**: Individual class, function, or tightly coupled set of functions that can be tested in isolation - **OTS Software Item**: Third-party component (library, framework, tool, or - published software package) providing functionality not developed in-house + published software package) providing functionality not developed within the program +- **Shared Package**: A software package produced by a different repository within + the same program, consumed as a dependency. Referenced by its advertised features + rather than internal design; traceability to program-level requirements runs + through the top-level project. **Naming**: When names collide in hierarchy, add descriptive suffix to higher-level entity: @@ -34,17 +34,28 @@ Categorize all software into five primary groups: # Naming Conventions in File Path Patterns -Two placeholder styles appear in path patterns across these standards: +Three placeholder forms appear in path patterns across these standards: -- **Kebab-case** (`{system-name}`, `{unit-name}`): always kebab-case - - used in documentation and requirements paths -- **Cased** (`{SystemName}`, `{UnitName}`): follow your language's convention - - `PascalCase` for C#/Java, `snake_case` for C++/Python - - used in source and test file paths +- **Kebab-case** (`{system-name}`, `{unit-name}`): always kebab-case — + documentation and requirements file paths +- **PascalCase IDs** (`{SystemName}`, `{UnitName}`): always PascalCase — + requirements IDs, ReviewMark IDs, and other documentation identifiers +- **Language-cased** (`{SystemName}` or `{system_name}`): follow your language's + convention — `PascalCase` for C#/Java, `snake_case` for C++/Python — + source and test file/folder names -# Categorization Guidelines +## Nesting Depth Notation -Choose the appropriate category based on scope and testability: +Subsystems nest to any depth. Patterns use bracket-ellipsis to express this without +enumerating levels — `[/{subsystem-name}...]` in paths, `[-{SubsystemName}...]` in +dash-separated IDs. Examples covering all three forms: + +- `{SystemName}[-{SubsystemName}...]-{UnitName}-Feature` (PascalCase ID) +- `docs/design/{system-name}[/{subsystem-name}...]/{unit-name}.md` (kebab-case doc path) +- `src/{SystemName}[/{SubsystemName}...]/{UnitName}.cs` (C# source path) +- `src/{system_name}[/{subsystem_name}...]/{unit_name}.cpp` (C++/Python source path) + +# Categorization Guidelines ## Software Package @@ -75,20 +86,47 @@ Choose the appropriate category based on scope and testability: ## OTS Software Item -- External dependency not developed in-house - typically a third-party published +- External dependency from outside the program - typically a third-party published software package (NuGet, npm, etc.), hosted service, or tool -- Our own published software package becomes an OTS item to any system that - consumes it +- A package produced by an unrelated program (inside or outside the organization) + is treated as OTS by any consuming system - Tested through integration tests proving required functionality works - Examples: System.Text.Json, Entity Framework, third-party APIs +- **Artifact locations** (OTS items have no internal design documentation): + - Requirements: `docs/reqstream/ots/{ots-name}.yaml` + - Design: `docs/design/ots/{ots-name}.md` (integration/usage design) + - Verification: `docs/verification/ots/{ots-name}.md` + - These folders sit parallel to system folders (not inside any system folder) +- System design documentation records which OTS items each system depends on +- **OTS test project**: If no other verification evidence is available (e.g., vendor test results, + published compliance reports), a dedicated test project holds OTS integration tests - one test + file per OTS item requiring tests. OTS items are repo-level (not per-system), so the project + uses a fixed repo-level name: `test/OtsSoftwareTests/` (C#) or `test/ots_software_tests/` + (Python/other) — never prefixed with a system or project name. + +## Shared Package + +- A software package produced by a different repository within the same program +- The consuming repository references advertised features, not internal design or source +- Traceability to program-level requirements runs through the top-level project, + not directly between repositories +- Verified through any appropriate approach in the consuming repository - most commonly + downstream integration tests that transitively prove the advertised features are functional +- **Artifact locations** (no internal design documentation in the consuming repository): + - Requirements: `docs/reqstream/shared/{package-name}.yaml` + - Design: `docs/design/shared/{package-name}.md` (integration/usage design) + - Verification: `docs/verification/shared/{package-name}.md` + - These folders sit parallel to system and OTS folders # Software Item Artifact Model -Each software item has four artifact types that together form a complete review +Each software item has five artifact types that together form a complete review unit - because reviewing any one artifact in isolation cannot determine whether the item is correct, well-designed, and proven to work: - **Requirements** - WHAT the item must do (drives all other artifacts; applies to all item types) -- **Design** - HOW the item satisfies its requirements (in-house items only: system, subsystem, unit) -- **Source code** - The implementation of the design (in-house units only) +- **Design** - HOW the item satisfies its requirements (full design for local items: system, + subsystem, unit; integration/usage design for OTS and Shared Package) +- **Verification Design** - HOW the requirements will be tested (applies to all item types) +- **Source code** - The implementation of the design (local units only; not applicable to OTS or Shared Package) - **Tests** - PROOF the item does WHAT it is required to do (applies to all item types) diff --git a/.github/standards/technical-documentation.md b/.github/standards/technical-documentation.md index 455b2fd..23893bd 100644 --- a/.github/standards/technical-documentation.md +++ b/.github/standards/technical-documentation.md @@ -1,14 +1,11 @@ --- name: Technical Documentation description: Follow these standards when creating technical documentation. -globs: ["docs/**/*.md", "README.md"] +globs: ["docs/**/*.md", "README.md", "!docs/**/generated/**"] --- # Technical Documentation Standards -This document defines standards for technical documentation within Continuous -Compliance environments. - # Core Principles Technical documentation serves as compliance evidence and must be structured @@ -23,63 +20,28 @@ for regulatory review: - **Review Integration**: Documentation follows ReviewMark patterns for formal review tracking -# Documentation Organization +# Pandoc Document Structure (MANDATORY) -Structure documentation under `docs/` following standard patterns for -consistency and tool compatibility: +Each document collection under `docs/` follows this layout: ```text -docs/ - build_notes.md # Generated by BuildMark - build_notes/ # Auto-generated build notes - versions.md # Generated by VersionMark - code_review_plan/ # Auto-generated review plans - plan.md # Generated by ReviewMark - code_review_report/ # Auto-generated review reports - report.md # Generated by ReviewMark - design/ # Design documentation - introduction.md # Design overview - {system-name}/ # System architecture folder - {system-name}.md # System architecture - {subsystem-name}/ # Subsystem folder; may nest recursively - {subsystem-name}.md # Subsystem-specific designs - {child-subsystem}/ # Child subsystem (same structure) - {unit-name}.md # Unit-specific designs - {unit-name}.md # Top-level unit design - reqstream/ # Requirements source files - {system-name}/ # System requirements folder - {system-name}.yaml # System requirements - platform-requirements.yaml # Platform requirements - {subsystem-name}/ # Subsystem folder; may nest recursively - {subsystem-name}.yaml # Subsystem requirements - {child-subsystem}/ # Child subsystem (same structure) - {unit-name}.yaml # Unit-specific requirements - {unit-name}.yaml # Top-level unit requirements - ots/ # OTS requirement files - {ots-name}.yaml # OTS requirements - requirements_doc/ # Auto-generated requirements reports - requirements.md # Generated by ReqStream - justifications.md # Generated by ReqStream - requirements_report/ # Auto-generated trace matrices - trace_matrix.md # Generated by ReqStream - user_guide/ # User-facing documentation - introduction.md # User guide overview - {section}.md # User guide sections +docs/{collection}/ + title.txt # MANDATORY - YAML document metadata (title, author, etc.) + definition.yaml # MANDATORY - Pandoc build definition (inputs, template, paths) + introduction.md # MANDATORY - document introduction (Purpose, Scope, References) + {section}.md # optional checked-in content sections (zero or more) + generated/ # BUILD OUTPUT - never read, edit, or lint these files + {report}.md # generated by CI tools (ReqStream, ReviewMark, SarifMark, etc.) + {collection}.html # generated by Pandoc ``` -# Pandoc Document Structure (MANDATORY) - -All document collections processed by Pandoc MUST include all four files below - -without `title.txt` and `definition.yaml` the pipeline cannot generate the document: - -- `title.txt` - YAML metadata (title, subtitle, author, description, lang, keywords) -- `definition.yaml` - Pandoc build definition (resource paths, input file list, template) -- `introduction.md` - document introduction -- `{sections}.md` - additional content sections +Without `title.txt` and `definition.yaml` the pipeline cannot generate the document. +When creating a new document collection, create these three files together and use +the existing collections under `docs/` as templates. -When creating a new document collection, create `title.txt` and `definition.yaml` -alongside `introduction.md`. Use the existing files under `docs/` as templates - -they share a consistent structure across all collections. +The `generated/` folder is **never committed** to the repository - it is created +locally and in CI by the build pipeline. Do not flag its absence as a conformance +issue. **`title.txt`** - YAML front matter with document metadata. Use the existing files under `docs/` as a pattern and keep fields consistent with the rest of @@ -106,27 +68,47 @@ Include regulatory or business drivers where applicable. Define what is covered and what is explicitly excluded from this documentation. Specify version, system boundaries, and applicability constraints. + +## References + +- [REF-1] Document Title, Author, Version, Date +- [REF-2] Standard Name (e.g., IEEE 12207, ISO 9001) ``` +The `Purpose`, `Scope`, and `References` sections are **unique to `introduction.md`** and must +**not** be replicated in other markdown files within the same document collection. Including them +elsewhere causes duplicate sections in the compiled PDF. + ## Document Ordering -List documents in logical reading order in Pandoc configuration because -readers need coherent information flow from general to specific topics. +List documents in logical reading order in `definition.yaml`. + +## Heading Depth Rule (MANDATORY) + +A file's top-level heading depth must equal its folder depth under the document +collection root - this ensures Pandoc can concatenate all files in `definition.yaml` +order and produce a coherent outline with no heading-shift configuration: + +| Folder depth | Top heading | +| --- | --- | +| 0 - collection root | `#` | +| 1 - one subfolder deep | `##` | +| 2 - two subfolders deep | `###` | +| N - N subfolders deep | `#` × (N+1) | + +Internal sections use the next heading level down (e.g. a `##` file uses `###` +for *Overview*, *Interfaces*, etc.). Deeply nested files have fewer heading levels +available - keep internal structure flat to avoid excessive nesting. # Writing Guidelines Write technical documentation for clarity and compliance verification: - **Clear and Concise**: Use direct language and avoid unnecessary complexity. - Regulatory reviewers must understand content quickly. -- **Structured Sections**: Use consistent heading hierarchy and section - organization. Enables automated processing and review. -- **Specific Examples**: Include concrete examples with actual values rather - than placeholders. Supports implementation verification. +- **Structured Sections**: Use consistent heading hierarchy and section organization. +- **Specific Examples**: Include concrete examples with actual values rather than placeholders. - **Current Information**: Keep documentation synchronized with code changes. - Outdated documentation invalidates compliance evidence. -- **Traceable Content**: Link documentation to requirements and implementation - where applicable for audit trails. +- **Traceable Content**: Link documentation to requirements and implementation where applicable. ## References Sections @@ -135,37 +117,37 @@ References in design/technical documents must point to **external specifications - **INCLUDE**: Requirements documents, system specifications, program documents, standards (IEEE, ISO, etc.) - **NEVER INCLUDE**: Internal development standards (`.github/standards/` files) - these are agent guides +## Cross-References (Within-Document and Cross-Document) + +Do **not** use markdown hyperlinks to reference other sections or documents. Markdown anchor links +(`[text](#heading)`) and relative file links work in a browser but break when compiled to a PDF. + +Instead use **verbal references** - plain prose that identifies the target by name: + +> See *XYZ Design* for more details. +> +> Refer to the *System Requirements* document for the full specification. + # Markdown Format Requirements -Markdown documentation in this repository must follow the formatting standards -defined in `.markdownlint-cli2.yaml` (subject to any exclusions configured there) -for consistency and professional presentation: - -- **120 Character Line Limit**: Keep lines 120 characters or fewer for readability. - Break long lines naturally at punctuation or logical breaks. -- **No Trailing Whitespace**: Remove all trailing spaces and tabs from line - endings to prevent formatting inconsistencies. -- **Blank Lines Around Headings**: Include a blank line both before and after - each heading to improve document structure and readability. -- **Blank Lines Around Lists**: Include a blank line both before and after - numbered and bullet lists to ensure proper rendering and visual separation. -- **ATX-Style Headers**: Use `#` syntax for headers instead of underline style - for consistency across all documentation. -- **Consistent List Indentation**: Use 2-space indentation for nested list - items to maintain uniform formatting. +Follow `.markdownlint-cli2.yaml` formatting standards: + +- **120 Character Line Limit**: Keep lines 120 characters or fewer; break at punctuation or logical breaks. +- **No Trailing Whitespace**: Remove all trailing spaces and tabs. +- **Blank Lines Around Headings**: Include a blank line before and after each heading. +- **Blank Lines Around Lists**: Include a blank line before and after numbered and bullet lists. +- **ATX-Style Headers**: Use `#` syntax, not underline style. +- **Consistent List Indentation**: Use 2-space indentation for nested list items. # Auto-Generated Content (CRITICAL) -**NEVER modify auto-generated markdown files** because changes will be -overwritten and break compliance automation: +**NEVER read, lint, or modify files inside any `generated/` folder** - they are +build outputs that are overwritten on every CI run: -- **Read-Only Files**: Generated reports under `docs/requirements_doc/`, - `docs/requirements_report/`, `docs/code_review_plan/`, and - `docs/code_review_report/` are regenerated on every build -- **Source Modification**: Update source files (requirements YAML, code - comments) instead of generated output -- **Tool Integration**: Generated content integrates with CI/CD pipelines and - manual changes disrupt automation +- **Location**: All generated files live in `generated/` subfolders within their + respective `docs/` sections, or in `docs/generated/` for final release artifacts +- **Source Modification**: Update source files (requirements YAML, `.reviewmark.yaml`, + tool configuration) instead of generated output # README.md Best Practices @@ -188,20 +170,12 @@ Structure README.md for both human readers and AI agent processing: - **Code Block Languages**: Specify language for syntax highlighting and tool processing - **Clear Prerequisites**: List exact version requirements and dependencies -## Quality Guidelines - -- **Scannable Structure**: Use bullet points, headings, and short paragraphs -- **Current Examples**: Verify all code examples work with current version -- **Link Validation**: Ensure all external links are accessible and current -- **Consistent Tone**: Professional, helpful tone appropriate for technical audience - # Quality Checks Before submitting technical documentation, verify: - [ ] Documentation organized under `docs/` following standard folder structure - [ ] Pandoc collections include `introduction.md` with Purpose and Scope sections -- [ ] Content follows clear and concise writing guidelines with specific examples - [ ] No modifications made to auto-generated markdown files in compliance folders - [ ] README.md includes all required sections with absolute URLs and concrete examples - [ ] Documentation integrated into ReviewMark review-sets for formal review diff --git a/.github/standards/testing-principles.md b/.github/standards/testing-principles.md index 73974ff..917463e 100644 --- a/.github/standards/testing-principles.md +++ b/.github/standards/testing-principles.md @@ -3,11 +3,6 @@ name: Testing Principles description: Follow these standards when developing any software tests. --- -# Testing Principles Standards - -This document defines universal testing principles and quality standards for test development within -Continuous Compliance environments. - # Test Dependency Boundaries (MANDATORY) Respect software item hierarchy boundaries to ensure review-sets can validate proper architectural scope. diff --git a/.github/standards/verification-documentation.md b/.github/standards/verification-documentation.md new file mode 100644 index 0000000..494e40f --- /dev/null +++ b/.github/standards/verification-documentation.md @@ -0,0 +1,101 @@ +--- +name: Verification Documentation +description: Follow these standards when creating software verification design documentation. +globs: ["docs/verification/**/*.md"] +--- + +# Required Standards + +- **`technical-documentation.md`** - General technical documentation standards +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) + +# Folder Structure + +```text +docs/verification/ +├── introduction.md # heading depth # +├── {system-name}.md # heading depth # +├── {system-name}/ +│ ├── {subsystem-name}.md # heading depth ## +│ ├── {subsystem-name}/ +│ │ └── {unit-name}.md # heading depth ### +│ └── {unit-name}.md # heading depth ## +├── ots.md # heading depth # (if OTS items exist) +├── ots/ +│ └── {ots-name}.md # heading depth ## +├── shared.md # heading depth # (if Shared Packages exist) +└── shared/ + └── {package-name}.md # heading depth ## +``` + +All sections in every file are mandatory; write "N/A - {justification}" rather than removing any. +Determine subsystem vs. unit classification from `docs/design/introduction.md` — folder depth does not determine classification. + +# introduction.md (MANDATORY) + +Must include: + +- **Purpose**: audience and compliance drivers +- **Scope**: items covered and explicitly excluded (no test projects) +- **Companion Artifact Structure**: parallel paths for requirements, design, verification, source, tests +- **References** _(if applicable)_: external standards or specifications - only in `introduction.md` + +# System Verification Design (MANDATORY) + +Create `{system-name}.md` (`#` heading) and `{system-name}/` folder: + +- **Verification Approach**: test types (unit, integration, end-to-end), framework, project structure +- **Test Environment**: OS, runtime, external services, files, or configuration required +- **Acceptance Criteria**: what constitutes a passing system test (IEC 62304 §5.7.2) +- **Test Scenarios**: named scenarios for each system requirement + +# Subsystem Verification Design (MANDATORY) + +Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children: + +- **Verification Approach**: integration test approach and mocking at subsystem boundary +- **Test Environment**: any environment setup beyond the standard test runner +- **Acceptance Criteria**: what constitutes a passing subsystem test (IEC 62304 §5.5.2) +- **Test Scenarios**: named scenarios including boundary conditions, error paths, and normal operation + +# Unit Verification Design (MANDATORY) + +Place `{unit-name}.md` in the **parent** folder: + +- **Verification Approach**: what is mocked/stubbed and why; injected vs. real dependencies +- **Test Environment**: any environment setup beyond the standard test runner +- **Acceptance Criteria**: what constitutes passing unit tests (IEC 62304 §5.5.2) +- **Test Scenarios**: named scenarios including boundary values, error paths, and normal operation + +# OTS Verification Evidence (when OTS items exist) + +Create `docs/verification/ots.md` (`#` heading) covering the overall OTS verification strategy. + +For each OTS item, create `docs/verification/ots/{ots-name}.md` (`##` heading) covering: +verification approach (self-validation, integration tests, vendor evidence). + +# Shared Package Verification Evidence (when Shared Packages exist) + +Create `docs/verification/shared.md` (`#` heading) covering the overall Shared Package verification strategy. + +For each Shared Package, create `docs/verification/shared/{package-name}.md` (`##` heading) covering: +verification approach. + +# Writing Guidelines + +- Name scenarios clearly ("Valid input returns parsed result", not "Test 1") +- Use verbal cross-references - not markdown hyperlinks (break in PDF) +- Use Mermaid diagrams to supplement (not replace) text + +# Quality Checks + +- [ ] `introduction.md` includes Companion Artifact Structure +- [ ] Each file's heading depth matches its folder depth +- [ ] All folders use kebab-case mirroring source structure +- [ ] Each system/subsystem/unit file includes all mandatory sections (Verification Approach, + Test Environment, Acceptance Criteria, Test Scenarios) +- [ ] Non-applicable mandatory sections contain "N/A - {justification}" +- [ ] Requirements-to-test coverage is tracked via the ReqStream trace matrix, not in these documents +- [ ] `docs/verification/ots.md` and `docs/verification/ots/{ots-name}.md` exist when OTS items are present +- [ ] `docs/verification/shared.md` and `docs/verification/shared/{package-name}.md` exist when Shared Packages are present +- [ ] Documents are integrated into ReviewMark review-sets diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml index c16c443..b65f6d8 100644 --- a/.markdownlint-cli2.yaml +++ b/.markdownlint-cli2.yaml @@ -52,3 +52,4 @@ ignores: - "**/3rd-party/**" - "**/AGENT_REPORT_*.md" - "**/.agent-logs/**" + - "artifacts/**" diff --git a/.reviewmark.yaml b/.reviewmark.yaml index 2330a74..846ddab 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 @@ -43,6 +44,8 @@ reviews: - "docs/reqstream/spdx-model/spdx-model.yaml" - "docs/design/introduction.md" - "docs/design/spdx-model/spdx-model.md" + - "docs/verification/introduction.md" + - "docs/verification/spdx-model/spdx-model.md" - "test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs" - id: SpdxModel-Design @@ -52,6 +55,8 @@ reviews: - "docs/reqstream/spdx-model/platform-requirements.yaml" - "docs/design/introduction.md" - "docs/design/spdx-model/**/*.md" + - "docs/verification/introduction.md" + - "docs/verification/spdx-model/**/*.md" - id: SpdxModel-AllRequirements title: Review that All SpdxModel Requirements are Complete @@ -65,6 +70,7 @@ reviews: paths: - "docs/reqstream/spdx-model/io/io.yaml" - "docs/design/spdx-model/io/io.md" + - "docs/verification/spdx-model/io/io.md" - "test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs" - id: SpdxModel-IO-Spdx2JsonDeserializer @@ -73,6 +79,8 @@ reviews: - "docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml" - "docs/design/spdx-model/io/spdx-2-json-deserializer.md" - "docs/design/spdx-model/io/spdx-constants.md" + - "docs/verification/spdx-model/io/spdx-2-json-deserializer.md" + - "docs/verification/spdx-model/io/spdx-constants.md" - "src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs" - "src/DemaConsulting.SpdxModel/IO/SpdxConstants.cs" - "test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize*.cs" @@ -83,6 +91,8 @@ reviews: - "docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml" - "docs/design/spdx-model/io/spdx-2-json-serializer.md" - "docs/design/spdx-model/io/spdx-constants.md" + - "docs/verification/spdx-model/io/spdx-2-json-serializer.md" + - "docs/verification/spdx-model/io/spdx-constants.md" - "src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs" - "src/DemaConsulting.SpdxModel/IO/SpdxConstants.cs" - "test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerialize*.cs" @@ -92,6 +102,7 @@ reviews: paths: - "docs/reqstream/spdx-model/transform/transform.yaml" - "docs/design/spdx-model/transform/transform.md" + - "docs/verification/spdx-model/transform/transform.md" - "test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs" - id: SpdxModel-Transform-SpdxRelationships @@ -99,6 +110,7 @@ reviews: paths: - "docs/reqstream/spdx-model/transform/spdx-relationships.yaml" - "docs/design/spdx-model/transform/spdx-relationships.md" + - "docs/verification/spdx-model/transform/spdx-relationships.md" - "src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs" - "test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs" @@ -107,6 +119,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-annotation.yaml" - "docs/design/spdx-model/spdx-annotation.md" + - "docs/verification/spdx-model/spdx-annotation.md" - "src/DemaConsulting.SpdxModel/SpdxAnnotation.cs" - "src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs" @@ -116,6 +129,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-checksum.yaml" - "docs/design/spdx-model/spdx-checksum.md" + - "docs/verification/spdx-model/spdx-checksum.md" - "src/DemaConsulting.SpdxModel/SpdxChecksum.cs" - "src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs" @@ -125,6 +139,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-creation-information.yaml" - "docs/design/spdx-model/spdx-creation-information.md" + - "docs/verification/spdx-model/spdx-creation-information.md" - "src/DemaConsulting.SpdxModel/SpdxCreationInformation.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs" @@ -133,6 +148,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-document.yaml" - "docs/design/spdx-model/spdx-document.md" + - "docs/verification/spdx-model/spdx-document.md" - "src/DemaConsulting.SpdxModel/SpdxDocument.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs" @@ -141,6 +157,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-element.yaml" - "docs/design/spdx-model/spdx-element.md" + - "docs/verification/spdx-model/spdx-element.md" - "src/DemaConsulting.SpdxModel/SpdxElement.cs" - id: SpdxModel-SpdxExternalDocumentReference @@ -148,6 +165,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-external-document-reference.yaml" - "docs/design/spdx-model/spdx-external-document-reference.md" + - "docs/verification/spdx-model/spdx-external-document-reference.md" - "src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs" @@ -156,6 +174,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-external-reference.yaml" - "docs/design/spdx-model/spdx-external-reference.md" + - "docs/verification/spdx-model/spdx-external-reference.md" - "src/DemaConsulting.SpdxModel/SpdxExternalReference.cs" - "src/DemaConsulting.SpdxModel/SpdxReferenceCategory.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs" @@ -165,6 +184,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml" - "docs/design/spdx-model/spdx-extracted-licensing-info.md" + - "docs/verification/spdx-model/spdx-extracted-licensing-info.md" - "src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs" @@ -173,6 +193,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-file.yaml" - "docs/design/spdx-model/spdx-file.md" + - "docs/verification/spdx-model/spdx-file.md" - "src/DemaConsulting.SpdxModel/SpdxFile.cs" - "src/DemaConsulting.SpdxModel/SpdxFileType.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs" @@ -182,6 +203,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-helpers.yaml" - "docs/design/spdx-model/spdx-helpers.md" + - "docs/verification/spdx-model/spdx-helpers.md" - "src/DemaConsulting.SpdxModel/SpdxHelpers.cs" - id: SpdxModel-SpdxLicenseElement @@ -189,6 +211,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-license-element.yaml" - "docs/design/spdx-model/spdx-license-element.md" + - "docs/verification/spdx-model/spdx-license-element.md" - "src/DemaConsulting.SpdxModel/SpdxLicenseElement.cs" - id: SpdxModel-SpdxPackage @@ -196,6 +219,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-package.yaml" - "docs/design/spdx-model/spdx-package.md" + - "docs/verification/spdx-model/spdx-package.md" - "src/DemaConsulting.SpdxModel/SpdxPackage.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs" @@ -204,6 +228,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-package-verification-code.yaml" - "docs/design/spdx-model/spdx-package-verification-code.md" + - "docs/verification/spdx-model/spdx-package-verification-code.md" - "src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs" @@ -212,6 +237,7 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-relationship.yaml" - "docs/design/spdx-model/spdx-relationship.md" + - "docs/verification/spdx-model/spdx-relationship.md" - "src/DemaConsulting.SpdxModel/SpdxRelationship.cs" - "src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs" @@ -221,5 +247,6 @@ reviews: paths: - "docs/reqstream/spdx-model/spdx-snippet.yaml" - "docs/design/spdx-model/spdx-snippet.md" + - "docs/verification/spdx-model/spdx-snippet.md" - "src/DemaConsulting.SpdxModel/SpdxSnippet.cs" - "test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs" diff --git a/AGENTS.md b/AGENTS.md index 7249295..9107b89 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,15 @@ +# Project Overview + +- **project-name**: `SpdxModel` +- **organization**: DEMA Consulting +- **project-tagline**: SPDX document model for .NET +- **description**: SpdxModel is a modern C# library for working with SPDX (Software Package Data + Exchange) documents. It provides a comprehensive in-memory model for reading, manipulating, and + writing SPDX Software Bill of Materials (SBOM) files in JSON format, with full support for SPDX + 2.2 and 2.3 specifications. +- **languages**: `C#` +- **technologies**: `.NET`, `.NET Standard 2.0`, `NuGet` + # Project Structure ```text @@ -10,13 +22,27 @@ │ ├── requirements_doc/ │ ├── requirements_report/ │ ├── reqstream/ -│ └── user_guide/ +│ ├── user_guide/ +│ └── verification/ ├── src/ │ └── DemaConsulting.SpdxModel/ └── test/ └── DemaConsulting.SpdxModel.Tests/ ``` +# Language and Spelling (ALL Agents) + +Always use **US English** spelling in all output (code, comments, documentation, +commit messages, and reports). + +# Reference Template + +This repository follows a reference template for structure and file conventions. + +- **Template URL**: `https://github.com/demaconsulting/Agents/raw/refs/heads/template` +- **Repository map**: `{template-url}/repository-map.md` +- **Template files**: `{template-url}/{file-path}` for files described in the map + # Codebase Navigation (ALL Agents) When working with source code, design, or requirements artifacts, read @@ -45,16 +71,15 @@ before searching the filesystem. Before performing any work, agents must read and apply the relevant standards from `.github/standards/`. Use this matrix to determine which to load: -| Work involves... | Load these standards | -|----------------------|------------------------------------------------------------------------------| -| Any code | `coding-principles.md` | -| C# code | `coding-principles.md`, `csharp-language.md` | -| Any tests | `testing-principles.md` | -| C# tests | `testing-principles.md`, `csharp-testing.md` | -| Requirements | `requirements-principles.md`, `software-items.md`, `reqstream-usage.md` | -| Design docs | `software-items.md`, `design-documentation.md`, `technical-documentation.md` | -| Review configuration | `software-items.md`, `reviewmark-usage.md` | -| Any documentation | `technical-documentation.md` | +- **Any code**: `coding-principles.md` +- **C# code**: `coding-principles.md`, `csharp-language.md` +- **Any tests**: `testing-principles.md` +- **C# tests**: `testing-principles.md`, `csharp-testing.md` +- **Requirements**: `requirements-principles.md`, `software-items.md`, `reqstream-usage.md` +- **Design docs**: `software-items.md`, `design-documentation.md`, `technical-documentation.md` +- **Verification docs**: `software-items.md`, `verification-documentation.md`, `technical-documentation.md` +- **Review configuration**: `software-items.md`, `reviewmark-usage.md` +- **Any documentation**: `technical-documentation.md` Load only the standards relevant to your specific task scope. @@ -69,26 +94,11 @@ Delegate to specialized agents only for specific scenarios: - **Formal feature implementation** (complex, multi-step) → Call the implementation agent - **Formal bug resolution** (complex debugging, systematic fixes) → Call the implementation agent - **Formal reviews** (compliance verification, detailed analysis) → Call the formal-review agent -- **Template consistency** (downstream repository alignment) → Call the repo-consistency agent - -## Available Specialized Agents - -- **lint-fix** - Pre-PR lint sweep agent that loops running `pwsh ./lint.ps1`, - fixing issues until the repository is lint-clean -- **developer** - General-purpose software development agent that applies appropriate - standards based on the work being performed -- **formal-review** - Agent for performing formal reviews using standardized review processes -- **implementation** - Orchestrator agent that manages quality implementations - through a formal state machine workflow -- **quality** - Quality assurance agent that grades developer work against project - standards and Continuous Compliance practices -- **repo-consistency** - Ensures downstream repositories remain consistent with - the TemplateDotNetLibrary template patterns and best practices +- **Structural audit**: (repository layout vs. template) → Call the template-sync agent # Agent Reporting (Specialized Agents Must Follow) -Specialized agents (lint-fix, developer, quality, implementation, -formal-review, repo-consistency) MUST generate a completion report: +Specialized agents MUST generate a completion report: 1. Save to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` where `{subject}` is a kebab-case task summary (max 5 words) and @@ -107,7 +117,7 @@ Result semantics for orchestrator decision-making: # Formatting (After Making Changes) After making changes, run the auto-fix pass. This applies all available fixers -silently and **always exits 0** — agents do not need to respond to its output. +silently and **always exits 0** - agents do not need to respond to its output. ```pwsh pwsh ./fix.ps1 @@ -115,7 +125,7 @@ pwsh ./fix.ps1 This automatically handles: `dotnet format`, markdown formatting, and YAML formatting. Full lint compliance is a **pre-PR responsibility**, not an agent -responsibility — invoke the lint-fix agent once before submitting a pull request. +responsibility - invoke the lint-fix agent once before submitting a pull request. ## CI Quality Tools @@ -124,6 +134,8 @@ reqstream, versionmark, and reviewmark. # Scope Discipline (ALL Agents Must Follow) +- **No generated file access**: Files inside any `generated/` folder are build + outputs - do not read, lint, or modify them - **Minimum necessary changes**: Only modify files directly required by the task - **No speculative refactoring**: Do not refactor code adjacent to the change unless the task explicitly requests it diff --git a/README.md b/README.md index b86e4a2..85604e0 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ comprehensive in-memory model for reading, manipulating, and writing SPDX Softwa - 🔄 **JSON Serialization** - Read and write SPDX documents in JSON format - 🎯 **Type-Safe** - Strongly-typed C# API with nullable reference types - 🔍 **Transform Support** - Built-in utilities for manipulating SPDX relationships +- 🔁 **Deep Copy Support** - Deep copy any SPDX element or entire documents +- ⚖️ **Comparison Utilities** - Compare and check equality of SPDX elements - ⚡ **Multi-Target** - Supports .NET Standard 2.0, .NET 8, 9, and 10 - 🖥️ **Multi-Platform** - Builds and runs on Windows, Linux, and macOS - 🧪 **Well-Tested** - Comprehensive test suite with high code coverage @@ -70,6 +72,7 @@ var document = new SpdxDocument Id = "SPDXRef-DOCUMENT", Name = "My Software", Version = "SPDX-2.3", + DataLicense = "CC0-1.0", DocumentNamespace = "https://example.com/my-software", CreationInformation = new SpdxCreationInformation { @@ -127,6 +130,12 @@ var rootPackages = document.GetRootPackages(); - **`SpdxSnippet`** - Represents a code snippet - **`SpdxRelationship`** - Represents relationships between elements - **`SpdxCreationInformation`** - Document creation metadata +- **`SpdxAnnotation`** - Represents document annotations +- **`SpdxChecksum`** - Represents element checksums +- **`SpdxExternalDocumentReference`** - Represents external document references +- **`SpdxExternalReference`** - Represents external references on packages +- **`SpdxExtractedLicensingInfo`** - Represents extracted licensing information +- **`SpdxPackageVerificationCode`** - Represents package verification codes ### Serialization diff --git a/docs/build_notes/definition.yaml b/docs/build_notes/definition.yaml index 207a375..99131c3 100644 --- a/docs/build_notes/definition.yaml +++ b/docs/build_notes/definition.yaml @@ -1,12 +1,10 @@ --- -resource-path: - - docs/build_notes - - docs/template +resource-path: [docs/build_notes, docs/template] input-files: - docs/build_notes/title.txt - docs/build_notes/introduction.md - - docs/build_notes.md - - docs/build_notes/versions.md + - docs/build_notes/generated/build_notes.md # Generated by BuildMark (git history, release notes) + - docs/build_notes/generated/versions.md # Generated by VersionMark (tool versions) template: template.html table-of-contents: true number-sections: true diff --git a/docs/build_notes/introduction.md b/docs/build_notes/introduction.md index c438381..f3f9050 100644 --- a/docs/build_notes/introduction.md +++ b/docs/build_notes/introduction.md @@ -1,33 +1,23 @@ # Introduction -This document contains the build notes for the SpdxModel project. +This document contains the build notes for SpdxModel. ## Purpose -This report serves as a comprehensive record of changes and bug fixes for this -release of SpdxModel. It provides transparency about what has changed since the -previous version and helps users understand the improvements and fixes included -in this build. +This document provides a record of the changes, new features, and bug fixes included +in this release of SpdxModel. It also records the versions of all tools used in +the build pipeline, providing traceability between the software artifacts and the +environment that produced them. ## Scope This build notes report covers: -- Version information and commit details -- Changes and new features implemented +- Version information and commit details for this release +- Changes and new features implemented since the previous version - Bugs fixed in this release +- Versions of all tools used in the build and compliance pipeline -## Generation Source +## References -This report is automatically generated by the BuildMark tool, analyzing the -Git repository history and issue tracking information. It provides evidence of -changes made to the SpdxModel library for SPDX SBOM manipulation. - -## Audience - -This document is intended for: - -- Software developers working on SpdxModel -- Users evaluating what has changed in this release -- Project stakeholders tracking progress -- Contributors understanding recent changes +- [SpdxModel releases](https://github.com/demaconsulting/SpdxModel/releases) diff --git a/docs/build_notes/title.txt b/docs/build_notes/title.txt index 906cef4..4024195 100644 --- a/docs/build_notes/title.txt +++ b/docs/build_notes/title.txt @@ -1,16 +1,14 @@ --- -title: SpdxModel -subtitle: Build Notes -author: DEMA Consulting -description: Build notes for the SpdxModel library for SPDX SBOM manipulation +title: "SpdxModel Build Notes" +subtitle: "SPDX document model for .NET" +author: "DEMA Consulting" +description: "Build Notes for SpdxModel" lang: en-US keywords: - - SpdxModel - Build Notes - - Release Notes + - SpdxModel - SPDX - SBOM - C# - .NET - - Documentation --- diff --git a/docs/code_quality/introduction.md b/docs/code_quality/introduction.md index a28a185..d05fe7d 100644 --- a/docs/code_quality/introduction.md +++ b/docs/code_quality/introduction.md @@ -1,35 +1,17 @@ # Introduction -This document contains the code quality analysis report for the SpdxModel project. +This document records the static analysis results for SpdxModel. ## Purpose -This report serves as evidence that the SpdxModel codebase maintains good quality -standards. It provides a comprehensive analysis of code quality metrics, including -quality gate status, code issues, security hotspots, technical debt, and code coverage. +To provide evidence that SpdxModel has been analyzed for code quality issues and that any findings have been +reviewed and resolved or accepted. ## Scope -This code quality report covers: +Covers static analysis of all source code in `src/` for SpdxModel using CodeQL (SARIF) and SonarCloud. +Test code is excluded from static analysis requirements. -- Quality gate status and conditions -- Code issues categorized by type and severity -- Security hotspots requiring review -- Technical debt assessment -- Code coverage and duplication metrics +## References -## Analysis Source - -This report contains quality analysis results captured at the time this version of SpdxModel -was built. It serves as evidence that the code maintains good quality standards and provides -transparency about the project's code health. The analysis includes results from various -quality tools run during the build process. - -## Audience - -This document is intended for: - -- Software developers working on SpdxModel -- Quality assurance teams reviewing code quality -- Project stakeholders evaluating project health -- Contributors understanding quality standards +- [SpdxModel releases](https://github.com/demaconsulting/SpdxModel/releases) diff --git a/docs/code_quality/title.txt b/docs/code_quality/title.txt index 28673de..e7611b3 100644 --- a/docs/code_quality/title.txt +++ b/docs/code_quality/title.txt @@ -1,15 +1,15 @@ --- title: SpdxModel Code Quality -subtitle: Code Quality Report +subtitle: SPDX document model for .NET author: DEMA Consulting -description: Code Quality Report for the SpdxModel C# library for serializing and deserializing SPDX SBOMs +description: Code Quality Report for SpdxModel lang: en-US keywords: - SpdxModel - Code Quality - - SonarCloud + - Static Analysis - CodeQL - - Analysis + - SonarCloud - C# - .NET - SPDX diff --git a/docs/code_review_plan/definition.yaml b/docs/code_review_plan/definition.yaml index 3a24f0b..5b612b8 100644 --- a/docs/code_review_plan/definition.yaml +++ b/docs/code_review_plan/definition.yaml @@ -1,11 +1,9 @@ --- -resource-path: - - docs/code_review_plan - - docs/template +resource-path: [docs/code_review_plan, docs/template] input-files: - docs/code_review_plan/title.txt - docs/code_review_plan/introduction.md - - docs/code_review_plan/plan.md + - docs/code_review_plan/generated/plan.md # Generated by ReviewMark (formal review plan) template: template.html table-of-contents: true number-sections: true diff --git a/docs/code_review_plan/introduction.md b/docs/code_review_plan/introduction.md index 2cfb085..829bbf5 100644 --- a/docs/code_review_plan/introduction.md +++ b/docs/code_review_plan/introduction.md @@ -1,33 +1,16 @@ # Introduction -This document contains the review plan for the SpdxModel project. +This document defines the formal code review plan for SpdxModel. ## Purpose -This review plan provides a comprehensive overview of all files requiring formal review -in the SpdxModel project. It identifies which review-sets cover which -files and serves as evidence that every file requiring review is covered by at least -one named review-set. +To define review-sets and the evidence required to demonstrate that each software item +has been reviewed according to the project's compliance requirements. ## Scope -This review plan covers: +This document covers all review-sets defined in `.reviewmark.yaml` for SpdxModel. -- C# source code files requiring formal review -- YAML configuration and requirements files requiring formal review -- Mapping of reviewed files to named review-sets +## References -## Generation Source - -This plan is automatically generated by the ReviewMark tool, analyzing the -`.reviewmark.yaml` configuration and the review evidence store. It serves as evidence -that every file requiring review is covered by a current, valid review. - -## Audience - -This document is intended for: - -- Software developers working on SpdxModel -- Quality assurance teams validating review coverage -- Project stakeholders reviewing compliance status -- Auditors verifying that all required files have been reviewed +- [SpdxModel releases](https://github.com/demaconsulting/SpdxModel/releases) diff --git a/docs/code_review_plan/title.txt b/docs/code_review_plan/title.txt index 227b7ca..b9c6e94 100644 --- a/docs/code_review_plan/title.txt +++ b/docs/code_review_plan/title.txt @@ -1,13 +1,13 @@ --- -title: SpdxModel Review Plan -subtitle: File Review Plan for the SpdxModel Library -author: DEMA Consulting -description: File Review Plan for the SpdxModel Library +title: "SpdxModel Code Review Plan" +subtitle: "SPDX document model for .NET" +author: "DEMA Consulting" +description: "Code Review Plan for SpdxModel" lang: en-US keywords: + - Code Review - SpdxModel - - Review Plan - - File Reviews + - C# - .NET - - Library + - SPDX --- diff --git a/docs/code_review_report/definition.yaml b/docs/code_review_report/definition.yaml index 6498e6c..bc8c94d 100644 --- a/docs/code_review_report/definition.yaml +++ b/docs/code_review_report/definition.yaml @@ -1,11 +1,9 @@ --- -resource-path: - - docs/code_review_report - - docs/template +resource-path: [docs/code_review_report, docs/template] input-files: - docs/code_review_report/title.txt - docs/code_review_report/introduction.md - - docs/code_review_report/report.md + - docs/code_review_report/generated/report.md # Generated by ReviewMark (completed review records) template: template.html table-of-contents: true number-sections: true diff --git a/docs/code_review_report/introduction.md b/docs/code_review_report/introduction.md index e834541..4a432e3 100644 --- a/docs/code_review_report/introduction.md +++ b/docs/code_review_report/introduction.md @@ -1,33 +1,16 @@ # Introduction -This document contains the review report for the SpdxModel project. +This document records the completed formal code reviews for SpdxModel. ## Purpose -This review report provides evidence that each review-set is current — the review -evidence matches the current file fingerprints. It confirms that all formal reviews -conducted for SpdxModel remain valid for the current state of the -reviewed files. +To provide compliance evidence that all required review-sets have been reviewed +and approved according to the project's review plan. ## Scope -This review report covers: +This document covers all completed reviews for review-sets defined in `.reviewmark.yaml`. -- Current review-set status (current, stale, or missing) -- File fingerprints and review evidence matching -- Review coverage verification +## References -## Generation Source - -This report is automatically generated by the ReviewMark tool, comparing the current -file fingerprints against the review evidence store. It serves as evidence that all -review-sets are current and no reviewed file has changed since its review was conducted. - -## Audience - -This document is intended for: - -- Software developers working on SpdxModel -- Quality assurance teams validating review currency -- Project stakeholders reviewing compliance status -- Auditors verifying that all reviews remain valid for the current release +- [SpdxModel releases](https://github.com/demaconsulting/SpdxModel/releases) diff --git a/docs/code_review_report/title.txt b/docs/code_review_report/title.txt index 029a5d6..4ab7a5a 100644 --- a/docs/code_review_report/title.txt +++ b/docs/code_review_report/title.txt @@ -1,13 +1,13 @@ --- -title: SpdxModel Review Report -subtitle: File Review Report for the SpdxModel Library -author: DEMA Consulting -description: File Review Report for the SpdxModel Library +title: "SpdxModel Code Review Report" +subtitle: "SPDX document model for .NET" +author: "DEMA Consulting" +description: "Code Review Report for SpdxModel" lang: en-US keywords: + - Code Review - SpdxModel - - Review Report - - File Reviews + - SPDX - .NET - - Library + - C# --- diff --git a/docs/design/definition.yaml b/docs/design/definition.yaml index da2bd1d..9ce59f5 100644 --- a/docs/design/definition.yaml +++ b/docs/design/definition.yaml @@ -5,7 +5,7 @@ resource-path: input-files: - docs/design/title.txt - docs/design/introduction.md - - docs/design/spdx-model/spdx-model.md + - docs/design/spdx-model.md - docs/design/spdx-model/spdx-document.md - docs/design/spdx-model/spdx-element.md - docs/design/spdx-model/spdx-package.md diff --git a/docs/design/introduction.md b/docs/design/introduction.md index 0d0ce7a..053b0a1 100644 --- a/docs/design/introduction.md +++ b/docs/design/introduction.md @@ -1,25 +1,28 @@ -# DemaConsulting.SpdxModel Design Documentation +# Introduction + +SpdxModel is a .NET library for working with SPDX (Software Package Data Exchange) documents. +It provides a comprehensive in-memory model for reading, manipulating, and writing SPDX Software +Bill of Materials (SBOM) files in JSON format. The library is organized into a root data model +system with IO and Transform subsystems. ## Purpose -This document provides the design overview for the DemaConsulting.SpdxModel library, a .NET library -for reading, writing, and manipulating SPDX (Software Package Data Exchange) documents. It serves as -the entry point for the design documentation, providing architectural context for formal code review, -compliance auditing, and maintenance support. +This document defines the design for each software item in SpdxModel — full architectural and +detailed design for local items (systems, subsystems, and units). A reviewer should be able to +understand how each item satisfies its requirements without reading source code. ## Scope -This design documentation covers the DemaConsulting.SpdxModel library, including: +Local items: -- The SPDX data model (documents, packages, files, snippets, relationships, annotations, checksums, etc.) -- JSON serialization and deserialization (SPDX 2.2 and SPDX 2.3) -- Relationship manipulation utilities +- **SpdxModel**: system, subsystem, and unit design. -Excluded from scope: +Out of scope: -- Consumer application code using this library -- CI/CD pipeline configuration +- Test projects +- Build pipeline - NuGet package distribution infrastructure +- The internal design of OTS software items ## Software Structure @@ -48,92 +51,88 @@ DemaConsulting.SpdxModel (System) └── SpdxSnippet (Unit) ``` -OTS Software Items: - -- MSTest — unit test framework -- ReqStream — requirements traceability enforcement -- BuildMark — build notes documentation generation -- VersionMark — tool version documentation -- SarifMark — CodeQL SARIF report generation -- SonarMark — SonarCloud quality report generation - ## Folder Layout -```text -docs/design/ -├── introduction.md -└── spdx-model/ - ├── spdx-model.md - ├── io/ - │ ├── io.md - │ ├── spdx-2-json-deserializer.md - │ ├── spdx-2-json-serializer.md - │ └── spdx-constants.md - ├── transform/ - │ ├── transform.md - │ └── spdx-relationships.md - ├── spdx-annotation.md - ├── spdx-checksum.md - ├── spdx-creation-information.md - ├── spdx-document.md - ├── spdx-element.md - ├── spdx-external-document-reference.md - ├── spdx-external-reference.md - ├── spdx-extracted-licensing-info.md - ├── spdx-file.md - ├── spdx-helpers.md - ├── spdx-license-element.md - ├── spdx-package-verification-code.md - ├── spdx-package.md - ├── spdx-relationship.md - └── spdx-snippet.md -``` - ```text src/DemaConsulting.SpdxModel/ ├── IO/ │ ├── Spdx2JsonDeserializer.cs — SPDX 2.x JSON deserialization │ ├── Spdx2JsonSerializer.cs — SPDX 2.x JSON serialization -│ └── SpdxConstants.cs — SPDX constants +│ └── SpdxConstants.cs — SPDX JSON field name constants ├── Transform/ │ └── SpdxRelationships.cs — Relationship manipulation utilities ├── SpdxAnnotation.cs — Annotation data model -├── SpdxAnnotationType.cs — Annotation type enum +├── SpdxAnnotationType.cs — Annotation type enumeration ├── SpdxChecksum.cs — Checksum data model -├── SpdxChecksumAlgorithm.cs — Checksum algorithm enum +├── SpdxChecksumAlgorithm.cs — Checksum algorithm enumeration ├── SpdxCreationInformation.cs — Creation information data model -├── SpdxDocument.cs — Document data model -├── SpdxElement.cs — Base element class +├── SpdxDocument.cs — Root document data model +├── SpdxElement.cs — Abstract base element class ├── SpdxExternalDocumentReference.cs — External document reference model ├── SpdxExternalReference.cs — External reference data model -├── SpdxExtractedLicensingInfo.cs — Extracted licensing info model +├── SpdxExtractedLicensingInfo.cs — Extracted licensing information model ├── SpdxFile.cs — File data model -├── SpdxFileType.cs — File type enum -├── SpdxHelpers.cs — Helper utilities -├── SpdxLicenseElement.cs — License element base class +├── SpdxFileType.cs — File type enumeration +├── SpdxHelpers.cs — Shared utility functions +├── SpdxLicenseElement.cs — Abstract license element base class ├── SpdxPackage.cs — Package data model ├── SpdxPackageVerificationCode.cs — Package verification code model -├── SpdxReferenceCategory.cs — Reference category enum +├── SpdxReferenceCategory.cs — Reference category enumeration ├── SpdxRelationship.cs — Relationship data model -├── SpdxRelationshipType.cs — Relationship type enum +├── SpdxRelationshipType.cs — Relationship type enumeration └── SpdxSnippet.cs — Snippet data model test/DemaConsulting.SpdxModel.Tests/ ├── IO/ │ ├── Examples/ — Test example JSON files -│ └── (Spdx2JsonDeserialize*.cs and Spdx2JsonSerialize*.cs test files) +│ ├── Spdx2JsonDeserialize*.cs — Deserializer unit tests +│ ├── Spdx2JsonSerialize*.cs — Serializer unit tests +│ └── SpdxModelIOTests.cs — IO subsystem integration tests ├── Transforms/ -│ └── SpdxRelationshipsTests.cs — Relationship utility tests -├── SpdxAnnotationTests.cs -├── SpdxChecksumTests.cs -├── SpdxCreationInformationTests.cs -├── SpdxDocumentTests.cs -├── SpdxExternalDocumentReferenceTests.cs -├── SpdxExternalReferenceTests.cs -├── SpdxExtractedLicensingInfoTests.cs -├── SpdxFileTests.cs -├── SpdxPackageTests.cs -├── SpdxPackageVerificationCodeTests.cs -├── SpdxRelationshipTests.cs -└── SpdxSnippetTests.cs +│ ├── SpdxModelTransformTests.cs — Transform subsystem integration tests +│ └── SpdxRelationshipsTests.cs — Relationship utilities tests +├── SpdxModelTests.cs — System-level integration tests +├── SpdxAnnotationTests.cs — SpdxAnnotation unit tests +├── SpdxChecksumTests.cs — SpdxChecksum unit tests +├── SpdxCreationInformationTests.cs — SpdxCreationInformation unit tests +├── SpdxDocumentTests.cs — SpdxDocument unit tests +├── SpdxExternalDocumentReferenceTests.cs — SpdxExternalDocumentReference unit tests +├── SpdxExternalReferenceTests.cs — SpdxExternalReference unit tests +├── SpdxExtractedLicensingInfoTests.cs — SpdxExtractedLicensingInfo unit tests +├── SpdxFileTests.cs — SpdxFile unit tests +├── SpdxHelpersTests.cs — SpdxHelpers unit tests +├── SpdxPackageTests.cs — SpdxPackage unit tests +├── SpdxPackageVerificationCodeTests.cs — SpdxPackageVerificationCode unit tests +├── SpdxRelationshipTests.cs — SpdxRelationship unit tests +└── SpdxSnippetTests.cs — SpdxSnippet unit tests ``` + +## Companion Artifact Structure + +Each local software item has corresponding artifacts in parallel directory trees: + +- Requirements: `docs/reqstream/spdx-model/spdx-model.yaml`, + `docs/reqstream/spdx-model[/{subsystem-name}...]/{item}.yaml` +- Design: `docs/design/spdx-model.md`, + `docs/design/spdx-model[/{subsystem-name}...]/{item}.md` +- Verification: `docs/verification/spdx-model.md`, + `docs/verification/spdx-model[/{subsystem-name}...]/{item}.md` +- Source: `src/DemaConsulting.SpdxModel[/{SubsystemName}...]/{Item}.cs` +- Tests: `test/DemaConsulting.SpdxModel.Tests[/{SubsystemName}...]/{Item}Tests.cs` + +Review-sets: defined in `.reviewmark.yaml` + +## References + +- [REF-1] SpdxModel releases, + +## Structural Deviation + +The companion artifact layout described in this document places subsystem design and verification +files at the subsystem level (e.g., `docs/design/spdx-model/io/` for the IO subsystem). +In practice, subsystem design files (`transform.md`, `io.md`) and verification files are located +inside their respective subsystem subfolders rather than at the parent `spdx-model/` level. +This layout was chosen for consistency with the IO subsystem file organization conventions +adopted early in the project and is accepted as a project-wide structural deviation. +Existing file references in review-sets and traceability tooling reflect the actual folder +layout and do not require updating. diff --git a/docs/design/spdx-model.md b/docs/design/spdx-model.md new file mode 100644 index 0000000..9420262 --- /dev/null +++ b/docs/design/spdx-model.md @@ -0,0 +1,123 @@ +# SpdxModel + +DemaConsulting.SpdxModel is a .NET library providing a complete implementation of the SPDX +(Software Package Data Exchange) data model. The library exposes an in-memory object model +representing all SPDX document elements, with serialization and transformation capabilities. + +## Architecture + +```mermaid +flowchart TD + subgraph IO + Spdx2JsonDeserializer + Spdx2JsonSerializer + SpdxConstants + end + subgraph Transform + SpdxRelationships + end + SpdxDocument + SpdxElement + SpdxLicenseElement + SpdxPackage + SpdxFile + SpdxSnippet + SpdxRelationship + SpdxAnnotation + SpdxChecksum + SpdxCreationInformation + SpdxExternalDocumentReference + SpdxExternalReference + SpdxExtractedLicensingInfo + SpdxPackageVerificationCode + SpdxHelpers + + SpdxDocument --> SpdxElement + SpdxLicenseElement --> SpdxElement + SpdxPackage --> SpdxLicenseElement + SpdxFile --> SpdxLicenseElement + SpdxSnippet --> SpdxLicenseElement + SpdxRelationship --> SpdxElement + SpdxAnnotation --> SpdxElement + + Spdx2JsonDeserializer --> SpdxDocument + Spdx2JsonDeserializer --> SpdxConstants + Spdx2JsonSerializer --> SpdxDocument + Spdx2JsonSerializer --> SpdxConstants + SpdxRelationships --> SpdxDocument + SpdxRelationships --> SpdxRelationship +``` + +## External Interfaces + +**SPDX JSON Input**: JSON file conforming to the SPDX 2.2 or 2.3 JSON schema. + +- *Type*: File (JSON) +- *Role*: Consumer +- *Contract*: `Spdx2JsonDeserializer.Deserialize(string)` accepts raw JSON text and returns a + populated `SpdxDocument`. +- *Constraints*: Input must be valid JSON; SPDX field validation is performed after + deserialization via `SpdxDocument.Validate()`. + +**SPDX JSON Output**: JSON file conforming to the SPDX 2.3 JSON schema. + +- *Type*: File (JSON) +- *Role*: Provider +- *Contract*: `Spdx2JsonSerializer.Serialize(SpdxDocument)` returns a complete SPDX 2.3 JSON + string. +- *Constraints*: Optional fields are omitted when empty or null; output always conforms to SPDX + 2.3 schema. + +**In-Process .NET Public API**: Object model and transformation API consumed by .NET callers. + +- *Type*: In-process .NET public API +- *Role*: Provider +- *Contract*: Exposes `SpdxDocument` and all data model classes, `Spdx2JsonDeserializer`, + `Spdx2JsonSerializer`, and `SpdxRelationships` as public types. +- *Constraints*: Targets `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`. + +**Error Handling**: + +- `Spdx2JsonDeserializer.Deserialize` throws `System.Text.Json.JsonException` when the input + is fatally malformed JSON that cannot be parsed. Missing or unknown SPDX fields do not throw; + they produce default or empty values in the resulting `SpdxDocument`. +- `SpdxDocument.Validate(List)` never throws. It appends human-readable issue strings + to the supplied list; an empty list after the call indicates a valid document. + +## Dependencies + +- **System.Text.Json**: used by the IO subsystem for JSON DOM parsing and serialization; + available in-box on modern .NET targets and via NuGet for .NET Standard 2.0. + +## Risk Control Measures + +N/A - not a safety-classified software item. + +## Data Flow + +```mermaid +flowchart LR + A[JSON File] --> B[Spdx2JsonDeserializer] + B --> C[SpdxDocument] + C --> D[Transform utilities] + D --> C + C --> E[Spdx2JsonSerializer] + E --> F[JSON File] +``` + +1. Caller provides a JSON string to `Spdx2JsonDeserializer.Deserialize`. +2. The deserializer uses `System.Text.Json.Nodes` to parse the JSON DOM. +3. Per-element helpers populate a new `SpdxDocument` instance. +4. The caller inspects or modifies the `SpdxDocument` in memory, optionally using + `SpdxRelationships` utilities. +5. `Spdx2JsonSerializer.Serialize` traverses the `SpdxDocument` and produces a JSON string. + +## Design Constraints + +- Targets `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0` simultaneously. +- Minimal runtime dependencies: relies on BCL/framework APIs where possible; compatibility NuGet + packages used on older targets. +- Nullable reference types enabled: all public API members declare nullability explicitly. +- Data model classes use public mutable properties to allow flexible construction; deep-copy + methods provide safe cloning. +- No static mutable state in data model classes; thread safety is the caller's responsibility. diff --git a/docs/design/spdx-model/io/io.md b/docs/design/spdx-model/io/io.md index 28dd3af..ca45b77 100644 --- a/docs/design/spdx-model/io/io.md +++ b/docs/design/spdx-model/io/io.md @@ -1,55 +1,62 @@ -# IO Subsystem Design - -## Purpose +### IO The IO subsystem provides JSON serialization and deserialization for SPDX 2.x documents, converting between the in-memory `SpdxDocument` object model and SPDX JSON files conforming to the SPDX 2.2 and 2.3 specifications. -## Units - -| Unit | File | Responsibility | -| ---- | ---- | -------------- | -| `Spdx2JsonDeserializer` | `IO/Spdx2JsonDeserializer.cs` | Reads SPDX 2.x JSON into the object model | -| `Spdx2JsonSerializer` | `IO/Spdx2JsonSerializer.cs` | Writes the object model to SPDX 2.x JSON | -| `SpdxConstants` | `IO/SpdxConstants.cs` | String constants for SPDX JSON field names | +#### Overview -## Design +The IO subsystem is responsible for all JSON I/O for SPDX 2.x documents. It contains three +units: `Spdx2JsonDeserializer`, which reads JSON text into a `SpdxDocument`; `Spdx2JsonSerializer`, +which writes a `SpdxDocument` back to JSON text; and `SpdxConstants`, which holds the JSON field +name strings used by both the deserializer and serializer. -### Spdx2JsonDeserializer +#### Interfaces -`Spdx2JsonDeserializer` reads a JSON stream or string and populates a `SpdxDocument`. It uses -`System.Text.Json.Nodes`, parsing input with `JsonNode.Parse` and traversing `JsonObject` and -`JsonArray` nodes to reconstruct each element. Both SPDX 2.2 and 2.3 JSON schemas are -supported; version differences are handled transparently during parsing. +**Spdx2JsonDeserializer.Deserialize**: Reads an SPDX 2.x JSON string into an `SpdxDocument`. -Key design decisions: +- *Type*: In-process .NET public API +- *Role*: Provider +- *Contract*: Accepts a raw JSON string; returns a fully populated `SpdxDocument`. +- *Constraints*: Input must be valid JSON; unsupported or unknown fields are silently ignored. -- DOM-based parsing via `JsonNode` (rather than streaming) to allow forward references between - document elements -- Graceful handling of optional SPDX fields (missing fields result in default values) +**Spdx2JsonSerializer.Serialize**: Writes an `SpdxDocument` to an SPDX 2.3 JSON string. -### Spdx2JsonSerializer +- *Type*: In-process .NET public API +- *Role*: Provider +- *Contract*: Accepts an `SpdxDocument`; returns a complete SPDX 2.3 JSON string. +- *Constraints*: Optional fields are omitted when null or empty. -`Spdx2JsonSerializer` takes an `SpdxDocument`, builds a JSON DOM using `JsonObject` and -`JsonArray`, and emits the final JSON with `ToJsonString(...)`. It iterates over each element -collection in document order, creating the appropriate JSON structure for each SPDX element -type. +**Error Handling**: -Key design decisions: +- `Spdx2JsonDeserializer.Deserialize` throws `System.Text.Json.JsonException` when the input is + fatally malformed JSON that cannot be parsed by `JsonNode.Parse`. Missing or unknown SPDX fields + do not throw; the corresponding properties in the returned `SpdxDocument` receive their default + values (empty strings for required fields, null for optional fields). +- `Spdx2JsonSerializer.Serialize` does not throw under normal operation. It silently omits null + or empty optional fields from the output JSON. -- Output follows SPDX 2.3 JSON schema by default -- Optional fields are omitted when empty or null to keep output clean +#### Design -### SpdxConstants +Deserialization uses `System.Text.Json.Nodes` DOM-based parsing so that all elements are +available before cross-references are resolved: -`SpdxConstants` is a static class holding string constants for every JSON property name used in -the SPDX 2.x JSON format. Using named constants prevents typos and centralizes the mapping -between the object model and the serialized form. +1. Caller invokes `Spdx2JsonDeserializer.Deserialize(jsonString)`. +2. `JsonNode.Parse` produces a `JsonObject` root. +3. `DeserializeDocument` traverses the root object, calling per-element helpers + (`DeserializePackage`, `DeserializeFile`, etc.) for each child array. +4. `SpdxConstants` supplies the JSON property name strings to avoid hard-coded literals. +5. A populated `SpdxDocument` is returned to the caller. -## Dependencies +Serialization follows the reverse path: -The IO subsystem depends on: +1. Caller invokes `Spdx2JsonSerializer.Serialize(document)`. +2. `SerializeDocument` builds a `JsonObject` root. +3. Per-element helpers (`SerializePackage`, `SerializeFile`, etc.) append child objects. +4. `SpdxConstants` supplies property names. +5. `ToJsonString(...)` produces the final JSON string. -- `System.Text.Json` (BCL / NuGet) -- All data model units in the root namespace (`SpdxDocument`, `SpdxPackage`, etc.) +Element-level field preservation (for example, verifying that all package fields survive +a round trip) is the responsibility of the unit-level IO tests, not the subsystem-level +integration tests. The subsystem-level tests verify that a complete document survives a +round trip and passes validation. diff --git a/docs/design/spdx-model/io/spdx-2-json-deserializer.md b/docs/design/spdx-model/io/spdx-2-json-deserializer.md index bf6e856..4b0a432 100644 --- a/docs/design/spdx-model/io/spdx-2-json-deserializer.md +++ b/docs/design/spdx-model/io/spdx-2-json-deserializer.md @@ -1,35 +1,76 @@ -# Spdx2JsonDeserializer Unit Design +### Spdx2JsonDeserializer -## Purpose +#### Purpose `Spdx2JsonDeserializer` reads SPDX 2.x JSON documents and populates the in-memory `SpdxDocument` object model. It supports both the SPDX 2.2 and SPDX 2.3 JSON schemas, handling version differences transparently during parsing. -## Design +#### Data Model -`Spdx2JsonDeserializer` is a public static class with no instance state. All public entry points -accept either a JSON string or a `JsonNode` and return strongly typed model objects. +N/A - `Spdx2JsonDeserializer` is a public static class with no instance state. -Key design decisions: +#### Key Methods -- DOM-based parsing via `System.Text.Json.Nodes` (`JsonNode`/`JsonArray`) to allow forward - references between document elements before the full document is assembled. -- Graceful handling of optional SPDX fields: missing properties result in default values rather - than exceptions. -- Per-element `Deserialize*` methods (`DeserializePackage`, `DeserializeFile`, etc.) are public - to support targeted unit testing and partial deserialization. +**Deserialize**: Entry point — parses a raw JSON string into an `SpdxDocument`. -Key methods: +- *Parameters*: `string json` — raw SPDX 2.x JSON text. +- *Returns*: `SpdxDocument` — fully populated in-memory document. +- *Preconditions*: `json` must be valid JSON text. +- *Postconditions*: The returned `SpdxDocument` reflects all elements present in the JSON input; + unrecognized fields are silently ignored. -| Method | Description | -| ------ | ----------- | -| `Deserialize(string)` | Entry point — parses a raw JSON string into an `SpdxDocument` | -| `DeserializeDocument(JsonNode)` | Converts a parsed `JsonNode` tree into an `SpdxDocument` | -| `Deserialize*(JsonNode?)` | Per-element helpers for each SPDX element type | +**DeserializeDocument**: Converts a parsed `JsonNode` tree into an `SpdxDocument`. -## Dependencies +- *Parameters*: `JsonNode node` — root JSON node from `JsonNode.Parse`. +- *Returns*: `SpdxDocument` — populated document. +- *Preconditions*: `node` must be a `JsonObject` representing the SPDX document root. +- *Postconditions*: All child element arrays are deserialized by the corresponding per-element + helpers. -- `System.Text.Json` (BCL) — JSON DOM parsing via `JsonNode` -- `SpdxDocument` and all data model units in the root namespace -- `SpdxConstants` — string constants for JSON property names +**Deserialize\* helpers**: Per-element deserialization methods (`DeserializePackage`, +`DeserializeFile`, `DeserializeSnippet`, `DeserializeRelationship`, `DeserializeAnnotation`, +`DeserializeChecksum`, `DeserializeExternalDocumentReference`, `DeserializeExternalReference`, +`DeserializeExtractedLicensingInfo`, `DeserializeCreationInformation`). + +- *Parameters*: `JsonNode? node` — the element's JSON node, which may be null. +- *Returns*: The corresponding model type (e.g., `SpdxPackage`), or a default instance when + `node` is null. +- *Preconditions*: none. +- *Postconditions*: Missing optional fields result in default values; no exception is thrown for + absent properties. + +**DeserializeVerificationCode**: Deserializes an optional `SpdxPackageVerificationCode`. + +- *Parameters*: `JsonNode? node` — the package verification code JSON node, which may be null. +- *Returns*: `SpdxPackageVerificationCode?` — a populated instance when `node` is non-null; + `null` when `node` is null (indicating the package verification code was absent in the JSON). +- *Preconditions*: none. +- *Postconditions*: Returns `null` when `node` is `null`; does not return a default instance. + +**Find (private helper)**: Locates a named descendant node within a JSON tree, descending +through arrays automatically. + +- *Algorithm*: Accepts a root `JsonNode?`, an index into the `names` path array, and the full + `names` array. When the current node is a `JsonArray`, each element is searched recursively + and the first non-null result is returned (enabling path resolution through SPDX `ranges` + arrays). When the current node is an object, the next name in the path is looked up and + recursion continues. Used by `DeserializeSnippet` to extract `startPointer` and `endPointer` + values from the SPDX 2.x `ranges` array structure without knowing the array index in advance. + +#### Error Handling + +Missing or null JSON properties produce default values rather than exceptions. Input must be +valid JSON; a `JsonException` from `System.Text.Json` will propagate to the caller if the input +is malformed. + +#### Dependencies + +- **System.Text.Json** — JSON DOM parsing via `JsonNode`, `JsonObject`, and `JsonArray`. +- **SpdxDocument** and all data model units in the root namespace. +- **SpdxConstants** — string constants for JSON property names. + +#### Callers + +- External consumers of the library who call `Spdx2JsonDeserializer.Deserialize` to load SPDX + documents. diff --git a/docs/design/spdx-model/io/spdx-2-json-serializer.md b/docs/design/spdx-model/io/spdx-2-json-serializer.md index e40ca4f..445f2fc 100644 --- a/docs/design/spdx-model/io/spdx-2-json-serializer.md +++ b/docs/design/spdx-model/io/spdx-2-json-serializer.md @@ -1,35 +1,76 @@ -# Spdx2JsonSerializer Unit Design +### Spdx2JsonSerializer -## Purpose +#### Purpose `Spdx2JsonSerializer` converts an in-memory `SpdxDocument` object model to an SPDX 2.3 JSON string. It is the counterpart to `Spdx2JsonDeserializer` and completes the round-trip serialization support for the IO subsystem. -## Design +#### Data Model -`Spdx2JsonSerializer` is a public static class with no instance state. All public methods -accept strongly typed model objects and return `JsonObject`/`JsonArray` nodes or a final JSON -string. +N/A - `Spdx2JsonSerializer` is a public static class with no instance state. -Key design decisions: +#### Key Methods -- Output conforms to SPDX 2.3 JSON schema. -- Optional fields are omitted entirely (not written as `null`) when empty or null to keep - output concise and compatible with strict schema validators. -- Per-element `Serialize*` methods (`SerializePackage`, `SerializeFile`, etc.) are public to - support targeted unit testing and partial serialization. +**Serialize**: Entry point — returns a complete SPDX 2.3 JSON string. -Key methods: +- *Parameters*: `SpdxDocument document` — the in-memory document to serialize. +- *Returns*: `string` — SPDX 2.3 JSON text. +- *Preconditions*: none. +- *Postconditions*: The returned string is valid JSON conforming to the SPDX 2.3 schema; optional + fields absent from the model are omitted from the output. -| Method | Description | -| ------ | ----------- | -| `Serialize(SpdxDocument)` | Entry point — returns a complete SPDX JSON string | -| `SerializeDocument(SpdxDocument)` | Converts an `SpdxDocument` to a `JsonObject` | -| `Serialize*(…)` | Per-element helpers for each SPDX element type | +**SerializeDocument**: Converts an `SpdxDocument` to a `JsonObject`. -## Dependencies +- *Parameters*: `SpdxDocument document` — document to serialize. +- *Returns*: `JsonObject` — root JSON object with all element arrays populated. +- *Preconditions*: none. +- *Postconditions*: All element arrays are serialized by the corresponding per-element helpers. -- `System.Text.Json` (BCL) — JSON node construction via `JsonObject`/`JsonArray` -- `SpdxDocument` and all data model units in the root namespace -- `SpdxConstants` — string constants for JSON property names +**Serialize\* helpers**: Per-element serialization methods (`SerializePackage`, `SerializeFile`, +`SerializeSnippet`, `SerializeRelationship`, `SerializeAnnotation`, `SerializeChecksum`, +`SerializeExternalDocumentReference`, `SerializeExternalReference`, +`SerializeExtractedLicensingInfo`, `SerializeCreationInformation`, +`SerializeVerificationCode`). + +- *Parameters*: The corresponding model object. +- *Returns*: A `JsonObject` or `JsonArray` representing the element. +- *Preconditions*: none. +- *Postconditions*: Optional fields that are null or empty are omitted from the output object. + Exception: the top-level `files`, `packages`, `snippets`, and `relationships` arrays are + required by the SPDX 2.x schema and are always emitted even when empty — they are not + optional at the document level. + +**Serialize\*s array helpers**: Per-element-array serialization methods (`SerializePackages`, +`SerializeFiles`, `SerializeSnippets`, `SerializeRelationships`, `SerializeAnnotations`, +`SerializeChecksums`, `SerializeExternalDocumentReferences`, `SerializeExternalReferences`, +`SerializeExtractedLicensingInfos`). + +- *Parameters*: A typed array of the corresponding model objects (e.g., `SpdxPackage[]`). +- *Returns*: A `JsonArray` containing one serialized `JsonObject` per element. +- *Pattern*: Each method creates an empty `JsonArray`, iterates the input array calling + the corresponding singular helper, and returns the populated array. + +#### Error Handling + +No exceptions are thrown for valid model objects. Null or empty optional fields are silently +omitted rather than written as null JSON values. + +Notable conditional serialization behaviors: + +- `SerializeAnnotation`: The `SPDXID` field is conditionally omitted when the annotation's + `Id` is null or empty (annotations on sub-elements often do not carry their own SPDX ID). +- `SerializeSnippet`: A line-range entry is only added to the `ranges` array when both + `SnippetLineStart` and `SnippetLineEnd` are non-zero; otherwise only the byte-range entry + is written. + +#### Dependencies + +- **System.Text.Json** — JSON node construction via `JsonObject` and `JsonArray`. +- **SpdxDocument** and all data model units in the root namespace. +- **SpdxConstants** — string constants for JSON property names. + +#### Callers + +- External consumers of the library who call `Spdx2JsonSerializer.Serialize` to produce SPDX + JSON output. diff --git a/docs/design/spdx-model/io/spdx-constants.md b/docs/design/spdx-model/io/spdx-constants.md index 1c48326..1515351 100644 --- a/docs/design/spdx-model/io/spdx-constants.md +++ b/docs/design/spdx-model/io/spdx-constants.md @@ -1,24 +1,33 @@ -# SpdxConstants Unit Design +### SpdxConstants -## Purpose +#### Purpose `SpdxConstants` is a static class that centralizes all JSON property-name strings used when serializing and deserializing SPDX 2.x JSON documents. It eliminates hard-coded string literals -scattered throughout the IO subsystem and provides a single place to update field names if the +throughout the IO subsystem and provides a single place to update field names if the SPDX specification changes. -## Design +#### Data Model -`SpdxConstants` is a non-instantiable `internal` static class containing only `internal const string` fields. -Each constant corresponds to one JSON property name in the SPDX 2.x JSON schema (e.g., -`FieldSpdxId`, `FieldName`, `FieldVersionInfo`). +N/A - `SpdxConstants` contains only `internal const string` fields and no instance state. +Representative constants include `FieldSpdxId` (`"SPDXID"`), `FieldName` (`"name"`), +`FieldVersionInfo` (`"versionInfo"`), `FieldPackages` (`"packages"`), +`FieldRelationships` (`"relationships"`), `FieldAnnotationType` (`"annotationType"`) and over +sixty other property-name constants covering all SPDX 2.x JSON fields. -Key design decisions: +#### Key Methods -- All constants are `const string` to allow use as switch-case labels and compile-time - embedding. -- No logic or state — purely a name registry. +N/A - `SpdxConstants` contains no methods; it is a pure name registry of `const string` values. -## Dependencies +#### Error Handling -- None (no external dependencies; consumed by `Spdx2JsonDeserializer` and `Spdx2JsonSerializer`) +N/A - no logic is executed; all values are compile-time constants. + +#### Dependencies + +N/A - no external dependencies; consumed by `Spdx2JsonDeserializer` and `Spdx2JsonSerializer`. + +#### Callers + +- **Spdx2JsonDeserializer** — uses constants as JSON property name keys when reading elements. +- **Spdx2JsonSerializer** — uses constants as JSON property name keys when writing elements. diff --git a/docs/design/spdx-model/spdx-annotation.md b/docs/design/spdx-model/spdx-annotation.md index 9587ad3..e6a7c31 100644 --- a/docs/design/spdx-model/spdx-annotation.md +++ b/docs/design/spdx-model/spdx-annotation.md @@ -1,32 +1,127 @@ -# SpdxAnnotation Unit Design +## SpdxAnnotation -## Purpose +### Purpose `SpdxAnnotation` represents an SPDX annotation — a comment or review note attached to any SPDX element by a person, organization, or tool. Annotations support compliance workflows where reviewers document findings about software components. -## Design +### Data Model -`SpdxAnnotation` is a sealed class that extends `SpdxElement` (inheriting the `Id` field). +**Annotator**: `string` — Person, organization, or tool that made the annotation, in the format +`Person: name`, `Organization: name`, or `Tool: name`. -Data members: +**Date**: `string` — ISO 8601 UTC timestamp of the annotation (e.g., `2023-01-01T00:00:00Z`). -| Property | Type | Description | -| -------- | ---- | ----------- | -| `Annotator` | `string` | Person, organization, or tool that made the annotation | -| `Date` | `string` | ISO 8601 UTC timestamp of the annotation | -| `Type` | `SpdxAnnotationType` | Enumerated annotation type (Review, Other) | -| `Comment` | `string` | Free-text annotation content | +**Type**: `SpdxAnnotationType` — Enumerated annotation type; either `Review` or `Other`. -Key methods: +**Comment**: `string` — Free-text annotation content describing the finding or note. -- `DeepCopy()` — returns a new `SpdxAnnotation` with all fields copied -- `Enhance(SpdxAnnotation)` — fills in missing fields from another instance -- `Validate(string, List)` — appends validation issues to the supplied list -- `Same` — static `IEqualityComparer` comparing annotator, date, type, and comment +### Key Methods -## Dependencies +**DeepCopy**: Returns a new `SpdxAnnotation` with all fields copied. -- `SpdxElement` (base class) -- `SpdxAnnotationType` (enum) +- *Parameters*: none. +- *Returns*: `SpdxAnnotation` — independent copy of this instance. +- *Preconditions*: none. +- *Postconditions*: The returned instance has the same field values and shares no mutable + references with the original. + +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxAnnotation other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Any empty or default-valued fields in this instance are replaced with the + corresponding non-empty values from `other`. + +**Enhance (static array overload)**: Merges two annotation arrays, returning an updated array. + +- *Parameters*: `SpdxAnnotation[] array` — base array to merge into; `SpdxAnnotation[] others` — + additional annotations to incorporate. +- *Returns*: `SpdxAnnotation[]` — updated array containing all annotations from both inputs. +- *Algorithm*: Iterates `others`; for each item, searches `array` using `Same`. If a match is + found, the existing item is enhanced with the other's field values. If no match is found, a + deep copy of the item is appended. +- *Preconditions*: none. +- *Postconditions*: The returned array contains at least all elements of `array` and at least + one representative of each element in `others`. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `string parent` — identifier of the parent element (e.g. package or file SPDX-ID) + used as a prefix in issue messages; `List issues` — list to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Any missing required fields are recorded as strings in `issues`. + +**Same**: `static IEqualityComparer` — compares annotations by annotator, date, +type, and comment. Used for deduplication when merging annotation arrays. + +### Error Handling + +Validation errors (missing required fields) are collected into a `List` passed to +`Validate` rather than thrown as exceptions. Callers decide whether to surface or suppress the +issues. No exceptions are thrown by `DeepCopy` or `Enhance`. + +### Dependencies + +- **SpdxElement** — base class providing the `Id` property. +- **SpdxAnnotationType** — enumeration for annotation type values. +- **SpdxHelpers** — `IsValidSpdxDateTime` used in `Validate`. + +### SpdxAnnotationType + +`SpdxAnnotationType` is an enumeration of the SPDX annotation type tokens, with round-trip text +conversion provided by `SpdxAnnotationTypeExtensions`. + +#### Purpose + +Enumerate the valid SPDX annotation type strings and provide lossless conversion between the enum +representation used in the in-memory model and the text representation used in SPDX JSON documents. + +#### Data Model + +| Enum Value | Integer | SPDX Text Form | +|------------|---------|----------------| +| `Missing` | -1 | `""` (sentinel; indicates no type has been set) | +| `Review` | 0 | `REVIEW` | +| `Other` | 1 | `OTHER` | + +#### Key Methods + +**FromText**: Converts an SPDX annotation type text string to its enum value. + +- *Signature*: `static SpdxAnnotationType FromText(string annotationType)` +- *Parameters*: `string annotationType` — the raw text from the SPDX document (case-insensitive). +- *Returns*: `SpdxAnnotationType.Missing` when `annotationType` is an empty string; otherwise the + matching enum value. +- *Exceptions*: `InvalidOperationException` — thrown when `annotationType` is a non-empty string + that does not match any known annotation type name. + +**ToText**: Converts an enum value to its SPDX text form. + +- *Signature*: `static string ToText(this SpdxAnnotationType annotationType)` +- *Parameters*: `SpdxAnnotationType annotationType` — the enum value to serialize. +- *Returns*: The canonical SPDX text (e.g., `"REVIEW"`, `"OTHER"`). +- *Exceptions*: `InvalidOperationException` — thrown when the value is `Missing` or is a numeric + value that does not correspond to any named enum member. + +#### Error Handling + +- **`FromText`**: throws `InvalidOperationException` with a message identifying the unsupported + value when given a non-empty string that is not a recognized annotation type token. +- **`ToText`**: throws `InvalidOperationException` when the enum value is `Missing` or does not + correspond to a named enum member. + +#### Dependencies / Callers + +- **Spdx2JsonDeserializer** — calls `FromText` when deserializing annotation type fields from JSON. +- **Spdx2JsonSerializer** — calls `ToText` when serializing annotation type fields to JSON. + +### Callers + +- **SpdxDocument** — holds the document-level `Annotations` array. +- **SpdxLicenseElement** — holds element-level `Annotations` arrays. +- **Spdx2JsonDeserializer** — constructs `SpdxAnnotation` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxAnnotation` instances to JSON. diff --git a/docs/design/spdx-model/spdx-checksum.md b/docs/design/spdx-model/spdx-checksum.md index 46b78ae..867cd16 100644 --- a/docs/design/spdx-model/spdx-checksum.md +++ b/docs/design/spdx-model/spdx-checksum.md @@ -1,29 +1,126 @@ -# SpdxChecksum Unit Design +## SpdxChecksum -## Purpose +### Purpose `SpdxChecksum` represents an SPDX checksum — an algorithm-value pair used to verify the integrity of files and packages. Supporting multiple algorithms provides flexibility across different security policies and tooling ecosystems. -## Design +### Data Model -`SpdxChecksum` is a sealed class with no base class (not an `SpdxElement`). +**Algorithm**: `SpdxChecksumAlgorithm` — Identifies the hash algorithm (e.g., `SHA1`, `SHA256`, +`SHA512`, `MD5`). Defaults to `Missing` when not populated. -Data members: +**Value**: `string` — Lower-case hexadecimal digest value produced by the algorithm. -| Property | Type | Description | -| -------- | ---- | ----------- | -| `Algorithm` | `SpdxChecksumAlgorithm` | Identifies the hash algorithm (SHA1, SHA256, MD5, etc.) | -| `Value` | `string` | Lower-case hexadecimal digest value | +### SpdxChecksumAlgorithm Enumeration -Key methods: +`SpdxChecksumAlgorithm` is an enumeration with a sentinel value and 17 named algorithm values: -- `DeepCopy()` — returns a new `SpdxChecksum` with all fields copied -- `Enhance(SpdxChecksum)` — fills in missing fields from another instance -- `Validate(string, List)` — appends validation issues to the supplied list -- `Same` — static `IEqualityComparer` comparing algorithm and value +| Enum Value | SPDX Text Form | +|----------------|-------------------------| +| `Missing` | `""` (empty string) | +| `Sha1` | `SHA1` | +| `Sha224` | `SHA224` | +| `Sha256` | `SHA256` | +| `Sha384` | `SHA384` | +| `Sha512` | `SHA512` | +| `Md2` | `MD2` | +| `Md4` | `MD4` | +| `Md5` | `MD5` | +| `Md6` | `MD6` | +| `Sha3256` | `SHA3-256` | +| `Sha3384` | `SHA3-384` | +| `Sha3512` | `SHA3-512` | +| `Blake2B256` | `BLAKE2b-256` | +| `Blake2B384` | `BLAKE2b-384` | +| `Blake2B512` | `BLAKE2b-512` | +| `Blake3` | `BLAKE3` | +| `Adler32` | `ADLER32` | -## Dependencies +`SpdxChecksumAlgorithmExtensions` provides two static helper methods: -- `SpdxChecksumAlgorithm` (enum) +**FromText**: Converts an SPDX algorithm text string to its enum value. + +- *Signature*: `static SpdxChecksumAlgorithm FromText(string checksumAlgorithm)` +- *Parameters*: `string checksumAlgorithm` — the SPDX text form of the algorithm (case-insensitive). +- *Returns*: `SpdxChecksumAlgorithm` — the corresponding enum value; returns `Missing` for an + empty string. +- *Exceptions*: `InvalidOperationException` — thrown when the input is a non-empty string that + does not match any known algorithm name (comparison is case-insensitive). +- *Case-insensitivity*: The input is converted to upper-case before comparison, so `"sha1"`, + `"SHA1"`, and `"Sha1"` are all accepted. + +**ToText**: Converts an enum value to its SPDX text form. + +- *Signature*: `static string ToText(this SpdxChecksumAlgorithm checksumAlgorithm)` +- *Parameters*: `SpdxChecksumAlgorithm checksumAlgorithm` — the algorithm enum value. +- *Returns*: `string` — the corresponding SPDX text representation. +- *Exceptions*: `InvalidOperationException` — thrown when the value is `Missing` or is a numeric + value that does not correspond to any named enum member. + +### Key Methods + +**DeepCopy**: Returns a new `SpdxChecksum` with all fields copied. + +- *Parameters*: none. +- *Returns*: `SpdxChecksum` — independent copy of this instance. +- *Preconditions*: none. +- *Postconditions*: The returned instance has the same field values and shares no mutable + references with the original. + +**Enhance (instance)**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxChecksum other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Any empty or default-valued fields in this instance are replaced with + non-empty values from `other`. + +**Enhance (static array merge)**: Merges two checksum arrays by matching on algorithm and value. + +- *Signature*: `static SpdxChecksum[] Enhance(SpdxChecksum[] array, SpdxChecksum[] others)` +- *Parameters*: `SpdxChecksum[] array` — base array; `SpdxChecksum[] others` — additions. +- *Returns*: `SpdxChecksum[]` — merged array. +- *Preconditions*: none. +- *Postconditions*: Entries in `others` that match an existing entry (by `Same.Equals`) are used + to enhance the existing entry. Entries in `others` that do not match any existing entry are + deep-copied and appended. The order of existing entries is preserved. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `string parent` — identifier for error messages; `List issues` — list + to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Missing or malformed fields are recorded in `issues`. Specifically: + - If `Algorithm` is `Missing`, the message `"{parent} Invalid Checksum Algorithm Field - Missing"` is appended. + - If `Algorithm` is a numeric value not defined in the enumeration, the message `"{parent} Invalid Checksum Algorithm Field - Unknown"` is appended. + - If `Value` is empty, the message `"{parent} Invalid Checksum Value Field - Empty"` is appended. + +**Same**: `static IEqualityComparer` — compares checksums by algorithm and value. +Used for deduplication when merging checksum arrays. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. No exceptions are +thrown by `DeepCopy` or `Enhance`. + +- **`FromText`**: throws `InvalidOperationException` when the input string is non-empty and + unrecognized. +- **`ToText`**: throws `InvalidOperationException` when the algorithm value is `Missing` or is + an out-of-range numeric value not corresponding to any named enum member. +- **`Validate` unknown-algorithm branch**: when `Algorithm` holds a numeric value that is not a named member of `SpdxChecksumAlgorithm`, `Validate` appends `"{parent} Invalid Checksum Algorithm Field - Unknown"` to the issues list. + +### Dependencies + +- **SpdxChecksumAlgorithm** — enumeration of supported hash algorithms. +- **SpdxHelpers** — `EnhanceString` utility used in the instance `Enhance` method. + +### Callers + +- **SpdxPackage** — holds the package-level `Checksums` array. +- **SpdxFile** — holds the file-level `Checksums` array. +- **SpdxExternalDocumentReference** — holds a single `Checksum` for document integrity. +- **Spdx2JsonDeserializer** — constructs `SpdxChecksum` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxChecksum` instances to JSON. diff --git a/docs/design/spdx-model/spdx-creation-information.md b/docs/design/spdx-model/spdx-creation-information.md index 78f16ce..5212906 100644 --- a/docs/design/spdx-model/spdx-creation-information.md +++ b/docs/design/spdx-model/spdx-creation-information.md @@ -1,31 +1,71 @@ -# SpdxCreationInformation Unit Design +## SpdxCreationInformation -## Purpose +### Purpose `SpdxCreationInformation` captures the metadata about who created an SPDX document and when. -One instance is required per SPDX document. It enables provenance tracing and forward/backward +One instance is required per `SpdxDocument`. It enables provenance tracing and forward/backward compatibility for processing tools. -## Design +### Data Model -`SpdxCreationInformation` is a sealed class with no base class. +**Creators**: `string[]` — Identifies the persons, organizations, or tools that created the +document, each entry in the format `Person: name`, `Organization: name`, or `Tool: name`. -Data members: +**Created**: `string` — ISO 8601 UTC timestamp of document creation; may be empty for +partially-constructed documents (empty is permitted and skips format validation). -| Property | Type | Description | -| -------- | ---- | ----------- | -| `Creators` | `string[]` | Identifies the persons, organizations, or tools that created the document | -| `Created` | `string` | ISO 8601 UTC timestamp of document creation; may be empty for partially-constructed documents | -| `Comment` | `string?` | Optional creator comment | -| `LicenseListVersion` | `string?` | Optional SPDX license list version used | +**Comment**: `string?` — Optional free-text comment from the creators. -Key methods: +**LicenseListVersion**: `string?` — Optional version string of the SPDX license list used +when constructing the document. -- `DeepCopy()` — returns a new `SpdxCreationInformation` with all fields copied -- `Enhance(SpdxCreationInformation)` — fills in missing fields from another instance -- `Validate(List)` — appends validation issues; validates `Created` format via regex when non-empty - (empty `Created` is permitted and skips format validation) +### Key Methods -## Dependencies +**DeepCopy**: Returns a new `SpdxCreationInformation` with all fields copied. -- `System.Text.RegularExpressions` — used internally to validate the `LicenseListVersion` field format +- *Parameters*: none. +- *Returns*: `SpdxCreationInformation` — independent copy of this instance. +- *Preconditions*: none. +- *Postconditions*: The returned instance has equal field values and shares no mutable references + with the original. + +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxCreationInformation other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: `Creators` is updated to the union of both arrays, deduplicated (preserving + order, removing duplicates). Scalar fields (`Created`, `Comment`, `LicenseListVersion`) are + filled from `other` only when currently empty or null in this instance. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `List issues` — list to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: All four validation rules are checked and violations recorded in `issues`: + (1) `Creators` must be non-empty; (2) each creator entry must start with `Person:`, + `Organization:`, or `Tool:`; (3) `Created` must be a valid SPDX date-time string when + non-empty (an empty `Created` is permitted); (4) `LicenseListVersion`, when present, must + match the pattern `\d+\.\d+`. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. Four rules are +checked: (1) `Creators` non-empty, (2) each creator prefixed with `Person:`, `Organization:`, +or `Tool:`, (3) `Created` valid SPDX date-time (when non-empty), (4) `LicenseListVersion` +matching `\d+\.\d+` (when present). `LicenseListVersion` format is validated via +`LicenseListVersionRegex`. No exceptions are thrown by `DeepCopy` or `Enhance`. + +### Dependencies + +- **System.Text.RegularExpressions** — used internally for `LicenseListVersion` format + validation via `LicenseListVersionRegex` (pattern `^\d+\.\d+$`). +- **SpdxHelpers** — `EnhanceString` used in `Enhance` to fill empty string fields; `IsValidSpdxDateTime` + used in `Validate` to check the `Created` field format. + +### Callers + +- **SpdxDocument** — holds exactly one `SpdxCreationInformation` instance as `CreationInformation`. +- **Spdx2JsonDeserializer** — constructs `SpdxCreationInformation` during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxCreationInformation` to JSON. diff --git a/docs/design/spdx-model/spdx-document.md b/docs/design/spdx-model/spdx-document.md index b120766..92b7af5 100644 --- a/docs/design/spdx-model/spdx-document.md +++ b/docs/design/spdx-model/spdx-document.md @@ -1,46 +1,119 @@ -# SpdxDocument Unit Design +## SpdxDocument -## Purpose +### Purpose `SpdxDocument` is the root container of the SPDX object model. It aggregates all SPDX elements (packages, files, snippets, relationships, annotations, and extracted licensing information) -and exposes document-level operations such as validation, deep copy, and root-package retrieval. - -## Design - -`SpdxDocument` is a sealed class that extends `SpdxElement` (inheriting the `Id` field). - -Data members (key fields): - -| Property | Type | Description | -| -------- | ---- | ----------- | -| `Name` | `string` | Document name | -| `Version` | `string` | SPDX specification version (e.g., `SPDX-2.3`) | -| `DataLicense` | `string` | License for the SPDX metadata itself | -| `DocumentNamespace` | `string` | Unique URI namespace for this document | -| `CreationInformation` | `SpdxCreationInformation` | Creation metadata | -| `ExternalDocumentReferences` | `SpdxExternalDocumentReference[]` | References to external SPDX documents | -| `ExtractedLicensingInfo` | `SpdxExtractedLicensingInfo[]` | Non-standard license texts | -| `Packages` | `SpdxPackage[]` | All packages in the document | -| `Files` | `SpdxFile[]` | All files in the document | -| `Snippets` | `SpdxSnippet[]` | All snippets in the document | -| `Relationships` | `SpdxRelationship[]` | All relationships in the document | -| `Annotations` | `SpdxAnnotation[]` | All annotations in the document | -| `Describes` | `string[]` | IDs of elements described by this document | - -Key methods: - -- `DeepCopy()` — returns a fully independent deep copy of the entire document graph -- `Validate(List, bool ntia)` — validates all contained elements; optional NTIA SBOM minimum elements check -- `GetRootPackages()` — returns packages directly described by the document via `DESCRIBES` relationships -- `GetAllElements()` — enumerates all contained `SpdxElement` instances -- `GetElement(string id)` / `GetElement(string id)` — retrieves an element by SPDX ID -- `Same` — static `IEqualityComparer` comparing by document name - -## Dependencies - -- `SpdxElement` (base class) -- All other data model units: `SpdxPackage`, `SpdxFile`, `SpdxSnippet`, `SpdxRelationship`, - `SpdxAnnotation`, `SpdxCreationInformation`, `SpdxExternalDocumentReference`, - `SpdxExtractedLicensingInfo` -- `System.Text.RegularExpressions` — version field format validation +and exposes document-level operations such as validation, deep copy, and element retrieval. + +### Data Model + +**Name**: `string` — Human-readable document name. + +**Version**: `string` — SPDX specification version string (e.g., `SPDX-2.3`). + +**DataLicense**: `string` — License for the SPDX metadata itself (always `CC0-1.0` per the +SPDX specification). + +**DocumentNamespace**: `string` — Unique URI namespace for this document; used to qualify +element IDs when referencing across documents. + +**Comment**: `string?` — Optional free-text comment about the document. + +**CreationInformation**: `SpdxCreationInformation` — Metadata about document authorship and +creation time. + +**ExternalDocumentReferences**: `SpdxExternalDocumentReference[]` — References to external SPDX +documents that this document's elements may relate to. + +**ExtractedLicensingInfo**: `SpdxExtractedLicensingInfo[]` — Non-standard license texts +extracted from software in this document. + +**Packages**: `SpdxPackage[]` — All software packages described in the document. + +**Files**: `SpdxFile[]` — All files described in the document. + +**Snippets**: `SpdxSnippet[]` — All snippets described in the document. + +**Relationships**: `SpdxRelationship[]` — All directed relationships between elements in the +document. + +**Annotations**: `SpdxAnnotation[]` — Document-level annotations. + +**Describes**: `string[]` — SPDX IDs of elements directly described by this document (used +when `DESCRIBES` relationships are not present). + +### Key Methods + +**DeepCopy**: Returns a fully independent deep copy of the entire document graph. + +- *Parameters*: none. +- *Returns*: `SpdxDocument` — new instance with all arrays and nested objects deep-copied. +- *Preconditions*: none. +- *Postconditions*: The returned instance is structurally identical and shares no mutable + references with the original. + +**Validate**: Validates all contained elements and the document itself. + +- *Parameters*: `List issues` — list to append issues to; `bool ntia` — when `true`, + also checks NTIA SBOM minimum elements requirements. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: All validation issues found in the document and its elements are appended + to `issues`. + +**GetRootPackages**: Returns packages directly described by the document via `DESCRIBES` +relationships. + +- *Parameters*: none. +- *Returns*: `IEnumerable` — packages whose SPDX ID appears in a `DESCRIBES` + relationship from this document. +- *Preconditions*: none. +- *Postconditions*: none. + +**GetAllElements**: Enumerates all `SpdxElement` instances contained in the document. + +- *Parameters*: none. +- *Returns*: `IEnumerable` — all packages, files, snippets, and annotations + (including the document itself and per-element annotations). `SpdxRelationship` elements are + excluded because relationships are not independently addressable elements and their inclusion + would cause them to appear as duplicates in element-ID lookups. +- *Preconditions*: none. +- *Postconditions*: none. + +**GetElement / GetElement\**: Retrieves an element by SPDX ID. + +- *Parameters*: `string id` — the `SPDXRef-…` identifier. +- *Returns*: `SpdxElement?` or `T?` — the matching element, or `null` if not found. +- *Preconditions*: none. +- *Postconditions*: none. + +**Same**: `static IEqualityComparer` — compares documents by `Name` and root-package +identity. Two documents are considered the same when their `Name` values match AND their sets of +root packages (as returned by `GetRootPackages`) are sequence-equal under `SpdxPackage.Same`. +Used for deduplication scenarios. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. The `Version` +field format is checked by a regular expression. No exceptions are thrown by `DeepCopy`, +`GetRootPackages`, `GetAllElements`, or `GetElement`. + +### Dependencies + +- **SpdxElement** — base class. +- **SpdxPackage**, **SpdxFile**, **SpdxSnippet** — package, file, and snippet elements. +- **SpdxRelationship** — relationship elements. +- **SpdxAnnotation** — annotation elements. +- **SpdxCreationInformation** — creation metadata. +- **SpdxExternalDocumentReference** — external document references. +- **SpdxExtractedLicensingInfo** — extracted licensing information. +- **System.Text.RegularExpressions** — `Version` field format validation. + +### Callers + +- **Spdx2JsonDeserializer** — produces `SpdxDocument` instances from JSON input. +- **Spdx2JsonSerializer** — consumes `SpdxDocument` instances to produce JSON output. +- **SpdxRelationships** — adds relationships to `SpdxDocument` instances. +- External consumers of this library — use `SpdxDocument` as the root of the in-memory SPDX + model. diff --git a/docs/design/spdx-model/spdx-element.md b/docs/design/spdx-model/spdx-element.md index e552e48..e644e2a 100644 --- a/docs/design/spdx-model/spdx-element.md +++ b/docs/design/spdx-model/spdx-element.md @@ -1,30 +1,46 @@ -# SpdxElement Unit Design +## SpdxElement -## Purpose +### Purpose `SpdxElement` is the abstract base class for all identifiable SPDX elements. It defines the common `Id` property (`SPDXRef-…`) and the shared `EnhanceElement` helper, ensuring consistent identity handling across all element types. -## Design +### Data Model -`SpdxElement` is a public abstract class. It is directly inherited by `SpdxDocument`, `SpdxRelationship`, -and `SpdxAnnotation`. `SpdxLicenseElement` is an abstract class that also inherits from `SpdxElement`, and -is in turn inherited by `SpdxPackage`, `SpdxFile`, and `SpdxSnippet`. +**Id**: `string` — SPDX element identifier in `SPDXRef-` format. Must be unique within +a document. -Data members: +**NoAssertion**: `const string` — The sentinel value `"NOASSERTION"` used by optional fields to +indicate that the value was intentionally omitted or is not known. -| Member | Type | Description | -| ------ | ---- | ----------- | -| `Id` | `string` | SPDX element identifier in `SPDXRef-` format | -| `NoAssertion` | `const string` | The sentinel value `"NOASSERTION"` used by optional fields | -| `SpdxRefRegex` | `protected static Regex` | Validates `SPDXRef-…` format | +**SpdxRefRegex**: `protected static Regex` — Pre-compiled regular expression that validates +the `SPDXRef-…` format; used by subclass `Validate` methods. Matches the full pattern +`^SPDXRef-[a-zA-Z0-9.-]+$`. The 100 ms timeout is a ReDoS protection measure against +pathological input strings from untrusted SPDX sources. -Key methods: +### Key Methods -- `EnhanceElement(SpdxElement)` — protected helper that populates `Id` from another element if currently empty +**EnhanceElement**: Protected helper that populates `Id` from another element if currently empty. -## Dependencies +- *Parameters*: `SpdxElement other` — source element. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: If `Id` is empty or null, it is set to `other.Id` via `SpdxHelpers.EnhanceString`. -- `System.Text.RegularExpressions` — `SpdxRefRegex` for ID validation -- `SpdxHelpers` — `EnhanceString` utility used in `EnhanceElement` +### Error Handling + +N/A - `SpdxElement` is abstract and contains no validation logic of its own. Subclasses +implement `Validate` and append issues to a `List`. + +### Dependencies + +- **System.Text.RegularExpressions** — `SpdxRefRegex` for ID format validation. +- **SpdxHelpers** — `EnhanceString` utility used in `EnhanceElement`. + +### Callers + +- **SpdxDocument** — extends `SpdxElement`. +- **SpdxRelationship** — extends `SpdxElement`. +- **SpdxAnnotation** — extends `SpdxElement`. +- **SpdxLicenseElement** — extends `SpdxElement` (abstract intermediate class). diff --git a/docs/design/spdx-model/spdx-external-document-reference.md b/docs/design/spdx-model/spdx-external-document-reference.md index 7d78f18..688c9cf 100644 --- a/docs/design/spdx-model/spdx-external-document-reference.md +++ b/docs/design/spdx-model/spdx-external-document-reference.md @@ -1,31 +1,67 @@ -# SpdxExternalDocumentReference Unit Design +## SpdxExternalDocumentReference -## Purpose +### Purpose `SpdxExternalDocumentReference` represents a reference from one SPDX document to another, enabling modular SBOM construction and cross-document element referencing. Each reference -includes a checksum to verify the referenced document's integrity. +includes a checksum to verify the integrity of the referenced document. -## Design +### Data Model -`SpdxExternalDocumentReference` is a sealed class with no base class (not an `SpdxElement`). +**ExternalDocumentId**: `string` — Local identifier for the referenced document within this +document (e.g., `DocumentRef-tools`). Used as a prefix when referencing elements across documents. -Data members: +**Document**: `string` — URI of the referenced SPDX document. -| Property | Type | Description | -| -------- | ---- | ----------- | -| `ExternalDocumentId` | `string` | Local identifier for the referenced document (e.g., `DocumentRef-tools`) | -| `Document` | `string` | URI of the referenced SPDX document | -| `Checksum` | `SpdxChecksum` | Checksum of the referenced document for integrity verification | +**Checksum**: `SpdxChecksum` — Cryptographic checksum of the referenced document for integrity +verification. -Key methods: +### Key Methods -- `DeepCopy()` — returns a new instance with all fields deep-copied -- `Enhance(SpdxExternalDocumentReference)` — fills in missing fields from another instance -- `Enhance(array, array)` — static method merging two arrays by matching on `ExternalDocumentId` -- `Validate(List)` — appends validation issues to the supplied list -- `Same` — static `IEqualityComparer` comparing by `Document` +**DeepCopy**: Returns a new instance with all fields deep-copied. -## Dependencies +- *Parameters*: none. +- *Returns*: `SpdxExternalDocumentReference` — independent copy. +- *Preconditions*: none. +- *Postconditions*: The returned instance shares no mutable references with the original. -- `SpdxChecksum` — integrity checksum for the referenced document +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxExternalDocumentReference other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Empty fields in this instance are populated from `other`. + +**Enhance (static array merge)**: Merges two external document reference arrays by matching on +`Document` URI. + +- *Parameters*: `SpdxExternalDocumentReference[] base`, `SpdxExternalDocumentReference[] additions`. +- *Returns*: `SpdxExternalDocumentReference[]` — merged array. +- *Preconditions*: none. +- *Postconditions*: Entries present in both arrays (matched by `Document` URI via `Same.Equals`) + are enhanced; new entries from `additions` are deep-copied and appended. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `List issues` — list to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Missing or malformed fields are recorded in `issues`. + +**Same**: `static IEqualityComparer` — compares by `Document` URI. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. The nested +`Checksum` is also validated. No exceptions are thrown by `DeepCopy`, `Enhance`, or the +static merge method. + +### Dependencies + +- **SpdxChecksum** — integrity checksum for the referenced document. + +### Callers + +- **SpdxDocument** — holds the `ExternalDocumentReferences` array. +- **Spdx2JsonDeserializer** — constructs `SpdxExternalDocumentReference` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxExternalDocumentReference` instances to JSON. diff --git a/docs/design/spdx-model/spdx-external-reference.md b/docs/design/spdx-model/spdx-external-reference.md index 8335b64..684604b 100644 --- a/docs/design/spdx-model/spdx-external-reference.md +++ b/docs/design/spdx-model/spdx-external-reference.md @@ -1,32 +1,108 @@ -# SpdxExternalReference Unit Design +## SpdxExternalReference -## Purpose +### Purpose `SpdxExternalReference` represents a link from an SPDX package to an external resource, such as a package registry URL, vulnerability database entry, or documentation site. External references enrich SBOMs with contextual information from authoritative sources. -## Design +### Data Model -`SpdxExternalReference` is a sealed class with no base class. +**Category**: `SpdxReferenceCategory` — Broad category of the reference (e.g., `SECURITY`, +`PACKAGE-MANAGER`, `OTHER`). -Data members: +**Type**: `string` — Specific reference type within the category (e.g., `cpe23Type`, `purl`, +`advisory`). -| Property | Type | Description | -| -------- | ---- | ----------- | -| `Category` | `SpdxReferenceCategory` | Broad category (e.g., SECURITY, PACKAGE-MANAGER) | -| `Type` | `string` | Specific reference type within the category (e.g., `cpe23Type`, `purl`) | -| `Locator` | `string` | URI or identifier for the external resource | -| `Comment` | `string?` | Optional explanatory comment | +**Locator**: `string` — URI or other identifier for the external resource. -Key methods: +**Comment**: `string?` — Optional explanatory comment. -- `DeepCopy()` — returns a new instance with all fields copied -- `Enhance(SpdxExternalReference)` — fills in missing fields from another instance -- `Enhance(array, array)` — static method merging two arrays by matching on category, type, and locator -- `Validate(string, List)` — validates the reference; `string` parameter is the owning package name -- `Same` — static `IEqualityComparer` comparing by category, type, and locator +### Key Methods -## Dependencies +**DeepCopy**: Returns a new instance with all fields copied. -- `SpdxReferenceCategory` (enum) +- *Parameters*: none. +- *Returns*: `SpdxExternalReference` — independent copy. +- *Preconditions*: none. +- *Postconditions*: The returned instance shares no mutable state with the original. + +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxExternalReference other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Empty fields in this instance are populated from `other`. + +**Enhance (static array merge)**: Merges two external reference arrays by matching on category, +type, and locator. + +- *Parameters*: `SpdxExternalReference[] base`, `SpdxExternalReference[] additions`. +- *Returns*: `SpdxExternalReference[]` — merged array. +- *Preconditions*: none. +- *Postconditions*: Matching entries are enhanced; new entries are appended. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `string package` — owning package name for error messages; + `List issues` — list to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Missing or invalid fields are recorded in `issues`. + +**Same**: `static IEqualityComparer` — compares by category, type, and +locator. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. No exceptions are +thrown by `DeepCopy`, `Enhance`, or the static merge method. + +### Dependencies + +- **SpdxReferenceCategory** — enumeration of supported reference categories. + +### SpdxReferenceCategory + +`SpdxReferenceCategory` is an enumeration of the broad reference categories defined by the SPDX +specification. + +#### Enum Values + +| Value | Integer | Description | +|------------------|---------|-------------------------------------------------| +| `Missing` | -1 | Sentinel indicating no category has been set. | +| `Security` | 0 | References to security-related information. | +| `PackageManager` | 1 | References to package management systems. | +| `PersistentId` | 2 | References to software-heritage persistent IDs. | +| `Other` | 3 | References that do not fit other categories. | + +#### FromText + +Converts a category string from an SPDX document to the corresponding `SpdxReferenceCategory` enum +value. + +- *Parameters*: `string category` — the raw text from the SPDX document (case-insensitive). +- *Returns*: `SpdxReferenceCategory.Missing` when `category` is an empty string; otherwise the + matching enum value. +- *Preconditions*: none. +- *Postconditions*: none. +- *Exceptions*: `InvalidOperationException` — thrown when `category` is not a recognized SPDX + reference category string. + +#### ToText + +Converts a `SpdxReferenceCategory` enum value to its canonical SPDX text representation. + +- *Parameters*: `SpdxReferenceCategory category` — the enum value to serialize. +- *Returns*: The canonical SPDX text (e.g., `"SECURITY"`, `"PACKAGE-MANAGER"`). +- *Preconditions*: `category` must not be `SpdxReferenceCategory.Missing`. +- *Postconditions*: none. +- *Exceptions*: `InvalidOperationException` — thrown when `category` is + `SpdxReferenceCategory.Missing` or an unsupported enum value. + +### Callers + +- **SpdxPackage** — holds the `ExternalReferences` array. +- **Spdx2JsonDeserializer** — constructs `SpdxExternalReference` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxExternalReference` instances to JSON. diff --git a/docs/design/spdx-model/spdx-extracted-licensing-info.md b/docs/design/spdx-model/spdx-extracted-licensing-info.md index 673d6e9..e53c88d 100644 --- a/docs/design/spdx-model/spdx-extracted-licensing-info.md +++ b/docs/design/spdx-model/spdx-extracted-licensing-info.md @@ -1,33 +1,68 @@ -# SpdxExtractedLicensingInfo Unit Design +## SpdxExtractedLicensingInfo -## Purpose +### Purpose `SpdxExtractedLicensingInfo` records the full text and metadata of a non-standard license found within a software package. It is used when the license does not appear on the SPDX License List and must be captured verbatim for compliance purposes. -## Design +### Data Model -`SpdxExtractedLicensingInfo` is a sealed class with no base class. +**LicenseId**: `string` — Local identifier in `LicenseRef-…` format, unique within the document. -Data members: +**ExtractedText**: `string` — Full verbatim text of the license as found in the software. -| Property | Type | Description | -| -------- | ---- | ----------- | -| `LicenseId` | `string` | Local identifier in `LicenseRef-…` format | -| `ExtractedText` | `string` | Full verbatim text of the license | -| `Name` | `string?` | Optional human-readable license name | -| `CrossReferences` | `string[]` | Optional URIs to the license text elsewhere | -| `Comment` | `string?` | Optional explanatory comment | +**Name**: `string?` — Optional human-readable license name. -Key methods: +**CrossReferences**: `string[]` — Optional URIs pointing to the license text at canonical +external locations. -- `DeepCopy()` — returns a new instance with all fields deep-copied -- `Enhance(SpdxExtractedLicensingInfo)` — fills in missing fields from another instance -- `Enhance(array, array)` — static method merging two arrays by matching on `LicenseId` -- `Validate(List)` — appends validation issues to the supplied list -- `Same` — static `IEqualityComparer` comparing by `ExtractedText` +**Comment**: `string?` — Optional explanatory comment. -## Dependencies +### Key Methods -- No external dependencies beyond base .NET BCL types +**DeepCopy**: Returns a new instance with all fields deep-copied. + +- *Parameters*: none. +- *Returns*: `SpdxExtractedLicensingInfo` — independent copy. +- *Preconditions*: none. +- *Postconditions*: The returned instance shares no mutable references with the original. + +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxExtractedLicensingInfo other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Empty or null fields are populated from `other`. + +**Enhance (static array merge)**: Merges two extracted licensing info arrays by matching on +`ExtractedText`. + +- *Parameters*: `SpdxExtractedLicensingInfo[] base`, `SpdxExtractedLicensingInfo[] additions`. +- *Returns*: `SpdxExtractedLicensingInfo[]` — merged array. +- *Preconditions*: none. +- *Postconditions*: Matching entries are enhanced; new entries are appended. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `List issues` — list to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Missing required fields are recorded in `issues`. + +**Same**: `static IEqualityComparer` — compares by `ExtractedText`. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. No exceptions are +thrown by `DeepCopy`, `Enhance`, or the static merge method. + +### Dependencies + +N/A - no external dependencies beyond base .NET BCL types. + +### Callers + +- **SpdxDocument** — holds the `ExtractedLicensingInfo` array. +- **Spdx2JsonDeserializer** — constructs `SpdxExtractedLicensingInfo` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxExtractedLicensingInfo` instances to JSON. diff --git a/docs/design/spdx-model/spdx-file.md b/docs/design/spdx-model/spdx-file.md index f639c37..4131bc6 100644 --- a/docs/design/spdx-model/spdx-file.md +++ b/docs/design/spdx-model/spdx-file.md @@ -1,37 +1,130 @@ -# SpdxFile Unit Design +## SpdxFile -## Purpose +### Purpose `SpdxFile` represents an individual file within an SPDX document, enabling fine-grained tracking of source files, binaries, and other artifacts together with their licensing, checksums, and contributor information. -## Design +### Data Model -`SpdxFile` is a sealed class that extends `SpdxLicenseElement` (which extends `SpdxElement`), -inheriting `Id`, `ConcludedLicense`, `CopyrightText`, and related license fields. +**FileName**: `string` — Relative path of the file (e.g., `./src/main.c`). Used as the match +key when merging file arrays. -Data members (beyond inherited fields): +**FileTypes**: `SpdxFileType[]` — File type classifications (e.g., `SOURCE`, `BINARY`, +`DOCUMENTATION`). -| Property | Type | Description | -| -------- | ---- | ----------- | -| `FileName` | `string` | Relative path of the file (e.g., `./src/main.c`) | -| `FileTypes` | `SpdxFileType[]` | File type classifications (SOURCE, BINARY, etc.) | -| `Checksums` | `SpdxChecksum[]` | Integrity checksums for the file | -| `LicenseInfoInFiles` | `string[]` | License expressions found in the file | -| `Comment` | `string?` | Optional comment | -| `Notice` | `string?` | Optional copyright notice text | -| `Contributors` | `string[]` | Contributors to this file | +**Checksums**: `SpdxChecksum[]` — Integrity checksums for this file using one or more algorithms. -Key methods: +**LicenseInfoInFiles**: `string[]` — License expressions found within the file. -- `DeepCopy()` — returns a fully deep-copied instance -- `Enhance(SpdxFile)` — fills in missing fields from another instance -- `Enhance(array, array)` — static method merging two file arrays, matching on `FileName` -- `Validate(List)` — appends validation issues to the supplied list -- `Same` — static `IEqualityComparer` comparing by `FileName` +**Comment**: `string?` — Optional free-text comment. -## Dependencies +**Notice**: `string?` — Optional copyright notice text found in or about the file. -- `SpdxLicenseElement` (base class) -- `SpdxChecksum`, `SpdxFileType` (enum) +**Contributors**: `string[]` — Contributors to this file. + +*Inherited from `SpdxLicenseElement`*: `Id`, `ConcludedLicense`, `LicenseComments`, +`CopyrightText`, `AttributionText`, `Annotations`. + +### Key Methods + +**DeepCopy**: Returns a fully deep-copied instance. + +- *Parameters*: none. +- *Returns*: `SpdxFile` — independent copy including all arrays. +- *Preconditions*: none. +- *Postconditions*: The returned instance shares no mutable references with the original. + +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxFile other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Empty fields and empty arrays in this instance are populated from `other`. + +**Enhance (static array merge)**: Merges two file arrays, matching on `FileName`. + +- *Parameters*: `SpdxFile[] base`, `SpdxFile[] additions`. +- *Returns*: `SpdxFile[]` — merged array. +- *Preconditions*: none. +- *Postconditions*: Matching entries are enhanced; new entries are appended. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `List issues` — list to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: The following rules are checked and any violations are appended to `issues`: + 1. `FileName` must start with `"./"` (SPDX §4.1 — relative paths required). + 2. `Id` must match the `SPDXRef-[id-string]` format (SPDX §4.2). + 3. At least one SHA1 checksum must be present in `Checksums` (SPDX §4.4). + Nested checksums and annotations are also validated. + +**Same**: `static IEqualityComparer` — compares by `FileName`, with the condition that +two files with differing SHA1 checksums are considered distinct even if their `FileName` values +match. + +#### SHA1 Tiebreaker (SPDX specification rationale) + +The SPDX specification allows the same file path to be tracked at multiple revisions. If both +entries carry a SHA1 checksum and the values differ, they represent distinct file versions. +If either entry lacks a SHA1 checksum, identity falls back to `FileName` alone. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. Nested checksums +are also validated. No exceptions are thrown by `DeepCopy`, `Enhance`, or the static merge method. + +### Dependencies + +- **SpdxLicenseElement** — abstract base class providing license and copyright fields. +- **SpdxChecksum** — checksum instances in the `Checksums` array. +- **SpdxFileType** — enumeration for file type classification. + +### SpdxFileType and SpdxFileTypeExtensions + +`SpdxFileType` is an enumeration of the file type categories defined by the SPDX specification. + +#### Enum Values + +| Value | Description | +|-----------------|-------------------------------------------------------| +| `Source` | Human-readable source code. | +| `Binary` | Compiled object, target image, or binary executable. | +| `Archive` | Archive file (e.g., zip, tar). | +| `Application` | Application file. | +| `Audio` | Audio file. | +| `Image` | Image file. | +| `Text` | Human-readable text file. | +| `Video` | Video file. | +| `Documentation` | Documentation file. | +| `Spdx` | SPDX document. | +| `Other` | Other type not matching standard categories. | + +#### FromText + +Converts a file type string from an SPDX document to the corresponding `SpdxFileType` enum value. + +- *Parameters*: `string fileType` — the raw text from the SPDX document (case-insensitive). +- *Returns*: The matching `SpdxFileType` enum value. +- *Preconditions*: none. +- *Postconditions*: none. +- *Exceptions*: `InvalidOperationException` — thrown with a message identifying the unsupported + value when `fileType` does not match any known SPDX file type string. + +#### ToText + +Converts a `SpdxFileType` enum value to its canonical SPDX text representation. + +- *Parameters*: `SpdxFileType fileType` — the enum value to serialize. +- *Returns*: The canonical SPDX text (e.g., `"SOURCE"`, `"BINARY"`). +- *Preconditions*: `fileType` must be a supported enum value. +- *Postconditions*: none. +- *Exceptions*: `InvalidOperationException` — thrown when `fileType` is an unsupported enum value. + +### Callers + +- **SpdxDocument** — holds the `Files` array. +- **Spdx2JsonDeserializer** — constructs `SpdxFile` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxFile` instances to JSON. diff --git a/docs/design/spdx-model/spdx-helpers.md b/docs/design/spdx-model/spdx-helpers.md index 2a06700..3300281 100644 --- a/docs/design/spdx-model/spdx-helpers.md +++ b/docs/design/spdx-model/spdx-helpers.md @@ -1,32 +1,50 @@ -# SpdxHelpers Unit Design +## SpdxHelpers -## Purpose +### Purpose `SpdxHelpers` is an internal static utility class providing shared helper methods used across -the data model. It centralizes common operations such as string enhancement (selecting the -best available value by fitness ranking) and SPDX date-time validation. +the data model. It centralizes common operations such as string enhancement (selecting the best +available value by fitness ranking) and SPDX date-time validation. -## Design +### Data Model -`SpdxHelpers` is a `partial` internal static class. Date-time validation uses -`[GeneratedRegex]` on .NET 7 and later (source-generated, AOT-safe), with a cached `Regex` -instance as a fallback for earlier targets such as `netstandard2.0`. +N/A - `SpdxHelpers` is a static utility class with no instance state. -Key methods: +### Key Methods -| Method | Description | -| ------ | ----------- | -| `IsValidSpdxDateTime(string?)` | Returns `true` if the value matches ISO 8601 UTC format | -| `EnhanceString(params string?[])` | Returns the highest-fitness value: concrete > `NOASSERTION` > empty > `null` | +**IsValidSpdxDateTime**: Returns `true` if the supplied value matches the ISO 8601 UTC timestamp +format required by SPDX. -Key design decisions: +- *Parameters*: `string? value` — the timestamp string to validate. +- *Returns*: Returns `true` if `value` matches the ISO 8601 UTC format, or if `value` is null or + empty (both treated as not-set and therefore valid); `false` otherwise. +- *Preconditions*: none. +- *Postconditions*: none. -- `internal` visibility — not part of the public API; only used within the assembly. -- `partial` class enables the `[GeneratedRegex]` attribute on .NET 7+; pre-.NET 7 targets use - a cached `Regex` instance instead. -- `EnhanceString` uses a fitness ranking so that a meaningful value is always preferred over - `NOASSERTION` or absent values, regardless of argument order. +**EnhanceString**: Returns the highest-fitness string from the supplied candidates. -## Dependencies +- *Parameters*: `params string?[] candidates` — ordered list of candidate values. +- *Returns*: `string?` — the best candidate: concrete (non-empty, non-NOASSERTION) > `NOASSERTION` + > empty string > `null`. +- *Preconditions*: none. +- *Postconditions*: The returned value is the most informative of the candidates regardless of + argument order. -- `System.Text.RegularExpressions` — date-time validation regex +### Error Handling + +N/A - both methods are pure functions with no side effects. `IsValidSpdxDateTime` returns `false` +for invalid input rather than throwing. + +### Dependencies + +- **System.Text.RegularExpressions** — date-time validation regex. On .NET 7 and later, a + source-generated `[GeneratedRegex]` is used for AOT safety; earlier targets use a cached + `Regex` instance. + +### Callers + +- **SpdxElement** — `EnhanceString` used in `EnhanceElement`. +- All data model units that call `EnhanceString` in their `Enhance` methods. +- **SpdxCreationInformation** — `IsValidSpdxDateTime` used in `Validate`. +- **SpdxAnnotation** — `IsValidSpdxDateTime` used in `Validate`. +- **SpdxPackage** — `IsValidSpdxDateTime` used in `Validate`. diff --git a/docs/design/spdx-model/spdx-license-element.md b/docs/design/spdx-model/spdx-license-element.md index 017b1a8..b7d8719 100644 --- a/docs/design/spdx-model/spdx-license-element.md +++ b/docs/design/spdx-model/spdx-license-element.md @@ -1,31 +1,67 @@ -# SpdxLicenseElement Unit Design +## SpdxLicenseElement -## Purpose +### Purpose `SpdxLicenseElement` is an abstract intermediate base class that adds license-related fields to `SpdxElement`. It is the common ancestor of `SpdxPackage`, `SpdxFile`, and `SpdxSnippet`, -avoiding duplication of the concluded-license, copyright, and attribution fields. +centralizing the concluded-license, copyright, and attribution fields to avoid duplication across +all three element types. -## Design +### Data Model -`SpdxLicenseElement` is a public abstract class that extends `SpdxElement`. +**ConcludedLicense**: `string` — License expression concluded by the SPDX document preparer +for this element. -Data members (beyond `SpdxElement.Id`): +**LicenseComments**: `string?` — Optional explanation of the concluded license choice. -| Property | Type | Description | -| -------- | ---- | ----------- | -| `ConcludedLicense` | `string` | License expression concluded by the SPDX document preparer | -| `LicenseComments` | `string?` | Explanation of the concluded license choice | -| `CopyrightText` | `string` | Copyright declarations text | -| `AttributionText` | `string[]` | Attribution notices required for use | -| `Annotations` | `SpdxAnnotation[]` | Additional information about this element | +**CopyrightText**: `string` — Copyright declarations text for this element. -Key design decisions: +**AttributionText**: `string[]` — Attribution notices required when redistributing or using +this element. -- Abstract (non-instantiable) — no direct consumers; always subclassed. -- Provides `EnhanceLicenseElement(SpdxLicenseElement)` protected helper analogous to - `SpdxElement.EnhanceElement` for consistent field merging. +**Annotations**: `SpdxAnnotation[]` — Element-level annotations (comments, reviews, or other +notes attached to this element). -## Dependencies +*Inherited from `SpdxElement`*: `Id`. -- `SpdxElement` (base class) +### Key Methods + +**EnhanceLicenseElement**: Protected helper that populates license-related fields from another +instance when the existing value has lower fitness than the source value. + +- *Parameters*: `SpdxLicenseElement other` — source element. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: + a) **String fields** (`ConcludedLicense`, `LicenseComments`, `CopyrightText`): fitness-based + selection — concrete value > NOASSERTION > empty string > null. + b) **AttributionText**: merged by concatenation and deduplication (union of both arrays). + c) **Annotations**: merged by identity-match (enhance existing entries by comparer) and + append (add new entries not already present). + d) **Base-class delegation**: also calls `EnhanceElement(other)`, which populates the + inherited `Id` field if absent. + +#### Algorithm + +The fitness ranking used for string fields is: null=0, empty string=1, NOASSERTION=2, concrete +value=3. The field with the higher fitness rank is retained. When both fields have equal fitness, +the current value is kept. + +For `AttributionText` and `Annotations`, both arrays are merged: existing entries are enhanced +in-place where a match is found (by annotation identity comparer), and unmatched entries from +`other` are appended as deep copies. + +### Error Handling + +N/A - `SpdxLicenseElement` is abstract. Subclasses implement their own `Validate` methods. + +### Dependencies + +- **SpdxElement** — abstract base class providing the `Id` property. +- **SpdxAnnotation** — element-level annotations. + +### Callers + +- **SpdxPackage** — extends `SpdxLicenseElement`. +- **SpdxFile** — extends `SpdxLicenseElement`. +- **SpdxSnippet** — extends `SpdxLicenseElement`. diff --git a/docs/design/spdx-model/spdx-model.md b/docs/design/spdx-model/spdx-model.md index fbd7f7a..0fabf3e 100644 --- a/docs/design/spdx-model/spdx-model.md +++ b/docs/design/spdx-model/spdx-model.md @@ -1,89 +1,123 @@ -# DemaConsulting.SpdxModel System Design - -## System Architecture +## SpdxModel DemaConsulting.SpdxModel is a .NET library providing a complete implementation of the SPDX (Software Package Data Exchange) data model. The library exposes an in-memory object model -representing all SPDX document elements, plus serialization and transformation capabilities. - -### Subsystems - -| Subsystem | Folder | Responsibility | -| --------- | ------ | -------------- | -| IO | `IO/` | JSON serialization and deserialization for SPDX 2.2 and 2.3 formats | -| Transform | `Transform/` | Utilities for manipulating SPDX documents in memory | - -### Data Model Units - -| Unit | File | Responsibility | -| ---- | ---- | -------------- | -| `SpdxElement` | `SpdxElement.cs` | Abstract base for all identifiable SPDX elements | -| `SpdxLicenseElement` | `SpdxLicenseElement.cs` | Abstract base for elements carrying license and copyright fields | -| `SpdxDocument` | `SpdxDocument.cs` | Root container of a complete SPDX document | -| `SpdxPackage` | `SpdxPackage.cs` | Represents a software package in the SBOM | -| `SpdxFile` | `SpdxFile.cs` | Represents an individual file in the SBOM | -| `SpdxSnippet` | `SpdxSnippet.cs` | Represents a code snippet within a file | -| `SpdxRelationship` | `SpdxRelationship.cs` | Represents a directional relationship between elements | -| `SpdxAnnotation` | `SpdxAnnotation.cs` | Represents a review or assessment annotation | -| `SpdxChecksum` | `SpdxChecksum.cs` | Represents a cryptographic checksum | -| `SpdxCreationInformation` | `SpdxCreationInformation.cs` | Metadata about document authorship and creation time | -| `SpdxExternalDocumentReference` | `SpdxExternalDocumentReference.cs` | Reference to an external SPDX document | -| `SpdxExternalReference` | `SpdxExternalReference.cs` | Reference to an external resource (registry, VDB, etc.) | -| `SpdxExtractedLicensingInfo` | `SpdxExtractedLicensingInfo.cs` | Non-standard license text extracted from software | -| `SpdxPackageVerificationCode` | `SpdxPackageVerificationCode.cs` | Cryptographic integrity code for a package | -| `SpdxHelpers` | `SpdxHelpers.cs` | Shared utility functions (date-time validation, string fitness) | - -## External Interfaces and Dependencies - -### External Dependencies - -- **System.Text.Json** — used by the IO subsystem for JSON reading and writing; available in-box on - modern .NET targets and via NuGet for .NET Standard 2.0 -- **.NET Standard 2.0 / .NET 8 / .NET 9 / .NET 10** — target frameworks - -### Public API Surface - -The library exposes: - -- `SpdxDocument` — root object representing a complete SPDX document -- Data model classes for all SPDX elements -- `Spdx2JsonDeserializer` — reads SPDX JSON into the object model -- `Spdx2JsonSerializer` — writes the object model to SPDX JSON -- `SpdxRelationships` — static utilities for relationship manipulation - -## Data Flow - -```text -JSON File - │ - ▼ -Spdx2JsonDeserializer ──► SpdxDocument (in-memory model) - │ - (manipulate via - Transform utilities) - │ - ▼ - Spdx2JsonSerializer ──► JSON File +representing all SPDX document elements, with serialization and transformation capabilities. + +### Architecture + +```mermaid +flowchart TD + subgraph IO + Spdx2JsonDeserializer + Spdx2JsonSerializer + SpdxConstants + end + subgraph Transform + SpdxRelationships + end + SpdxDocument + SpdxElement + SpdxLicenseElement + SpdxPackage + SpdxFile + SpdxSnippet + SpdxRelationship + SpdxAnnotation + SpdxChecksum + SpdxCreationInformation + SpdxExternalDocumentReference + SpdxExternalReference + SpdxExtractedLicensingInfo + SpdxPackageVerificationCode + SpdxHelpers + + SpdxDocument --> SpdxElement + SpdxLicenseElement --> SpdxElement + SpdxPackage --> SpdxLicenseElement + SpdxFile --> SpdxLicenseElement + SpdxSnippet --> SpdxLicenseElement + SpdxRelationship --> SpdxElement + SpdxAnnotation --> SpdxElement + + Spdx2JsonDeserializer --> SpdxDocument + Spdx2JsonDeserializer --> SpdxConstants + Spdx2JsonSerializer --> SpdxDocument + Spdx2JsonSerializer --> SpdxConstants + SpdxRelationships --> SpdxDocument + SpdxRelationships --> SpdxRelationship ``` -## System-Wide Design Constraints and Decisions +### External Interfaces + +**SPDX JSON Input**: JSON file conforming to the SPDX 2.2 or 2.3 JSON schema. + +- *Type*: File (JSON) +- *Role*: Consumer +- *Contract*: `Spdx2JsonDeserializer.Deserialize(string)` accepts raw JSON text and returns a + populated `SpdxDocument`. +- *Constraints*: Input must be valid JSON; SPDX field validation is performed after + deserialization via `SpdxDocument.Validate()`. + +**SPDX JSON Output**: JSON file conforming to the SPDX 2.3 JSON schema. + +- *Type*: File (JSON) +- *Role*: Provider +- *Contract*: `Spdx2JsonSerializer.Serialize(SpdxDocument)` returns a complete SPDX 2.3 JSON + string. +- *Constraints*: Optional fields are omitted when empty or null; output always conforms to SPDX + 2.3 schema. + +**In-Process .NET Public API**: Object model and transformation API consumed by .NET callers. -- **Immutability by convention**: data model classes use public mutable properties to allow - flexible construction while deep-copy methods provide safe cloning -- **Nullable reference types enabled**: all public API members declare nullability explicitly -- **Minimal runtime dependencies**: keeps the library lightweight and avoids dependency conflicts - for consumers by relying only on BCL/framework-provided APIs where available, with - compatibility NuGet packages used on older targets such as `netstandard2.0` -- **Target multi-framework**: the library targets `netstandard2.0`, `net8.0`, `net9.0`, - and `net10.0` simultaneously +- *Type*: In-process .NET public API +- *Role*: Provider +- *Contract*: Exposes `SpdxDocument` and all data model classes, `Spdx2JsonDeserializer`, + `Spdx2JsonSerializer`, and `SpdxRelationships` as public types. +- *Constraints*: Targets `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`. -## Integration Patterns +#### Error Handling -Consumers typically: +- `Spdx2JsonDeserializer.Deserialize` throws `System.Text.Json.JsonException` when the input is + fatally malformed JSON (i.e., the input cannot be parsed as a JSON document). Missing or unknown + SPDX fields do not cause an exception; they are silently ignored or left at their default values. +- `SpdxDocument.Validate(List)` never throws; it appends human-readable issue strings to + the supplied list and returns normally, allowing callers to inspect all issues at once. -1. Deserialize an SPDX document from a JSON file using `Spdx2JsonDeserializer` -2. Inspect or modify the `SpdxDocument` object model in memory -3. Serialize back to JSON using `Spdx2JsonSerializer` +### Dependencies + +- **System.Text.Json**: used by the IO subsystem for JSON DOM parsing and serialization; + available in-box on modern .NET targets and via NuGet for .NET Standard 2.0. + +### Risk Control Measures + +N/A - not a safety-classified software item. + +### Data Flow + +```mermaid +flowchart LR + A[JSON File] --> B[Spdx2JsonDeserializer] + B --> C[SpdxDocument] + C --> D[Transform utilities] + D --> C + C --> E[Spdx2JsonSerializer] + E --> F[JSON File] +``` -For programmatic SBOM construction, consumers create `SpdxDocument` instances directly and -populate the data model before serializing. +1. Caller provides a JSON string to `Spdx2JsonDeserializer.Deserialize`. +2. The deserializer uses `System.Text.Json.Nodes` to parse the JSON DOM. +3. Per-element helpers populate a new `SpdxDocument` instance. +4. The caller inspects or modifies the `SpdxDocument` in memory, optionally using + `SpdxRelationships` utilities. +5. `Spdx2JsonSerializer.Serialize` traverses the `SpdxDocument` and produces a JSON string. + +### Design Constraints + +- Targets `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0` simultaneously. +- Minimal runtime dependencies: relies on BCL/framework APIs where possible; compatibility NuGet + packages used on older targets. +- Nullable reference types enabled: all public API members declare nullability explicitly. +- Data model classes use public mutable properties to allow flexible construction; deep-copy + methods provide safe cloning. +- No static mutable state in data model classes; thread safety is the caller's responsibility. diff --git a/docs/design/spdx-model/spdx-package-verification-code.md b/docs/design/spdx-model/spdx-package-verification-code.md index d2d4d8f..c8af38d 100644 --- a/docs/design/spdx-model/spdx-package-verification-code.md +++ b/docs/design/spdx-model/spdx-package-verification-code.md @@ -1,29 +1,56 @@ -# SpdxPackageVerificationCode Unit Design +## SpdxPackageVerificationCode -## Purpose +### Purpose `SpdxPackageVerificationCode` represents an SPDX package verification code — a SHA1 digest computed over the contents of a package (optionally excluding specified files). It provides cryptographic assurance that package contents have not been modified. -## Design +### Data Model -`SpdxPackageVerificationCode` is a sealed class with no base class. +**Value**: `string` — SHA1 hex digest computed over the sorted file checksums of the package. -Data members: +**ExcludedFiles**: `string[]` — File paths excluded from the verification code computation +(e.g., the `.spdx` file itself). -| Property | Type | Description | -| -------- | ---- | ----------- | -| `Value` | `string` | SHA1 hex digest of the package contents | -| `ExcludedFiles` | `string[]` | Files excluded from the verification code computation | +### Key Methods -Key methods: +**DeepCopy**: Returns a new instance with all fields deep-copied. -- `DeepCopy()` — returns a new instance with all fields deep-copied -- `Enhance(SpdxPackageVerificationCode)` — fills in missing fields from another instance -- `Validate(string, List)` — validates the code value; `string` parameter is the owning package name -- `Same` — static `IEqualityComparer` comparing by `Value` and `ExcludedFiles` +- *Parameters*: none. +- *Returns*: `SpdxPackageVerificationCode` — independent copy. +- *Preconditions*: none. +- *Postconditions*: The returned instance shares no mutable references with the original. -## Dependencies +**Enhance**: Fills in missing fields from another instance. -- No external dependencies beyond base .NET BCL types +- *Parameters*: `SpdxPackageVerificationCode other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Empty or null fields in this instance are populated from `other`. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `string package` — owning package name for error messages; + `List issues` — list to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: An empty or malformed `Value` is recorded in `issues`. + +**Same**: `static IEqualityComparer` — compares by `Value` only. +`ExcludedFiles` is not considered for equality. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. No exceptions are +thrown by `DeepCopy` or `Enhance`. + +### Dependencies + +N/A - no external dependencies beyond base .NET BCL types. + +### Callers + +- **SpdxPackage** — holds an optional `VerificationCode` instance. +- **Spdx2JsonDeserializer** — constructs `SpdxPackageVerificationCode` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxPackageVerificationCode` instances to JSON. diff --git a/docs/design/spdx-model/spdx-package.md b/docs/design/spdx-model/spdx-package.md index 99ffdc8..1991159 100644 --- a/docs/design/spdx-model/spdx-package.md +++ b/docs/design/spdx-model/spdx-package.md @@ -1,43 +1,119 @@ -# SpdxPackage Unit Design +## SpdxPackage -## Purpose +### Purpose `SpdxPackage` represents an SPDX package — the primary building block of a Software Bill of Materials. It captures identity, provenance, licensing, verification, and dependency metadata for a software package. -## Design - -`SpdxPackage` is a sealed class that extends `SpdxLicenseElement`, inheriting `Id`, -`ConcludedLicense`, `CopyrightText`, and attribution fields. - -Data members (key fields beyond inherited): - -| Property | Type | Description | -| -------- | ---- | ----------- | -| `Name` | `string` | Package name | -| `Version` | `string?` | Package version string | -| `FileName` | `string?` | Filename of the package archive | -| `Supplier` / `Originator` | `string?` | Entity distributing / originating the package | -| `DownloadLocation` | `string` | URI from which the package was obtained | -| `FilesAnalyzed` | `bool?` | Whether files in the package have been analyzed | -| `VerificationCode` | `SpdxPackageVerificationCode?` | Cryptographic verification code | -| `Checksums` | `SpdxChecksum[]` | Package-level checksums | -| `LicenseInfoFromFiles` | `string[]` | Licenses found in files of the package | -| `DeclaredLicense` | `string` | License declared by the package authors; may be empty when not specified | -| `ExternalReferences` | `SpdxExternalReference[]` | Links to external resources | -| `PrimaryPackagePurpose` | `string?` | Primary purpose classification | - -Key methods: - -- `DeepCopy()` — returns a fully deep-copied instance -- `Enhance(SpdxPackage)` — fills in missing fields from another instance -- `Enhance(array, array)` — static merging of two package arrays, matching on `Name` + `Version` -- `Validate(List, SpdxDocument?, bool ntia)` — full validation including NTIA minimum elements; - empty `DeclaredLicense` is permitted and does not produce a validation issue -- `Same` — static `IEqualityComparer` comparing by `Name` and `Version` - -## Dependencies - -- `SpdxLicenseElement` (base class) -- `SpdxChecksum`, `SpdxExternalReference`, `SpdxPackageVerificationCode` +### Data Model + +**Name**: `string` — Package name. Used as the match key (together with `Version`) for array +merging. + +**Version**: `string?` — Package version string; null if not specified. + +**FileName**: `string?` — Filename of the package archive or distribution artifact. + +**Supplier**: `string?` — Entity distributing the package (in `Organization: name` or +`Person: name` format). + +**Originator**: `string?` — Entity that originally authored or created the package. + +**DownloadLocation**: `string` — URI from which the package was or can be obtained. + +**FilesAnalyzed**: `bool?` — Whether the files within the package have been analyzed; `null` +means unspecified. + +**HasFiles**: `string[]` — SPDX IDs of `SpdxFile` elements that belong to this package. When +`doc` is passed to `Validate`, each ID is verified to exist in `doc.Files`. + +**VerificationCode**: `SpdxPackageVerificationCode?` — Cryptographic verification code computed +over the package's files; absent when `FilesAnalyzed` is `false`. + +**Checksums**: `SpdxChecksum[]` — Package-level checksums using one or more algorithms. + +**HomePage**: `string?` — URI of the package home page; `null` if not specified. + +**SourceInformation**: `string?` — Human-readable description of how the package was acquired +or modified from the original source; `null` if not specified. + +**LicenseInfoFromFiles**: `string[]` — License expressions found in files of the package. + +**DeclaredLicense**: `string` — License declared by the package authors; may be empty when not +specified by the package. + +**Summary**: `string?` — Short human-readable description of the package; `null` if not +specified. + +**Description**: `string?` — Detailed human-readable description of the package; `null` if not +specified. + +**Comment**: `string?` — Free-text human annotation; `null` if not specified. + +**ExternalReferences**: `SpdxExternalReference[]` — Links to external resources such as package +registries or vulnerability databases. + +**PrimaryPackagePurpose**: `string?` — Primary purpose classification (e.g., `LIBRARY`, +`APPLICATION`). + +*Inherited from `SpdxLicenseElement`*: `Id`, `ConcludedLicense`, `LicenseComments`, +`CopyrightText`, `AttributionText`, `Annotations`. + +### Key Methods + +**DeepCopy**: Returns a fully deep-copied instance. + +- *Parameters*: none. +- *Returns*: `SpdxPackage` — independent copy including all nested objects and arrays. +- *Preconditions*: none. +- *Postconditions*: The returned instance shares no mutable references with the original. + +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxPackage other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Empty or null fields in this instance are populated from `other`. + +**Enhance (static array merge)**: Merges two package arrays, matching on `Name` and `Version`. + +- *Parameters*: `SpdxPackage[] base`, `SpdxPackage[] additions`. +- *Returns*: `SpdxPackage[]` — merged array. +- *Preconditions*: none. +- *Postconditions*: Matching entries are enhanced; new entries are appended. + +**Validate**: Validates the package, including NTIA minimum element checks when requested. + +- *Parameters*: `List issues` — list to append issues to; `SpdxDocument? document` — + owning document for cross-reference validation; `bool ntia` — when `true`, also checks NTIA + SBOM minimum elements. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: All discovered issues including nested checksum and external reference + issues are appended to `issues`; an empty `DeclaredLicense` does not produce a validation issue. + When `doc` is non-null, entries in `HasFiles` that do not match any file ID in `doc.Files` + cause an issue to be recorded. + +**Same**: `static IEqualityComparer` — compares by `Name` and `Version`. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. Nested checksums, +external references, and the verification code are also validated. No exceptions are thrown by +`DeepCopy`, `Enhance`, or the static merge method. When `doc` is provided and `HasFiles` +contains IDs for files not present in `doc.Files`, a single issue is appended: +`Package '{name}' HasFiles references missing files`. + +### Dependencies + +- **SpdxLicenseElement** — abstract base class. +- **SpdxChecksum** — package-level checksums. +- **SpdxExternalReference** — external resource links. +- **SpdxPackageVerificationCode** — optional package integrity code. + +### Callers + +- **SpdxDocument** — holds the `Packages` array. +- **Spdx2JsonDeserializer** — constructs `SpdxPackage` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxPackage` instances to JSON. diff --git a/docs/design/spdx-model/spdx-relationship.md b/docs/design/spdx-model/spdx-relationship.md index fd31528..9d3f668 100644 --- a/docs/design/spdx-model/spdx-relationship.md +++ b/docs/design/spdx-model/spdx-relationship.md @@ -1,36 +1,126 @@ -# SpdxRelationship Unit Design +## SpdxRelationship -## Purpose +### Purpose `SpdxRelationship` represents a directed relationship between two SPDX elements. Relationships define the dependency graph, containment hierarchy, and other associations between packages, files, and snippets in an SPDX document. -## Design +### Data Model -`SpdxRelationship` is a sealed class that extends `SpdxElement` (inheriting the `Id` field, -which identifies the *source* element of the relationship). +**Id** (inherited): `string` — SPDX ID of the source element of the relationship. -Data members: +**RelatedSpdxElement**: `string` — SPDX ID of the target element. May be `NOASSERTION` or use +a `DocumentRef-` prefix for cross-document references. -| Property | Type | Description | -| -------- | ---- | ----------- | -| `Id` (inherited) | `string` | SPDX ID of the source element | -| `RelatedSpdxElement` | `string` | SPDX ID of the target element | -| `RelationshipType` | `SpdxRelationshipType` | Type of relationship (DESCRIBES, CONTAINS, DEPENDS_ON, etc.) | -| `Comment` | `string?` | Optional explanatory comment | +**RelationshipType**: `SpdxRelationshipType` — Type of relationship (e.g., `DESCRIBES`, +`CONTAINS`, `DEPENDS_ON`, `GENERATED_FROM`). -Key methods: +**Comment**: `string?` — Optional explanatory comment. -- `DeepCopy()` — returns a new instance with all fields copied -- `Enhance(SpdxRelationship)` — fills in missing fields from another instance -- `Enhance(array, array)` — static method merging two relationship arrays by source, target, and type -- `Validate(List, SpdxDocument?)` — validates element ID references exist in the document -- `Same` — static `IEqualityComparer` comparing source, target, and type -- `SameElements` — static `IEqualityComparer` comparing only source and target (ignoring type) +### Key Methods -## Dependencies +**DeepCopy**: Returns a new instance with all fields copied. -- `SpdxElement` (base class) -- `SpdxRelationshipType` (enum) -- `SpdxDocument` — used during validation to resolve element IDs +- *Parameters*: none. +- *Returns*: `SpdxRelationship` — independent copy. +- *Preconditions*: none. +- *Postconditions*: The returned instance shares no mutable references with the original. + +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxRelationship other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Empty or null fields are populated from `other`. + +**Enhance (static array merge)**: Merges two relationship arrays by matching on source ID, target +ID, and type. + +- *Parameters*: `SpdxRelationship[] base`, `SpdxRelationship[] additions`. +- *Returns*: `SpdxRelationship[]` — merged array. +- *Preconditions*: none. +- *Postconditions*: Matching entries are enhanced; new entries are appended. + +**Validate**: Validates that the referenced element IDs exist in the owning document. + +- *Parameters*: `List issues` — list to append issues to; `SpdxDocument? document` — + document for element ID resolution. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: References to non-existent elements (that are not `NOASSERTION` or + `DocumentRef-`) are recorded in `issues`. + +**Same**: `static IEqualityComparer` — compares by source ID, target ID, and +relationship type. + +**SameElements**: `static IEqualityComparer` — compares by source ID and +target ID only, ignoring relationship type. Used by `SpdxRelationships.Add` when `replace` is +`true`. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. No exceptions are +thrown by `DeepCopy`, `Enhance`, or the static merge method. + +### Dependencies + +- **SpdxElement** — base class providing the `Id` property. +- **SpdxRelationshipType** — enumeration of relationship types. +- **SpdxDocument** — used during `Validate` to resolve element IDs. + +### Callers + +- **SpdxDocument** — holds the `Relationships` array. +- **SpdxRelationships** — adds and deduplicates relationships in a document. +- **Spdx2JsonDeserializer** — constructs `SpdxRelationship` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxRelationship` instances to JSON. + +### SpdxRelationshipType + +#### Overview + +`SpdxRelationshipType` is an enumeration of SPDX-defined relationship type tokens and the +`SpdxRelationshipTypeExtensions` static class provides round-trip text conversion between enum +values and their canonical SPDX string representations. + +#### Enum Values + +The enumeration defines 44 relationship type values plus the sentinel value `Missing` (= -1). +Key values include: + +| Enum Value | SPDX String | +|-----------------|-------------------------------| +| `Missing` | (sentinel — not serializable) | +| `Describes` | `DESCRIBES` | +| `Contains` | `CONTAINS` | +| `DependsOn` | `DEPENDS_ON` | +| `GeneratedFrom` | `GENERATED_FROM` | +| … | … (44 values total) | + +#### Conversion Methods + +**FromText**: Converts a string to `SpdxRelationshipType`. + +- *Parameters*: `string relationshipType` — SPDX relationship type string (case-insensitive); + `null` is treated as empty string. +- *Returns*: `SpdxRelationshipType` — corresponding enum value. +- *Postconditions*: An empty or null string returns `Missing`. +- *Throws*: `InvalidOperationException` when the string is not recognized. + +**ToText**: Converts a `SpdxRelationshipType` to its canonical SPDX string. + +- *Parameters*: `SpdxRelationshipType relationshipType` — enum value to convert. +- *Returns*: `string` — SPDX text representation. +- *Throws*: `InvalidOperationException` when `relationshipType` is `Missing` or an unrecognized enum value. + +#### Enum Error Handling + +- `FromText` throws `InvalidOperationException` for unrecognized (non-empty) strings. +- `ToText` throws `InvalidOperationException` for `Missing` or out-of-range enum values. +- Neither method performs I/O or has side effects. + +#### Dependencies and Callers + +- Consumed by **Spdx2JsonDeserializer** (via `FromText`) during JSON parsing. +- Consumed by **Spdx2JsonSerializer** (via `ToText`) during JSON serialization. diff --git a/docs/design/spdx-model/spdx-snippet.md b/docs/design/spdx-model/spdx-snippet.md index b2483d2..4fc27ab 100644 --- a/docs/design/spdx-model/spdx-snippet.md +++ b/docs/design/spdx-model/spdx-snippet.md @@ -1,37 +1,78 @@ -# SpdxSnippet Unit Design +## SpdxSnippet -## Purpose +### Purpose `SpdxSnippet` represents a portion of a file in an SPDX document. Snippets are used when a -specific range of bytes (or lines) within a file has different licensing or provenance from the -rest of the file, enabling granular compliance tracking for reused code segments. +specific byte or line range within a file has different licensing or provenance from the rest of +the file, enabling granular compliance tracking for reused code segments. -## Design +### Data Model -`SpdxSnippet` is a sealed class that extends `SpdxLicenseElement`, inheriting `Id`, -`ConcludedLicense`, `CopyrightText`, and attribution fields. +**SnippetFromFile**: `string` — SPDX ID of the `SpdxFile` containing this snippet. -Data members (beyond inherited fields): +**SnippetByteStart**: `int` — Inclusive start byte offset of the snippet within the file. -| Property | Type | Description | -| -------- | ---- | ----------- | -| `SnippetFromFile` | `string` | SPDX ID of the file containing this snippet | -| `SnippetByteStart` | `int` | Inclusive start byte offset of the snippet | -| `SnippetByteEnd` | `int` | Inclusive end byte offset of the snippet | -| `SnippetLineStart` | `int` | Optional start line number | -| `SnippetLineEnd` | `int` | Optional end line number | -| `LicenseInfoInSnippet` | `string[]` | License expressions found in this snippet | -| `Comment` | `string?` | Optional comment | -| `Name` | `string?` | Optional human-readable snippet name | +**SnippetByteEnd**: `int` — Inclusive end byte offset of the snippet within the file. -Key methods: +**SnippetLineStart**: `int` — Optional start line number; `0` if unspecified. -- `DeepCopy()` — returns a fully deep-copied instance -- `Enhance(SpdxSnippet)` — fills in missing fields from another instance -- `Enhance(array, array)` — static method merging snippet arrays, matching on file ID and byte range -- `Validate(List)` — appends validation issues to the supplied list -- `Same` — static `IEqualityComparer` comparing by file, byte start, and byte end +**SnippetLineEnd**: `int` — Optional end line number; `0` if unspecified. -## Dependencies +**LicenseInfoInSnippet**: `string[]` — License expressions found in this snippet. -- `SpdxLicenseElement` (base class) +**Comment**: `string?` — Optional free-text comment. + +**Name**: `string?` — Optional human-readable snippet name. + +*Inherited from `SpdxLicenseElement`*: `Id`, `ConcludedLicense`, `LicenseComments`, +`CopyrightText`, `AttributionText`, `Annotations`. + +### Key Methods + +**DeepCopy**: Returns a fully deep-copied instance. + +- *Parameters*: none. +- *Returns*: `SpdxSnippet` — independent copy. +- *Preconditions*: none. +- *Postconditions*: The returned instance shares no mutable references with the original. + +**Enhance**: Fills in missing fields from another instance. + +- *Parameters*: `SpdxSnippet other` — source of additional field values. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Empty or null fields are populated from `other`. + +**Enhance (static array merge)**: Merges two snippet arrays by matching on file SPDX ID and byte +range. + +- *Parameters*: `SpdxSnippet[] base`, `SpdxSnippet[] additions`. +- *Returns*: `SpdxSnippet[]` — merged array. +- *Preconditions*: none. +- *Postconditions*: Matching entries are enhanced; new entries are appended. + +**Validate**: Appends validation issues to the supplied list. + +- *Parameters*: `List issues` — list to append issues to. +- *Returns*: `void` +- *Preconditions*: none. +- *Postconditions*: Missing required fields, invalid byte ranges, malformed IDs, and invalid + annotations are recorded in `issues`. + +**Same**: `static IEqualityComparer` — compares by `SnippetFromFile`, byte start, +and byte end. + +### Error Handling + +Validation errors are collected into the `List` passed to `Validate`. No exceptions are +thrown by `DeepCopy`, `Enhance`, or the static merge method. + +### Dependencies + +- **SpdxLicenseElement** — abstract base class. + +### Callers + +- **SpdxDocument** — holds the `Snippets` array. +- **Spdx2JsonDeserializer** — constructs `SpdxSnippet` instances during deserialization. +- **Spdx2JsonSerializer** — serializes `SpdxSnippet` instances to JSON. diff --git a/docs/design/spdx-model/transform/spdx-relationships.md b/docs/design/spdx-model/transform/spdx-relationships.md index a8f5456..3f43852 100644 --- a/docs/design/spdx-model/transform/spdx-relationships.md +++ b/docs/design/spdx-model/transform/spdx-relationships.md @@ -1,30 +1,60 @@ -# SpdxRelationships Unit Design +### SpdxRelationships -## Purpose +#### Purpose `SpdxRelationships` provides utility methods for adding SPDX relationships to an `SpdxDocument` without duplication. It simplifies the common pattern of programmatically constructing SPDX relationship graphs by handling deduplication automatically. -## Design +#### Data Model -`SpdxRelationships` is a public static utility class with no instance state. +N/A - `SpdxRelationships` is a public static utility class with no instance state. -Key methods: +#### Key Methods -| Method | Description | -| ------ | ----------- | -| `Add(SpdxDocument, IEnumerable, bool)` | Adds relationships with deduplication; optional replace | -| `Add(SpdxDocument, SpdxRelationship)` | Adds a single relationship if not already present | +**Add (batch)**: Adds multiple relationships to a document with optional replacement. -Key design decisions: +- *Parameters*: `SpdxDocument document` — target document; `IEnumerable relationships` — + relationships to add; `bool replace = false` — when `true`, existing relationships with matching + source and target elements are removed before adding. +- *Returns*: `void` +- *Preconditions*: Each relationship's source element ID must exist in the document. Each + relationship's target element ID must either exist in the document, be `NOASSERTION`, or use + the `DocumentRef-` prefix. +- *Postconditions*: All supplied relationships are present in the document; if `replace` is `true`, + previously existing relationships with the same source and target are removed. +- *Note*: When `replace` is `true`, removal uses `SpdxRelationship.SameElements` (type-agnostic, + matches by source and target ID only) so that a replace-and-add can change the relationship type + between the same pair of elements. Deduplication during the add path uses `SpdxRelationship.Same` + (type-inclusive) so that relationships of different types between the same elements co-exist. -- Deduplication is performed using the `SpdxRelationship.Same` equality comparer so that the - same logical relationship (same elements and type) is never written twice. -- The optional `replace` flag on the batch overload allows callers to update existing - relationships rather than skip duplicates. +**Add (single)**: Adds a single relationship to the document if not already present. -## Dependencies +- *Parameters*: `SpdxDocument document` — target document; `SpdxRelationship relationship` — + relationship to add. +- *Returns*: `void` +- *Preconditions*: The relationship source ID must match an element in the document. The target + ID must either match an element in the document, be `NOASSERTION`, or use the `DocumentRef-` + external-reference prefix. +- *Postconditions*: If the same logical relationship (same source, target, and type) already + exists, it is enhanced with any new field values. Otherwise a deep copy is appended. -- `SpdxDocument` — the target document whose `Relationships` array is modified -- `SpdxRelationship` — the relationship type and its `Same` equality comparer +#### Error Handling + +An `ArgumentException` (parameter name: `relationship`) is thrown under two conditions: + +1. The relationship's source element ID (`SpdxRelationship.Id`) is not found in the document. +2. The relationship's target element ID (`SpdxRelationship.RelatedSpdxElement`) is not found + in the document and is neither `NOASSERTION` nor prefixed with `DocumentRef-`. + +These checks prevent malformed documents from being constructed. In the batch overload all +relationships are validated before any mutation so that a failure leaves the document unchanged. + +#### Dependencies + +- **SpdxDocument** — the target document whose `Relationships` array is modified. +- **SpdxRelationship** — the relationship type and its `Same` and `SameElements` equality comparers. + +#### Callers + +- External consumers of the library who programmatically build or modify SPDX relationship graphs. diff --git a/docs/design/spdx-model/transform/transform.md b/docs/design/spdx-model/transform/transform.md index 81e11dd..c21ef40 100644 --- a/docs/design/spdx-model/transform/transform.md +++ b/docs/design/spdx-model/transform/transform.md @@ -1,36 +1,53 @@ -# Transform Subsystem Design - -## Purpose +### Transform The Transform subsystem provides utilities for manipulating SPDX documents in memory, enabling consumers to programmatically build and modify SPDX relationship graphs. -## Units +#### Overview -| Unit | File | Responsibility | -| ---- | ---- | -------------- | -| `SpdxRelationships` | `Transform/SpdxRelationships.cs` | Utilities for adding and managing SPDX relationships | +The Transform subsystem contains a single unit, `SpdxRelationships`, which provides static +helper methods for adding relationships to an `SpdxDocument`. It handles deduplication +automatically so that callers do not need to check for existing relationships before adding new +ones. -## Design +#### Interfaces -### SpdxRelationships +**SpdxRelationships.Add (batch)**: Adds multiple relationships to a document with deduplication. -`SpdxRelationships` is a static utility class that provides helper methods for adding relationships -to an `SpdxDocument`. It ensures relationships are added without duplication and in a consistent -manner, reducing boilerplate for consumers constructing SPDX documents programmatically. +- *Type*: In-process .NET public API +- *Role*: Provider +- *Contract*: Accepts an `SpdxDocument`, an `IEnumerable`, and an optional + `replace` flag. Adds each relationship if not already present; when `replace` is `true`, + existing relationships with matching source and target elements are removed first. +- *Constraints*: The source element ID must always exist in the document; the target element ID + must either exist in the document, be `NOASSERTION`, or use the `DocumentRef-` external-reference + prefix. An `ArgumentException` is thrown when either constraint is violated. +- *Atomicity*: When `ArgumentException` is thrown, the document is left in its original state — + no relationships are added or removed. -Key methods: +**SpdxRelationships.Add (single)**: Adds a single relationship to a document if not already present. -- `Add(...)` — adds a single relationship to the document if it does not already exist -- `Add(...)` — adds multiple relationships, deduplicating against existing entries, with an optional `replace` parameter +- *Type*: In-process .NET public API +- *Role*: Provider +- *Contract*: Accepts an `SpdxDocument` and an `SpdxRelationship`. If the same relationship + (same source, target, and type) already exists it is enhanced; otherwise a deep copy is appended. +- *Constraints*: The source element ID must exist in the document. The target element ID must + either exist in the document, be `NOASSERTION`, or use the `DocumentRef-` prefix. -Key design decisions: +#### Design -- Static class with no instance state to simplify usage -- Deduplication logic prevents malformed documents with duplicate relationship entries +`SpdxRelationships` is a static class with no instance state: -## Dependencies +The batch `Add` overload executes in three phases to preserve atomicity: -The Transform subsystem depends on: +1. **Pre-validate**: Materialize the incoming enumerable into an array and call the internal + `ValidateRelationship` helper on every relationship. If any validation fails an + `ArgumentException` is thrown immediately and the document is left unchanged. +2. **Replace** (when `replace` is `true`): Remove all existing relationships whose source and + target element IDs match any incoming relationship, using `SpdxRelationship.SameElements` + (type-agnostic comparison). +3. **Add**: Call the internal `AddValidated` helper for each incoming relationship. `AddValidated` + searches for an existing match using `SpdxRelationship.Same` (type-inclusive); if found it + calls `Enhance`, otherwise it appends a `DeepCopy`. -- `SpdxDocument` and `SpdxRelationship` data model units +The single `Add` overload delegates directly: it calls `ValidateRelationship` then `AddValidated`. diff --git a/docs/design/title.txt b/docs/design/title.txt index ce459d6..6073a50 100644 --- a/docs/design/title.txt +++ b/docs/design/title.txt @@ -1,15 +1,14 @@ --- -title: SpdxModel Software Design -subtitle: Software Design Document for the SpdxModel Library -author: DEMA Consulting -description: Software design document for the SpdxModel C# library for reading, writing, and manipulating SPDX SBOM documents +title: "SpdxModel Software Design Document" +subtitle: "SPDX document model for .NET" +author: "DEMA Consulting" +description: "Software Design Document for SpdxModel" lang: en-US keywords: - - SpdxModel - Design - Software Design Document - C# - .NET - SPDX - SBOM ---- +--- \ No newline at end of file diff --git a/docs/reqstream/ots/buildmark.yaml b/docs/reqstream/ots/buildmark.yaml index d97a927..d44cbfe 100644 --- a/docs/reqstream/ots/buildmark.yaml +++ b/docs/reqstream/ots/buildmark.yaml @@ -1,7 +1,5 @@ --- -# BuildMark OTS Software Requirements -# -# Requirements for the BuildMark build documentation tool functionality. +# BuildMark OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/fileassert.yaml b/docs/reqstream/ots/fileassert.yaml index 6bf0b37..251bf29 100644 --- a/docs/reqstream/ots/fileassert.yaml +++ b/docs/reqstream/ots/fileassert.yaml @@ -1,7 +1,5 @@ --- -# FileAssert OTS Software Requirements -# -# Requirements for the FileAssert document assertion tool functionality. +# FileAssert OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/mstest.yaml b/docs/reqstream/ots/mstest.yaml index d3ed5a2..fbc8b8f 100644 --- a/docs/reqstream/ots/mstest.yaml +++ b/docs/reqstream/ots/mstest.yaml @@ -1,7 +1,5 @@ --- -# MSTest OTS Software Requirements -# -# Requirements for the MSTest testing framework functionality. +# MSTest OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/pandoc.yaml b/docs/reqstream/ots/pandoc.yaml index f00d443..712e121 100644 --- a/docs/reqstream/ots/pandoc.yaml +++ b/docs/reqstream/ots/pandoc.yaml @@ -1,7 +1,5 @@ --- -# Pandoc OTS Software Requirements -# -# Requirements for the Pandoc document conversion tool functionality. +# Pandoc OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/reqstream.yaml b/docs/reqstream/ots/reqstream.yaml index a2bcd3c..366778d 100644 --- a/docs/reqstream/ots/reqstream.yaml +++ b/docs/reqstream/ots/reqstream.yaml @@ -1,7 +1,5 @@ --- -# ReqStream OTS Software Requirements -# -# Requirements for the ReqStream requirements traceability tool functionality. +# ReqStream OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/reviewmark.yaml b/docs/reqstream/ots/reviewmark.yaml index b179786..941a5ed 100644 --- a/docs/reqstream/ots/reviewmark.yaml +++ b/docs/reqstream/ots/reviewmark.yaml @@ -1,7 +1,5 @@ --- -# ReviewMark OTS Software Requirements -# -# Requirements for the ReviewMark file review tool functionality. +# ReviewMark OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/sarifmark.yaml b/docs/reqstream/ots/sarifmark.yaml index 81f7ecb..354bb09 100644 --- a/docs/reqstream/ots/sarifmark.yaml +++ b/docs/reqstream/ots/sarifmark.yaml @@ -1,7 +1,5 @@ --- -# SarifMark OTS Software Requirements -# -# Requirements for the SarifMark SARIF report processing tool functionality. +# SarifMark OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/sonarmark.yaml b/docs/reqstream/ots/sonarmark.yaml index 4df432a..6a2e711 100644 --- a/docs/reqstream/ots/sonarmark.yaml +++ b/docs/reqstream/ots/sonarmark.yaml @@ -1,7 +1,5 @@ --- -# SonarMark OTS Software Requirements -# -# Requirements for the SonarMark quality reporting tool functionality. +# SonarMark OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/versionmark.yaml b/docs/reqstream/ots/versionmark.yaml index bca0c76..754492e 100644 --- a/docs/reqstream/ots/versionmark.yaml +++ b/docs/reqstream/ots/versionmark.yaml @@ -1,7 +1,5 @@ --- -# VersionMark OTS Software Requirements -# -# Requirements for the VersionMark version tracking tool functionality. +# VersionMark OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/ots/weasyprint.yaml b/docs/reqstream/ots/weasyprint.yaml index f9aae37..f17fd3c 100644 --- a/docs/reqstream/ots/weasyprint.yaml +++ b/docs/reqstream/ots/weasyprint.yaml @@ -1,7 +1,5 @@ --- -# WeasyPrint OTS Software Requirements -# -# Requirements for the WeasyPrint PDF generation tool functionality. +# WeasyPrint OTS requirements sections: - title: OTS Software Requirements diff --git a/docs/reqstream/spdx-model/io/io.yaml b/docs/reqstream/spdx-model/io/io.yaml index b668ca0..0630a90 100644 --- a/docs/reqstream/spdx-model/io/io.yaml +++ b/docs/reqstream/spdx-model/io/io.yaml @@ -1,29 +1,40 @@ --- -# SpdxModel IO Subsystem Requirements -# -# This is the subsystem-level requirements file for the IO subsystem. -# Unit requirements are in the sibling unit files: -# spdx-2-json-deserializer.yaml -# spdx-2-json-serializer.yaml +# IO subsystem requirements sections: - - title: IO Subsystem Requirements - requirements: - - id: SpdxModel-IO-Serialization - title: The library shall support JSON serialization and deserialization of SPDX 2.x documents. - tags: - - io - - serialization - justification: | - JSON is the primary interchange format for SPDX documents. Supporting serialization and - deserialization in JSON ensures interoperability with other SPDX tools and systems that - produce and consume SPDX SBOMs. - children: - - SpdxModel-Serialization-Deserialize22Json - - SpdxModel-Serialization-Deserialize23Json - - SpdxModel-Serialization-DeserializeElements - - SpdxModel-Serialization-SerializeJson - - SpdxModel-Serialization-SerializeElements - tests: - - SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidDocument - - SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidDocument + - title: SpdxModel Requirements + sections: + - title: IO Requirements + requirements: + - id: SpdxModel-IO-Deserialization + title: The library shall support JSON deserialization of SPDX 2.x documents. + tags: + - io + - serialization + justification: | + JSON deserialization is the primary mechanism for reading SPDX documents produced by + other tools. Supporting both SPDX 2.2 and 2.3 formats ensures compatibility with the + broad ecosystem of SPDX tooling. + children: + - SpdxModel-IO-Spdx2JsonDeserializer-Deserialize22Json + - SpdxModel-IO-Spdx2JsonDeserializer-Deserialize23Json + - SpdxModel-IO-Spdx2JsonDeserializer-DeserializeElements + tests: + - SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidDocument + - SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidDocument + + - id: SpdxModel-IO-Serialization + title: The library shall support JSON serialization of SPDX 2.x documents. + tags: + - io + - serialization + justification: | + JSON serialization enables applications to produce SPDX documents for distribution, + toolchain integration, and compliance reporting. Producing well-formed SPDX JSON ensures + interoperability with other tools in the SPDX ecosystem. + children: + - SpdxModel-Serialization-SerializeJson + - SpdxModel-Serialization-SerializeElements + tests: + - SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidDocument + - SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidDocument diff --git a/docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml b/docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml index c4f7e2f..3a95b7a 100644 --- a/docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml +++ b/docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml @@ -1,63 +1,64 @@ --- -# SpdxModel Spdx2JsonDeserializer Unit Requirements -# -# This file defines the requirements for the Spdx2JsonDeserializer unit -# in the IO subsystem of the SpdxModel library. +# Spdx2JsonDeserializer unit requirements sections: - - title: Spdx2JsonDeserializer Requirements - requirements: - - id: SpdxModel-Serialization-Deserialize22Json - title: The library shall support deserializing SPDX 2.2 JSON documents. - tags: - - serialization - justification: | - Deserializing SPDX 2.2 JSON documents is essential for backward compatibility and - interoperability with systems using the SPDX 2.2 specification. This ensures that the - library can read and process existing SPDX 2.2 documents from various sources. - tests: - - Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument + - title: SpdxModel Requirements + sections: + - title: IO Requirements + sections: + - title: Spdx2JsonDeserializer Requirements + requirements: + - id: SpdxModel-IO-Spdx2JsonDeserializer-Deserialize22Json + title: The library shall support deserializing SPDX 2.2 JSON documents. + tags: + - serialization + justification: | + Deserializing SPDX 2.2 JSON documents is essential for backward compatibility and + interoperability with systems using the SPDX 2.2 specification. This ensures that the + library can read and process existing SPDX 2.2 documents from various sources. + tests: + - Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument - - id: SpdxModel-Serialization-Deserialize23Json - title: The library shall support deserializing SPDX 2.3 JSON documents. - tags: - - serialization - justification: | - Support for SPDX 2.3 JSON documents ensures the library remains current with the latest - SPDX specification. This allows users to leverage new features and improvements introduced - in SPDX 2.3 while maintaining compatibility with modern SBOM tools. - tests: - - Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument + - id: SpdxModel-IO-Spdx2JsonDeserializer-Deserialize23Json + title: The library shall support deserializing SPDX 2.3 JSON documents. + tags: + - serialization + justification: | + Support for SPDX 2.3 JSON documents ensures the library remains current with the latest + SPDX specification. This allows users to leverage new features and improvements introduced + in SPDX 2.3 while maintaining compatibility with modern SBOM tools. + tests: + - Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument - - id: SpdxModel-Serialization-DeserializeElements - title: The library shall correctly deserialize all SPDX 2.x element types from JSON. - tags: - - serialization - justification: | - Each SPDX element type (annotation, checksum, package, file, snippet, relationship, etc.) - has distinct JSON representation and field mappings. Verifying each element type is - deserialized correctly ensures the library faithfully reconstructs every artifact present - in a source SPDX JSON document. - tests: - - Spdx2JsonDeserializer_DeserializeDocument_CorrectResults - - Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults - - Spdx2JsonDeserializer_DeserializeAnnotations_CorrectResults - - Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults - - Spdx2JsonDeserializer_DeserializeChecksums_CorrectResults - - Spdx2JsonDeserializer_DeserializeCreationInformation_CorrectResults - - Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectResults - - Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_CorrectResults - - Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults - - Spdx2JsonDeserializer_DeserializeExternalReferences_CorrectResults - - Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResults - - Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_CorrectResults - - Spdx2JsonDeserializer_DeserializeFile_CorrectResults - - Spdx2JsonDeserializer_DeserializeFiles_CorrectResults - - Spdx2JsonDeserializer_DeserializePackage_CorrectResults - - Spdx2JsonDeserializer_DeserializePackages_CorrectResults - - Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResults - - Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults - - Spdx2JsonDeserializer_DeserializeRelationships_CorrectResults - - Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults - - Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero - - Spdx2JsonDeserializer_DeserializeSnippets_CorrectResults + - id: SpdxModel-IO-Spdx2JsonDeserializer-DeserializeElements + title: The library shall correctly deserialize all SPDX 2.x element types from JSON. + tags: + - serialization + justification: | + Each SPDX element type (annotation, checksum, package, file, snippet, relationship, etc.) + has distinct JSON representation and field mappings. Verifying each element type is + deserialized correctly ensures the library faithfully reconstructs every artifact present + in a source SPDX JSON document. + tests: + - Spdx2JsonDeserializer_DeserializeDocument_CorrectResults + - Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults + - Spdx2JsonDeserializer_DeserializeAnnotations_CorrectResults + - Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults + - Spdx2JsonDeserializer_DeserializeChecksums_CorrectResults + - Spdx2JsonDeserializer_DeserializeCreationInformation_CorrectResults + - Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectResults + - Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_CorrectResults + - Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults + - Spdx2JsonDeserializer_DeserializeExternalReferences_CorrectResults + - Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResults + - Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_CorrectResults + - Spdx2JsonDeserializer_DeserializeFile_CorrectResults + - Spdx2JsonDeserializer_DeserializeFiles_CorrectResults + - Spdx2JsonDeserializer_DeserializePackage_CorrectResults + - Spdx2JsonDeserializer_DeserializePackages_CorrectResults + - Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResults + - Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults + - Spdx2JsonDeserializer_DeserializeRelationships_CorrectResults + - Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults + - Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero + - Spdx2JsonDeserializer_DeserializeSnippets_CorrectResults diff --git a/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml b/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml index a99ef74..a4ff373 100644 --- a/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml +++ b/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml @@ -1,51 +1,52 @@ --- -# SpdxModel Spdx2JsonSerializer Unit Requirements -# -# This file defines the requirements for the Spdx2JsonSerializer unit -# in the IO subsystem of the SpdxModel library. +# Spdx2JsonSerializer unit requirements sections: - - title: Spdx2JsonSerializer Requirements - requirements: - - id: SpdxModel-Serialization-SerializeJson - title: The library shall support serializing SPDX documents to JSON format. - tags: - - serialization - justification: | - Serialization capability is fundamental for creating and exporting SPDX documents in JSON - format. This enables users to generate SBOMs programmatically and share them with other - systems and tools in the SPDX ecosystem. - tests: - - Spdx2JsonSerializer_SerializeDocument_CorrectResults - - Spdx2JsonSerializer_Serialize_CorrectResults + - title: SpdxModel Requirements + sections: + - title: IO Requirements + sections: + - title: Spdx2JsonSerializer Requirements + requirements: + - id: SpdxModel-Serialization-SerializeJson + title: The library shall support serializing SPDX documents to JSON format. + tags: + - serialization + justification: | + Serialization capability is fundamental for creating and exporting SPDX documents in JSON + format. This enables users to generate SBOMs programmatically and share them with other + systems and tools in the SPDX ecosystem. + tests: + - Spdx2JsonSerializer_SerializeDocument_CorrectResults + - Spdx2JsonSerializer_Serialize_CorrectResults - - id: SpdxModel-Serialization-SerializeElements - title: The library shall correctly serialize all SPDX 2.x element types to JSON. - tags: - - serialization - justification: | - Each SPDX element type (annotation, checksum, package, file, snippet, relationship, etc.) - has distinct JSON representation and field mappings. Verifying each element type is - serialized correctly ensures the library faithfully produces all artifacts when writing - an SPDX JSON document. - tests: - - Spdx2JsonSerializer_SerializeAnnotation_CorrectResults - - Spdx2JsonSerializer_SerializeAnnotations_CorrectResults - - Spdx2JsonSerializer_SerializeChecksum_CorrectResults - - Spdx2JsonSerializer_SerializeChecksums_CorrectResults - - Spdx2JsonSerializer_SerializeCreationInformation_CorrectResults - - Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResults - - Spdx2JsonSerializer_SerializeExternalDocumentReferences_CorrectResults - - Spdx2JsonSerializer_SerializeExternalReference_CorrectResults - - Spdx2JsonSerializer_SerializeExternalReferences_CorrectResults - - Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults - - Spdx2JsonSerializer_SerializeExtractedLicensingInfos_CorrectResults - - Spdx2JsonSerializer_SerializeFile_CorrectResults - - Spdx2JsonSerializer_SerializeFiles_CorrectResults - - Spdx2JsonSerializer_SerializePackage_CorrectResults - - Spdx2JsonSerializer_SerializePackages_CorrectResults - - Spdx2JsonSerializer_SerializePackageVerificationCode_CorrectResults - - Spdx2JsonSerializer_SerializeRelationship_CorrectResults - - Spdx2JsonSerializer_SerializeRelationships_CorrectResults - - Spdx2JsonSerializer_SerializeSnippet_CorrectResults - - Spdx2JsonSerializer_SerializeSnippets_CorrectResults + - id: SpdxModel-Serialization-SerializeElements + title: The library shall correctly serialize all SPDX 2.x element types to JSON. + tags: + - serialization + justification: | + Each SPDX element type (annotation, checksum, package, file, snippet, relationship, etc.) + has distinct JSON representation and field mappings. Verifying each element type is + serialized correctly ensures the library faithfully produces all artifacts when writing + an SPDX JSON document. + tests: + - Spdx2JsonSerializer_SerializeAnnotation_CorrectResults + - Spdx2JsonSerializer_SerializeAnnotations_CorrectResults + - Spdx2JsonSerializer_SerializeChecksum_CorrectResults + - Spdx2JsonSerializer_SerializeChecksums_CorrectResults + - Spdx2JsonSerializer_SerializeCreationInformation_CorrectResults + - Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResults + - Spdx2JsonSerializer_SerializeExternalDocumentReferences_CorrectResults + - Spdx2JsonSerializer_SerializeExternalReference_CorrectResults + - Spdx2JsonSerializer_SerializeExternalReferences_CorrectResults + - Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults + - Spdx2JsonSerializer_SerializeExtractedLicensingInfos_CorrectResults + - Spdx2JsonSerializer_SerializeFile_CorrectResults + - Spdx2JsonSerializer_SerializeFiles_CorrectResults + - Spdx2JsonSerializer_SerializePackage_CorrectResults + - Spdx2JsonSerializer_SerializePackages_CorrectResults + - Spdx2JsonSerializer_SerializePackageVerificationCode_CorrectResults + - Spdx2JsonSerializer_SerializeRelationship_CorrectResults + - Spdx2JsonSerializer_SerializeRelationships_CorrectResults + - Spdx2JsonSerializer_SerializeSnippet_CorrectResults + - Spdx2JsonSerializer_SerializeSnippets_CorrectResults diff --git a/docs/reqstream/spdx-model/io/spdx-constants.yaml b/docs/reqstream/spdx-model/io/spdx-constants.yaml new file mode 100644 index 0000000..aa5db38 --- /dev/null +++ b/docs/reqstream/spdx-model/io/spdx-constants.yaml @@ -0,0 +1,24 @@ +--- +# SpdxConstants unit requirements + +sections: + - title: SpdxModel Requirements + sections: + - title: IO Requirements + sections: + - title: SpdxConstants Requirements + requirements: + - id: SpdxModel-IO-Constants + title: >- + The library shall produce and consume JSON output using the correct SPDX field + names as defined by the SPDX 2.x specification. + tags: + - io + justification: | + Centralizing SPDX JSON field name strings in constants avoids hard-coded literals + in the serializer and deserializer, reducing the risk of typos and making future + specification updates easier to manage. This requirement is verified indirectly + through the IO serialization and deserialization tests. + tests: + - SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidDocument + - SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidDocument diff --git a/docs/reqstream/spdx-model/platform-requirements.yaml b/docs/reqstream/spdx-model/platform-requirements.yaml index 1cb5b46..4fe5767 100644 --- a/docs/reqstream/spdx-model/platform-requirements.yaml +++ b/docs/reqstream/spdx-model/platform-requirements.yaml @@ -1,69 +1,91 @@ --- -# SpdxModel Platform Requirements -# -# This file defines the platform support requirements for the SpdxModel library. +# SpdxModel platform requirements sections: - - title: Platform Requirements - requirements: - - id: SpdxModel-Platform-MacOS - title: The library shall build and run on macOS platforms. - tags: - - platform - justification: | - DEMA Consulting libraries must support macOS for developers using Apple platforms. - tests: - # Tests link to "macos" to ensure results come from macOS platform - - "macos@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully" - - "macos@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds" + - title: SpdxModel Requirements + sections: + - title: Platform Support + requirements: + - id: SpdxModel-Platform-MacOS + title: The library shall build and run on macOS platforms. + tags: + - platform + justification: | + DEMA Consulting libraries must support macOS for developers using Apple platforms. + tests: + # Tests link to "macos" to ensure results come from macOS platform + - "macos@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully" + - "macos@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds" - - id: SpdxModel-Platform-Net8 - title: The library shall support .NET 8 runtime. - tags: - - platform - justification: | - .NET 8 is a long-term support (LTS) release and provides a stable foundation for - enterprise applications. Supporting .NET 8 ensures the library can be used in production - environments requiring long-term stability and support. - tests: - - net8.0@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully - - net8.0@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds + - id: SpdxModel-Platform-Windows + title: The library shall build and run on Windows platforms. + tags: + - platform + justification: | + DEMA Consulting libraries must support Windows as the primary development and + deployment platform for enterprise .NET applications. + tests: + - "windows@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully" + - "windows@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds" - - id: SpdxModel-Platform-Net9 - title: The library shall support .NET 9 runtime. - tags: - - platform - justification: | - .NET 9 is a standard-term support (STS) release providing newer features and performance - improvements. Supporting .NET 9 allows users to leverage the latest .NET capabilities while - the framework is current. - tests: - - net9.0@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully - - net9.0@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds + - id: SpdxModel-Platform-Linux + title: The library shall build and run on Linux platforms. + tags: + - platform + justification: | + DEMA Consulting libraries must support Linux for CI/CD pipelines and server + deployments in cloud-native environments. + tests: + - "ubuntu@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully" + - "ubuntu@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds" - - id: SpdxModel-Platform-Net10 - title: The library shall support .NET 10 runtime. - tags: - - platform - justification: | - .NET 10 represents the latest .NET platform release. Supporting .NET 10 ensures users - can adopt the most recent framework version and benefit from the latest performance, - security, and feature improvements. - tests: - - net10.0@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully - - net10.0@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds + - id: SpdxModel-Platform-Net8 + title: The library shall support .NET 8 runtime. + tags: + - platform + justification: | + .NET 8 is a long-term support (LTS) release and provides a stable foundation for + enterprise applications. Supporting .NET 8 ensures the library can be used in production + environments requiring long-term stability and support. + tests: + - net8.0@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully + - net8.0@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds - - id: SpdxModel-Platform-NetStd20 - title: The library shall support the .NET Standard 2.0 target framework. - tags: - - platform - justification: | - .NET Standard 2.0 is a widely-supported target framework that enables the library to - be used in MSBuild extensions and other tooling that requires .NET Standard compatibility. - Supporting this target framework ensures the library can be integrated into a broader - range of .NET projects, including those targeting .NET Framework and older .NET Core versions. - The net481 test target on Windows provides direct runtime evidence of .NET Standard 2.0 - compatibility, as .NET Framework 4.8.1 fully implements the .NET Standard 2.0 API surface. - tests: - - "net481@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully" - - "net481@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds" + - id: SpdxModel-Platform-Net9 + title: The library shall support .NET 9 runtime. + tags: + - platform + justification: | + .NET 9 is a standard-term support (STS) release providing newer features and performance + improvements. Supporting .NET 9 allows users to leverage the latest .NET capabilities while + the framework is current. + tests: + - net9.0@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully + - net9.0@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds + + - id: SpdxModel-Platform-Net10 + title: The library shall support .NET 10 runtime. + tags: + - platform + justification: | + .NET 10 represents the latest .NET platform release. Supporting .NET 10 ensures users + can adopt the most recent framework version and benefit from the latest performance, + security, and feature improvements. + tests: + - net10.0@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully + - net10.0@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds + + - id: SpdxModel-Platform-NetStd20 + title: The library shall support the .NET Standard 2.0 target framework. + tags: + - platform + justification: | + .NET Standard 2.0 is a widely-supported target framework that enables the library to + be used in MSBuild extensions and other tooling that requires .NET Standard compatibility. + Supporting this target framework ensures the library can be integrated into a broader + range of .NET projects, including those targeting .NET Framework and older .NET Core versions. + The net481 test target on Windows provides direct runtime evidence of .NET Standard 2.0 + compatibility, as .NET Framework 4.8.1 fully implements the .NET Standard 2.0 API surface. + tests: + - "net481@SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully" + - "net481@SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds" diff --git a/docs/reqstream/spdx-model/spdx-annotation.yaml b/docs/reqstream/spdx-model/spdx-annotation.yaml index 55666c2..94abf84 100644 --- a/docs/reqstream/spdx-model/spdx-annotation.yaml +++ b/docs/reqstream/spdx-model/spdx-annotation.yaml @@ -1,28 +1,28 @@ --- -# SpdxAnnotation Unit Requirements -# -# This file defines requirements for the SpdxAnnotation unit. +# SpdxAnnotation unit requirements sections: - - title: SpdxAnnotation Unit Requirements - requirements: - - id: SpdxModel-Data-Annotations - title: The library shall support SPDX annotations. - tags: - - data-model - justification: | - Annotations allow adding review and assessment information to SPDX elements. This supports - compliance workflows where reviewers need to document their findings and decisions about - software components. - tests: - - SpdxAnnotation_SameComparer_ComparesCorrectly - - SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance - - SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxAnnotation_Validate_InvalidAnnotator - - SpdxAnnotation_Validate_InvalidDate - - SpdxAnnotation_Validate_InvalidType - - SpdxAnnotation_Validate_InvalidComment - - SpdxAnnotationTypeExtensions_FromText_Valid - - SpdxAnnotationTypeExtensions_FromText_Invalid - - SpdxAnnotationTypeExtensions_ToText_Valid - - SpdxAnnotationTypeExtensions_ToText_Invalid + - title: SpdxModel Requirements + sections: + - title: SpdxAnnotation Requirements + requirements: + - id: SpdxModel-Data-Annotations + title: The library shall support SPDX annotations. + tags: + - data-model + justification: | + Annotations allow adding review and assessment information to SPDX elements. This supports + compliance workflows where reviewers need to document their findings and decisions about + software components. + tests: + - SpdxAnnotation_SameComparer_ComparesCorrectly + - SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance + - SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxAnnotation_Validate_InvalidAnnotator + - SpdxAnnotation_Validate_InvalidDate + - SpdxAnnotation_Validate_InvalidType + - SpdxAnnotation_Validate_InvalidComment + - SpdxAnnotationTypeExtensions_FromText_Valid + - SpdxAnnotationTypeExtensions_FromText_Invalid + - SpdxAnnotationTypeExtensions_ToText_Valid + - SpdxAnnotationTypeExtensions_ToText_Invalid diff --git a/docs/reqstream/spdx-model/spdx-checksum.yaml b/docs/reqstream/spdx-model/spdx-checksum.yaml index 5739569..8cf059b 100644 --- a/docs/reqstream/spdx-model/spdx-checksum.yaml +++ b/docs/reqstream/spdx-model/spdx-checksum.yaml @@ -1,26 +1,72 @@ --- -# SpdxChecksum Unit Requirements -# -# This file defines requirements for the SpdxChecksum unit. +# SpdxChecksum unit requirements sections: - - title: SpdxChecksum Unit Requirements - requirements: - - id: SpdxModel-Data-Checksums - title: The library shall support SPDX checksums with multiple algorithms. - tags: - - data-model - justification: | - Checksums with multiple algorithms provide integrity verification for files and packages. - Supporting multiple algorithms ensures flexibility and compatibility with different security - requirements and organizational policies. - tests: - - SpdxChecksum_SameComparer_ComparesCorrectly - - SpdxChecksum_DeepCopy_CreatesEqualButDistinctInstance - - SpdxChecksum_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxChecksum_Validate_InvalidAlgorithm - - SpdxChecksum_Validate_InvalidValue - - SpdxChecksumAlgorithmExtensions_FromText_Valid - - SpdxChecksumAlgorithmExtensions_FromText_InvalidAlgorithm - - SpdxChecksumAlgorithmExtensions_ToText_Valid - - SpdxChecksumAlgorithmExtensions_ToText_InvalidAlgorithm + - title: SpdxModel Requirements + sections: + - title: SpdxChecksum Requirements + requirements: + - id: SpdxModel-Data-Checksum-Compare + title: The library shall support equality comparison of SPDX checksums by algorithm and value. + tags: + - data-model + justification: | + Checksums must be compared by algorithm and value to support array merging and + deduplication across SPDX documents. + tests: + - SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquality + - SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse + - SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse + - SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue + - id: SpdxModel-Data-Checksum-DeepCopy + title: The library shall support creating independent deep copies of SPDX checksums. + tags: + - data-model + justification: | + Deep copies are required so that merging operations do not mutate the original + checksum instances. + tests: + - SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInstance + - id: SpdxModel-Data-Checksum-Enhance + title: The library shall support merging SPDX checksum arrays by algorithm and value identity. + tags: + - data-model + justification: | + Enhance enables combining checksum information from multiple SPDX documents into + a single deduplicated record. + tests: + - SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformation + - id: SpdxModel-Data-Checksum-Validate + title: The library shall validate SPDX checksum fields and report all issues found. + tags: + - data-model + - validation + justification: | + Validation ensures that checksum data is complete and consistent before + serialization or use by downstream consumers. + tests: + - SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue + - SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue + - SpdxChecksum_Validate_EmptyValue_ReportsValueIssue + - id: SpdxModel-Data-Checksum-FromText + title: The library shall convert SPDX algorithm text strings to the SpdxChecksumAlgorithm enumeration. + tags: + - data-model + justification: | + Deserialization from SPDX JSON and tag-value formats requires mapping algorithm + name strings to typed enum values. + tests: + - SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_ReturnsCorrectEnumValues + - SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_ThrowsInvalidOperationException + - SpdxChecksumAlgorithmExtensions_FromText_EmptyString_ReturnsMissing + - id: SpdxModel-Data-Checksum-ToText + title: The library shall convert SpdxChecksumAlgorithm enumeration values to SPDX algorithm text strings. + tags: + - data-model + justification: | + Serialization to SPDX JSON and tag-value formats requires mapping typed enum + values back to their canonical algorithm name strings. + tests: + - SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCorrectStrings + - SpdxChecksumAlgorithmExtensions_ToText_OutOfRangeEnum_ThrowsInvalidOperationException + - SpdxChecksumAlgorithmExtensions_ToText_MissingAlgorithm_ThrowsInvalidOperationException diff --git a/docs/reqstream/spdx-model/spdx-creation-information.yaml b/docs/reqstream/spdx-model/spdx-creation-information.yaml index d463e00..3045209 100644 --- a/docs/reqstream/spdx-model/spdx-creation-information.yaml +++ b/docs/reqstream/spdx-model/spdx-creation-information.yaml @@ -1,23 +1,26 @@ --- -# SpdxCreationInformation Unit Requirements -# -# This file defines requirements for the SpdxCreationInformation unit. +# SpdxCreationInformation unit requirements sections: - - title: SpdxCreationInformation Unit Requirements - requirements: - - id: SpdxModel-Data-CreationInformation - title: The library shall support SPDX document creation information. - tags: - - data-model - justification: | - Creation information is a required element of SPDX documents that provides metadata about - who created the document and when. Supporting this element is essential for SPDX compliance - and traceability of document provenance. - tests: - - SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance - - SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxCreationInformation_Validate_MissingCreators - - SpdxCreationInformation_Validate_InvalidCreator - - SpdxCreationInformation_Validate_InvalidCreatedDate - - SpdxCreationInformation_Validate_InvalidVersion + - title: SpdxModel Requirements + sections: + - title: SpdxCreationInformation Requirements + requirements: + - id: SpdxModel-Data-CreationInformation + title: The library shall support SPDX document creation information. + tags: + - data-model + justification: | + Creation information is a required element of SPDX documents that provides metadata about + who created the document and when. Supporting this element is essential for SPDX compliance + and traceability of document provenance. + tests: + - SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance + - SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxCreationInformation_Enhance_DuplicateCreators_DeduplicatesCreators + - SpdxCreationInformation_Validate_ValidInformation_NoIssues + - SpdxCreationInformation_Validate_MissingCreators + - SpdxCreationInformation_Validate_InvalidCreator + - SpdxCreationInformation_Validate_InvalidCreatedDate + - SpdxCreationInformation_Validate_InvalidVersion + - SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue diff --git a/docs/reqstream/spdx-model/spdx-document.yaml b/docs/reqstream/spdx-model/spdx-document.yaml index e4984ba..9d86101 100644 --- a/docs/reqstream/spdx-model/spdx-document.yaml +++ b/docs/reqstream/spdx-model/spdx-document.yaml @@ -1,65 +1,78 @@ --- -# SpdxDocument Unit Requirements -# -# This file defines requirements for the SpdxDocument unit. +# SpdxDocument unit requirements sections: - - title: SpdxDocument Unit Requirements - requirements: - - id: SpdxModel-Data-Document - title: The library shall support SPDX documents as the root container of the SPDX object model. - tags: - - data-model - justification: | - The SPDX document is the top-level artifact in an SPDX SBOM. It aggregates all - packages, files, snippets, relationships, and annotations, and is required for - expressing any SPDX-compliant bill of materials. - tests: - - SpdxDocument_SameComparer_ComparesCorrectly - - SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance + - title: SpdxModel Requirements + sections: + - title: SpdxDocument Requirements + requirements: + - id: SpdxModel-Data-Document + title: The library shall support SPDX documents as the root container of the SPDX object model. + tags: + - data-model + justification: | + The SPDX document is the top-level artifact in an SPDX SBOM. It aggregates all + packages, files, snippets, relationships, and annotations, and is required for + expressing any SPDX-compliant bill of materials. + tests: + - SpdxDocument_SameComparer_ComparesCorrectly + - SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance - - id: SpdxModel-Data-Document-Validate - title: The library shall validate SPDX document fields and report all violations. - tags: - - data-model - - validation - justification: | - Validation ensures that SPDX documents conform to the specification before they are - consumed or distributed. Reporting all violations rather than failing on the first - allows tooling to produce complete diagnostic output in a single pass. - tests: - - SpdxDocument_Validate_NoIssues - - SpdxDocument_Validate_InvalidId - - SpdxDocument_Validate_InvalidName - - SpdxDocument_Validate_InvalidVersion - - SpdxDocument_Validate_InvalidDataLicense - - SpdxDocument_Validate_InvalidNameSpace - - SpdxDocument_Validate_DuplicatePackageIds - - SpdxDocument_Validate_InvalidRelationship - - SpdxDocument_Validate_InvalidAnnotation + - id: SpdxModel-Data-Document-Validate + title: The library shall validate SPDX document fields and report all violations. + tags: + - data-model + - validation + justification: | + Validation ensures that SPDX documents conform to the specification before they are + consumed or distributed. Reporting all violations rather than failing on the first + allows tooling to produce complete diagnostic output in a single pass. + tests: + - SpdxDocument_Validate_NoIssues + - SpdxDocument_Validate_InvalidId + - SpdxDocument_Validate_InvalidName + - SpdxDocument_Validate_InvalidVersion + - SpdxDocument_Validate_InvalidDataLicense + - SpdxDocument_Validate_InvalidNameSpace + - SpdxDocument_Validate_DuplicatePackageIds + - SpdxDocument_Validate_InvalidRelationship + - SpdxDocument_Validate_InvalidAnnotation - - id: SpdxModel-Data-Document-Ntia - title: The library shall validate SPDX documents for NTIA minimum SBOM element compliance. - tags: - - data-model - - validation - - ntia - justification: | - The NTIA Minimum Elements for a Software Bill of Materials defines a baseline set of - data fields that must be present in a conforming SBOM. Supporting this check allows - downstream tooling to assess NTIA compliance without reimplementing the rules. - tests: - - SpdxDocument_Validate_NtiaIssues + - id: SpdxModel-Data-Document-Ntia + title: The library shall validate SPDX documents for NTIA minimum SBOM element compliance. + tags: + - data-model + - validation + - ntia + justification: | + The NTIA Minimum Elements for a Software Bill of Materials defines a baseline set of + data fields that must be present in a conforming SBOM. Supporting this check allows + downstream tooling to assess NTIA compliance without reimplementing the rules. + tests: + - SpdxDocument_Validate_NtiaIssues - - id: SpdxModel-Data-Document-Query - title: The library shall support querying SPDX document elements by ID and by root-package relationship. - tags: - - data-model - justification: | - Consumers of SPDX documents frequently need to locate specific elements or determine - which packages are directly described by the document. Providing these query helpers - reduces boilerplate in downstream tools and ensures consistent traversal semantics. - tests: - - SpdxDocument_GetRootPackages_CorrectPackages - - SpdxDocument_GetAllElements_Correct - - SpdxDocument_GetElement_Correct + - id: SpdxModel-Data-Document-ElementQuery + title: The library shall support querying SPDX document elements by ID. + tags: + - data-model + justification: | + Consumers of SPDX documents frequently need to locate specific elements by their + SPDX identifier. Providing a GetElement helper reduces boilerplate in downstream + tools and ensures consistent traversal semantics. + tests: + - SpdxDocument_GetAllElements_Correct + - SpdxDocument_GetElement_Document_ReturnsDocumentElement + - SpdxDocument_GetElement_File_ReturnsFileElement + - SpdxDocument_GetElement_Package_ReturnsPackageElement + - SpdxDocument_GetElement_Snippet_ReturnsSnippetElement + + - id: SpdxModel-Data-Document-RootPackageQuery + title: The library shall support querying SPDX documents for root packages via relationship traversal. + tags: + - data-model + justification: | + Consumers of SPDX documents frequently need to determine which packages are directly + described by the document. Providing a GetRootPackages helper reduces boilerplate in + downstream tools and ensures consistent traversal semantics. + tests: + - SpdxDocument_GetRootPackages_CorrectPackages diff --git a/docs/reqstream/spdx-model/spdx-element.yaml b/docs/reqstream/spdx-model/spdx-element.yaml index 31c8211..7cbfc1f 100644 --- a/docs/reqstream/spdx-model/spdx-element.yaml +++ b/docs/reqstream/spdx-model/spdx-element.yaml @@ -1,20 +1,19 @@ --- -# SpdxElement Unit Requirements -# -# This file defines requirements for the SpdxElement unit. +# SpdxElement unit requirements sections: - - title: SpdxElement Unit Requirements - requirements: - - id: SpdxModel-Data-Element - title: Every SPDX element shall carry a unique identifier in the form SPDXRef-. - tags: - - data-model - justification: | - Every SPDX element (document, package, file, snippet) must carry a unique SPDX - identifier in the form SPDXRef-. Consistent identity handling across the - entire object model enables element lookup and traversal. - tests: - - SpdxDocument_Validate_InvalidId - - SpdxDocument_GetAllElements_Correct - - SpdxDocument_GetElement_Correct + - title: SpdxModel Requirements + sections: + - title: SpdxElement Requirements + requirements: + - id: SpdxModel-Data-Element + title: Every SPDX element shall carry a unique identifier in the form SPDXRef-. + tags: + - data-model + justification: | + Every SPDX element (document, package, file, snippet) must carry a unique SPDX + identifier in the form SPDXRef-. Consistent identity handling across the + entire object model enables element lookup and traversal. + tests: + - SpdxElement_Id_ValidFormat_PassesValidation + - SpdxElement_Id_InvalidFormat_ReportsValidationIssue diff --git a/docs/reqstream/spdx-model/spdx-external-document-reference.yaml b/docs/reqstream/spdx-model/spdx-external-document-reference.yaml index cf03d62..e5e7cbc 100644 --- a/docs/reqstream/spdx-model/spdx-external-document-reference.yaml +++ b/docs/reqstream/spdx-model/spdx-external-document-reference.yaml @@ -1,22 +1,23 @@ --- -# SpdxExternalDocumentReference Unit Requirements -# -# This file defines requirements for the SpdxExternalDocumentReference unit. +# SpdxExternalDocumentReference unit requirements sections: - - title: SpdxExternalDocumentReference Unit Requirements - requirements: - - id: SpdxModel-Data-ExternalDocumentReferences - title: The library shall support SPDX external document references. - tags: - - data-model - justification: | - External document references allow SPDX documents to reference other SPDX documents, - enabling modular SBOM construction and linking between related software inventories. This - is essential for managing complex multi-component software systems. - tests: - - SpdxExternalDocumentReference_SameComparer_ComparesCorrectly - - SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstance - - SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxExternalDocumentReference_Validate_MissingId - - SpdxExternalDocumentReference_Validate_MissingDocument + - title: SpdxModel Requirements + sections: + - title: SpdxExternalDocumentReference Requirements + requirements: + - id: SpdxModel-Data-ExternalDocumentReferences + title: The library shall support SPDX external document references. + tags: + - data-model + justification: | + External document references allow SPDX documents to reference other SPDX documents, + enabling modular SBOM construction and linking between related software inventories. This + is essential for managing complex multi-component software systems. + tests: + - SpdxExternalDocumentReference_SameComparer_ComparesCorrectly + - SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstance + - SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxExternalDocumentReference_Validate_MissingId + - SpdxExternalDocumentReference_Validate_MissingDocument + - SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue diff --git a/docs/reqstream/spdx-model/spdx-external-reference.yaml b/docs/reqstream/spdx-model/spdx-external-reference.yaml index 21cd1ed..014f847 100644 --- a/docs/reqstream/spdx-model/spdx-external-reference.yaml +++ b/docs/reqstream/spdx-model/spdx-external-reference.yaml @@ -1,28 +1,28 @@ --- -# SpdxExternalReference Unit Requirements -# -# This file defines requirements for the SpdxExternalReference unit. +# SpdxExternalReference unit requirements sections: - - title: SpdxExternalReference Unit Requirements - requirements: - - id: SpdxModel-Data-ExternalReferences - title: The library shall support SPDX external references. - tags: - - data-model - justification: | - External references enable linking SPDX elements to external resources like package - registries, vulnerability databases, and documentation. This enriches SBOMs with contextual - information from authoritative sources. - tests: - - SpdxExternalReference_SameComparer_ComparesCorrectly - - SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance - - SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxExternalReference_Validate_InvalidCategory - - SpdxExternalReference_Validate_InvalidType - - SpdxExternalReference_Validate_InvalidLocator - - SpdxReferenceCategoryExtensions_FromText_Valid - - SpdxReferenceCategoryExtensions_FromText_Invalid - - SpdxReferenceCategoryExtensions_ToText_Valid - - SpdxReferenceCategoryExtensions_ToText_InvalidCategory - - SpdxReferenceCategoryExtensions_ToText_MissingCategory + - title: SpdxModel Requirements + sections: + - title: SpdxExternalReference Requirements + requirements: + - id: SpdxModel-Data-ExternalReferences + title: The library shall support SPDX external references. + tags: + - data-model + justification: | + External references enable linking SPDX elements to external resources like package + registries, vulnerability databases, and documentation. This enriches SBOMs with + contextual information from authoritative sources. + tests: + - SpdxExternalReference_SameComparer_ComparesCorrectly + - SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance + - SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxExternalReference_Validate_InvalidCategory + - SpdxExternalReference_Validate_InvalidType + - SpdxExternalReference_Validate_InvalidLocator + - SpdxReferenceCategoryExtensions_FromText_Valid + - SpdxReferenceCategoryExtensions_FromText_Invalid + - SpdxReferenceCategoryExtensions_ToText_Valid + - SpdxReferenceCategoryExtensions_ToText_InvalidCategory + - SpdxReferenceCategoryExtensions_ToText_MissingCategory diff --git a/docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml b/docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml index 7c2343e..56d484a 100644 --- a/docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml +++ b/docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml @@ -1,22 +1,23 @@ --- -# SpdxExtractedLicensingInfo Unit Requirements -# -# This file defines requirements for the SpdxExtractedLicensingInfo unit. +# SpdxExtractedLicensingInfo unit requirements sections: - - title: SpdxExtractedLicensingInfo Unit Requirements - requirements: - - id: SpdxModel-Data-ExtractedLicensingInformation - title: The library shall support SPDX extracted licensing information. - tags: - - data-model - justification: | - Extracted licensing information supports documenting non-standard licenses found in - software packages. This is critical for compliance when software contains licenses not - in the SPDX license list. - tests: - - SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly - - SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance - - SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxExtractedLicensingInfo_Validate_InvalidLicenseId - - SpdxExtractedLicensingInfo_Validate_InvalidExtractedText + - title: SpdxModel Requirements + sections: + - title: SpdxExtractedLicensingInfo Requirements + requirements: + - id: SpdxModel-Data-ExtractedLicensingInformation + title: The library shall support SPDX extracted licensing information. + tags: + - data-model + justification: | + Extracted licensing information supports documenting non-standard licenses found in + software packages. This is critical for compliance when software contains licenses not + in the SPDX license list. + tests: + - SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly + - SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance + - SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues + - SpdxExtractedLicensingInfo_Validate_InvalidLicenseId + - SpdxExtractedLicensingInfo_Validate_InvalidExtractedText diff --git a/docs/reqstream/spdx-model/spdx-file.yaml b/docs/reqstream/spdx-model/spdx-file.yaml index f41e575..64fbdac 100644 --- a/docs/reqstream/spdx-model/spdx-file.yaml +++ b/docs/reqstream/spdx-model/spdx-file.yaml @@ -1,26 +1,28 @@ --- -# SpdxFile Unit Requirements -# -# This file defines requirements for the SpdxFile unit. +# SpdxFile unit requirements sections: - - title: SpdxFile Unit Requirements - requirements: - - id: SpdxModel-Data-Files - title: The library shall support SPDX files. - tags: - - data-model - justification: | - Files are essential components in SPDX documents for detailed SBOM creation. Supporting - file elements enables fine-grained tracking of individual source files, binaries, and - their associated licensing and copyright information. - tests: - - SpdxFile_SameComparer_ComparesCorrectly - - SpdxFile_DeepCopy_CreatesEqualButDistinctInstance - - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxFile_Validate_ReportsInvalidFileId - - SpdxFile_Validate_Success - - SpdxFileTypeExtensions_FromText_Valid - - SpdxFileTypeExtensions_FromText_Invalid - - SpdxFileTypeExtensions_ToText_Valid - - SpdxFileTypeExtensions_ToText_Invalid + - title: SpdxModel Requirements + sections: + - title: SpdxFile Requirements + requirements: + - id: SpdxModel-Data-Files + title: The library shall support SPDX files. + tags: + - data-model + justification: | + Files are essential components in SPDX documents for detailed SBOM creation. Supporting + file elements enables fine-grained tracking of individual source files, binaries, and + their associated licensing and copyright information. + tests: + - SpdxFile_SameComparer_ComparesCorrectly + - SpdxFile_DeepCopy_CreatesEqualButDistinctInstance + - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxFile_Validate_ReportsInvalidFileId + - SpdxFile_Validate_ReportsInvalidFileName + - SpdxFile_Validate_ReportsWhenSha1ChecksumMissing + - SpdxFile_Validate_Success + - SpdxFileTypeExtensions_FromText_Valid + - SpdxFileTypeExtensions_FromText_Invalid + - SpdxFileTypeExtensions_ToText_Valid + - SpdxFileTypeExtensions_ToText_Invalid diff --git a/docs/reqstream/spdx-model/spdx-helpers.yaml b/docs/reqstream/spdx-model/spdx-helpers.yaml index 9cae8e5..2e3d778 100644 --- a/docs/reqstream/spdx-model/spdx-helpers.yaml +++ b/docs/reqstream/spdx-model/spdx-helpers.yaml @@ -1,35 +1,43 @@ --- -# SpdxHelpers Unit Requirements -# -# This file defines requirements for the SpdxHelpers unit. +# SpdxHelpers unit requirements sections: - - title: SpdxHelpers Unit Requirements - requirements: - - id: SpdxModel-Data-Helpers-DateTime - title: The library shall validate SPDX date-time strings against the ISO 8601 UTC format. - tags: - - data-model - - validation - justification: | - SPDX requires all date-time fields to conform to ISO 8601 UTC format - (YYYY-MM-DDThh:mm:ssZ). Consistent validation of this format across all - date-time fields ensures compliant SPDX documents are produced. - tests: - - SpdxCreationInformation_Validate_InvalidCreatedDate + - title: SpdxModel Requirements + sections: + - title: SpdxHelpers Requirements + requirements: + - id: SpdxModel-Data-Helpers-DateTime + title: The library shall validate SPDX date-time strings against the ISO 8601 UTC format. + tags: + - data-model + - validation + justification: | + SPDX requires all date-time fields to conform to ISO 8601 UTC format + (YYYY-MM-DDThh:mm:ssZ). Consistent validation of this format across all + date-time fields ensures compliant SPDX documents are produced. + Absent or empty date-time values (null, empty string) shall be treated as + not-set and shall be considered valid. + tests: + - SpdxCreationInformation_Validate_InvalidCreatedDate + - SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue + - SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue + - SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue + - SpdxHelpers_IsValidSpdxDateTime_InvalidFormat_ReturnsFalse - - id: SpdxModel-Data-Helpers-EnhanceString - title: >- - When merging optional SPDX fields, the library shall prefer a concrete - value over NOASSERTION or absent values. - tags: - - data-model - justification: | - When combining partial SPDX data from multiple sources, individual fields may be - null, empty, NOASSERTION, or a concrete value. Preferring a concrete value ensures - meaningful data is preserved regardless of the order in which sources are merged. - tests: - - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly - - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly + - id: SpdxModel-Data-Helpers-EnhanceString + title: >- + When merging optional SPDX fields, the library shall prefer a concrete + value over NOASSERTION or absent values. + tags: + - data-model + justification: | + When combining partial SPDX data from multiple sources, individual fields may be + null, empty, NOASSERTION, or a concrete value. Preferring a concrete value ensures + meaningful data is preserved regardless of the order in which sources are merged. + tests: + - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly + - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue + - SpdxHelpers_EnhanceString_NullInputs_ReturnsNull diff --git a/docs/reqstream/spdx-model/spdx-license-element.yaml b/docs/reqstream/spdx-model/spdx-license-element.yaml index b6937be..b7cc76b 100644 --- a/docs/reqstream/spdx-model/spdx-license-element.yaml +++ b/docs/reqstream/spdx-model/spdx-license-element.yaml @@ -1,38 +1,58 @@ --- -# SpdxLicenseElement Unit Requirements -# -# This file defines requirements for the SpdxLicenseElement unit. +# SpdxLicenseElement unit requirements sections: - - title: SpdxLicenseElement Unit Requirements - requirements: - - id: SpdxModel-Data-LicenseElement - title: >- - Packages, files, and snippets shall each carry concluded license, - copyright text, license comments, attribution notices, and annotations. - tags: - - data-model - justification: | - Packages, files, and snippets all carry the same set of license and copyright fields - as required by the SPDX specification. Consistent semantics across all three element - types enables uniform processing of license and copyright information. - tests: - - SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance - - SpdxFile_DeepCopy_CreatesEqualButDistinctInstance - - SpdxSnippet_DeepCopy_CreatesEqualButDistinctInstance - - - id: SpdxModel-Data-LicenseElement-Enhance - title: >- - The library shall merge license and copyright fields from a secondary - source when the primary fields are absent. - tags: - - data-model - justification: | - When enriching an incomplete SPDX element with data from a supplementary source, - concluded license, copyright text, attribution texts, and annotations may be absent - from the primary record. These fields shall be populated from the secondary source - only when the primary record lacks a concrete value. - tests: - - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly - - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly + - title: SpdxModel Requirements + sections: + - title: SpdxLicenseElement Requirements + requirements: + - id: SpdxModel-Data-LicenseElement + title: >- + Packages, files, and snippets shall each carry concluded license, + copyright text, license comments, attribution notices, and annotations. + tags: + - data-model + justification: | + Packages, files, and snippets all carry the same set of license and copyright fields + as required by the SPDX specification. Consistent semantics across all three element + types enables uniform processing of license and copyright information. + This is a parent summary requirement; its behavioral responsibilities are decomposed + into the child requirements SpdxModel-Data-LicenseElement-Data and + SpdxModel-Data-LicenseElement-Enhance below. + tests: + - SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance + - SpdxFile_DeepCopy_CreatesEqualButDistinctInstance + - SpdxSnippet_DeepCopy_CreatesEqualButDistinctInstance + children: + - SpdxModel-Data-LicenseElement-Data + - SpdxModel-Data-LicenseElement-Enhance + - id: SpdxModel-Data-LicenseElement-Data + title: >- + The library shall store concluded license, copyright text, license comments, + attribution notices, and annotations on each element type. + tags: + - data-model + justification: | + Each SPDX element type (package, file, snippet) must independently carry the full + set of license and copyright fields so that per-element license information can be + reported and validated without reference to other elements. + tests: + - SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance + - SpdxFile_DeepCopy_CreatesEqualButDistinctInstance + - SpdxSnippet_DeepCopy_CreatesEqualButDistinctInstance + - id: SpdxModel-Data-LicenseElement-Enhance + title: >- + The library shall merge concluded license, copyright text, license comments, + attribution texts, and annotations from a secondary source when the primary + fields are null, empty, or set to NOASSERTION. + tags: + - data-model + justification: | + When enriching an incomplete SPDX element with data from a supplementary source, + concluded license, copyright text, license comments, attribution texts, and + annotations may be absent from the primary record. These fields shall be populated + from the secondary source only when the primary record lacks a concrete value. + tests: + - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly + - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly diff --git a/docs/reqstream/spdx-model/spdx-model.yaml b/docs/reqstream/spdx-model/spdx-model.yaml index c12b234..33ced56 100644 --- a/docs/reqstream/spdx-model/spdx-model.yaml +++ b/docs/reqstream/spdx-model/spdx-model.yaml @@ -1,12 +1,44 @@ --- -# SpdxModel System Requirements -# -# This file defines the system-level and cross-cutting requirements -# for the SpdxModel library. +# SpdxModel system-level requirements sections: - - title: SpdxModel Library Requirements + - title: SpdxModel Requirements sections: + - title: SPDX Elements + requirements: + - id: SpdxModel-Data-Elements + title: The library shall provide an in-memory model for all SPDX 2.x element types. + tags: + - data-model + justification: | + Supporting all SPDX 2.x element types ensures complete coverage of the specification + and enables applications to work with any SPDX document without information loss. + tests: + - SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully + - SpdxModel_ReadSpdxJson_Spdx23Example_ParsesSuccessfully + children: + - SpdxModel-Data-Document-Ntia + - SpdxModel-Data-ExternalDocumentReferences + - SpdxModel-Data-ExternalReferences + - SpdxModel-Data-ExtractedLicensingInformation + - SpdxModel-Data-Snippet-DataModel + - id: SpdxModel-Data-Helpers + title: The library shall provide helper utilities for SPDX data processing. + tags: + - data-model + justification: | + Helper utilities provide shared functionality used across the data model, including + date-time validation and string enhancement operations. + tests: + - SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue + - SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue + children: + - SpdxModel-Data-Helpers-DateTime + - SpdxModel-Data-Helpers-EnhanceString + - SpdxModel-Data-LicenseElement + - SpdxModel-Data-LicenseElement-Enhance + - SpdxModel-Data-PackageVerificationCode-Validate + - title: Data Model requirements: - id: SpdxModel-Data-RootPackages @@ -18,7 +50,7 @@ sections: for understanding the primary software packages in a document and navigating the dependency graph from its entry points. children: - - SpdxModel-Data-Document-Query + - SpdxModel-Data-Document-RootPackageQuery tests: - SpdxModel_ReadSpdxJson_Spdx23Example_RootPackagesIdentified @@ -32,7 +64,7 @@ sections: filtering, or transforming documents. children: - SpdxModel-Data-Document - - SpdxModel-Data-Packages + - SpdxModel-Data-Package-DeepCopy - SpdxModel-Data-Files tests: - SpdxModel_ReadSpdxJson_Spdx23Example_DeepCopyProducesEquivalentDocument @@ -47,9 +79,9 @@ sections: children: - SpdxModel-Data-Element - SpdxModel-Data-Document - - SpdxModel-Data-Packages + - SpdxModel-Data-Package-Validate tests: - - SpdxModel_ReadSpdxJson_Spdx23Example_DeepCopyProducesEquivalentDocument + - SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNullable - id: SpdxModel-Data-ComparisonUtilities title: The library shall provide comparison utilities for SPDX elements. @@ -61,9 +93,10 @@ sections: like document merging or deduplication. children: - SpdxModel-Data-Document - - SpdxModel-Data-Packages + - SpdxModel-Data-Package-Compare - SpdxModel-Data-Files - - SpdxModel-Data-Relationships + - SpdxModel-Data-Relationship-Compare + - SpdxModel-Data-Snippet-DataModel tests: - SpdxModel_ReadSpdxJson_Spdx23Example_DeepCopyProducesEquivalentDocument @@ -98,7 +131,7 @@ sections: other tools. Supporting both SPDX 2.2 and 2.3 formats ensures compatibility with the broad ecosystem of SPDX tooling and enables consumers to work with existing SBOMs. children: - - SpdxModel-IO-Serialization + - SpdxModel-IO-Deserialization tests: - SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully - SpdxModel_ReadSpdxJson_Spdx23Example_ParsesSuccessfully @@ -116,3 +149,17 @@ sections: - SpdxModel-IO-Serialization tests: - SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds + + - title: Transform + requirements: + - id: SpdxModel-Transform + title: The library shall provide utilities for transforming SPDX documents. + tags: + - transform + justification: | + Transform utilities enable programmatic modification of SPDX documents, supporting + use cases where documents need to be modified or enriched after initial creation. + children: + - SpdxModel-Transform-Utilities + tests: + - SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists diff --git a/docs/reqstream/spdx-model/spdx-package-verification-code.yaml b/docs/reqstream/spdx-model/spdx-package-verification-code.yaml index a755508..f4c25b9 100644 --- a/docs/reqstream/spdx-model/spdx-package-verification-code.yaml +++ b/docs/reqstream/spdx-model/spdx-package-verification-code.yaml @@ -1,21 +1,46 @@ --- -# SpdxPackageVerificationCode Unit Requirements -# -# This file defines requirements for the SpdxPackageVerificationCode unit. +# SpdxPackageVerificationCode unit requirements sections: - - title: SpdxPackageVerificationCode Unit Requirements - requirements: - - id: SpdxModel-Data-PackageVerificationCodes - title: The library shall support SPDX package verification codes. - tags: - - data-model - justification: | - Package verification codes provide a way to verify package contents integrity. This - cryptographic verification mechanism is important for ensuring that package contents - have not been tampered with or corrupted. - tests: - - SpdxPackageVerificationCode_SameComparer_ComparesCorrectly - - SpdxPackageVerificationCode_DeepCopy_CreatesEqualButDistinctInstance - - SpdxPackageVerificationCode_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxPackageVerificationCode_Validate_InvalidValue + - title: SpdxModel Requirements + sections: + - title: SpdxPackageVerificationCode Requirements + requirements: + - id: SpdxModel-Data-PackageVerificationCode-Compare + title: The library shall support equality comparison of package verification codes by value. + tags: + - data-model + justification: | + Verification codes must be compared by value to support deduplication and merging + operations. Equality is determined by the SHA1 digest value alone. + tests: + - SpdxPackageVerificationCode_SameComparer_SameValueDifferentExcludedFiles_ReturnsEqual + - id: SpdxModel-Data-PackageVerificationCode-DeepCopy + title: The library shall support creating independent deep copies of package verification codes. + tags: + - data-model + justification: | + Deep copies are required so that merging operations do not mutate the original + verification code instance. + tests: + - SpdxPackageVerificationCode_DeepCopy_FullyPopulatedCode_CreatesEqualButDistinctCopy + - id: SpdxModel-Data-PackageVerificationCode-Enhance + title: The library shall merge missing fields into a package verification code from another instance. + tags: + - data-model + justification: | + Enhance enables combining partial verification code information from multiple SPDX + documents into a single complete record. + tests: + - SpdxPackageVerificationCode_Enhance_MissingFields_MergesCorrectly + - id: SpdxModel-Data-PackageVerificationCode-Validate + title: The library shall validate that a package verification code value is a 40-character SHA1 hex digest. + tags: + - data-model + justification: | + The SPDX specification requires the verification code to be a valid SHA1 hex digest. + Validation ensures the value is exactly 40 lowercase or uppercase hexadecimal characters. + tests: + - SpdxPackageVerificationCode_Validate_ValidValue_ReportsNoIssues + - SpdxPackageVerificationCode_Validate_InvalidValue_ReportsIssue + - SpdxPackageVerificationCode_Validate_NonHexValue_ReportsIssue diff --git a/docs/reqstream/spdx-model/spdx-package.yaml b/docs/reqstream/spdx-model/spdx-package.yaml index 54bc3df..f295c71 100644 --- a/docs/reqstream/spdx-model/spdx-package.yaml +++ b/docs/reqstream/spdx-model/spdx-package.yaml @@ -1,30 +1,64 @@ --- -# SpdxPackage Unit Requirements -# -# This file defines requirements for the SpdxPackage unit. +# SpdxPackage unit requirements sections: - - title: SpdxPackage Unit Requirements - requirements: - - id: SpdxModel-Data-Packages - title: The library shall support SPDX packages. - tags: - - data-model - justification: | - Packages are core elements in SPDX documents representing software packages in an SBOM. - Supporting package elements is fundamental to the library's purpose of managing software - bill of materials and dependency information. - tests: - - SpdxPackage_SameComparer_ComparesCorrectly - - SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance - - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly - - SpdxPackage_Validate_Success - - SpdxPackage_Validate_MissingPackageName - - SpdxPackage_Validate_InvalidPackageId - - SpdxPackage_Validate_MissingDownload - - SpdxPackage_Validate_InvalidSupplier - - SpdxPackage_Validate_InvalidOriginator - - SpdxPackage_Validate_InvalidReleaseDate - - SpdxPackage_Validate_InvalidBuiltDate - - SpdxPackage_Validate_InvalidValidUntilDate - - SpdxPackage_Validate_InvalidAnnotation + - title: SpdxModel Requirements + sections: + - title: SpdxPackage Requirements + requirements: + - id: SpdxModel-Data-Package-Compare + title: The library shall support equality comparison of SPDX packages by name and version. + tags: + - data-model + justification: | + Packages must be compared by name and version to support array merging and + deduplication across SPDX documents. + tests: + - SpdxPackage_SameComparer_ComparesCorrectly + - id: SpdxModel-Data-Package-DeepCopy + title: The library shall support creating independent deep copies of SPDX packages. + tags: + - data-model + justification: | + Deep copies are required so that merging operations do not mutate the original + package instances. + tests: + - SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance + - id: SpdxModel-Data-Package-Enhance + title: The library shall support merging missing fields into an SPDX package from another instance. + tags: + - data-model + justification: | + Enhance enables combining partial package information from multiple SPDX documents + into a single complete record. + tests: + - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly + - id: SpdxModel-Data-Package-Validate + title: The library shall validate SPDX package fields and report all issues found. + tags: + - data-model + justification: | + Validation ensures that package data conforms to the SPDX specification before + serialization or use by downstream consumers. + tests: + - SpdxPackage_Validate_Success + - SpdxPackage_Validate_MissingPackageName + - SpdxPackage_Validate_InvalidPackageId + - SpdxPackage_Validate_MissingDownload + - SpdxPackage_Validate_InvalidSupplier + - SpdxPackage_Validate_InvalidOriginator + - SpdxPackage_Validate_InvalidReleaseDate + - SpdxPackage_Validate_InvalidBuiltDate + - SpdxPackage_Validate_InvalidValidUntilDate + - SpdxPackage_Validate_InvalidAnnotation + - SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue + - id: SpdxModel-Data-Package-ValidateNtia + title: The library shall validate NTIA minimum element requirements for SPDX packages when requested. + tags: + - data-model + justification: | + NTIA minimum elements (supplier and version) are an opt-in compliance check + that consumers may request independently of general validation. + tests: + - SpdxPackage_ValidateNtia_MissingSupplier_ReportsIssue + - SpdxPackage_ValidateNtia_MissingVersion_ReportsIssue diff --git a/docs/reqstream/spdx-model/spdx-relationship.yaml b/docs/reqstream/spdx-model/spdx-relationship.yaml index 51baa84..c1b0446 100644 --- a/docs/reqstream/spdx-model/spdx-relationship.yaml +++ b/docs/reqstream/spdx-model/spdx-relationship.yaml @@ -1,28 +1,59 @@ --- -# SpdxRelationship Unit Requirements -# -# This file defines requirements for the SpdxRelationship unit. +# SpdxRelationship unit requirements sections: - - title: SpdxRelationship Unit Requirements - requirements: - - id: SpdxModel-Data-Relationships - title: The library shall support SPDX relationships. - tags: - - data-model - justification: | - Relationships define connections between SPDX elements and are critical for expressing - dependency graphs, containment hierarchies, and other associations in SBOMs. This is - fundamental to representing complex software structures. - tests: - - SpdxRelationship_SameComparer_ComparesCorrectly - - SpdxRelationship_SameElementsComparer_ComparesCorrectly - - SpdxRelationship_DeepCopy_CreatesEqualButDistinctInstance - - SpdxRelationship_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxRelationship_Validate_MissingId - - SpdxRelationship_Validate_MissingRelatedId - - SpdxRelationship_Validate_MissingRelationship - - SpdxRelationshipTypeExtensions_FromText_Valid - - SpdxRelationshipTypeExtensions_FromText_Invalid - - SpdxRelationshipTypeExtensions_ToText_Valid - - SpdxRelationshipTypeExtensions_ToText_Invalid + - title: SpdxModel Requirements + sections: + - title: SpdxRelationship Requirements + requirements: + - id: SpdxModel-Data-Relationship-Compare + title: The library shall support equality comparison of SPDX relationships. + tags: + - data-model + justification: | + Relationships must be compared to support array merging and deduplication across + SPDX documents. + tests: + - SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqual + - SpdxRelationship_SameElementsComparer_SameElementsDifferentType_ReturnsEqual + - id: SpdxModel-Data-Relationship-DeepCopy + title: The library shall support creating independent deep copies of SPDX relationships. + tags: + - data-model + justification: | + Deep copies are required so that merging operations do not mutate the original + relationship instances. + tests: + - SpdxRelationship_DeepCopy_FullyPopulatedRelationship_CreatesEqualButDistinctCopy + - id: SpdxModel-Data-Relationship-Enhance + title: The library shall support merging missing fields into an SPDX relationship from another instance. + tags: + - data-model + justification: | + Enhance enables combining partial relationship information from multiple SPDX + documents into a single complete record. + tests: + - SpdxRelationship_Enhance_MatchingAndNewRelationships_MergesCorrectly + - id: SpdxModel-Data-Relationship-Validate + title: The library shall validate SPDX relationship fields and report all issues found. + tags: + - data-model + justification: | + Relationships define connections between SPDX elements and are critical for expressing + dependency graphs, containment hierarchies, and other associations in SBOMs. + tests: + - SpdxRelationship_Validate_MissingRelationshipId_ReportsIssue + - SpdxRelationship_Validate_MissingRelatedElementId_ReportsIssue + - SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue + - id: SpdxModel-Data-RelationshipType-Conversion + title: The library shall support round-trip text conversion for all 44 SPDX relationship type tokens. + tags: + - data-model + justification: | + Relationship types must be serialized to and deserialized from their SPDX string + representations to support reading and writing SPDX JSON documents. + tests: + - SpdxRelationshipTypeExtensions_FromText_KnownText_ReturnsMappedEnum + - SpdxRelationshipTypeExtensions_FromText_UnknownText_ThrowsInvalidOperationException + - SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText + - SpdxRelationshipTypeExtensions_ToText_UnknownEnum_ThrowsInvalidOperationException diff --git a/docs/reqstream/spdx-model/spdx-snippet.yaml b/docs/reqstream/spdx-model/spdx-snippet.yaml index d284aa5..52b0f18 100644 --- a/docs/reqstream/spdx-model/spdx-snippet.yaml +++ b/docs/reqstream/spdx-model/spdx-snippet.yaml @@ -1,23 +1,52 @@ --- -# SpdxSnippet Unit Requirements -# -# This file defines requirements for the SpdxSnippet unit. +# SpdxSnippet unit requirements sections: - - title: SpdxSnippet Unit Requirements - requirements: - - id: SpdxModel-Data-Snippets - title: The library shall support SPDX snippets. - tags: - - data-model - justification: | - Snippets represent portions of files and are important for documenting code reuse at a - granular level. This supports compliance scenarios where specific code segments have - different licensing or provenance than their containing files. - tests: - - SpdxSnippet_SameComparer_ComparesCorrectly - - SpdxSnippet_DeepCopy_CreatesEqualButDistinctInstance - - SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxSnippet_Validate_ReportsInvalidSnippetId - - SpdxSnippet_Validate_Success - - SpdxSnippet_Validate_InvalidAnnotation + - title: SpdxModel Requirements + sections: + - title: SpdxSnippet Requirements + requirements: + - id: SpdxModel-Data-Snippet-DataModel + title: The library shall represent SPDX snippet data including byte ranges and licensing information. + tags: + - data-model + justification: | + Snippets represent portions of files and are important for documenting code reuse at a + granular level, supporting compliance scenarios where specific code segments have + different licensing or provenance than their containing files. + tests: + - SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual + - id: SpdxModel-Data-Snippet-DeepCopy + title: The library shall support creating independent deep copies of SPDX snippets. + tags: + - data-model + justification: | + Deep copies are required so that merging operations do not mutate the original + snippet instances. + tests: + - SpdxSnippet_DeepCopy_FullyPopulatedSnippet_CreatesEqualButDistinctCopy + - id: SpdxModel-Data-Snippet-Enhance + title: The library shall support merging missing fields into an SPDX snippet from another instance. + tags: + - data-model + justification: | + Enhance enables combining partial snippet information from multiple SPDX documents + into a single complete record. + tests: + - SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly + - id: SpdxModel-Data-Snippet-Validate + title: The library shall validate SPDX snippet fields and report all issues found. + tags: + - data-model + justification: | + Validation ensures that snippet data conforms to the SPDX specification including + valid byte ranges, required fields, and correct ID format. + tests: + - SpdxSnippet_Validate_InvalidSnippetId_ReportsIssue + - SpdxSnippet_Validate_AllRequiredFieldsPresent_ReturnsNoIssues + - SpdxSnippet_Validate_InvalidAnnotation_ReportsIssue + - SpdxSnippet_Validate_EmptySnippetFromFile_ReportsIssue + - SpdxSnippet_Validate_InvalidByteStart_ReportsIssue + - SpdxSnippet_Validate_InvalidByteEnd_ReportsIssue + - SpdxSnippet_Validate_EmptyConcludedLicense_ReportsIssue + - SpdxSnippet_Validate_EmptyCopyrightText_ReportsIssue diff --git a/docs/reqstream/spdx-model/transform/spdx-relationships.yaml b/docs/reqstream/spdx-model/transform/spdx-relationships.yaml index c9419df..6ec0813 100644 --- a/docs/reqstream/spdx-model/transform/spdx-relationships.yaml +++ b/docs/reqstream/spdx-model/transform/spdx-relationships.yaml @@ -1,25 +1,56 @@ --- -# SpdxModel SpdxRelationships Unit Requirements -# -# This file defines the requirements for the SpdxRelationships unit -# in the Transform subsystem of the SpdxModel library. +# SpdxRelationships unit requirements sections: - - title: SpdxRelationships Requirements - requirements: - - id: SpdxModel-Data-RelationshipUtilities - title: The library shall provide utilities for manipulating SPDX relationships. - tags: - - data-model - justification: | - Relationship manipulation utilities simplify common operations on SPDX documents such as - adding and managing relationships between elements. This improves developer productivity - and reduces errors when constructing complex SBOMs. - tests: - - SpdxRelationships_AddSingle_Success - - SpdxRelationships_AddSingle_MissingId - - SpdxRelationships_AddSingle_MissingRelatedElement - - SpdxRelationships_AddSingle_Duplicate - - SpdxRelationships_AddMultiple_Success - - SpdxRelationships_AddMultiple_Duplicate - - SpdxRelationships_AddMultiple_Replace + - title: SpdxModel Requirements + sections: + - title: Transform Requirements + sections: + - title: SpdxRelationships Requirements + requirements: + - id: SpdxModel-Transform-RelationshipUtilities-AddSingle + title: The library shall provide a utility to add a single relationship to an SPDX document. + tags: + - data-model + justification: | + Adding a single relationship is the fundamental mutation operation. Atomic requirements + allow each behaviour to be verified and traced independently. + tests: + - SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship + - SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRelationship + - SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship + - SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship + + - id: SpdxModel-Transform-RelationshipUtilities-AddMultiple + title: The library shall provide a utility to batch-add relationships to an SPDX document with optional replacement. + tags: + - data-model + justification: | + Batch operations improve developer productivity when constructing or updating relationship + graphs. The replace flag enables idempotent updates to existing relationships. + tests: + - SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship + - SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRelationships + - SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRelationships + + - id: SpdxModel-Transform-RelationshipUtilities-Validate + title: The library shall validate relationship element IDs before adding them to an SPDX document. + tags: + - data-model + justification: | + Validation prevents corrupt or incomplete relationships from being written into the document + and gives callers actionable error messages when IDs cannot be resolved. + tests: + - SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException + - SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentException + - SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified + + - id: SpdxModel-Transform-RelationshipUtilities-Atomicity + title: The library shall guarantee that a failed batch-add leaves the document in its original state. + tags: + - data-model + justification: | + Atomicity prevents partial writes when one of several incoming relationships is invalid, + ensuring the document is never left in a half-updated state. + tests: + - SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified diff --git a/docs/reqstream/spdx-model/transform/transform.yaml b/docs/reqstream/spdx-model/transform/transform.yaml index bef1680..54d9e1b 100644 --- a/docs/reqstream/spdx-model/transform/transform.yaml +++ b/docs/reqstream/spdx-model/transform/transform.yaml @@ -1,22 +1,20 @@ --- -# SpdxModel Transform Subsystem Requirements -# -# This is the subsystem-level requirements file for the Transform subsystem. -# Unit requirements are in the sibling unit file: -# spdx-relationships.yaml +# Transform subsystem requirements sections: - - title: Transform Subsystem Requirements - requirements: - - id: SpdxModel-Transform-Utilities - title: The library shall provide utilities for transforming SPDX document relationships. - tags: - - transform - justification: | - Transform utilities enable programmatic modification of SPDX documents, starting with - relationship management. This supports use cases where documents need to be modified or - enriched after initial creation. - children: - - SpdxModel-Data-RelationshipUtilities - tests: - - SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists + - title: SpdxModel Requirements + sections: + - title: Transform Requirements + requirements: + - id: SpdxModel-Transform-Utilities + title: The library shall provide utilities for transforming SPDX document relationships. + tags: + - transform + justification: | + Transform utilities enable programmatic modification of SPDX documents, starting with + relationship management. This supports use cases where documents need to be modified or + enriched after initial creation. + children: + - SpdxModel-Transform-RelationshipUtilities + tests: + - SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists diff --git a/docs/requirements_doc/definition.yaml b/docs/requirements_doc/definition.yaml index 0f4ccd2..bc9c807 100644 --- a/docs/requirements_doc/definition.yaml +++ b/docs/requirements_doc/definition.yaml @@ -1,12 +1,10 @@ --- -resource-path: - - docs/requirements_doc - - docs/template +resource-path: [docs/requirements_doc, docs/template] input-files: - docs/requirements_doc/title.txt - docs/requirements_doc/introduction.md - - docs/requirements_doc/requirements.md - - docs/requirements_doc/justifications.md + - docs/requirements_doc/generated/requirements.md # Generated by ReqStream (requirements listing) + - docs/requirements_doc/generated/justifications.md # Generated by ReqStream (requirement justifications) template: template.html table-of-contents: true number-sections: true diff --git a/docs/requirements_doc/introduction.md b/docs/requirements_doc/introduction.md index b15eab3..30d7e30 100644 --- a/docs/requirements_doc/introduction.md +++ b/docs/requirements_doc/introduction.md @@ -1,30 +1,16 @@ # Introduction -This document specifies the requirements for the SpdxModel library, a C# library for working with -SPDX (Software Package Data Exchange) documents. +This document lists all requirements for SpdxModel. ## Purpose -The purpose of this document is to: - -- Define the functional and quality requirements for the SpdxModel library -- Provide traceability between requirements and test cases -- Serve as a baseline for development and validation +To provide a complete, traceable record of all requirements for SpdxModel, +including requirements at the system, subsystem, and unit levels, plus OTS and Shared Package requirements. ## Scope -The SpdxModel library is designed to: - -- Provide a comprehensive in-memory model for SPDX documents -- Support SPDX 2.2 and 2.3 specifications -- Enable reading and writing SPDX documents in JSON format -- Offer utilities for manipulating SPDX relationships -- Target .NET 8, 9, and 10 frameworks - -## Audience +This document covers all requirements defined in `docs/reqstream/` for SpdxModel. -This document is intended for: +## References -- Software developers implementing the library -- Quality assurance engineers validating the requirements -- Project stakeholders reviewing the scope and capabilities +- [SpdxModel releases](https://github.com/demaconsulting/SpdxModel/releases) diff --git a/docs/requirements_doc/title.txt b/docs/requirements_doc/title.txt index f82e162..af678f1 100644 --- a/docs/requirements_doc/title.txt +++ b/docs/requirements_doc/title.txt @@ -1,15 +1,14 @@ --- title: SpdxModel Requirements -subtitle: Requirements Specification +subtitle: SPDX document model for .NET author: DEMA Consulting -description: Requirements specification for the SpdxModel C# library for working with SPDX (Software Package Data Exchange) documents +description: "Requirements Document for SpdxModel" lang: en-US keywords: - - SpdxModel - Requirements + - SpdxModel - C# - .NET - SPDX - SBOM - - Software Bill of Materials --- diff --git a/docs/requirements_report/definition.yaml b/docs/requirements_report/definition.yaml index 918a645..e8b5571 100644 --- a/docs/requirements_report/definition.yaml +++ b/docs/requirements_report/definition.yaml @@ -1,11 +1,9 @@ --- -resource-path: - - docs/requirements_report - - docs/template +resource-path: [docs/requirements_report, docs/template] input-files: - docs/requirements_report/title.txt - docs/requirements_report/introduction.md - - docs/requirements_report/trace_matrix.md + - docs/requirements_report/generated/trace_matrix.md # Generated by ReqStream (requirements traceability matrix) template: template.html table-of-contents: true number-sections: true diff --git a/docs/requirements_report/introduction.md b/docs/requirements_report/introduction.md index d25068d..d5a59fb 100644 --- a/docs/requirements_report/introduction.md +++ b/docs/requirements_report/introduction.md @@ -1,29 +1,17 @@ # Introduction -This document provides a requirements traceability matrix for the SpdxModel library, showing -the relationship between requirements and test cases. +This document provides the requirements Trace Matrix for SpdxModel, +mapping each requirement to its corresponding test evidence. ## Purpose -The purpose of this document is to: - -- Demonstrate that all requirements are covered by test cases -- Provide traceability from requirements to implementation verification -- Support compliance and validation activities +To demonstrate that every requirement is covered by at least one passing test, +providing compliance evidence for SpdxModel. ## Scope -This trace matrix covers: - -- All functional, quality, and documentation requirements -- Test cases that verify each requirement -- Pass/fail status for each requirement verification - -## How to Read This Document +This document covers all requirements in `docs/reqstream/` and their test evidence. -The trace matrix shows: +## References -- **Requirement ID**: Unique identifier for each requirement -- **Requirement Title**: Brief description of the requirement -- **Test Cases**: Tests that verify the requirement -- **Status**: Pass/fail status based on test execution results +- [SpdxModel releases](https://github.com/demaconsulting/SpdxModel/releases) diff --git a/docs/requirements_report/title.txt b/docs/requirements_report/title.txt index 8fd6f31..26fae56 100644 --- a/docs/requirements_report/title.txt +++ b/docs/requirements_report/title.txt @@ -1,16 +1,14 @@ --- -title: SpdxModel Trace Matrix -subtitle: Requirements Trace Matrix +title: SpdxModel Requirements Report +subtitle: SPDX document model for .NET author: DEMA Consulting -description: Requirements Traceability Matrix for the SpdxModel C# library showing the relationship between requirements and test cases +description: Trace Matrix for SpdxModel lang: en-US keywords: - - SpdxModel - - Trace Matrix - Requirements + - Trace Matrix + - SpdxModel - C# - .NET - SPDX - - Testing - - Traceability --- diff --git a/docs/user_guide/definition.yaml b/docs/user_guide/definition.yaml index affad56..0377821 100644 --- a/docs/user_guide/definition.yaml +++ b/docs/user_guide/definition.yaml @@ -1,13 +1,11 @@ --- -resource-path: - - docs/user_guide - - docs/template - +resource-path: [docs/user_guide, docs/template] input-files: - docs/user_guide/title.txt - docs/user_guide/introduction.md + - docs/user_guide/installation.md + - docs/user_guide/usage.md + - docs/user_guide/troubleshooting.md template: template.html - table-of-contents: true - number-sections: true diff --git a/docs/user_guide/installation.md b/docs/user_guide/installation.md new file mode 100644 index 0000000..ada5a20 --- /dev/null +++ b/docs/user_guide/installation.md @@ -0,0 +1,31 @@ +# Installation + +## Prerequisites + +Using SpdxModel requires: + +- .NET Standard 2.0, .NET 8.0, 9.0, or 10.0 +- C# 12 or later + +## Installing via NuGet + +Install the SpdxModel package via the .NET CLI: + +```bash +dotnet add package DemaConsulting.SpdxModel +``` + +Or via the Package Manager Console in Visual Studio: + +```powershell +Install-Package DemaConsulting.SpdxModel +``` + +## Verifying Installation + +After installation, verify that you can reference the library namespaces: + +```csharp +using DemaConsulting.SpdxModel; +using DemaConsulting.SpdxModel.IO; +``` diff --git a/docs/user_guide/introduction.md b/docs/user_guide/introduction.md index 351d148..5d51d8d 100644 --- a/docs/user_guide/introduction.md +++ b/docs/user_guide/introduction.md @@ -1,685 +1,30 @@ # Introduction -## Purpose - -The SpdxModel library is a modern C# library designed for working with SPDX (Software Package Data Exchange) documents. -SPDX is an open standard for communicating software bill of materials (SBOM) information, including components, -licenses, copyrights, and security references. +This guide describes how to install, configure, and use SpdxModel. -This library provides a comprehensive in-memory model for reading, manipulating, and writing SPDX SBOM files in JSON -format. +## Purpose -The library offers the following key features: +SpdxModel is a modern C# library for working with SPDX (Software Package Data Exchange) documents. +SPDX is an open standard for communicating software bill of materials (SBOM) information, including +components, licenses, copyrights, and security references. -- 🚀 **Full SPDX Support**: Complete implementation of SPDX 2.2 and 2.3 specifications -- 📦 **In-Memory Model**: Efficient object model for SPDX documents -- 🔄 **JSON Serialization**: Read and write SPDX documents in JSON format -- 🎯 **Type-Safe**: Strongly-typed C# API with nullable reference types -- 🔍 **Transform Support**: Built-in utilities for manipulating SPDX relationships -- ⚡ **Multi-Target**: Supports .NET Standard 2.0, .NET 8, 9, and 10 -- 🧪 **Well-Tested**: Comprehensive test suite with high code coverage -- 📚 **Well-Documented**: XML documentation for all public APIs +The library provides a comprehensive in-memory model for reading, manipulating, and writing SPDX SBOM +files in JSON format, with full support for SPDX 2.2 and 2.3 specifications. It targets .NET Standard +2.0, .NET 8, 9, and 10, and builds and runs on Windows, Linux, and macOS. It is suitable for use in +.NET applications, tools, and CI/CD pipelines. ## Scope -This library is ideal for: - -- **SBOM Generation**: Creating software bill of materials for your applications -- **SBOM Analysis**: Parsing and analyzing existing SPDX documents -- **License Compliance**: Tracking and managing software licenses -- **Supply Chain Security**: Managing software component relationships and dependencies -- **CI/CD Integration**: Automating SBOM creation and validation in build pipelines -- **Tool Integration**: Building tools that work with SPDX documents - -The library fully supports the following SPDX specifications: - -- **SPDX 2.2**: Full support for SPDX 2.2 specification -- **SPDX 2.3**: Full support for SPDX 2.3 specification - -# Continuous Compliance - -This library follows the [Continuous Compliance][continuous-compliance] methodology, which ensures -compliance evidence is generated automatically on every CI run. - -## Key Practices +This guide covers installation of the SpdxModel NuGet package, basic and advanced usage of the library +API, and troubleshooting common issues. It includes usage examples for reading and writing SPDX documents, +working with packages, files, snippets, relationships, and custom license references. -- **Requirements Traceability**: Every requirement is linked to passing tests, and a trace matrix is - auto-generated on each release -- **Linting Enforcement**: markdownlint, cspell, and yamllint are enforced before any build proceeds -- **Automated Audit Documentation**: Each release ships with generated requirements, justifications, - trace matrix, and quality reports -- **CodeQL and SonarCloud**: Security and quality analysis runs on every build - -# Installation - -## Prerequisites +Prerequisites for using SpdxModel: - .NET Standard 2.0, .NET 8.0, 9.0, or 10.0 - C# 12 or later -## Installing via NuGet - -You can install the SpdxModel package via NuGet Package Manager: - -```bash -dotnet add package DemaConsulting.SpdxModel -``` - -Or via the Package Manager Console in Visual Studio: - -```powershell -Install-Package DemaConsulting.SpdxModel -``` - -## Verifying Installation - -After installation, verify that you can import the library: - -```csharp -using DemaConsulting.SpdxModel; -using DemaConsulting.SpdxModel.IO; -``` - -# Quick Start - -## Reading an SPDX Document - -The simplest way to get started is to read an existing SPDX document: - -```csharp -using DemaConsulting.SpdxModel; -using DemaConsulting.SpdxModel.IO; - -// Read SPDX document from JSON file -var json = File.ReadAllText("sbom.spdx.json"); -var document = Spdx2JsonDeserializer.Deserialize(json); - -// Access document properties -Console.WriteLine($"Document: {document.Name}"); -Console.WriteLine($"Version: {document.Version}"); -Console.WriteLine($"Namespace: {document.DocumentNamespace}"); -Console.WriteLine($"Packages: {document.Packages.Length}"); -Console.WriteLine($"Files: {document.Files.Length}"); -``` - -## Creating a Simple SPDX Document - -Here's how to create a minimal SPDX document: - -```csharp -using DemaConsulting.SpdxModel; -using DemaConsulting.SpdxModel.IO; - -// Create a new SPDX document -var document = new SpdxDocument -{ - Id = "SPDXRef-DOCUMENT", - Name = "My Software", - Version = "SPDX-2.3", - DocumentNamespace = "https://example.com/my-software/1.0.0", - CreationInformation = new SpdxCreationInformation - { - Created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), - Creators = ["Tool: MyTool-1.0", "Organization: Example Corp"] - }, - Packages = - [ - new SpdxPackage - { - Id = "SPDXRef-Package", - Name = "MyPackage", - Version = "1.0.0", - DownloadLocation = "https://example.com/package", - FilesAnalyzed = false, - LicenseConcluded = "MIT", - LicenseDeclared = "MIT", - CopyrightText = "Copyright (c) 2024 Example Corp" - } - ] -}; - -// Serialize to JSON -var json = Spdx2JsonSerializer.Serialize(document); -File.WriteAllText("output.spdx.json", json); -``` - -# Core Concepts - -## SPDX Elements - -SPDX documents consist of several key element types: - -- **Document**: The root element containing metadata and all other elements -- **Package**: Represents a software package or component -- **File**: Represents individual files within packages -- **Snippet**: Represents code snippets within files -- **Relationship**: Describes relationships between elements - -## SPDX Identifiers - -Every SPDX element must have a unique identifier within the document. Identifiers follow the format `SPDXRef-{name}`: - -```csharp -var package = new SpdxPackage -{ - Id = "SPDXRef-MyPackage", - Name = "MyPackage" -}; -``` - -## Document Namespace - -Every SPDX document must have a unique namespace URI that identifies the document: - -```csharp -var document = new SpdxDocument -{ - DocumentNamespace = "https://example.com/my-software/1.0.0" -}; -``` - -The namespace should be unique for each version of your software. - -# Working with Documents - -## Document Properties - -A complete SPDX document includes: - -```csharp -var document = new SpdxDocument -{ - // Required fields - Id = "SPDXRef-DOCUMENT", - Name = "My Software SBOM", - Version = "SPDX-2.3", - DataLicense = "CC0-1.0", - DocumentNamespace = "https://example.com/my-software/1.0.0", - - // Creation information - CreationInformation = new SpdxCreationInformation - { - Created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), - Creators = - [ - "Tool: MyTool-1.0", - "Organization: Example Corp", - "Person: John Doe (john@example.com)" - ], - LicenseListVersion = "3.21" - }, - - // Optional fields - Comment = "This SBOM describes the software components", - - // Collections - Packages = [], - Files = [], - Snippets = [], - Relationships = [] -}; -``` - -## Document Describes Relationship - -Every SPDX document should have at least one "DESCRIBES" relationship indicating what the document describes: - -```csharp -document.Relationships = -[ - new SpdxRelationship - { - Id = "SPDXRef-DOCUMENT", - RelationshipType = SpdxRelationshipType.Describes, - RelatedSpdxElement = "SPDXRef-RootPackage" - } -]; -``` - -# Working with Packages - -## Creating Packages - -A package represents a software component or library: - -```csharp -var package = new SpdxPackage -{ - // Required fields - Id = "SPDXRef-Package", - Name = "MyPackage", - DownloadLocation = "https://github.com/example/mypackage", - FilesAnalyzed = false, - - // Version information - Version = "1.0.0", - - // License information - LicenseConcluded = "MIT", - LicenseDeclared = "MIT", - LicenseComments = "This package is licensed under MIT", - - // Copyright information - CopyrightText = "Copyright (c) 2024 Example Corp", - - // Optional metadata - Summary = "A useful package for doing things", - Description = "This package provides functionality for...", - Homepage = "https://example.com/mypackage", - - // Source information - SourceInfo = "Built from commit abc123", - - // Package supplier and originator - Supplier = "Organization: Example Corp", - Originator = "Organization: Original Corp" -}; -``` - -## Package with Files - -When a package includes file information: - -```csharp -var package = new SpdxPackage -{ - Id = "SPDXRef-Package", - Name = "MyPackage", - DownloadLocation = "https://github.com/example/mypackage", - FilesAnalyzed = true, // Set to true when analyzing files - VerificationCode = new SpdxPackageVerificationCode - { - Value = "d6a770ba38583ed4bb4525bd96e50461655d2758", - ExcludedFiles = ["./package.spdx"] - } -}; - -// Add files (see next section) -document.Files = -[ - new SpdxFile - { - Id = "SPDXRef-File1", - FileName = "./src/main.cs" - } -]; - -// Add relationship linking package to files -document.Relationships = -[ - new SpdxRelationship - { - Id = "SPDXRef-Package", - RelationshipType = SpdxRelationshipType.Contains, - RelatedSpdxElement = "SPDXRef-File1" - } -]; -``` - -## External References - -Packages can include external references for security and other metadata: - -```csharp -var package = new SpdxPackage -{ - Id = "SPDXRef-Package", - Name = "MyPackage", - ExternalReferences = - [ - new SpdxExternalReference - { - Category = SpdxReferenceCategory.Security, - Type = "cpe23Type", - Locator = "cpe:2.3:a:example:my-package:1.0.0:*:*:*:*:*:*:*" - }, - new SpdxExternalReference - { - Category = SpdxReferenceCategory.PackageManager, - Type = "purl", - Locator = "pkg:nuget/MyPackage@1.0.0" - } - ] -}; -``` - -# Working with Files - -## Creating Files - -Files represent individual files within packages: - -```csharp -var file = new SpdxFile -{ - // Required fields - Id = "SPDXRef-File1", - FileName = "./src/main.cs", - - // Checksums - Checksums = - [ - new SpdxChecksum - { - Algorithm = SpdxChecksumAlgorithm.SHA256, - Value = "abc123def456..." - } - ], - - // License information - LicenseConcluded = "MIT", - LicenseInfoInFiles = ["MIT"], - CopyrightText = "Copyright (c) 2024 Example Corp", - - // File type - FileTypes = [SpdxFileType.Source], - - // Optional fields - Comment = "Main application file" -}; -``` - -## File Checksums - -Files should include checksums for integrity verification: - -```csharp -var file = new SpdxFile -{ - Id = "SPDXRef-File1", - FileName = "./lib/library.dll", - Checksums = - [ - new SpdxChecksum - { - Algorithm = SpdxChecksumAlgorithm.SHA256, - Value = "abc123..." - }, - new SpdxChecksum - { - Algorithm = SpdxChecksumAlgorithm.SHA1, - Value = "def456..." - } - ] -}; -``` - -# Working with Relationships - -## Relationship Types - -SPDX defines many relationship types. Common ones include: - -- **DESCRIBES**: Document describes an element -- **CONTAINS**: Package contains files or other packages -- **DEPENDS_ON**: Package depends on another package -- **DEPENDENCY_OF**: Inverse of DEPENDS_ON -- **BUILD_DEPENDENCY_OF**: Build-time dependency -- **DEV_DEPENDENCY_OF**: Development dependency -- **GENERATED_FROM**: File generated from another file - -## Adding Relationships - -```csharp -using DemaConsulting.SpdxModel.Transform; - -// Add a relationship -var relationship = new SpdxRelationship -{ - Id = "SPDXRef-Package1", - RelationshipType = SpdxRelationshipType.DependsOn, - RelatedSpdxElement = "SPDXRef-Package2" -}; - -SpdxRelationships.Add(document, relationship); -``` - -## Finding Related Elements - -```csharp -// Get packages described by the document -var rootPackages = document.GetRootPackages(); -``` - -# Advanced Usage - -## Custom License References - -For licenses not in the SPDX license list: - -```csharp -var document = new SpdxDocument -{ - // ... other fields ... - - ExtractedLicensingInfo = - [ - new SpdxExtractedLicensingInfo - { - LicenseId = "LicenseRef-CustomLicense", - ExtractedText = "Full license text here...", - Name = "Custom License", - CrossReferences = ["https://example.com/license"] - } - ] -}; - -// Reference the custom license -var package = new SpdxPackage -{ - Id = "SPDXRef-Package", - Name = "MyPackage", - LicenseConcluded = "LicenseRef-CustomLicense" -}; -``` - -## Document Annotations - -Add annotations to provide additional information: - -```csharp -var annotation = new SpdxAnnotation -{ - Annotator = "Person: John Doe", - Date = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), - Type = SpdxAnnotationType.Review, - Comment = "This package has been reviewed for security concerns" -}; - -document.Annotations = [annotation]; -``` - -## Working with Snippets - -Snippets represent portions of files: - -```csharp -var snippet = new SpdxSnippet -{ - Id = "SPDXRef-Snippet1", - SnippetFromFile = "SPDXRef-File1", - Name = "Authentication Function", - SnippetLineStart = 100, - SnippetLineEnd = 150, - LicenseConcluded = "MIT" -}; - -document.Snippets = [snippet]; -``` - -# Best Practices - -## Use Meaningful Identifiers - -Choose descriptive SPDX identifiers: - -```csharp -// Good -Id = "SPDXRef-Package-MyLibrary-1.0.0" - -// Not as good -Id = "SPDXRef-Package1" -``` - -## Include Complete License Information - -Always specify both concluded and declared licenses: - -```csharp -var package = new SpdxPackage -{ - LicenseConcluded = "MIT", // What you determined - LicenseDeclared = "MIT" // What the package declares -}; -``` - -## Use Checksums for Files - -Always include checksums for files when possible: - -```csharp -var file = new SpdxFile -{ - FileName = "./app.exe", - Checksums = - [ - new SpdxChecksum - { - Algorithm = SpdxChecksumAlgorithm.SHA256, - Value = "..." - } - ] -}; -``` - -## Maintain Unique Document Namespaces - -Each version of your software should have a unique namespace: - -```csharp -// Good - includes version -DocumentNamespace = "https://example.com/myapp/1.0.0" - -// Not as good - no version -DocumentNamespace = "https://example.com/myapp" -``` - -## Document Relationships - -Explicitly document relationships between components: - -```csharp -// Document what it describes -document.Relationships = -[ - new SpdxRelationship - { - Id = "SPDXRef-DOCUMENT", - RelationshipType = SpdxRelationshipType.Describes, - RelatedSpdxElement = "SPDXRef-RootPackage" - } -]; -``` - -## Include Creator Information - -Provide complete creator information: - -```csharp -CreationInformation = new SpdxCreationInformation -{ - Created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), - Creators = - [ - "Tool: MyTool-1.0", - "Organization: Example Corp", - "Person: Build System" - ] -}; -``` - -## Error Handling - -Always handle potential errors when deserializing: - -```csharp -try -{ - var json = File.ReadAllText("sbom.spdx.json"); - var document = Spdx2JsonDeserializer.Deserialize(json); -} -catch (JsonException ex) -{ - Console.WriteLine($"Failed to parse SPDX document: {ex.Message}"); -} -catch (IOException ex) -{ - Console.WriteLine($"Failed to read file: {ex.Message}"); -} -``` - -# Troubleshooting - -## Common Issues - -### Invalid SPDX Identifier Format - -SPDX identifiers must start with "SPDXRef-": - -```csharp -// Correct -Id = "SPDXRef-MyPackage" - -// Incorrect -Id = "MyPackage" -``` - -### Missing Required Fields - -Ensure all required fields are populated: - -```csharp -var document = new SpdxDocument -{ - Id = "SPDXRef-DOCUMENT", // Required - Name = "MyDoc", // Required - Version = "SPDX-2.3", // Required - DataLicense = "CC0-1.0", // Required - DocumentNamespace = "...", // Required - CreationInformation = new ... // Required -}; -``` - -# Additional Resources - -- [SPDX Specification][spdx-spec] - Official SPDX specification -- [API Documentation][api-docs] - Detailed API reference -- [GitHub Repository][github-repo] - Source code and issues -- [NuGet Package][nuget-package] - Package downloads -- [spdx-tool][spdx-tool] - Command-line tool for SPDX documents - -# Support - -For help and support: - -- 📫 **Issues**: [GitHub Issues][github-issues] -- 💬 **Discussions**: [GitHub Discussions][github-discussions] -- 📧 **Email**: Contact DEMA Consulting for enterprise support - -# License - -This library is licensed under the MIT License. See the LICENSE file for details. - ---- - -Made with ❤️ by [DEMA Consulting][dema-consulting] +## References -[spdx-spec]: https://spdx.dev/ -[api-docs]: https://github.com/demaconsulting/SpdxModel/wiki -[github-repo]: https://github.com/demaconsulting/SpdxModel -[nuget-package]: https://www.nuget.org/packages/DemaConsulting.SpdxModel/ -[spdx-tool]: https://github.com/demaconsulting/spdx-tool -[github-issues]: https://github.com/demaconsulting/SpdxModel/issues -[github-discussions]: https://github.com/demaconsulting/SpdxModel/discussions -[dema-consulting]: https://github.com/demaconsulting -[continuous-compliance]: https://github.com/demaconsulting/ContinuousCompliance +- [REF-1] SpdxModel releases, +- [REF-2] SPDX Specification, diff --git a/docs/user_guide/title.txt b/docs/user_guide/title.txt index c17bd05..1f70118 100644 --- a/docs/user_guide/title.txt +++ b/docs/user_guide/title.txt @@ -1,15 +1,14 @@ --- -title: SpdxModel User Guide -subtitle: User Guide -author: DEMA Consulting -description: A C# library for serializing and deserializing SPDX SBOMs +title: "SpdxModel User Guide" +subtitle: "SPDX document model for .NET" +author: "DEMA Consulting" +description: "User Guide for SpdxModel" lang: en-US keywords: - SpdxModel - - C# - - .NET + - User Guide - SPDX - SBOM - - Software Bill of Materials - - Documentation + - C# + - .NET --- diff --git a/docs/user_guide/troubleshooting.md b/docs/user_guide/troubleshooting.md new file mode 100644 index 0000000..e088d4b --- /dev/null +++ b/docs/user_guide/troubleshooting.md @@ -0,0 +1,59 @@ +# Troubleshooting + +## Common Issues + +### Invalid SPDX Identifier Format + +SPDX identifiers must start with `SPDXRef-`. Using any other prefix causes validation errors: + +```csharp +// Correct +Id = "SPDXRef-MyPackage" + +// Incorrect +Id = "MyPackage" +``` + +### Missing Required Fields + +Ensure all required fields are populated before serializing a document: + +```csharp +var document = new SpdxDocument +{ + Id = "SPDXRef-DOCUMENT", // Required + Name = "MyDoc", // Required + Version = "SPDX-2.3", // Required + DataLicense = "CC0-1.0", // Required + DocumentNamespace = "...", // Required + CreationInformation = new ... // Required +}; +``` + +### Error Handling During Deserialization + +Always handle potential errors when reading SPDX documents from untrusted sources: + +```csharp +try +{ + var json = File.ReadAllText("sbom.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); +} +catch (JsonException ex) +{ + Console.WriteLine($"Failed to parse SPDX document: {ex.Message}"); +} +catch (IOException ex) +{ + Console.WriteLine($"Failed to read file: {ex.Message}"); +} +``` + +## Additional Resources + +- [SPDX Specification](https://spdx.dev/) — Official SPDX specification +- [SpdxModel NuGet Package](https://www.nuget.org/packages/DemaConsulting.SpdxModel/) — Package downloads +- [spdx-tool](https://github.com/demaconsulting/spdx-tool) — Command-line tool for SPDX documents +- [GitHub Issues](https://github.com/demaconsulting/SpdxModel/issues) — Bug reports and feature requests +- [GitHub Discussions](https://github.com/demaconsulting/SpdxModel/discussions) — Community questions and support diff --git a/docs/user_guide/usage.md b/docs/user_guide/usage.md new file mode 100644 index 0000000..89d2f10 --- /dev/null +++ b/docs/user_guide/usage.md @@ -0,0 +1,569 @@ +# Usage + +## Quick Start + +### Reading an SPDX Document + +The simplest way to get started is to read an existing SPDX document: + +```csharp +using DemaConsulting.SpdxModel; +using DemaConsulting.SpdxModel.IO; + +// Read SPDX document from JSON file +var json = File.ReadAllText("sbom.spdx.json"); +var document = Spdx2JsonDeserializer.Deserialize(json); + +// Access document properties +Console.WriteLine($"Document: {document.Name}"); +Console.WriteLine($"Version: {document.Version}"); +Console.WriteLine($"Namespace: {document.DocumentNamespace}"); +Console.WriteLine($"Packages: {document.Packages.Length}"); +Console.WriteLine($"Files: {document.Files.Length}"); +``` + +### Creating a Simple SPDX Document + +Here is how to create a minimal SPDX document: + +```csharp +using DemaConsulting.SpdxModel; +using DemaConsulting.SpdxModel.IO; + +// Create a new SPDX document +var document = new SpdxDocument +{ + Id = "SPDXRef-DOCUMENT", + Name = "My Software", + Version = "SPDX-2.3", + DocumentNamespace = "https://example.com/my-software/1.0.0", + CreationInformation = new SpdxCreationInformation + { + Created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + Creators = ["Tool: MyTool-1.0", "Organization: Example Corp"] + }, + Packages = + [ + new SpdxPackage + { + Id = "SPDXRef-Package", + Name = "MyPackage", + Version = "1.0.0", + DownloadLocation = "https://example.com/package", + FilesAnalyzed = false, + LicenseConcluded = "MIT", + LicenseDeclared = "MIT", + CopyrightText = "Copyright (c) 2024 Example Corp" + } + ] +}; + +// Serialize to JSON +var json = Spdx2JsonSerializer.Serialize(document); +File.WriteAllText("output.spdx.json", json); +``` + +## Core Concepts + +### SPDX Elements + +SPDX documents consist of several key element types: + +- **Document**: The root element containing metadata and all other elements +- **Package**: Represents a software package or component +- **File**: Represents individual files within packages +- **Snippet**: Represents code snippets within files +- **Relationship**: Describes relationships between elements + +### SPDX Identifiers + +Every SPDX element must have a unique identifier within the document. Identifiers follow the +format `SPDXRef-{name}`: + +```csharp +var package = new SpdxPackage +{ + Id = "SPDXRef-MyPackage", + Name = "MyPackage" +}; +``` + +### Document Namespace + +Every SPDX document must have a unique namespace URI that identifies the document: + +```csharp +var document = new SpdxDocument +{ + DocumentNamespace = "https://example.com/my-software/1.0.0" +}; +``` + +The namespace should be unique for each version of your software. + +## Working with Documents + +### Document Properties + +A complete SPDX document includes: + +```csharp +var document = new SpdxDocument +{ + // Required fields + Id = "SPDXRef-DOCUMENT", + Name = "My Software SBOM", + Version = "SPDX-2.3", + DataLicense = "CC0-1.0", + DocumentNamespace = "https://example.com/my-software/1.0.0", + + // Creation information + CreationInformation = new SpdxCreationInformation + { + Created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + Creators = + [ + "Tool: MyTool-1.0", + "Organization: Example Corp", + "Person: John Doe (john@example.com)" + ], + LicenseListVersion = "3.21" + }, + + // Optional fields + Comment = "This SBOM describes the software components", + + // Collections + Packages = [], + Files = [], + Snippets = [], + Relationships = [] +}; +``` + +### Document Describes Relationship + +Every SPDX document should have at least one DESCRIBES relationship indicating what the document +describes: + +```csharp +document.Relationships = +[ + new SpdxRelationship + { + Id = "SPDXRef-DOCUMENT", + RelationshipType = SpdxRelationshipType.Describes, + RelatedSpdxElement = "SPDXRef-RootPackage" + } +]; +``` + +## Working with Packages + +### Creating Packages + +A package represents a software component or library: + +```csharp +var package = new SpdxPackage +{ + // Required fields + Id = "SPDXRef-Package", + Name = "MyPackage", + DownloadLocation = "https://github.com/example/mypackage", + FilesAnalyzed = false, + + // Version information + Version = "1.0.0", + + // License information + LicenseConcluded = "MIT", + LicenseDeclared = "MIT", + LicenseComments = "This package is licensed under MIT", + + // Copyright information + CopyrightText = "Copyright (c) 2024 Example Corp", + + // Optional metadata + Summary = "A useful package for doing things", + Description = "This package provides functionality for...", + Homepage = "https://example.com/mypackage", + + // Source information + SourceInfo = "Built from commit abc123", + + // Package supplier and originator + Supplier = "Organization: Example Corp", + Originator = "Organization: Original Corp" +}; +``` + +### Package with Files + +When a package includes file information, set `FilesAnalyzed` to `true` and provide a verification +code: + +```csharp +var package = new SpdxPackage +{ + Id = "SPDXRef-Package", + Name = "MyPackage", + DownloadLocation = "https://github.com/example/mypackage", + FilesAnalyzed = true, + VerificationCode = new SpdxPackageVerificationCode + { + Value = "d6a770ba38583ed4bb4525bd96e50461655d2758", + ExcludedFiles = ["./package.spdx"] + } +}; + +// Add files and a relationship linking the package to those files +document.Files = +[ + new SpdxFile + { + Id = "SPDXRef-File1", + FileName = "./src/main.cs" + } +]; + +document.Relationships = +[ + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelationshipType = SpdxRelationshipType.Contains, + RelatedSpdxElement = "SPDXRef-File1" + } +]; +``` + +### External References + +Packages can include external references for security and package-manager metadata: + +```csharp +var package = new SpdxPackage +{ + Id = "SPDXRef-Package", + Name = "MyPackage", + ExternalReferences = + [ + new SpdxExternalReference + { + Category = SpdxReferenceCategory.Security, + Type = "cpe23Type", + Locator = "cpe:2.3:a:example:my-package:1.0.0:*:*:*:*:*:*:*" + }, + new SpdxExternalReference + { + Category = SpdxReferenceCategory.PackageManager, + Type = "purl", + Locator = "pkg:nuget/MyPackage@1.0.0" + } + ] +}; +``` + +## Working with Files + +### Creating Files + +Files represent individual files within packages: + +```csharp +var file = new SpdxFile +{ + // Required fields + Id = "SPDXRef-File1", + FileName = "./src/main.cs", + + // Checksums + Checksums = + [ + new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.SHA256, + Value = "abc123def456..." + } + ], + + // License information + LicenseConcluded = "MIT", + LicenseInfoInFiles = ["MIT"], + CopyrightText = "Copyright (c) 2024 Example Corp", + + // File type + FileTypes = [SpdxFileType.Source], + + // Optional fields + Comment = "Main application file" +}; +``` + +### File Checksums + +Include multiple checksum algorithms for stronger integrity verification: + +```csharp +var file = new SpdxFile +{ + Id = "SPDXRef-File1", + FileName = "./lib/library.dll", + Checksums = + [ + new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.SHA256, + Value = "abc123..." + }, + new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.SHA1, + Value = "def456..." + } + ] +}; +``` + +## Working with Relationships + +### Relationship Types + +SPDX defines many relationship types. Common ones include: + +- **DESCRIBES**: Document describes an element +- **CONTAINS**: Package contains files or other packages +- **DEPENDS_ON**: Package depends on another package +- **DEPENDENCY_OF**: Inverse of DEPENDS_ON +- **BUILD_DEPENDENCY_OF**: Build-time dependency +- **DEV_DEPENDENCY_OF**: Development dependency +- **GENERATED_FROM**: File generated from another file + +### Adding Relationships + +```csharp +using DemaConsulting.SpdxModel.Transform; + +// Add a relationship +var relationship = new SpdxRelationship +{ + Id = "SPDXRef-Package1", + RelationshipType = SpdxRelationshipType.DependsOn, + RelatedSpdxElement = "SPDXRef-Package2" +}; + +SpdxRelationships.Add(document, relationship); +``` + +### Finding Related Elements + +```csharp +// Get packages described by the document +var rootPackages = document.GetRootPackages(); +``` + +## Advanced Usage + +### Custom License References + +For licenses not in the SPDX license list, use a `LicenseRef-` identifier: + +```csharp +var document = new SpdxDocument +{ + // ... other fields ... + ExtractedLicensingInfo = + [ + new SpdxExtractedLicensingInfo + { + LicenseId = "LicenseRef-CustomLicense", + ExtractedText = "Full license text here...", + Name = "Custom License", + CrossReferences = ["https://example.com/license"] + } + ] +}; + +// Reference the custom license in a package +var package = new SpdxPackage +{ + Id = "SPDXRef-Package", + Name = "MyPackage", + LicenseConcluded = "LicenseRef-CustomLicense" +}; +``` + +### Document Annotations + +Add annotations to provide review or informational comments: + +```csharp +var annotation = new SpdxAnnotation +{ + Annotator = "Person: John Doe", + Date = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + Type = SpdxAnnotationType.Review, + Comment = "This package has been reviewed for security concerns" +}; + +document.Annotations = [annotation]; +``` + +### Working with Snippets + +Snippets represent portions of files and carry their own license information: + +```csharp +var snippet = new SpdxSnippet +{ + Id = "SPDXRef-Snippet1", + SnippetFromFile = "SPDXRef-File1", + Name = "Authentication Function", + SnippetLineStart = 100, + SnippetLineEnd = 150, + LicenseConcluded = "MIT" +}; + +document.Snippets = [snippet]; +``` + +### Deep Copying Elements + +The library supports deep copying of any SPDX element or an entire document. This creates +a completely independent copy with no shared references to the original: + +```csharp +// Deep copy an entire document +var original = Spdx2JsonDeserializer.Deserialize(json); +var copy = original.DeepCopy(); + +// Deep copy individual elements +var packageCopy = package.DeepCopy(); +var fileCopy = file.DeepCopy(); +``` + +Deep copying is useful when you need to modify an element without affecting the original, +such as when merging SPDX documents or creating variants. + +### Comparing SPDX Elements + +The library provides equality comparers for all SPDX element types. Each element class +exposes a `Same` static comparer that checks all significant fields: + +```csharp +// Compare two documents for equality +bool areEqual = SpdxDocument.Same.Equals(doc1, doc2); + +// Compare packages +bool samePackage = SpdxPackage.Same.Equals(pkg1, pkg2); + +// Compare relationships (ignoring comment field) +bool sameRelationship = SpdxRelationship.Same.Equals(rel1, rel2); + +// Compare relationships by elements only (ignoring relationship type) +bool sameElements = SpdxRelationship.SameElements.Equals(rel1, rel2); +``` + +These comparers are used internally by the deep-copy verification logic and by the +`SpdxRelationships.Add` deduplication logic. + +### Validating SPDX Documents + +Call `Validate()` on any SPDX document to get a list of validation issues. An empty list +means the document is valid: + +```csharp +var issues = new List(); +document.Validate(issues); + +if (issues.Count == 0) +{ + Console.WriteLine("Document is valid."); +} +else +{ + Console.WriteLine("Validation issues found:"); + foreach (var issue in issues) + { + Console.WriteLine($" - {issue}"); + } +} +``` + +The recommended workflow is to validate after deserialization to catch malformed or +incomplete SPDX documents early, before processing them further. + +## Best Practices + +### Use Meaningful Identifiers + +Choose descriptive SPDX identifiers so documents are human-readable: + +```csharp +// Good +Id = "SPDXRef-Package-MyLibrary-1.0.0" + +// Not as good +Id = "SPDXRef-Package1" +``` + +### Include Complete License Information + +Always specify both concluded and declared licenses: + +```csharp +var package = new SpdxPackage +{ + LicenseConcluded = "MIT", // What you determined after analysis + LicenseDeclared = "MIT" // What the package itself declares +}; +``` + +### Use Checksums for Files + +Always include checksums for files when possible to support integrity verification: + +```csharp +var file = new SpdxFile +{ + FileName = "./app.exe", + Checksums = + [ + new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.SHA256, + Value = "..." + } + ] +}; +``` + +### Maintain Unique Document Namespaces + +Each version of your software should have a unique namespace to avoid conflicts: + +```csharp +// Good - includes version +DocumentNamespace = "https://example.com/myapp/1.0.0" + +// Not as good - no version +DocumentNamespace = "https://example.com/myapp" +``` + +### Include Creator Information + +Provide complete creator information to support audit and traceability: + +```csharp +CreationInformation = new SpdxCreationInformation +{ + Created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + Creators = + [ + "Tool: MyTool-1.0", + "Organization: Example Corp", + "Person: Build System" + ] +}; +``` diff --git a/docs/verification/definition.yaml b/docs/verification/definition.yaml new file mode 100644 index 0000000..60618c1 --- /dev/null +++ b/docs/verification/definition.yaml @@ -0,0 +1,32 @@ +--- +resource-path: + - docs/verification + - docs/template +input-files: + - docs/verification/title.txt + - docs/verification/introduction.md + - docs/verification/spdx-model.md + - docs/verification/spdx-model/spdx-annotation.md + - docs/verification/spdx-model/spdx-checksum.md + - docs/verification/spdx-model/spdx-creation-information.md + - docs/verification/spdx-model/spdx-document.md + - docs/verification/spdx-model/spdx-element.md + - docs/verification/spdx-model/spdx-external-document-reference.md + - docs/verification/spdx-model/spdx-external-reference.md + - docs/verification/spdx-model/spdx-extracted-licensing-info.md + - docs/verification/spdx-model/spdx-file.md + - docs/verification/spdx-model/spdx-helpers.md + - docs/verification/spdx-model/spdx-license-element.md + - docs/verification/spdx-model/spdx-package-verification-code.md + - docs/verification/spdx-model/spdx-package.md + - docs/verification/spdx-model/spdx-relationship.md + - docs/verification/spdx-model/spdx-snippet.md + - docs/verification/spdx-model/io/io.md + - docs/verification/spdx-model/io/spdx-constants.md + - docs/verification/spdx-model/io/spdx-2-json-serializer.md + - docs/verification/spdx-model/io/spdx-2-json-deserializer.md + - docs/verification/spdx-model/transform/transform.md + - docs/verification/spdx-model/transform/spdx-relationships.md +template: template.html +table-of-contents: true +number-sections: true diff --git a/docs/verification/introduction.md b/docs/verification/introduction.md new file mode 100644 index 0000000..ba3e1ec --- /dev/null +++ b/docs/verification/introduction.md @@ -0,0 +1,38 @@ +# Introduction + +This document describes how each software item in the SpdxModel library is verified. + +## Purpose + +This document provides the verification design for the SpdxModel library. For each local +software item — system, subsystems, and units — the document names the test scenarios that +verify the item's requirements. A reviewer can confirm coverage completeness by reading this +document without consulting test source code. + +## Scope + +The following items are in scope for this document: + +- SpdxModel system verification +- IO subsystem and unit verification +- Transform subsystem and unit verification +- All software unit verifications within the SpdxModel system + +The following items are out of scope: + +- OTS item verification: MSTest, ReqStream, BuildMark, VersionMark, SarifMark, SonarMark, + ReviewMark, FileAssert, Pandoc, WeasyPrint +- Test infrastructure and test helpers + +## Companion Artifact Structure + +The following companion artifacts are related to this verification document: + +- Requirements artifacts are located in `docs/reqstream/spdx-model/` +- Software design documents are located in `docs/design/spdx-model/` +- Production source code is located in `src/DemaConsulting.SpdxModel/` +- Automated test suite is located in `test/DemaConsulting.SpdxModel.Tests/` + +## References + +- [REF-1] SpdxModel releases, diff --git a/docs/verification/spdx-model.md b/docs/verification/spdx-model.md new file mode 100644 index 0000000..4876851 --- /dev/null +++ b/docs/verification/spdx-model.md @@ -0,0 +1,56 @@ +# SpdxModel + +## Verification Approach + +The SpdxModel library is verified through automated unit and integration tests using the MSTest +framework. Tests are organized in `test/DemaConsulting.SpdxModel.Tests/`. Unit tests verify +individual data model classes in isolation; integration tests verify the IO subsystem end-to-end +and the Transform subsystem with real document instances. No external dependencies are mocked. + +## Test Environment + +N/A - standard test environment. + +## Acceptance Criteria + +All automated tests pass with zero failures. + +## Test Scenarios + +**SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully**: Verifies that the library +successfully reads and parses an SPDX 2.2 example JSON document, returning a non-null +SpdxDocument without throwing exceptions. + +**SpdxModel_ReadSpdxJson_Spdx23Example_ParsesSuccessfully**: Verifies that the library +successfully reads and parses an SPDX 2.3 example JSON document, returning a non-null +SpdxDocument without throwing exceptions. + +**SpdxModel_ReadSpdxJson_Spdx22Example_PassesValidation**: Verifies that the document parsed +from the SPDX 2.2 example JSON passes all validation checks with no reported issues. + +**SpdxModel_ReadSpdxJson_Spdx23Example_PassesValidation**: Verifies that the document parsed +from the SPDX 2.3 example JSON passes all validation checks with no reported issues. + +**SpdxModel_ReadSpdxJson_Spdx23Example_RootPackagesIdentified**: Verifies that the root +packages are correctly identified in the SPDX 2.3 example document after parsing, confirming +DESCRIBES relationship traversal works as expected. + +**SpdxModel_ReadSpdxJson_Spdx23Example_DeepCopyProducesEquivalentDocument**: Verifies that a +deep copy of the parsed SPDX 2.3 document is structurally equal to the original, confirming +that all nested objects are fully duplicated. + +**SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds**: Verifies that an SPDX 2.3 +document serialized to JSON and then deserialized produces a document equivalent to the +original, confirming end-to-end serialization fidelity. + +**SpdxModel_Deserialize_MalformedJson_ThrowsJsonException**: Verifies that passing malformed +JSON to `Spdx2JsonDeserializer.Deserialize` throws a `JsonException` rather than returning a +partially-populated document. + +**SpdxModel_Validate_InvalidDocument_ReportsIssues**: Verifies that calling `Validate()` on a +deliberately incomplete SPDX document produces a non-empty issues list with the expected +validation error messages. + +**SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNullable**: Verifies that +required fields on key SPDX data model types are non-nullable string types with default empty +values, and that optional fields are nullable. diff --git a/docs/verification/spdx-model/io/io.md b/docs/verification/spdx-model/io/io.md new file mode 100644 index 0000000..e391f8b --- /dev/null +++ b/docs/verification/spdx-model/io/io.md @@ -0,0 +1,34 @@ +### IO + +#### Verification Approach + +The IO subsystem is verified through automated integration tests using the MSTest framework. +Tests are located in `test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs`. Integration +tests verify the IO subsystem end-to-end using real JSON input and output. +System.Text.Json is not mocked as JSON parsing is part of the verification scope. +Round-trip tests serialize and then deserialize documents to confirm fidelity. +Element-level field preservation is verified by the unit-level IO tests (Spdx2JsonDeserializer +and Spdx2JsonSerializer test classes). The subsystem-level tests verify end-to-end document +fidelity only. + +#### Test Environment + +N/A - standard test environment. + +#### Acceptance Criteria + +All integration tests pass with zero failures. + +#### Test Scenarios + +**SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidDocument**: Verifies +that an SPDX 2.2 document read from JSON and then written back to JSON and re-read produces +a document that passes all validation checks. + +**SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidDocument**: Verifies +that an SPDX 2.3 document read from JSON and then written back to JSON and re-read produces +a document that passes all validation checks. + +**SpdxModelIO_ReadSpdxJson_InvalidJson_ThrowsJsonException**: Verifies that passing malformed +JSON to `Spdx2JsonDeserializer.Deserialize` throws a `JsonException`, confirming that the +subsystem correctly propagates fatal parsing errors to callers. diff --git a/docs/verification/spdx-model/io/spdx-2-json-deserializer.md b/docs/verification/spdx-model/io/spdx-2-json-deserializer.md new file mode 100644 index 0000000..96a48aa --- /dev/null +++ b/docs/verification/spdx-model/io/spdx-2-json-deserializer.md @@ -0,0 +1,142 @@ +### Spdx2JsonDeserializer + +#### Verification Approach + +Spdx2JsonDeserializer is verified through automated unit tests using the MSTest framework. +Tests are located in the IO subdirectory under +`test/DemaConsulting.SpdxModel.Tests/`. Each test provides representative JSON input and +verifies the deserialized SPDX model objects match the expected values. System.Text.Json +is not mocked as JSON parsing is part of the verification scope. + +#### Test Environment + +N/A - standard test environment. + +#### Acceptance Criteria + +All automated tests pass with zero failures. + +#### Test Scenarios + +**Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument**: Verifies that +a complete SPDX 2.2 JSON document is deserialized to a fully populated SpdxDocument with +all fields correctly mapped. +This scenario is tested by +`Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument`. + +**Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument**: Verifies that +a complete SPDX 2.3 JSON document is deserialized to a fully populated SpdxDocument with +all fields correctly mapped. +This scenario is tested by +`Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument`. + +**Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults**: Verifies that a single +annotation JSON object is deserialized to an SpdxAnnotation with all fields correctly +populated. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeAnnotations_CorrectResults**: Verifies that a JSON array +of annotation objects is deserialized to a collection of SpdxAnnotation instances. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeAnnotations_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults**: Verifies that a single checksum +JSON object is deserialized to an SpdxChecksum with algorithm and value fields correctly +populated. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeChecksums_CorrectResults**: Verifies that a JSON array of +checksum objects is deserialized to a collection of SpdxChecksum instances. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeChecksums_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeCreationInformation_CorrectResults**: Verifies that the +creationInfo JSON object is deserialized to an SpdxCreationInformation with all fields +correctly populated. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializeCreationInformation_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeDocument_CorrectResults**: Verifies that the top-level +document JSON fields are deserialized to the SpdxDocument with all document-level fields +correctly populated. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeDocument_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectResults**: Verifies that +a single external document reference JSON object is deserialized to an +SpdxExternalDocumentReference with all fields correctly populated. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_CorrectResults**: Verifies that +a JSON array of external document reference objects is deserialized to a collection of +SpdxExternalDocumentReference instances. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults**: Verifies that a single +external reference JSON object is deserialized to an SpdxExternalReference with category, +type, and locator fields correctly populated. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeExternalReferences_CorrectResults**: Verifies that a JSON +array of external reference objects is deserialized to a collection of SpdxExternalReference +instances. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializeExternalReferences_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResults**: Verifies that a +single extracted licensing info JSON object is deserialized to an SpdxExtractedLicensingInfo +with all fields correctly populated. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_CorrectResults**: Verifies that a +JSON array of extracted licensing info objects is deserialized to a collection of +SpdxExtractedLicensingInfo instances. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeFile_CorrectResults**: Verifies that a single file JSON +object is deserialized to an SpdxFile with all fields correctly populated. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeFile_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeFiles_CorrectResults**: Verifies that a JSON array of file +objects is deserialized to a collection of SpdxFile instances. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeFiles_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializePackage_CorrectResults**: Verifies that a single package +JSON object is deserialized to an SpdxPackage with all fields correctly populated. +This scenario is tested by `Spdx2JsonDeserializer_DeserializePackage_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializePackages_CorrectResults**: Verifies that a JSON array of +package objects is deserialized to a collection of SpdxPackage instances. +This scenario is tested by `Spdx2JsonDeserializer_DeserializePackages_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResults**: Verifies that a +package verification code JSON object is deserialized to an SpdxPackageVerificationCode with +all fields correctly populated. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults**: Verifies that a single +relationship JSON object is deserialized to an SpdxRelationship with all fields correctly +populated. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeRelationships_CorrectResults**: Verifies that a JSON array +of relationship objects is deserialized to a collection of SpdxRelationship instances. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeRelationships_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults**: Verifies that a single snippet +JSON object is deserialized to an SpdxSnippet with byte ranges and line ranges correctly +populated. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults`. + +**Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero**: Verifies that +when a snippet JSON object omits line range fields, the deserialized SpdxSnippet defaults +those fields to zero. +This scenario is tested by +`Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero`. + +**Spdx2JsonDeserializer_DeserializeSnippets_CorrectResults**: Verifies that a JSON array of +snippet objects is deserialized to a collection of SpdxSnippet instances. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeSnippets_CorrectResults`. diff --git a/docs/verification/spdx-model/io/spdx-2-json-serializer.md b/docs/verification/spdx-model/io/spdx-2-json-serializer.md new file mode 100644 index 0000000..298e1fa --- /dev/null +++ b/docs/verification/spdx-model/io/spdx-2-json-serializer.md @@ -0,0 +1,120 @@ +### Spdx2JsonSerializer + +#### Verification Approach + +Spdx2JsonSerializer is verified through automated unit tests using the MSTest framework. +Tests are located in the IO subdirectory under +`test/DemaConsulting.SpdxModel.Tests/`. Each test constructs the relevant SPDX model +objects directly and verifies the serialized JSON output. System.Text.Json is not mocked +as JSON output is part of the verification scope. + +#### Test Environment + +N/A - standard test environment. + +#### Acceptance Criteria + +All automated tests pass with zero failures. + +#### Test Scenarios + +**Spdx2JsonSerializer_SerializeAnnotation_CorrectResults**: Verifies that a single +SpdxAnnotation is serialized to the expected JSON structure with all fields correctly mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializeAnnotation_CorrectResults`. + +**Spdx2JsonSerializer_SerializeAnnotations_CorrectResults**: Verifies that a collection of +SpdxAnnotation instances is serialized to the expected JSON array structure. +This scenario is tested by `Spdx2JsonSerializer_SerializeAnnotations_CorrectResults`. + +**Spdx2JsonSerializer_SerializeChecksum_CorrectResults**: Verifies that a single SpdxChecksum +is serialized to the expected JSON structure with algorithm and value fields correctly mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializeChecksum_CorrectResults`. + +**Spdx2JsonSerializer_SerializeChecksums_CorrectResults**: Verifies that a collection of +SpdxChecksum instances is serialized to the expected JSON array structure. +This scenario is tested by `Spdx2JsonSerializer_SerializeChecksums_CorrectResults`. + +**Spdx2JsonSerializer_SerializeCreationInformation_CorrectResults**: Verifies that +SpdxCreationInformation is serialized to the expected JSON structure with all creation fields +correctly mapped. +This scenario is tested by +`Spdx2JsonSerializer_SerializeCreationInformation_CorrectResults`. + +**Spdx2JsonSerializer_SerializeDocument_CorrectResults**: Verifies that the top-level +SpdxDocument structure (excluding nested collections) is serialized to the expected JSON +with all document-level fields correctly mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializeDocument_CorrectResults`. + +**Spdx2JsonSerializer_Serialize_CorrectResults**: Verifies that a complete SpdxDocument +including all nested packages, files, snippets, and relationships is serialized to the +expected JSON output. +This scenario is tested by `Spdx2JsonSerializer_Serialize_CorrectResults`. + +**Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResults**: Verifies that a +single SpdxExternalDocumentReference is serialized to the expected JSON structure. +This scenario is tested by +`Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResults`. + +**Spdx2JsonSerializer_SerializeExternalDocumentReferences_CorrectResults**: Verifies that a +collection of SpdxExternalDocumentReference instances is serialized to the expected JSON +array. +This scenario is tested by +`Spdx2JsonSerializer_SerializeExternalDocumentReferences_CorrectResults`. + +**Spdx2JsonSerializer_SerializeExternalReference_CorrectResults**: Verifies that a single +SpdxExternalReference is serialized to the expected JSON structure with category, type, and +locator fields correctly mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializeExternalReference_CorrectResults`. + +**Spdx2JsonSerializer_SerializeExternalReferences_CorrectResults**: Verifies that a +collection of SpdxExternalReference instances is serialized to the expected JSON array. +This scenario is tested by `Spdx2JsonSerializer_SerializeExternalReferences_CorrectResults`. + +**Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults**: Verifies that a +single SpdxExtractedLicensingInfo is serialized to the expected JSON structure. +This scenario is tested by +`Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults`. + +**Spdx2JsonSerializer_SerializeExtractedLicensingInfos_CorrectResults**: Verifies that a +collection of SpdxExtractedLicensingInfo instances is serialized to the expected JSON array. +This scenario is tested by +`Spdx2JsonSerializer_SerializeExtractedLicensingInfos_CorrectResults`. + +**Spdx2JsonSerializer_SerializeFile_CorrectResults**: Verifies that a single SpdxFile is +serialized to the expected JSON structure with all file fields correctly mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializeFile_CorrectResults`. + +**Spdx2JsonSerializer_SerializeFiles_CorrectResults**: Verifies that a collection of SpdxFile +instances is serialized to the expected JSON array. +This scenario is tested by `Spdx2JsonSerializer_SerializeFiles_CorrectResults`. + +**Spdx2JsonSerializer_SerializePackage_CorrectResults**: Verifies that a single SpdxPackage +is serialized to the expected JSON structure with all package fields correctly mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializePackage_CorrectResults`. + +**Spdx2JsonSerializer_SerializePackages_CorrectResults**: Verifies that a collection of +SpdxPackage instances is serialized to the expected JSON array. +This scenario is tested by `Spdx2JsonSerializer_SerializePackages_CorrectResults`. + +**Spdx2JsonSerializer_SerializePackageVerificationCode_CorrectResults**: Verifies that an +SpdxPackageVerificationCode is serialized to the expected JSON structure. +This scenario is tested by +`Spdx2JsonSerializer_SerializePackageVerificationCode_CorrectResults`. + +**Spdx2JsonSerializer_SerializeRelationship_CorrectResults**: Verifies that a single +SpdxRelationship is serialized to the expected JSON structure with all relationship fields +correctly mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializeRelationship_CorrectResults`. + +**Spdx2JsonSerializer_SerializeRelationships_CorrectResults**: Verifies that a collection of +SpdxRelationship instances is serialized to the expected JSON array. +This scenario is tested by `Spdx2JsonSerializer_SerializeRelationships_CorrectResults`. + +**Spdx2JsonSerializer_SerializeSnippet_CorrectResults**: Verifies that a single SpdxSnippet +is serialized to the expected JSON structure with byte ranges and line ranges correctly +mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializeSnippet_CorrectResults`. + +**Spdx2JsonSerializer_SerializeSnippets_CorrectResults**: Verifies that a collection of +SpdxSnippet instances is serialized to the expected JSON array. +This scenario is tested by `Spdx2JsonSerializer_SerializeSnippets_CorrectResults`. diff --git a/docs/verification/spdx-model/io/spdx-constants.md b/docs/verification/spdx-model/io/spdx-constants.md new file mode 100644 index 0000000..14f3d8d --- /dev/null +++ b/docs/verification/spdx-model/io/spdx-constants.md @@ -0,0 +1,22 @@ +### SpdxConstants + +#### Verification Approach + +SpdxConstants defines string constants used by the Spdx2JsonDeserializer and +Spdx2JsonSerializer units. No dedicated unit test file exists for SpdxConstants. The +correctness of these constants is verified implicitly through the IO round-trip and unit +tests for the deserializer and serializer. + +#### Test Environment + +N/A - standard test environment. + +#### Acceptance Criteria + +All automated tests for Spdx2JsonDeserializer and Spdx2JsonSerializer pass with zero +failures. + +#### Test Scenarios + +N/A — SpdxConstants defines string constants verified implicitly through +`Spdx2JsonDeserializer` and `Spdx2JsonSerializer` unit tests. diff --git a/docs/verification/spdx-model/spdx-annotation.md b/docs/verification/spdx-model/spdx-annotation.md new file mode 100644 index 0000000..7d27868 --- /dev/null +++ b/docs/verification/spdx-model/spdx-annotation.md @@ -0,0 +1,72 @@ +## SpdxAnnotation + +### Verification Approach + +SpdxAnnotation is verified through automated unit tests using the MSTest framework. Tests are +located in `test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs`. Each test constructs +an SpdxAnnotation instance directly and exercises the method under test with no mocked +dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxAnnotation_SameComparer_ComparesCorrectly**: Verifies that SameComparer correctly +identifies two SpdxAnnotation instances as equal when all fields match and as distinct when +any field differs. +This scenario is tested by `SpdxAnnotation_SameComparer_ComparesCorrectly`. + +**SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep copy +produces a new SpdxAnnotation instance with equal field values but a distinct object reference, +confirming no shared state between original and copy. +This scenario is tested by `SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance`. + +**SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that Enhance merges +annotation data by adding missing fields from the source while preserving existing field +values on the target. +This scenario is tested by `SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly`. + +**SpdxAnnotation_Validate_InvalidAnnotator**: Verifies that validation reports an issue when +the Annotator field is missing or does not conform to the required format. +This scenario is tested by `SpdxAnnotation_Validate_InvalidAnnotator`. + +**SpdxAnnotation_Validate_InvalidDate**: Verifies that validation reports an issue when the +annotation date is missing or does not conform to ISO 8601 date-time format. +This scenario is tested by `SpdxAnnotation_Validate_InvalidDate`. + +**SpdxAnnotation_Validate_InvalidType**: Verifies that validation reports an issue when the +annotation type is set to an unrecognized or unsupported value. +This scenario is tested by `SpdxAnnotation_Validate_InvalidType`. + +**SpdxAnnotation_Validate_InvalidComment**: Verifies that validation reports an issue when the +annotation comment is missing or empty. +This scenario is tested by `SpdxAnnotation_Validate_InvalidComment`. + +**SpdxAnnotationTypeExtensions_FromText_Valid**: Verifies that FromText correctly parses a +recognized annotation type string to its corresponding enum value. +This scenario is tested by `SpdxAnnotationTypeExtensions_FromText_Valid`. + +**SpdxAnnotationTypeExtensions_FromText_Invalid**: Verifies that `FromText` throws +`InvalidOperationException` with the message `"Unsupported SPDX Annotation Type 'invalid'"` when +given an unrecognized annotation type string. +This scenario is tested by `SpdxAnnotationTypeExtensions_FromText_Invalid`. + +**SpdxAnnotationTypeExtensions_ToText_Valid**: Verifies that ToText correctly converts a +recognized annotation type enum value to its SPDX text representation. +This scenario is tested by `SpdxAnnotationTypeExtensions_ToText_Valid`. + +**SpdxAnnotationTypeExtensions_ToText_Invalid**: Verifies that ToText always throws +`InvalidOperationException` when given an unknown or unsupported annotation type enum value. +The method never returns an empty string for an invalid input. +This scenario is tested by `SpdxAnnotationTypeExtensions_ToText_Invalid`. + +**SpdxAnnotationTypeExtensions_ToText_Missing**: Verifies that calling +`SpdxAnnotationType.Missing.ToText()` throws `InvalidOperationException` with the message +"Attempt to serialize missing SPDX Annotation Type". +This scenario is tested by `SpdxAnnotationTypeExtensions_ToText_Missing`. diff --git a/docs/verification/spdx-model/spdx-checksum.md b/docs/verification/spdx-model/spdx-checksum.md new file mode 100644 index 0000000..2daa9dd --- /dev/null +++ b/docs/verification/spdx-model/spdx-checksum.md @@ -0,0 +1,88 @@ +## SpdxChecksum + +### Verification Approach + +SpdxChecksum is verified through automated unit tests using the MSTest framework. Tests are +located in `test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs`. Each test constructs +an SpdxChecksum instance directly and exercises the method under test with no mocked +dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquality**: Verifies that +SameComparer correctly identifies two SpdxChecksum instances as equal when algorithm and +value both match, and as distinct when either field differs. +This scenario is tested by `SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquality`. + +**SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse**: Verifies that the Same comparer +returns false when the first argument is null. +This scenario is tested by `SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse`. + +**SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse**: Verifies that the Same comparer +returns false when the second argument is null. +This scenario is tested by `SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse`. + +**SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue**: Verifies that the Same comparer +returns true when both arguments are null. +This scenario is tested by `SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue`. + +**SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInstance**: Verifies that a +deep copy produces a new SpdxChecksum instance with equal field values but a distinct object +reference. +This scenario is tested by `SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInstance`. + +**SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformation**: Verifies that +Enhance merges checksum data by adding missing fields from the source while preserving existing +field values on the target. +This scenario is tested by `SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformation`. + +**SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue**: Verifies that validation +reports an issue when the checksum algorithm field is set to Missing. +This scenario is tested by `SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue`. + +**SpdxChecksum_Validate_EmptyValue_ReportsValueIssue**: Verifies that validation reports an +issue when the checksum value field is empty. +This scenario is tested by `SpdxChecksum_Validate_EmptyValue_ReportsValueIssue`. + +**SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue**: Verifies that +validation reports an issue when the checksum algorithm field holds a numeric value that is +not a named member of the SpdxChecksumAlgorithm enumeration. +This scenario is tested by `SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue`. + +**SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_ReturnsCorrectEnumValues**: +Verifies that FromText correctly parses all recognized checksum algorithm strings +(case-insensitive) to their corresponding enum values. +This scenario is tested by `SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_ReturnsCorrectEnumValues`. + +**SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_ThrowsInvalidOperationException**: +Verifies that FromText throws `InvalidOperationException` when given an unrecognized checksum +algorithm string. +This scenario is tested by `SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_ThrowsInvalidOperationException`. + +**SpdxChecksumAlgorithmExtensions_FromText_EmptyString_ReturnsMissing**: Verifies that FromText +returns `Missing` when given an empty string. +This scenario is tested by `SpdxChecksumAlgorithmExtensions_FromText_EmptyString_ReturnsMissing`. + +**SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCorrectStrings**: Verifies +that ToText correctly converts all recognized checksum algorithm enum values to their SPDX text +representations. +This scenario is tested by `SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCorrectStrings`. + +**SpdxChecksumAlgorithmExtensions_ToText_OutOfRangeEnum_ThrowsInvalidOperationException**: +Verifies that ToText throws `InvalidOperationException` when given a numeric enum value that +does not correspond to any named algorithm member (e.g., `(SpdxChecksumAlgorithm)1000`). +This scenario is tested by `SpdxChecksumAlgorithmExtensions_ToText_OutOfRangeEnum_ThrowsInvalidOperationException`. + +**SpdxChecksumAlgorithmExtensions_ToText_MissingAlgorithm_ThrowsInvalidOperationException**: +Verifies that ToText throws `InvalidOperationException` when given the `Missing` sentinel +value, which must never be serialized to SPDX text form. +This scenario is tested by +`SpdxChecksumAlgorithmExtensions_ToText_MissingAlgorithm_ThrowsInvalidOperationException`. diff --git a/docs/verification/spdx-model/spdx-creation-information.md b/docs/verification/spdx-model/spdx-creation-information.md new file mode 100644 index 0000000..9d96a3f --- /dev/null +++ b/docs/verification/spdx-model/spdx-creation-information.md @@ -0,0 +1,63 @@ +## SpdxCreationInformation + +### Verification Approach + +SpdxCreationInformation is verified through automated unit tests using the MSTest framework. +Tests are located in +`test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs`. Each test constructs +an SpdxCreationInformation instance directly and exercises the method under test with no +mocked dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep +copy produces a new SpdxCreationInformation instance with equal field values but a distinct +object reference, confirming no shared state between original and copy. +This scenario is tested by +`SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance`. + +**SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that Enhance +merges creation information by adding missing fields from the source while preserving +existing field values on the target. +This scenario is tested by +`SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly`. + +**SpdxCreationInformation_Validate_ValidInformation_NoIssues**: Verifies that validation +reports no issues for a fully populated valid SpdxCreationInformation instance. +This scenario is tested by `SpdxCreationInformation_Validate_ValidInformation_NoIssues`. + +**SpdxCreationInformation_Validate_MissingCreators**: Verifies that validation reports an +issue when the Creators list is empty or absent. +This scenario is tested by `SpdxCreationInformation_Validate_MissingCreators`. + +**SpdxCreationInformation_Validate_InvalidCreator**: Verifies that validation reports an issue +when one or more entries in the Creators list do not conform to the required tool or +organization format. +This scenario is tested by `SpdxCreationInformation_Validate_InvalidCreator`. + +**SpdxCreationInformation_Validate_InvalidCreatedDate**: Verifies that validation reports an +issue when the Created timestamp is missing or does not conform to ISO 8601 date-time format. +This scenario is tested by `SpdxCreationInformation_Validate_InvalidCreatedDate`. + +**SpdxCreationInformation_Validate_InvalidVersion**: Verifies that validation reports an issue +when the LicenseListVersion field is present but does not conform to the expected version +format. +This scenario is tested by `SpdxCreationInformation_Validate_InvalidVersion`. + +**SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue**: Verifies that validation +does not report a date issue when the Created field is empty (empty is a permitted value for +partially-constructed documents). +This scenario is tested by `SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue`. + +**SpdxCreationInformation_Enhance_DuplicateCreators_DeduplicatesCreators**: Verifies that +Enhance deduplicates the Creators array when merging, ensuring no duplicate entries appear in +the result. +This scenario is tested by `SpdxCreationInformation_Enhance_DuplicateCreators_DeduplicatesCreators`. diff --git a/docs/verification/spdx-model/spdx-document.md b/docs/verification/spdx-model/spdx-document.md new file mode 100644 index 0000000..2a9cfcb --- /dev/null +++ b/docs/verification/spdx-model/spdx-document.md @@ -0,0 +1,92 @@ +## SpdxDocument + +### Verification Approach + +SpdxDocument is verified through automated unit tests using the MSTest framework. Tests are +located in `test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs`. Each test constructs +an SpdxDocument instance directly and exercises the method under test with no mocked +dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxDocument_GetRootPackages_CorrectPackages**: Verifies that GetRootPackages returns only +the packages that are the targets of DESCRIBES relationships from the document element. +This scenario is tested by `SpdxDocument_GetRootPackages_CorrectPackages`. + +**SpdxDocument_SameComparer_ComparesCorrectly**: Verifies that SameComparer correctly +identifies two SpdxDocument instances as equal when all fields match and as distinct when any +field differs. +This scenario is tested by `SpdxDocument_SameComparer_ComparesCorrectly`. + +**SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep copy produces +a new SpdxDocument instance with equal field values but a distinct object reference, including +all nested packages, files, snippets, and relationships. +This scenario is tested by `SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance`. + +**SpdxDocument_Validate_NoIssues**: Verifies that a fully populated valid SpdxDocument passes +all validation checks without reporting any issues. +This scenario is tested by `SpdxDocument_Validate_NoIssues`. + +**SpdxDocument_Validate_InvalidId**: Verifies that validation reports an issue when the +document SPDX-ID field is missing or does not conform to the required format. +This scenario is tested by `SpdxDocument_Validate_InvalidId`. + +**SpdxDocument_Validate_InvalidName**: Verifies that validation reports an issue when the +document name field is missing or empty. +This scenario is tested by `SpdxDocument_Validate_InvalidName`. + +**SpdxDocument_Validate_InvalidVersion**: Verifies that validation reports an issue when the +SPDX version field is missing or does not match the expected SPDX-2.x format. +This scenario is tested by `SpdxDocument_Validate_InvalidVersion`. + +**SpdxDocument_Validate_InvalidDataLicense**: Verifies that validation reports an issue when +the data license field is missing or is not set to the required CC0-1.0 value. +This scenario is tested by `SpdxDocument_Validate_InvalidDataLicense`. + +**SpdxDocument_Validate_InvalidNameSpace**: Verifies that validation reports an issue when the +document namespace field is missing or is not a valid URI. +This scenario is tested by `SpdxDocument_Validate_InvalidNameSpace`. + +**SpdxDocument_Validate_DuplicatePackageIds**: Verifies that validation reports an issue when +two or more packages in the document share the same SPDX-ID. +This scenario is tested by `SpdxDocument_Validate_DuplicatePackageIds`. + +**SpdxDocument_Validate_InvalidRelationship**: Verifies that validation reports an issue when +a relationship references an element ID that does not exist within the document. +This scenario is tested by `SpdxDocument_Validate_InvalidRelationship`. + +**SpdxDocument_Validate_NtiaIssues**: Verifies that validation reports issues when the +document does not satisfy NTIA minimum element requirements for an SBOM. +This scenario is tested by `SpdxDocument_Validate_NtiaIssues`. + +**SpdxDocument_GetAllElements_Correct**: Verifies that GetAllElements returns the combined +collection of all packages, files, and snippets within the document. +This scenario is tested by `SpdxDocument_GetAllElements_Correct`. + +**SpdxDocument_GetElement_Document_ReturnsDocumentElement**: Verifies that GetElement returns +the document element when queried by the document SPDX-ID. +This scenario is tested by `SpdxDocument_GetElement_Document_ReturnsDocumentElement`. + +**SpdxDocument_GetElement_File_ReturnsFileElement**: Verifies that GetElement returns a file +element when queried by a file SPDX-ID. +This scenario is tested by `SpdxDocument_GetElement_File_ReturnsFileElement`. + +**SpdxDocument_GetElement_Package_ReturnsPackageElement**: Verifies that GetElement returns a +package element when queried by a package SPDX-ID. +This scenario is tested by `SpdxDocument_GetElement_Package_ReturnsPackageElement`. + +**SpdxDocument_GetElement_Snippet_ReturnsSnippetElement**: Verifies that GetElement returns a +snippet element when queried by a snippet SPDX-ID. +This scenario is tested by `SpdxDocument_GetElement_Snippet_ReturnsSnippetElement`. + +**SpdxDocument_Validate_InvalidAnnotation**: Verifies that validation reports an issue when +an annotation within the document contains invalid fields. +This scenario is tested by `SpdxDocument_Validate_InvalidAnnotation`. diff --git a/docs/verification/spdx-model/spdx-element.md b/docs/verification/spdx-model/spdx-element.md new file mode 100644 index 0000000..4f3aa16 --- /dev/null +++ b/docs/verification/spdx-model/spdx-element.md @@ -0,0 +1,27 @@ +## SpdxElement + +### Verification Approach + +SpdxElement is verified through automated unit tests using the MSTest framework. Tests are +located in `test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs`. Each test exercises +the element ID validation logic directly with no mocked dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxElement_Id_ValidFormat_PassesValidation**: Verifies that an element with a properly +formatted SPDX-ID (matching the SPDXRef- prefix pattern) passes validation without reporting +any issues. +This scenario is tested by `SpdxElement_Id_ValidFormat_PassesValidation`. + +**SpdxElement_Id_InvalidFormat_ReportsValidationIssue**: Verifies that an element with a +malformed SPDX-ID (missing the SPDXRef- prefix or containing invalid characters) is reported +as a validation issue. +This scenario is tested by `SpdxElement_Id_InvalidFormat_ReportsValidationIssue`. diff --git a/docs/verification/spdx-model/spdx-external-document-reference.md b/docs/verification/spdx-model/spdx-external-document-reference.md new file mode 100644 index 0000000..13ea056 --- /dev/null +++ b/docs/verification/spdx-model/spdx-external-document-reference.md @@ -0,0 +1,50 @@ +## SpdxExternalDocumentReference + +### Verification Approach + +SpdxExternalDocumentReference is verified through automated unit tests using the MSTest +framework. Tests are located in +`test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs`. Each test +constructs an SpdxExternalDocumentReference instance directly and exercises the method under +test with no mocked dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxExternalDocumentReference_SameComparer_ComparesCorrectly**: Verifies the comparer +considers two instances equal when their `Document` URI matches, and distinct when the URI +differs (regardless of `ExternalDocumentId`). +This scenario is tested by +`SpdxExternalDocumentReference_SameComparer_ComparesCorrectly`. + +**SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a +deep copy produces a new SpdxExternalDocumentReference with equal field values but a distinct +object reference. +This scenario is tested by +`SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstance`. + +**SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that +Enhance merges external document reference data by adding missing fields from the source +while preserving existing values on the target. +This scenario is tested by +`SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrectly`. + +**SpdxExternalDocumentReference_Validate_MissingId**: Verifies that validation reports an +issue when the external document reference ID field is missing or empty. +This scenario is tested by `SpdxExternalDocumentReference_Validate_MissingId`. + +**SpdxExternalDocumentReference_Validate_MissingDocument**: Verifies that validation reports +an issue when the referenced external document URI is missing or empty. +This scenario is tested by `SpdxExternalDocumentReference_Validate_MissingDocument`. + +**SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue**: Verifies that +validation reports an issue when the external document reference contains an invalid checksum +(missing algorithm or empty value). +This scenario is tested by `SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue`. diff --git a/docs/verification/spdx-model/spdx-external-reference.md b/docs/verification/spdx-model/spdx-external-reference.md new file mode 100644 index 0000000..c2651df --- /dev/null +++ b/docs/verification/spdx-model/spdx-external-reference.md @@ -0,0 +1,68 @@ +## SpdxExternalReference + +### Verification Approach + +SpdxExternalReference is verified through automated unit tests using the MSTest framework. +Tests are located in +`test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs`. Each test constructs +an SpdxExternalReference instance directly and exercises the method under test with no +mocked dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxExternalReference_SameComparer_ComparesCorrectly**: Verifies that SameComparer +correctly identifies two SpdxExternalReference instances as equal when all fields match and +as distinct when any field differs. +This scenario is tested by `SpdxExternalReference_SameComparer_ComparesCorrectly`. + +**SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep copy +produces a new SpdxExternalReference with equal field values but a distinct object reference. +This scenario is tested by `SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance`. + +**SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that Enhance +merges external reference data by adding missing fields from the source while preserving +existing values on the target. +This scenario is tested by +`SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly`. + +**SpdxExternalReference_Validate_InvalidCategory**: Verifies that validation reports an issue +when the reference category is set to an unrecognized value. +This scenario is tested by `SpdxExternalReference_Validate_InvalidCategory`. + +**SpdxExternalReference_Validate_InvalidType**: Verifies that validation reports an issue when +the reference type does not conform to the expected format for the given category. +This scenario is tested by `SpdxExternalReference_Validate_InvalidType`. + +**SpdxExternalReference_Validate_InvalidLocator**: Verifies that validation reports an issue +when the reference locator field is missing or empty. +This scenario is tested by `SpdxExternalReference_Validate_InvalidLocator`. + +**SpdxReferenceCategoryExtensions_FromText_Valid**: Verifies that FromText correctly parses a +recognized reference category string to its corresponding enum value. +This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_Valid`. + +**SpdxReferenceCategoryExtensions_FromText_Invalid**: Verifies that `FromText` throws +`InvalidOperationException` with a message identifying the unsupported value when given an +unrecognized reference category string. +This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_Invalid`. + +**SpdxReferenceCategoryExtensions_ToText_Valid**: Verifies that ToText correctly converts a +recognized reference category enum value to its SPDX text representation. +This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_Valid`. + +**SpdxReferenceCategoryExtensions_ToText_InvalidCategory**: Verifies that ToText throws +`InvalidOperationException` with the unsupported-category message when called with an +unrecognized enum value. +This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_InvalidCategory`. + +**SpdxReferenceCategoryExtensions_ToText_MissingCategory**: Verifies that `ToText` throws +`InvalidOperationException` with a specific message when called with `SpdxReferenceCategory.Missing`. +This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_MissingCategory`. diff --git a/docs/verification/spdx-model/spdx-extracted-licensing-info.md b/docs/verification/spdx-model/spdx-extracted-licensing-info.md new file mode 100644 index 0000000..2b2ad7e --- /dev/null +++ b/docs/verification/spdx-model/spdx-extracted-licensing-info.md @@ -0,0 +1,48 @@ +## SpdxExtractedLicensingInfo + +### Verification Approach + +SpdxExtractedLicensingInfo is verified through automated unit tests using the MSTest +framework. Tests are located in +`test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs`. Each test +constructs an SpdxExtractedLicensingInfo instance directly and exercises the method under +test with no mocked dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly**: Verifies that SameComparer +correctly identifies two SpdxExtractedLicensingInfo instances as equal when all fields match +and as distinct when any field differs. +This scenario is tested by `SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly`. + +**SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep +copy produces a new SpdxExtractedLicensingInfo instance with equal field values but a +distinct object reference, including array independence for CrossReferences. +This scenario is tested by +`SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance`. + +**SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that +Enhance merges extracted licensing information by adding missing fields from the source while +preserving existing values on the target. +This scenario is tested by +`SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly`. + +**SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues**: Verifies that a valid +extracted licensing info with both LicenseId and ExtractedText populated returns no issues. +This scenario is tested by `SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues`. + +**SpdxExtractedLicensingInfo_Validate_InvalidLicenseId**: Verifies that validation reports an +issue when the LicenseId field is empty. +This scenario is tested by `SpdxExtractedLicensingInfo_Validate_InvalidLicenseId`. + +**SpdxExtractedLicensingInfo_Validate_InvalidExtractedText**: Verifies that validation reports +an issue when the extracted license text field is missing or empty. +This scenario is tested by `SpdxExtractedLicensingInfo_Validate_InvalidExtractedText`. diff --git a/docs/verification/spdx-model/spdx-file.md b/docs/verification/spdx-model/spdx-file.md new file mode 100644 index 0000000..e49597d --- /dev/null +++ b/docs/verification/spdx-model/spdx-file.md @@ -0,0 +1,65 @@ +## SpdxFile + +### Verification Approach + +SpdxFile is verified through automated unit tests using the MSTest framework. Tests are +located in `test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs`. Each test constructs an +SpdxFile instance directly and exercises the method under test with no mocked dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxFile_SameComparer_ComparesCorrectly**: Verifies that SameComparer correctly identifies +two SpdxFile instances as equal when all fields match and as distinct when any field differs. +This scenario is tested by `SpdxFile_SameComparer_ComparesCorrectly`. + +**SpdxFile_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep copy produces a +new SpdxFile instance with equal field values but a distinct object reference, including all +nested checksums and annotations. +This scenario is tested by `SpdxFile_DeepCopy_CreatesEqualButDistinctInstance`. + +**SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that Enhance merges file +data by adding missing fields from the source while preserving existing field values on the +target. +This scenario is tested by `SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly`. + +**SpdxFile_Validate_ReportsInvalidFileId**: Verifies that validation reports an issue when +the file SPDX-ID does not conform to the required SPDXRef- prefix format. +This scenario is tested by `SpdxFile_Validate_ReportsInvalidFileId`. + +**SpdxFile_Validate_ReportsInvalidFileName**: Verifies that validation reports an issue when +the FileName does not start with the required "./" prefix. +This scenario is tested by `SpdxFile_Validate_ReportsInvalidFileName`. + +**SpdxFile_Validate_ReportsWhenSha1ChecksumMissing**: Verifies that validation reports an +issue when no SHA1 checksum is present in the Checksums array. +This scenario is tested by `SpdxFile_Validate_ReportsWhenSha1ChecksumMissing`. + +**SpdxFile_Validate_Success**: Verifies that a fully populated valid SpdxFile passes all +validation checks without reporting any issues. +This scenario is tested by `SpdxFile_Validate_Success`. + +**SpdxFileTypeExtensions_FromText_Valid**: Verifies that FromText correctly parses a +recognized file type string to its corresponding enum value, and that matching is +case-insensitive. +This scenario is tested by `SpdxFileTypeExtensions_FromText_Valid`. + +**SpdxFileTypeExtensions_FromText_Invalid**: Verifies that `FromText` throws +`InvalidOperationException` with a message identifying the unsupported value when given an +unrecognized file type string. +This scenario is tested by `SpdxFileTypeExtensions_FromText_Invalid`. + +**SpdxFileTypeExtensions_ToText_Valid**: Verifies that ToText correctly converts a recognized +file type enum value to its SPDX text representation. +This scenario is tested by `SpdxFileTypeExtensions_ToText_Valid`. + +**SpdxFileTypeExtensions_ToText_Invalid**: Verifies that `ToText` throws +`InvalidOperationException` when given an unsupported file type enum value. +This scenario is tested by `SpdxFileTypeExtensions_ToText_Invalid`. diff --git a/docs/verification/spdx-model/spdx-helpers.md b/docs/verification/spdx-model/spdx-helpers.md new file mode 100644 index 0000000..056f4eb --- /dev/null +++ b/docs/verification/spdx-model/spdx-helpers.md @@ -0,0 +1,48 @@ +## SpdxHelpers + +### Verification Approach + +SpdxHelpers is verified through automated unit tests using the MSTest framework. +Tests are located in +`test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs`. Each test exercises the helper +method directly with no mocked dependencies. Additional coverage is provided implicitly +through the unit tests of dependent classes. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue**: Verifies that `IsValidSpdxDateTime` +returns `true` when passed a null value, since null represents a not-set field which is valid. +This scenario is tested by `SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue`. + +**SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue**: Verifies that `IsValidSpdxDateTime` +returns `true` when passed an empty string, since an empty string represents a not-set field +which is valid. +This scenario is tested by `SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue`. + +**SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue**: Verifies that `IsValidSpdxDateTime` +returns `true` when passed a correctly formatted ISO 8601 UTC timestamp such as +`"2024-01-01T00:00:00Z"`. +This scenario is tested by `SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue`. + +**SpdxHelpers_IsValidSpdxDateTime_InvalidFormat_ReturnsFalse**: Verifies that +`IsValidSpdxDateTime` returns `false` when passed a string that does not match the ISO 8601 +UTC format. +This scenario is tested by `SpdxHelpers_IsValidSpdxDateTime_InvalidFormat_ReturnsFalse`. + +**SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue**: Verifies +that `EnhanceString` returns the concrete value when given a mix of a concrete value and +`NOASSERTION`. +This scenario is tested by +`SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue`. + +**SpdxHelpers_EnhanceString_NullInputs_ReturnsNull**: Verifies that `EnhanceString` returns +`null` when all inputs are null. +This scenario is tested by `SpdxHelpers_EnhanceString_NullInputs_ReturnsNull`. diff --git a/docs/verification/spdx-model/spdx-license-element.md b/docs/verification/spdx-model/spdx-license-element.md new file mode 100644 index 0000000..23732ff --- /dev/null +++ b/docs/verification/spdx-model/spdx-license-element.md @@ -0,0 +1,21 @@ +## SpdxLicenseElement + +### Verification Approach + +N/A — SpdxLicenseElement is an abstract base class that defines common license-related +properties shared by concrete license classes. No dedicated unit test file exists for +SpdxLicenseElement. Correctness is verified implicitly through the unit tests of its +concrete subclasses. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests for subclass units pass with zero failures. + +### Test Scenarios + +N/A — no dedicated unit tests exist; SpdxLicenseElement is verified implicitly through +higher-level tests of its concrete subclasses. diff --git a/docs/verification/spdx-model/spdx-model.md b/docs/verification/spdx-model/spdx-model.md new file mode 100644 index 0000000..08077c2 --- /dev/null +++ b/docs/verification/spdx-model/spdx-model.md @@ -0,0 +1,56 @@ +## SpdxModel + +### Verification Approach + +The SpdxModel library is verified through automated unit and integration tests using the MSTest +framework. Tests are organized in `test/DemaConsulting.SpdxModel.Tests/`. Unit tests verify +individual data model classes in isolation; integration tests verify the IO subsystem end-to-end +and the Transform subsystem with real document instances. No external dependencies are mocked. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully**: Verifies that the library +successfully reads and parses an SPDX 2.2 example JSON document, returning a non-null +SpdxDocument without throwing exceptions. + +**SpdxModel_ReadSpdxJson_Spdx23Example_ParsesSuccessfully**: Verifies that the library +successfully reads and parses an SPDX 2.3 example JSON document, returning a non-null +SpdxDocument without throwing exceptions. + +**SpdxModel_ReadSpdxJson_Spdx22Example_PassesValidation**: Verifies that the document parsed +from the SPDX 2.2 example JSON passes all validation checks with no reported issues. + +**SpdxModel_ReadSpdxJson_Spdx23Example_PassesValidation**: Verifies that the document parsed +from the SPDX 2.3 example JSON passes all validation checks with no reported issues. + +**SpdxModel_ReadSpdxJson_Spdx23Example_RootPackagesIdentified**: Verifies that the root +packages are correctly identified in the SPDX 2.3 example document after parsing, confirming +DESCRIBES relationship traversal works as expected. + +**SpdxModel_ReadSpdxJson_Spdx23Example_DeepCopyProducesEquivalentDocument**: Verifies that a +deep copy of the parsed SPDX 2.3 document is structurally equal to the original, confirming +that all nested objects are fully duplicated. + +**SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds**: Verifies that an SPDX 2.3 +document serialized to JSON and then deserialized produces a document equivalent to the +original, confirming end-to-end serialization fidelity. + +**SpdxModel_Deserialize_MalformedJson_ThrowsJsonException**: Verifies that passing malformed +JSON to `Spdx2JsonDeserializer.Deserialize` throws a `JsonException` rather than returning a +partially-populated document. + +**SpdxModel_Validate_InvalidDocument_ReportsIssues**: Verifies that calling `Validate()` on a +deliberately incomplete SPDX document produces a non-empty issues list with the expected +validation error messages. + +**SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNullable**: Verifies that +required fields on key SPDX data model types are non-nullable string types with default empty +values, and that optional fields are nullable. diff --git a/docs/verification/spdx-model/spdx-package-verification-code.md b/docs/verification/spdx-model/spdx-package-verification-code.md new file mode 100644 index 0000000..91688b8 --- /dev/null +++ b/docs/verification/spdx-model/spdx-package-verification-code.md @@ -0,0 +1,51 @@ +## SpdxPackageVerificationCode + +### Verification Approach + +SpdxPackageVerificationCode is verified through automated unit tests using the MSTest +framework. Tests are located in +`test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs`. Each test +constructs an SpdxPackageVerificationCode instance directly and exercises the method under +test with no mocked dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxPackageVerificationCode_SameComparer_SameValueDifferentExcludedFiles_ReturnsEqual**: Verifies that SameComparer +correctly identifies two SpdxPackageVerificationCode instances as equal when their Value +fields are identical (even if ExcludedFiles differs), and as distinct when Values differ. +Equality is determined by Value alone. +This scenario is tested by `SpdxPackageVerificationCode_SameComparer_SameValueDifferentExcludedFiles_ReturnsEqual`. + +**SpdxPackageVerificationCode_DeepCopy_FullyPopulatedCode_CreatesEqualButDistinctCopy**: Verifies that a +deep copy produces a new SpdxPackageVerificationCode instance with equal field values but a +distinct object reference. +This scenario is tested by +`SpdxPackageVerificationCode_DeepCopy_FullyPopulatedCode_CreatesEqualButDistinctCopy`. + +**SpdxPackageVerificationCode_Enhance_MissingFields_MergesCorrectly**: Verifies that +Enhance merges verification code data by adding missing fields from the source while +preserving existing values on the target. +This scenario is tested by +`SpdxPackageVerificationCode_Enhance_MissingFields_MergesCorrectly`. + +**SpdxPackageVerificationCode_Validate_InvalidValue_ReportsIssue**: Verifies that validation reports an +issue when the verification code value field is missing or does not conform to the required +SHA1 hash format. +This scenario is tested by `SpdxPackageVerificationCode_Validate_InvalidValue_ReportsIssue`. + +**SpdxPackageVerificationCode_Validate_ValidValue_ReportsNoIssues**: Verifies that validation +reports no issues when the verification code value is a valid 40-character SHA1 hex digest. +This scenario is tested by `SpdxPackageVerificationCode_Validate_ValidValue_ReportsNoIssues`. + +**SpdxPackageVerificationCode_Validate_NonHexValue_ReportsIssue**: Verifies that validation +reports an issue when the verification code value is 40 characters but contains non-hexadecimal +characters. +This scenario is tested by `SpdxPackageVerificationCode_Validate_NonHexValue_ReportsIssue`. diff --git a/docs/verification/spdx-model/spdx-package.md b/docs/verification/spdx-model/spdx-package.md new file mode 100644 index 0000000..0382ffc --- /dev/null +++ b/docs/verification/spdx-model/spdx-package.md @@ -0,0 +1,87 @@ +## SpdxPackage + +### Verification Approach + +SpdxPackage is verified through automated unit tests using the MSTest framework. Tests are +located in `test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs`. Each test constructs +an SpdxPackage instance directly and exercises the method under test with no mocked +dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxPackage_SameComparer_ComparesCorrectly**: Verifies that SameComparer correctly +identifies two SpdxPackage instances as equal when all fields match and as distinct when any +field differs. +This scenario is tested by `SpdxPackage_SameComparer_ComparesCorrectly`. + +**SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep copy produces +a new SpdxPackage instance with equal field values but a distinct object reference, including +all nested checksums, verification code, external references, and annotations. Also verifies +that the VerificationCode is deep-copied (value equality but reference distinctness). +This scenario is tested by `SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance`. + +**SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly**: Verifies that Enhance merges package +data by adding missing fields from the source while preserving existing values on the target. +This scenario is tested by `SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly`. + +**SpdxPackage_Validate_Success**: Verifies that a fully populated valid SpdxPackage passes +all validation checks without reporting any issues. +This scenario is tested by `SpdxPackage_Validate_Success`. + +**SpdxPackage_Validate_MissingPackageName**: Verifies that validation reports an issue when +the package name field is missing or empty. +This scenario is tested by `SpdxPackage_Validate_MissingPackageName`. + +**SpdxPackage_Validate_InvalidPackageId**: Verifies that validation reports an issue when +the package SPDX-ID does not conform to the required SPDXRef- prefix format. +This scenario is tested by `SpdxPackage_Validate_InvalidPackageId`. + +**SpdxPackage_Validate_MissingDownload**: Verifies that validation reports an issue when the +download location field is missing or empty. +This scenario is tested by `SpdxPackage_Validate_MissingDownload`. + +**SpdxPackage_Validate_InvalidSupplier**: Verifies that validation reports an issue when the +supplier field is present but does not conform to the required organization or tool format. +This scenario is tested by `SpdxPackage_Validate_InvalidSupplier`. + +**SpdxPackage_Validate_InvalidOriginator**: Verifies that validation reports an issue when +the originator field is present but does not conform to the required organization or tool +format. +This scenario is tested by `SpdxPackage_Validate_InvalidOriginator`. + +**SpdxPackage_Validate_InvalidReleaseDate**: Verifies that validation reports an issue when +the release date field is present but does not conform to ISO 8601 date-time format. +This scenario is tested by `SpdxPackage_Validate_InvalidReleaseDate`. + +**SpdxPackage_Validate_InvalidBuiltDate**: Verifies that validation reports an issue when the +built date field is present but does not conform to ISO 8601 date-time format. +This scenario is tested by `SpdxPackage_Validate_InvalidBuiltDate`. + +**SpdxPackage_Validate_InvalidValidUntilDate**: Verifies that validation reports an issue when +the valid-until date field is present but does not conform to ISO 8601 date-time format. +This scenario is tested by `SpdxPackage_Validate_InvalidValidUntilDate`. + +**SpdxPackage_Validate_InvalidAnnotation**: Verifies that validation reports an issue when an +annotation within the package contains invalid fields. +This scenario is tested by `SpdxPackage_Validate_InvalidAnnotation`. + +**SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue**: Verifies that validation +reports an issue when the HasFiles array references a file ID that does not exist in the +owning document. +This scenario is tested by `SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue`. + +**SpdxPackage_ValidateNtia_MissingSupplier_ReportsIssue**: Verifies that NTIA validation +reports an issue when the package supplier field is absent. +This scenario is tested by `SpdxPackage_ValidateNtia_MissingSupplier_ReportsIssue`. + +**SpdxPackage_ValidateNtia_MissingVersion_ReportsIssue**: Verifies that NTIA validation +reports an issue when the package version field is absent. +This scenario is tested by `SpdxPackage_ValidateNtia_MissingVersion_ReportsIssue`. diff --git a/docs/verification/spdx-model/spdx-relationship.md b/docs/verification/spdx-model/spdx-relationship.md new file mode 100644 index 0000000..acc9102 --- /dev/null +++ b/docs/verification/spdx-model/spdx-relationship.md @@ -0,0 +1,68 @@ +## SpdxRelationship + +### Verification Approach + +SpdxRelationship is verified through automated unit tests using the MSTest framework. Tests +are located in `test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs`. Each test +constructs an SpdxRelationship instance directly and exercises the method under test with no +mocked dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqual**: Verifies that SameComparer correctly +identifies two SpdxRelationship instances as equal when all fields match and as distinct when +any field differs. +This scenario is tested by `SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqual`. + +**SpdxRelationship_SameElementsComparer_SameElementsDifferentType_ReturnsEqual**: Verifies that +SameElementsComparer correctly identifies two relationships as equal based solely on the +source and target element IDs, ignoring relationship type. +This scenario is tested by `SpdxRelationship_SameElementsComparer_SameElementsDifferentType_ReturnsEqual`. + +**SpdxRelationship_DeepCopy_FullyPopulatedRelationship_CreatesEqualButDistinctCopy**: Verifies that a deep copy +produces a new SpdxRelationship instance with equal field values but a distinct object +reference. +This scenario is tested by `SpdxRelationship_DeepCopy_FullyPopulatedRelationship_CreatesEqualButDistinctCopy`. + +**SpdxRelationship_Enhance_MatchingAndNewRelationships_MergesCorrectly**: Verifies that Enhance merges +relationship data by adding missing fields from the source while preserving existing values +on the target. +This scenario is tested by `SpdxRelationship_Enhance_MatchingAndNewRelationships_MergesCorrectly`. + +**SpdxRelationship_Validate_MissingRelationshipId_ReportsIssue**: Verifies that validation reports an issue when the +relationship SPDX-ID field is missing or empty. +This scenario is tested by `SpdxRelationship_Validate_MissingRelationshipId_ReportsIssue`. + +**SpdxRelationship_Validate_MissingRelatedElementId_ReportsIssue**: Verifies that validation reports an issue +when the related element ID field is missing or empty. +This scenario is tested by `SpdxRelationship_Validate_MissingRelatedElementId_ReportsIssue`. + +**SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue**: Verifies that validation reports an issue +when the relationship type field is missing or set to an unrecognized value. +This scenario is tested by `SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue`. + +**SpdxRelationshipTypeExtensions_FromText_KnownText_ReturnsMappedEnum**: Verifies that FromText correctly parses a +recognized relationship type string to its corresponding enum value. +This scenario is tested by `SpdxRelationshipTypeExtensions_FromText_KnownText_ReturnsMappedEnum`. + +**SpdxRelationshipTypeExtensions_FromText_UnknownText_ThrowsInvalidOperationException**: Verifies that FromText throws an +InvalidOperationException with a descriptive message when given an unrecognized relationship +type string. +This scenario is tested by `SpdxRelationshipTypeExtensions_FromText_UnknownText_ThrowsInvalidOperationException`. + +**SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText**: Verifies that ToText correctly converts a +recognized relationship type enum value to its SPDX text representation. +This scenario is tested by `SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText`. + +**SpdxRelationshipTypeExtensions_ToText_UnknownEnum_ThrowsInvalidOperationException**: Verifies that ToText throws an +InvalidOperationException with a descriptive message when given an unknown (out-of-range) +relationship type enum value. +This scenario is tested by `SpdxRelationshipTypeExtensions_ToText_UnknownEnum_ThrowsInvalidOperationException`. diff --git a/docs/verification/spdx-model/spdx-snippet.md b/docs/verification/spdx-model/spdx-snippet.md new file mode 100644 index 0000000..23946b7 --- /dev/null +++ b/docs/verification/spdx-model/spdx-snippet.md @@ -0,0 +1,65 @@ +## SpdxSnippet + +### Verification Approach + +SpdxSnippet is verified through automated unit tests using the MSTest framework. Tests are +located in `test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs`. Each test constructs +an SpdxSnippet instance directly and exercises the method under test with no mocked +dependencies. + +### Test Environment + +N/A - standard test environment. + +### Acceptance Criteria + +All automated tests pass with zero failures. + +### Test Scenarios + +**SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual**: Verifies that SameComparer correctly +identifies two SpdxSnippet instances as equal when all fields match and as distinct when any +field differs. +This scenario is tested by `SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual`. + +**SpdxSnippet_DeepCopy_FullyPopulatedSnippet_CreatesEqualButDistinctCopy**: Verifies that a deep copy produces +a new SpdxSnippet instance with equal field values but a distinct object reference, including +all nested byte and line ranges. +This scenario is tested by `SpdxSnippet_DeepCopy_FullyPopulatedSnippet_CreatesEqualButDistinctCopy`. + +**SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly**: Verifies that Enhance merges +snippet data by adding missing fields from the source while preserving existing values on +the target. +This scenario is tested by `SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly`. + +**SpdxSnippet_Validate_InvalidSnippetId_ReportsIssue**: Verifies that validation reports an issue +when the snippet SPDX-ID does not conform to the required SPDXRef- prefix format. +This scenario is tested by `SpdxSnippet_Validate_InvalidSnippetId_ReportsIssue`. + +**SpdxSnippet_Validate_AllRequiredFieldsPresent_ReturnsNoIssues**: Verifies that a fully populated valid SpdxSnippet passes +all validation checks without reporting any issues. +This scenario is tested by `SpdxSnippet_Validate_AllRequiredFieldsPresent_ReturnsNoIssues`. + +**SpdxSnippet_Validate_InvalidAnnotation_ReportsIssue**: Verifies that validation reports an issue when an +annotation within the snippet contains invalid fields. +This scenario is tested by `SpdxSnippet_Validate_InvalidAnnotation_ReportsIssue`. + +**SpdxSnippet_Validate_EmptySnippetFromFile_ReportsIssue**: Verifies that validation reports an +issue when the snippet-from-file field is empty. +This scenario is tested by `SpdxSnippet_Validate_EmptySnippetFromFile_ReportsIssue`. + +**SpdxSnippet_Validate_InvalidByteStart_ReportsIssue**: Verifies that validation reports an issue +when the snippet byte range start is less than 1. +This scenario is tested by `SpdxSnippet_Validate_InvalidByteStart_ReportsIssue`. + +**SpdxSnippet_Validate_InvalidByteEnd_ReportsIssue**: Verifies that validation reports an issue +when the snippet byte range end is less than the byte range start. +This scenario is tested by `SpdxSnippet_Validate_InvalidByteEnd_ReportsIssue`. + +**SpdxSnippet_Validate_EmptyConcludedLicense_ReportsIssue**: Verifies that validation reports an +issue when the concluded license field is empty. +This scenario is tested by `SpdxSnippet_Validate_EmptyConcludedLicense_ReportsIssue`. + +**SpdxSnippet_Validate_EmptyCopyrightText_ReportsIssue**: Verifies that validation reports an issue +when the copyright text field is empty. +This scenario is tested by `SpdxSnippet_Validate_EmptyCopyrightText_ReportsIssue`. diff --git a/docs/verification/spdx-model/transform/spdx-relationships.md b/docs/verification/spdx-model/transform/spdx-relationships.md new file mode 100644 index 0000000..58559e1 --- /dev/null +++ b/docs/verification/spdx-model/transform/spdx-relationships.md @@ -0,0 +1,38 @@ +### SpdxRelationships + +#### Verification Approach + +SpdxRelationships is verified through automated unit tests using the MSTest framework. Tests +are located in `test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs`. +Each test constructs an SpdxDocument with a known set of relationships and exercises the +SpdxRelationships methods directly with no mocked dependencies. + +#### Test Environment + +N/A - standard test environment. + +#### Acceptance Criteria + +All automated tests pass with zero failures. + +#### Test Scenarios + +**SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException**: Verifies that attempting to add a single relationship where the source element ID does not exist in the document throws `ArgumentException` with a message identifying the missing element and leaves the document unmodified. + +**SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentException**: Verifies that attempting to add a single relationship where the related element ID does not exist in the document (and is neither NOASSERTION nor DocumentRef-prefixed) throws `ArgumentException` and leaves the document unmodified. + +**SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship**: Verifies that adding a single valid relationship between two existing elements results in the relationship being appended to the document's relationships collection. + +**SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRelationship**: Verifies that adding a relationship that is identical to one already present in the document enhances the existing entry rather than creating a duplicate. + +**SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship**: Verifies that a relationship whose target element is `NOASSERTION` is accepted as valid and added to the document without error. + +**SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship**: Verifies that a relationship whose target element uses the `DocumentRef-` external-reference prefix is accepted as valid and added to the document without error. + +**SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship**: Verifies that the batch Add overload with a single-element array appends the relationship to the document. + +**SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRelationships**: Verifies that passing duplicate relationships in a single batch call results in only one entry being added to the document. + +**SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRelationships**: Verifies that invoking the batch Add with `replace=true` removes pre-existing relationships between the same source and target elements before adding the new ones. + +**SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified**: Verifies that when any relationship in a batch is invalid (e.g., missing source ID), an `ArgumentException` is thrown and the document's relationships collection is left in its original state — no relationships are added or removed. diff --git a/docs/verification/spdx-model/transform/transform.md b/docs/verification/spdx-model/transform/transform.md new file mode 100644 index 0000000..93b4110 --- /dev/null +++ b/docs/verification/spdx-model/transform/transform.md @@ -0,0 +1,50 @@ +### Transform + +#### Verification Approach + +The Transform subsystem is verified through automated integration tests using the MSTest +framework. Tests are located in +`test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs`. Integration tests +verify Transform operations using real SpdxDocument instances with no mocked dependencies. + +#### Test Environment + +N/A - standard test environment. + +#### Acceptance Criteria + +All integration tests pass with zero failures. + +#### Test Scenarios + +**SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists**: Verifies that adding +a relationship to an SpdxDocument through the Transform subsystem results in the relationship +being present in the document's relationship collection after the operation. + +**SpdxModelTransform_AddRelationship_InvalidSourceId_ThrowsArgumentException**: Verifies that +providing a source element ID that does not exist in the document causes `AddRelationship` to +throw `ArgumentException`. + +**SpdxModelTransform_AddRelationship_InvalidTargetId_ThrowsArgumentException**: Verifies that +providing a target element ID that does not exist in the document (and is neither `NOASSERTION` +nor `DocumentRef-`-prefixed) causes `AddRelationship` to throw `ArgumentException`. + +**SpdxModelTransform_AddRelationship_Duplicate_EnhancesExistingRelationship**: Verifies that +adding the same relationship twice does not duplicate the entry — the second add enhances the +existing relationship rather than appending a new one. + +**SpdxModelTransform_AddRelationship_Replace_RemovesPreExistingRelationships**: Verifies that +the batch Add overload with `replace=true` removes all pre-existing relationships between the +same source and target elements before adding the new relationship. + +**SpdxModelTransform_AddRelationship_BatchMultiple_AddsAllRelationships**: Verifies that +passing multiple distinct relationships in a single batch call results in all of them being +appended to the document. + +**SpdxModelTransform_AddRelationship_NoAssertionTarget_AddsRelationship**: Verifies that a +relationship with `NOASSERTION` as the target element is accepted as valid and added without +error. + +**SpdxModelTransform_AddRelationship_DocumentRefTarget_AddsRelationship**: Verifies that a +relationship whose target uses the `DocumentRef-` external-reference prefix is accepted as +valid and added without error. diff --git a/docs/verification/title.txt b/docs/verification/title.txt new file mode 100644 index 0000000..e67288b --- /dev/null +++ b/docs/verification/title.txt @@ -0,0 +1,14 @@ +--- +title: SpdxModel Verification Design +subtitle: SPDX document model for .NET +author: DEMA Consulting +description: Verification Design Document for SpdxModel +lang: en-US +keywords: + - Verification + - Testing + - SpdxModel + - C# + - .NET + - SPDX +--- diff --git a/requirements.yaml b/requirements.yaml index c64db5b..3287605 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -8,6 +8,7 @@ includes: - docs/reqstream/spdx-model/spdx-model.yaml - docs/reqstream/spdx-model/platform-requirements.yaml - docs/reqstream/spdx-model/io/io.yaml + - docs/reqstream/spdx-model/io/spdx-constants.yaml - docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml - docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml - docs/reqstream/spdx-model/transform/transform.yaml diff --git a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs index dd453b2..fd04c15 100644 --- a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs +++ b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonDeserializer.cs @@ -26,14 +26,22 @@ namespace DemaConsulting.SpdxModel.IO; /// /// JSON Deserializer class /// +/// +/// This class is stateless: all methods are static and carry no instance state, making +/// it safe for concurrent calls from multiple threads. Unrecognised JSON fields are +/// silently ignored during deserialization. +/// public static class Spdx2JsonDeserializer { /// /// Deserialize SPDX Document /// + /// + /// Parses the JSON string into a DOM and delegates to . + /// /// Json string /// SPDX Document - /// thrown on error + /// Thrown when is not valid JSON text or does not represent a JSON object. public static SpdxDocument Deserialize(string json) { // Deserialize the Json @@ -47,6 +55,10 @@ public static SpdxDocument Deserialize(string json) /// /// Deserialize the SPDX Document /// + /// + /// Maps all SPDX 2.x JSON top-level fields to the object model. + /// Fields absent from the JSON produce empty strings, empty arrays, or null optional values. + /// /// Json Document Node /// SPDX Document public static SpdxDocument DeserializeDocument(JsonNode json) @@ -76,8 +88,12 @@ public static SpdxDocument DeserializeDocument(JsonNode json) /// /// Deserialize SPDX Creation Information /// + /// + /// Returns a default-valued when + /// is null (i.e., the creationInfo key is absent from the document). + /// /// Json Creation Information Node - /// SPDX Document + /// Populated ; fields absent in the JSON default to empty strings or empty arrays. public static SpdxCreationInformation DeserializeCreationInformation(JsonNode? json) { return new SpdxCreationInformation @@ -92,6 +108,9 @@ public static SpdxCreationInformation DeserializeCreationInformation(JsonNode? j /// /// Deserialize SPDX External Document References /// + /// + /// Returns an empty array when is null. + /// /// Json External Document References Array /// SPDX External Document References public static SpdxExternalDocumentReference[] DeserializeExternalDocumentReferences(JsonArray? json) @@ -103,6 +122,9 @@ public static SpdxExternalDocumentReference[] DeserializeExternalDocumentReferen /// /// Deserialize SPDX External Document Reference /// + /// + /// Deserializes a single external document reference including its nested checksum object. + /// /// Json External Document Reference Node /// SPDX External Document Reference public static SpdxExternalDocumentReference DeserializeExternalDocumentReference(JsonNode? json) @@ -118,6 +140,9 @@ public static SpdxExternalDocumentReference DeserializeExternalDocumentReference /// /// Deserialize SPDX Extracted Licensing Infos /// + /// + /// Returns an empty array when is null. + /// /// Json Extracted Licensing Info Array /// SPDX Extracted Licensing Infos public static SpdxExtractedLicensingInfo[] DeserializeExtractedLicensingInfos(JsonArray? json) @@ -128,6 +153,9 @@ public static SpdxExtractedLicensingInfo[] DeserializeExtractedLicensingInfos(Js /// /// Deserialize SPDX Extracted Licensing Info /// + /// + /// Deserializes a single extracted licensing info entry; optional fields default to null. + /// /// Json Extracted Licensing Info Node /// SPDX Extracted Licensing Info public static SpdxExtractedLicensingInfo DeserializeExtractedLicensingInfo(JsonNode? json) @@ -145,6 +173,9 @@ public static SpdxExtractedLicensingInfo DeserializeExtractedLicensingInfo(JsonN /// /// Deserialize SPDX Files /// + /// + /// Returns an empty array when is null. + /// /// Json Files Array /// SPDX Files public static SpdxFile[] DeserializeFiles(JsonArray? json) @@ -155,6 +186,11 @@ public static SpdxFile[] DeserializeFiles(JsonArray? json) /// /// Deserialize SPDX File /// + /// + /// Deserializes a single SPDX file entry including file types, checksums, and annotations. + /// File type strings are converted to enum values via + /// . + /// /// Json File Node /// SPDX File public static SpdxFile DeserializeFile(JsonNode? json) @@ -181,6 +217,9 @@ [.. ParseStringArray(json, SpdxConstants.FieldFileTypes).Select(SpdxFileTypeExte /// /// Deserialize SPDX Packages /// + /// + /// Returns an empty array when is null. + /// /// Json Packages Array /// SPDX Packages public static SpdxPackage[] DeserializePackages(JsonArray? json) @@ -191,6 +230,10 @@ public static SpdxPackage[] DeserializePackages(JsonArray? json) /// /// Deserialize SPDX Package /// + /// + /// Deserializes a single SPDX package entry. Optional fields are mapped to null or empty + /// string when absent from the JSON. + /// /// Json Package Node /// SPDX Package public static SpdxPackage DeserializePackage(JsonNode? json) @@ -231,6 +274,9 @@ public static SpdxPackage DeserializePackage(JsonNode? json) /// /// Deserialize SPDX Snippets /// + /// + /// Returns an empty array when is null. + /// /// Json Snippets Array /// SPDX Snippets public static SpdxSnippet[] DeserializeSnippets(JsonArray? json) @@ -241,6 +287,10 @@ public static SpdxSnippet[] DeserializeSnippets(JsonArray? json) /// /// Deserialize SPDX Snippet /// + /// + /// Byte-range and line-range values are extracted from the nested ranges array + /// using the private Find helper. Values absent from the JSON default to 0. + /// /// Json Snippet Node /// SPDX Snippet public static SpdxSnippet DeserializeSnippet(JsonNode? json) @@ -271,6 +321,9 @@ public static SpdxSnippet DeserializeSnippet(JsonNode? json) /// /// Deserialize SPDX Relationships /// + /// + /// Returns an empty array when is null. + /// /// Json Relationships Array /// SPDX Relationships public static SpdxRelationship[] DeserializeRelationships(JsonArray? json) @@ -281,6 +334,10 @@ public static SpdxRelationship[] DeserializeRelationships(JsonArray? json) /// /// Deserialize SPDX Relationship /// + /// + /// The relationship type string is converted to via + /// . + /// /// Json Relationship Node /// SPDX Relationship public static SpdxRelationship DeserializeRelationship(JsonNode? json) @@ -298,6 +355,10 @@ public static SpdxRelationship DeserializeRelationship(JsonNode? json) /// /// Deserialize SPDX Package Verification Code /// + /// + /// Returns null when is null (i.e., the field is absent from + /// the package JSON object). + /// /// Json Package Verification Code Node /// SPDX Package Verification Code public static SpdxPackageVerificationCode? DeserializeVerificationCode(JsonNode? json) @@ -314,6 +375,9 @@ public static SpdxRelationship DeserializeRelationship(JsonNode? json) /// /// Deserialize SPDX External References /// + /// + /// Returns an empty array when is null. + /// /// Json External References Array /// SPDX External References public static SpdxExternalReference[] DeserializeExternalReferences(JsonArray? json) @@ -324,6 +388,10 @@ public static SpdxExternalReference[] DeserializeExternalReferences(JsonArray? j /// /// Deserialize SPDX External Reference /// + /// + /// The reference category string is converted to via + /// . + /// /// Json External Reference Node /// SPDX External Reference public static SpdxExternalReference DeserializeExternalReference(JsonNode? json) @@ -341,6 +409,9 @@ public static SpdxExternalReference DeserializeExternalReference(JsonNode? json) /// /// Deserialize SPDX Checksums /// + /// + /// Returns an empty array when is null. + /// /// Json Checksums Array /// SPDX Checksums public static SpdxChecksum[] DeserializeChecksums(JsonArray? json) @@ -351,6 +422,10 @@ public static SpdxChecksum[] DeserializeChecksums(JsonArray? json) /// /// Deserialize SPDX Checksum /// + /// + /// The algorithm string is converted to via + /// . + /// /// Json Checksum Node /// SPDX Checksum public static SpdxChecksum DeserializeChecksum(JsonNode? json) @@ -365,6 +440,9 @@ public static SpdxChecksum DeserializeChecksum(JsonNode? json) /// /// Deserialize SPDX Annotations /// + /// + /// Returns an empty array when is null. + /// /// Json Annotations Array /// SPDX Annotations public static SpdxAnnotation[] DeserializeAnnotations(JsonArray? json) @@ -375,6 +453,10 @@ public static SpdxAnnotation[] DeserializeAnnotations(JsonArray? json) /// /// Deserialize SPDX Annotation /// + /// + /// The annotation type string is converted to via + /// . + /// /// Json Annotation Node /// SPDX Annotation public static SpdxAnnotation DeserializeAnnotation(JsonNode? json) @@ -392,6 +474,9 @@ public static SpdxAnnotation DeserializeAnnotation(JsonNode? json) /// /// Deserialize JSON String /// + /// + /// Returns when the node or the named property is absent. + /// /// Json Node /// String Name /// String Value @@ -403,6 +488,10 @@ private static string ParseString(JsonNode? node, string name) /// /// Deserialize JSON Optional String /// + /// + /// Returns null when the node or the named property is absent, distinguishing an absent + /// optional field from an empty-string field. + /// /// Json Node /// String Name /// String Value or null @@ -414,6 +503,9 @@ private static string ParseString(JsonNode? node, string name) /// /// Deserialize Json String Array /// + /// + /// Returns an empty array when the node or the named property is absent. + /// /// Json Node /// Strings Name /// String Array @@ -425,6 +517,9 @@ private static string[] ParseStringArray(JsonNode? node, string name) /// /// Deserialize Json Boolean /// + /// + /// Returns null when the named property is absent or cannot be parsed as a boolean. + /// /// Json Node /// Bool Name /// Bool value or null @@ -436,6 +531,10 @@ private static string[] ParseStringArray(JsonNode? node, string name) /// /// Find a node /// + /// + /// Delegates to the recursive + /// overload starting at index 0. + /// /// Starting node /// Node search path /// JsonNode or null @@ -447,13 +546,18 @@ private static string[] ParseStringArray(JsonNode? node, string name) /// /// Find a named node /// + /// + /// Recursively descends through named properties. When an intermediate node is a + /// , the method searches each element in + /// order and returns the first non-null match, enabling path traversal through arrays. + /// /// Starting node /// Name index /// Names list /// JsonNode if found, else null private static JsonNode? Find(JsonNode? node, int idx, IReadOnlyList names) { - // Fail if at end + // All path segments traversed — return the current node (found) if (node == null || idx >= names.Count) { return node; diff --git a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs index 7bca0c1..5054e66 100644 --- a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs +++ b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs @@ -25,13 +25,24 @@ namespace DemaConsulting.SpdxModel.IO; /// -/// JSON Serializer class +/// Serializes an in-memory to SPDX 2.x JSON text or a +/// DOM. /// +/// +/// This class is the counterpart to and completes +/// the round-trip serialization support for the IO subsystem. All methods are static +/// and the class carries no instance state, making it safe for concurrent calls on +/// different documents without external synchronization. +/// public static class Spdx2JsonSerializer { /// /// Serialize SPDX Document /// + /// + /// Delegates to then converts the resulting + /// to an indented JSON string. + /// /// SPDX Document /// Json string public static string Serialize(SpdxDocument document) @@ -51,6 +62,12 @@ public static string Serialize(SpdxDocument document) /// /// Serialize SPDX Document /// + /// + /// Top-level arrays (files, packages, snippets, relationships) + /// are always emitted even when empty, as required by the SPDX 2.x schema. + /// Optional arrays (e.g., externalDocumentRefs, annotations) are omitted + /// when empty. + /// /// SPDX Document /// Json object public static JsonObject SerializeDocument(SpdxDocument document) @@ -91,6 +108,10 @@ public static JsonObject SerializeDocument(SpdxDocument document) /// /// Serialize SPDX Creation information to JSON object /// + /// + /// Serializes the creation information object including optional creators array, + /// creation date, and optional comment and license list version fields. + /// /// SPDX Creation Information /// JSON object public static JsonObject SerializeCreationInformation(SpdxCreationInformation info) @@ -106,6 +127,10 @@ public static JsonObject SerializeCreationInformation(SpdxCreationInformation in /// /// Serialize array of SPDX External Document References to JSON array /// + /// + /// Delegates to for each element. + /// Returns a of external document reference objects. + /// /// SPDX External Document References /// JSON array public static JsonArray SerializeExternalDocumentReferences(SpdxExternalDocumentReference[] references) @@ -122,6 +147,9 @@ public static JsonArray SerializeExternalDocumentReferences(SpdxExternalDocument /// /// Serialize SPDX External Document Reference to JSON object /// + /// + /// Serializes a single external document reference including its nested checksum object. + /// /// SPDX External Document Reference /// JSON object public static JsonObject SerializeExternalDocumentReference(SpdxExternalDocumentReference reference) @@ -136,6 +164,9 @@ public static JsonObject SerializeExternalDocumentReference(SpdxExternalDocument /// /// Serialize array of SPDX Extracted Licensing Infos to JSON array /// + /// + /// Delegates to for each element. + /// /// SPDX Extracted Licensing Infos /// JSON array public static JsonArray SerializeExtractedLicensingInfos(SpdxExtractedLicensingInfo[] infos) @@ -152,6 +183,9 @@ public static JsonArray SerializeExtractedLicensingInfos(SpdxExtractedLicensingI /// /// Serialize SPDX Extracted Licensing Info to JSON object /// + /// + /// Serializes a single extracted licensing info entry; optional fields are omitted when null or empty. + /// /// SPDX Extracted Licensing Info /// JSON object public static JsonObject SerializeExtractedLicensingInfo(SpdxExtractedLicensingInfo info) @@ -168,6 +202,10 @@ public static JsonObject SerializeExtractedLicensingInfo(SpdxExtractedLicensingI /// /// Serialize array of SPDX Files to JSON array /// + /// + /// Delegates to for each element. Always emits at the + /// document level even when the array is empty. + /// /// SPDX Files /// JSON array public static JsonArray SerializeFiles(SpdxFile[] files) @@ -184,6 +222,10 @@ public static JsonArray SerializeFiles(SpdxFile[] files) /// /// Serialize SPDX File to JSON object /// + /// + /// Serializes a single SPDX file entry including file types, checksums, and optional + /// annotations. The annotations sub-array is omitted when empty. + /// /// SPDX File /// JSON object public static JsonObject SerializeFile(SpdxFile file) @@ -213,6 +255,10 @@ public static JsonObject SerializeFile(SpdxFile file) /// /// Serialize array of SPDX Packages to JSON array /// + /// + /// Delegates to for each element. Always emits at the + /// document level even when the array is empty. + /// /// SPDX Packages /// JSON array public static JsonArray SerializePackages(SpdxPackage[] packages) @@ -229,6 +275,11 @@ public static JsonArray SerializePackages(SpdxPackage[] packages) /// /// Serialize SPDX Package to JSON object /// + /// + /// Serializes a single SPDX package entry. The filesAnalyzed field is omitted + /// when null; the verification code, external references, and annotations sub-objects + /// are omitted when absent or empty. + /// /// SPDX Package /// JSON object public static JsonObject SerializePackage(SpdxPackage package) @@ -284,6 +335,10 @@ public static JsonObject SerializePackage(SpdxPackage package) /// /// Serialize array of SPDX Snippets to JSON array /// + /// + /// Delegates to for each element. Always emits at the + /// document level even when the array is empty. + /// /// SPDX Snippets /// JSON array public static JsonArray SerializeSnippets(SpdxSnippet[] snippets) @@ -300,6 +355,12 @@ public static JsonArray SerializeSnippets(SpdxSnippet[] snippets) /// /// Serialize SPDX Snippet to JSON object /// + /// + /// Always emits a byte-range entry in the ranges array. A line-range entry is + /// only added when BOTH and + /// are non-zero; if either is zero the + /// line-range entry is omitted entirely. + /// /// SPDX Snippet /// JSON object public static JsonObject SerializeSnippet(SpdxSnippet snippet) @@ -336,7 +397,7 @@ public static JsonObject SerializeSnippet(SpdxSnippet snippet) } } }; - if (snippet.SnippetLineEnd > 0 || snippet.SnippetLineStart > 0) + if (snippet.SnippetLineEnd > 0 && snippet.SnippetLineStart > 0) { ranges.Add(new JsonObject { @@ -360,6 +421,10 @@ public static JsonObject SerializeSnippet(SpdxSnippet snippet) /// /// Serialize array of SPDX Relationships to JSON array /// + /// + /// Delegates to for each element. Always emits at the + /// document level even when the array is empty. + /// /// SPDX Relationships /// JSON array public static JsonArray SerializeRelationships(SpdxRelationship[] relationships) @@ -376,6 +441,10 @@ public static JsonArray SerializeRelationships(SpdxRelationship[] relationships) /// /// Serialize SPDX Relationship to JSON object /// + /// + /// Serializes a single SPDX relationship. The optional comment field is omitted when null + /// or empty. + /// /// SPDX Relationship /// JSON object public static JsonObject SerializeRelationship(SpdxRelationship relationship) @@ -391,6 +460,10 @@ public static JsonObject SerializeRelationship(SpdxRelationship relationship) /// /// Serialize SPDX Package Verification Code to JSON object /// + /// + /// Serializes the package verification code object including the hash value and optional + /// excluded files array. + /// /// SPDX Package Verification Code /// JSON object public static JsonObject SerializeVerificationCode(SpdxPackageVerificationCode code) @@ -404,14 +477,17 @@ public static JsonObject SerializeVerificationCode(SpdxPackageVerificationCode c /// /// Serialize array of SPDX External References to JSON array /// + /// + /// Delegates to for each element. + /// /// SPDX External References /// JSON array public static JsonArray SerializeExternalReferences(SpdxExternalReference[] references) { var json = new JsonArray(); - foreach (var checksum in references) + foreach (var reference in references) { - json.Add(SerializeExternalReference(checksum)); + json.Add(SerializeExternalReference(reference)); } return json; @@ -420,6 +496,10 @@ public static JsonArray SerializeExternalReferences(SpdxExternalReference[] refe /// /// Serialize SPDX External Reference to JSON object /// + /// + /// Serializes a single external reference entry including category, type, locator, and + /// optional comment. + /// /// SPDX External Reference /// JSON object public static JsonObject SerializeExternalReference(SpdxExternalReference reference) @@ -435,6 +515,9 @@ public static JsonObject SerializeExternalReference(SpdxExternalReference refere /// /// Serialize array of SPDX Checksums to JSON array /// + /// + /// Delegates to for each element. + /// /// SPDX Checksums /// JSON array public static JsonArray SerializeChecksums(SpdxChecksum[] checksums) @@ -451,6 +534,9 @@ public static JsonArray SerializeChecksums(SpdxChecksum[] checksums) /// /// Serialize SPDX Checksum to JSON object /// + /// + /// Serializes a single checksum entry with its algorithm and value fields. + /// /// SPDX Checksum /// JSON object public static JsonObject SerializeChecksum(SpdxChecksum checksum) @@ -464,6 +550,9 @@ public static JsonObject SerializeChecksum(SpdxChecksum checksum) /// /// Serialize array of SPDX Annotations to JSON array /// + /// + /// Delegates to for each element. + /// /// SPDX Annotations /// JSON array public static JsonArray SerializeAnnotations(SpdxAnnotation[] annotations) @@ -480,6 +569,11 @@ public static JsonArray SerializeAnnotations(SpdxAnnotation[] annotations) /// /// Serialize SPDX Annotation to JSON object /// + /// + /// The SPDXID field is conditionally omitted when + /// is null or empty, because annotations on sub-elements often do not carry their own + /// SPDX ID. + /// /// SPDX Annotation to serialize /// JSON object public static JsonObject SerializeAnnotation(SpdxAnnotation annotation) @@ -496,6 +590,10 @@ public static JsonObject SerializeAnnotation(SpdxAnnotation annotation) /// /// Emit a string property into a JSON object /// + /// + /// Always writes the property, even for empty strings. Use + /// for fields that should be omitted when empty. + /// /// JSON object /// Property name /// Property value @@ -507,6 +605,10 @@ private static void EmitString(JsonNode json, string name, string value) /// /// Emit an optional string property into a JSON object /// + /// + /// Omits the property entirely when is null or empty, consistent + /// with the serializer convention of not writing null or empty optional fields. + /// /// JSON object /// Property name /// Optional property value @@ -521,6 +623,16 @@ private static void EmitOptionalString(JsonNode json, string name, string? value json[name] = value; } + /// + /// Emit an optional string array property into a JSON object. + /// + /// JSON object to write into + /// Property name + /// Array of values; omitted when empty + /// + /// Omits the property entirely when is empty, consistent + /// with the serializer convention of not writing null or empty optional fields. + /// private static void EmitOptionalStrings(JsonNode json, string name, string[] values) { // Skip if empty diff --git a/src/DemaConsulting.SpdxModel/IO/SpdxConstants.cs b/src/DemaConsulting.SpdxModel/IO/SpdxConstants.cs index 403c6c8..0739fc1 100644 --- a/src/DemaConsulting.SpdxModel/IO/SpdxConstants.cs +++ b/src/DemaConsulting.SpdxModel/IO/SpdxConstants.cs @@ -1,377 +1,477 @@ +// Copyright(c) 2024 DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + namespace DemaConsulting.SpdxModel.IO; /// /// SPDX 2.x constants. /// +/// +/// All SPDX 2.x JSON field name strings are centralised here to provide a single +/// maintenance point. Both and +/// consume these constants, ensuring that any +/// SPDX schema field name change requires an update in exactly one place. +/// internal static class SpdxConstants { /// /// Constant for SPDX ID field /// + /// The SPDX 2.x JSON field name for the SPDX element identifier. internal const string FieldSpdxId = "SPDXID"; /// /// Constant for SPDX Version field /// + /// The SPDX 2.x JSON field name for the SPDX specification version. internal const string FieldSpdxVersion = "spdxVersion"; /// /// Constant for SPDX Name field /// + /// The SPDX 2.x JSON field name for the document or element name. internal const string FieldName = "name"; /// /// Constant for SPDX Data License field /// + /// The SPDX 2.x JSON field name for the data license of the SPDX document. internal const string FieldDataLicense = "dataLicense"; /// /// Constant for SPDX Document Namespace field /// + /// The SPDX 2.x JSON field name for the document namespace URI. internal const string FieldDocumentNamespace = "documentNamespace"; /// /// Constant for SPDX Comment field /// + /// The SPDX 2.x JSON field name for an optional free-text comment. internal const string FieldComment = "comment"; /// /// Constant for SPDX Creation Info field /// + /// The SPDX 2.x JSON field name for the document creation information object. internal const string FieldCreationInfo = "creationInfo"; /// /// Constant for SPDX External Document Refs field /// + /// The SPDX 2.x JSON field name for the array of external document references. internal const string FieldExternalDocumentRefs = "externalDocumentRefs"; /// /// Constant for SPDX Has Extracted Licensing Infos field /// + /// The SPDX 2.x JSON field name for the array of extracted licensing info entries. internal const string FieldHasExtractedLicensingInfos = "hasExtractedLicensingInfos"; /// /// Constant for SPDX Annotations field /// + /// The SPDX 2.x JSON field name for the annotations array on a document or element. internal const string FieldAnnotations = "annotations"; /// /// Constant for SPDX Files field /// + /// The SPDX 2.x JSON field name for the top-level files array. internal const string FieldFiles = "files"; /// /// Constant for SPDX Packages field /// + /// The SPDX 2.x JSON field name for the top-level packages array. internal const string FieldPackages = "packages"; /// /// Constant for SPDX Snippets field /// + /// The SPDX 2.x JSON field name for the top-level snippets array. internal const string FieldSnippets = "snippets"; /// /// Constant for SPDX Relationships field /// + /// The SPDX 2.x JSON field name for the top-level relationships array. internal const string FieldRelationships = "relationships"; /// /// Constant for SPDX Document Describes field /// + /// The SPDX 2.x JSON field name for the array of element IDs described by the document. internal const string FieldDocumentDescribes = "documentDescribes"; /// /// Constant for SPDX Creators field /// + /// The SPDX 2.x JSON field name for the creators array in creation information. internal const string FieldCreators = "creators"; /// /// Constant for SPDX Created field /// + /// The SPDX 2.x JSON field name for the creation date-time in creation information. internal const string FieldCreated = "created"; /// /// Constant for SPDX License List Version field /// + /// The SPDX 2.x JSON field name for the SPDX license list version. internal const string FieldLicenseListVersion = "licenseListVersion"; /// /// Constant for SPDX External Document Id field /// + /// The SPDX 2.x JSON field name for the external document identifier. internal const string FieldExternalDocumentId = "externalDocumentId"; /// /// Constant for SPDX Checksum field /// + /// The SPDX 2.x JSON field name for a single checksum object (used in external document references). internal const string FieldChecksum = "checksum"; /// /// Constant for SPDX Checksums field /// + /// The SPDX 2.x JSON field name for the checksums array on a package or file. internal const string FieldChecksums = "checksums"; /// /// Constant for SPDX Document field /// + /// The SPDX 2.x JSON field name for the SPDX document URI in an external document reference. internal const string FieldSpdxDocument = "spdxDocument"; /// /// Constant for SPDX License ID field /// + /// The SPDX 2.x JSON field name for the license identifier in extracted licensing info. internal const string FieldLicenseId = "licenseId"; /// /// Constant for SPDX Extracted Text field /// + /// The SPDX 2.x JSON field name for the extracted license text. internal const string FieldExtractedText = "extractedText"; /// /// Constant for SPDX See Alsos field /// + /// The SPDX 2.x JSON field name for the cross-reference URLs in extracted licensing info. internal const string FieldSeeAlsos = "seeAlsos"; /// /// Constant for SPDX File Name field /// + /// The SPDX 2.x JSON field name for the file path or name. internal const string FieldFileName = "fileName"; /// /// Constant for SPDX File Types field /// + /// The SPDX 2.x JSON field name for the file type classification array. internal const string FieldFileTypes = "fileTypes"; /// /// Constant for SPDX License Concluded field /// + /// The SPDX 2.x JSON field name for the concluded license expression. internal const string FieldLicenseConcluded = "licenseConcluded"; /// /// Constant for SPDX License Info In Files field /// + /// The SPDX 2.x JSON field name for the license information found in a file. internal const string FieldLicenseInfoInFiles = "licenseInfoInFiles"; /// /// Constant for SPDX License Comments field /// + /// The SPDX 2.x JSON field name for license-related comments. internal const string FieldLicenseComments = "licenseComments"; /// /// Constant for SPDX Copyright Text field /// + /// The SPDX 2.x JSON field name for the copyright text. internal const string FieldCopyrightText = "copyrightText"; /// /// Constant for SPDX Notice Text field /// + /// The SPDX 2.x JSON field name for the notice text on a file. internal const string FieldNoticeText = "noticeText"; /// /// Constant for SPDX File Contributors field /// + /// The SPDX 2.x JSON field name for the file contributors array. internal const string FieldFileContributors = "fileContributors"; /// /// Constant for SPDX Attribution Texts field /// + /// The SPDX 2.x JSON field name for the attribution texts array. internal const string FieldAttributionTexts = "attributionTexts"; /// /// Constant for SPDX Version Info field /// + /// The SPDX 2.x JSON field name for the package version string. internal const string FieldVersionInfo = "versionInfo"; /// /// Constant for SPDX Package File Name field /// + /// The SPDX 2.x JSON field name for the package file name. internal const string FieldPackageFileName = "packageFileName"; /// /// Constant for SPDX Supplier field /// + /// The SPDX 2.x JSON field name for the package supplier. internal const string FieldSupplier = "supplier"; /// /// Constant for SPDX Originator field /// + /// The SPDX 2.x JSON field name for the package originator. internal const string FieldOriginator = "originator"; /// /// Constant for SPDX Download Location field /// + /// The SPDX 2.x JSON field name for the package download location URI. internal const string FieldDownloadLocation = "downloadLocation"; /// /// Constant for SPDX Files Analyzed field /// + /// The SPDX 2.x JSON field name for the files-analyzed flag on a package. internal const string FieldFilesAnalyzed = "filesAnalyzed"; /// /// Constant for SPDX Has Files field /// + /// The SPDX 2.x JSON field name for the array of file IDs belonging to a package. internal const string FieldHasFiles = "hasFiles"; /// /// Constant for SPDX Package Verification Code field /// + /// The SPDX 2.x JSON field name for the package verification code object. internal const string FieldPackageVerificationCode = "packageVerificationCode"; /// /// Constant for SPDX Home Page field /// + /// The SPDX 2.x JSON field name for the package home page URL. internal const string FieldHomePage = "homepage"; /// /// Constant for SPDX Source Info field /// + /// The SPDX 2.x JSON field name for the package source information text. internal const string FieldSourceInfo = "sourceInfo"; /// /// Constant for SPDX License Info From Files field /// + /// The SPDX 2.x JSON field name for the license information from files array in a package. internal const string FieldLicenseInfoFromFiles = "licenseInfoFromFiles"; /// /// Constant for SPDX License Declared field /// + /// The SPDX 2.x JSON field name for the declared license expression of a package. internal const string FieldLicenseDeclared = "licenseDeclared"; /// /// Constant for SPDX Summary field /// + /// The SPDX 2.x JSON field name for the package summary text. internal const string FieldSummary = "summary"; /// /// Constant for SPDX Description field /// + /// The SPDX 2.x JSON field name for the package description text. internal const string FieldDescription = "description"; /// /// Constant for SPDX External Refs field /// + /// The SPDX 2.x JSON field name for the external references array on a package. internal const string FieldExternalRefs = "externalRefs"; /// /// Constant for SPDX Primary Package Purpose field /// + /// The SPDX 2.x JSON field name for the primary package purpose string. internal const string FieldPrimaryPackagePurpose = "primaryPackagePurpose"; /// /// Constant for SPDX Release Date field /// + /// The SPDX 2.x JSON field name for the package release date. internal const string FieldReleaseDate = "releaseDate"; /// /// Constant for SPDX Build Date field /// + /// The SPDX 2.x JSON field name for the package build date. internal const string FieldBuiltDate = "builtDate"; /// /// Constant for SPDX Valid Until Date field /// + /// The SPDX 2.x JSON field name for the package valid-until date. internal const string FieldValidUntilDate = "validUntilDate"; /// /// Constant for SPDX Snippet From File field /// + /// The SPDX 2.x JSON field name for the file reference in a snippet. internal const string FieldSnippetFromFile = "snippetFromFile"; /// /// Constant for SPDX License Info In Snippets field /// + /// The SPDX 2.x JSON field name for the license information in snippets array. internal const string FieldLicenseInfoInSnippets = "licenseInfoInSnippets"; /// /// Constant for SPDX Ranges field /// + /// The SPDX 2.x JSON field name for the ranges array in a snippet. internal const string FieldRanges = "ranges"; /// /// Constant for SPDX Start Pointer field /// + /// The SPDX 2.x JSON field name for the start pointer object in a snippet range. internal const string FieldStartPointer = "startPointer"; /// /// Constant for SPDX End Pointer field /// + /// The SPDX 2.x JSON field name for the end pointer object in a snippet range. internal const string FieldEndPointer = "endPointer"; /// /// Constant for SPDX Offset field /// + /// The SPDX 2.x JSON field name for the byte offset in a snippet pointer. internal const string FieldOffset = "offset"; /// /// Constant for SPDX Line Number field /// + /// The SPDX 2.x JSON field name for the line number in a snippet pointer. internal const string FieldLineNumber = "lineNumber"; /// /// Constant for SPDX Reference field /// + /// The SPDX 2.x JSON field name for the file reference in a snippet pointer. internal const string FieldReference = "reference"; /// /// Constant for SPDX Element ID field /// + /// The SPDX 2.x JSON field name for the source element ID in a relationship. internal const string FieldSpdxElementId = "spdxElementId"; /// /// Constant for SPDX Related Spdx Element field /// + /// The SPDX 2.x JSON field name for the target element ID in a relationship. internal const string FieldRelatedSpdxElement = "relatedSpdxElement"; /// /// Constant for SPDX Relationship Type field /// + /// The SPDX 2.x JSON field name for the relationship type string. internal const string FieldRelationshipType = "relationshipType"; /// /// Constant for SPDX Package Verification Code Excluded Files field /// + /// The SPDX 2.x JSON field name for the excluded files array in a package verification code. internal const string FieldPackageVerificationCodeExcludedFiles = "packageVerificationCodeExcludedFiles"; /// /// Constant for SPDX Package Verification Code Value field /// + /// The SPDX 2.x JSON field name for the hash value in a package verification code. internal const string FieldPackageVerificationCodeValue = "packageVerificationCodeValue"; /// /// Constant for SPDX Reference Category field /// + /// The SPDX 2.x JSON field name for the reference category in an external reference. internal const string FieldReferenceCategory = "referenceCategory"; /// /// Constant for SPDX Reference Type field /// + /// The SPDX 2.x JSON field name for the reference type in an external reference. internal const string FieldReferenceType = "referenceType"; /// /// Constant for SPDX Reference Locator field /// + /// The SPDX 2.x JSON field name for the reference locator in an external reference. internal const string FieldReferenceLocator = "referenceLocator"; /// /// Constant for SPDX Algorithm field /// + /// The SPDX 2.x JSON field name for the checksum algorithm. internal const string FieldAlgorithm = "algorithm"; /// /// Constant for SPDX Checksum Value field /// + /// The SPDX 2.x JSON field name for the checksum hash value. internal const string FieldChecksumValue = "checksumValue"; /// /// Constant for SPDX Annotator field /// + /// The SPDX 2.x JSON field name for the annotator field in an annotation. internal const string FieldAnnotator = "annotator"; /// /// Constant for SPDX Annotation Date field /// + /// The SPDX 2.x JSON field name for the annotation date field. internal const string FieldAnnotationDate = "annotationDate"; /// /// Constant for SPDX Annotation Type field /// + /// The SPDX 2.x JSON field name for the annotation type field. internal const string FieldAnnotationType = "annotationType"; } diff --git a/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs b/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs index 1f91c56..0da5485 100644 --- a/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs +++ b/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs @@ -38,37 +38,57 @@ public sealed class SpdxAnnotation : SpdxElement public static readonly IEqualityComparer Same = new SpdxAnnotationSame(); /// - /// Annotator Field (optional) + /// Annotator Field /// /// /// This field identifies the person, organization, or tool that has /// commented on a file, package, snippet, or the entire document. + /// This field is required for a valid SPDX annotation; + /// will report an error if it is absent. /// public string Annotator { get; set; } = ""; /// - /// Annotation Date Field (optional) + /// Annotation Date Field /// /// /// Identify when the comment was made. This is to be specified according /// to the combined date and time in the UTC format, as specified in the /// ISO 8601 standard. + /// This field is required for a valid SPDX annotation; + /// will report an error if the value is absent or not a valid ISO 8601 date-time. /// public string Date { get; set; } = ""; /// - /// Annotation Type Field (optional) + /// Annotation Type Field /// + /// + /// Indicates the category of the annotation. Valid values are + /// and . + /// This field is required for a valid SPDX annotation; + /// will report an error if the value is . + /// public SpdxAnnotationType Type { get; set; } = SpdxAnnotationType.Missing; /// - /// Annotation Comment field (optional) + /// Annotation Comment field /// + /// + /// Free-text content of the annotation describing the finding or note left by + /// the annotator. This field is required for a valid SPDX annotation; + /// will report an error if the value is absent or empty. + /// public string Comment { get; set; } = ""; /// /// Make a deep-copy of this object /// + /// + /// Returns an independent copy with no shared mutable references — mutating the copy + /// does not affect the original and vice versa. Used by + /// when a new annotation is appended from the other array. + /// /// Deep copy of this object public SpdxAnnotation DeepCopy() { @@ -85,6 +105,12 @@ public SpdxAnnotation DeepCopy() /// /// Enhance missing fields in the annotation /// + /// + /// This operation is additive-only: it never overwrites a non-empty field on + /// this instance. Fields are only populated when they are currently empty/missing. + /// The sentinel is used to detect an absent + /// field. + /// /// Other annotation to enhance with public void Enhance(SpdxAnnotation other) { @@ -110,6 +136,12 @@ public void Enhance(SpdxAnnotation other) /// /// Enhance missing annotations in array /// + /// + /// Matches annotations using (annotator + date + type + comment). + /// For each entry in : if a matching annotation already exists + /// in it is enhanced in place; otherwise a deep copy is appended. + /// The returned array may be larger than the input array. + /// /// Array to enhance /// Other array to enhance with /// Updated array @@ -142,8 +174,16 @@ public static SpdxAnnotation[] Enhance(SpdxAnnotation[] array, SpdxAnnotation[] /// /// Perform validation of information /// - /// Associated parent node - /// List to populate with issues + /// + /// Issues are appended to rather than thrown as exceptions, + /// allowing all validation errors across the document to be collected in a single pass. + /// The caller is responsible for checking whether any issues were added after this call. + /// + /// + /// Identifier of the parent element (e.g. package or file SPDX-ID) used as + /// a prefix in issue messages so callers can locate the problematic annotation. + /// + /// List to populate with validation issues public void Validate(string parent, List issues) { // Validate Annotator Field diff --git a/src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs b/src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs index 2a2f4dc..aef3da3 100644 --- a/src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs +++ b/src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs @@ -23,6 +23,12 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX Annotation Type enumeration /// +/// +/// The sentinel value (-1) is used internally to represent +/// an absent or uninitialized annotation type and must not be serialized to JSON. +/// will throw if called with +/// . +/// public enum SpdxAnnotationType { /// @@ -44,14 +50,25 @@ public enum SpdxAnnotationType /// /// SPDX Annotation Type Extensions /// +/// +/// Provides string ↔ enum conversion for SPDX 2.x JSON serialization. Both directions +/// are consumed by and +/// . +/// public static class SpdxAnnotationTypeExtensions { /// /// Convert text to SpdxAnnotationType /// + /// + /// Matching is case-insensitive: "review", "REVIEW", and "Review" + /// all map to . An empty string maps to + /// . Any other value throws + /// . + /// /// Annotation Type text /// SpdxAnnotationType - /// on error + /// Thrown when is not a recognized SPDX annotation type string. public static SpdxAnnotationType FromText(string annotationType) { return annotationType.ToUpperInvariant() switch @@ -66,9 +83,15 @@ public static SpdxAnnotationType FromText(string annotationType) /// /// Convert SpdxAnnotationType to text /// + /// + /// Returns the uppercase SPDX 2.x text representation of the enum value + /// (e.g., "REVIEW"). + /// Throws for + /// and for any unrecognized numeric value to prevent silent serialization of invalid data. + /// /// SpdxAnnotationType /// Annotation Type text - /// on error + /// Thrown when the enum value is or is not a recognized value. public static string ToText(this SpdxAnnotationType annotationType) { return annotationType switch diff --git a/src/DemaConsulting.SpdxModel/SpdxChecksum.cs b/src/DemaConsulting.SpdxModel/SpdxChecksum.cs index 7f59fa6..f46aaa7 100644 --- a/src/DemaConsulting.SpdxModel/SpdxChecksum.cs +++ b/src/DemaConsulting.SpdxModel/SpdxChecksum.cs @@ -60,6 +60,11 @@ public sealed class SpdxChecksum /// /// Make a deep-copy of this object /// + /// + /// Deep copy is required to prevent aliasing when checksums are shared between + /// documents during merge operations. Mutating a checksum on one document must not + /// affect the corresponding checksum on another. + /// /// Deep copy of this object public SpdxChecksum DeepCopy() { @@ -73,6 +78,11 @@ public SpdxChecksum DeepCopy() /// /// Enhance missing fields in the checksum /// + /// + /// This method is called during document merge operations to fill in any + /// fields that are absent in this instance. Fields that are already populated + /// are preserved; only missing (default/empty) values are replaced. + /// /// Other checksum to enhance with public void Enhance(SpdxChecksum other) { @@ -89,6 +99,12 @@ public void Enhance(SpdxChecksum other) /// /// Enhance missing checksums in array /// + /// + /// Matching uses the comparer (algorithm + value equality). Entries + /// in that match an existing entry are merged in place via the + /// instance method. Entries with no match are + /// deep-copied before being appended to preserve independence from the source array. + /// /// Array to enhance /// Other array to enhance with /// Updated array @@ -121,6 +137,11 @@ public static SpdxChecksum[] Enhance(SpdxChecksum[] array, SpdxChecksum[] others /// /// Perform validation of information /// + /// + /// Called by containing element validators (e.g., SpdxPackage, SpdxFile, SpdxExternalDocumentReference) + /// to verify algorithm and value fields are populated and consistent. The + /// string is prepended to each issue message to provide context in diagnostic output. + /// /// Associated parent node /// List to populate with issues public void Validate(string parent, List issues) @@ -130,6 +151,10 @@ public void Validate(string parent, List issues) { issues.Add($"{parent} Invalid Checksum Algorithm Field - Missing"); } + else if (!Enum.IsDefined(typeof(SpdxChecksumAlgorithm), Algorithm)) + { + issues.Add($"{parent} Invalid Checksum Algorithm Field - Unknown"); + } // Validate Checksum Value Field if (Value.Length == 0) @@ -141,6 +166,11 @@ public void Validate(string parent, List issues) /// /// Equality Comparer to test for the same relationship /// + /// + /// Two checksums are considered the same when both their + /// and fields are equal. This semantics is used to + /// deduplicate and merge checksum arrays during document merge operations. + /// private sealed class SpdxChecksumSame : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs b/src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs index 1276940..7ebc2ee 100644 --- a/src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs +++ b/src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs @@ -23,110 +23,153 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX Checksum Algorithm enumeration /// +/// +/// The sentinel value (-1) indicates that no algorithm has been +/// assigned. It is used as a default/uninitialised state and is not a valid SPDX +/// algorithm name. All other members correspond to named SPDX algorithm identifiers. +/// public enum SpdxChecksumAlgorithm { /// /// Missing checksum algorithm /// + /// + /// Sentinel value indicating that no algorithm has been assigned. This value is not + /// a valid SPDX checksum algorithm and must not be serialized to SPDX text form. + /// It is used as the default state before an algorithm is explicitly set. + /// Missing = -1, /// /// SHA-1 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier SHA1. Sha1, /// /// SHA-224 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier SHA224. Sha224, /// /// SHA-256 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier SHA256. Sha256, /// /// SHA-384 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier SHA384. Sha384, /// /// SHA-512 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier SHA512. Sha512, /// /// MD2 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier MD2. Md2, /// /// MD4 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier MD4. Md4, /// /// MD5 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier MD5. Md5, /// /// MD6 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier MD6. Md6, /// /// SHA3-256 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier SHA3-256. Sha3256, /// /// SHA3-384 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier SHA3-384. Sha3384, /// /// SHA3-512 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier SHA3-512. Sha3512, /// /// BLAKE2b-256 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier BLAKE2b-256. Blake2B256, /// /// BLAKE2b-384 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier BLAKE2b-384. Blake2B384, /// /// BLAKE2b-512 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier BLAKE2b-512. Blake2B512, /// /// BLAKE3 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier BLAKE3. Blake3, /// /// ADLER32 checksum algorithm /// + /// Corresponds to the SPDX algorithm identifier ADLER32. Adler32 } /// /// SPDX Checksum Algorithm Extensions /// +/// +/// Provides the factory method and the +/// extension method to convert between SPDX algorithm text strings and the +/// enumeration. Use when +/// parsing SPDX JSON/tag-value input and when serializing back +/// to SPDX text form. +/// public static class SpdxChecksumAlgorithmExtensions { /// /// Convert text to SpdxChecksumAlgorithm /// + /// + /// The input string is converted to upper-case via ToUpperInvariant() before + /// comparison, so the lookup is case-insensitive and locale-independent. An empty + /// string maps to rather than throwing; + /// any other unrecognized value throws . + /// /// Checksum algorithm text /// SpdxChecksumAlgorithm - /// on error + /// + /// Thrown when is a non-empty string that does not match any + /// known SPDX checksum algorithm name (case-insensitive comparison is used before the exception is raised). + /// public static SpdxChecksumAlgorithm FromText(string checksumAlgorithm) { return checksumAlgorithm.ToUpperInvariant() switch @@ -156,9 +199,18 @@ public static SpdxChecksumAlgorithm FromText(string checksumAlgorithm) /// /// Convert SpdxChecksumAlgorithm to text /// + /// + /// is intentionally rejected rather than + /// round-tripping to an empty string: serializing a checksum without an algorithm + /// would produce invalid SPDX output, so the caller must ensure only valid, named + /// algorithms are passed to this method. + /// /// SpdxChecksumAlgorithm /// Checksum Algorithm text - /// on error + /// + /// Thrown when is or + /// is a numeric value that is not a named member of the enumeration. + /// public static string ToText(this SpdxChecksumAlgorithm checksumAlgorithm) { return checksumAlgorithm switch diff --git a/src/DemaConsulting.SpdxModel/SpdxCreationInformation.cs b/src/DemaConsulting.SpdxModel/SpdxCreationInformation.cs index 9139043..c808261 100644 --- a/src/DemaConsulting.SpdxModel/SpdxCreationInformation.cs +++ b/src/DemaConsulting.SpdxModel/SpdxCreationInformation.cs @@ -35,6 +35,11 @@ public sealed class SpdxCreationInformation /// /// Regular expression for checking license list versions /// + /// + /// The pattern ^\d+\.\d+$ matches the SPDX major.minor version format (e.g., + /// 3.9 or 3.21). The 100 ms timeout passed to the + /// constructor guards against ReDoS on untrusted or malformed input. + /// private static readonly Regex LicenseListVersionRegex = new( @"^\d+\.\d+$", RegexOptions.None, @@ -68,6 +73,11 @@ public sealed class SpdxCreationInformation /// /// Creator Comment Field (optional) /// + /// + /// An optional free-text comment from the document creators providing supplemental context + /// about the creation process. This field is preserved during deep-copy and propagated + /// during enhance if absent from this instance. + /// public string? Comment { get; set; } /// @@ -82,6 +92,11 @@ public sealed class SpdxCreationInformation /// /// Make a deep-copy of this object /// + /// + /// Returns a structurally independent copy with no shared mutable references. The + /// array is cloned so that mutations to one instance do not + /// affect the other. + /// /// Deep copy of this object public SpdxCreationInformation DeepCopy() { @@ -97,6 +112,11 @@ public SpdxCreationInformation DeepCopy() /// /// Enhance missing fields in the creation information /// + /// + /// Merges into this instance using a union-and-deduplicate strategy + /// for and a fill-if-absent strategy for scalar fields. This method + /// is used during document merge operations to consolidate creation metadata. + /// /// Other creation information to enhance with public void Enhance(SpdxCreationInformation other) { @@ -116,6 +136,13 @@ public void Enhance(SpdxCreationInformation other) /// /// Perform validation of information /// + /// + /// Checks four rules: (1) must be non-empty; (2) each creator entry + /// must start with Person:, Organization:, or Tool:; (3) + /// must be a valid SPDX date-time string when non-empty (empty is permitted for + /// partially-constructed documents); (4) , when present, + /// must match the pattern \d+\.\d+. + /// /// List to populate with issues public void Validate(List issues) { diff --git a/src/DemaConsulting.SpdxModel/SpdxDocument.cs b/src/DemaConsulting.SpdxModel/SpdxDocument.cs index 870e4e4..732b0fb 100644 --- a/src/DemaConsulting.SpdxModel/SpdxDocument.cs +++ b/src/DemaConsulting.SpdxModel/SpdxDocument.cs @@ -30,6 +30,11 @@ public sealed class SpdxDocument : SpdxElement /// /// Regular expression for checking SPDX version fields /// + /// + /// The pattern ^SPDX-\d+\.\d+$ matches valid SPDX version strings such as + /// SPDX-2.3. The 100 ms timeout passed to the constructor + /// guards against ReDoS on untrusted or malformed input. + /// private static readonly Regex VersionRegex = new( @"^SPDX-\d+\.\d+$", RegexOptions.None, @@ -48,6 +53,10 @@ public sealed class SpdxDocument : SpdxElement /// /// Document Name Field /// + /// + /// Human-readable name for this SPDX document as defined in SPDX 2.x §2.4. + /// Must be non-empty for a valid document. + /// public string Name { get; set; } = string.Empty; /// @@ -78,16 +87,30 @@ public sealed class SpdxDocument : SpdxElement /// /// SPDX Document Namespace Field /// + /// + /// A unique URI that globally identifies this SPDX document as defined in SPDX 2.x §2.5. + /// Used to qualify element IDs when cross-referencing elements across documents. + /// Must be non-empty for a valid document. + /// public string DocumentNamespace { get; set; } = string.Empty; /// /// Document Comment Field (optional) /// + /// + /// An optional free-text comment about this document. When , + /// the field is absent from serialized output. The value is preserved during + /// deep-copy and propagated by enhance if absent from this instance. + /// public string? Comment { get; set; } /// /// Creation Information /// + /// + /// Mandatory metadata describing who created this document and when as defined in SPDX 2.x §2.7–2.9. + /// One instance is required per document. + /// public SpdxCreationInformation CreationInformation { get; set; } = new(); /// @@ -103,17 +126,28 @@ public sealed class SpdxDocument : SpdxElement /// /// Extracted Licensing Information /// + /// + /// Non-standard license texts extracted from software described by this document, as defined in + /// SPDX 2.x §10. Each entry provides a locally unique license identifier and the full text. + /// public SpdxExtractedLicensingInfo[] ExtractedLicensingInfo { get; set; } = []; /// /// Annotations /// + /// + /// Document-level reviewer or review annotations as defined in SPDX 2.x §12. + /// These annotations apply to the document itself rather than to individual elements. + /// public SpdxAnnotation[] Annotations { get; set; } = []; /// /// Files /// + /// + /// All file elements described in this SPDX document as defined in SPDX 2.x §4. + /// public SpdxFile[] Files { get; set; } = []; /// @@ -127,11 +161,19 @@ public sealed class SpdxDocument : SpdxElement /// /// Snippets /// + /// + /// All snippet elements described in this SPDX document as defined in SPDX 2.x §5. + /// public SpdxSnippet[] Snippets { get; set; } = []; /// /// Relationships /// + /// + /// Relationship elements are intentionally excluded from + /// to avoid them appearing as duplicate elements alongside the source/target elements + /// they connect. They are validated separately via the method. + /// public SpdxRelationship[] Relationships { get; set; } = []; /// @@ -145,6 +187,11 @@ public sealed class SpdxDocument : SpdxElement /// /// Make a deep-copy of this object /// + /// + /// Produces a fully independent object graph — every nested array and object is + /// recursively deep-copied so that mutations to the returned instance have no effect + /// on this instance, and vice versa. This method is stateless and does not throw. + /// /// Deep copy of this object public SpdxDocument DeepCopy() { @@ -171,6 +218,12 @@ public SpdxDocument DeepCopy() /// /// Perform validation of information /// + /// + /// Issues are appended to rather than thrown as exceptions, + /// enabling a complete diagnostic pass in a single call. The method is not thread-safe + /// if the document is mutated concurrently. When is + /// , additional NTIA minimum-element checks are performed. + /// /// List to populate with issues /// Perform NTIA validation public void Validate(List issues, bool ntia = false) @@ -267,6 +320,13 @@ public void Validate(List issues, bool ntia = false) /// /// Get the root packages this document claims to describe /// + /// + /// A package qualifies as a root package if its ID appears in the + /// array, is the target of a DESCRIBES relationship + /// from the document element, or is the source of a DESCRIBED_BY relationship + /// pointing at the document element. All three mechanisms are checked and the results + /// are unioned. + /// /// Array of packages described by this document public SpdxPackage[] GetRootPackages() { @@ -291,6 +351,13 @@ public SpdxPackage[] GetRootPackages() /// /// Get all SPDX elements in the document /// + /// + /// elements are deliberately excluded from the + /// returned sequence. Relationships are not SPDX elements in the same sense as + /// packages, files, and snippets, and including them would cause them to appear + /// alongside the elements they connect during ID-uniqueness checks and other + /// traversals. + /// /// Enumerable of all elements public IEnumerable GetAllElements() { @@ -309,6 +376,12 @@ public IEnumerable GetAllElements() /// /// Get an SPDX element by ID /// + /// + /// Returns the first element whose matches + /// , or if no matching element exists. + /// The search includes the document itself, all files, packages, snippets, and their + /// annotations. Relationships are not searched. + /// /// Element ID /// SPDX element or null public SpdxElement? GetElement(string id) @@ -319,6 +392,12 @@ public IEnumerable GetAllElements() /// /// Get an SPDX element of a specific type /// + /// + /// Delegates to and casts the result to + /// . Returns if no element with + /// exists, or if the element exists but is not of type + /// . + /// /// SPDX element type /// Element ID /// SPDX element or null @@ -328,8 +407,14 @@ public IEnumerable GetAllElements() } /// - /// Equality Comparer to test for the same relationship + /// Equality Comparer to test for the same document /// + /// + /// Two documents are considered the same when their + /// fields are equal and their root-package collections (as returned by + /// ) contain the same packages in any order, + /// compared using . + /// private sealed class SpdxDocumentSame : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxElement.cs b/src/DemaConsulting.SpdxModel/SpdxElement.cs index d451649..5717c68 100644 --- a/src/DemaConsulting.SpdxModel/SpdxElement.cs +++ b/src/DemaConsulting.SpdxModel/SpdxElement.cs @@ -25,20 +25,37 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX Element base class /// +/// +/// Acts as the abstract base for all identifiable SPDX model objects (documents, packages, +/// files, snippets, relationships, and annotations). Centralizing the identity property here +/// ensures that element lookup, traversal, and duplicate-detection logic works uniformly +/// across the entire object model. +/// public abstract class SpdxElement { /// - /// No Assertion value + /// Sentinel value indicating that a field was intentionally omitted or its value is not known. /// + /// + /// Used by optional fields throughout the SPDX model (e.g., package supplier, originator, and + /// download location) to distinguish an explicit "no assertion" from an absent value. + /// public const string NoAssertion = "NOASSERTION"; /// /// Regular expression for checking element IDs of the form "SPDXRef-name" /// + /// + /// Matches the full pattern ^SPDXRef-[a-zA-Z0-9.-]+$, which allows letters, + /// digits, hyphens, and dots after the mandatory SPDXRef- prefix. + /// Declared as static readonly so a single compiled instance is shared safely + /// across all concurrent callers. The 100 ms timeout is a ReDoS protection measure + /// against pathological input strings from untrusted SPDX sources. + /// protected static readonly Regex SpdxRefRegex = new( "^SPDXRef-[a-zA-Z0-9.-]+$", RegexOptions.None, - TimeSpan.FromMilliseconds(100)); + TimeSpan.FromMilliseconds(100)); // 100 ms timeout guards against ReDoS on untrusted SPDX identifier strings /// /// Gets or sets the Element ID @@ -52,6 +69,11 @@ public abstract class SpdxElement /// /// Enhance missing fields in the element /// + /// + /// Called by subclass Enhance methods to propagate the element identity from + /// when the current is empty. This is a no-op if + /// 's is also empty. + /// /// Other element to enhance with protected void EnhanceElement(SpdxElement other) { diff --git a/src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs b/src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs index 69a6e7e..82a2c82 100644 --- a/src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs +++ b/src/DemaConsulting.SpdxModel/SpdxExternalDocumentReference.cs @@ -50,16 +50,31 @@ public sealed class SpdxExternalDocumentReference /// /// External Document Checksum Field /// + /// + /// A cryptographic checksum of the referenced SPDX document used for integrity verification. + /// This allows consumers to confirm that the referenced document has not been modified since + /// the reference was created. + /// public SpdxChecksum Checksum { get; set; } = new(); /// /// SPDX Document URI Field /// + /// + /// The URI of the referenced external SPDX document. Must be a valid absolute URI that + /// uniquely identifies the external document's namespace. Used together with + /// to qualify cross-document element references. + /// public string Document { get; set; } = ""; /// /// Make a deep-copy of this object /// + /// + /// Produces an independent copy with no shared mutable references. The nested + /// is deep-copied so that mutations to the returned instance + /// do not affect this instance, and vice versa. + /// /// Deep copy of this object public SpdxExternalDocumentReference DeepCopy() { @@ -72,9 +87,14 @@ public SpdxExternalDocumentReference DeepCopy() } /// - /// Enhance missing fields in the checksum + /// Enhance missing fields in the external document reference /// - /// Other checksum to enhance with + /// + /// Applies a fill-if-absent strategy: each field is updated only when its current + /// value is empty or default. The nested is enhanced in place + /// using the same additive semantics. + /// + /// Other external document reference to enhance with public void Enhance(SpdxExternalDocumentReference other) { // Populate the external document ID if missing @@ -90,6 +110,13 @@ public void Enhance(SpdxExternalDocumentReference other) /// /// Enhance missing external document references in array /// + /// + /// Matching uses the comparer (Document URI equality). Entries in + /// that match an existing entry are merged in place via the + /// instance method. Entries with + /// no match are deep-copied before being appended to preserve independence from the + /// source array. + /// /// Array to enhance /// Other array to enhance with /// Updated array @@ -123,6 +150,11 @@ public static SpdxExternalDocumentReference[] Enhance(SpdxExternalDocumentRefere /// /// Perform validation of information /// + /// + /// Issues are appended to rather than thrown as exceptions, + /// enabling a complete diagnostic pass across all references in a single call. The + /// nested is validated via . + /// /// List to populate with issues public void Validate(List issues) { @@ -145,6 +177,13 @@ public void Validate(List issues) /// /// Equality Comparer to test for the same external document reference /// + /// + /// Two external document references are considered the same when their + /// URI fields are equal. + /// The field is + /// intentionally excluded from the comparison because the same external document + /// may be referenced under different local aliases in different documents. + /// private sealed class SpdxExternalDocumentReferenceSame : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxExternalReference.cs b/src/DemaConsulting.SpdxModel/SpdxExternalReference.cs index eee7245..331f59a 100644 --- a/src/DemaConsulting.SpdxModel/SpdxExternalReference.cs +++ b/src/DemaConsulting.SpdxModel/SpdxExternalReference.cs @@ -70,12 +70,19 @@ public sealed class SpdxExternalReference /// /// External Reference Comment Field (optional) /// + /// + /// Null when no comment is present. An empty string is not used. + /// public string? Comment { get; set; } /// /// Make a deep-copy of this object /// /// Deep copy of this object + /// + /// Used by the static Enhance merge to add new entries without aliasing the source array; also used by callers + /// that need an independent snapshot. + /// public SpdxExternalReference DeepCopy() { return new SpdxExternalReference @@ -90,6 +97,12 @@ public SpdxExternalReference DeepCopy() /// /// Enhance missing fields in the external reference /// + /// + /// Mutates this instance in place. is only overwritten when it + /// equals ; all other fields use fitness-based + /// selection (see ). Not thread-safe for concurrent + /// mutation of the same instance. + /// /// Other external reference to enhance with public void Enhance(SpdxExternalReference other) { @@ -112,6 +125,12 @@ public void Enhance(SpdxExternalReference other) /// /// Enhance missing external references in array /// + /// + /// Neither input array is modified; a new array is always returned. Matching uses the + /// comparer (category + type + locator). Entries in + /// that have no match in are appended as independent deep copies so + /// that mutations to the returned array do not affect the source. + /// /// Array to enhance /// Other array to enhance with /// Updated array @@ -144,6 +163,12 @@ public static SpdxExternalReference[] Enhance(SpdxExternalReference[] array, Spd /// /// Perform validation of information /// + /// + /// Issues are collected non-throwingly into the caller-supplied list. + /// The following fields are validated: (must not be + /// ), (must be non-empty), and + /// (must be non-empty). + /// /// Package name /// List to populate with issues public void Validate(string package, List issues) @@ -170,6 +195,13 @@ public void Validate(string package, List issues) /// /// Equality Comparer to test for the same external reference /// + /// + /// Equality is based on , + /// , and + /// only. is intentionally excluded so that two + /// references pointing to the same resource but carrying different annotations are still + /// recognized as the same entry during merge operations. + /// private sealed class SpdxExternalReferenceSame : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs b/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs index b3b7875..cfc84af 100644 --- a/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs +++ b/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs @@ -34,8 +34,7 @@ public sealed class SpdxExtractedLicensingInfo /// Equality comparer for the same extracted licensing info /// /// - /// This considers packages as being the same if they have the same - /// extracted text. + /// This considers extracted licensing infos as being the same if they have the same extracted text. /// public static readonly IEqualityComparer Same = new SpdxExtractedLicensingInfoSame(); @@ -60,22 +59,35 @@ public sealed class SpdxExtractedLicensingInfo /// /// License Name Field /// + /// + /// Null when no name is present. + /// public string? Name { get; set; } /// /// License Cross-Reference Field (optional) /// + /// + /// An empty array when no cross-references are present. + /// public string[] CrossReferences { get; set; } = []; /// /// License Comment Field (optional) /// + /// + /// Null when no comment is present. An empty string is not used. + /// public string? Comment { get; set; } /// /// Make a deep-copy of this object /// /// Deep copy of this object + /// + /// Used by the static Enhance merge to add new entries without aliasing the source array; also used by callers + /// that need an independent snapshot. + /// public SpdxExtractedLicensingInfo DeepCopy() { return new SpdxExtractedLicensingInfo @@ -92,6 +104,10 @@ public SpdxExtractedLicensingInfo DeepCopy() /// Enhance missing fields in the extracted licensing info /// /// Other extracted licensing info to enhance with + /// + /// Populates LicenseId, ExtractedText, Name, and Comment using fitness-based selection. + /// CrossReferences are merged by concatenation and deduplication. + /// public void Enhance(SpdxExtractedLicensingInfo other) { // Populate the license-id field if missing @@ -116,6 +132,10 @@ public void Enhance(SpdxExtractedLicensingInfo other) /// Array to enhance /// Other array to enhance with /// Updated array + /// + /// Matches existing entries by ExtractedText (via the Same comparer) and enhances them; + /// entries with no match are appended as deep copies. + /// public static SpdxExtractedLicensingInfo[] Enhance(SpdxExtractedLicensingInfo[] array, SpdxExtractedLicensingInfo[] others) { @@ -147,9 +167,13 @@ public static SpdxExtractedLicensingInfo[] Enhance(SpdxExtractedLicensingInfo[] /// Perform validation of information /// /// List to populate with issues + /// + /// Validates that LicenseId is non-empty and ExtractedText is non-empty. + /// Issues are appended to ; no exceptions are thrown. + /// public void Validate(List issues) { - // Validate Extracted License ID ID Field + // Validate Extracted License ID Field if (LicenseId.Length == 0) { issues.Add("Extracted License Information Invalid License ID Field - Empty"); @@ -165,9 +189,22 @@ public void Validate(List issues) /// /// Equality Comparer to test for the same extracted licensing info /// + /// + /// Instantiated once and held in the field. Comparison is solely by + /// ; other fields such as + /// and + /// are intentionally excluded so that + /// two entries carrying the same license text but different metadata are still recognized + /// as the same entry during merge operations. + /// private sealed class SpdxExtractedLicensingInfoSame : IEqualityComparer { /// + /// + /// Evaluation order: reference equality is checked first (returns true + /// immediately), then null-safety (either null returns false), then + /// string equality. + /// public bool Equals(SpdxExtractedLicensingInfo? l1, SpdxExtractedLicensingInfo? l2) { if (ReferenceEquals(l1, l2)) diff --git a/src/DemaConsulting.SpdxModel/SpdxFile.cs b/src/DemaConsulting.SpdxModel/SpdxFile.cs index a674407..e5ad9a0 100644 --- a/src/DemaConsulting.SpdxModel/SpdxFile.cs +++ b/src/DemaConsulting.SpdxModel/SpdxFile.cs @@ -23,6 +23,13 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX File Information Element /// +/// +/// Represents the SPDX File element, capturing per-file license, checksum, and contributor +/// information as defined by the SPDX specification. The class is sealed because the +/// SPDX data model does not define any further subtypes of a file element, and sealing +/// prevents unintended subclassing that could introduce inconsistent behavior. Not thread-safe +/// for concurrent mutation of the same instance. +/// public sealed class SpdxFile : SpdxLicenseElement { /// @@ -73,6 +80,9 @@ public sealed class SpdxFile : SpdxLicenseElement /// /// File Comment Field (optional) /// + /// + /// Null when no comment is present. An empty string is not used. + /// public string? Comment { get; set; } /// @@ -100,6 +110,13 @@ public sealed class SpdxFile : SpdxLicenseElement /// Make a deep-copy of this object /// /// Deep copy of this object + /// + /// All arrays (, , + /// , , + /// , and + /// ) are independently deep-copied; the + /// returned instance shares no mutable state with the original. + /// public SpdxFile DeepCopy() { return new SpdxFile @@ -123,6 +140,12 @@ public SpdxFile DeepCopy() /// /// Enhance missing fields in the file /// + /// + /// Non-destructive merge: existing non-empty fields are preserved. String fields use + /// fitness-based selection (concrete value > NOASSERTION > empty > null). Array fields + /// (, , ) + /// are merged by concatenation and deduplication. + /// /// Other file to enhance with public void Enhance(SpdxFile other) { @@ -154,6 +177,12 @@ public void Enhance(SpdxFile other) /// /// Enhance missing files in array /// + /// + /// Matching uses the comparer, which is keyed on + /// with a SHA1 checksum tiebreaker: two entries with the same + /// file name but differing SHA1 digests are treated as distinct. Entries in + /// that have no match are appended as independent deep copies. + /// /// Array to enhance /// Other array to enhance with /// Updated array @@ -186,6 +215,11 @@ public static SpdxFile[] Enhance(SpdxFile[] array, SpdxFile[] others) /// /// Perform validation of information /// + /// + /// Issues are appended to rather than thrown. Nested + /// and are also + /// validated by delegating to their respective Validate methods. + /// /// List to populate with issues public void Validate(List issues) { diff --git a/src/DemaConsulting.SpdxModel/SpdxFileType.cs b/src/DemaConsulting.SpdxModel/SpdxFileType.cs index 3fd4436..d703fc0 100644 --- a/src/DemaConsulting.SpdxModel/SpdxFileType.cs +++ b/src/DemaConsulting.SpdxModel/SpdxFileType.cs @@ -23,72 +23,118 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX File Type enumeration /// +/// +/// Enumerates the file types defined by the SPDX specification. Each value corresponds to +/// a canonical uppercase SPDX text constant used during serialization and deserialization. +/// public enum SpdxFileType { /// /// Human-readable source code /// + /// + /// Corresponds to the SPDX specification text constant SOURCE. + /// Source, /// /// Compiled object, target image or binary executable /// + /// + /// Corresponds to the SPDX specification text constant BINARY. + /// Binary, /// /// File represents an archive /// + /// + /// Corresponds to the SPDX specification text constant ARCHIVE. + /// Archive, /// /// Application file /// + /// + /// Corresponds to the SPDX specification text constant APPLICATION. + /// Application, /// /// Audio file /// + /// + /// Corresponds to the SPDX specification text constant AUDIO. + /// Audio, /// /// Image file /// + /// + /// Corresponds to the SPDX specification text constant IMAGE. + /// Image, /// /// Human-readable text file /// + /// + /// Corresponds to the SPDX specification text constant TEXT. + /// Text, /// /// Video file /// + /// + /// Corresponds to the SPDX specification text constant VIDEO. + /// Video, /// /// Documentation file /// + /// + /// Corresponds to the SPDX specification text constant DOCUMENTATION. + /// Documentation, /// /// SPDX document /// + /// + /// Corresponds to the SPDX specification text constant SPDX. + /// Spdx, /// /// Other type of document not matching standard categories /// + /// + /// Corresponds to the SPDX specification text constant OTHER. + /// Other } /// /// SPDX File Type Extensions /// +/// +/// Static extension companion to that provides serialization +/// helpers for mapping between the enum and the canonical uppercase SPDX document text +/// values. +/// public static class SpdxFileTypeExtensions { /// /// Convert text to SpdxFileType /// + /// + /// Matching is case-insensitive, implemented via ToUpperInvariant before the + /// switch expression. + /// /// File Type text /// SpdxFileType /// on error @@ -114,8 +160,12 @@ public static SpdxFileType FromText(string fileType) /// /// Convert SpdxFileType to text /// + /// + /// Returned strings are canonical uppercase SPDX representations per the SPDX + /// specification (e.g., SOURCE, BINARY). + /// /// SpdxFileType - /// Annotation Type text + /// File Type text /// on error public static string ToText(this SpdxFileType fileType) { diff --git a/src/DemaConsulting.SpdxModel/SpdxHelpers.cs b/src/DemaConsulting.SpdxModel/SpdxHelpers.cs index 9d5d9cf..7e4d52e 100644 --- a/src/DemaConsulting.SpdxModel/SpdxHelpers.cs +++ b/src/DemaConsulting.SpdxModel/SpdxHelpers.cs @@ -28,18 +28,31 @@ internal static partial class SpdxHelpers /// /// Regular expression for checking date/time formats (source-generated for .NET 7+) /// + /// + /// The source-generated variant is used on .NET 7+ because it is AOT-safe: the compiler + /// emits a fully static, allocation-free regex with no runtime compilation step, which is + /// required for Native AOT deployments. Stateless and thread-safe. + /// [GeneratedRegex(@"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$", RegexOptions.None, 100)] private static partial Regex DateTimeRegex(); #else /// /// Cached regular expression instance for checking date/time formats (pre-.NET 7 fallback) /// + /// + /// The instance is initialized once at type load and shared across all calls. Thread-safe + /// because instances are immutable after construction. + /// private static readonly Regex DateTimeRegexInstance = new Regex(@"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$", RegexOptions.None, TimeSpan.FromMilliseconds(100)); /// /// Regular expression for checking date/time formats /// + /// + /// Pre-.NET 7 wrapper that returns the cached to + /// provide the same call site as the source-generated variant above. + /// /// Compiled instance private static Regex DateTimeRegex() => DateTimeRegexInstance; #endif @@ -47,8 +60,18 @@ internal static partial class SpdxHelpers /// /// Test if a string is a valid SPDX date/time field (which include null/empty) /// - /// String value - /// True if valid + /// + /// Null and empty strings are treated as valid because SPDX date/time fields are optional; + /// a null or empty value means "not set", which is permitted by the specification. Non-empty + /// values are validated against the ISO 8601 UTC format (yyyy-MM-ddTHH:mm:ssZ) using + /// a regular expression. Stateless and thread-safe. + /// + /// The timestamp string to validate. Null and empty strings are treated as valid (not-set). + /// + /// Returns true if matches the ISO 8601 UTC format, or if + /// is null or empty (both treated as not-set and therefore valid); + /// false otherwise. + /// internal static bool IsValidSpdxDateTime(string? value) { return string.IsNullOrEmpty(value) || DateTimeRegex().IsMatch(value); @@ -59,6 +82,11 @@ internal static bool IsValidSpdxDateTime(string? value) /// /// String values to pick from /// Best string + /// + /// Fitness ranking: null=0, empty string=1, NOASSERTION=2, any other concrete value=3. + /// Returns the candidate with the highest fitness. When all candidates are null (or the + /// array is empty), returns null. + /// internal static string? EnhanceString(params string?[] values) { // Return the value with the highest fitness diff --git a/src/DemaConsulting.SpdxModel/SpdxLicenseElement.cs b/src/DemaConsulting.SpdxModel/SpdxLicenseElement.cs index 51c5ffa..8cd55b2 100644 --- a/src/DemaConsulting.SpdxModel/SpdxLicenseElement.cs +++ b/src/DemaConsulting.SpdxModel/SpdxLicenseElement.cs @@ -23,6 +23,12 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX Element with License /// +/// +/// Abstract intermediate base class that centralizes license-related fields (concluded +/// license, copyright text, license comments, attribution notices, and annotations) to +/// remove duplication across , , and +/// . +/// public abstract class SpdxLicenseElement : SpdxElement { /// @@ -75,6 +81,13 @@ public abstract class SpdxLicenseElement : SpdxElement /// /// Enhance missing fields in the license element /// + /// + /// String fields (ConcludedLicense, LicenseComments, CopyrightText) are selected by + /// fitness ranking: concrete value (rank 3) > NOASSERTION (rank 2) > empty string (rank 1) + /// > null (rank 0). AttributionText is merged by concatenation and deduplication. + /// Annotations are merged by identity-match (enhance existing) and append (add new). + /// Also calls EnhanceElement(other) to populate the inherited Id field if absent. + /// /// Other license element to enhance with protected void EnhanceLicenseElement(SpdxLicenseElement other) { diff --git a/src/DemaConsulting.SpdxModel/SpdxPackage.cs b/src/DemaConsulting.SpdxModel/SpdxPackage.cs index d34ed32..3ecd285 100644 --- a/src/DemaConsulting.SpdxModel/SpdxPackage.cs +++ b/src/DemaConsulting.SpdxModel/SpdxPackage.cs @@ -21,8 +21,13 @@ namespace DemaConsulting.SpdxModel; /// -/// SPDX Package class +/// Represents an SPDX package — the primary unit of a Software Bill of Materials, capturing identity, provenance, +/// licensing, and verification metadata for a software component. /// +/// +/// An is mutable. Instances are not thread-safe. The comparer matches +/// by and ; it does not consider all fields. +/// public sealed class SpdxPackage : SpdxLicenseElement { /// @@ -127,11 +132,18 @@ public sealed class SpdxPackage : SpdxLicenseElement /// /// Package Checksum Field (optional) /// + /// + /// Multiple algorithms may be present simultaneously (e.g., both SHA-1 and SHA-256) to allow consumers to verify + /// with their preferred algorithm. + /// public SpdxChecksum[] Checksums { get; set; } = []; /// /// Package Home Page Field (optional) /// + /// + /// Should be a valid URI. No URI format validation is performed during . + /// public string? HomePage { get; set; } /// @@ -184,6 +196,9 @@ public sealed class SpdxPackage : SpdxLicenseElement /// /// Package Comment Field (optional) /// + /// + /// Human-readable annotation for this package record. Not interpreted by the library. + /// public string? Comment { get; set; } /// @@ -236,6 +251,11 @@ public sealed class SpdxPackage : SpdxLicenseElement /// /// Make a deep-copy of this object /// + /// + /// All nested objects and arrays (including , , + /// , ) are deep-copied, so the caller is + /// free to mutate the result without affecting the original. + /// /// Deep copy of this object public SpdxPackage DeepCopy() { @@ -275,6 +295,11 @@ public SpdxPackage DeepCopy() /// /// Enhance missing fields in the package /// + /// + /// Field fitness ranking used when choosing which value wins: null < "" < + /// "NOASSERTION" < any concrete value. Array fields such as , + /// , and are merged by deduplication. + /// /// Other package to enhance with public void Enhance(SpdxPackage other) { @@ -352,6 +377,11 @@ public void Enhance(SpdxPackage other) /// /// Enhance missing packages in array /// + /// + /// Packages are matched using the comparer (by and + /// ). Matching packages are enhanced in place; non-matching packages from + /// are deep-copied and appended. + /// /// Array to enhance /// Other array to enhance with /// Updated array @@ -384,6 +414,12 @@ public static SpdxPackage[] Enhance(SpdxPackage[] array, SpdxPackage[] others) /// /// Perform validation of information /// + /// + /// When is non-null, each entry in is verified to exist as a file + /// ID in doc.Files. When is true, additionally calls + /// to enforce the NTIA minimum-elements requirements for and + /// . + /// /// List to populate with issues /// Optional document for checking file-references /// Perform NTIA validation @@ -480,6 +516,10 @@ public void Validate(List issues, SpdxDocument? doc, bool ntia = false) /// /// Perform NTIA validation of information /// + /// + /// This is a separate validation path because NTIA minimum-elements compliance is an opt-in check + /// (ntia flag) — not all SPDX documents need to comply with the NTIA minimum element requirements. + /// /// List to populate with issues private void ValidateNtia(List issues) { @@ -499,6 +539,13 @@ private void ValidateNtia(List issues) /// /// Equality Comparer to test for the same package /// + /// + /// Two packages are considered the same when they share the same and + /// . A dedicated nested class is used rather than an ad-hoc lambda so the + /// comparer instance can be stored in the field and passed to LINQ operations. + /// The match key intentionally excludes because different SPDX documents may + /// assign different IDs to the same logical package. + /// private sealed class SpdxPackageSame : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs b/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs index 87b19fc..d1eed13 100644 --- a/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs +++ b/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs @@ -65,6 +65,10 @@ public sealed class SpdxPackageVerificationCode /// /// Make a deep-copy of this object /// + /// + /// Both and are fully copied, so the caller is free to mutate + /// the result without affecting the original. + /// /// Deep copy of this object public SpdxPackageVerificationCode DeepCopy() { @@ -78,6 +82,10 @@ public SpdxPackageVerificationCode DeepCopy() /// /// Enhance missing fields in the verification code /// + /// + /// entries from are merged by deduplication. + /// is updated only if the current value is null or empty. + /// /// Other verification code to enhance with public void Enhance(SpdxPackageVerificationCode other) { @@ -91,20 +99,26 @@ public void Enhance(SpdxPackageVerificationCode other) /// /// Perform validation of information /// + /// + /// Validation checks that is exactly 40 hex characters (a SHA1 hex digest). No format check + /// is performed on entries. + /// /// Associated package /// List to populate with issues public void Validate(string package, List issues) { // Validate Package Verification Code Value Field - if (Value.Length != 40) + if (Value.Length != 40 || !System.Text.RegularExpressions.Regex.IsMatch(Value, "^[0-9a-fA-F]{40}$")) { issues.Add($"Package '{package}' Invalid Package Verification Code Value '{Value}'"); } } - /// - /// Equality Comparer to test for the same package verification code - /// + /// Equality comparer that considers two package verification codes the same when their Value fields are identical. + /// + /// A dedicated nested class is used rather than an ad-hoc lambda so the comparer instance can be stored in the + /// field and passed to LINQ operations without boxing or allocation. + /// private sealed class SpdxPackageVerificationCodeSame : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxReferenceCategory.cs b/src/DemaConsulting.SpdxModel/SpdxReferenceCategory.cs index d030d18..fcafa91 100644 --- a/src/DemaConsulting.SpdxModel/SpdxReferenceCategory.cs +++ b/src/DemaConsulting.SpdxModel/SpdxReferenceCategory.cs @@ -23,45 +23,83 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX Reference Category enumeration /// +/// +/// Enumerates the broad reference categories defined by the SPDX specification. +/// The sentinel (value -1) is used internally to represent an +/// unset category and must never be serialized to an SPDX document. +/// public enum SpdxReferenceCategory { /// /// Missing reference category /// + /// + /// Sentinel value indicating that no category has been assigned. This value must never + /// be serialized to an SPDX document; see + /// which throws when called with + /// this value. + /// Missing = -1, /// /// Reference for security-related information /// + /// + /// Corresponds to the SPDX specification text constant SECURITY. + /// Security, /// /// Reference for package management information /// + /// + /// Corresponds to the SPDX specification text constant PACKAGE-MANAGER. + /// PackageManager, /// /// Reference for software heritage archive persistent identifier /// + /// + /// Corresponds to the SPDX specification text constant PERSISTENT-ID. + /// PersistentId, /// /// Reference for other reasons /// + /// + /// Corresponds to the SPDX specification text constant OTHER. + /// Other } /// /// SPDX Reference Category Extensions /// +/// +/// Static extension companion to that provides +/// serialization helpers for mapping between the enum and the SPDX document text values +/// defined in the SPDX specification. +/// public static class SpdxReferenceCategoryExtensions { /// /// Convert text to SpdxReferenceCategory /// + /// + /// Matching is case-insensitive (implemented via ToUpperInvariant). Both + /// PACKAGE-MANAGER and PACKAGE_MANAGER are accepted for backward + /// compatibility with documents that use the underscore variant. + /// /// Reference Category text - /// SpdxReferenceCategory - /// on error + /// + /// Returns SpdxReferenceCategory.Missing when category is an empty string; otherwise returns the matching enum + /// value. + /// + /// + /// Thrown when is not a recognized SPDX reference category string. + /// public static SpdxReferenceCategory FromText(string category) { return category.ToUpperInvariant() switch @@ -79,9 +117,17 @@ public static SpdxReferenceCategory FromText(string category) /// /// Convert SpdxReferenceCategory to text /// + /// + /// The output is always the canonical SPDX specification string (e.g., PACKAGE-MANAGER + /// not PACKAGE_MANAGER). Calling this method with + /// always throws; callers must check for the + /// sentinel before serializing. + /// /// SpdxReferenceCategory /// Reference Category text - /// on error + /// + /// Thrown when is SpdxReferenceCategory.Missing or an unsupported enum value. + /// public static string ToText(this SpdxReferenceCategory category) { return category switch diff --git a/src/DemaConsulting.SpdxModel/SpdxRelationship.cs b/src/DemaConsulting.SpdxModel/SpdxRelationship.cs index 2a321f2..0e24d93 100644 --- a/src/DemaConsulting.SpdxModel/SpdxRelationship.cs +++ b/src/DemaConsulting.SpdxModel/SpdxRelationship.cs @@ -67,11 +67,18 @@ public sealed class SpdxRelationship : SpdxElement /// /// Relationship Comment Field /// + /// + /// Optional free-text comment providing human-readable context for the relationship. + /// public string? Comment { get; set; } /// /// Make a deep-copy of this object /// + /// + /// All scalar fields are copied by value. The returned instance is fully independent + /// of the original and may be mutated without side effects. + /// /// Deep copy of this object public SpdxRelationship DeepCopy() { @@ -87,6 +94,10 @@ public SpdxRelationship DeepCopy() /// /// Enhance missing fields in the relationship /// + /// + /// Each field in this instance is replaced only if its current value is null, empty, or the + /// sentinel. Existing non-empty values are never overwritten. + /// /// Other relationship to enhance with public void Enhance(SpdxRelationship other) { @@ -109,6 +120,11 @@ public void Enhance(SpdxRelationship other) /// /// Enhance missing relationships in array /// + /// + /// Relationships are matched using the comparer (by , + /// , and ). Matching relationships are enhanced + /// in place; non-matching relationships from are deep-copied and appended. + /// /// Array to enhance /// Other array to enhance with /// Updated array @@ -141,6 +157,12 @@ public static SpdxRelationship[] Enhance(SpdxRelationship[] array, SpdxRelations /// /// Perform validation of information /// + /// + /// Validates the SPDX element ID, the related element ID, and the relationship type. When + /// is provided, element IDs are also checked for existence within that document. + /// External references (prefixed with DocumentRef-) and NOASSERTION are accepted without + /// document lookup. + /// /// List to populate with issues /// Optional document for checking references public void Validate(List issues, SpdxDocument? doc) @@ -176,6 +198,12 @@ public void Validate(List issues, SpdxDocument? doc) /// /// Equality Comparer to test for the same relationship /// + /// + /// Two relationships are considered the same when they share the same , + /// , and . + /// A dedicated nested class is used rather than an ad-hoc lambda so the comparer instance can be stored in the + /// field and passed to LINQ operations without boxing or allocation. + /// private sealed class SpdxRelationshipSame : IEqualityComparer { /// @@ -209,6 +237,12 @@ public int GetHashCode(SpdxRelationship obj) /// /// Equality Comparer to test for the same elements /// + /// + /// Two relationships are considered to share the same elements when they have the same + /// and , + /// regardless of . Used when deduplicating by + /// element endpoints regardless of relationship kind. Backed by the field. + /// private sealed class SpdxRelationshipSameElements : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs b/src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs index 3e2797f..4870114 100644 --- a/src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs +++ b/src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs @@ -23,253 +23,408 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX Relationship Type enumeration /// +/// +/// The sentinel value (value -1) represents an unknown or uninitialized +/// relationship type during partial deserialization. It must not be serialized to SPDX output; +/// throws +/// for . +/// public enum SpdxRelationshipType { /// /// Missing relationship type /// + /// + /// Sentinel value used internally to represent an absent or uninitialized relationship type. + /// Never serialized to SPDX output. + /// Missing = -1, /// /// Element describes the related element /// + /// + /// Maps to the SPDX relationship type token DESCRIBES. + /// Describes, /// /// Element is described by the related element /// + /// + /// Maps to the SPDX relationship type token DESCRIBED_BY. + /// DescribedBy, /// /// Element contains the related element /// + /// + /// Maps to the SPDX relationship type token CONTAINS. + /// Contains, /// /// Element is contained by the related element /// + /// + /// Maps to the SPDX relationship type token CONTAINED_BY. + /// ContainedBy, /// /// Element depends on the related element /// + /// + /// Maps to the SPDX relationship type token DEPENDS_ON. + /// DependsOn, /// /// Element is a dependency of the related element /// + /// + /// Maps to the SPDX relationship type token DEPENDENCY_OF. + /// DependencyOf, /// /// Element is a manifest file that lists a set of dependencies for the related element /// + /// + /// Maps to the SPDX relationship type token DEPENDENCY_MANIFEST_OF. + /// DependencyManifestOf, /// /// Element is a build dependency of the related element /// + /// + /// Maps to the SPDX relationship type token BUILD_DEPENDENCY_OF. + /// BuildDependencyOf, /// /// Element is a development dependency of the related element /// + /// + /// Maps to the SPDX relationship type token DEV_DEPENDENCY_OF. + /// DevDependencyOf, /// /// Element is an optional dependency of the related element /// + /// + /// Maps to the SPDX relationship type token OPTIONAL_DEPENDENCY_OF. + /// OptionalDependencyOf, /// /// Element is a to-be-provided dependency of the related element /// + /// + /// Maps to the SPDX relationship type token PROVIDED_DEPENDENCY_OF. + /// ProvidedDependencyOf, /// /// Element is a test dependency of the related element /// + /// + /// Maps to the SPDX relationship type token TEST_DEPENDENCY_OF. + /// TestDependencyOf, /// /// Element is a dependency required for the execution of the related element /// + /// + /// Maps to the SPDX relationship type token RUNTIME_DEPENDENCY_OF. + /// RuntimeDependencyOf, /// /// Element is an example of the related element /// + /// + /// Maps to the SPDX relationship type token EXAMPLE_OF. + /// ExampleOf, /// /// Element generates the related element /// + /// + /// Maps to the SPDX relationship type token GENERATES. + /// Generates, /// /// Element was generated from the related element /// + /// + /// Maps to the SPDX relationship type token GENERATED_FROM. + /// GeneratedFrom, /// /// Element is an ancestor (same lineage but pre-dated) the related element /// + /// + /// Maps to the SPDX relationship type token ANCESTOR_OF. + /// AncestorOf, /// /// Element is a descendant of (same lineage but post-dates) the related element /// + /// + /// Maps to the SPDX relationship type token DESCENDANT_OF. + /// DescendantOf, /// /// Element is a variant of (same lineage but not clear which came first) the related element /// + /// + /// Maps to the SPDX relationship type token VARIANT_OF. + /// VariantOf, /// /// Element is a distribution artifact of the related element /// + /// + /// Maps to the SPDX relationship type token DISTRIBUTION_ARTIFACT. + /// DistributionArtifact, /// /// Element is a patch file for (to be applied to) the related element /// + /// + /// Maps to the SPDX relationship type token PATCH_FOR. + /// PatchFor, /// /// Element is a patch file that has been applied to the related element /// + /// + /// Maps to the SPDX relationship type token PATCH_APPLIED. + /// PatchApplied, /// /// Element is an exact copy of the related element /// + /// + /// Maps to the SPDX relationship type token COPY_OF. + /// CopyOf, /// /// Element is a file that was added to the related element /// + /// + /// Maps to the SPDX relationship type token FILE_ADDED. + /// FileAdded, /// /// Element is a file that was deleted from the related element /// + /// + /// Maps to the SPDX relationship type token FILE_DELETED. + /// FileDeleted, /// /// Element is a file that was modified from the related element /// + /// + /// Maps to the SPDX relationship type token FILE_MODIFIED. + /// FileModified, /// /// Element has been expanded from an archive file /// + /// + /// Maps to the SPDX relationship type token EXPANDED_FROM_ARCHIVE. + /// ExpandedFromArchive, /// /// Element dynamically links to the related element /// + /// + /// Maps to the SPDX relationship type token DYNAMIC_LINK. + /// DynamicLink, /// /// Element statically links to the related element /// + /// + /// Maps to the SPDX relationship type token STATIC_LINK. + /// StaticLink, /// /// Element is a data file used by the related element /// + /// + /// Maps to the SPDX relationship type token DATA_FILE_OF. + /// DataFileOf, /// - /// Element is a test cased used in testing the related element + /// Element is a test case used in testing the related element /// + /// + /// Maps to the SPDX relationship type token TEST_CASE_OF. + /// TestCaseOf, /// /// Element is used to build the related element /// + /// + /// Maps to the SPDX relationship type token BUILD_TOOL_OF. + /// BuildToolOf, /// /// Element is used as a development tool for the related element /// + /// + /// Maps to the SPDX relationship type token DEV_TOOL_OF. + /// DevToolOf, /// /// Element is used for testing the related element /// + /// + /// Maps to the SPDX relationship type token TEST_OF. + /// TestOf, /// /// Element is used as a test tool for the related element /// + /// + /// Maps to the SPDX relationship type token TEST_TOOL_OF. + /// TestToolOf, /// /// Element provides documentation of the related element /// + /// + /// Maps to the SPDX relationship type token DOCUMENTATION_OF. + /// DocumentationOf, /// /// Element is an optional component of the related element /// + /// + /// Maps to the SPDX relationship type token OPTIONAL_COMPONENT_OF. + /// OptionalComponentOf, /// /// Element is a metafile of the related element /// + /// + /// Maps to the SPDX relationship type token METAFILE_OF. + /// MetafileOf, /// /// Element is a package as part of the related element /// + /// + /// Maps to the SPDX relationship type token PACKAGE_OF. + /// PackageOf, /// /// Element is an SPDX document amending the SPDX information in the related element /// + /// + /// Maps to the SPDX relationship type token AMENDS. + /// Amends, /// /// Element is a prerequisite for the related element /// + /// + /// Maps to the SPDX relationship type token PREREQUISITE_FOR. + /// PrerequisiteFor, /// /// Element has a prerequisite of the related element /// + /// + /// Maps to the SPDX relationship type token HAS_PREREQUISITE. + /// HasPrerequisite, /// /// Element describes, illustrates, or specifies a requirement statement for the related element /// + /// + /// Maps to the SPDX relationship type token REQUIREMENT_DESCRIPTION_FOR. + /// RequirementDescriptionFor, /// /// Element describes, illustrates, or defines a design specification for the related element /// + /// + /// Maps to the SPDX relationship type token SPECIFICATION_FOR. + /// SpecificationFor, /// /// Element has a relationship with the related element described in the comment field /// + /// + /// Maps to the SPDX relationship type token OTHER. + /// Other } /// /// SPDX Relationship Type Extensions /// +/// +/// This class is stateless and thread-safe. Note the asymmetry: accepts an empty string and +/// returns , but throws for +/// . This design prevents silent serialization of sentinel values while +/// permitting partial deserialization. +/// public static class SpdxRelationshipTypeExtensions { /// /// Convert text to SpdxRelationshipType /// + /// + /// Comparison is case-insensitive. An empty or null input maps to . + /// All 44 SPDX 2.x relationship type tokens are recognized. + /// /// Relationship Type text /// SpdxRelationshipType - /// on error + /// Thrown when is not a recognized SPDX relationship type string. public static SpdxRelationshipType FromText(string relationshipType) { - return relationshipType.ToUpperInvariant() switch + return (relationshipType ?? string.Empty).ToUpperInvariant() switch { "" => SpdxRelationshipType.Missing, "DESCRIBES" => SpdxRelationshipType.Describes, @@ -324,9 +479,14 @@ public static SpdxRelationshipType FromText(string relationshipType) /// /// Convert SpdxRelationshipType to text /// + /// + /// This is an extension method so it can be called directly on a value. + /// Throws for the sentinel to prevent accidental serialization + /// of incomplete relationship data. + /// /// SpdxRelationshipType /// Relationship Type text - /// on error + /// Thrown when is or an unrecognized enum value. public static string ToText(this SpdxRelationshipType relationshipType) { return relationshipType switch diff --git a/src/DemaConsulting.SpdxModel/SpdxSnippet.cs b/src/DemaConsulting.SpdxModel/SpdxSnippet.cs index cb24a01..c41372e 100644 --- a/src/DemaConsulting.SpdxModel/SpdxSnippet.cs +++ b/src/DemaConsulting.SpdxModel/SpdxSnippet.cs @@ -49,21 +49,34 @@ public sealed class SpdxSnippet : SpdxLicenseElement /// /// Snippet Byte Range Start Field /// + /// + /// Must be ≥ 1. Validated by . Used as part of the snippet identity key for array merging. + /// public int SnippetByteStart { get; set; } /// /// Snippet Byte Range End Field /// + /// + /// Must be ≥ . Validated by . Used as part of the snippet + /// identity key for array merging. + /// public int SnippetByteEnd { get; set; } /// /// Snippet Line Range Start Field /// + /// + /// 0 signifies that the line range start is not specified. Not validated by . + /// public int SnippetLineStart { get; set; } /// /// Snippet Line Range End Field /// + /// + /// 0 signifies that the line range end is not specified. Not validated by . + /// public int SnippetLineEnd { get; set; } /// @@ -80,6 +93,9 @@ public sealed class SpdxSnippet : SpdxLicenseElement /// /// Snippet Comment Field (optional) /// + /// + /// Optional free-text comment providing human-readable context for the snippet. + /// public string? Comment { get; set; } /// @@ -93,6 +109,11 @@ public sealed class SpdxSnippet : SpdxLicenseElement /// /// Make a deep-copy of this object /// + /// + /// All nested arrays (including , , + /// ) are deep-copied, so the caller is free to mutate the result without + /// affecting the original. + /// /// Deep copy of this object public SpdxSnippet DeepCopy() { @@ -118,6 +139,10 @@ public SpdxSnippet DeepCopy() /// /// Enhance missing fields in the snippet /// + /// + /// Field fitness ranking: 0 or empty values are replaced by non-zero / non-empty values from + /// . Array fields such as are merged by deduplication. + /// /// Other snippet to enhance with public void Enhance(SpdxSnippet other) { @@ -164,6 +189,11 @@ public void Enhance(SpdxSnippet other) /// /// Enhance missing snippets in array /// + /// + /// Snippets are matched using the comparer (by , byte start, and + /// byte end). Matching snippets are enhanced in place; non-matching snippets from are + /// deep-copied and appended. + /// /// Array to enhance /// Other array to enhance with /// Updated array @@ -196,6 +226,12 @@ public static SpdxSnippet[] Enhance(SpdxSnippet[] array, SpdxSnippet[] others) /// /// Perform validation of information /// + /// + /// Validates snippet ID, , byte range ( ≥ 1, + /// ), , + /// , and annotations. Missing required fields, invalid byte ranges, + /// malformed IDs, and invalid annotations are recorded in . + /// /// List to populate with issues public void Validate(List issues) { @@ -245,6 +281,12 @@ public void Validate(List issues) /// /// Equality Comparer to test for the same snippet /// + /// + /// Two snippets are considered the same when they share the same , + /// , and . This is the backing implementation + /// for . A dedicated nested class is used rather than an ad-hoc lambda so the + /// comparer instance can be stored as a field and passed to LINQ operations without boxing or allocation. + /// private sealed class SpdxSnippetSame : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs b/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs index 2a3d78a..6827eaf 100644 --- a/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs +++ b/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs @@ -1,18 +1,75 @@ +// Copyright(c) 2024 DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + namespace DemaConsulting.SpdxModel.Transform; /// /// Transformations for SPDX relationships. /// +/// +/// This class is a stateless utility: all methods are static and carry no instance +/// state. The same instance (or the class itself) may be called concurrently from +/// multiple threads provided each call operates on a different ; +/// concurrent calls on the same document are not safe without external synchronization. +/// All mutating methods operate directly on . +/// public static class SpdxRelationships { /// /// Add new relationships to the SPDX document. /// - /// SPDX document + /// SPDX document to modify /// New relationships to add - /// True to replace existing relationships + /// + /// When true, existing relationships whose source and target match any incoming + /// relationship are removed before the new relationships are added. + /// + /// + /// Thrown when any relationship's source element ID is not found in the document, + /// or when any relationship's target element ID is not found in the document and is + /// neither NOASSERTION nor prefixed with DocumentRef-. + /// When this exception is thrown, the document is left unmodified. + /// + /// + /// All incoming relationships are validated before any mutation is performed. + /// This ensures that a validation failure leaves the document in its original state + /// (atomic with respect to the batch). Replacement uses + /// (type-agnostic, matches by source and + /// target ID only) so that a replace-and-add can change the relationship type between + /// the same pair of elements. Individual deduplication within the add path uses + /// (type-inclusive) so that relationships of + /// different types between the same elements co-exist correctly. + /// Side-effect: mutates .. + /// public static void Add(SpdxDocument document, IEnumerable relationships, bool replace = false) { + // Materialize the enumerable so it can be iterated multiple times + var incoming = relationships.ToArray(); + + // Pre-validate all relationships before making any mutations. + // This ensures the document is left unmodified if any relationship is invalid. + foreach (var relationship in incoming) + { + ValidateRelationship(document, relationship); + } + // Handle replacing existing relationships if (replace) { @@ -20,24 +77,59 @@ public static void Add(SpdxDocument document, IEnumerable rela document.Relationships = [ ..document.Relationships - .Where(r => !relationships.Any(r2 => SpdxRelationship.SameElements.Equals(r, r2))) + .Where(r => !incoming.Any(r2 => SpdxRelationship.SameElements.Equals(r, r2))) ]; } - // Add the new relationships - foreach (var relationship in relationships) + // Add the new relationships (validation already passed, so no exceptions expected here) + foreach (var relationship in incoming) { - Add(document, relationship); + AddValidated(document, relationship); } } /// /// Add a relationship to the SPDX document. /// - /// SPDX document + /// SPDX document to modify /// SPDX relationship to add - /// Thrown if the relationship argument is invalid + /// + /// Thrown when the relationship's source element ID () + /// is not found in the document, or when the target element ID + /// () is not found in the document and + /// is neither NOASSERTION nor prefixed with DocumentRef-. + /// + /// + /// If a relationship with the same source, target, and type already exists (as determined + /// by ), the existing entry is enhanced with any + /// additional field values from . Otherwise a deep copy is + /// appended to . + /// Side-effect: mutates .. + /// public static void Add(SpdxDocument document, SpdxRelationship relationship) + { + // Validate before any mutation + ValidateRelationship(document, relationship); + + // Perform the actual add (validation already passed) + AddValidated(document, relationship); + } + + /// + /// Validates a relationship against the document without mutating it. + /// + /// + /// This method performs all ID-existence checks WITHOUT mutating the document. + /// It MUST be called before any mutation (replacement or addition) to preserve + /// the atomicity guarantee: if validation fails the document remains unchanged. + /// + /// SPDX document providing the element registry + /// Relationship to validate + /// + /// Thrown when the source element ID is not found in the document, or when the target + /// element ID is not found and is neither NOASSERTION nor DocumentRef--prefixed. + /// + private static void ValidateRelationship(SpdxDocument document, SpdxRelationship relationship) { // Ensure the relationship ID matches an element if (document.GetElement(relationship.Id) == null) @@ -53,7 +145,20 @@ public static void Add(SpdxDocument document, SpdxRelationship relationship) throw new ArgumentException($"Element {relationship.RelatedSpdxElement} not found in SPDX document", nameof(relationship)); } + } + /// + /// Adds a pre-validated relationship to the document (no validation performed). + /// + /// + /// Callers MUST invoke on + /// before calling this method. No further ID validation is performed here; passing an + /// unvalidated relationship may produce an inconsistent document. + /// + /// SPDX document to modify + /// Already-validated relationship to add + private static void AddValidated(SpdxDocument document, SpdxRelationship relationship) + { // Look for an existing relationship var existing = Array.Find(document.Relationships, r => SpdxRelationship.Same.Equals(r, relationship)); if (existing != null) diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs index 797329e..02db53a 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs @@ -37,14 +37,14 @@ public void Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocu // Arrange: Load the SPDX 2.2 JSON example from embedded resources var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.2.spdx.json"); + + // Act: Deserialize the JSON document var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); - // Act: Validate the document + // Assert: Verify that the document is valid + Assert.IsNotNull(doc); var issues = new List(); doc.Validate(issues); - - // Assert: Verify that there are no validation issues Assert.IsEmpty(issues); // Assert: Verify the document properties diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs index 80cde95..538e051 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs @@ -37,14 +37,14 @@ public void Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocu // Arrange: Get the SPDX 2.3 JSON example from embedded resources var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + + // Act: Deserialize the JSON document var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); - // Act: Validate the document + // Assert: Verify that the document is valid + Assert.IsNotNull(doc); var issues = new List(); doc.Validate(issues); - - // Assert: Verify that there are no validation issues Assert.IsEmpty(issues); // Assert: Verify document diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs new file mode 100644 index 0000000..0901e89 --- /dev/null +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs @@ -0,0 +1,60 @@ +// Copyright(c) 2024 DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Text.Json; +using DemaConsulting.SpdxModel.IO; + +namespace DemaConsulting.SpdxModel.Tests.IO; + +/// +/// Error-path tests for . +/// +/// +/// Covers the error-handling paths of : invalid JSON +/// input that should throw rather than return a partially-populated document. +/// +[TestClass] +public class Spdx2JsonDeserializerTests +{ + /// + /// Tests that deserializing malformed JSON throws a . + /// + /// + /// Confirms that syntactically broken JSON (missing closing brace) causes a + /// rather than a silent failure. + /// + [TestMethod] + public void Spdx2JsonDeserializer_Deserialize_MalformedJsonThrowsJsonException() + { + // Arrange + const string malformedJson = "{ not valid json"; + + // Act / Assert + try + { + Spdx2JsonDeserializer.Deserialize(malformedJson); + Assert.Fail("Expected JsonException was not thrown"); + } + catch (JsonException) + { + // Pass + } + } +} diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs index b828c44..cc8c592 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs @@ -99,4 +99,31 @@ public void Spdx2JsonSerializer_SerializeAnnotations_CorrectResults() SpdxJsonHelpers.AssertEqual("OTHER", json[1]?["annotationType"]); SpdxJsonHelpers.AssertEqual("This is another comment", json[1]?["comment"]); } + + /// + /// Tests that an annotation with no ID omits the SPDXID field from the serialized JSON. + /// + [TestMethod] + public void Spdx2JsonSerializer_SerializeAnnotation_NoId_OmitsSpdxId() + { + // Arrange: Create an annotation with no ID (empty string) + var annotation = new SpdxAnnotation + { + Id = string.Empty, + Annotator = "John Doe", + Date = "2021-09-01T12:00:00Z", + Type = SpdxAnnotationType.Review, + Comment = "This is a comment" + }; + + // Act: Serialize the annotation to JSON + var json = Spdx2JsonSerializer.SerializeAnnotation(annotation); + + // Assert: SPDXID is absent and other fields are present + Assert.IsNull(json["SPDXID"]); + SpdxJsonHelpers.AssertEqual("John Doe", json["annotator"]); + SpdxJsonHelpers.AssertEqual("2021-09-01T12:00:00Z", json["annotationDate"]); + SpdxJsonHelpers.AssertEqual("REVIEW", json["annotationType"]); + SpdxJsonHelpers.AssertEqual("This is a comment", json["comment"]); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs index ceb7a82..48263cb 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs @@ -87,6 +87,13 @@ public void Spdx2JsonSerializer_SerializeDocument_CorrectResults() SpdxJsonHelpers.AssertEqual( "http://spdx.org/spdxdocs/spdx-example-json-2.3-444504E0-4F89-41D3-9A0C-0305E82C3301", json["documentNamespace"]); + + // Assert: Verify creationInfo fields + SpdxJsonHelpers.AssertEqual("2010-01-29T18:30:22Z", json["creationInfo"]?["created"]); + SpdxJsonHelpers.AssertEqual("Tool: LicenseFind-1.0", json["creationInfo"]?["creators"]?[0]); + SpdxJsonHelpers.AssertEqual("Organization: ExampleCodeInspect ()", json["creationInfo"]?["creators"]?[1]); + SpdxJsonHelpers.AssertEqual("Person: Jane Doe ()", json["creationInfo"]?["creators"]?[2]); + SpdxJsonHelpers.AssertEqual("3.17", json["creationInfo"]?["licenseListVersion"]); } /// @@ -149,5 +156,12 @@ public void Spdx2JsonSerializer_Serialize_CorrectResults() SpdxJsonHelpers.AssertEqual( "http://spdx.org/spdxdocs/spdx-example-json-2.3-444504E0-4F89-41D3-9A0C-0305E82C3301", json["documentNamespace"]); + + // Assert: Verify creationInfo fields + SpdxJsonHelpers.AssertEqual("2010-01-29T18:30:22Z", json["creationInfo"]?["created"]); + SpdxJsonHelpers.AssertEqual("Tool: LicenseFind-1.0", json["creationInfo"]?["creators"]?[0]); + SpdxJsonHelpers.AssertEqual("Organization: ExampleCodeInspect ()", json["creationInfo"]?["creators"]?[1]); + SpdxJsonHelpers.AssertEqual("Person: Jane Doe ()", json["creationInfo"]?["creators"]?[2]); + SpdxJsonHelpers.AssertEqual("3.17", json["creationInfo"]?["licenseListVersion"]); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs index 752aa53..82e4159 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs @@ -114,7 +114,7 @@ public void Spdx2JsonSerializer_SerializeFile_CorrectResults() [TestMethod] public void Spdx2JsonSerializer_SerializeFiles_CorrectResults() { - // Arrange + // Arrange: Create a sample array containing a single SpdxFile with all fields populated var file = new[] { new SpdxFile @@ -161,10 +161,10 @@ public void Spdx2JsonSerializer_SerializeFiles_CorrectResults() } }; - // Act + // Act: Serialize the array of files to JSON var json = Spdx2JsonSerializer.SerializeFiles(file); - // Assert + // Assert: Verify the JSON output has the expected structure and values Assert.IsNotNull(json); Assert.AreEqual(1, json.Count); SpdxJsonHelpers.AssertEqual("SPDXRef-DoapSource", json[0]?["SPDXID"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs index 8e54276..e7c7525 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs @@ -127,4 +127,109 @@ public void Spdx2JsonSerializer_SerializeSnippets_CorrectResults() SpdxJsonHelpers.AssertEqual("SnippetFromFile", json[0]?["ranges"]?[1]?["startPointer"]?["reference"]); SpdxJsonHelpers.AssertEqual("3", json[0]?["ranges"]?[1]?["startPointer"]?["lineNumber"]); } + + /// + /// Tests serializing a snippet that includes an annotation, covering the annotation branch. + /// + [TestMethod] + public void Spdx2JsonSerializer_SerializeSnippet_WithAnnotation_IncludesAnnotation() + { + // Arrange: Create a snippet with a single annotation + var snippet = new SpdxSnippet + { + Id = "SPDXRef-SnippetAnnotated", + SnippetFromFile = "SPDXRef-SourceFile", + SnippetByteStart = 10, + SnippetByteEnd = 20, + ConcludedLicense = "MIT", + LicenseInfoInSnippet = ["MIT"], + CopyrightText = "Copyright 2024", + Annotations = + [ + new SpdxAnnotation + { + Annotator = "Tool: TestTool", + Date = "2024-01-01T00:00:00Z", + Type = SpdxAnnotationType.Review, + Comment = "Reviewed this snippet" + } + ] + }; + + // Act: Serialize the snippet to JSON + var json = Spdx2JsonSerializer.SerializeSnippet(snippet); + + // Assert: Verify the annotation is present in the serialized output + Assert.IsNotNull(json); + Assert.IsNotNull(json["annotations"], "annotations array should be present"); + SpdxJsonHelpers.AssertEqual("Tool: TestTool", json["annotations"]?[0]?["annotator"]); + SpdxJsonHelpers.AssertEqual("2024-01-01T00:00:00Z", json["annotations"]?[0]?["annotationDate"]); + SpdxJsonHelpers.AssertEqual("REVIEW", json["annotations"]?[0]?["annotationType"]); + SpdxJsonHelpers.AssertEqual("Reviewed this snippet", json["annotations"]?[0]?["comment"]); + } + + /// + /// Tests that a snippet with both line values set to zero emits only the byte-range entry. + /// + [TestMethod] + public void Spdx2JsonSerializer_SerializeSnippet_NoLineRange_EmitsByteRangeOnly() + { + // Arrange: Create a snippet with zero line values + var snippet = new SpdxSnippet + { + Id = "SPDXRef-Snippet", + SnippetFromFile = "SPDXRef-SourceFile", + SnippetByteStart = 10, + SnippetByteEnd = 20, + SnippetLineStart = 0, + SnippetLineEnd = 0, + ConcludedLicense = "MIT", + LicenseInfoInSnippet = ["MIT"], + CopyrightText = "Copyright 2024" + }; + + // Act: Serialize the snippet to JSON + var json = Spdx2JsonSerializer.SerializeSnippet(snippet); + + // Assert: Only one ranges entry (byte-range) is present — no line-range + Assert.IsNotNull(json); + Assert.IsNotNull(json["ranges"]); + Assert.AreEqual(1, json["ranges"]!.AsArray().Count); + SpdxJsonHelpers.AssertEqual("10", json["ranges"]?[0]?["startPointer"]?["offset"]); + SpdxJsonHelpers.AssertEqual("20", json["ranges"]?[0]?["endPointer"]?["offset"]); + Assert.IsNull(json["ranges"]?[0]?["startPointer"]?["lineNumber"]); + } + + /// + /// Tests that a snippet with only one line value non-zero emits only the byte-range entry + /// (verifies AND logic — both values must be non-zero for line-range to be emitted). + /// + [TestMethod] + public void Spdx2JsonSerializer_SerializeSnippet_PartialLineRange_EmitsByteRangeOnly() + { + // Arrange: Create a snippet where only one line value is non-zero + var snippet = new SpdxSnippet + { + Id = "SPDXRef-Snippet", + SnippetFromFile = "SPDXRef-SourceFile", + SnippetByteStart = 10, + SnippetByteEnd = 20, + SnippetLineStart = 5, + SnippetLineEnd = 0, + ConcludedLicense = "MIT", + LicenseInfoInSnippet = ["MIT"], + CopyrightText = "Copyright 2024" + }; + + // Act: Serialize the snippet to JSON + var json = Spdx2JsonSerializer.SerializeSnippet(snippet); + + // Assert: Only one ranges entry (byte-range) is present — partial line-range is not emitted + Assert.IsNotNull(json); + Assert.IsNotNull(json["ranges"]); + Assert.AreEqual(1, json["ranges"]!.AsArray().Count); + SpdxJsonHelpers.AssertEqual("10", json["ranges"]?[0]?["startPointer"]?["offset"]); + SpdxJsonHelpers.AssertEqual("20", json["ranges"]?[0]?["endPointer"]?["offset"]); + Assert.IsNull(json["ranges"]?[0]?["startPointer"]?["lineNumber"]); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs b/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs index aa37d3e..05e62b6 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs @@ -75,4 +75,27 @@ public void SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidD roundTripped.Validate(issues); Assert.IsEmpty(issues); } + + /// + /// Tests that malformed JSON throws a JsonException during deserialization. + /// + [TestMethod] + public void SpdxModelIO_ReadSpdxJson_InvalidJson_ThrowsJsonException() + { + // Arrange: Prepare malformed JSON text + const string malformedJson = "{ not valid json at all }"; + + // Act / Assert: Deserialize should throw a JsonException or derived type + var threw = false; + try + { + Spdx2JsonDeserializer.Deserialize(malformedJson); + } + catch (System.Text.Json.JsonException) + { + threw = true; + } + + Assert.IsTrue(threw, "Expected JsonException was not thrown."); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs index 33dd4c1..158f9a7 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs @@ -55,12 +55,12 @@ public void SpdxAnnotation_SameComparer_ComparesCorrectly() Type = SpdxAnnotationType.Other }; - // Assert: Verify annotations compare to themselves + // Act / Assert: Verify annotations compare to themselves Assert.IsTrue(SpdxAnnotation.Same.Equals(a1, a1)); Assert.IsTrue(SpdxAnnotation.Same.Equals(a2, a2)); Assert.IsTrue(SpdxAnnotation.Same.Equals(a3, a3)); - // Assert: Verify annotations compare correctly + // Act / Assert: Verify annotations compare correctly Assert.IsTrue(SpdxAnnotation.Same.Equals(a1, a2)); Assert.IsTrue(SpdxAnnotation.Same.Equals(a2, a1)); Assert.IsFalse(SpdxAnnotation.Same.Equals(a1, a3)); @@ -68,7 +68,7 @@ public void SpdxAnnotation_SameComparer_ComparesCorrectly() Assert.IsFalse(SpdxAnnotation.Same.Equals(a2, a3)); Assert.IsFalse(SpdxAnnotation.Same.Equals(a3, a2)); - // Assert: Verify same annotations have identical hashes + // Act / Assert: Verify same annotations have identical hashes Assert.AreEqual(SpdxAnnotation.Same.GetHashCode(a1), SpdxAnnotation.Same.GetHashCode(a2)); } @@ -251,6 +251,9 @@ public void SpdxAnnotation_Validate_InvalidComment() [TestMethod] public void SpdxAnnotationTypeExtensions_FromText_Valid() { + // Arrange: no setup needed - testing pure string-to-enum conversion + + // Act / Assert: each recognized text converts to the correct enum value Assert.AreEqual(SpdxAnnotationType.Missing, SpdxAnnotationTypeExtensions.FromText("")); Assert.AreEqual(SpdxAnnotationType.Review, SpdxAnnotationTypeExtensions.FromText("REVIEW")); Assert.AreEqual(SpdxAnnotationType.Review, SpdxAnnotationTypeExtensions.FromText("review")); @@ -266,6 +269,9 @@ public void SpdxAnnotationTypeExtensions_FromText_Valid() [TestMethod] public void SpdxAnnotationTypeExtensions_FromText_Invalid() { + // Arrange: no setup needed — testing pure string-to-enum conversion error path + + // Act / Assert: var exception = Assert.ThrowsExactly(() => SpdxAnnotationTypeExtensions.FromText("invalid")); Assert.AreEqual("Unsupported SPDX Annotation Type 'invalid'", exception.Message); @@ -277,6 +283,9 @@ public void SpdxAnnotationTypeExtensions_FromText_Invalid() [TestMethod] public void SpdxAnnotationTypeExtensions_ToText_Valid() { + // Arrange: no setup needed - testing pure enum-to-string conversion + + // Act / Assert: each recognized enum value converts to the expected text Assert.AreEqual("REVIEW", SpdxAnnotationType.Review.ToText()); Assert.AreEqual("OTHER", SpdxAnnotationType.Other.ToText()); } @@ -288,7 +297,25 @@ public void SpdxAnnotationTypeExtensions_ToText_Valid() [TestMethod] public void SpdxAnnotationTypeExtensions_ToText_Invalid() { + // Arrange: no setup needed - testing pure enum-to-string conversion error path + + // Act / Assert: an unknown numeric enum value throws var exception = Assert.ThrowsExactly(() => ((SpdxAnnotationType)1000).ToText()); Assert.AreEqual("Unsupported SPDX Annotation Type '1000'", exception.Message); } + + /// + /// Tests that throws for the + /// sentinel value. + /// + [TestMethod] + public void SpdxAnnotationTypeExtensions_ToText_Missing() + { + // Arrange: no setup needed - testing the Missing sentinel value error path + + // Act / Assert: Missing throws with the expected message + var exception = Assert.ThrowsExactly( + () => SpdxAnnotationType.Missing.ToText()); + Assert.AreEqual("Attempt to serialize missing SPDX Annotation Type", exception.Message); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs index 64e4450..af66de2 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs @@ -30,8 +30,9 @@ public class SpdxChecksumTests /// Tests the comparer compares checksums correctly. /// [TestMethod] - public void SpdxChecksum_SameComparer_ComparesCorrectly() + public void SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquality() { + // Arrange: Create three checksums with different algorithm/value combinations var c1 = new SpdxChecksum { Algorithm = SpdxChecksumAlgorithm.Sha1, @@ -50,12 +51,12 @@ public void SpdxChecksum_SameComparer_ComparesCorrectly() Value = "624c1abb3664f4b35547e7c73864ad24" }; - // Assert checksums compare to themselves + // Assert: Verify checksums compare to themselves Assert.IsTrue(SpdxChecksum.Same.Equals(c1, c1)); Assert.IsTrue(SpdxChecksum.Same.Equals(c2, c2)); Assert.IsTrue(SpdxChecksum.Same.Equals(c3, c3)); - // Assert checksums compare correctly + // Assert: Verify checksums compare correctly Assert.IsTrue(SpdxChecksum.Same.Equals(c1, c2)); Assert.IsTrue(SpdxChecksum.Same.Equals(c2, c1)); Assert.IsFalse(SpdxChecksum.Same.Equals(c1, c3)); @@ -71,7 +72,7 @@ public void SpdxChecksum_SameComparer_ComparesCorrectly() /// Tests the method successfully creates a deep copy. /// [TestMethod] - public void SpdxChecksum_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInstance() { // Arrange: Create a checksum instance var c1 = new SpdxChecksum @@ -97,7 +98,7 @@ public void SpdxChecksum_DeepCopy_CreatesEqualButDistinctInstance() /// correctly. /// [TestMethod] - public void SpdxChecksum_Enhance_AddsOrUpdatesInformationCorrectly() + public void SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformation() { // Arrange: Create an original checksum var checksums = new[] @@ -137,7 +138,7 @@ public void SpdxChecksum_Enhance_AddsOrUpdatesInformationCorrectly() /// Tests the method reports bad algorithms. /// [TestMethod] - public void SpdxChecksum_Validate_InvalidAlgorithm() + public void SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue() { // Arrange: Create a bad instance var checksum = new SpdxChecksum @@ -158,7 +159,7 @@ public void SpdxChecksum_Validate_InvalidAlgorithm() /// Tests the method reports bad values. /// [TestMethod] - public void SpdxChecksum_Validate_InvalidValue() + public void SpdxChecksum_Validate_EmptyValue_ReportsValueIssue() { // Arrange: Create a bad instance var checksum = new SpdxChecksum @@ -175,12 +176,36 @@ public void SpdxChecksum_Validate_InvalidValue() Assert.Contains(issue => issue.Contains("Test Invalid Checksum Value Field - Empty"), issues); } + /// + /// Tests the method reports unknown numeric algorithms. + /// + [TestMethod] + public void SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue() + { + // Arrange: Create a checksum with an out-of-range numeric algorithm value + var checksum = new SpdxChecksum + { + Algorithm = (SpdxChecksumAlgorithm)1000, + Value = "c2b4e1c67a2d28fced849ee1bb76e7391b93f125" + }; + + // Act: Perform validation on the SpdxChecksum instance + var issues = new List(); + checksum.Validate("Test", issues); + + // Assert: Verify that the validation reports the unknown algorithm + Assert.Contains(issue => issue.Contains("Test Invalid Checksum Algorithm Field - Unknown"), issues); + } + /// /// Tests the method. /// [TestMethod] - public void SpdxChecksumAlgorithmExtensions_FromText_Valid() + public void SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_ReturnsCorrectEnumValues() { + // Arrange: Known algorithm strings are implicit in the Act/Assert pairs below + + // Assert: Verify each known algorithm string maps to the correct enum value Assert.AreEqual(SpdxChecksumAlgorithm.Missing, SpdxChecksumAlgorithmExtensions.FromText("")); Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("SHA1")); Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("sha1")); @@ -207,8 +232,12 @@ public void SpdxChecksumAlgorithmExtensions_FromText_Valid() /// Tests the method. /// [TestMethod] - public void SpdxChecksumAlgorithmExtensions_FromText_InvalidAlgorithm() + public void SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_ThrowsInvalidOperationException() { + // Arrange: Use an algorithm string that is not in the known-algorithm list + // (No variable needed — the input is inlined directly into the Act / Assert.) + + // Act / Assert: Verify that FromText throws for an unrecognized algorithm string var exception = Assert.ThrowsExactly(() => SpdxChecksumAlgorithmExtensions.FromText("unknown")); Assert.AreEqual("Unsupported SPDX Checksum Algorithm 'unknown'", exception.Message); @@ -218,8 +247,11 @@ public void SpdxChecksumAlgorithmExtensions_FromText_InvalidAlgorithm() /// Tests the method. /// [TestMethod] - public void SpdxChecksumAlgorithmExtensions_ToText_Valid() + public void SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCorrectStrings() { + // Arrange: Known algorithm enum values are implicit in the Act/Assert pairs below + + // Assert: Verify each known algorithm enum maps to the correct string Assert.AreEqual("SHA1", SpdxChecksumAlgorithm.Sha1.ToText()); Assert.AreEqual("SHA224", SpdxChecksumAlgorithm.Sha224.ToText()); Assert.AreEqual("SHA256", SpdxChecksumAlgorithm.Sha256.ToText()); @@ -243,9 +275,103 @@ public void SpdxChecksumAlgorithmExtensions_ToText_Valid() /// Tests the method. /// [TestMethod] - public void SpdxChecksumAlgorithmExtensions_ToText_InvalidAlgorithm() + public void SpdxChecksumAlgorithmExtensions_ToText_OutOfRangeEnum_ThrowsInvalidOperationException() { + // Arrange: Use a numeric enum value that has no named member in SpdxChecksumAlgorithm + // (No variable needed — the value is inlined directly into the Act / Assert.) + + // Act / Assert: Verify that ToText throws for an out-of-range enum value var exception = Assert.ThrowsExactly(() => ((SpdxChecksumAlgorithm)1000).ToText()); Assert.AreEqual("Unsupported SPDX Checksum Algorithm '1000'", exception.Message); } + + /// + /// Tests the method throws for + /// . + /// + [TestMethod] + public void SpdxChecksumAlgorithmExtensions_ToText_MissingAlgorithm_ThrowsInvalidOperationException() + { + // Arrange: Use the Missing sentinel value, which must never be serialized + + // Act / Assert: Verify that ToText throws for the Missing sentinel + var exception = Assert.ThrowsExactly( + () => SpdxChecksumAlgorithm.Missing.ToText()); + Assert.AreEqual("Attempt to serialize missing SPDX Checksum Algorithm", exception.Message); + } + + /// + /// Tests that returns false when the first argument is null. + /// + [TestMethod] + public void SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse() + { + // Arrange: Create one valid checksum and one null reference + var c1 = new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.Sha1, + Value = "c2b4e1c67a2d28fced849ee1bb76e7391b93f125" + }; + SpdxChecksum? nullChecksum = null; + + // Act: Compare null with a valid checksum + var result = SpdxChecksum.Same.Equals(nullChecksum!, c1); + + // Assert: Verify null-first comparison returns false + Assert.IsFalse(result); + } + + /// + /// Tests that returns false when the second argument is null. + /// + [TestMethod] + public void SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse() + { + // Arrange: Create one valid checksum and one null reference + var c1 = new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.Sha1, + Value = "c2b4e1c67a2d28fced849ee1bb76e7391b93f125" + }; + SpdxChecksum? nullChecksum = null; + + // Act: Compare a valid checksum with null + var result = SpdxChecksum.Same.Equals(c1, nullChecksum!); + + // Assert: Verify null-second comparison returns false + Assert.IsFalse(result); + } + + /// + /// Tests that returns true when both arguments are null. + /// + [TestMethod] + public void SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue() + { + // Arrange: Two null references + SpdxChecksum? c1 = null; + SpdxChecksum? c2 = null; + + // Act: Compare null with null + var result = SpdxChecksum.Same.Equals(c1!, c2!); + + // Assert: Verify null-null comparison returns true + Assert.IsTrue(result); + } + + /// + /// Tests that returns Missing for an empty string. + /// + [TestMethod] + public void SpdxChecksumAlgorithmExtensions_FromText_EmptyString_ReturnsMissing() + { + // Arrange: An empty string + var input = ""; + + // Act: Convert the empty string to an algorithm + var result = SpdxChecksumAlgorithmExtensions.FromText(input); + + // Assert: Verify empty string maps to Missing + Assert.AreEqual(SpdxChecksumAlgorithm.Missing, result); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs index f18533f..46ab302 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs @@ -23,12 +23,23 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Unit tests for . Each test is self-contained +/// with no shared state and no external dependencies. Tests cover deep copy, enhance, +/// validate, and edge-case behaviors. +/// [TestClass] public class SpdxCreationInformationTests { /// /// Tests the method successfully creates a deep copy /// + /// + /// Exercises the deep-copy path with all four fields populated (two or more creators, + /// a created timestamp, a comment, and a license-list version). Verifying that both + /// the top-level reference and the Creators array reference are distinct confirms that + /// no shallow-copy aliasing occurs. + /// [TestMethod] public void SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance() { @@ -58,6 +69,11 @@ public void SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance() /// /// Tests the method adds or updates information correctly /// + /// + /// Exercises the enhance path where the base instance is missing a creator and the + /// LicenseListVersion field. The source instance provides both, allowing the test to + /// confirm additive merging of creators and fill-if-absent semantics for scalar fields. + /// [TestMethod] public void SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly() { @@ -90,6 +106,11 @@ public void SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly() /// /// Tests the method reports missing creators. /// + /// + /// Boundary test: an empty Creators array is the minimal invalid state. Chosen to + /// confirm that the absence of any creator entry is caught independently of other + /// field values. + /// [TestMethod] public void SpdxCreationInformation_Validate_MissingCreators() { @@ -112,6 +133,11 @@ public void SpdxCreationInformation_Validate_MissingCreators() /// /// Tests the method reports invalid creators. /// + /// + /// Exercises the per-entry validation rule that each creator must start with + /// Person:, Organization:, or Tool:. The input "BadCreator" + /// fails all three prefixes, making the expected issue deterministic. + /// [TestMethod] public void SpdxCreationInformation_Validate_InvalidCreator() { @@ -134,6 +160,11 @@ public void SpdxCreationInformation_Validate_InvalidCreator() /// /// Tests the method reports invalid created dates. /// + /// + /// Exercises the Created field validation rule. The value "BadDate" is + /// chosen because it is unambiguously non-empty and non-conforming, confirming that + /// the regex/helper rejects it without false negatives. + /// [TestMethod] public void SpdxCreationInformation_Validate_InvalidCreatedDate() { @@ -156,6 +187,11 @@ public void SpdxCreationInformation_Validate_InvalidCreatedDate() /// /// Tests the method reports invalid versions. /// + /// + /// Exercises the LicenseListVersion field validation rule. The value + /// "BadVersion" does not match the \d+\.\d+ pattern and confirms + /// that the regex rejects non-numeric version strings. + /// [TestMethod] public void SpdxCreationInformation_Validate_InvalidVersion() { @@ -175,4 +211,90 @@ public void SpdxCreationInformation_Validate_InvalidVersion() // Assert: Verify that the validation reports the invalid license list version Assert.Contains(issue => issue.Contains("Document Invalid License List Version Field 'BadVersion'"), issues); } + + /// + /// Tests the method reports no issues for valid information. + /// + /// + /// Happy-path test using a fully-populated valid instance. Confirms that no + /// spurious validation issues are reported when all fields satisfy their + /// respective rules. + /// + [TestMethod] + public void SpdxCreationInformation_Validate_ValidInformation_NoIssues() + { + // Arrange: Create valid creation information + var info = new SpdxCreationInformation + { + Creators = ["Tool: LicenseFind-1.0", "Organization: ExampleCodeInspect ()"], + Created = "2010-01-29T18:30:22Z", + Comment = "This package has been shipped in source and binary form.", + LicenseListVersion = "3.9" + }; + + // Act: Perform validation on the SpdxCreationInformation instance + var issues = new List(); + info.Validate(issues); + + // Assert: Verify that no issues are reported + Assert.IsEmpty(issues); + } + + /// + /// Tests the method accepts an empty Created field. + /// + /// + /// Boundary test: an empty Created field is permitted for partially-constructed + /// documents. Confirms that the validator does not report a date-format issue when + /// the field is intentionally left blank. + /// + [TestMethod] + public void SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue() + { + // Arrange: Create creation information with an empty Created field + var info = new SpdxCreationInformation + { + Creators = ["Tool: LicenseFind-1.0"], + Created = "" + }; + + // Act: Perform validation + var issues = new List(); + info.Validate(issues); + + // Assert: Verify that no date-related issue is reported (empty Created is permitted) + Assert.IsFalse(issues.Any(issue => issue.Contains("Invalid Created Field"))); + } + + /// + /// Tests the method deduplicates creators. + /// + /// + /// Exercises the deduplication branch of the Enhance method. The base and source + /// instances share one common creator, allowing the test to confirm that the merged + /// Creators array contains exactly three distinct entries without duplicates. + /// + [TestMethod] + public void SpdxCreationInformation_Enhance_DuplicateCreators_DeduplicatesCreators() + { + // Arrange: Create creation information with an initial creator list that contains a duplicate + var info = new SpdxCreationInformation + { + Creators = ["Tool: LicenseFind-1.0", "Organization: ExampleCodeInspect ()"], + Created = "2010-01-29T18:30:22Z" + }; + + // Act: Enhance with an overlapping creator list + info.Enhance( + new SpdxCreationInformation + { + Creators = ["Tool: LicenseFind-1.0", "Person: Jane Doe ()"] + }); + + // Assert: Verify that duplicate creators are removed and unique entries are preserved + Assert.HasCount(3, info.Creators); + Assert.IsTrue(info.Creators.Contains("Tool: LicenseFind-1.0")); + Assert.IsTrue(info.Creators.Contains("Organization: ExampleCodeInspect ()")); + Assert.IsTrue(info.Creators.Contains("Person: Jane Doe ()")); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs index fbbbe8b..71f1e7e 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs @@ -143,12 +143,12 @@ public void SpdxDocument_SameComparer_ComparesCorrectly() ] }; - // Assert: Verify documents compare to themselves + // Act / Assert: Verify documents compare to themselves Assert.IsTrue(SpdxDocument.Same.Equals(d1, d1)); Assert.IsTrue(SpdxDocument.Same.Equals(d2, d2)); Assert.IsTrue(SpdxDocument.Same.Equals(d3, d3)); - // Assert: Verify documents compare correctly + // Act / Assert: Verify documents compare correctly Assert.IsTrue(SpdxDocument.Same.Equals(d1, d2)); Assert.IsTrue(SpdxDocument.Same.Equals(d2, d1)); Assert.IsFalse(SpdxDocument.Same.Equals(d1, d3)); @@ -288,18 +288,17 @@ public void SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance() [TestMethod] public void SpdxDocument_Validate_NoIssues() { + // Arrange: Load a valid SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); - - // Deserialize the document var doc = Spdx2JsonDeserializer.Deserialize(json22Example); Assert.IsNotNull(doc); - // Perform validation + // Act: Perform validation on the document var issues = new List(); doc.Validate(issues); - // Ensure no validation issues + // Assert: Verify no validation issues are reported Assert.IsEmpty(issues); } @@ -559,10 +558,10 @@ public void SpdxDocument_GetAllElements_Correct() } /// - /// Tests the method returns the correct element by ID. + /// Tests the method returns the document element. /// [TestMethod] - public void SpdxDocument_GetElement_Correct() + public void SpdxDocument_GetElement_Document_ReturnsDocumentElement() { // Arrange: Load a sample SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -570,23 +569,70 @@ public void SpdxDocument_GetElement_Correct() var doc = Spdx2JsonDeserializer.Deserialize(json22Example); Assert.IsNotNull(doc); - // Assert: Verify finding the document returns the correct element + // Act: Find the document element by ID var foundDoc = doc.GetElement("SPDXRef-DOCUMENT"); + + // Assert: Verify the document element is correct Assert.IsNotNull(foundDoc); Assert.AreEqual("SPDX-Tools-v2.0", foundDoc.Name); + } - // Assert: Verify finding a file returns the correct element + /// + /// Tests the method returns the correct file element. + /// + [TestMethod] + public void SpdxDocument_GetElement_File_ReturnsFileElement() + { + // Arrange: Load a sample SPDX JSON document + var json22Example = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var doc = Spdx2JsonDeserializer.Deserialize(json22Example); + Assert.IsNotNull(doc); + + // Act: Find a file element by ID var foundFile = doc.GetElement("SPDXRef-JenaLib"); + + // Assert: Verify the file element is correct Assert.IsNotNull(foundFile); Assert.AreEqual("./lib-source/jena-2.6.3-sources.jar", foundFile.FileName); + } + + /// + /// Tests the method returns the correct package element. + /// + [TestMethod] + public void SpdxDocument_GetElement_Package_ReturnsPackageElement() + { + // Arrange: Load a sample SPDX JSON document + var json22Example = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var doc = Spdx2JsonDeserializer.Deserialize(json22Example); + Assert.IsNotNull(doc); - // Assert: Verify finding a package returns the correct element + // Act: Find a package element by ID var foundPackage = doc.GetElement("SPDXRef-Saxon"); + + // Assert: Verify the package element is correct Assert.IsNotNull(foundPackage); Assert.AreEqual("saxonB-8.8.zip", foundPackage.FileName); + } - // Assert: Verify finding a snippet returns the correct element + /// + /// Tests the method returns the correct snippet element. + /// + [TestMethod] + public void SpdxDocument_GetElement_Snippet_ReturnsSnippetElement() + { + // Arrange: Load a sample SPDX JSON document + var json22Example = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var doc = Spdx2JsonDeserializer.Deserialize(json22Example); + Assert.IsNotNull(doc); + + // Act: Find a snippet element by ID var foundSnippet = doc.GetElement("SPDXRef-Snippet"); + + // Assert: Verify the snippet element is correct Assert.IsNotNull(foundSnippet); Assert.AreEqual("SPDXRef-DoapSource", foundSnippet.SnippetFromFile); } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs new file mode 100644 index 0000000..b251cb4 --- /dev/null +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs @@ -0,0 +1,68 @@ +// Copyright(c) 2024 DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DemaConsulting.SpdxModel.Tests; + +/// +/// Tests for the base class identity behavior. +/// +/// +/// is abstract; is used as the +/// concrete subclass to exercise base-class identity behavior, as its Validate method +/// uses the standard SpdxRefRegex check. +/// +[TestClass] +public class SpdxElementTests +{ + /// + /// Tests that an element with a valid SPDXRef-<name> identifier passes identity validation. + /// + [TestMethod] + public void SpdxElement_Id_ValidFormat_PassesValidation() + { + // Arrange: Create a minimal package element with a valid SPDXRef- ID + var element = new SpdxPackage { Id = "SPDXRef-valid", Name = "test-package", Version = "1.0" }; + + // Act: Validate the element + var issues = new List(); + element.Validate(issues, null); + + // Assert: No issue about the SPDX Identifier field + Assert.IsFalse(issues.Any(i => i.Contains("Invalid SPDX Identifier"))); + } + + /// + /// Tests that an element with an ID that does not follow the SPDXRef-<name> format + /// reports an identity validation issue. + /// + [TestMethod] + public void SpdxElement_Id_InvalidFormat_ReportsValidationIssue() + { + // Arrange: Create a minimal package element with a bad ID + var element = new SpdxPackage { Id = "BadId", Name = "test-package", Version = "1.0" }; + + // Act: Validate + var issues = new List(); + element.Validate(issues, null); + + // Assert: The invalid identifier is reported + Assert.IsTrue(issues.Any(i => i.Contains("Invalid SPDX Identifier Field 'BadId'"))); + } +} diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs index 8feb76c..3544571 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs @@ -65,7 +65,7 @@ public void SpdxExternalDocumentReference_SameComparer_ComparesCorrectly() Document = "http://demo.com/some-document" }; - // Assert: Verify external-document-references compare to themselves + // Act / Assert: Verify external-document-references compare to themselves Assert.IsTrue(SpdxExternalDocumentReference.Same.Equals(r1, r1)); Assert.IsTrue(SpdxExternalDocumentReference.Same.Equals(r2, r2)); Assert.IsTrue(SpdxExternalDocumentReference.Same.Equals(r3, r3)); @@ -212,4 +212,32 @@ public void SpdxExternalDocumentReference_Validate_MissingDocument() // Assert: Verify that the validation fails and the error message includes the description Assert.Contains(issue => issue.Contains("External Document Reference 'DocumentRef-spdx-tool-1.2' Invalid SPDX Document URI Field - Empty"), issues); } + + /// + /// Tests the method reports an invalid checksum. + /// + [TestMethod] + public void SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue() + { + // Arrange: Create a reference with a missing-algorithm checksum + var reference = new SpdxExternalDocumentReference + { + ExternalDocumentId = "DocumentRef-spdx-tool-1.2", + Checksum = new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.Missing, + Value = "" + }, + Document = "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" + }; + + // Act: Perform validation + var issues = new List(); + reference.Validate(issues); + + // Assert: Verify that the checksum algorithm issue is reported + Assert.Contains( + issue => issue.Contains("Invalid Checksum Algorithm Field - Missing"), + issues); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs index ffc4351..f258b15 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs @@ -53,7 +53,7 @@ public void SpdxExternalReference_SameComparer_ComparesCorrectly() Locator = "pkg:nuget/SomePackage@0.0.0" }; - // Assert: Verify external-references compare to themselves + // Act / Assert: Verify external-references compare to themselves Assert.IsTrue(SpdxExternalReference.Same.Equals(r1, r1)); Assert.IsTrue(SpdxExternalReference.Same.Equals(r2, r2)); Assert.IsTrue(SpdxExternalReference.Same.Equals(r3, r3)); @@ -222,6 +222,9 @@ public void SpdxExternalReference_Validate_InvalidLocator() [TestMethod] public void SpdxReferenceCategoryExtensions_FromText_Valid() { + // Arrange: (no external state needed) + + // Act / Assert: Verify all recognized category strings map to expected enum values Assert.AreEqual(SpdxReferenceCategory.Missing, SpdxReferenceCategoryExtensions.FromText("")); Assert.AreEqual(SpdxReferenceCategory.Security, SpdxReferenceCategoryExtensions.FromText("SECURITY")); Assert.AreEqual(SpdxReferenceCategory.Security, SpdxReferenceCategoryExtensions.FromText("security")); @@ -240,6 +243,9 @@ public void SpdxReferenceCategoryExtensions_FromText_Valid() [TestMethod] public void SpdxReferenceCategoryExtensions_FromText_Invalid() { + // Arrange: (no external state needed) + + // Act / Assert: Verify that FromText throws for an unrecognized category string var exception = Assert.ThrowsExactly(() => SpdxReferenceCategoryExtensions.FromText("invalid")); Assert.AreEqual("Unsupported SPDX Reference Category 'invalid'", exception.Message); @@ -251,6 +257,9 @@ public void SpdxReferenceCategoryExtensions_FromText_Invalid() [TestMethod] public void SpdxReferenceCategoryExtensions_ToText_Valid() { + // Arrange: (no external state needed) + + // Act / Assert: Verify all known enum values map to expected text representations Assert.AreEqual("SECURITY", SpdxReferenceCategory.Security.ToText()); Assert.AreEqual("PACKAGE-MANAGER", SpdxReferenceCategory.PackageManager.ToText()); Assert.AreEqual("PERSISTENT-ID", SpdxReferenceCategory.PersistentId.ToText()); diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs index a577088..3798337 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs @@ -29,6 +29,12 @@ public class SpdxExtractedLicensingInfoTests /// /// Tests the comparer compares extracted licensing infos correctly. /// + /// + /// Validates that the Same comparer treats two instances with identical ExtractedText as equal + /// regardless of other field differences, treats instances with differing ExtractedText as + /// distinct, handles reference equality, null arguments, and produces consistent hash codes + /// for equal instances. + /// [TestMethod] public void SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly() { @@ -63,6 +69,11 @@ public void SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly() Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(l2, l3)); Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(l3, l2)); + // Assert: Verify null handling + Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(null!, null!)); + Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(null!, l1)); + Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(l1, null!)); + // Assert: Verify same extracted-licensing-infos have identical hashes Assert.AreEqual(SpdxExtractedLicensingInfo.Same.GetHashCode(l1), SpdxExtractedLicensingInfo.Same.GetHashCode(l2)); @@ -71,6 +82,10 @@ public void SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly() /// /// Tests the method. /// + /// + /// Validates that DeepCopy produces a new instance with field values equal to the original + /// and that arrays are independently copied (no shared references between original and copy). + /// [TestMethod] public void SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance() { @@ -79,6 +94,7 @@ public void SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance( { LicenseId = "LicenseRef-1", ExtractedText = "The CyberNeko Software License", + CrossReferences = ["https://example.com/license"], Comment = "Extracted from files" }; @@ -93,6 +109,7 @@ public void SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance( // Assert: Verify deep-copy has distinct instance Assert.IsFalse(ReferenceEquals(l1, l2)); + Assert.IsFalse(ReferenceEquals(l1.CrossReferences, l2.CrossReferences)); } /// @@ -100,6 +117,10 @@ public void SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance( /// /// method adds or updates information correctly. /// + /// + /// Validates that the static Enhance merges arrays by enhancing matching entries (matched + /// by ExtractedText) and appending unmatched entries as new deep copies. + /// [TestMethod] public void SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly() { @@ -139,9 +160,38 @@ public void SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly Assert.AreEqual("Some Random License", infos[1].ExtractedText); } + /// + /// Tests the method returns no issues for a valid input. + /// + /// + /// Validates that Validate reports no issues when both LicenseId and ExtractedText are + /// non-empty, confirming the happy-path behavior of the validation logic. + /// + [TestMethod] + public void SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues() + { + // Arrange: Create a valid extracted licensing info + var info = new SpdxExtractedLicensingInfo + { + LicenseId = "LicenseRef-1", + ExtractedText = "The CyberNeko Software License" + }; + + // Act: Perform validation on the SpdxExtractedLicensingInfo instance. + var issues = new List(); + info.Validate(issues); + + // Assert: Verify that the validation reports no issues + Assert.IsEmpty(issues); + } + /// /// Tests the method reports bad license IDs /// + /// + /// Validates that Validate appends an issue message to the supplied list when LicenseId is + /// empty, confirming the LicenseId validation path. + /// [TestMethod] public void SpdxExtractedLicensingInfo_Validate_InvalidLicenseId() { @@ -163,6 +213,10 @@ public void SpdxExtractedLicensingInfo_Validate_InvalidLicenseId() /// /// Tests the method reports bad extracted text /// + /// + /// Validates that Validate appends an issue message to the supplied list when ExtractedText + /// is empty, confirming the ExtractedText validation path. + /// [TestMethod] public void SpdxExtractedLicensingInfo_Validate_InvalidExtractedText() { diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs index 2b27830..e254ddd 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs @@ -76,6 +76,11 @@ public void SpdxFile_SameComparer_ComparesCorrectly() } ] }; + var f4 = new SpdxFile + { + FileName = "./file1.txt" + // no checksums — should still match f1/f2 by FileName + }; // Assert: Verify files compare to themselves Assert.IsTrue(SpdxFile.Same.Equals(f1, f1)); @@ -90,6 +95,10 @@ public void SpdxFile_SameComparer_ComparesCorrectly() Assert.IsFalse(SpdxFile.Same.Equals(f2, f3)); Assert.IsFalse(SpdxFile.Same.Equals(f3, f2)); + // Assert: Verify one-sided SHA1 boundary — same FileName, one has SHA1, other does not + Assert.IsTrue(SpdxFile.Same.Equals(f1, f4)); + Assert.IsTrue(SpdxFile.Same.Equals(f4, f1)); + // Assert: Verify same files have identical hashes Assert.AreEqual(SpdxFile.Same.GetHashCode(f1), SpdxFile.Same.GetHashCode(f2)); } @@ -100,11 +109,12 @@ public void SpdxFile_SameComparer_ComparesCorrectly() [TestMethod] public void SpdxFile_DeepCopy_CreatesEqualButDistinctInstance() { - // Arrange: Create an SpdxFile instance with checksums and comments + // Arrange: Create an SpdxFile instance with all deep-copied fields populated var f1 = new SpdxFile { Id = "SPDXRef-File1", FileName = "./file1.txt", + FileTypes = [SpdxFileType.Source, SpdxFileType.Text], Checksums = [ new SpdxChecksum @@ -118,7 +128,24 @@ public void SpdxFile_DeepCopy_CreatesEqualButDistinctInstance() Value = "624c1abb3664f4b35547e7c73864ad24" } ], - Comment = "File 1" + LicenseInfoInFiles = ["MIT"], + LicenseComments = "No issues", + ConcludedLicense = "MIT", + CopyrightText = "Copyright 2024", + Comment = "File 1", + Notice = "See LICENSE", + Contributors = ["Contributor A"], + AttributionText = ["Attribution notice"], + Annotations = + [ + new SpdxAnnotation + { + Annotator = "Tool: test", + Date = "2024-01-01T00:00:00Z", + Type = SpdxAnnotationType.Review, + Comment = "Reviewed" + } + ] }; // Act: Create a deep copy of the SpdxFile instance @@ -128,12 +155,25 @@ public void SpdxFile_DeepCopy_CreatesEqualButDistinctInstance() Assert.AreEqual(f1, f2, SpdxFile.Same); Assert.AreEqual(f1.Id, f2.Id); Assert.AreEqual(f1.FileName, f2.FileName); + CollectionAssert.AreEquivalent(f1.FileTypes, f2.FileTypes); CollectionAssert.AreEquivalent(f1.Checksums, f2.Checksums, SpdxChecksum.Same); + CollectionAssert.AreEquivalent(f1.LicenseInfoInFiles, f2.LicenseInfoInFiles); + Assert.AreEqual(f1.LicenseComments, f2.LicenseComments); + Assert.AreEqual(f1.ConcludedLicense, f2.ConcludedLicense); + Assert.AreEqual(f1.CopyrightText, f2.CopyrightText); Assert.AreEqual(f1.Comment, f2.Comment); + Assert.AreEqual(f1.Notice, f2.Notice); + CollectionAssert.AreEquivalent(f1.Contributors, f2.Contributors); + CollectionAssert.AreEquivalent(f1.AttributionText, f2.AttributionText); // Assert: Verify deep-copy has distinct instances Assert.IsFalse(ReferenceEquals(f1, f2)); Assert.IsFalse(ReferenceEquals(f1.Checksums, f2.Checksums)); + Assert.IsFalse(ReferenceEquals(f1.FileTypes, f2.FileTypes)); + Assert.IsFalse(ReferenceEquals(f1.LicenseInfoInFiles, f2.LicenseInfoInFiles)); + Assert.IsFalse(ReferenceEquals(f1.Contributors, f2.Contributors)); + Assert.IsFalse(ReferenceEquals(f1.AttributionText, f2.AttributionText)); + Assert.IsFalse(ReferenceEquals(f1.Annotations, f2.Annotations)); } /// @@ -241,6 +281,64 @@ public void SpdxFile_Validate_ReportsInvalidFileId() Assert.Contains(issue => issue.Contains("File './file1.txt' Invalid SPDX Identifier Field"), issues); } + /// + /// Tests that an invalid file name fails validation. + /// + [TestMethod] + public void SpdxFile_Validate_ReportsInvalidFileName() + { + // Arrange: Create an SpdxFile instance with a FileName that has no "./" prefix + var spdxFile = new SpdxFile + { + Id = "SPDXRef-File1", + FileName = "file1.txt", + Checksums = + [ + new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.Sha1, + Value = "85ed0817af83a24ad8da68c2b5094de69833983c" + } + ] + }; + + // Act: Perform validation on the SpdxFile instance. + var issues = new List(); + spdxFile.Validate(issues); + + // Assert: Verify that the validation reports the invalid file name. + Assert.Contains(issue => issue.Contains("Invalid File Name Field"), issues); + } + + /// + /// Tests that a missing SHA1 checksum fails validation. + /// + [TestMethod] + public void SpdxFile_Validate_ReportsWhenSha1ChecksumMissing() + { + // Arrange: Create an SpdxFile instance with only an MD5 checksum (no SHA1) + var spdxFile = new SpdxFile + { + Id = "SPDXRef-File1", + FileName = "./file1.txt", + Checksums = + [ + new SpdxChecksum + { + Algorithm = SpdxChecksumAlgorithm.Md5, + Value = "624c1abb3664f4b35547e7c73864ad24" + } + ] + }; + + // Act: Perform validation on the SpdxFile instance. + var issues = new List(); + spdxFile.Validate(issues); + + // Assert: Verify that the validation reports the missing SHA1. + Assert.Contains(issue => issue.Contains("missing SHA1"), issues); + } + /// /// Tests that a valid file passes validation. /// @@ -281,6 +379,9 @@ public void SpdxFile_Validate_Success() [TestMethod] public void SpdxFileTypeExtensions_FromText_Valid() { + // Arrange: (no external state needed) + + // Act / Assert: Verify all recognized file type strings map to expected enum values Assert.AreEqual(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("SOURCE")); Assert.AreEqual(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("source")); Assert.AreEqual(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("Source")); @@ -302,6 +403,9 @@ public void SpdxFileTypeExtensions_FromText_Valid() [TestMethod] public void SpdxFileTypeExtensions_FromText_Invalid() { + // Arrange: An unrecognized file type string + + // Act / Assert: Verify that FromText throws with a message identifying the unsupported value var exception = Assert.ThrowsExactly(() => SpdxFileTypeExtensions.FromText("invalid")); Assert.AreEqual("Unsupported SPDX File Type 'invalid'", exception.Message); @@ -313,6 +417,9 @@ public void SpdxFileTypeExtensions_FromText_Invalid() [TestMethod] public void SpdxFileTypeExtensions_ToText_Valid() { + // Arrange: (no external state needed) + + // Act / Assert: Verify all known enum values map to expected text representations Assert.AreEqual("SOURCE", SpdxFileType.Source.ToText()); Assert.AreEqual("BINARY", SpdxFileType.Binary.ToText()); Assert.AreEqual("ARCHIVE", SpdxFileType.Archive.ToText()); @@ -332,6 +439,9 @@ public void SpdxFileTypeExtensions_ToText_Valid() [TestMethod] public void SpdxFileTypeExtensions_ToText_Invalid() { + // Arrange: An unsupported file type enum value + + // Act / Assert: Verify that ToText throws when given an unsupported enum value var exception = Assert.ThrowsExactly(() => ((SpdxFileType)1000).ToText()); Assert.AreEqual("Unsupported SPDX File Type '1000'", exception.Message); } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs new file mode 100644 index 0000000..f0644f0 --- /dev/null +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs @@ -0,0 +1,123 @@ +// Copyright(c) 2024 DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DemaConsulting.SpdxModel.Tests; + +/// +/// Tests for the class. +/// +[TestClass] +public class SpdxHelpersTests +{ + /// + /// Tests that returns true for null input. + /// + [TestMethod] + public void SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue() + { + // Arrange: null represents a not-set date-time field + + // Act + var result = SpdxHelpers.IsValidSpdxDateTime(null); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that returns true for an empty string. + /// + [TestMethod] + public void SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue() + { + // Arrange: empty string represents a not-set date-time field + + // Act + var result = SpdxHelpers.IsValidSpdxDateTime(""); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that returns true for a valid ISO 8601 UTC timestamp. + /// + [TestMethod] + public void SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue() + { + // Arrange + const string validDateTime = "2024-01-01T00:00:00Z"; + + // Act + var result = SpdxHelpers.IsValidSpdxDateTime(validDateTime); + + // Assert + Assert.IsTrue(result); + } + + /// + /// Tests that returns false for an invalid format. + /// + [TestMethod] + public void SpdxHelpers_IsValidSpdxDateTime_InvalidFormat_ReturnsFalse() + { + // Arrange + const string invalidDateTime = "not-a-date"; + + // Act + var result = SpdxHelpers.IsValidSpdxDateTime(invalidDateTime); + + // Assert + Assert.IsFalse(result); + } + + /// + /// Tests that returns the concrete value when given a + /// mix of concrete value and NOASSERTION. + /// + [TestMethod] + public void SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue() + { + // Arrange + const string concrete = "MIT"; + const string noAssertion = SpdxElement.NoAssertion; + + // Act + var result = SpdxHelpers.EnhanceString(noAssertion, concrete); + + // Assert + Assert.AreEqual(concrete, result); + } + + /// + /// Tests that returns null when all inputs are null. + /// + [TestMethod] + public void SpdxHelpers_EnhanceString_NullInputs_ReturnsNull() + { + // Arrange: all candidates are null + + // Act + var result = SpdxHelpers.EnhanceString(null, null); + + // Assert + Assert.IsNull(result); + } +} diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs index 495a427..0ba7ce3 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs @@ -169,4 +169,90 @@ public void SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds() roundTripped.Validate(issues); Assert.IsEmpty(issues); } + + /// + /// Tests that malformed JSON throws a JsonException when deserialized. + /// + [TestMethod] + public void SpdxModel_Deserialize_MalformedJson_ThrowsJsonException() + { + // Arrange: Prepare malformed JSON text + const string malformedJson = "{ this is not valid json }"; + + // Act / Assert: Deserialize should throw a JsonException or derived type + var threw = false; + try + { + Spdx2JsonDeserializer.Deserialize(malformedJson); + } + catch (System.Text.Json.JsonException) + { + threw = true; + } + + Assert.IsTrue(threw, "Expected JsonException was not thrown."); + } + + /// + /// Tests that an invalid SPDX document reports specific validation issues. + /// + [TestMethod] + public void SpdxModel_Validate_InvalidDocument_ReportsIssues() + { + // Arrange: Create a deliberately incomplete SPDX document + var document = new SpdxDocument + { + Id = "", + Name = "", + Version = "", + DataLicense = "", + DocumentNamespace = "" + }; + + // Act: Validate the document + var issues = new List(); + document.Validate(issues); + + // Assert: Verify that specific validation issues are reported + Assert.IsTrue(issues.Count > 0, "Expected validation issues but none were reported."); + Assert.IsTrue(issues.Exists(i => i.Contains("SPDX Version")), + "Expected a SPDX Version validation issue."); + } + + /// + /// Tests that required fields on SPDX data model types are non-nullable, + /// and optional fields are nullable. + /// + [TestMethod] + public void SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNullable() + { + // Arrange: Create a default instance of key data model types + var document = new SpdxDocument(); + var package = new SpdxPackage(); + var file = new SpdxFile(); + var relationship = new SpdxRelationship(); + + // Act / Assert: default-constructed instances have the expected field nullability + + // Assert: Required fields are non-nullable (strings default to empty, not null) + Assert.IsNotNull(document.Id); + Assert.IsNotNull(document.Name); + Assert.IsNotNull(document.Version); + Assert.IsNotNull(document.DataLicense); + Assert.IsNotNull(document.DocumentNamespace); + Assert.IsNotNull(package.Id); + Assert.IsNotNull(package.Name); + Assert.IsNotNull(package.DownloadLocation); + Assert.IsNotNull(file.Id); + Assert.IsNotNull(file.FileName); + Assert.IsNotNull(relationship.Id); + Assert.IsNotNull(relationship.RelatedSpdxElement); + + // Assert: Optional fields are nullable + Assert.IsNull(document.Comment); + Assert.IsNull(package.Comment); + Assert.IsNull(package.Version); + Assert.IsNull(file.Comment); + Assert.IsNull(relationship.Comment); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs index 076f4b0..7f18718 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs @@ -23,12 +23,23 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Covers equality comparison via the comparer, deep-copy independence, +/// field merging via , and full +/// validation including NTIA minimum-elements checks. Each test exercises a single scenario or +/// boundary condition in isolation with no shared state between tests. +/// [TestClass] public class SpdxPackageTests { /// /// Tests the comparer compares packages correctly. /// + /// + /// Verifies that two packages with the same Name and Version are considered equal + /// regardless of differing Id values, that packages with different names or versions are + /// distinct, and that null arguments are handled correctly. + /// [TestMethod] public void SpdxPackage_SameComparer_ComparesCorrectly() { @@ -52,7 +63,7 @@ public void SpdxPackage_SameComparer_ComparesCorrectly() Version = "1.2.3" }; - // Assert: Verify packages compare to themselves + // Act / Assert: Verify packages compare to themselves Assert.IsTrue(SpdxPackage.Same.Equals(p1, p1)); Assert.IsTrue(SpdxPackage.Same.Equals(p2, p2)); Assert.IsTrue(SpdxPackage.Same.Equals(p3, p3)); @@ -67,11 +78,20 @@ public void SpdxPackage_SameComparer_ComparesCorrectly() // Assert: Verify same packages have identical hashes Assert.AreEqual(SpdxPackage.Same.GetHashCode(p1), SpdxPackage.Same.GetHashCode(p2)); + + // Assert: Verify null handling + Assert.IsFalse(SpdxPackage.Same.Equals(null!, p1)); + Assert.IsFalse(SpdxPackage.Same.Equals(p1, null!)); + Assert.IsTrue(SpdxPackage.Same.Equals(null!, null!)); } /// /// Tests the method successfully creates a deep copy. /// + /// + /// Verifies that the returned instance has equal field values, that all array and nested object + /// fields are new independent instances, and that mutating the copy does not affect the original. + /// [TestMethod] public void SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance() { @@ -81,6 +101,10 @@ public void SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance() Id = "SPDXRef-Package1", Name = "DemaConsulting.SpdxModel", Version = "0.0.0", + VerificationCode = new SpdxPackageVerificationCode + { + Value = "d6a770ba38583ed4bb4525bd96e50461655d2759" + }, Checksums = [ new SpdxChecksum @@ -124,6 +148,8 @@ public void SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance() CollectionAssert.AreEquivalent(p1.ExternalReferences, p2.ExternalReferences, SpdxExternalReference.Same); CollectionAssert.AreEquivalent(p1.AttributionText, p2.AttributionText); CollectionAssert.AreEquivalent(p1.Annotations, p2.Annotations, SpdxAnnotation.Same); + Assert.IsNotNull(p2.VerificationCode); + Assert.AreEqual(p1.VerificationCode!.Value, p2.VerificationCode.Value); // Assert: Verify deep-copy has distinct instances Assert.IsFalse(ReferenceEquals(p1, p2)); @@ -133,12 +159,17 @@ public void SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance() Assert.IsFalse(ReferenceEquals(p1.ExternalReferences, p2.ExternalReferences)); Assert.IsFalse(ReferenceEquals(p1.AttributionText, p2.AttributionText)); Assert.IsFalse(ReferenceEquals(p1.Annotations, p2.Annotations)); + Assert.IsFalse(ReferenceEquals(p1.VerificationCode, p2.VerificationCode)); } /// /// Tests the method correctly adds or updates /// packages. /// + /// + /// Verifies that a matching package (same name and version) is enhanced in place and that a non-matching + /// package from the source array is deep-copied and appended, resulting in an array of length two. + /// [TestMethod] public void SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly() { @@ -184,6 +215,10 @@ public void SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly() /// /// Tests that a valid package passes validation. /// + /// + /// Exercises the happy-path: a fully populated package with a valid SPDX ID, non-empty name, download + /// location, and a conforming supplier string passes all validation checks including NTIA minimum elements. + /// [TestMethod] public void SpdxPackage_Validate_Success() { @@ -208,6 +243,10 @@ public void SpdxPackage_Validate_Success() /// /// Tests the method reports missing package names /// + /// + /// Verifies the boundary condition where Name is empty: validation must report the + /// "Invalid Package Name Field - Empty" issue. + /// [TestMethod] public void SpdxPackage_Validate_MissingPackageName() { @@ -232,6 +271,10 @@ public void SpdxPackage_Validate_MissingPackageName() /// /// Tests the method reports invalid package IDs /// + /// + /// Verifies that an Id not starting with SPDXRef- is flagged as an invalid SPDX + /// identifier field. + /// [TestMethod] public void SpdxPackage_Validate_InvalidPackageId() { @@ -256,6 +299,10 @@ public void SpdxPackage_Validate_InvalidPackageId() /// /// Tests the method reports missing download locations /// + /// + /// Verifies that an empty DownloadLocation causes the "Invalid Package Download Location Field - Empty" + /// issue to be reported. + /// [TestMethod] public void SpdxPackage_Validate_MissingDownload() { @@ -280,6 +327,10 @@ public void SpdxPackage_Validate_MissingDownload() /// /// Tests the method reports invalid suppliers. /// + /// + /// Verifies that a supplier value that does not start with Person:, Organization:, or equal + /// NOASSERTION is flagged as an invalid supplier field. + /// [TestMethod] public void SpdxPackage_Validate_InvalidSupplier() { @@ -304,6 +355,10 @@ public void SpdxPackage_Validate_InvalidSupplier() /// /// Tests the method reports invalid originators. /// + /// + /// Verifies that an originator value that does not start with Person:, Organization:, or equal + /// NOASSERTION is flagged as an invalid originator field. + /// [TestMethod] public void SpdxPackage_Validate_InvalidOriginator() { @@ -329,6 +384,10 @@ public void SpdxPackage_Validate_InvalidOriginator() /// /// Tests the method reports invalid release dates. /// + /// + /// Verifies that a ReleaseDate that does not conform to the SPDX date-time format causes the + /// "Invalid Release Date Field" issue to be reported. + /// [TestMethod] public void SpdxPackage_Validate_InvalidReleaseDate() { @@ -354,6 +413,10 @@ public void SpdxPackage_Validate_InvalidReleaseDate() /// /// Tests the method reports invalid built dates. /// + /// + /// Verifies that a BuiltDate that does not conform to the SPDX date-time format causes the + /// "Invalid Built Date Field" issue to be reported. + /// [TestMethod] public void SpdxPackage_Validate_InvalidBuiltDate() { @@ -379,6 +442,10 @@ public void SpdxPackage_Validate_InvalidBuiltDate() /// /// Tests the method reports bad valid until dates /// + /// + /// Verifies that a ValidUntilDate that does not conform to the SPDX date-time format causes the + /// "Invalid Valid Until Date Field" issue to be reported. + /// [TestMethod] public void SpdxPackage_Validate_InvalidValidUntilDate() { @@ -404,6 +471,10 @@ public void SpdxPackage_Validate_InvalidValidUntilDate() /// /// Tests the method validates annotations. /// + /// + /// Verifies that an annotation with an empty Annotator field causes the + /// "Invalid Annotator Field - Empty" issue to be reported with the correct package prefix. + /// [TestMethod] public void SpdxPackage_Validate_InvalidAnnotation() { @@ -434,4 +505,91 @@ public void SpdxPackage_Validate_InvalidAnnotation() // Assert: Verify the annotation issue is reported with the correct prefix Assert.Contains("Package 'DemaConsulting.SpdxModel' Invalid Annotator Field - Empty", issues); } + + /// + /// Tests the method reports HasFiles references to missing files. + /// + /// + /// Verifies that when a document is provided and HasFiles references a file ID that does not + /// exist in doc.Files, the "HasFiles references missing files" issue is reported. + /// + [TestMethod] + public void SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue() + { + // Arrange: Create a package that references a file that does not exist in the document + var package = new SpdxPackage + { + Id = "SPDXRef-Package-SpdxModel", + Name = "DemaConsulting.SpdxModel", + Version = "0.0.0", + DownloadLocation = "https://www.nuget.org/packages/DemaConsulting.SpdxModel", + Supplier = "Organization: DemaConsulting", + HasFiles = ["SPDXRef-Missing-File"] + }; + var doc = new SpdxDocument + { + Files = [] + }; + + // Act: Validate the package with the document + var issues = new List(); + package.Validate(issues, doc); + + // Assert: Verify the HasFiles reference issue is reported + Assert.Contains(issue => issue.Contains("Package 'DemaConsulting.SpdxModel' HasFiles references missing files"), issues); + } + + /// + /// Tests the method reports missing NTIA supplier. + /// + /// + /// Verifies that when NTIA validation is enabled, a package without a supplier causes the + /// "NTIA: Package Missing Supplier" issue to be reported. + /// + [TestMethod] + public void SpdxPackage_ValidateNtia_MissingSupplier_ReportsIssue() + { + // Arrange: Create a package with no supplier + var package = new SpdxPackage + { + Id = "SPDXRef-Package-SpdxModel", + Name = "DemaConsulting.SpdxModel", + Version = "0.0.0", + DownloadLocation = "https://www.nuget.org/packages/DemaConsulting.SpdxModel" + }; + + // Act: Validate the package with NTIA checks enabled + var issues = new List(); + package.Validate(issues, null, ntia: true); + + // Assert: Verify the missing supplier issue is reported + Assert.Contains(issue => issue.Contains("NTIA: Package 'DemaConsulting.SpdxModel' Missing Supplier"), issues); + } + + /// + /// Tests the method reports missing NTIA version. + /// + /// + /// Verifies that when NTIA validation is enabled, a package without a version string causes the + /// "NTIA: Package Missing Version" issue to be reported. + /// + [TestMethod] + public void SpdxPackage_ValidateNtia_MissingVersion_ReportsIssue() + { + // Arrange: Create a package with no version + var package = new SpdxPackage + { + Id = "SPDXRef-Package-SpdxModel", + Name = "DemaConsulting.SpdxModel", + DownloadLocation = "https://www.nuget.org/packages/DemaConsulting.SpdxModel", + Supplier = "Organization: DemaConsulting" + }; + + // Act: Validate the package with NTIA checks enabled + var issues = new List(); + package.Validate(issues, null, ntia: true); + + // Assert: Verify the missing version issue is reported + Assert.Contains(issue => issue.Contains("NTIA: Package 'DemaConsulting.SpdxModel' Missing Version"), issues); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs index a4a074e..4d542bb 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs @@ -23,14 +23,23 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Covers equality comparison via the comparer, +/// deep-copy independence, field merging via , +/// and SHA1 hex digest validation via . +/// [TestClass] public class SpdxPackageVerificationCodeTests { /// /// Tests the comparer compares package verification codes correctly. /// + /// + /// Verifies that two codes with the same Value but different ExcludedFiles are considered equal, + /// while codes with different values are considered distinct. Also validates null handling and hash consistency. + /// [TestMethod] - public void SpdxPackageVerificationCode_SameComparer_ComparesCorrectly() + public void SpdxPackageVerificationCode_SameComparer_SameValueDifferentExcludedFiles_ReturnsEqual() { // Arrange: Create three package verification codes with different properties var v1 = new SpdxPackageVerificationCode @@ -47,7 +56,7 @@ public void SpdxPackageVerificationCode_SameComparer_ComparesCorrectly() Value = "85ed0817af83a24ad8da68c2b5094de69833983c" }; - // Assert: Verify package-verification-codes compare to themselves + // Act / Assert: Verify package-verification-codes compare to themselves Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(v1, v1)); Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(v2, v2)); Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(v3, v3)); @@ -63,13 +72,22 @@ public void SpdxPackageVerificationCode_SameComparer_ComparesCorrectly() // Assert: Verify same package-verification-codes have identical hashes Assert.AreEqual(SpdxPackageVerificationCode.Same.GetHashCode(v1), SpdxPackageVerificationCode.Same.GetHashCode(v2)); + + // Assert: Verify null handling + Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(null!, null!)); + Assert.IsFalse(SpdxPackageVerificationCode.Same.Equals(null!, v1)); + Assert.IsFalse(SpdxPackageVerificationCode.Same.Equals(v1, null!)); } /// /// Tests the method successfully creates a deep copy. /// + /// + /// Verifies that the deep copy has equal field values and that both the code object and its + /// ExcludedFiles array are distinct instances that can be mutated independently. + /// [TestMethod] - public void SpdxPackageVerificationCode_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxPackageVerificationCode_DeepCopy_FullyPopulatedCode_CreatesEqualButDistinctCopy() { // Arrange: Create a package verification code with excluded files and a value var v1 = new SpdxPackageVerificationCode @@ -94,8 +112,12 @@ public void SpdxPackageVerificationCode_DeepCopy_CreatesEqualButDistinctInstance /// /// Tests the method adds or updates information correctly. /// + /// + /// Verifies that enhancing a code with excluded files merges them by deduplication, and that an + /// existing non-empty Value is not overwritten by the source. + /// [TestMethod] - public void SpdxPackageVerificationCode_Enhance_AddsOrUpdatesInformationCorrectly() + public void SpdxPackageVerificationCode_Enhance_MissingFields_MergesCorrectly() { // Arrange: Create a package verification code with a value var info = new SpdxPackageVerificationCode @@ -118,10 +140,14 @@ public void SpdxPackageVerificationCode_Enhance_AddsOrUpdatesInformationCorrectl } /// - /// Tests the method reports bad annotators + /// Tests the Validate method reports an issue when the verification code value is invalid. /// + /// + /// Exercises the short-string boundary condition: a value shorter than 40 characters is not a valid + /// SHA1 hex digest and must produce a validation issue. + /// [TestMethod] - public void SpdxPackageVerificationCode_Validate_InvalidValue() + public void SpdxPackageVerificationCode_Validate_InvalidValue_ReportsIssue() { // Arrange: Create a bad package verification code var info = new SpdxPackageVerificationCode @@ -136,4 +162,52 @@ public void SpdxPackageVerificationCode_Validate_InvalidValue() // Assert: Verify that the validation fails and the error message includes the description Assert.Contains(issue => issue.Contains("Package 'Test' Invalid Package Verification Code Value 'BadValue'"), issues); } + + /// + /// Tests the method reports no issues for a valid value. + /// + /// + /// Verifies the happy-path: a well-formed 40-character lowercase hex SHA1 digest passes all validation + /// checks without reporting any issues. + /// + [TestMethod] + public void SpdxPackageVerificationCode_Validate_ValidValue_ReportsNoIssues() + { + // Arrange: Create a package verification code with a valid SHA1 value + var info = new SpdxPackageVerificationCode + { + Value = "d6a770ba38583ed4bb4525bd96e50461655d2758" + }; + + // Act: Perform validation on the SpdxPackageVerificationCode instance + var issues = new List(); + info.Validate("Test", issues); + + // Assert: Verify that the validation reports no issues + Assert.IsEmpty(issues); + } + + /// + /// Tests the method reports an issue for non-hex characters. + /// + /// + /// Exercises the non-hex boundary condition: a string that is exactly 40 characters long but contains + /// characters outside the hexadecimal alphabet (0–9, a–f, A–F) must produce a validation issue. + /// + [TestMethod] + public void SpdxPackageVerificationCode_Validate_NonHexValue_ReportsIssue() + { + // Arrange: Create a package verification code with 40 chars but invalid hex + var info = new SpdxPackageVerificationCode + { + Value = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + }; + + // Act: Perform validation on the SpdxPackageVerificationCode instance + var issues = new List(); + info.Validate("Test", issues); + + // Assert: Verify that the validation fails + Assert.Contains(issue => issue.Contains("Package 'Test' Invalid Package Verification Code Value"), issues); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs index 7e9d3c1..acd3746 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs @@ -23,14 +23,27 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Covers equality comparison via and +/// comparers, deep-copy independence, field merging via +/// , validation via +/// , and round-trip text conversion via +/// . Each test exercises a single scenario or +/// boundary condition in isolation with no shared state between tests. +/// [TestClass] public class SpdxRelationshipTests { /// /// Tests the comparer compares relationships correctly. /// + /// + /// Verifies that two relationships with the same Id, RelationshipType, and + /// RelatedSpdxElement are considered equal even when Comment differs, and that + /// relationships with different element IDs or types are considered distinct. + /// [TestMethod] - public void SpdxRelationship_SameComparer_ComparesCorrectly() + public void SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqual() { // Arrange: Create three relationships with different properties var r1 = new SpdxRelationship @@ -53,7 +66,7 @@ public void SpdxRelationship_SameComparer_ComparesCorrectly() RelatedSpdxElement = "SPDXRef-Package4" }; - // Assert: Verify relationships compare to themselves + // Act / Assert: Verify relationships compare to themselves Assert.IsTrue(SpdxRelationship.Same.Equals(r1, r1)); Assert.IsTrue(SpdxRelationship.Same.Equals(r2, r2)); Assert.IsTrue(SpdxRelationship.Same.Equals(r3, r3)); @@ -74,8 +87,12 @@ public void SpdxRelationship_SameComparer_ComparesCorrectly() /// Tests the comparer compares relationships with the same elements /// correctly, /// + /// + /// Verifies that two relationships with the same Id and RelatedSpdxElement are considered equal + /// even when RelationshipType differs, and that relationships with different element IDs are distinct. + /// [TestMethod] - public void SpdxRelationship_SameElementsComparer_ComparesCorrectly() + public void SpdxRelationship_SameElementsComparer_SameElementsDifferentType_ReturnsEqual() { // Arrange: Create three relationships with different properties var r1 = new SpdxRelationship @@ -98,7 +115,7 @@ public void SpdxRelationship_SameElementsComparer_ComparesCorrectly() RelatedSpdxElement = "SPDXRef-Package4" }; - // Assert: Verifies relationships compare to themselves + // Act / Assert: Verifies relationships compare to themselves Assert.IsTrue(SpdxRelationship.SameElements.Equals(r1, r1)); Assert.IsTrue(SpdxRelationship.SameElements.Equals(r2, r2)); Assert.IsTrue(SpdxRelationship.SameElements.Equals(r3, r3)); @@ -118,8 +135,12 @@ public void SpdxRelationship_SameElementsComparer_ComparesCorrectly() /// /// Tests the method successfully creates a deep copy. /// + /// + /// Verifies that the returned instance has equal field values for all scalar properties and + /// is a distinct object reference from the original. + /// [TestMethod] - public void SpdxRelationship_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxRelationship_DeepCopy_FullyPopulatedRelationship_CreatesEqualButDistinctCopy() { // Arrange: Create a relationship with properties var r1 = new SpdxRelationship @@ -148,8 +169,13 @@ public void SpdxRelationship_DeepCopy_CreatesEqualButDistinctInstance() /// Tests the method adds or updates /// information correctly. /// + /// + /// Verifies that a matching relationship (same id, type, and related element) is enhanced in place + /// and that a non-matching relationship from the source array is deep-copied and appended, resulting in + /// an array of length two. + /// [TestMethod] - public void SpdxRelationship_Enhance_AddsOrUpdatesInformationCorrectly() + public void SpdxRelationship_Enhance_MatchingAndNewRelationships_MergesCorrectly() { // Arrange: Create an array of relationships var relationships = new[] @@ -195,8 +221,12 @@ public void SpdxRelationship_Enhance_AddsOrUpdatesInformationCorrectly() /// /// Tests the method reports missing ids /// + /// + /// Verifies that an empty Id causes the "Relationship Invalid SPDX Element ID Field - Empty" + /// issue to be reported. + /// [TestMethod] - public void SpdxRelationship_Validate_MissingId() + public void SpdxRelationship_Validate_MissingRelationshipId_ReportsIssue() { // Arrange: Create a bad relationship var relationship = new SpdxRelationship() @@ -215,10 +245,14 @@ public void SpdxRelationship_Validate_MissingId() } /// - /// Tests the method reports missing ids + /// Tests the method reports missing or empty related element IDs. /// + /// + /// Verifies that an empty RelatedSpdxElement causes the "Relationship Invalid Related SPDX Element + /// Field - Empty" issue to be reported. + /// [TestMethod] - public void SpdxRelationship_Validate_MissingRelatedId() + public void SpdxRelationship_Validate_MissingRelatedElementId_ReportsIssue() { // Arrange: Create a bad relationship var relationship = new SpdxRelationship() @@ -239,8 +273,12 @@ public void SpdxRelationship_Validate_MissingRelatedId() /// /// Tests the method reports missing relationships /// + /// + /// Verifies that a RelationshipType of causes the + /// "Relationship Invalid Relationship Type Field - Missing" issue to be reported. + /// [TestMethod] - public void SpdxRelationship_Validate_MissingRelationship() + public void SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue() { // Arrange: Create a bad relationship var relationship = new SpdxRelationship() @@ -262,9 +300,17 @@ public void SpdxRelationship_Validate_MissingRelationship() /// Tests the method for the "CONTAINS" relationship /// type. /// + /// + /// Verifies that all 44 recognized SPDX relationship type tokens (including case-insensitive variants) are + /// correctly parsed to their corresponding enum values, and that an empty + /// string maps to . + /// [TestMethod] - public void SpdxRelationshipTypeExtensions_FromText_Valid() + public void SpdxRelationshipTypeExtensions_FromText_KnownText_ReturnsMappedEnum() { + // Arrange: (none required) + + // Act / Assert: Verify all recognized relationship type strings parse to their enum values Assert.AreEqual(SpdxRelationshipType.Missing, SpdxRelationshipTypeExtensions.FromText("")); Assert.AreEqual(SpdxRelationshipType.Describes, SpdxRelationshipTypeExtensions.FromText("DESCRIBES")); Assert.AreEqual(SpdxRelationshipType.Describes, SpdxRelationshipTypeExtensions.FromText("describes")); @@ -333,9 +379,16 @@ public void SpdxRelationshipTypeExtensions_FromText_Valid() /// /// Tests the method for an invalid relationship type. /// + /// + /// Verifies that an unrecognized relationship type string throws an + /// with a descriptive error message. + /// [TestMethod] - public void SpdxRelationshipTypeExtensions_FromText_Invalid() + public void SpdxRelationshipTypeExtensions_FromText_UnknownText_ThrowsInvalidOperationException() { + // Arrange: (none required) + + // Act / Assert: Verify that an unknown type throws InvalidOperationException var exception = Assert.ThrowsExactly(() => SpdxRelationshipTypeExtensions.FromText("invalid")); Assert.AreEqual("Unsupported SPDX Relationship Type 'invalid'", exception.Message); @@ -345,9 +398,16 @@ public void SpdxRelationshipTypeExtensions_FromText_Invalid() /// Tests the method for the "CONTAINS" /// relationship type. /// + /// + /// Verifies that all 44 recognized enum values are correctly serialized to + /// their canonical SPDX text representations (uppercase, underscore-separated tokens). + /// [TestMethod] - public void SpdxRelationshipTypeExtensions_ToText_Valid() + public void SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText() { + // Arrange: (none required) + + // Act / Assert: Verify all relationship type enum values serialize to their SPDX text representations Assert.AreEqual("DESCRIBES", SpdxRelationshipType.Describes.ToText()); Assert.AreEqual("DESCRIBED_BY", SpdxRelationshipType.DescribedBy.ToText()); Assert.AreEqual("CONTAINS", SpdxRelationshipType.Contains.ToText()); @@ -399,9 +459,17 @@ public void SpdxRelationshipTypeExtensions_ToText_Valid() /// Tests the method for an invalid /// relationship type. /// + /// + /// Verifies that an out-of-range value (including the + /// sentinel) throws an + /// with a descriptive error message. + /// [TestMethod] - public void SpdxRelationshipTypeExtensions_ToText_Invalid() + public void SpdxRelationshipTypeExtensions_ToText_UnknownEnum_ThrowsInvalidOperationException() { + // Arrange: (none required) + + // Act / Assert: Verify that an unknown type throws InvalidOperationException var exception = Assert.ThrowsExactly(() => ((SpdxRelationshipType)1000).ToText()); Assert.AreEqual("Unsupported SPDX Relationship Type '1000'", exception.Message); } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs index 9827482..60c8f5a 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs @@ -23,15 +23,27 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Covers equality comparison via the comparer, deep-copy independence, +/// field merging via , and full +/// validation of required fields and byte range constraints. Each test exercises a single scenario or +/// boundary condition in isolation with no shared state between tests. +/// [TestClass] public class SpdxSnippetTests { /// /// Tests the comparer compares snippets correctly. /// + /// + /// Verifies that two snippets with the same SnippetFromFile, SnippetByteStart, and + /// SnippetByteEnd are considered equal even when other fields differ, and that snippets + /// with different file or byte range are distinct. + /// [TestMethod] - public void SpdxSnippet_SameComparer_ComparesCorrectly() + public void SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual() { + // Arrange: Create three snippet instances with different properties var s1 = new SpdxSnippet { SnippetFromFile = "SPDXRef-File1", @@ -55,7 +67,7 @@ public void SpdxSnippet_SameComparer_ComparesCorrectly() SnippetByteEnd = 40 }; - // Assert snippets compare to themselves + // Act / Assert: Verify snippets compare to themselves Assert.IsTrue(SpdxSnippet.Same.Equals(s1, s1)); Assert.IsTrue(SpdxSnippet.Same.Equals(s2, s2)); Assert.IsTrue(SpdxSnippet.Same.Equals(s3, s3)); @@ -75,8 +87,12 @@ public void SpdxSnippet_SameComparer_ComparesCorrectly() /// /// Tests the method successfully creates a deep copy. /// + /// + /// Verifies that the returned instance has equal field values for all scalar properties and + /// is a distinct object reference from the original. + /// [TestMethod] - public void SpdxSnippet_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxSnippet_DeepCopy_FullyPopulatedSnippet_CreatesEqualButDistinctCopy() { // Arrange: Create a SpdxSnippet instance with various properties var s1 = new SpdxSnippet @@ -107,8 +123,12 @@ public void SpdxSnippet_DeepCopy_CreatesEqualButDistinctInstance() /// Tests the method adds or updates information /// correctly. /// + /// + /// Verifies that a matching snippet (same file and byte range) is enhanced in place and that a non-matching + /// snippet from the source array is deep-copied and appended, resulting in an array of length two. + /// [TestMethod] - public void SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly() + public void SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly() { // Arrange: Create an array of SpdxSnippet objects var snippets = new[] @@ -156,8 +176,12 @@ public void SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly() /// /// Tests that an invalid snippet ID fails validation. /// + /// + /// Verifies that an Id not matching the SPDXRef- prefix format causes the + /// "Snippet Invalid SPDX Identifier Field" issue to be reported. + /// [TestMethod] - public void SpdxSnippet_Validate_ReportsInvalidSnippetId() + public void SpdxSnippet_Validate_InvalidSnippetId_ReportsIssue() { // Arrange: Create a SpdxSnippet with an invalid ID var snippet = new SpdxSnippet @@ -179,8 +203,13 @@ public void SpdxSnippet_Validate_ReportsInvalidSnippetId() /// /// Tests that a valid snippet passes validation. /// + /// + /// Exercises the happy-path: a snippet with all required fields populated (valid SPDX ID, + /// non-empty SnippetFromFile, byte range ≥ 1, non-empty license, and copyright) + /// passes all validation checks without reporting any issues. + /// [TestMethod] - public void SpdxSnippet_Validate_Success() + public void SpdxSnippet_Validate_AllRequiredFieldsPresent_ReturnsNoIssues() { // Arrange: Create a valid SpdxSnippet var snippet = new SpdxSnippet @@ -204,8 +233,12 @@ public void SpdxSnippet_Validate_Success() /// /// Tests the method validates annotations. /// + /// + /// Verifies that an annotation with an empty Annotator field causes the + /// "Invalid Annotator Field - Empty" issue to be reported with the correct snippet prefix. + /// [TestMethod] - public void SpdxSnippet_Validate_InvalidAnnotation() + public void SpdxSnippet_Validate_InvalidAnnotation_ReportsIssue() { // Arrange: Create a valid snippet with an invalid annotation var snippet = new SpdxSnippet @@ -235,4 +268,149 @@ public void SpdxSnippet_Validate_InvalidAnnotation() // Assert: Verify the annotation issue is reported with the correct prefix Assert.Contains("Snippet 'SPDXRef-Snippet' Invalid Annotator Field - Empty", issues); } + + /// + /// Tests the method reports an empty snippet-from-file field. + /// + /// + /// Verifies the boundary condition where SnippetFromFile is empty: validation must report + /// the "Invalid Snippet From File Field - Empty" issue. + /// + [TestMethod] + public void SpdxSnippet_Validate_EmptySnippetFromFile_ReportsIssue() + { + // Arrange: Create a snippet with an empty SnippetFromFile + var snippet = new SpdxSnippet + { + Id = "SPDXRef-Snippet", + SnippetFromFile = "", + SnippetByteStart = 100, + SnippetByteEnd = 200, + ConcludedLicense = "MIT", + CopyrightText = "Copyright(c) 2024 DEMA Consulting" + }; + + // Act: Validate the snippet + var issues = new List(); + snippet.Validate(issues); + + // Assert: Verify the empty SnippetFromFile issue is reported + Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet From File Field - Empty"), issues); + } + + /// + /// Tests the method reports an invalid byte start value. + /// + /// + /// Verifies the lower boundary condition: a SnippetByteStart value of 0 (less than the + /// required minimum of 1) causes the "Invalid Snippet Byte Range Start Field" issue to be reported. + /// + [TestMethod] + public void SpdxSnippet_Validate_InvalidByteStart_ReportsIssue() + { + // Arrange: Create a snippet with SnippetByteStart < 1 + var snippet = new SpdxSnippet + { + Id = "SPDXRef-Snippet", + SnippetFromFile = "SPDXRef-File1", + SnippetByteStart = 0, + SnippetByteEnd = 200, + ConcludedLicense = "MIT", + CopyrightText = "Copyright(c) 2024 DEMA Consulting" + }; + + // Act: Validate the snippet + var issues = new List(); + snippet.Validate(issues); + + // Assert: Verify the invalid byte start issue is reported + Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet Byte Range Start Field '0'"), issues); + } + + /// + /// Tests the method reports an invalid byte end value. + /// + /// + /// Verifies the range boundary condition: a SnippetByteEnd less than SnippetByteStart + /// causes the "Invalid Snippet Byte Range End Field" issue to be reported. + /// + [TestMethod] + public void SpdxSnippet_Validate_InvalidByteEnd_ReportsIssue() + { + // Arrange: Create a snippet where SnippetByteEnd is less than SnippetByteStart + var snippet = new SpdxSnippet + { + Id = "SPDXRef-Snippet", + SnippetFromFile = "SPDXRef-File1", + SnippetByteStart = 100, + SnippetByteEnd = 50, + ConcludedLicense = "MIT", + CopyrightText = "Copyright(c) 2024 DEMA Consulting" + }; + + // Act: Validate the snippet + var issues = new List(); + snippet.Validate(issues); + + // Assert: Verify the invalid byte end issue is reported + Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet Byte Range End Field '50' < '100'"), issues); + } + + /// + /// Tests the method reports an empty concluded license field. + /// + /// + /// Verifies that an empty ConcludedLicense causes the "Invalid Concluded License Field - Empty" + /// issue to be reported. + /// + [TestMethod] + public void SpdxSnippet_Validate_EmptyConcludedLicense_ReportsIssue() + { + // Arrange: Create a snippet with an empty ConcludedLicense + var snippet = new SpdxSnippet + { + Id = "SPDXRef-Snippet", + SnippetFromFile = "SPDXRef-File1", + SnippetByteStart = 100, + SnippetByteEnd = 200, + ConcludedLicense = "", + CopyrightText = "Copyright(c) 2024 DEMA Consulting" + }; + + // Act: Validate the snippet + var issues = new List(); + snippet.Validate(issues); + + // Assert: Verify the empty ConcludedLicense issue is reported + Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Concluded License Field - Empty"), issues); + } + + /// + /// Tests the method reports an empty copyright text field. + /// + /// + /// Verifies that an empty CopyrightText causes the "Invalid Copyright Text Field - Empty" + /// issue to be reported. + /// + [TestMethod] + public void SpdxSnippet_Validate_EmptyCopyrightText_ReportsIssue() + { + // Arrange: Create a snippet with an empty CopyrightText + var snippet = new SpdxSnippet + { + Id = "SPDXRef-Snippet", + SnippetFromFile = "SPDXRef-File1", + SnippetByteStart = 100, + SnippetByteEnd = 200, + ConcludedLicense = "MIT", + CopyrightText = "" + }; + + // Act: Validate the snippet + var issues = new List(); + snippet.Validate(issues); + + // Assert: Verify the empty CopyrightText issue is reported + Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Copyright Text Field - Empty"), issues); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs index 1842928..3b2b315 100644 --- a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs @@ -62,4 +62,231 @@ public void SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists() document.Validate(issues); Assert.IsEmpty(issues); } + + /// + /// Tests that adding a relationship with an invalid source element ID throws . + /// + [TestMethod] + public void SpdxModelTransform_AddRelationship_InvalidSourceId_ThrowsArgumentException() + { + // Arrange: Load the SPDX 2.3 JSON example as a real document to transform + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + + // Act / Assert: Adding with a non-existent source ID throws ArgumentException + Assert.ThrowsExactly(() => + { + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-NonExistent", + RelatedSpdxElement = "SPDXRef-fromDoap-0", + RelationshipType = SpdxRelationshipType.DependsOn + }); + }); + } + + /// + /// Tests that adding a relationship with an invalid target element ID throws . + /// + [TestMethod] + public void SpdxModelTransform_AddRelationship_InvalidTargetId_ThrowsArgumentException() + { + // Arrange: Load the SPDX 2.3 JSON example as a real document to transform + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + + // Act / Assert: Adding with a non-existent target that is neither NOASSERTION nor DocumentRef- throws + Assert.ThrowsExactly(() => + { + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = "SPDXRef-NonExistent", + RelationshipType = SpdxRelationshipType.DependsOn + }); + }); + } + + /// + /// Tests that adding a duplicate relationship enhances the existing entry rather than duplicating it. + /// + [TestMethod] + public void SpdxModelTransform_AddRelationship_Duplicate_EnhancesExistingRelationship() + { + // Arrange: Load the SPDX 2.3 JSON example and add an initial relationship + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + var initialCount = document.Relationships.Length; + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = "SPDXRef-fromDoap-0", + RelationshipType = SpdxRelationshipType.DependsOn + }); + + // Act: Add the same relationship again + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = "SPDXRef-fromDoap-0", + RelationshipType = SpdxRelationshipType.DependsOn + }); + + // Assert: Only one new relationship was added (duplicate was merged, not appended) + Assert.AreEqual(initialCount + 1, document.Relationships.Length); + } + + /// + /// Tests that the batch Add with replace=true removes pre-existing matching relationships. + /// + [TestMethod] + public void SpdxModelTransform_AddRelationship_Replace_RemovesPreExistingRelationships() + { + // Arrange: Load the SPDX 2.3 JSON example and add an initial relationship + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = "SPDXRef-fromDoap-0", + RelationshipType = SpdxRelationshipType.DependsOn + }); + var countAfterFirstAdd = document.Relationships.Length; + + // Act: Replace the relationship with a different type using the batch overload + SpdxRelationships.Add( + document, + [ + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = "SPDXRef-fromDoap-0", + RelationshipType = SpdxRelationshipType.BuildToolOf + } + ], + replace: true); + + // Assert: The count is unchanged (old removed, new added) and the type changed + Assert.AreEqual(countAfterFirstAdd, document.Relationships.Length); + Assert.IsTrue(Array.Exists( + document.Relationships, + r => r.Id == "SPDXRef-Package" && + r.RelatedSpdxElement == "SPDXRef-fromDoap-0" && + r.RelationshipType == SpdxRelationshipType.BuildToolOf)); + Assert.IsFalse(Array.Exists( + document.Relationships, + r => r.Id == "SPDXRef-Package" && + r.RelatedSpdxElement == "SPDXRef-fromDoap-0" && + r.RelationshipType == SpdxRelationshipType.DependsOn)); + } + + /// + /// Tests that the batch Add with multiple relationships adds all of them. + /// + [TestMethod] + public void SpdxModelTransform_AddRelationship_BatchMultiple_AddsAllRelationships() + { + // Arrange: Load the SPDX 2.3 JSON example + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + var initialCount = document.Relationships.Length; + + // Act: Add two distinct relationships in a single batch call + SpdxRelationships.Add( + document, + [ + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = "SPDXRef-fromDoap-0", + RelationshipType = SpdxRelationshipType.DependsOn + }, + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = "SPDXRef-fromDoap-1", + RelationshipType = SpdxRelationshipType.DependsOn + } + ]); + + // Assert: Both relationships were added + Assert.AreEqual(initialCount + 2, document.Relationships.Length); + } + + /// + /// Tests that a relationship with NOASSERTION as the target element is accepted as valid. + /// + [TestMethod] + public void SpdxModelTransform_AddRelationship_NoAssertionTarget_AddsRelationship() + { + // Arrange: Load the SPDX 2.3 JSON example + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + var initialCount = document.Relationships.Length; + + // Act: Add a relationship where the target is NOASSERTION + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = SpdxElement.NoAssertion, + RelationshipType = SpdxRelationshipType.DependsOn + }); + + // Assert: Relationship was added without an exception + Assert.AreEqual(initialCount + 1, document.Relationships.Length); + Assert.IsTrue(Array.Exists( + document.Relationships, + r => r.Id == "SPDXRef-Package" && + r.RelatedSpdxElement == SpdxElement.NoAssertion && + r.RelationshipType == SpdxRelationshipType.DependsOn)); + } + + /// + /// Tests that a relationship with a DocumentRef- prefixed target is accepted as valid. + /// + [TestMethod] + public void SpdxModelTransform_AddRelationship_DocumentRefTarget_AddsRelationship() + { + // Arrange: Load the SPDX 2.3 JSON example + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + var initialCount = document.Relationships.Length; + + // Act: Add a relationship where the target uses the DocumentRef- prefix + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package", + RelatedSpdxElement = "DocumentRef-spdx-tool-1.2:SPDXRef-Package", + RelationshipType = SpdxRelationshipType.DependsOn + }); + + // Assert: Relationship was added without an exception + Assert.AreEqual(initialCount + 1, document.Relationships.Length); + Assert.IsTrue(Array.Exists( + document.Relationships, + r => r.Id == "SPDXRef-Package" && + r.RelatedSpdxElement == "DocumentRef-spdx-tool-1.2:SPDXRef-Package" && + r.RelationshipType == SpdxRelationshipType.DependsOn)); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs index 930d7b1..d016d9d 100644 --- a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs @@ -47,10 +47,10 @@ public class SpdxRelationshipsTests """; /// - /// Tests adding a relationship with a missing ID. + /// Tests that adding a relationship with a missing source ID throws . /// [TestMethod] - public void SpdxRelationships_AddSingle_MissingId() + public void SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException() { // Arrange: Deserialize the test document contents var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); @@ -75,10 +75,10 @@ public void SpdxRelationships_AddSingle_MissingId() } /// - /// Tests adding a relationship with a missing related element. + /// Tests that adding a relationship with a missing related element throws . /// [TestMethod] - public void SpdxRelationships_AddSingle_MissingRelatedElement() + public void SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentException() { // Arrange: Deserialize the test document contents var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); @@ -103,10 +103,10 @@ public void SpdxRelationships_AddSingle_MissingRelatedElement() } /// - /// Tests adding a relationship. + /// Tests that adding a valid relationship appends it to the document. /// [TestMethod] - public void SpdxRelationships_AddSingle_Success() + public void SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship() { // Arrange: Deserialize the test document contents var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); @@ -129,10 +129,10 @@ public void SpdxRelationships_AddSingle_Success() } /// - /// Tests adding a duplicate relationship. + /// Tests that adding a duplicate relationship enhances the existing entry rather than duplicating it. /// [TestMethod] - public void SpdxRelationships_AddSingle_Duplicate() + public void SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRelationship() { // Arrange: Deserialize the test document contents var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); @@ -163,10 +163,62 @@ public void SpdxRelationships_AddSingle_Duplicate() } /// - /// Tests adding multiple relationships. + /// Tests that a relationship with a NOASSERTION target is accepted as valid. /// [TestMethod] - public void SpdxRelationships_AddMultiple_Success() + public void SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship() + { + // Arrange: Deserialize the test document contents + var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); + + // Act: Add a relationship where the target is NOASSERTION + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package-1", + RelatedSpdxElement = SpdxElement.NoAssertion, + RelationshipType = SpdxRelationshipType.DependsOn + }); + + // Assert: Verify the relationship was added correctly + Assert.HasCount(1, document.Relationships); + Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.AreEqual(SpdxElement.NoAssertion, document.Relationships[0].RelatedSpdxElement); + Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + } + + /// + /// Tests that a relationship with a DocumentRef- prefixed target is accepted as valid. + /// + [TestMethod] + public void SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship() + { + // Arrange: Deserialize the test document contents + var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); + + // Act: Add a relationship where the target uses the DocumentRef- external-reference prefix + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package-1", + RelatedSpdxElement = "DocumentRef-external:SPDXRef-Package-3", + RelationshipType = SpdxRelationshipType.DependsOn + }); + + // Assert: Verify the relationship was added correctly without requiring the element in the document + Assert.HasCount(1, document.Relationships); + Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.AreEqual("DocumentRef-external:SPDXRef-Package-3", document.Relationships[0].RelatedSpdxElement); + Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + } + + /// + /// Tests that the batch Add with a single relationship appends it to the document. + /// + [TestMethod] + public void SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship() { // Arrange: Deserialize the test document contents var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); @@ -191,10 +243,10 @@ public void SpdxRelationships_AddMultiple_Success() } /// - /// Tests adding multiple relationships with a duplicate. + /// Tests that adding multiple duplicate relationships deduplicates them. /// [TestMethod] - public void SpdxRelationships_AddMultiple_Duplicate() + public void SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRelationships() { // Arrange: Deserialize the test document contents var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); @@ -225,10 +277,10 @@ public void SpdxRelationships_AddMultiple_Duplicate() } /// - /// Tests adding multiple relationships with a duplicate and replace. + /// Tests that the batch Add with replace=true removes pre-existing matching relationships. /// [TestMethod] - public void SpdxRelationships_AddMultiple_Replace() + public void SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRelationships() { // Arrange: Deserialize the test document contents and add an initial relationship var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); @@ -258,4 +310,51 @@ public void SpdxRelationships_AddMultiple_Replace() Assert.AreEqual("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); Assert.AreEqual(SpdxRelationshipType.BuildToolOf, document.Relationships[0].RelationshipType); } + + /// + /// Tests that a batch Add with replace=true and an invalid relationship leaves the document unmodified. + /// + [TestMethod] + public void SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified() + { + // Arrange: Deserialize the test document and add an initial relationship + var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); + SpdxRelationships.Add( + document, + new SpdxRelationship + { + Id = "SPDXRef-Package-1", + RelatedSpdxElement = "SPDXRef-Package-2", + RelationshipType = SpdxRelationshipType.DependsOn + }); + var initialRelationships = document.Relationships.ToArray(); + + // Act: Attempt a batch-add with replace=true where the second relationship has an invalid source ID + Assert.ThrowsExactly(() => + { + SpdxRelationships.Add( + document, + [ + new SpdxRelationship + { + Id = "SPDXRef-Package-1", + RelatedSpdxElement = "SPDXRef-Package-2", + RelationshipType = SpdxRelationshipType.BuildToolOf + }, + new SpdxRelationship + { + Id = "SPDXRef-Package-Missing", + RelatedSpdxElement = "SPDXRef-Package-2", + RelationshipType = SpdxRelationshipType.DependsOn + } + ], + replace: true); + }); + + // Assert: Document relationships are unchanged after the failed batch-add + Assert.HasCount(initialRelationships.Length, document.Relationships); + Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.AreEqual("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); + Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + } } From d275bd14ea156d66afbeb0a71a2482554fab7579 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Tue, 26 May 2026 19:12:02 -0400 Subject: [PATCH 02/12] Apply third formal review cycle fixes across all 24 review-sets Groups 1-5 formal reviews completed with issues fixed: Group 1 (Architecture, Design, AllRequirements, Purpose): - Add SpdxElementTests.cs, TestHelpers.cs, SpdxJsonHelpers.cs to folder inventory - Replace stale children refs in spdx-model.yaml and transform.yaml - Add 8 missing edges to architecture Mermaid diagram - Replace vague callers entry with 13 named callers in spdx-helpers.md - Add system-level tests for Helpers and Transform to SpdxModelTests.cs - Add EnhanceElement N/A justification to spdx-element.md verification doc - Add SpdxElementTests.cs to SpdxModel-SpdxElement review set in .reviewmark.yaml Group 2 (IO-Deserializer, IO-Serializer, Transform, Transform-Relationships, Annotation): - Add remarks to all 14 deserializer test classes and methods - Fix 3 test names missing underscore separator; rename 19 happy-path to 4-part - Rename serializer requirement IDs from SpdxModel-Serialization-* to SpdxModel-IO-* - Fix remarks ordering in EmitOptionalStrings and both Add methods - Rename 22 serializer tests to 4-part naming; add 4 missing boundary scenarios - Add remarks to Transform test class and 8 methods - Add remarks to SpdxAnnotationSame, enum members, test class, 12 test methods - Fix test isolation in Validate_InvalidComment (Missing -> Review) Group 3 (SpdxChecksum, SpdxCreationInformation, SpdxDocument, SpdxExternalDocumentReference): - Add remarks to SpdxChecksumTests class and 15 methods; fix AAA colon - Rename 6 SpdxCreationInformation tests to 4-part; fix contaminated Arrange - Add remarks to SpdxDocument class; add remarks to SpdxDocumentTests class and 18 methods - Rename 14 SpdxDocument tests to 4-part naming - Add remarks to SpdxExternalDocumentReferenceTests class and 6 methods - Rename 5 SpdxExternalDocumentReference tests to 4-part naming - Update all companion YAML and verification docs for renamed tests Group 4 (SpdxExternalReference, SpdxExtractedLicensingInfo, SpdxFile, SpdxHelpers, SpdxLicenseElement): - Fix DeepCopy remarks ordering in SpdxExternalReference, SpdxFile, SpdxExtractedLicensingInfo - Add remarks to SpdxFileSame nested class; add remarks to GetHashCode in SpdxExtractedLicensingInfoSame - Fix EnhanceString remarks ordering in SpdxHelpers - Fix stale SpdxSnippet_Enhance test ref in spdx-helpers.yaml - Fix 2 stale SpdxSnippet test refs in spdx-license-element.yaml - Add SpdxHelpers dependency to spdx-license-element.md design doc - Add remarks to test classes and all methods for ExternalReference, File, ExtractedLicensingInfo - Rename 8 ExternalReference, 11 File, 2 ExtractedLicensingInfo tests to 4-part naming - Fix Act & Assert -> Act / Assert comments; add Act / Assert label to SameComparer test - Update all companion YAML and verification docs for renamed tests Group 5 (SpdxPackage, SpdxSnippet, SpdxRelationship): - Add ReleaseDate/BuiltDate/ValidUntilDate to spdx-package.md Data Model section - Fix Validate_InvalidSnippetId test isolation (add ConcludedLicense, CopyrightText) - Fix two missing AAA colons in SpdxSnippetTests SameComparer test - Update relationship type count 44 -> 45 in YAML, design doc, and test remarks - Replace 4 inheritdoc tags with explicit XmlDoc in SpdxRelationship nested classes - Add SpdxRelationshipTypeExtensions_ToText_MissingSentinel test - Split 2 multi-scenario SameComparer tests into 6 focused single-scenario tests - Update companion YAML and verification docs for split and new tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .reviewmark.yaml | 1 + docs/design/introduction.md | 5 +- docs/design/spdx-model/spdx-helpers.md | 18 +- .../design/spdx-model/spdx-license-element.md | 1 + docs/design/spdx-model/spdx-model.md | 8 + docs/design/spdx-model/spdx-package.md | 15 +- docs/design/spdx-model/spdx-relationship.md | 4 +- docs/reqstream/spdx-model/io/io.yaml | 4 +- .../io/spdx-2-json-deserializer.yaml | 46 ++--- .../spdx-model/io/spdx-2-json-serializer.yaml | 52 ++--- .../reqstream/spdx-model/spdx-annotation.yaml | 1 + .../spdx-model/spdx-creation-information.yaml | 12 +- docs/reqstream/spdx-model/spdx-document.yaml | 28 +-- .../spdx-external-document-reference.yaml | 10 +- .../spdx-model/spdx-external-reference.yaml | 16 +- .../spdx-extracted-licensing-info.yaml | 4 +- docs/reqstream/spdx-model/spdx-file.yaml | 22 +- docs/reqstream/spdx-model/spdx-helpers.yaml | 4 +- .../spdx-model/spdx-license-element.yaml | 12 +- docs/reqstream/spdx-model/spdx-model.yaml | 12 +- .../spdx-model/spdx-relationship.yaml | 11 +- .../spdx-model/transform/transform.yaml | 7 +- .../spdx-model/io/spdx-2-json-deserializer.md | 92 ++++----- .../spdx-model/io/spdx-2-json-serializer.md | 110 ++++++---- .../spdx-model/spdx-creation-information.md | 24 +-- docs/verification/spdx-model/spdx-document.md | 59 +++--- docs/verification/spdx-model/spdx-element.md | 8 + .../spdx-external-document-reference.md | 20 +- .../spdx-model/spdx-external-reference.md | 32 +-- docs/verification/spdx-model/spdx-file.md | 44 ++-- .../spdx-model/spdx-relationship.md | 35 +++- .../IO/Spdx2JsonSerializer.cs | 6 +- .../SpdxAnnotation.cs | 20 ++ .../SpdxAnnotationType.cs | 12 ++ src/DemaConsulting.SpdxModel/SpdxDocument.cs | 8 + .../SpdxExternalReference.cs | 2 +- .../SpdxExtractedLicensingInfo.cs | 5 +- src/DemaConsulting.SpdxModel/SpdxFile.cs | 6 +- src/DemaConsulting.SpdxModel/SpdxHelpers.cs | 4 +- .../SpdxRelationship.cs | 36 +++- .../Transform/SpdxRelationships.cs | 36 ++-- .../IO/Spdx2JsonDeserialize22.cs | 11 +- .../IO/Spdx2JsonDeserialize23.cs | 11 +- .../IO/Spdx2JsonDeserializeAnnotation.cs | 18 +- .../IO/Spdx2JsonDeserializeChecksum.cs | 19 +- ...Spdx2JsonDeserializeCreationInformation.cs | 12 +- .../IO/Spdx2JsonDeserializeDocument.cs | 12 +- ...sonDeserializeExternalDocumentReference.cs | 18 +- .../Spdx2JsonDeserializeExternalReference.cs | 18 +- ...x2JsonDeserializeExtractedLicensingInfo.cs | 19 +- .../IO/Spdx2JsonDeserializeFile.cs | 19 +- .../IO/Spdx2JsonDeserializePackage.cs | 19 +- ...2JsonDeserializePackageVerificationCode.cs | 14 +- .../IO/Spdx2JsonDeserializeRelationship.cs | 18 +- .../IO/Spdx2JsonDeserializeSnippet.cs | 24 ++- .../IO/Spdx2JsonDeserializerTests.cs | 2 +- .../IO/Spdx2JsonSerializeAnnotation.cs | 4 +- .../IO/Spdx2JsonSerializeChecksum.cs | 4 +- .../Spdx2JsonSerializeCreationInformation.cs | 2 +- .../IO/Spdx2JsonSerializeDocument.cs | 4 +- ...2JsonSerializeExternalDocumentReference.cs | 4 +- .../IO/Spdx2JsonSerializeExternalReference.cs | 4 +- ...pdx2JsonSerializeExtractedLicensingInfo.cs | 4 +- .../IO/Spdx2JsonSerializeFile.cs | 4 +- .../IO/Spdx2JsonSerializePackage.cs | 4 +- ...dx2JsonSerializePackageVerificationCode.cs | 2 +- .../IO/Spdx2JsonSerializeRelationship.cs | 4 +- .../IO/Spdx2JsonSerializeSnippet.cs | 4 +- .../SpdxAnnotationTests.cs | 67 +++++- .../SpdxChecksumTests.cs | 83 +++++++- .../SpdxCreationInformationTests.cs | 14 +- .../SpdxDocumentTests.cs | 126 ++++++++++-- .../SpdxExternalDocumentReferenceTests.cs | 48 ++++- .../SpdxExternalReferenceTests.cs | 66 +++++- .../SpdxExtractedLicensingInfoTests.cs | 9 +- .../SpdxFileTests.cs | 71 ++++++- .../SpdxModelTests.cs | 72 ++++++- .../SpdxRelationshipTests.cs | 191 ++++++++++++++---- .../SpdxSnippetTests.cs | 8 +- .../Transforms/SpdxModelTransformTests.cs | 44 ++++ 80 files changed, 1453 insertions(+), 476 deletions(-) diff --git a/.reviewmark.yaml b/.reviewmark.yaml index 846ddab..28a22d4 100644 --- a/.reviewmark.yaml +++ b/.reviewmark.yaml @@ -159,6 +159,7 @@ reviews: - "docs/design/spdx-model/spdx-element.md" - "docs/verification/spdx-model/spdx-element.md" - "src/DemaConsulting.SpdxModel/SpdxElement.cs" + - "test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs" - id: SpdxModel-SpdxExternalDocumentReference title: Review that SpdxModel SpdxExternalDocumentReference Implementation is Correct diff --git a/docs/design/introduction.md b/docs/design/introduction.md index 053b0a1..e62ab4a 100644 --- a/docs/design/introduction.md +++ b/docs/design/introduction.md @@ -87,6 +87,7 @@ test/DemaConsulting.SpdxModel.Tests/ │ ├── Examples/ — Test example JSON files │ ├── Spdx2JsonDeserialize*.cs — Deserializer unit tests │ ├── Spdx2JsonSerialize*.cs — Serializer unit tests +│ ├── SpdxJsonHelpers.cs — IO test utility helpers │ └── SpdxModelIOTests.cs — IO subsystem integration tests ├── Transforms/ │ ├── SpdxModelTransformTests.cs — Transform subsystem integration tests @@ -96,6 +97,7 @@ test/DemaConsulting.SpdxModel.Tests/ ├── SpdxChecksumTests.cs — SpdxChecksum unit tests ├── SpdxCreationInformationTests.cs — SpdxCreationInformation unit tests ├── SpdxDocumentTests.cs — SpdxDocument unit tests +├── SpdxElementTests.cs — SpdxElement unit tests ├── SpdxExternalDocumentReferenceTests.cs — SpdxExternalDocumentReference unit tests ├── SpdxExternalReferenceTests.cs — SpdxExternalReference unit tests ├── SpdxExtractedLicensingInfoTests.cs — SpdxExtractedLicensingInfo unit tests @@ -104,7 +106,8 @@ test/DemaConsulting.SpdxModel.Tests/ ├── SpdxPackageTests.cs — SpdxPackage unit tests ├── SpdxPackageVerificationCodeTests.cs — SpdxPackageVerificationCode unit tests ├── SpdxRelationshipTests.cs — SpdxRelationship unit tests -└── SpdxSnippetTests.cs — SpdxSnippet unit tests +├── SpdxSnippetTests.cs — SpdxSnippet unit tests +└── TestHelpers.cs — Test utility helpers (SpdxTestHelpers) ``` ## Companion Artifact Structure diff --git a/docs/design/spdx-model/spdx-helpers.md b/docs/design/spdx-model/spdx-helpers.md index 3300281..368cc8a 100644 --- a/docs/design/spdx-model/spdx-helpers.md +++ b/docs/design/spdx-model/spdx-helpers.md @@ -43,8 +43,24 @@ for invalid input rather than throwing. ### Callers +**EnhanceString callers** (all data model `Enhance` methods): + - **SpdxElement** — `EnhanceString` used in `EnhanceElement`. -- All data model units that call `EnhanceString` in their `Enhance` methods. +- **SpdxAnnotation** — `EnhanceString` used in `Enhance`. +- **SpdxChecksum** — `EnhanceString` used in `Enhance`. +- **SpdxCreationInformation** — `EnhanceString` used in `Enhance`. +- **SpdxExternalDocumentReference** — `EnhanceString` used in `Enhance`. +- **SpdxExternalReference** — `EnhanceString` used in `Enhance`. +- **SpdxExtractedLicensingInfo** — `EnhanceString` used in `Enhance`. +- **SpdxFile** — `EnhanceString` used in `Enhance`. +- **SpdxLicenseElement** — `EnhanceString` used in `Enhance`. +- **SpdxPackage** — `EnhanceString` used in `Enhance`. +- **SpdxPackageVerificationCode** — `EnhanceString` used in `Enhance`. +- **SpdxRelationship** — `EnhanceString` used in `Enhance`. +- **SpdxSnippet** — `EnhanceString` used in `Enhance`. + +**IsValidSpdxDateTime callers**: + - **SpdxCreationInformation** — `IsValidSpdxDateTime` used in `Validate`. - **SpdxAnnotation** — `IsValidSpdxDateTime` used in `Validate`. - **SpdxPackage** — `IsValidSpdxDateTime` used in `Validate`. diff --git a/docs/design/spdx-model/spdx-license-element.md b/docs/design/spdx-model/spdx-license-element.md index b7d8719..35e6d84 100644 --- a/docs/design/spdx-model/spdx-license-element.md +++ b/docs/design/spdx-model/spdx-license-element.md @@ -59,6 +59,7 @@ N/A - `SpdxLicenseElement` is abstract. Subclasses implement their own `Validate - **SpdxElement** — abstract base class providing the `Id` property. - **SpdxAnnotation** — element-level annotations. +- **SpdxHelpers** — shared utility functions for fitness-ranked string selection. ### Callers diff --git a/docs/design/spdx-model/spdx-model.md b/docs/design/spdx-model/spdx-model.md index 0fabf3e..4f5f9b7 100644 --- a/docs/design/spdx-model/spdx-model.md +++ b/docs/design/spdx-model/spdx-model.md @@ -46,6 +46,14 @@ flowchart TD Spdx2JsonSerializer --> SpdxConstants SpdxRelationships --> SpdxDocument SpdxRelationships --> SpdxRelationship + SpdxDocument --> SpdxCreationInformation + SpdxDocument --> SpdxExtractedLicensingInfo + SpdxPackage --> SpdxChecksum + SpdxPackage --> SpdxExternalReference + SpdxPackage --> SpdxPackageVerificationCode + SpdxExternalDocumentReference --> SpdxChecksum + Spdx2JsonDeserializer --> SpdxHelpers + Spdx2JsonSerializer --> SpdxHelpers ``` ### External Interfaces diff --git a/docs/design/spdx-model/spdx-package.md b/docs/design/spdx-model/spdx-package.md index 1991159..446e351 100644 --- a/docs/design/spdx-model/spdx-package.md +++ b/docs/design/spdx-model/spdx-package.md @@ -57,6 +57,15 @@ registries or vulnerability databases. **PrimaryPackagePurpose**: `string?` — Primary purpose classification (e.g., `LIBRARY`, `APPLICATION`). +**ReleaseDate**: `string?` — Date and time the package was released, in SPDX date-time format +(`YYYY-MM-DDThh:mm:ssZ`); `null` if not specified. + +**BuiltDate**: `string?` — Date and time the package was built, in SPDX date-time format +(`YYYY-MM-DDThh:mm:ssZ`); `null` if not specified. + +**ValidUntilDate**: `string?` — Date and time after which the package should no longer be +considered valid, in SPDX date-time format (`YYYY-MM-DDThh:mm:ssZ`); `null` if not specified. + *Inherited from `SpdxLicenseElement`*: `Id`, `ConcludedLicense`, `LicenseComments`, `CopyrightText`, `AttributionText`, `Annotations`. @@ -92,8 +101,10 @@ registries or vulnerability databases. - *Preconditions*: none. - *Postconditions*: All discovered issues including nested checksum and external reference issues are appended to `issues`; an empty `DeclaredLicense` does not produce a validation issue. - When `doc` is non-null, entries in `HasFiles` that do not match any file ID in `doc.Files` - cause an issue to be recorded. + Non-null `ReleaseDate`, `BuiltDate`, or `ValidUntilDate` values that do not conform to the SPDX + date-time format (`YYYY-MM-DDThh:mm:ssZ`) each cause a validation issue to be recorded. + When `doc` is non-null, entries in `HasFiles` that do not match any file ID in `doc.Files` + cause an issue to be recorded. **Same**: `static IEqualityComparer` — compares by `Name` and `Version`. diff --git a/docs/design/spdx-model/spdx-relationship.md b/docs/design/spdx-model/spdx-relationship.md index 9d3f668..6910a4c 100644 --- a/docs/design/spdx-model/spdx-relationship.md +++ b/docs/design/spdx-model/spdx-relationship.md @@ -86,7 +86,7 @@ values and their canonical SPDX string representations. #### Enum Values -The enumeration defines 44 relationship type values plus the sentinel value `Missing` (= -1). +The enumeration defines 45 relationship type values plus the sentinel value `Missing` (= -1). Key values include: | Enum Value | SPDX String | @@ -96,7 +96,7 @@ Key values include: | `Contains` | `CONTAINS` | | `DependsOn` | `DEPENDS_ON` | | `GeneratedFrom` | `GENERATED_FROM` | -| … | … (44 values total) | +| … | … (45 values total) | #### Conversion Methods diff --git a/docs/reqstream/spdx-model/io/io.yaml b/docs/reqstream/spdx-model/io/io.yaml index 0630a90..9116dc4 100644 --- a/docs/reqstream/spdx-model/io/io.yaml +++ b/docs/reqstream/spdx-model/io/io.yaml @@ -33,8 +33,8 @@ sections: toolchain integration, and compliance reporting. Producing well-formed SPDX JSON ensures interoperability with other tools in the SPDX ecosystem. children: - - SpdxModel-Serialization-SerializeJson - - SpdxModel-Serialization-SerializeElements + - SpdxModel-IO-SerializeJson + - SpdxModel-IO-SerializeElements tests: - SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidDocument - SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidDocument diff --git a/docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml b/docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml index 3a95b7a..f8f33a3 100644 --- a/docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml +++ b/docs/reqstream/spdx-model/io/spdx-2-json-deserializer.yaml @@ -17,7 +17,7 @@ sections: interoperability with systems using the SPDX 2.2 specification. This ensures that the library can read and process existing SPDX 2.2 documents from various sources. tests: - - Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument + - Spdx2JsonDeserializer_Deserialize_ValidSpdx22Json_ReturnsExpectedDocument - id: SpdxModel-IO-Spdx2JsonDeserializer-Deserialize23Json title: The library shall support deserializing SPDX 2.3 JSON documents. @@ -28,7 +28,7 @@ sections: SPDX specification. This allows users to leverage new features and improvements introduced in SPDX 2.3 while maintaining compatibility with modern SBOM tools. tests: - - Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument + - Spdx2JsonDeserializer_Deserialize_ValidSpdx23Json_ReturnsExpectedDocument - id: SpdxModel-IO-Spdx2JsonDeserializer-DeserializeElements title: The library shall correctly deserialize all SPDX 2.x element types from JSON. @@ -40,25 +40,25 @@ sections: deserialized correctly ensures the library faithfully reconstructs every artifact present in a source SPDX JSON document. tests: - - Spdx2JsonDeserializer_DeserializeDocument_CorrectResults - - Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults - - Spdx2JsonDeserializer_DeserializeAnnotations_CorrectResults - - Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults - - Spdx2JsonDeserializer_DeserializeChecksums_CorrectResults - - Spdx2JsonDeserializer_DeserializeCreationInformation_CorrectResults - - Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectResults - - Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_CorrectResults - - Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults - - Spdx2JsonDeserializer_DeserializeExternalReferences_CorrectResults - - Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResults - - Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_CorrectResults - - Spdx2JsonDeserializer_DeserializeFile_CorrectResults - - Spdx2JsonDeserializer_DeserializeFiles_CorrectResults - - Spdx2JsonDeserializer_DeserializePackage_CorrectResults - - Spdx2JsonDeserializer_DeserializePackages_CorrectResults - - Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResults - - Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults - - Spdx2JsonDeserializer_DeserializeRelationships_CorrectResults - - Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults + - Spdx2JsonDeserializer_DeserializeDocument_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeAnnotation_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeAnnotations_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeChecksum_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeChecksums_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeCreationInformation_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeExternalDocumentReference_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeExternalReference_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeExternalReferences_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeFile_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeFiles_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializePackage_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializePackages_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializePackageVerificationCode_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeRelationship_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeRelationships_ValidInput_CorrectResults + - Spdx2JsonDeserializer_DeserializeSnippet_ValidInput_CorrectResults - Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero - - Spdx2JsonDeserializer_DeserializeSnippets_CorrectResults + - Spdx2JsonDeserializer_DeserializeSnippets_ValidInput_CorrectResults diff --git a/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml b/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml index a4ff373..1aa31f6 100644 --- a/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml +++ b/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml @@ -8,7 +8,7 @@ sections: sections: - title: Spdx2JsonSerializer Requirements requirements: - - id: SpdxModel-Serialization-SerializeJson + - id: SpdxModel-IO-SerializeJson title: The library shall support serializing SPDX documents to JSON format. tags: - serialization @@ -17,10 +17,10 @@ sections: format. This enables users to generate SBOMs programmatically and share them with other systems and tools in the SPDX ecosystem. tests: - - Spdx2JsonSerializer_SerializeDocument_CorrectResults - - Spdx2JsonSerializer_Serialize_CorrectResults + - Spdx2JsonSerializer_SerializeDocument_ValidInput_CorrectResults + - Spdx2JsonSerializer_Serialize_ValidInput_CorrectResults - - id: SpdxModel-Serialization-SerializeElements + - id: SpdxModel-IO-SerializeElements title: The library shall correctly serialize all SPDX 2.x element types to JSON. tags: - serialization @@ -30,23 +30,27 @@ sections: serialized correctly ensures the library faithfully produces all artifacts when writing an SPDX JSON document. tests: - - Spdx2JsonSerializer_SerializeAnnotation_CorrectResults - - Spdx2JsonSerializer_SerializeAnnotations_CorrectResults - - Spdx2JsonSerializer_SerializeChecksum_CorrectResults - - Spdx2JsonSerializer_SerializeChecksums_CorrectResults - - Spdx2JsonSerializer_SerializeCreationInformation_CorrectResults - - Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResults - - Spdx2JsonSerializer_SerializeExternalDocumentReferences_CorrectResults - - Spdx2JsonSerializer_SerializeExternalReference_CorrectResults - - Spdx2JsonSerializer_SerializeExternalReferences_CorrectResults - - Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults - - Spdx2JsonSerializer_SerializeExtractedLicensingInfos_CorrectResults - - Spdx2JsonSerializer_SerializeFile_CorrectResults - - Spdx2JsonSerializer_SerializeFiles_CorrectResults - - Spdx2JsonSerializer_SerializePackage_CorrectResults - - Spdx2JsonSerializer_SerializePackages_CorrectResults - - Spdx2JsonSerializer_SerializePackageVerificationCode_CorrectResults - - Spdx2JsonSerializer_SerializeRelationship_CorrectResults - - Spdx2JsonSerializer_SerializeRelationships_CorrectResults - - Spdx2JsonSerializer_SerializeSnippet_CorrectResults - - Spdx2JsonSerializer_SerializeSnippets_CorrectResults + - Spdx2JsonSerializer_SerializeAnnotation_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeAnnotations_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeAnnotation_NoId_OmitsSpdxId + - Spdx2JsonSerializer_SerializeChecksum_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeChecksums_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeCreationInformation_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeExternalDocumentReference_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeExternalDocumentReferences_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeExternalReference_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeExternalReferences_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeExtractedLicensingInfo_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeExtractedLicensingInfos_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeFile_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeFiles_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializePackage_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializePackages_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializePackageVerificationCode_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeRelationships_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeSnippet_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeSnippets_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeSnippet_WithAnnotation_IncludesAnnotation + - Spdx2JsonSerializer_SerializeSnippet_NoLineRange_EmitsByteRangeOnly + - Spdx2JsonSerializer_SerializeSnippet_PartialLineRange_EmitsByteRangeOnly diff --git a/docs/reqstream/spdx-model/spdx-annotation.yaml b/docs/reqstream/spdx-model/spdx-annotation.yaml index 94abf84..638cae1 100644 --- a/docs/reqstream/spdx-model/spdx-annotation.yaml +++ b/docs/reqstream/spdx-model/spdx-annotation.yaml @@ -26,3 +26,4 @@ sections: - SpdxAnnotationTypeExtensions_FromText_Invalid - SpdxAnnotationTypeExtensions_ToText_Valid - SpdxAnnotationTypeExtensions_ToText_Invalid + - SpdxAnnotationTypeExtensions_ToText_Missing diff --git a/docs/reqstream/spdx-model/spdx-creation-information.yaml b/docs/reqstream/spdx-model/spdx-creation-information.yaml index 3045209..f19946c 100644 --- a/docs/reqstream/spdx-model/spdx-creation-information.yaml +++ b/docs/reqstream/spdx-model/spdx-creation-information.yaml @@ -15,12 +15,12 @@ sections: who created the document and when. Supporting this element is essential for SPDX compliance and traceability of document provenance. tests: - - SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance - - SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxCreationInformation_DeepCopy_WithAllFieldsPopulated_CreatesEqualButDistinctInstance + - SpdxCreationInformation_Enhance_WithMissingFieldsInBase_AddsOrUpdatesInformationCorrectly - SpdxCreationInformation_Enhance_DuplicateCreators_DeduplicatesCreators - SpdxCreationInformation_Validate_ValidInformation_NoIssues - - SpdxCreationInformation_Validate_MissingCreators - - SpdxCreationInformation_Validate_InvalidCreator - - SpdxCreationInformation_Validate_InvalidCreatedDate - - SpdxCreationInformation_Validate_InvalidVersion + - SpdxCreationInformation_Validate_MissingCreators_ReportsIssue + - SpdxCreationInformation_Validate_InvalidCreator_ReportsIssue + - SpdxCreationInformation_Validate_InvalidCreatedDate_ReportsIssue + - SpdxCreationInformation_Validate_InvalidVersion_ReportsIssue - SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue diff --git a/docs/reqstream/spdx-model/spdx-document.yaml b/docs/reqstream/spdx-model/spdx-document.yaml index 9d86101..4ce88bd 100644 --- a/docs/reqstream/spdx-model/spdx-document.yaml +++ b/docs/reqstream/spdx-model/spdx-document.yaml @@ -15,8 +15,8 @@ sections: packages, files, snippets, relationships, and annotations, and is required for expressing any SPDX-compliant bill of materials. tests: - - SpdxDocument_SameComparer_ComparesCorrectly - - SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance + - SpdxDocument_Same_DocumentsWithMatchingRootPackages_AreEqual + - SpdxDocument_DeepCopy_WithPopulatedDocument_CreatesEqualButDistinctInstance - id: SpdxModel-Data-Document-Validate title: The library shall validate SPDX document fields and report all violations. @@ -28,15 +28,15 @@ sections: consumed or distributed. Reporting all violations rather than failing on the first allows tooling to produce complete diagnostic output in a single pass. tests: - - SpdxDocument_Validate_NoIssues - - SpdxDocument_Validate_InvalidId - - SpdxDocument_Validate_InvalidName - - SpdxDocument_Validate_InvalidVersion - - SpdxDocument_Validate_InvalidDataLicense - - SpdxDocument_Validate_InvalidNameSpace - - SpdxDocument_Validate_DuplicatePackageIds - - SpdxDocument_Validate_InvalidRelationship - - SpdxDocument_Validate_InvalidAnnotation + - SpdxDocument_Validate_ValidDocument_ReportsNoIssues + - SpdxDocument_Validate_InvalidId_ReportsIssue + - SpdxDocument_Validate_InvalidName_ReportsIssue + - SpdxDocument_Validate_InvalidVersion_ReportsIssue + - SpdxDocument_Validate_InvalidDataLicense_ReportsIssue + - SpdxDocument_Validate_InvalidNameSpace_ReportsIssue + - SpdxDocument_Validate_DuplicatePackageIds_ReportsIssue + - SpdxDocument_Validate_InvalidRelationship_ReportsIssue + - SpdxDocument_Validate_InvalidAnnotation_ReportsIssue - id: SpdxModel-Data-Document-Ntia title: The library shall validate SPDX documents for NTIA minimum SBOM element compliance. @@ -49,7 +49,7 @@ sections: data fields that must be present in a conforming SBOM. Supporting this check allows downstream tooling to assess NTIA compliance without reimplementing the rules. tests: - - SpdxDocument_Validate_NtiaIssues + - SpdxDocument_Validate_NtiaMinimumElements_ReportsIssues - id: SpdxModel-Data-Document-ElementQuery title: The library shall support querying SPDX document elements by ID. @@ -60,7 +60,7 @@ sections: SPDX identifier. Providing a GetElement helper reduces boilerplate in downstream tools and ensures consistent traversal semantics. tests: - - SpdxDocument_GetAllElements_Correct + - SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelationshipElements - SpdxDocument_GetElement_Document_ReturnsDocumentElement - SpdxDocument_GetElement_File_ReturnsFileElement - SpdxDocument_GetElement_Package_ReturnsPackageElement @@ -75,4 +75,4 @@ sections: described by the document. Providing a GetRootPackages helper reduces boilerplate in downstream tools and ensures consistent traversal semantics. tests: - - SpdxDocument_GetRootPackages_CorrectPackages + - SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCorrectPackages diff --git a/docs/reqstream/spdx-model/spdx-external-document-reference.yaml b/docs/reqstream/spdx-model/spdx-external-document-reference.yaml index e5e7cbc..3046174 100644 --- a/docs/reqstream/spdx-model/spdx-external-document-reference.yaml +++ b/docs/reqstream/spdx-model/spdx-external-document-reference.yaml @@ -15,9 +15,9 @@ sections: enabling modular SBOM construction and linking between related software inventories. This is essential for managing complex multi-component software systems. tests: - - SpdxExternalDocumentReference_SameComparer_ComparesCorrectly - - SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstance - - SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxExternalDocumentReference_Validate_MissingId - - SpdxExternalDocumentReference_Validate_MissingDocument + - SpdxExternalDocumentReference_SameComparer_SameDocument_ReturnsEqual + - SpdxExternalDocumentReference_DeepCopy_ValidInstance_ReturnsEqualButDistinctInstance + - SpdxExternalDocumentReference_Enhance_WithNewAndMatchingEntries_MergesAndAppendsCorrectly + - SpdxExternalDocumentReference_Validate_MissingId_ReportsIssue + - SpdxExternalDocumentReference_Validate_MissingDocument_ReportsIssue - SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue diff --git a/docs/reqstream/spdx-model/spdx-external-reference.yaml b/docs/reqstream/spdx-model/spdx-external-reference.yaml index 014f847..dc7a8c2 100644 --- a/docs/reqstream/spdx-model/spdx-external-reference.yaml +++ b/docs/reqstream/spdx-model/spdx-external-reference.yaml @@ -18,11 +18,11 @@ sections: - SpdxExternalReference_SameComparer_ComparesCorrectly - SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance - SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxExternalReference_Validate_InvalidCategory - - SpdxExternalReference_Validate_InvalidType - - SpdxExternalReference_Validate_InvalidLocator - - SpdxReferenceCategoryExtensions_FromText_Valid - - SpdxReferenceCategoryExtensions_FromText_Invalid - - SpdxReferenceCategoryExtensions_ToText_Valid - - SpdxReferenceCategoryExtensions_ToText_InvalidCategory - - SpdxReferenceCategoryExtensions_ToText_MissingCategory + - SpdxExternalReference_Validate_InvalidCategory_ReportsIssue + - SpdxExternalReference_Validate_InvalidType_ReportsIssue + - SpdxExternalReference_Validate_InvalidLocator_ReportsIssue + - SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly + - SpdxReferenceCategoryExtensions_FromText_InvalidInput_ReturnsNull + - SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrectly + - SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull + - SpdxReferenceCategoryExtensions_ToText_MissingCategory_ReturnsNull diff --git a/docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml b/docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml index 56d484a..41d2e5e 100644 --- a/docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml +++ b/docs/reqstream/spdx-model/spdx-extracted-licensing-info.yaml @@ -19,5 +19,5 @@ sections: - SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance - SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly - SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues - - SpdxExtractedLicensingInfo_Validate_InvalidLicenseId - - SpdxExtractedLicensingInfo_Validate_InvalidExtractedText + - SpdxExtractedLicensingInfo_Validate_InvalidLicenseId_ReportsIssue + - SpdxExtractedLicensingInfo_Validate_InvalidExtractedText_ReportsIssue diff --git a/docs/reqstream/spdx-model/spdx-file.yaml b/docs/reqstream/spdx-model/spdx-file.yaml index 64fbdac..8594975 100644 --- a/docs/reqstream/spdx-model/spdx-file.yaml +++ b/docs/reqstream/spdx-model/spdx-file.yaml @@ -15,14 +15,14 @@ sections: file elements enables fine-grained tracking of individual source files, binaries, and their associated licensing and copyright information. tests: - - SpdxFile_SameComparer_ComparesCorrectly - - SpdxFile_DeepCopy_CreatesEqualButDistinctInstance - - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxFile_Validate_ReportsInvalidFileId - - SpdxFile_Validate_ReportsInvalidFileName - - SpdxFile_Validate_ReportsWhenSha1ChecksumMissing - - SpdxFile_Validate_Success - - SpdxFileTypeExtensions_FromText_Valid - - SpdxFileTypeExtensions_FromText_Invalid - - SpdxFileTypeExtensions_ToText_Valid - - SpdxFileTypeExtensions_ToText_Invalid + - SpdxFile_SameComparer_MatchingAndDistinctFiles_ComparesCorrectly + - SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy + - SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly + - SpdxFile_Validate_InvalidFileId_ReportsIssue + - SpdxFile_Validate_InvalidFileName_ReportsIssue + - SpdxFile_Validate_MissingSha1Checksum_ReportsIssue + - SpdxFile_Validate_ValidFile_ReportsNoIssues + - SpdxFileTypeExtensions_FromText_ValidInput_ParsesCorrectly + - SpdxFileTypeExtensions_FromText_InvalidInput_ThrowsException + - SpdxFileTypeExtensions_ToText_ValidEnum_FormatsCorrectly + - SpdxFileTypeExtensions_ToText_InvalidEnum_ThrowsException diff --git a/docs/reqstream/spdx-model/spdx-helpers.yaml b/docs/reqstream/spdx-model/spdx-helpers.yaml index 2e3d778..5554023 100644 --- a/docs/reqstream/spdx-model/spdx-helpers.yaml +++ b/docs/reqstream/spdx-model/spdx-helpers.yaml @@ -36,8 +36,8 @@ sections: meaningful data is preserved regardless of the order in which sources are merged. tests: - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly - - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly + - SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly - SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly - SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue - SpdxHelpers_EnhanceString_NullInputs_ReturnsNull diff --git a/docs/reqstream/spdx-model/spdx-license-element.yaml b/docs/reqstream/spdx-model/spdx-license-element.yaml index b7cc76b..fbf7477 100644 --- a/docs/reqstream/spdx-model/spdx-license-element.yaml +++ b/docs/reqstream/spdx-model/spdx-license-element.yaml @@ -21,8 +21,8 @@ sections: SpdxModel-Data-LicenseElement-Enhance below. tests: - SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance - - SpdxFile_DeepCopy_CreatesEqualButDistinctInstance - - SpdxSnippet_DeepCopy_CreatesEqualButDistinctInstance + - SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy + - SpdxSnippet_DeepCopy_FullyPopulatedSnippet_CreatesEqualButDistinctCopy children: - SpdxModel-Data-LicenseElement-Data - SpdxModel-Data-LicenseElement-Enhance @@ -38,8 +38,8 @@ sections: reported and validated without reference to other elements. tests: - SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance - - SpdxFile_DeepCopy_CreatesEqualButDistinctInstance - - SpdxSnippet_DeepCopy_CreatesEqualButDistinctInstance + - SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy + - SpdxSnippet_DeepCopy_FullyPopulatedSnippet_CreatesEqualButDistinctCopy - id: SpdxModel-Data-LicenseElement-Enhance title: >- The library shall merge concluded license, copyright text, license comments, @@ -54,5 +54,5 @@ sections: from the secondary source only when the primary record lacks a concrete value. tests: - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly - - SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly - - SpdxSnippet_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly + - SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly diff --git a/docs/reqstream/spdx-model/spdx-model.yaml b/docs/reqstream/spdx-model/spdx-model.yaml index 33ced56..9e17d8b 100644 --- a/docs/reqstream/spdx-model/spdx-model.yaml +++ b/docs/reqstream/spdx-model/spdx-model.yaml @@ -30,8 +30,7 @@ sections: Helper utilities provide shared functionality used across the data model, including date-time validation and string enhancement operations. tests: - - SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue - - SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue + - SpdxModel_Helpers_DateTimeValidation_IsObservableThroughDocumentModel children: - SpdxModel-Data-Helpers-DateTime - SpdxModel-Data-Helpers-EnhanceString @@ -113,7 +112,12 @@ sections: children: - SpdxModel-Data-Document-Validate - SpdxModel-Data-Annotations - - SpdxModel-Data-Checksums + - SpdxModel-Data-Checksum-Compare + - SpdxModel-Data-Checksum-DeepCopy + - SpdxModel-Data-Checksum-Enhance + - SpdxModel-Data-Checksum-Validate + - SpdxModel-Data-Checksum-FromText + - SpdxModel-Data-Checksum-ToText - SpdxModel-Data-CreationInformation tests: - SpdxModel_ReadSpdxJson_Spdx22Example_PassesValidation @@ -162,4 +166,4 @@ sections: children: - SpdxModel-Transform-Utilities tests: - - SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists + - SpdxModel_Transform_AddRelationship_IsObservableThroughDocumentModel diff --git a/docs/reqstream/spdx-model/spdx-relationship.yaml b/docs/reqstream/spdx-model/spdx-relationship.yaml index c1b0446..b413d3a 100644 --- a/docs/reqstream/spdx-model/spdx-relationship.yaml +++ b/docs/reqstream/spdx-model/spdx-relationship.yaml @@ -14,8 +14,12 @@ sections: Relationships must be compared to support array merging and deduplication across SPDX documents. tests: - - SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqual - - SpdxRelationship_SameElementsComparer_SameElementsDifferentType_ReturnsEqual + - SpdxRelationship_SameComparer_MatchingRelationships_ReturnsTrue + - SpdxRelationship_SameComparer_DifferentRelationships_ReturnsFalse + - SpdxRelationship_SameComparer_MatchingRelationships_ReturnsSameHashCode + - SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsTrue + - SpdxRelationship_SameElementsComparer_DifferentElements_ReturnsFalse + - SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsSameHashCode - id: SpdxModel-Data-Relationship-DeepCopy title: The library shall support creating independent deep copies of SPDX relationships. tags: @@ -46,7 +50,7 @@ sections: - SpdxRelationship_Validate_MissingRelatedElementId_ReportsIssue - SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue - id: SpdxModel-Data-RelationshipType-Conversion - title: The library shall support round-trip text conversion for all 44 SPDX relationship type tokens. + title: The library shall support round-trip text conversion for all 45 SPDX relationship type tokens. tags: - data-model justification: | @@ -56,4 +60,5 @@ sections: - SpdxRelationshipTypeExtensions_FromText_KnownText_ReturnsMappedEnum - SpdxRelationshipTypeExtensions_FromText_UnknownText_ThrowsInvalidOperationException - SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText + - SpdxRelationshipTypeExtensions_ToText_MissingSentinel_ThrowsInvalidOperationException - SpdxRelationshipTypeExtensions_ToText_UnknownEnum_ThrowsInvalidOperationException diff --git a/docs/reqstream/spdx-model/transform/transform.yaml b/docs/reqstream/spdx-model/transform/transform.yaml index 54d9e1b..b7027d9 100644 --- a/docs/reqstream/spdx-model/transform/transform.yaml +++ b/docs/reqstream/spdx-model/transform/transform.yaml @@ -15,6 +15,9 @@ sections: relationship management. This supports use cases where documents need to be modified or enriched after initial creation. children: - - SpdxModel-Transform-RelationshipUtilities + - SpdxModel-Transform-RelationshipUtilities-AddSingle + - SpdxModel-Transform-RelationshipUtilities-AddMultiple + - SpdxModel-Transform-RelationshipUtilities-Validate + - SpdxModel-Transform-RelationshipUtilities-Atomicity tests: - - SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists + - SpdxModel_Transform_AddRelationship_IsObservableThroughDocumentModel diff --git a/docs/verification/spdx-model/io/spdx-2-json-deserializer.md b/docs/verification/spdx-model/io/spdx-2-json-deserializer.md index 96a48aa..a8545f1 100644 --- a/docs/verification/spdx-model/io/spdx-2-json-deserializer.md +++ b/docs/verification/spdx-model/io/spdx-2-json-deserializer.md @@ -18,118 +18,118 @@ All automated tests pass with zero failures. #### Test Scenarios -**Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument**: Verifies that +**Spdx2JsonDeserializer_Deserialize_ValidSpdx22Json_ReturnsExpectedDocument**: Verifies that a complete SPDX 2.2 JSON document is deserialized to a fully populated SpdxDocument with all fields correctly mapped. This scenario is tested by -`Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument`. +`Spdx2JsonDeserializer_Deserialize_ValidSpdx22Json_ReturnsExpectedDocument`. -**Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument**: Verifies that +**Spdx2JsonDeserializer_Deserialize_ValidSpdx23Json_ReturnsExpectedDocument**: Verifies that a complete SPDX 2.3 JSON document is deserialized to a fully populated SpdxDocument with all fields correctly mapped. This scenario is tested by -`Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument`. +`Spdx2JsonDeserializer_Deserialize_ValidSpdx23Json_ReturnsExpectedDocument`. -**Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults**: Verifies that a single +**Spdx2JsonDeserializer_DeserializeAnnotation_ValidInput_CorrectResults**: Verifies that a single annotation JSON object is deserialized to an SpdxAnnotation with all fields correctly populated. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeAnnotation_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeAnnotations_CorrectResults**: Verifies that a JSON array +**Spdx2JsonDeserializer_DeserializeAnnotations_ValidInput_CorrectResults**: Verifies that a JSON array of annotation objects is deserialized to a collection of SpdxAnnotation instances. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeAnnotations_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeAnnotations_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults**: Verifies that a single checksum +**Spdx2JsonDeserializer_DeserializeChecksum_ValidInput_CorrectResults**: Verifies that a single checksum JSON object is deserialized to an SpdxChecksum with algorithm and value fields correctly populated. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeChecksum_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeChecksums_CorrectResults**: Verifies that a JSON array of +**Spdx2JsonDeserializer_DeserializeChecksums_ValidInput_CorrectResults**: Verifies that a JSON array of checksum objects is deserialized to a collection of SpdxChecksum instances. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeChecksums_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeChecksums_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeCreationInformation_CorrectResults**: Verifies that the +**Spdx2JsonDeserializer_DeserializeCreationInformation_ValidInput_CorrectResults**: Verifies that the creationInfo JSON object is deserialized to an SpdxCreationInformation with all fields correctly populated. This scenario is tested by -`Spdx2JsonDeserializer_DeserializeCreationInformation_CorrectResults`. +`Spdx2JsonDeserializer_DeserializeCreationInformation_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeDocument_CorrectResults**: Verifies that the top-level +**Spdx2JsonDeserializer_DeserializeDocument_ValidInput_CorrectResults**: Verifies that the top-level document JSON fields are deserialized to the SpdxDocument with all document-level fields correctly populated. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeDocument_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeDocument_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectResults**: Verifies that +**Spdx2JsonDeserializer_DeserializeExternalDocumentReference_ValidInput_CorrectResults**: Verifies that a single external document reference JSON object is deserialized to an SpdxExternalDocumentReference with all fields correctly populated. This scenario is tested by -`Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectResults`. +`Spdx2JsonDeserializer_DeserializeExternalDocumentReference_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_CorrectResults**: Verifies that +**Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_ValidInput_CorrectResults**: Verifies that a JSON array of external document reference objects is deserialized to a collection of SpdxExternalDocumentReference instances. This scenario is tested by -`Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_CorrectResults`. +`Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults**: Verifies that a single +**Spdx2JsonDeserializer_DeserializeExternalReference_ValidInput_CorrectResults**: Verifies that a single external reference JSON object is deserialized to an SpdxExternalReference with category, type, and locator fields correctly populated. This scenario is tested by -`Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults`. +`Spdx2JsonDeserializer_DeserializeExternalReference_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeExternalReferences_CorrectResults**: Verifies that a JSON +**Spdx2JsonDeserializer_DeserializeExternalReferences_ValidInput_CorrectResults**: Verifies that a JSON array of external reference objects is deserialized to a collection of SpdxExternalReference instances. This scenario is tested by -`Spdx2JsonDeserializer_DeserializeExternalReferences_CorrectResults`. +`Spdx2JsonDeserializer_DeserializeExternalReferences_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResults**: Verifies that a +**Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_ValidInput_CorrectResults**: Verifies that a single extracted licensing info JSON object is deserialized to an SpdxExtractedLicensingInfo with all fields correctly populated. This scenario is tested by -`Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResults`. +`Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_CorrectResults**: Verifies that a +**Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_ValidInput_CorrectResults**: Verifies that a JSON array of extracted licensing info objects is deserialized to a collection of SpdxExtractedLicensingInfo instances. This scenario is tested by -`Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_CorrectResults`. +`Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeFile_CorrectResults**: Verifies that a single file JSON +**Spdx2JsonDeserializer_DeserializeFile_ValidInput_CorrectResults**: Verifies that a single file JSON object is deserialized to an SpdxFile with all fields correctly populated. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeFile_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeFile_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeFiles_CorrectResults**: Verifies that a JSON array of file +**Spdx2JsonDeserializer_DeserializeFiles_ValidInput_CorrectResults**: Verifies that a JSON array of file objects is deserialized to a collection of SpdxFile instances. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeFiles_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeFiles_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializePackage_CorrectResults**: Verifies that a single package +**Spdx2JsonDeserializer_DeserializePackage_ValidInput_CorrectResults**: Verifies that a single package JSON object is deserialized to an SpdxPackage with all fields correctly populated. -This scenario is tested by `Spdx2JsonDeserializer_DeserializePackage_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializePackage_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializePackages_CorrectResults**: Verifies that a JSON array of +**Spdx2JsonDeserializer_DeserializePackages_ValidInput_CorrectResults**: Verifies that a JSON array of package objects is deserialized to a collection of SpdxPackage instances. -This scenario is tested by `Spdx2JsonDeserializer_DeserializePackages_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializePackages_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResults**: Verifies that a +**Spdx2JsonDeserializer_DeserializePackageVerificationCode_ValidInput_CorrectResults**: Verifies that a package verification code JSON object is deserialized to an SpdxPackageVerificationCode with all fields correctly populated. This scenario is tested by -`Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResults`. +`Spdx2JsonDeserializer_DeserializePackageVerificationCode_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults**: Verifies that a single +**Spdx2JsonDeserializer_DeserializeRelationship_ValidInput_CorrectResults**: Verifies that a single relationship JSON object is deserialized to an SpdxRelationship with all fields correctly populated. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeRelationship_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeRelationships_CorrectResults**: Verifies that a JSON array +**Spdx2JsonDeserializer_DeserializeRelationships_ValidInput_CorrectResults**: Verifies that a JSON array of relationship objects is deserialized to a collection of SpdxRelationship instances. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeRelationships_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeRelationships_ValidInput_CorrectResults`. -**Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults**: Verifies that a single snippet +**Spdx2JsonDeserializer_DeserializeSnippet_ValidInput_CorrectResults**: Verifies that a single snippet JSON object is deserialized to an SpdxSnippet with byte ranges and line ranges correctly populated. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeSnippet_ValidInput_CorrectResults`. **Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero**: Verifies that when a snippet JSON object omits line range fields, the deserialized SpdxSnippet defaults @@ -137,6 +137,6 @@ those fields to zero. This scenario is tested by `Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero`. -**Spdx2JsonDeserializer_DeserializeSnippets_CorrectResults**: Verifies that a JSON array of +**Spdx2JsonDeserializer_DeserializeSnippets_ValidInput_CorrectResults**: Verifies that a JSON array of snippet objects is deserialized to a collection of SpdxSnippet instances. -This scenario is tested by `Spdx2JsonDeserializer_DeserializeSnippets_CorrectResults`. +This scenario is tested by `Spdx2JsonDeserializer_DeserializeSnippets_ValidInput_CorrectResults`. diff --git a/docs/verification/spdx-model/io/spdx-2-json-serializer.md b/docs/verification/spdx-model/io/spdx-2-json-serializer.md index 298e1fa..4b86681 100644 --- a/docs/verification/spdx-model/io/spdx-2-json-serializer.md +++ b/docs/verification/spdx-model/io/spdx-2-json-serializer.md @@ -18,103 +18,125 @@ All automated tests pass with zero failures. #### Test Scenarios -**Spdx2JsonSerializer_SerializeAnnotation_CorrectResults**: Verifies that a single +**Spdx2JsonSerializer_SerializeAnnotation_ValidInput_CorrectResults**: Verifies that a single SpdxAnnotation is serialized to the expected JSON structure with all fields correctly mapped. -This scenario is tested by `Spdx2JsonSerializer_SerializeAnnotation_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeAnnotation_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeAnnotations_CorrectResults**: Verifies that a collection of +**Spdx2JsonSerializer_SerializeAnnotations_ValidInput_CorrectResults**: Verifies that a collection of SpdxAnnotation instances is serialized to the expected JSON array structure. -This scenario is tested by `Spdx2JsonSerializer_SerializeAnnotations_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeAnnotations_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeChecksum_CorrectResults**: Verifies that a single SpdxChecksum +**Spdx2JsonSerializer_SerializeAnnotation_NoId_OmitsSpdxId**: Verifies that when an +`SpdxAnnotation` has an empty `Id` string, the serializer omits the `SPDXID` field from the +JSON output while still emitting all other annotation fields (annotator, annotationDate, +annotationType, comment). +This scenario is tested by `Spdx2JsonSerializer_SerializeAnnotation_NoId_OmitsSpdxId`. + +**Spdx2JsonSerializer_SerializeChecksum_ValidInput_CorrectResults**: Verifies that a single SpdxChecksum is serialized to the expected JSON structure with algorithm and value fields correctly mapped. -This scenario is tested by `Spdx2JsonSerializer_SerializeChecksum_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeChecksum_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeChecksums_CorrectResults**: Verifies that a collection of +**Spdx2JsonSerializer_SerializeChecksums_ValidInput_CorrectResults**: Verifies that a collection of SpdxChecksum instances is serialized to the expected JSON array structure. -This scenario is tested by `Spdx2JsonSerializer_SerializeChecksums_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeChecksums_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeCreationInformation_CorrectResults**: Verifies that +**Spdx2JsonSerializer_SerializeCreationInformation_ValidInput_CorrectResults**: Verifies that SpdxCreationInformation is serialized to the expected JSON structure with all creation fields correctly mapped. This scenario is tested by -`Spdx2JsonSerializer_SerializeCreationInformation_CorrectResults`. +`Spdx2JsonSerializer_SerializeCreationInformation_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeDocument_CorrectResults**: Verifies that the top-level +**Spdx2JsonSerializer_SerializeDocument_ValidInput_CorrectResults**: Verifies that the top-level SpdxDocument structure (excluding nested collections) is serialized to the expected JSON with all document-level fields correctly mapped. -This scenario is tested by `Spdx2JsonSerializer_SerializeDocument_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeDocument_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_Serialize_CorrectResults**: Verifies that a complete SpdxDocument +**Spdx2JsonSerializer_Serialize_ValidInput_CorrectResults**: Verifies that a complete SpdxDocument including all nested packages, files, snippets, and relationships is serialized to the expected JSON output. -This scenario is tested by `Spdx2JsonSerializer_Serialize_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_Serialize_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResults**: Verifies that a +**Spdx2JsonSerializer_SerializeExternalDocumentReference_ValidInput_CorrectResults**: Verifies that a single SpdxExternalDocumentReference is serialized to the expected JSON structure. This scenario is tested by -`Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResults`. +`Spdx2JsonSerializer_SerializeExternalDocumentReference_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeExternalDocumentReferences_CorrectResults**: Verifies that a +**Spdx2JsonSerializer_SerializeExternalDocumentReferences_ValidInput_CorrectResults**: Verifies that a collection of SpdxExternalDocumentReference instances is serialized to the expected JSON array. This scenario is tested by -`Spdx2JsonSerializer_SerializeExternalDocumentReferences_CorrectResults`. +`Spdx2JsonSerializer_SerializeExternalDocumentReferences_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeExternalReference_CorrectResults**: Verifies that a single +**Spdx2JsonSerializer_SerializeExternalReference_ValidInput_CorrectResults**: Verifies that a single SpdxExternalReference is serialized to the expected JSON structure with category, type, and locator fields correctly mapped. -This scenario is tested by `Spdx2JsonSerializer_SerializeExternalReference_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeExternalReference_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeExternalReferences_CorrectResults**: Verifies that a +**Spdx2JsonSerializer_SerializeExternalReferences_ValidInput_CorrectResults**: Verifies that a collection of SpdxExternalReference instances is serialized to the expected JSON array. -This scenario is tested by `Spdx2JsonSerializer_SerializeExternalReferences_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeExternalReferences_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults**: Verifies that a +**Spdx2JsonSerializer_SerializeExtractedLicensingInfo_ValidInput_CorrectResults**: Verifies that a single SpdxExtractedLicensingInfo is serialized to the expected JSON structure. This scenario is tested by -`Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults`. +`Spdx2JsonSerializer_SerializeExtractedLicensingInfo_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeExtractedLicensingInfos_CorrectResults**: Verifies that a +**Spdx2JsonSerializer_SerializeExtractedLicensingInfos_ValidInput_CorrectResults**: Verifies that a collection of SpdxExtractedLicensingInfo instances is serialized to the expected JSON array. This scenario is tested by -`Spdx2JsonSerializer_SerializeExtractedLicensingInfos_CorrectResults`. +`Spdx2JsonSerializer_SerializeExtractedLicensingInfos_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeFile_CorrectResults**: Verifies that a single SpdxFile is +**Spdx2JsonSerializer_SerializeFile_ValidInput_CorrectResults**: Verifies that a single SpdxFile is serialized to the expected JSON structure with all file fields correctly mapped. -This scenario is tested by `Spdx2JsonSerializer_SerializeFile_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeFile_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeFiles_CorrectResults**: Verifies that a collection of SpdxFile +**Spdx2JsonSerializer_SerializeFiles_ValidInput_CorrectResults**: Verifies that a collection of SpdxFile instances is serialized to the expected JSON array. -This scenario is tested by `Spdx2JsonSerializer_SerializeFiles_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeFiles_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializePackage_CorrectResults**: Verifies that a single SpdxPackage +**Spdx2JsonSerializer_SerializePackage_ValidInput_CorrectResults**: Verifies that a single SpdxPackage is serialized to the expected JSON structure with all package fields correctly mapped. -This scenario is tested by `Spdx2JsonSerializer_SerializePackage_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializePackage_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializePackages_CorrectResults**: Verifies that a collection of +**Spdx2JsonSerializer_SerializePackages_ValidInput_CorrectResults**: Verifies that a collection of SpdxPackage instances is serialized to the expected JSON array. -This scenario is tested by `Spdx2JsonSerializer_SerializePackages_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializePackages_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializePackageVerificationCode_CorrectResults**: Verifies that an +**Spdx2JsonSerializer_SerializePackageVerificationCode_ValidInput_CorrectResults**: Verifies that an SpdxPackageVerificationCode is serialized to the expected JSON structure. This scenario is tested by -`Spdx2JsonSerializer_SerializePackageVerificationCode_CorrectResults`. +`Spdx2JsonSerializer_SerializePackageVerificationCode_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeRelationship_CorrectResults**: Verifies that a single +**Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults**: Verifies that a single SpdxRelationship is serialized to the expected JSON structure with all relationship fields correctly mapped. -This scenario is tested by `Spdx2JsonSerializer_SerializeRelationship_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeRelationships_CorrectResults**: Verifies that a collection of +**Spdx2JsonSerializer_SerializeRelationships_ValidInput_CorrectResults**: Verifies that a collection of SpdxRelationship instances is serialized to the expected JSON array. -This scenario is tested by `Spdx2JsonSerializer_SerializeRelationships_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeRelationships_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeSnippet_CorrectResults**: Verifies that a single SpdxSnippet +**Spdx2JsonSerializer_SerializeSnippet_ValidInput_CorrectResults**: Verifies that a single SpdxSnippet is serialized to the expected JSON structure with byte ranges and line ranges correctly mapped. -This scenario is tested by `Spdx2JsonSerializer_SerializeSnippet_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeSnippet_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializeSnippets_CorrectResults**: Verifies that a collection of +**Spdx2JsonSerializer_SerializeSnippets_ValidInput_CorrectResults**: Verifies that a collection of SpdxSnippet instances is serialized to the expected JSON array. -This scenario is tested by `Spdx2JsonSerializer_SerializeSnippets_CorrectResults`. +This scenario is tested by `Spdx2JsonSerializer_SerializeSnippets_ValidInput_CorrectResults`. + +**Spdx2JsonSerializer_SerializeSnippet_WithAnnotation_IncludesAnnotation**: Verifies that +when an `SpdxSnippet` includes an annotations array, the serialized JSON output contains +the `annotations` array with all annotation fields correctly mapped. +This scenario is tested by `Spdx2JsonSerializer_SerializeSnippet_WithAnnotation_IncludesAnnotation`. + +**Spdx2JsonSerializer_SerializeSnippet_NoLineRange_EmitsByteRangeOnly**: Verifies that when +a snippet has both `SnippetLineStart` and `SnippetLineEnd` set to zero, the serialized +`ranges` array contains only the byte-range entry and no line-range entry is emitted. +This scenario is tested by `Spdx2JsonSerializer_SerializeSnippet_NoLineRange_EmitsByteRangeOnly`. + +**Spdx2JsonSerializer_SerializeSnippet_PartialLineRange_EmitsByteRangeOnly**: Verifies that +when a snippet has only one of `SnippetLineStart` or `SnippetLineEnd` set to a non-zero +value (AND logic), the serialized `ranges` array contains only the byte-range entry — a +partial line range is not emitted. +This scenario is tested by `Spdx2JsonSerializer_SerializeSnippet_PartialLineRange_EmitsByteRangeOnly`. diff --git a/docs/verification/spdx-model/spdx-creation-information.md b/docs/verification/spdx-model/spdx-creation-information.md index 9d96a3f..ad5a239 100644 --- a/docs/verification/spdx-model/spdx-creation-information.md +++ b/docs/verification/spdx-model/spdx-creation-information.md @@ -18,39 +18,39 @@ All automated tests pass with zero failures. ### Test Scenarios -**SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep +**SpdxCreationInformation_DeepCopy_WithAllFieldsPopulated_CreatesEqualButDistinctInstance**: Verifies that a deep copy produces a new SpdxCreationInformation instance with equal field values but a distinct object reference, confirming no shared state between original and copy. This scenario is tested by -`SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance`. +`SpdxCreationInformation_DeepCopy_WithAllFieldsPopulated_CreatesEqualButDistinctInstance`. -**SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that Enhance +**SpdxCreationInformation_Enhance_WithMissingFieldsInBase_AddsOrUpdatesInformationCorrectly**: Verifies that Enhance merges creation information by adding missing fields from the source while preserving existing field values on the target. This scenario is tested by -`SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly`. +`SpdxCreationInformation_Enhance_WithMissingFieldsInBase_AddsOrUpdatesInformationCorrectly`. **SpdxCreationInformation_Validate_ValidInformation_NoIssues**: Verifies that validation reports no issues for a fully populated valid SpdxCreationInformation instance. This scenario is tested by `SpdxCreationInformation_Validate_ValidInformation_NoIssues`. -**SpdxCreationInformation_Validate_MissingCreators**: Verifies that validation reports an +**SpdxCreationInformation_Validate_MissingCreators_ReportsIssue**: Verifies that validation reports an issue when the Creators list is empty or absent. -This scenario is tested by `SpdxCreationInformation_Validate_MissingCreators`. +This scenario is tested by `SpdxCreationInformation_Validate_MissingCreators_ReportsIssue`. -**SpdxCreationInformation_Validate_InvalidCreator**: Verifies that validation reports an issue +**SpdxCreationInformation_Validate_InvalidCreator_ReportsIssue**: Verifies that validation reports an issue when one or more entries in the Creators list do not conform to the required tool or organization format. -This scenario is tested by `SpdxCreationInformation_Validate_InvalidCreator`. +This scenario is tested by `SpdxCreationInformation_Validate_InvalidCreator_ReportsIssue`. -**SpdxCreationInformation_Validate_InvalidCreatedDate**: Verifies that validation reports an +**SpdxCreationInformation_Validate_InvalidCreatedDate_ReportsIssue**: Verifies that validation reports an issue when the Created timestamp is missing or does not conform to ISO 8601 date-time format. -This scenario is tested by `SpdxCreationInformation_Validate_InvalidCreatedDate`. +This scenario is tested by `SpdxCreationInformation_Validate_InvalidCreatedDate_ReportsIssue`. -**SpdxCreationInformation_Validate_InvalidVersion**: Verifies that validation reports an issue +**SpdxCreationInformation_Validate_InvalidVersion_ReportsIssue**: Verifies that validation reports an issue when the LicenseListVersion field is present but does not conform to the expected version format. -This scenario is tested by `SpdxCreationInformation_Validate_InvalidVersion`. +This scenario is tested by `SpdxCreationInformation_Validate_InvalidVersion_ReportsIssue`. **SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue**: Verifies that validation does not report a date issue when the Created field is empty (empty is a permitted value for diff --git a/docs/verification/spdx-model/spdx-document.md b/docs/verification/spdx-model/spdx-document.md index 2a9cfcb..eaf4c2d 100644 --- a/docs/verification/spdx-model/spdx-document.md +++ b/docs/verification/spdx-model/spdx-document.md @@ -17,59 +17,60 @@ All automated tests pass with zero failures. ### Test Scenarios -**SpdxDocument_GetRootPackages_CorrectPackages**: Verifies that GetRootPackages returns only +**SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCorrectPackages**: Verifies that GetRootPackages returns only the packages that are the targets of DESCRIBES relationships from the document element. -This scenario is tested by `SpdxDocument_GetRootPackages_CorrectPackages`. +This scenario is tested by `SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCorrectPackages`. -**SpdxDocument_SameComparer_ComparesCorrectly**: Verifies that SameComparer correctly +**SpdxDocument_Same_DocumentsWithMatchingRootPackages_AreEqual**: Verifies that SameComparer correctly identifies two SpdxDocument instances as equal when all fields match and as distinct when any field differs. -This scenario is tested by `SpdxDocument_SameComparer_ComparesCorrectly`. +This scenario is tested by `SpdxDocument_Same_DocumentsWithMatchingRootPackages_AreEqual`. -**SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep copy produces +**SpdxDocument_DeepCopy_WithPopulatedDocument_CreatesEqualButDistinctInstance**: Verifies that a deep copy produces a new SpdxDocument instance with equal field values but a distinct object reference, including all nested packages, files, snippets, and relationships. -This scenario is tested by `SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance`. +This scenario is tested by `SpdxDocument_DeepCopy_WithPopulatedDocument_CreatesEqualButDistinctInstance`. -**SpdxDocument_Validate_NoIssues**: Verifies that a fully populated valid SpdxDocument passes +**SpdxDocument_Validate_ValidDocument_ReportsNoIssues**: Verifies that a fully populated valid SpdxDocument passes all validation checks without reporting any issues. -This scenario is tested by `SpdxDocument_Validate_NoIssues`. +This scenario is tested by `SpdxDocument_Validate_ValidDocument_ReportsNoIssues`. -**SpdxDocument_Validate_InvalidId**: Verifies that validation reports an issue when the +**SpdxDocument_Validate_InvalidId_ReportsIssue**: Verifies that validation reports an issue when the document SPDX-ID field is missing or does not conform to the required format. -This scenario is tested by `SpdxDocument_Validate_InvalidId`. +This scenario is tested by `SpdxDocument_Validate_InvalidId_ReportsIssue`. -**SpdxDocument_Validate_InvalidName**: Verifies that validation reports an issue when the +**SpdxDocument_Validate_InvalidName_ReportsIssue**: Verifies that validation reports an issue when the document name field is missing or empty. -This scenario is tested by `SpdxDocument_Validate_InvalidName`. +This scenario is tested by `SpdxDocument_Validate_InvalidName_ReportsIssue`. -**SpdxDocument_Validate_InvalidVersion**: Verifies that validation reports an issue when the +**SpdxDocument_Validate_InvalidVersion_ReportsIssue**: Verifies that validation reports an issue when the SPDX version field is missing or does not match the expected SPDX-2.x format. -This scenario is tested by `SpdxDocument_Validate_InvalidVersion`. +This scenario is tested by `SpdxDocument_Validate_InvalidVersion_ReportsIssue`. -**SpdxDocument_Validate_InvalidDataLicense**: Verifies that validation reports an issue when +**SpdxDocument_Validate_InvalidDataLicense_ReportsIssue**: Verifies that validation reports an issue when the data license field is missing or is not set to the required CC0-1.0 value. -This scenario is tested by `SpdxDocument_Validate_InvalidDataLicense`. +This scenario is tested by `SpdxDocument_Validate_InvalidDataLicense_ReportsIssue`. -**SpdxDocument_Validate_InvalidNameSpace**: Verifies that validation reports an issue when the +**SpdxDocument_Validate_InvalidNameSpace_ReportsIssue**: Verifies that validation reports an issue when the document namespace field is missing or is not a valid URI. -This scenario is tested by `SpdxDocument_Validate_InvalidNameSpace`. +This scenario is tested by `SpdxDocument_Validate_InvalidNameSpace_ReportsIssue`. -**SpdxDocument_Validate_DuplicatePackageIds**: Verifies that validation reports an issue when +**SpdxDocument_Validate_DuplicatePackageIds_ReportsIssue**: Verifies that validation reports an issue when two or more packages in the document share the same SPDX-ID. -This scenario is tested by `SpdxDocument_Validate_DuplicatePackageIds`. +This scenario is tested by `SpdxDocument_Validate_DuplicatePackageIds_ReportsIssue`. -**SpdxDocument_Validate_InvalidRelationship**: Verifies that validation reports an issue when +**SpdxDocument_Validate_InvalidRelationship_ReportsIssue**: Verifies that validation reports an issue when a relationship references an element ID that does not exist within the document. -This scenario is tested by `SpdxDocument_Validate_InvalidRelationship`. +This scenario is tested by `SpdxDocument_Validate_InvalidRelationship_ReportsIssue`. -**SpdxDocument_Validate_NtiaIssues**: Verifies that validation reports issues when the +**SpdxDocument_Validate_NtiaMinimumElements_ReportsIssues**: Verifies that validation reports issues when the document does not satisfy NTIA minimum element requirements for an SBOM. -This scenario is tested by `SpdxDocument_Validate_NtiaIssues`. +This scenario is tested by `SpdxDocument_Validate_NtiaMinimumElements_ReportsIssues`. -**SpdxDocument_GetAllElements_Correct**: Verifies that GetAllElements returns the combined -collection of all packages, files, and snippets within the document. -This scenario is tested by `SpdxDocument_GetAllElements_Correct`. +**SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelationshipElements**: Verifies that `GetAllElements` returns the combined +collection of all packages, files, snippets, annotations, and the document element itself, and +that `SpdxRelationship` elements are excluded. +This scenario is tested by `SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelationshipElements`. **SpdxDocument_GetElement_Document_ReturnsDocumentElement**: Verifies that GetElement returns the document element when queried by the document SPDX-ID. @@ -87,6 +88,6 @@ This scenario is tested by `SpdxDocument_GetElement_Package_ReturnsPackageElemen snippet element when queried by a snippet SPDX-ID. This scenario is tested by `SpdxDocument_GetElement_Snippet_ReturnsSnippetElement`. -**SpdxDocument_Validate_InvalidAnnotation**: Verifies that validation reports an issue when +**SpdxDocument_Validate_InvalidAnnotation_ReportsIssue**: Verifies that validation reports an issue when an annotation within the document contains invalid fields. -This scenario is tested by `SpdxDocument_Validate_InvalidAnnotation`. +This scenario is tested by `SpdxDocument_Validate_InvalidAnnotation_ReportsIssue`. diff --git a/docs/verification/spdx-model/spdx-element.md b/docs/verification/spdx-model/spdx-element.md index 4f3aa16..5e6f33a 100644 --- a/docs/verification/spdx-model/spdx-element.md +++ b/docs/verification/spdx-model/spdx-element.md @@ -25,3 +25,11 @@ This scenario is tested by `SpdxElement_Id_ValidFormat_PassesValidation`. malformed SPDX-ID (missing the SPDXRef- prefix or containing invalid characters) is reported as a validation issue. This scenario is tested by `SpdxElement_Id_InvalidFormat_ReportsValidationIssue`. + +### Methods Without Direct Test Scenarios + +**EnhanceElement**: `EnhanceElement` is a `protected` method and therefore cannot be invoked +directly from test code. Its behavior is verified indirectly through the `Enhance` method tests +of concrete subclasses (`SpdxAnnotation`, `SpdxChecksum`, `SpdxPackage`, etc.), each of which +exercises the inherited `EnhanceElement` call as part of their own `Enhance` validation. No +separate `SpdxElement`-level scenario is required. diff --git a/docs/verification/spdx-model/spdx-external-document-reference.md b/docs/verification/spdx-model/spdx-external-document-reference.md index 13ea056..9655ae3 100644 --- a/docs/verification/spdx-model/spdx-external-document-reference.md +++ b/docs/verification/spdx-model/spdx-external-document-reference.md @@ -18,31 +18,31 @@ All automated tests pass with zero failures. ### Test Scenarios -**SpdxExternalDocumentReference_SameComparer_ComparesCorrectly**: Verifies the comparer +**SpdxExternalDocumentReference_SameComparer_SameDocument_ReturnsEqual**: Verifies the comparer considers two instances equal when their `Document` URI matches, and distinct when the URI differs (regardless of `ExternalDocumentId`). This scenario is tested by -`SpdxExternalDocumentReference_SameComparer_ComparesCorrectly`. +`SpdxExternalDocumentReference_SameComparer_SameDocument_ReturnsEqual`. -**SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a +**SpdxExternalDocumentReference_DeepCopy_ValidInstance_ReturnsEqualButDistinctInstance**: Verifies that a deep copy produces a new SpdxExternalDocumentReference with equal field values but a distinct object reference. This scenario is tested by -`SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstance`. +`SpdxExternalDocumentReference_DeepCopy_ValidInstance_ReturnsEqualButDistinctInstance`. -**SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that +**SpdxExternalDocumentReference_Enhance_WithNewAndMatchingEntries_MergesAndAppendsCorrectly**: Verifies that Enhance merges external document reference data by adding missing fields from the source while preserving existing values on the target. This scenario is tested by -`SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrectly`. +`SpdxExternalDocumentReference_Enhance_WithNewAndMatchingEntries_MergesAndAppendsCorrectly`. -**SpdxExternalDocumentReference_Validate_MissingId**: Verifies that validation reports an +**SpdxExternalDocumentReference_Validate_MissingId_ReportsIssue**: Verifies that validation reports an issue when the external document reference ID field is missing or empty. -This scenario is tested by `SpdxExternalDocumentReference_Validate_MissingId`. +This scenario is tested by `SpdxExternalDocumentReference_Validate_MissingId_ReportsIssue`. -**SpdxExternalDocumentReference_Validate_MissingDocument**: Verifies that validation reports +**SpdxExternalDocumentReference_Validate_MissingDocument_ReportsIssue**: Verifies that validation reports an issue when the referenced external document URI is missing or empty. -This scenario is tested by `SpdxExternalDocumentReference_Validate_MissingDocument`. +This scenario is tested by `SpdxExternalDocumentReference_Validate_MissingDocument_ReportsIssue`. **SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue**: Verifies that validation reports an issue when the external document reference contains an invalid checksum diff --git a/docs/verification/spdx-model/spdx-external-reference.md b/docs/verification/spdx-model/spdx-external-reference.md index c2651df..f4bed53 100644 --- a/docs/verification/spdx-model/spdx-external-reference.md +++ b/docs/verification/spdx-model/spdx-external-reference.md @@ -33,36 +33,36 @@ existing values on the target. This scenario is tested by `SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly`. -**SpdxExternalReference_Validate_InvalidCategory**: Verifies that validation reports an issue +**SpdxExternalReference_Validate_InvalidCategory_ReportsIssue**: Verifies that validation reports an issue when the reference category is set to an unrecognized value. -This scenario is tested by `SpdxExternalReference_Validate_InvalidCategory`. +This scenario is tested by `SpdxExternalReference_Validate_InvalidCategory_ReportsIssue`. -**SpdxExternalReference_Validate_InvalidType**: Verifies that validation reports an issue when +**SpdxExternalReference_Validate_InvalidType_ReportsIssue**: Verifies that validation reports an issue when the reference type does not conform to the expected format for the given category. -This scenario is tested by `SpdxExternalReference_Validate_InvalidType`. +This scenario is tested by `SpdxExternalReference_Validate_InvalidType_ReportsIssue`. -**SpdxExternalReference_Validate_InvalidLocator**: Verifies that validation reports an issue +**SpdxExternalReference_Validate_InvalidLocator_ReportsIssue**: Verifies that validation reports an issue when the reference locator field is missing or empty. -This scenario is tested by `SpdxExternalReference_Validate_InvalidLocator`. +This scenario is tested by `SpdxExternalReference_Validate_InvalidLocator_ReportsIssue`. -**SpdxReferenceCategoryExtensions_FromText_Valid**: Verifies that FromText correctly parses a +**SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly**: Verifies that FromText correctly parses a recognized reference category string to its corresponding enum value. -This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_Valid`. +This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly`. -**SpdxReferenceCategoryExtensions_FromText_Invalid**: Verifies that `FromText` throws +**SpdxReferenceCategoryExtensions_FromText_InvalidInput_ReturnsNull**: Verifies that `FromText` throws `InvalidOperationException` with a message identifying the unsupported value when given an unrecognized reference category string. -This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_Invalid`. +This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_InvalidInput_ReturnsNull`. -**SpdxReferenceCategoryExtensions_ToText_Valid**: Verifies that ToText correctly converts a +**SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrectly**: Verifies that ToText correctly converts a recognized reference category enum value to its SPDX text representation. -This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_Valid`. +This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrectly`. -**SpdxReferenceCategoryExtensions_ToText_InvalidCategory**: Verifies that ToText throws +**SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull**: Verifies that ToText throws `InvalidOperationException` with the unsupported-category message when called with an unrecognized enum value. -This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_InvalidCategory`. +This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull`. -**SpdxReferenceCategoryExtensions_ToText_MissingCategory**: Verifies that `ToText` throws +**SpdxReferenceCategoryExtensions_ToText_MissingCategory_ReturnsNull**: Verifies that `ToText` throws `InvalidOperationException` with a specific message when called with `SpdxReferenceCategory.Missing`. -This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_MissingCategory`. +This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_MissingCategory_ReturnsNull`. diff --git a/docs/verification/spdx-model/spdx-file.md b/docs/verification/spdx-model/spdx-file.md index e49597d..6af1761 100644 --- a/docs/verification/spdx-model/spdx-file.md +++ b/docs/verification/spdx-model/spdx-file.md @@ -16,50 +16,50 @@ All automated tests pass with zero failures. ### Test Scenarios -**SpdxFile_SameComparer_ComparesCorrectly**: Verifies that SameComparer correctly identifies +**SpdxFile_SameComparer_MatchingAndDistinctFiles_ComparesCorrectly**: Verifies that SameComparer correctly identifies two SpdxFile instances as equal when all fields match and as distinct when any field differs. -This scenario is tested by `SpdxFile_SameComparer_ComparesCorrectly`. +This scenario is tested by `SpdxFile_SameComparer_MatchingAndDistinctFiles_ComparesCorrectly`. -**SpdxFile_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep copy produces a +**SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy**: Verifies that a deep copy produces a new SpdxFile instance with equal field values but a distinct object reference, including all nested checksums and annotations. -This scenario is tested by `SpdxFile_DeepCopy_CreatesEqualButDistinctInstance`. +This scenario is tested by `SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy`. -**SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that Enhance merges file +**SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly**: Verifies that Enhance merges file data by adding missing fields from the source while preserving existing field values on the target. -This scenario is tested by `SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly`. +This scenario is tested by `SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly`. -**SpdxFile_Validate_ReportsInvalidFileId**: Verifies that validation reports an issue when +**SpdxFile_Validate_InvalidFileId_ReportsIssue**: Verifies that validation reports an issue when the file SPDX-ID does not conform to the required SPDXRef- prefix format. -This scenario is tested by `SpdxFile_Validate_ReportsInvalidFileId`. +This scenario is tested by `SpdxFile_Validate_InvalidFileId_ReportsIssue`. -**SpdxFile_Validate_ReportsInvalidFileName**: Verifies that validation reports an issue when +**SpdxFile_Validate_InvalidFileName_ReportsIssue**: Verifies that validation reports an issue when the FileName does not start with the required "./" prefix. -This scenario is tested by `SpdxFile_Validate_ReportsInvalidFileName`. +This scenario is tested by `SpdxFile_Validate_InvalidFileName_ReportsIssue`. -**SpdxFile_Validate_ReportsWhenSha1ChecksumMissing**: Verifies that validation reports an +**SpdxFile_Validate_MissingSha1Checksum_ReportsIssue**: Verifies that validation reports an issue when no SHA1 checksum is present in the Checksums array. -This scenario is tested by `SpdxFile_Validate_ReportsWhenSha1ChecksumMissing`. +This scenario is tested by `SpdxFile_Validate_MissingSha1Checksum_ReportsIssue`. -**SpdxFile_Validate_Success**: Verifies that a fully populated valid SpdxFile passes all +**SpdxFile_Validate_ValidFile_ReportsNoIssues**: Verifies that a fully populated valid SpdxFile passes all validation checks without reporting any issues. -This scenario is tested by `SpdxFile_Validate_Success`. +This scenario is tested by `SpdxFile_Validate_ValidFile_ReportsNoIssues`. -**SpdxFileTypeExtensions_FromText_Valid**: Verifies that FromText correctly parses a +**SpdxFileTypeExtensions_FromText_ValidInput_ParsesCorrectly**: Verifies that FromText correctly parses a recognized file type string to its corresponding enum value, and that matching is case-insensitive. -This scenario is tested by `SpdxFileTypeExtensions_FromText_Valid`. +This scenario is tested by `SpdxFileTypeExtensions_FromText_ValidInput_ParsesCorrectly`. -**SpdxFileTypeExtensions_FromText_Invalid**: Verifies that `FromText` throws +**SpdxFileTypeExtensions_FromText_InvalidInput_ThrowsException**: Verifies that `FromText` throws `InvalidOperationException` with a message identifying the unsupported value when given an unrecognized file type string. -This scenario is tested by `SpdxFileTypeExtensions_FromText_Invalid`. +This scenario is tested by `SpdxFileTypeExtensions_FromText_InvalidInput_ThrowsException`. -**SpdxFileTypeExtensions_ToText_Valid**: Verifies that ToText correctly converts a recognized +**SpdxFileTypeExtensions_ToText_ValidEnum_FormatsCorrectly**: Verifies that ToText correctly converts a recognized file type enum value to its SPDX text representation. -This scenario is tested by `SpdxFileTypeExtensions_ToText_Valid`. +This scenario is tested by `SpdxFileTypeExtensions_ToText_ValidEnum_FormatsCorrectly`. -**SpdxFileTypeExtensions_ToText_Invalid**: Verifies that `ToText` throws +**SpdxFileTypeExtensions_ToText_InvalidEnum_ThrowsException**: Verifies that `ToText` throws `InvalidOperationException` when given an unsupported file type enum value. -This scenario is tested by `SpdxFileTypeExtensions_ToText_Invalid`. +This scenario is tested by `SpdxFileTypeExtensions_ToText_InvalidEnum_ThrowsException`. diff --git a/docs/verification/spdx-model/spdx-relationship.md b/docs/verification/spdx-model/spdx-relationship.md index acc9102..4e4fac7 100644 --- a/docs/verification/spdx-model/spdx-relationship.md +++ b/docs/verification/spdx-model/spdx-relationship.md @@ -17,15 +17,33 @@ All automated tests pass with zero failures. ### Test Scenarios -**SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqual**: Verifies that SameComparer correctly -identifies two SpdxRelationship instances as equal when all fields match and as distinct when -any field differs. -This scenario is tested by `SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqual`. +**SpdxRelationship_SameComparer_MatchingRelationships_ReturnsTrue**: Verifies that SameComparer correctly +identifies two SpdxRelationship instances as equal when the key fields (Id, RelationshipType, RelatedSpdxElement) +match, even when Comment differs. +This scenario is tested by `SpdxRelationship_SameComparer_MatchingRelationships_ReturnsTrue`. -**SpdxRelationship_SameElementsComparer_SameElementsDifferentType_ReturnsEqual**: Verifies that +**SpdxRelationship_SameComparer_DifferentRelationships_ReturnsFalse**: Verifies that SameComparer correctly +identifies two SpdxRelationship instances as distinct when any key field differs. +This scenario is tested by `SpdxRelationship_SameComparer_DifferentRelationships_ReturnsFalse`. + +**SpdxRelationship_SameComparer_MatchingRelationships_ReturnsSameHashCode**: Verifies that SameComparer +produces identical hash codes for two relationships that are considered equal, satisfying the hash/equality contract. +This scenario is tested by `SpdxRelationship_SameComparer_MatchingRelationships_ReturnsSameHashCode`. + +**SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsTrue**: Verifies that SameElementsComparer correctly identifies two relationships as equal based solely on the source and target element IDs, ignoring relationship type. -This scenario is tested by `SpdxRelationship_SameElementsComparer_SameElementsDifferentType_ReturnsEqual`. +This scenario is tested by `SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsTrue`. + +**SpdxRelationship_SameElementsComparer_DifferentElements_ReturnsFalse**: Verifies that +SameElementsComparer correctly identifies two relationships as distinct when their source or +target element IDs differ. +This scenario is tested by `SpdxRelationship_SameElementsComparer_DifferentElements_ReturnsFalse`. + +**SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsSameHashCode**: Verifies that +SameElementsComparer produces identical hash codes for two relationships with the same element +IDs, satisfying the hash/equality contract. +This scenario is tested by `SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsSameHashCode`. **SpdxRelationship_DeepCopy_FullyPopulatedRelationship_CreatesEqualButDistinctCopy**: Verifies that a deep copy produces a new SpdxRelationship instance with equal field values but a distinct object @@ -62,6 +80,11 @@ This scenario is tested by `SpdxRelationshipTypeExtensions_FromText_UnknownText_ recognized relationship type enum value to its SPDX text representation. This scenario is tested by `SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText`. +**SpdxRelationshipTypeExtensions_ToText_MissingSentinel_ThrowsInvalidOperationException**: Verifies that ToText +throws an InvalidOperationException with message "Attempt to serialize missing SPDX Relationship Type" when +called on the Missing sentinel value. +This scenario is tested by `SpdxRelationshipTypeExtensions_ToText_MissingSentinel_ThrowsInvalidOperationException`. + **SpdxRelationshipTypeExtensions_ToText_UnknownEnum_ThrowsInvalidOperationException**: Verifies that ToText throws an InvalidOperationException with a descriptive message when given an unknown (out-of-range) relationship type enum value. diff --git a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs index 5054e66..7e94151 100644 --- a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs +++ b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs @@ -626,13 +626,13 @@ private static void EmitOptionalString(JsonNode json, string name, string? value /// /// Emit an optional string array property into a JSON object. /// - /// JSON object to write into - /// Property name - /// Array of values; omitted when empty /// /// Omits the property entirely when is empty, consistent /// with the serializer convention of not writing null or empty optional fields. /// + /// JSON object to write into + /// Property name + /// Array of values; omitted when empty private static void EmitOptionalStrings(JsonNode json, string name, string[] values) { // Skip if empty diff --git a/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs b/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs index 0da5485..626ad4f 100644 --- a/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs +++ b/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs @@ -214,9 +214,23 @@ public void Validate(string parent, List issues) /// /// Equality Comparer to test for the same annotation /// + /// + /// Implements identity semantics used by + /// to detect duplicate annotations before merging. Two annotations are considered the + /// same when their , , + /// , and fields are all equal, + /// regardless of their . + /// private sealed class SpdxAnnotationSame : IEqualityComparer { /// + /// + /// Compares the four identity fields: , + /// , , and + /// . The field + /// is intentionally excluded so that an annotation with or without an assigned + /// SPDX-ID is still recognized as the same logical entry. + /// public bool Equals(SpdxAnnotation? a1, SpdxAnnotation? a2) { if (ReferenceEquals(a1, a2)) @@ -236,6 +250,12 @@ public bool Equals(SpdxAnnotation? a1, SpdxAnnotation? a2) } /// + /// + /// Computes the hash from the same four identity fields as : + /// , , + /// , and . + /// Uses HashCode.Combine for a well-distributed composite hash. + /// public int GetHashCode(SpdxAnnotation obj) { return HashCode.Combine( diff --git a/src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs b/src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs index aef3da3..7078fbb 100644 --- a/src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs +++ b/src/DemaConsulting.SpdxModel/SpdxAnnotationType.cs @@ -34,16 +34,28 @@ public enum SpdxAnnotationType /// /// Missing annotation type /// + /// + /// Sentinel value (-1) representing an absent or uninitialized annotation type. + /// will throw + /// if called with this value, and it must never + /// be written to a serialized SPDX document. + /// Missing = -1, /// /// Annotation created during review /// + /// + /// Canonical SPDX 2.x text form: "REVIEW". + /// Review, /// /// Annotation created for other reasons /// + /// + /// Canonical SPDX 2.x text form: "OTHER". + /// Other } diff --git a/src/DemaConsulting.SpdxModel/SpdxDocument.cs b/src/DemaConsulting.SpdxModel/SpdxDocument.cs index 732b0fb..37eff8d 100644 --- a/src/DemaConsulting.SpdxModel/SpdxDocument.cs +++ b/src/DemaConsulting.SpdxModel/SpdxDocument.cs @@ -25,6 +25,14 @@ namespace DemaConsulting.SpdxModel; /// /// SPDX Document class /// +/// +/// is the root container of the SPDX in-memory object model. +/// It aggregates all packages, files, snippets, relationships, and annotations that +/// together form a complete SPDX Software Bill of Materials. The class is +/// to prevent inheritance. All mutable collection and scalar +/// properties are not thread-safe; external synchronization is required when instances +/// are shared across threads. +/// public sealed class SpdxDocument : SpdxElement { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxExternalReference.cs b/src/DemaConsulting.SpdxModel/SpdxExternalReference.cs index 331f59a..cf38fe4 100644 --- a/src/DemaConsulting.SpdxModel/SpdxExternalReference.cs +++ b/src/DemaConsulting.SpdxModel/SpdxExternalReference.cs @@ -78,11 +78,11 @@ public sealed class SpdxExternalReference /// /// Make a deep-copy of this object /// - /// Deep copy of this object /// /// Used by the static Enhance merge to add new entries without aliasing the source array; also used by callers /// that need an independent snapshot. /// + /// Deep copy of this object public SpdxExternalReference DeepCopy() { return new SpdxExternalReference diff --git a/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs b/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs index cfc84af..6e74f00 100644 --- a/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs +++ b/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs @@ -83,11 +83,11 @@ public sealed class SpdxExtractedLicensingInfo /// /// Make a deep-copy of this object /// - /// Deep copy of this object /// /// Used by the static Enhance merge to add new entries without aliasing the source array; also used by callers /// that need an independent snapshot. /// + /// Deep copy of this object public SpdxExtractedLicensingInfo DeepCopy() { return new SpdxExtractedLicensingInfo @@ -221,6 +221,9 @@ public bool Equals(SpdxExtractedLicensingInfo? l1, SpdxExtractedLicensingInfo? l } /// + /// + /// The hash code is derived solely from . + /// public int GetHashCode(SpdxExtractedLicensingInfo obj) { return obj.ExtractedText.GetHashCode(); diff --git a/src/DemaConsulting.SpdxModel/SpdxFile.cs b/src/DemaConsulting.SpdxModel/SpdxFile.cs index e5ad9a0..5a2d641 100644 --- a/src/DemaConsulting.SpdxModel/SpdxFile.cs +++ b/src/DemaConsulting.SpdxModel/SpdxFile.cs @@ -109,7 +109,6 @@ public sealed class SpdxFile : SpdxLicenseElement /// /// Make a deep-copy of this object /// - /// Deep copy of this object /// /// All arrays (, , /// , , @@ -117,6 +116,7 @@ public sealed class SpdxFile : SpdxLicenseElement /// ) are independently deep-copied; the /// returned instance shares no mutable state with the original. /// + /// Deep copy of this object public SpdxFile DeepCopy() { return new SpdxFile @@ -256,6 +256,10 @@ public void Validate(List issues) /// /// Equality Comparer to test for the same file /// + /// + /// Equality is based on with a SHA1 checksum tiebreaker: + /// two entries with the same file name but differing SHA1 digests are treated as distinct. + /// private sealed class SpdxFileSame : IEqualityComparer { /// diff --git a/src/DemaConsulting.SpdxModel/SpdxHelpers.cs b/src/DemaConsulting.SpdxModel/SpdxHelpers.cs index 7e4d52e..61298f2 100644 --- a/src/DemaConsulting.SpdxModel/SpdxHelpers.cs +++ b/src/DemaConsulting.SpdxModel/SpdxHelpers.cs @@ -80,13 +80,13 @@ internal static bool IsValidSpdxDateTime(string? value) /// /// This method picks the best string. /// - /// String values to pick from - /// Best string /// /// Fitness ranking: null=0, empty string=1, NOASSERTION=2, any other concrete value=3. /// Returns the candidate with the highest fitness. When all candidates are null (or the /// array is empty), returns null. /// + /// String values to pick from + /// Best string internal static string? EnhanceString(params string?[] values) { // Return the value with the highest fitness diff --git a/src/DemaConsulting.SpdxModel/SpdxRelationship.cs b/src/DemaConsulting.SpdxModel/SpdxRelationship.cs index 0e24d93..0062161 100644 --- a/src/DemaConsulting.SpdxModel/SpdxRelationship.cs +++ b/src/DemaConsulting.SpdxModel/SpdxRelationship.cs @@ -206,7 +206,14 @@ public void Validate(List issues, SpdxDocument? doc) /// private sealed class SpdxRelationshipSame : IEqualityComparer { - /// + /// + /// Determines whether two instances are considered the same. + /// + /// + /// Two relationships are considered equal when they share the same , + /// , and . + /// Fields such as are ignored during comparison. + /// public bool Equals(SpdxRelationship? r1, SpdxRelationship? r2) { if (ReferenceEquals(r1, r2)) @@ -224,7 +231,14 @@ public bool Equals(SpdxRelationship? r1, SpdxRelationship? r2) r1.RelatedSpdxElement == r2.RelatedSpdxElement; } - /// + /// + /// Returns a hash code for the specified instance. + /// + /// + /// The hash is derived from the , , + /// and fields, consistent with the equality comparison + /// performed by . + /// public int GetHashCode(SpdxRelationship obj) { return HashCode.Combine( @@ -245,7 +259,14 @@ public int GetHashCode(SpdxRelationship obj) /// private sealed class SpdxRelationshipSameElements : IEqualityComparer { - /// + /// + /// Determines whether two instances share the same elements. + /// + /// + /// Two relationships are considered equal when they share the same and + /// , regardless of + /// . Used when deduplicating by element endpoints only. + /// public bool Equals(SpdxRelationship? r1, SpdxRelationship? r2) { if (ReferenceEquals(r1, r2)) @@ -262,7 +283,14 @@ public bool Equals(SpdxRelationship? r1, SpdxRelationship? r2) r1.RelatedSpdxElement == r2.RelatedSpdxElement; } - /// + /// + /// Returns a hash code for the specified instance. + /// + /// + /// The hash is derived from the and + /// fields, consistent with the equality comparison + /// performed by . + /// public int GetHashCode(SpdxRelationship obj) { return HashCode.Combine(obj.Id, obj.RelatedSpdxElement); diff --git a/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs b/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs index 6827eaf..0e9d185 100644 --- a/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs +++ b/src/DemaConsulting.SpdxModel/Transform/SpdxRelationships.cs @@ -35,6 +35,17 @@ public static class SpdxRelationships /// /// Add new relationships to the SPDX document. /// + /// + /// All incoming relationships are validated before any mutation is performed. + /// This ensures that a validation failure leaves the document in its original state + /// (atomic with respect to the batch). Replacement uses + /// (type-agnostic, matches by source and + /// target ID only) so that a replace-and-add can change the relationship type between + /// the same pair of elements. Individual deduplication within the add path uses + /// (type-inclusive) so that relationships of + /// different types between the same elements co-exist correctly. + /// Side-effect: mutates .. + /// /// SPDX document to modify /// New relationships to add /// @@ -47,17 +58,6 @@ public static class SpdxRelationships /// neither NOASSERTION nor prefixed with DocumentRef-. /// When this exception is thrown, the document is left unmodified. /// - /// - /// All incoming relationships are validated before any mutation is performed. - /// This ensures that a validation failure leaves the document in its original state - /// (atomic with respect to the batch). Replacement uses - /// (type-agnostic, matches by source and - /// target ID only) so that a replace-and-add can change the relationship type between - /// the same pair of elements. Individual deduplication within the add path uses - /// (type-inclusive) so that relationships of - /// different types between the same elements co-exist correctly. - /// Side-effect: mutates .. - /// public static void Add(SpdxDocument document, IEnumerable relationships, bool replace = false) { // Materialize the enumerable so it can be iterated multiple times @@ -91,6 +91,13 @@ public static void Add(SpdxDocument document, IEnumerable rela /// /// Add a relationship to the SPDX document. /// + /// + /// If a relationship with the same source, target, and type already exists (as determined + /// by ), the existing entry is enhanced with any + /// additional field values from . Otherwise a deep copy is + /// appended to . + /// Side-effect: mutates .. + /// /// SPDX document to modify /// SPDX relationship to add /// @@ -99,13 +106,6 @@ public static void Add(SpdxDocument document, IEnumerable rela /// () is not found in the document and /// is neither NOASSERTION nor prefixed with DocumentRef-. /// - /// - /// If a relationship with the same source, target, and type already exists (as determined - /// by ), the existing entry is enhanced with any - /// additional field values from . Otherwise a deep copy is - /// appended to . - /// Side-effect: mutates .. - /// public static void Add(SpdxDocument document, SpdxRelationship relationship) { // Validate before any mutation diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs index 02db53a..1f92630 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs @@ -25,14 +25,23 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX 2.2 JSON documents to classes. /// +/// +/// Exercises end-to-end deserialization of a real SPDX 2.2 JSON example document +/// (embedded resource) using MSTest as the approved test framework for this repository. +/// [TestClass] public class Spdx2JsonDeserialize22 { /// /// Test parsing SPDX 2.2 JSON document. /// + /// + /// Uses the canonical SPDX 2.2 JSON example bundled as an embedded resource. Verifies + /// document-level fields, external document references, extracted licensing info, + /// annotations, files, snippets, and relationships are all correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument() + public void Spdx2JsonDeserializer_Deserialize_ValidSpdx22Json_ReturnsExpectedDocument() { // Arrange: Load the SPDX 2.2 JSON example from embedded resources var json22Example = SpdxTestHelpers.GetEmbeddedResource( diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs index 538e051..75a9e0c 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs @@ -25,14 +25,23 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX 2.3 JSON documents to classes. /// +/// +/// Exercises end-to-end deserialization of a real SPDX 2.3 JSON example document +/// (embedded resource) using MSTest as the approved test framework for this repository. +/// [TestClass] public class Spdx2JsonDeserialize23 { /// /// Test parsing SPDX 2.3 JSON document. /// + /// + /// Uses the canonical SPDX 2.3 JSON example bundled as an embedded resource. Verifies + /// document-level fields, external document references, extracted licensing info, + /// annotations, files, snippets, and relationships are all correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument() + public void Spdx2JsonDeserializer_Deserialize_ValidSpdx23Json_ReturnsExpectedDocument() { // Arrange: Get the SPDX 2.3 JSON example from embedded resources var json22Example = SpdxTestHelpers.GetEmbeddedResource( diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs index 2304e2e..61c284c 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs @@ -26,14 +26,24 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX annotations to classes. /// +/// +/// Exercises deserialization of SPDX annotation elements using MSTest as the approved +/// test framework for this repository. Each test constructs inline JSON and verifies +/// the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeAnnotation { /// /// Tests deserializing an annotation. /// + /// + /// Verifies that all four annotation fields (annotationDate, annotationType, annotator, + /// comment) are mapped to the correct properties when a + /// single JSON object is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeAnnotation_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing an annotation var json = new JsonObject @@ -57,8 +67,12 @@ public void Spdx2JsonDeserializer_DeserializeAnnotation_CorrectResults() /// /// Tests deserializing multiple annotations. /// + /// + /// Verifies that a JSON array of two annotation objects is deserialized to an array + /// of two instances with fields correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeAnnotations_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeAnnotations_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple annotations var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeChecksum.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeChecksum.cs index 651a675..f4fec83 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeChecksum.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeChecksum.cs @@ -26,14 +26,24 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX checksums to classes. /// +/// +/// Exercises deserialization of SPDX checksum elements using MSTest as the approved +/// test framework for this repository. Each test constructs inline JSON and verifies +/// the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeChecksum { /// /// Tests deserializing a checksum. /// + /// + /// Verifies that the algorithm and checksumValue JSON fields are mapped to the + /// corresponding properties when a single checksum object + /// is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeChecksum_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a checksum var json = new JsonObject @@ -53,8 +63,13 @@ public void Spdx2JsonDeserializer_DeserializeChecksum_CorrectResults() /// /// Tests deserializing multiple checksums. /// + /// + /// Verifies that a JSON array of two checksum objects (SHA1 and MD5) is deserialized + /// to an array of two instances with correct algorithm and + /// value mappings. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeChecksums_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeChecksums_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple checksums var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeCreationInformation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeCreationInformation.cs index efc774b..9e0c0c7 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeCreationInformation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeCreationInformation.cs @@ -26,14 +26,24 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX creation information to classes. /// +/// +/// Exercises deserialization of SPDX creation information using MSTest as the approved +/// test framework for this repository. Constructs inline JSON and verifies the resulting +/// fields. +/// [TestClass] public class Spdx2JsonDeserializeCreationInformation { /// /// Tests deserializing creation information. /// + /// + /// Verifies that all creation information fields (comment, created, creators array, + /// licenseListVersion) are correctly mapped to the + /// properties. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeCreationInformation_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeCreationInformation_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing creation information var json = new JsonObject diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeDocument.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeDocument.cs index c8c6634..cd33191 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeDocument.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeDocument.cs @@ -26,14 +26,24 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX documents to classes. /// +/// +/// Exercises deserialization of SPDX document-level elements using MSTest as the +/// approved test framework for this repository. Constructs inline JSON and verifies +/// the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeDocument { /// /// Tests deserializing a document. /// + /// + /// Verifies that all top-level document fields (SPDXID, spdxVersion, name, dataLicense, + /// comment, documentNamespace, documentDescribes) and empty collection fields are + /// correctly mapped when a full document JSON object is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeDocument_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeDocument_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a document var json = new JsonObject diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalDocumentReference.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalDocumentReference.cs index d3bdfd9..5199659 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalDocumentReference.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalDocumentReference.cs @@ -26,14 +26,24 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX external document references to classes. /// +/// +/// Exercises deserialization of SPDX external document reference elements using MSTest +/// as the approved test framework for this repository. Each test constructs inline JSON +/// and verifies the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeExternalDocumentReference { /// /// Tests deserializing an external document reference. /// + /// + /// Verifies that externalDocumentId, checksum (algorithm and value), and spdxDocument + /// JSON fields are correctly mapped to the + /// properties when a single object is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeExternalDocumentReference_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing an external document reference var json = new JsonObject @@ -61,8 +71,12 @@ public void Spdx2JsonDeserializer_DeserializeExternalDocumentReference_CorrectRe /// /// Tests deserializing multiple external document references. /// + /// + /// Verifies that a JSON array containing one external document reference object is + /// deserialized to a single-element array with all fields correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple external document references var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalReference.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalReference.cs index 9904eb2..fe72e64 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalReference.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalReference.cs @@ -26,14 +26,24 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX external references to classes. /// +/// +/// Exercises deserialization of SPDX external reference elements using MSTest as the +/// approved test framework for this repository. Each test constructs inline JSON and +/// verifies the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeExternalReference { /// /// Tests deserializing an external reference. /// + /// + /// Verifies that comment, referenceLocator, referenceType, and referenceCategory JSON + /// fields are correctly mapped to the properties + /// when a single SECURITY-category reference is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeExternalReference_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing an external reference var json = new JsonObject @@ -57,8 +67,12 @@ public void Spdx2JsonDeserializer_DeserializeExternalReference_CorrectResults() /// /// Tests deserializing multiple external references. /// + /// + /// Verifies that a JSON array of two external reference objects (one SECURITY, one OTHER + /// category) is deserialized to a two-element array with all fields correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeExternalReferences_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeExternalReferences_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple external references var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExtractedLicensingInfo.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExtractedLicensingInfo.cs index 97a38ed..f7a6fed 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExtractedLicensingInfo.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExtractedLicensingInfo.cs @@ -26,14 +26,25 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX extracted licensing information to classes. /// +/// +/// Exercises deserialization of SPDX extracted licensing information elements using +/// MSTest as the approved test framework for this repository. Each test constructs +/// inline JSON and verifies the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeExtractedLicensingInfo { /// /// Tests deserializing an extracted licensing information. /// + /// + /// Verifies that licenseId, extractedText, name, seeAlsos (cross-references), and + /// comment JSON fields are correctly mapped to the + /// properties when a single object is + /// deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing extracted licensing information var json = new JsonObject @@ -60,8 +71,12 @@ public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_CorrectResul /// /// Tests deserializing multiple extracted licensing information. /// + /// + /// Verifies that a JSON array containing one extracted licensing info object is + /// deserialized to a single-element array with all fields correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple extracted licensing information var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeFile.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeFile.cs index 6998912..085c99d 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeFile.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeFile.cs @@ -26,14 +26,25 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX files to classes. /// +/// +/// Exercises deserialization of SPDX file elements using MSTest as the approved test +/// framework for this repository. Each test constructs inline JSON and verifies the +/// resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeFile { /// /// Tests deserializing a file. /// + /// + /// Verifies that all standard file fields (SPDXID, fileName, fileTypes, checksums, + /// licenseConcluded, licenseInfoInFiles, licenseComments, comment, noticeText) are + /// correctly mapped to properties when a single file JSON + /// object is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeFile_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeFile_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a file var json = new JsonObject @@ -78,8 +89,12 @@ public void Spdx2JsonDeserializer_DeserializeFile_CorrectResults() /// /// Tests deserializing multiple files. /// + /// + /// Verifies that a JSON array containing one file object is deserialized to a + /// single-element array with all fields correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeFiles_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeFiles_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple files var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackage.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackage.cs index 410f0a9..9630bd9 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackage.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackage.cs @@ -26,14 +26,25 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX packages to classes. /// +/// +/// Exercises deserialization of SPDX package elements using MSTest as the approved +/// test framework for this repository. Each test constructs inline JSON and verifies +/// the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializePackage { /// /// Tests deserializing a package. /// + /// + /// Verifies that all standard package fields (SPDXID, annotations, attributionTexts, + /// builtDate, checksums, copyrightText, description, downloadLocation, externalRefs) + /// are correctly mapped to properties when a single package + /// JSON object is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializePackage_CorrectResults() + public void Spdx2JsonDeserializer_DeserializePackage_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a package var json = new JsonObject @@ -124,8 +135,12 @@ public void Spdx2JsonDeserializer_DeserializePackage_CorrectResults() /// /// Tests deserializing multiple packages. /// + /// + /// Verifies that a JSON array containing one package object is deserialized to a + /// single-element array with all fields correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializePackages_CorrectResults() + public void Spdx2JsonDeserializer_DeserializePackages_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple packages var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackageVerificationCode.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackageVerificationCode.cs index 03e64bf..876cadc 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackageVerificationCode.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackageVerificationCode.cs @@ -26,14 +26,24 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX package verification codes to classes. /// +/// +/// Exercises deserialization of SPDX package verification code elements using MSTest +/// as the approved test framework for this repository. Constructs inline JSON and +/// verifies the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializePackageVerificationCode { /// /// Tests deserializing a package verification code. /// + /// + /// Verifies that packageVerificationCodeValue and packageVerificationCodeExcludedFiles + /// JSON fields are correctly mapped to the + /// properties and that the result is non-null. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResults() + public void Spdx2JsonDeserializer_DeserializePackageVerificationCode_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a package verification code var json = new JsonObject @@ -48,9 +58,9 @@ public void Spdx2JsonDeserializer_DeserializePackageVerificationCode_CorrectResu // Act: Deserialize the JSON object to an SpdxPackageVerificationCode object var packageVerificationCode = Spdx2JsonDeserializer.DeserializeVerificationCode(json); - Assert.IsNotNull(packageVerificationCode); // Assert: Verify the deserialized object has the expected properties + Assert.IsNotNull(packageVerificationCode); Assert.AreEqual("d3b07384d113edec49eaa6238ad5ff00", packageVerificationCode.Value); Assert.HasCount(2, packageVerificationCode.ExcludedFiles); Assert.AreEqual("file1.txt", packageVerificationCode.ExcludedFiles[0]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeRelationship.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeRelationship.cs index 5c50f28..bd5aace 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeRelationship.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeRelationship.cs @@ -26,14 +26,24 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX relationships to classes. /// +/// +/// Exercises deserialization of SPDX relationship elements using MSTest as the approved +/// test framework for this repository. Each test constructs inline JSON and verifies +/// the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeRelationship { /// /// Tests deserializing a relationship. /// + /// + /// Verifies that spdxElementId, relatedSpdxElement, relationshipType, and comment JSON + /// fields are correctly mapped to the properties when a + /// single relationship object is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeRelationship_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a relationship var json = new JsonObject @@ -57,8 +67,12 @@ public void Spdx2JsonDeserializer_DeserializeRelationship_CorrectResults() /// /// Tests deserializing multiple relationships. /// + /// + /// Verifies that a JSON array of two relationship objects (DESCRIBES and DESCRIBED_BY) + /// is deserialized to a two-element array with all fields correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeRelationships_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeRelationships_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple relationships var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs index 76e941d..f55d2ec 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs @@ -26,14 +26,25 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for deserializing SPDX snippets to classes. /// +/// +/// Exercises deserialization of SPDX snippet elements using MSTest as the approved +/// test framework for this repository. Each test constructs inline JSON and verifies +/// the resulting fields. +/// [TestClass] public class Spdx2JsonDeserializeSnippet { /// /// Tests deserializing a snippet. /// + /// + /// Verifies that all snippet fields (SPDXID, comment, copyrightText, licenseComments, + /// licenseConcluded, licenseInfoInSnippets, name, byte ranges, line ranges, and + /// snippetFromFile) are correctly mapped to properties when + /// a single snippet JSON object with both byte and line ranges is deserialized. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeSnippet_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a snippet var json = new JsonObject @@ -110,6 +121,11 @@ public void Spdx2JsonDeserializer_DeserializeSnippet_CorrectResults() /// This is a regression test for a thrown when line-number /// range fields were absent and the old code used Convert.ToInt32(""). /// + /// + /// Boundary condition: when only a byte-range entry exists in the ranges array and no + /// lineNumber pointers are present, and + /// must default to zero rather than throwing. + /// [TestMethod] public void Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero() { @@ -152,8 +168,12 @@ public void Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsT /// /// Tests deserializing multiple snippets. /// + /// + /// Verifies that a JSON array containing one snippet object with both byte and line + /// ranges is deserialized to a single-element array with all fields correctly populated. + /// [TestMethod] - public void Spdx2JsonDeserializer_DeserializeSnippets_CorrectResults() + public void Spdx2JsonDeserializer_DeserializeSnippets_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple snippets var json = new JsonArray diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs index 0901e89..4e47926 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs @@ -41,7 +41,7 @@ public class Spdx2JsonDeserializerTests /// rather than a silent failure. /// [TestMethod] - public void Spdx2JsonDeserializer_Deserialize_MalformedJsonThrowsJsonException() + public void Spdx2JsonDeserializer_Deserialize_MalformedJson_ThrowsJsonException() { // Arrange const string malformedJson = "{ not valid json"; diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs index cc8c592..7eb6205 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeAnnotation /// Tests serializing an annotation. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeAnnotation_CorrectResults() + public void Spdx2JsonSerializer_SerializeAnnotation_ValidInput_CorrectResults() { // Arrange: Create a sample annotation var annotation = new SpdxAnnotation @@ -59,7 +59,7 @@ public void Spdx2JsonSerializer_SerializeAnnotation_CorrectResults() /// Tests serializing multiple annotations. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeAnnotations_CorrectResults() + public void Spdx2JsonSerializer_SerializeAnnotations_ValidInput_CorrectResults() { // Arrange: Create a sample list of annotations var annotations = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeChecksum.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeChecksum.cs index 15bd478..e851335 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeChecksum.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeChecksum.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeChecksum /// Tests serializing a checksum. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeChecksum_CorrectResults() + public void Spdx2JsonSerializer_SerializeChecksum_ValidInput_CorrectResults() { // Arrange: Create a sample checksum var checksum = new SpdxChecksum @@ -54,7 +54,7 @@ public void Spdx2JsonSerializer_SerializeChecksum_CorrectResults() /// Tests serializing multiple checksums. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeChecksums_CorrectResults() + public void Spdx2JsonSerializer_SerializeChecksums_ValidInput_CorrectResults() { // Arrange: Create sample checksums var checksums = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeCreationInformation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeCreationInformation.cs index 812c719..b19e100 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeCreationInformation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeCreationInformation.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeCreationInformation /// Tests serializing creation information. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeCreationInformation_CorrectResults() + public void Spdx2JsonSerializer_SerializeCreationInformation_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxCreationInformation object var creationInformation = new SpdxCreationInformation diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs index 48263cb..6514c4e 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs @@ -33,7 +33,7 @@ public class Spdx2JsonSerializeDocument /// Tests serializing a document to JSON. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeDocument_CorrectResults() + public void Spdx2JsonSerializer_SerializeDocument_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxDocument object var document = new SpdxDocument @@ -100,7 +100,7 @@ public void Spdx2JsonSerializer_SerializeDocument_CorrectResults() /// Tests serializing a document to text /// [TestMethod] - public void Spdx2JsonSerializer_Serialize_CorrectResults() + public void Spdx2JsonSerializer_Serialize_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxDocument object var document = new SpdxDocument diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalDocumentReference.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalDocumentReference.cs index 7f37959..75c29f8 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalDocumentReference.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalDocumentReference.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeExternalDocumentReference /// Tests serializing an external document reference. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResults() + public void Spdx2JsonSerializer_SerializeExternalDocumentReference_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxExternalDocumentReference object var reference = new SpdxExternalDocumentReference @@ -61,7 +61,7 @@ public void Spdx2JsonSerializer_SerializeExternalDocumentReference_CorrectResult /// Tests serializing multiple external document references. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeExternalDocumentReferences_CorrectResults() + public void Spdx2JsonSerializer_SerializeExternalDocumentReferences_ValidInput_CorrectResults() { // Arrange: Create a sample array of SpdxExternalDocumentReference objects var references = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalReference.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalReference.cs index ec50aaf..5bcc58c 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalReference.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalReference.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeExternalReference /// Tests serializing an external reference. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeExternalReference_CorrectResults() + public void Spdx2JsonSerializer_SerializeExternalReference_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxExternalReference object var reference = new SpdxExternalReference @@ -59,7 +59,7 @@ public void Spdx2JsonSerializer_SerializeExternalReference_CorrectResults() /// Tests serializing multiple external references. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeExternalReferences_CorrectResults() + public void Spdx2JsonSerializer_SerializeExternalReferences_ValidInput_CorrectResults() { // Arrange: Create sample SpdxExternalReference objects var references = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExtractedLicensingInfo.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExtractedLicensingInfo.cs index a1be70f..b7cece5 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExtractedLicensingInfo.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExtractedLicensingInfo.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeExtractedLicensingInfo /// Tests serializing an extracted licensing info. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults() + public void Spdx2JsonSerializer_SerializeExtractedLicensingInfo_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxExtractedLicensingInfo object var info = new SpdxExtractedLicensingInfo @@ -59,7 +59,7 @@ public void Spdx2JsonSerializer_SerializeExtractedLicensingInfo_CorrectResults() /// Tests serializing multiple extracted licensing infos. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeExtractedLicensingInfos_CorrectResults() + public void Spdx2JsonSerializer_SerializeExtractedLicensingInfos_ValidInput_CorrectResults() { // Arrange: Create a sample array of SpdxExtractedLicensingInfo objects var info = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs index 82e4159..5663fe4 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeFile /// Tests serializing a file. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeFile_CorrectResults() + public void Spdx2JsonSerializer_SerializeFile_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxFile object var file = new SpdxFile @@ -112,7 +112,7 @@ public void Spdx2JsonSerializer_SerializeFile_CorrectResults() /// Tests serializing multiple files. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeFiles_CorrectResults() + public void Spdx2JsonSerializer_SerializeFiles_ValidInput_CorrectResults() { // Arrange: Create a sample array containing a single SpdxFile with all fields populated var file = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackage.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackage.cs index 8d325dc..174c977 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackage.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackage.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializePackage /// Tests serializing a package. /// [TestMethod] - public void Spdx2JsonSerializer_SerializePackage_CorrectResults() + public void Spdx2JsonSerializer_SerializePackage_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxPackage object var package = new SpdxPackage @@ -137,7 +137,7 @@ public void Spdx2JsonSerializer_SerializePackage_CorrectResults() /// Tests serializing multiple packages. /// [TestMethod] - public void Spdx2JsonSerializer_SerializePackages_CorrectResults() + public void Spdx2JsonSerializer_SerializePackages_ValidInput_CorrectResults() { // Arrange: Create a sample array of SpdxPackage objects var packages = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs index a714a4e..4daead2 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializePackageVerificationCode /// Tests serializing a package verification code. /// [TestMethod] - public void Spdx2JsonSerializer_SerializePackageVerificationCode_CorrectResults() + public void Spdx2JsonSerializer_SerializePackageVerificationCode_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxPackageVerificationCode object var code = new SpdxPackageVerificationCode diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeRelationship.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeRelationship.cs index 353c5fd..3fe7eb4 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeRelationship.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeRelationship.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeRelationship /// Tests serializing a relationship. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeRelationship_CorrectResults() + public void Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxRelationship object var relationship = new SpdxRelationship @@ -58,7 +58,7 @@ public void Spdx2JsonSerializer_SerializeRelationship_CorrectResults() /// Tests serializing multiple relationships. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeRelationships_CorrectResults() + public void Spdx2JsonSerializer_SerializeRelationships_ValidInput_CorrectResults() { // Arrange: Create an array of sample SpdxRelationship objects var relationships = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs index e7c7525..ef7b1b4 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializeSnippet /// Tests serializing a snippet. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeSnippet_CorrectResults() + public void Spdx2JsonSerializer_SerializeSnippet_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxSnippet object var snippet = new SpdxSnippet @@ -80,7 +80,7 @@ public void Spdx2JsonSerializer_SerializeSnippet_CorrectResults() /// Tests serializing multiple snippets. /// [TestMethod] - public void Spdx2JsonSerializer_SerializeSnippets_CorrectResults() + public void Spdx2JsonSerializer_SerializeSnippets_ValidInput_CorrectResults() { // Arrange: Create a sample array of SpdxSnippet objects var snippets = new[] diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs index 158f9a7..6811b1b 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs @@ -23,12 +23,23 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Uses MSTest as the approved test framework for this repository (formal exception to +/// xUnit documented in csharp-testing.md). Each test method is fully isolated: +/// no shared state is maintained between tests and all test inputs are constructed +/// inline within the method body. +/// [TestClass] public class SpdxAnnotationTests { /// /// Tests the comparer compares annotations correctly. /// + /// + /// Verifies reflexive equality (each annotation equals itself), cross-equality (two + /// annotations with identical fields but different IDs are equal), and that hash codes + /// match for equal annotations. + /// [TestMethod] public void SpdxAnnotation_SameComparer_ComparesCorrectly() { @@ -75,6 +86,11 @@ public void SpdxAnnotation_SameComparer_ComparesCorrectly() /// /// Tests the method successfully creates a deep copy. /// + /// + /// Verifies that the deep copy is logically equal (all fields match) but is a distinct + /// object (not reference-equal), confirming no shared mutable references exist between + /// original and copy. + /// [TestMethod] public void SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance() { @@ -105,6 +121,12 @@ public void SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance() /// Tests the method adds or updates /// information correctly /// + /// + /// Verifies two sub-scenarios in one test: (1) an existing annotation is enhanced with + /// additional field values from a matching entry (Id is populated), and (2) a new + /// annotation is appended when no match exists. Both sub-scenarios use the + /// comparer for matching. + /// [TestMethod] public void SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly() { @@ -156,6 +178,11 @@ public void SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly() /// /// Tests the method reports bad annotators. /// + /// + /// Boundary: an empty string is the only invalid + /// field; all other fields are valid so that the issue list contains exactly one entry + /// for the annotator. + /// [TestMethod] public void SpdxAnnotation_Validate_InvalidAnnotator() { @@ -179,6 +206,10 @@ public void SpdxAnnotation_Validate_InvalidAnnotator() /// /// Tests the method reports bad dates. /// + /// + /// Boundary: a non-ISO-8601 date string is the only invalid field; all other fields are + /// valid so that the issue list contains exactly one entry for the date. + /// [TestMethod] public void SpdxAnnotation_Validate_InvalidDate() { @@ -202,6 +233,10 @@ public void SpdxAnnotation_Validate_InvalidDate() /// /// Tests the method reports bad annotation types. /// + /// + /// Boundary: is the only invalid field; all + /// other fields are valid so that the issue list contains exactly one entry for the type. + /// [TestMethod] public void SpdxAnnotation_Validate_InvalidType() { @@ -225,6 +260,12 @@ public void SpdxAnnotation_Validate_InvalidType() /// /// Tests the method reports bad comments. /// + /// + /// Boundary: an empty string is the only invalid + /// field; is set to + /// (valid) so that the issue list contains + /// exactly one entry for the comment. + /// [TestMethod] public void SpdxAnnotation_Validate_InvalidComment() { @@ -233,7 +274,7 @@ public void SpdxAnnotation_Validate_InvalidComment() { Annotator = "Person: Malcolm Nixon", Date = "2024-05-28T01:30:00Z", - Type = SpdxAnnotationType.Missing, + Type = SpdxAnnotationType.Review, Comment = "" }; @@ -248,6 +289,11 @@ public void SpdxAnnotation_Validate_InvalidComment() /// /// Tests the method for valid annotation types. /// + /// + /// Verifies case-insensitive mapping: "REVIEW", "review", and "Review" all map to + /// ; similarly for "OTHER". An empty string maps + /// to . + /// [TestMethod] public void SpdxAnnotationTypeExtensions_FromText_Valid() { @@ -266,6 +312,10 @@ public void SpdxAnnotationTypeExtensions_FromText_Valid() /// /// Tests the method for an invalid annotation type. /// + /// + /// Boundary: an unrecognized string causes with + /// the expected message, confirming the error path is not silently swallowed. + /// [TestMethod] public void SpdxAnnotationTypeExtensions_FromText_Invalid() { @@ -280,6 +330,10 @@ public void SpdxAnnotationTypeExtensions_FromText_Invalid() /// /// Tests the method for valid annotation types. /// + /// + /// Verifies that produces "REVIEW" and + /// produces "OTHER". + /// [TestMethod] public void SpdxAnnotationTypeExtensions_ToText_Valid() { @@ -294,6 +348,11 @@ public void SpdxAnnotationTypeExtensions_ToText_Valid() /// Tests the method for an invalid annotation /// type. /// + /// + /// Boundary: an unrecognized numeric enum value (1000, which is not a defined + /// member) causes + /// with the expected message. + /// [TestMethod] public void SpdxAnnotationTypeExtensions_ToText_Invalid() { @@ -308,6 +367,12 @@ public void SpdxAnnotationTypeExtensions_ToText_Invalid() /// Tests that throws for the /// sentinel value. /// + /// + /// Boundary: is a sentinel value that must + /// never be serialized. Calling ToText with it throws + /// with the expected "Attempt to serialize + /// missing SPDX Annotation Type" message. + /// [TestMethod] public void SpdxAnnotationTypeExtensions_ToText_Missing() { diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs index af66de2..15dedc7 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs @@ -23,12 +23,23 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Tests the class and the +/// extension methods. Uses MSTest as the +/// test framework (approved exception: xUnit adoption is deferred for this project). +/// Each test method is fully self-contained with no shared fixture state. +/// [TestClass] public class SpdxChecksumTests { /// /// Tests the comparer compares checksums correctly. /// + /// + /// Sets up three checksums: two with identical algorithm and value (c1 and c2) and one + /// with a different algorithm and value (c3). Verifies reflexive, symmetric, and + /// cross-inequality comparisons, and that equal checksums produce identical hash codes. + /// [TestMethod] public void SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquality() { @@ -64,13 +75,18 @@ public void SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquali Assert.IsFalse(SpdxChecksum.Same.Equals(c2, c3)); Assert.IsFalse(SpdxChecksum.Same.Equals(c3, c2)); - // Assert same checksums have identical hashes + // Assert: same checksums have identical hashes Assert.AreEqual(SpdxChecksum.Same.GetHashCode(c1), SpdxChecksum.Same.GetHashCode(c2)); } /// /// Tests the method successfully creates a deep copy. /// + /// + /// Creates a fully-populated checksum instance and deep-copies it. Verifies that the + /// copy has equal field values (Algorithm and Value) but is a distinct object reference + /// from the original, confirming no shallow aliasing. + /// [TestMethod] public void SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInstance() { @@ -97,6 +113,11 @@ public void SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInsta /// Tests the method adds or updates information /// correctly. /// + /// + /// Starts with a single SHA1 checksum and enhances with a list containing the same SHA1 + /// entry and a new MD5 entry. Verifies that the existing entry is preserved and the new + /// entry is appended, resulting in exactly two checksums. + /// [TestMethod] public void SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformation() { @@ -137,6 +158,11 @@ public void SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformati /// /// Tests the method reports bad algorithms. /// + /// + /// Uses as the algorithm sentinel to confirm + /// that the validator catches the absent algorithm and includes the expected description + /// string in the reported issue. + /// [TestMethod] public void SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue() { @@ -158,6 +184,11 @@ public void SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue() /// /// Tests the method reports bad values. /// + /// + /// Uses an empty string as the checksum value — the minimal invalid state — to confirm + /// that the validator catches the empty value and includes the expected description string + /// in the reported issue. + /// [TestMethod] public void SpdxChecksum_Validate_EmptyValue_ReportsValueIssue() { @@ -179,6 +210,11 @@ public void SpdxChecksum_Validate_EmptyValue_ReportsValueIssue() /// /// Tests the method reports unknown numeric algorithms. /// + /// + /// Casts the integer literal 1000 to to produce an + /// out-of-range enum value. Verifies that the validator treats it as an unknown algorithm + /// and reports the expected diagnostic message. + /// [TestMethod] public void SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue() { @@ -200,6 +236,11 @@ public void SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue( /// /// Tests the method. /// + /// + /// Exercises every known algorithm string defined by the SPDX 2.x specification, plus + /// case-variant inputs for SHA1, to confirm that + /// maps each string to the correct enum value. + /// [TestMethod] public void SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_ReturnsCorrectEnumValues() { @@ -231,6 +272,12 @@ public void SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_Retur /// /// Tests the method. /// + /// + /// Passes the unrecognized string "unknown" to confirm that + /// throws + /// with the expected message rather than + /// returning a default value or silently succeeding. + /// [TestMethod] public void SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_ThrowsInvalidOperationException() { @@ -246,6 +293,11 @@ public void SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_Thro /// /// Tests the method. /// + /// + /// Exercises every serializable enum value to confirm + /// that returns the canonical + /// SPDX 2.x algorithm string for each value. + /// [TestMethod] public void SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCorrectStrings() { @@ -274,6 +326,11 @@ public void SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCo /// /// Tests the method. /// + /// + /// Casts the integer literal 1000 to to produce an + /// out-of-range value. Verifies that + /// throws with the expected message. + /// [TestMethod] public void SpdxChecksumAlgorithmExtensions_ToText_OutOfRangeEnum_ThrowsInvalidOperationException() { @@ -289,6 +346,11 @@ public void SpdxChecksumAlgorithmExtensions_ToText_OutOfRangeEnum_ThrowsInvalidO /// Tests the method throws for /// . /// + /// + /// Passes — the sentinel value that must + /// never be serialized — to confirm that + /// throws with the expected message. + /// [TestMethod] public void SpdxChecksumAlgorithmExtensions_ToText_MissingAlgorithm_ThrowsInvalidOperationException() { @@ -303,6 +365,11 @@ public void SpdxChecksumAlgorithmExtensions_ToText_MissingAlgorithm_ThrowsInvali /// /// Tests that returns false when the first argument is null. /// + /// + /// Passes a null reference as the first argument and a valid checksum as the second. + /// Verifies that the comparer returns false rather than throwing, exercising the null-guard + /// on the left-hand operand. + /// [TestMethod] public void SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse() { @@ -324,6 +391,11 @@ public void SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse() /// /// Tests that returns false when the second argument is null. /// + /// + /// Passes a valid checksum as the first argument and a null reference as the second. + /// Verifies that the comparer returns false rather than throwing, exercising the null-guard + /// on the right-hand operand. + /// [TestMethod] public void SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse() { @@ -345,6 +417,10 @@ public void SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse() /// /// Tests that returns true when both arguments are null. /// + /// + /// Passes two null references to confirm that the comparer returns true when both + /// operands are null, consistent with standard equality-comparer semantics. + /// [TestMethod] public void SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue() { @@ -362,6 +438,11 @@ public void SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue() /// /// Tests that returns Missing for an empty string. /// + /// + /// Passes an empty string to confirm that + /// returns rather than throwing, treating + /// the empty string as the absent-algorithm sentinel. + /// [TestMethod] public void SpdxChecksumAlgorithmExtensions_FromText_EmptyString_ReturnsMissing() { diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs index 46ab302..50970dc 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs @@ -41,7 +41,7 @@ public class SpdxCreationInformationTests /// no shallow-copy aliasing occurs. /// [TestMethod] - public void SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxCreationInformation_DeepCopy_WithAllFieldsPopulated_CreatesEqualButDistinctInstance() { // Arrange: Create an instance of SpdxCreationInformation with multiple creators var c1 = new SpdxCreationInformation @@ -75,7 +75,7 @@ public void SpdxCreationInformation_DeepCopy_CreatesEqualButDistinctInstance() /// confirm additive merging of creators and fill-if-absent semantics for scalar fields. /// [TestMethod] - public void SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly() + public void SpdxCreationInformation_Enhance_WithMissingFieldsInBase_AddsOrUpdatesInformationCorrectly() { // Arrange: Create an instance of SpdxCreationInformation with initial values var info = new SpdxCreationInformation @@ -112,7 +112,7 @@ public void SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly() /// field values. /// [TestMethod] - public void SpdxCreationInformation_Validate_MissingCreators() + public void SpdxCreationInformation_Validate_MissingCreators_ReportsIssue() { // Arrange: Create creation information with empty creators array var info = new SpdxCreationInformation @@ -139,7 +139,7 @@ public void SpdxCreationInformation_Validate_MissingCreators() /// fails all three prefixes, making the expected issue deterministic. /// [TestMethod] - public void SpdxCreationInformation_Validate_InvalidCreator() + public void SpdxCreationInformation_Validate_InvalidCreator_ReportsIssue() { // Arrange: Create creation information with invalid creator format var info = new SpdxCreationInformation @@ -166,7 +166,7 @@ public void SpdxCreationInformation_Validate_InvalidCreator() /// the regex/helper rejects it without false negatives. /// [TestMethod] - public void SpdxCreationInformation_Validate_InvalidCreatedDate() + public void SpdxCreationInformation_Validate_InvalidCreatedDate_ReportsIssue() { // Arrange: Create creation information with invalid created date var info = new SpdxCreationInformation @@ -193,13 +193,13 @@ public void SpdxCreationInformation_Validate_InvalidCreatedDate() /// that the regex rejects non-numeric version strings. /// [TestMethod] - public void SpdxCreationInformation_Validate_InvalidVersion() + public void SpdxCreationInformation_Validate_InvalidVersion_ReportsIssue() { // Arrange: Create creation information with invalid license list version var info = new SpdxCreationInformation { Creators = ["Tool: LicenseFind-1.0", "Organization: ExampleCodeInspect ()"], - Created = "BadDate", + Created = "2021-01-01T00:00:00Z", Comment = "This package has been shipped in source and binary form.", LicenseListVersion = "BadVersion" }; diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs index 71f1e7e..80c8225 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs @@ -25,14 +25,25 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Tests the class using MSTest (approved exception: xUnit +/// adoption is deferred for this project). Each test constructs its own document state +/// from scratch or deserializes the embedded JSON fixture +/// SPDXJSONExample-v2.3.spdx.json; no shared instance state is used. +/// [TestClass] public class SpdxDocumentTests { /// /// Tests the method returns the correct root packages /// + /// + /// Constructs a document with three packages and two root-package indicators (one via the + /// Describes list and one via a DescribedBy relationship) to verify that GetRootPackages + /// returns exactly the two packages named as roots and excludes the third. + /// [TestMethod] - public void SpdxDocument_GetRootPackages_CorrectPackages() + public void SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCorrectPackages() { // Arrange: Create a sample SPDX document with multiple packages and relationships var document = new SpdxDocument @@ -85,8 +96,14 @@ public void SpdxDocument_GetRootPackages_CorrectPackages() /// /// Tests the comparer compares documents correctly. /// + /// + /// Sets up three documents: d1 and d2 both describe a root package with the same name + /// and version (but different IDs and versions), and d3 describes a different package. + /// Verifies reflexive, symmetric, cross-inequality comparisons, and hash-code consistency + /// for equal documents. + /// [TestMethod] - public void SpdxDocument_SameComparer_ComparesCorrectly() + public void SpdxDocument_Same_DocumentsWithMatchingRootPackages_AreEqual() { // Arrange: Create three documents with different properties var d1 = new SpdxDocument @@ -163,8 +180,14 @@ public void SpdxDocument_SameComparer_ComparesCorrectly() /// /// Tests the method successfully creates a deep copy. /// + /// + /// Constructs a fully-populated document with external document references, extracted + /// licensing info, annotations, files, packages, snippets, relationships, and a Describes + /// list, then deep-copies it. Verifies that every collection and scalar field is equal + /// but that all array references are distinct from the original. + /// [TestMethod] - public void SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxDocument_DeepCopy_WithPopulatedDocument_CreatesEqualButDistinctInstance() { // Arrange: Create a sample SPDX document with various elements var d1 = new SpdxDocument @@ -285,8 +308,13 @@ public void SpdxDocument_DeepCopy_CreatesEqualButDistinctInstance() /// /// Tests the method successfully validates a document with no issues. /// + /// + /// Deserializes SPDXJSONExample-v2.3.spdx.json (a known-good SPDX 2.3 example) + /// to obtain a fully valid document and verifies that the validator reports no issues. + /// Using the embedded JSON fixture ensures the document satisfies all field constraints. + /// [TestMethod] - public void SpdxDocument_Validate_NoIssues() + public void SpdxDocument_Validate_ValidDocument_ReportsNoIssues() { // Arrange: Load a valid SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -305,8 +333,13 @@ public void SpdxDocument_Validate_NoIssues() /// /// Tests the method reports invalid IDs. /// + /// + /// Deserializes the embedded JSON fixture to obtain a valid baseline document, then + /// overwrites its SPDX-ID with "BadId". Verifies that the validator reports + /// the expected diagnostic for a malformed SPDX identifier. + /// [TestMethod] - public void SpdxDocument_Validate_InvalidId() + public void SpdxDocument_Validate_InvalidId_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -328,8 +361,13 @@ public void SpdxDocument_Validate_InvalidId() /// /// Tests the method reports invalid names. /// + /// + /// Deserializes the embedded JSON fixture to obtain a valid baseline document, then + /// clears its name field. Verifies that the validator reports the expected diagnostic + /// for an empty document name. + /// [TestMethod] - public void SpdxDocument_Validate_InvalidName() + public void SpdxDocument_Validate_InvalidName_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -351,8 +389,13 @@ public void SpdxDocument_Validate_InvalidName() /// /// Tests the method reports invalid versions. /// + /// + /// Deserializes the embedded JSON fixture to obtain a valid baseline document, then + /// overwrites the version with "BadVersion". Verifies that the validator reports + /// the expected diagnostic for a version string that does not match the SPDX-2.x pattern. + /// [TestMethod] - public void SpdxDocument_Validate_InvalidVersion() + public void SpdxDocument_Validate_InvalidVersion_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -374,8 +417,13 @@ public void SpdxDocument_Validate_InvalidVersion() /// /// Tests the method reports invalid data licenses. /// + /// + /// Deserializes the embedded JSON fixture to obtain a valid baseline document, then + /// overwrites the data license with "BadLicense". Verifies that the validator + /// reports the expected diagnostic for a value other than the mandatory CC0-1.0 license. + /// [TestMethod] - public void SpdxDocument_Validate_InvalidDataLicense() + public void SpdxDocument_Validate_InvalidDataLicense_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -397,8 +445,13 @@ public void SpdxDocument_Validate_InvalidDataLicense() /// /// Tests the method reports invalid namespaces. /// + /// + /// Deserializes the embedded JSON fixture to obtain a valid baseline document, then + /// clears the namespace URI. Verifies that the validator reports the expected diagnostic + /// for an empty document namespace. + /// [TestMethod] - public void SpdxDocument_Validate_InvalidNameSpace() + public void SpdxDocument_Validate_InvalidNameSpace_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -420,8 +473,13 @@ public void SpdxDocument_Validate_InvalidNameSpace() /// /// Tests the method detects duplicate IDs. /// + /// + /// Constructs a minimal document containing two packages with the same SPDX-ID. Verifies + /// that the validator reports the expected duplicate-ID diagnostic rather than silently + /// accepting the malformed document. + /// [TestMethod] - public void SpdxDocument_Validate_DuplicatePackageIds() + public void SpdxDocument_Validate_DuplicatePackageIds_ReportsIssue() { // Arrange: Create a sample SPDX document with duplicate package IDs var doc = new SpdxDocument @@ -463,8 +521,13 @@ public void SpdxDocument_Validate_DuplicatePackageIds() /// /// Tests the method detects bad relationships. /// + /// + /// Constructs a minimal document containing a relationship whose + /// RelatedSpdxElement references an ID that does not exist in the document. + /// Verifies that the validator reports the expected dangling-reference diagnostic. + /// [TestMethod] - public void SpdxDocument_Validate_InvalidRelationship() + public void SpdxDocument_Validate_InvalidRelationship_ReportsIssue() { // Arrange: Create a sample SPDX document with a relationship to a non-existent package var doc = new SpdxDocument @@ -502,8 +565,13 @@ public void SpdxDocument_Validate_InvalidRelationship() /// /// Tests the method detects NTIA issues. /// + /// + /// Deserializes the embedded JSON fixture, which deliberately omits required NTIA fields + /// for some packages (supplier and version for Apache Commons Lang; supplier for Jena and + /// Saxon). Verifies that the NTIA validation mode reports exactly those expected issues. + /// [TestMethod] - public void SpdxDocument_Validate_NtiaIssues() + public void SpdxDocument_Validate_NtiaMinimumElements_ReportsIssues() { // Arrange: Load a sample SPDX JSON document with known NTIA issues var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -525,8 +593,14 @@ public void SpdxDocument_Validate_NtiaIssues() /// /// Tests the method returns all elements in the document. /// + /// + /// Deserializes the embedded JSON fixture, which contains a document element, four + /// packages, and five files. Verifies that + /// returns exactly those elements and that entries are + /// excluded from the result. + /// [TestMethod] - public void SpdxDocument_GetAllElements_Correct() + public void SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelationshipElements() { // Arrange: Load a sample SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( @@ -560,6 +634,11 @@ public void SpdxDocument_GetAllElements_Correct() /// /// Tests the method returns the document element. /// + /// + /// Deserializes the embedded JSON fixture and queries for the document element by its + /// SPDX-ID. Verifies that the returned object is the document itself and has the expected + /// document name. + /// [TestMethod] public void SpdxDocument_GetElement_Document_ReturnsDocumentElement() { @@ -580,6 +659,10 @@ public void SpdxDocument_GetElement_Document_ReturnsDocumentElement() /// /// Tests the method returns the correct file element. /// + /// + /// Deserializes the embedded JSON fixture and queries for a file element by its SPDX-ID. + /// Verifies that the returned object is the correct file and has the expected file name. + /// [TestMethod] public void SpdxDocument_GetElement_File_ReturnsFileElement() { @@ -600,6 +683,11 @@ public void SpdxDocument_GetElement_File_ReturnsFileElement() /// /// Tests the method returns the correct package element. /// + /// + /// Deserializes the embedded JSON fixture and queries for a package element by its + /// SPDX-ID. Verifies that the returned object is the correct package and has the + /// expected file name property. + /// [TestMethod] public void SpdxDocument_GetElement_Package_ReturnsPackageElement() { @@ -620,6 +708,11 @@ public void SpdxDocument_GetElement_Package_ReturnsPackageElement() /// /// Tests the method returns the correct snippet element. /// + /// + /// Deserializes the embedded JSON fixture and queries for a snippet element by its + /// SPDX-ID. Verifies that the returned object is the correct snippet and references + /// the expected source file SPDX-ID. + /// [TestMethod] public void SpdxDocument_GetElement_Snippet_ReturnsSnippetElement() { @@ -640,8 +733,13 @@ public void SpdxDocument_GetElement_Snippet_ReturnsSnippetElement() /// /// Tests the method validates document-level annotations. /// + /// + /// Constructs a minimal valid document that contains a single annotation with an empty + /// Annotator field. Verifies that the validator reports the annotation-level issue using + /// the document element prefix so the issue can be attributed to the correct context. + /// [TestMethod] - public void SpdxDocument_Validate_InvalidAnnotation() + public void SpdxDocument_Validate_InvalidAnnotation_ReportsIssue() { // Arrange: Create a document with an invalid annotation var doc = new SpdxDocument diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs index 3544571..5cd6e2b 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs @@ -23,6 +23,12 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Tests the class using MSTest (approved +/// exception: xUnit adoption is deferred for this project). Each test method constructs +/// its own instance state with no shared fixture, covering the Same comparer, DeepCopy, +/// Enhance, and Validate methods. +/// [TestClass] public class SpdxExternalDocumentReferenceTests { @@ -30,8 +36,14 @@ public class SpdxExternalDocumentReferenceTests /// Tests the comparer compares external document references /// correctly. /// + /// + /// Constructs three references: r1 and r2 share the same Document URI (making them + /// equal under the Same comparer) while r3 has a different URI. Verifies reflexive, + /// symmetric, and cross-inequality comparisons, and that equal references produce + /// identical hash codes. + /// [TestMethod] - public void SpdxExternalDocumentReference_SameComparer_ComparesCorrectly() + public void SpdxExternalDocumentReference_SameComparer_SameDocument_ReturnsEqual() { // Arrange: Create three external document references with different properties var r1 = new SpdxExternalDocumentReference @@ -86,8 +98,13 @@ public void SpdxExternalDocumentReference_SameComparer_ComparesCorrectly() /// /// Tests the method successfully creates a deep copy. /// + /// + /// Creates a fully-populated external document reference with a checksum and deep-copies + /// it. Verifies that the copy has equal field values but that both the top-level reference + /// and the nested Checksum are distinct object references from the original. + /// [TestMethod] - public void SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxExternalDocumentReference_DeepCopy_ValidInstance_ReturnsEqualButDistinctInstance() { // Arrange: Create an external document reference with a checksum var r1 = new SpdxExternalDocumentReference @@ -120,8 +137,14 @@ public void SpdxExternalDocumentReference_DeepCopy_CreatesEqualButDistinctInstan /// /// method adds or updates information correctly. /// + /// + /// Starts with a single reference that lacks a checksum, then enhances with a list + /// containing one entry that updates the existing reference's checksum and one entirely + /// new reference. Verifies that the merged array has exactly two entries with the correct + /// checksum data and new reference details. + /// [TestMethod] - public void SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrectly() + public void SpdxExternalDocumentReference_Enhance_WithNewAndMatchingEntries_MergesAndAppendsCorrectly() { // Arrange: Create an array of external document references var references = new[] @@ -174,8 +197,13 @@ public void SpdxExternalDocumentReference_Enhance_AddsOrUpdatesInformationCorrec /// /// Tests the method reports missing external document ID /// + /// + /// Sets ExternalDocumentId to an empty string — the minimal invalid state — to confirm + /// that the validator catches the absent ID and includes the expected description string + /// in the reported issue. + /// [TestMethod] - public void SpdxExternalDocumentReference_Validate_MissingId() + public void SpdxExternalDocumentReference_Validate_MissingId_ReportsIssue() { // Arrange: Create a bad reference var reference = new SpdxExternalDocumentReference @@ -195,8 +223,13 @@ public void SpdxExternalDocumentReference_Validate_MissingId() /// /// Tests the method reports missing document URI /// + /// + /// Sets the Document URI to an empty string with a valid ExternalDocumentId to confirm + /// that the validator catches the absent URI and includes the expected description string + /// (including the reference ID) in the reported issue. + /// [TestMethod] - public void SpdxExternalDocumentReference_Validate_MissingDocument() + public void SpdxExternalDocumentReference_Validate_MissingDocument_ReportsIssue() { // Arrange: Create a bad reference var reference = new SpdxExternalDocumentReference @@ -216,6 +249,11 @@ public void SpdxExternalDocumentReference_Validate_MissingDocument() /// /// Tests the method reports an invalid checksum. /// + /// + /// Constructs a reference with a algorithm + /// and an empty checksum value to confirm that the validator delegates to the checksum + /// validator and surfaces the algorithm-missing diagnostic in the reported issues. + /// [TestMethod] public void SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue() { diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs index f258b15..7719f27 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs @@ -23,12 +23,21 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Covers the Same equality comparer, DeepCopy, Enhance merge, Validate, and the +/// SpdxReferenceCategory text-conversion extension methods (FromText/ToText). +/// [TestClass] public class SpdxExternalReferenceTests { /// /// Tests the comparer compares external references correctly. /// + /// + /// Verifies that two references with the same Category, Type, and Locator are equal + /// regardless of Comment differences, that differing field values produce inequality, + /// and that equal references produce identical hash codes. + /// [TestMethod] public void SpdxExternalReference_SameComparer_ComparesCorrectly() { @@ -73,6 +82,10 @@ public void SpdxExternalReference_SameComparer_ComparesCorrectly() /// /// Tests the method successfully creates a deep copy. /// + /// + /// Verifies that the copy has equal field values to the original and that it is a distinct + /// object reference (not the same instance). + /// [TestMethod] public void SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance() { @@ -103,6 +116,10 @@ public void SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance() /// Tests the method /// adds or updates information correctly. /// + /// + /// Verifies that matching entries are enhanced in place and unmatched entries from the + /// source array are appended as new independent copies. + /// [TestMethod] public void SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly() { @@ -150,8 +167,12 @@ public void SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly() /// /// Tests the method reports invalid categories. /// + /// + /// Verifies that Validate appends an issue message when the Category field is + /// . + /// [TestMethod] - public void SpdxExternalReference_Validate_InvalidCategory() + public void SpdxExternalReference_Validate_InvalidCategory_ReportsIssue() { // Arrange: Create an external reference with invalid category var reference = new SpdxExternalReference @@ -173,8 +194,11 @@ public void SpdxExternalReference_Validate_InvalidCategory() /// /// Tests the method reports invalid types. /// + /// + /// Verifies that Validate appends an issue message when the Type field is empty. + /// [TestMethod] - public void SpdxExternalReference_Validate_InvalidType() + public void SpdxExternalReference_Validate_InvalidType_ReportsIssue() { // Arrange: Create an external reference with invalid type var reference = new SpdxExternalReference @@ -196,8 +220,11 @@ public void SpdxExternalReference_Validate_InvalidType() /// /// Tests the method reports invalid locators. /// + /// + /// Verifies that Validate appends an issue message when the Locator field is empty. + /// [TestMethod] - public void SpdxExternalReference_Validate_InvalidLocator() + public void SpdxExternalReference_Validate_InvalidLocator_ReportsIssue() { // Arrange: Create an external reference with invalid locator var reference = new SpdxExternalReference @@ -219,8 +246,12 @@ public void SpdxExternalReference_Validate_InvalidLocator() /// /// Tests the method with valid input. /// + /// + /// Verifies that all recognized category strings, including case variants, map to the + /// expected enum values, and that an empty string maps to Missing. + /// [TestMethod] - public void SpdxReferenceCategoryExtensions_FromText_Valid() + public void SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly() { // Arrange: (no external state needed) @@ -240,8 +271,12 @@ public void SpdxReferenceCategoryExtensions_FromText_Valid() /// /// Tests the method with invalid input. /// + /// + /// Verifies that FromText throws with a message + /// identifying the unsupported value when given an unrecognized category string. + /// [TestMethod] - public void SpdxReferenceCategoryExtensions_FromText_Invalid() + public void SpdxReferenceCategoryExtensions_FromText_InvalidInput_ReturnsNull() { // Arrange: (no external state needed) @@ -254,8 +289,11 @@ public void SpdxReferenceCategoryExtensions_FromText_Invalid() /// /// Tests the method with valid input. /// + /// + /// Verifies that all known enum values map to their expected SPDX text representations. + /// [TestMethod] - public void SpdxReferenceCategoryExtensions_ToText_Valid() + public void SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrectly() { // Arrange: (no external state needed) @@ -269,13 +307,17 @@ public void SpdxReferenceCategoryExtensions_ToText_Valid() /// /// Tests the method with invalid input. /// + /// + /// Verifies that ToText throws with the + /// unsupported-category message when called with an unrecognized enum value. + /// [TestMethod] - public void SpdxReferenceCategoryExtensions_ToText_InvalidCategory() + public void SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull() { // Arrange: Create an invalid reference category var invalidCategory = (SpdxReferenceCategory)1000; - // Act & Assert: Verify that ToText throws an exception for unsupported category + // Act / Assert: Verify that ToText throws an exception for unsupported category var exception = Assert.ThrowsExactly(() => invalidCategory.ToText()); Assert.AreEqual("Unsupported SPDX Reference Category '1000'", exception.Message); } @@ -283,13 +325,17 @@ public void SpdxReferenceCategoryExtensions_ToText_InvalidCategory() /// /// Tests the method with Missing category. /// + /// + /// Verifies that ToText throws with a specific + /// message when called with . + /// [TestMethod] - public void SpdxReferenceCategoryExtensions_ToText_MissingCategory() + public void SpdxReferenceCategoryExtensions_ToText_MissingCategory_ReturnsNull() { // Arrange: Use Missing reference category var category = SpdxReferenceCategory.Missing; - // Act & Assert: Verify that ToText throws an exception for Missing category + // Act / Assert: Verify that ToText throws an exception for Missing category var exception = Assert.ThrowsExactly(() => category.ToText()); Assert.AreEqual("Attempt to serialize missing SPDX Reference Category", exception.Message); } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs index 3798337..81a5ae0 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs @@ -23,6 +23,9 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Covers the Same equality comparer, DeepCopy, Enhance merge, and Validate methods. +/// [TestClass] public class SpdxExtractedLicensingInfoTests { @@ -56,7 +59,7 @@ public void SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly() ExtractedText = "Some Random License" }; - // Assert: Verify extracted-licensing-infos compare to themselves + // Act / Assert: Verify extracted-licensing-infos compare to themselves Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(l1, l1)); Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(l2, l2)); Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(l3, l3)); @@ -193,7 +196,7 @@ public void SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues() /// empty, confirming the LicenseId validation path. /// [TestMethod] - public void SpdxExtractedLicensingInfo_Validate_InvalidLicenseId() + public void SpdxExtractedLicensingInfo_Validate_InvalidLicenseId_ReportsIssue() { // Arrange: Create a bad licensing info var info = new SpdxExtractedLicensingInfo @@ -218,7 +221,7 @@ public void SpdxExtractedLicensingInfo_Validate_InvalidLicenseId() /// is empty, confirming the ExtractedText validation path. /// [TestMethod] - public void SpdxExtractedLicensingInfo_Validate_InvalidExtractedText() + public void SpdxExtractedLicensingInfo_Validate_InvalidExtractedText_ReportsIssue() { // Arrange: Create a bad licensing info var info = new SpdxExtractedLicensingInfo diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs index e254ddd..82610a1 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs @@ -23,14 +23,23 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// +/// +/// Covers the Same equality comparer, DeepCopy, Enhance merge, Validate, and the +/// SpdxFileType text-conversion extension methods (FromText/ToText). +/// [TestClass] public class SpdxFileTests { /// /// Tests the comparer compares files correctly. /// + /// + /// Verifies that two files with the same FileName and compatible SHA1 checksums are + /// considered equal, that differing SHA1 checksums or file names produce inequality, + /// and that equal files produce identical hash codes. + /// [TestMethod] - public void SpdxFile_SameComparer_ComparesCorrectly() + public void SpdxFile_SameComparer_MatchingAndDistinctFiles_ComparesCorrectly() { // Arrange: Create several SpdxFile instances with different IDs, names, and checksums var f1 = new SpdxFile @@ -106,8 +115,12 @@ public void SpdxFile_SameComparer_ComparesCorrectly() /// /// Tests the method successfully creates a deep copy. /// + /// + /// Verifies that the copy has equal field values to the original and that all array + /// fields are independently copied with no shared references between original and copy. + /// [TestMethod] - public void SpdxFile_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy() { // Arrange: Create an SpdxFile instance with all deep-copied fields populated var f1 = new SpdxFile @@ -179,8 +192,12 @@ public void SpdxFile_DeepCopy_CreatesEqualButDistinctInstance() /// /// Tests the method correctly adds or updates information /// + /// + /// Verifies that matching entries are enhanced in place and unmatched entries from the + /// source array are appended as new independent copies. + /// [TestMethod] - public void SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly() + public void SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly() { // Arrange: Create an array of SpdxFile objects with one file var files = new[] @@ -255,8 +272,12 @@ public void SpdxFile_Enhance_AddsOrUpdatesInformationCorrectly() /// /// Tests that an invalid file ID fails validation. /// + /// + /// Verifies that Validate appends an issue message when the SPDX-ID does not conform + /// to the required SPDXRef- prefix format. + /// [TestMethod] - public void SpdxFile_Validate_ReportsInvalidFileId() + public void SpdxFile_Validate_InvalidFileId_ReportsIssue() { // Arrange: Create an SpdxFile instance with an invalid ID format var spdxFile = new SpdxFile @@ -284,8 +305,12 @@ public void SpdxFile_Validate_ReportsInvalidFileId() /// /// Tests that an invalid file name fails validation. /// + /// + /// Verifies that Validate appends an issue message when FileName does not start with + /// the required "./" prefix. + /// [TestMethod] - public void SpdxFile_Validate_ReportsInvalidFileName() + public void SpdxFile_Validate_InvalidFileName_ReportsIssue() { // Arrange: Create an SpdxFile instance with a FileName that has no "./" prefix var spdxFile = new SpdxFile @@ -313,8 +338,12 @@ public void SpdxFile_Validate_ReportsInvalidFileName() /// /// Tests that a missing SHA1 checksum fails validation. /// + /// + /// Verifies that Validate appends an issue message when no SHA1 checksum is present + /// in the Checksums array. + /// [TestMethod] - public void SpdxFile_Validate_ReportsWhenSha1ChecksumMissing() + public void SpdxFile_Validate_MissingSha1Checksum_ReportsIssue() { // Arrange: Create an SpdxFile instance with only an MD5 checksum (no SHA1) var spdxFile = new SpdxFile @@ -342,8 +371,12 @@ public void SpdxFile_Validate_ReportsWhenSha1ChecksumMissing() /// /// Tests that a valid file passes validation. /// + /// + /// Verifies that a fully populated valid SpdxFile passes all validation checks + /// without reporting any issues. + /// [TestMethod] - public void SpdxFile_Validate_Success() + public void SpdxFile_Validate_ValidFile_ReportsNoIssues() { // Arrange: Create a valid SpdxFile instance var spdxFile = new SpdxFile @@ -376,8 +409,12 @@ public void SpdxFile_Validate_Success() /// /// Tests the method with valid inputs. /// + /// + /// Verifies that all recognized file type strings, including case variants, map to the + /// expected enum values. + /// [TestMethod] - public void SpdxFileTypeExtensions_FromText_Valid() + public void SpdxFileTypeExtensions_FromText_ValidInput_ParsesCorrectly() { // Arrange: (no external state needed) @@ -400,8 +437,12 @@ public void SpdxFileTypeExtensions_FromText_Valid() /// /// Tests the method with invalid input. /// + /// + /// Verifies that FromText throws with a message + /// identifying the unsupported value when given an unrecognized file type string. + /// [TestMethod] - public void SpdxFileTypeExtensions_FromText_Invalid() + public void SpdxFileTypeExtensions_FromText_InvalidInput_ThrowsException() { // Arrange: An unrecognized file type string @@ -414,8 +455,12 @@ public void SpdxFileTypeExtensions_FromText_Invalid() /// /// Tests the method with valid inputs /// + /// + /// Verifies that all known file type enum values map to their expected SPDX text + /// representations. + /// [TestMethod] - public void SpdxFileTypeExtensions_ToText_Valid() + public void SpdxFileTypeExtensions_ToText_ValidEnum_FormatsCorrectly() { // Arrange: (no external state needed) @@ -436,8 +481,12 @@ public void SpdxFileTypeExtensions_ToText_Valid() /// /// Tests the method with invalid input. /// + /// + /// Verifies that ToText throws when given an + /// unsupported file type enum value. + /// [TestMethod] - public void SpdxFileTypeExtensions_ToText_Invalid() + public void SpdxFileTypeExtensions_ToText_InvalidEnum_ThrowsException() { // Arrange: An unsupported file type enum value diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs index 0ba7ce3..1747929 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs @@ -179,18 +179,16 @@ public void SpdxModel_Deserialize_MalformedJson_ThrowsJsonException() // Arrange: Prepare malformed JSON text const string malformedJson = "{ this is not valid json }"; - // Act / Assert: Deserialize should throw a JsonException or derived type - var threw = false; + // Act / Assert: malformed JSON throws a JsonException try { Spdx2JsonDeserializer.Deserialize(malformedJson); + Assert.Fail("Expected JsonException was not thrown."); } catch (System.Text.Json.JsonException) { - threw = true; + // Expected — pass } - - Assert.IsTrue(threw, "Expected JsonException was not thrown."); } /// @@ -255,4 +253,68 @@ public void SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNulla Assert.IsNull(file.Comment); Assert.IsNull(relationship.Comment); } + + /// + /// Tests that SPDX date-time validation helper behavior is observable through the document model. + /// + /// + /// Demonstrates that is exercised end-to-end when + /// a real SPDX document is validated, satisfying the system-level observability requirement for + /// helper utilities. + /// + [TestMethod] + public void SpdxModel_Helpers_DateTimeValidation_IsObservableThroughDocumentModel() + { + // Arrange: Load and deserialize a real SPDX 2.3 document + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + + // Act: Validate the document — validation internally invokes IsValidSpdxDateTime on + // the creation-information timestamp, making helper behavior observable at system level + var issues = new List(); + document.Validate(issues); + + // Assert: The document is valid and the Created timestamp is a non-empty, well-formed value + Assert.IsEmpty(issues); + Assert.IsFalse(string.IsNullOrEmpty(document.CreationInformation.Created), + "Expected the Created field to be non-empty after deserialization."); + } + + /// + /// Tests that adding a relationship via the transform API is observable through the document model. + /// + /// + /// Demonstrates end-to-end transform behavior: deserialize a document, add a relationship using + /// the public transform API, and verify the relationship is present in the document model. + /// This satisfies the system-level observability requirement for the transform subsystem. + /// + [TestMethod] + public void SpdxModel_Transform_AddRelationship_IsObservableThroughDocumentModel() + { + // Arrange: Load and deserialize a real SPDX 2.3 document + var json = SpdxTestHelpers.GetEmbeddedResource( + "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); + var document = Spdx2JsonDeserializer.Deserialize(json); + var initialCount = document.Relationships.Length; + var newRelationship = new SpdxRelationship + { + Id = "SPDXRef-DOCUMENT", + RelationshipType = SpdxRelationshipType.DependsOn, + RelatedSpdxElement = "SPDXRef-Package" + }; + + // Act: Add a relationship using the transform public API + DemaConsulting.SpdxModel.Transform.SpdxRelationships.Add(document, newRelationship); + + // Assert: The relationship is now present in the document + Assert.AreEqual(initialCount + 1, document.Relationships.Length, + "Expected document.Relationships to grow by one after Add."); + Assert.IsTrue( + Array.Exists(document.Relationships, r => + r.Id == "SPDXRef-DOCUMENT" && + r.RelationshipType == SpdxRelationshipType.DependsOn && + r.RelatedSpdxElement == "SPDXRef-Package"), + "Expected the newly added relationship to be present in the document."); + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs index acd3746..4b2f507 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs @@ -35,17 +35,16 @@ namespace DemaConsulting.SpdxModel.Tests; public class SpdxRelationshipTests { /// - /// Tests the comparer compares relationships correctly. + /// Tests that the comparer identifies matching relationships as equal. /// /// /// Verifies that two relationships with the same Id, RelationshipType, and - /// RelatedSpdxElement are considered equal even when Comment differs, and that - /// relationships with different element IDs or types are considered distinct. + /// RelatedSpdxElement are considered equal even when Comment differs. /// [TestMethod] - public void SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqual() + public void SpdxRelationship_SameComparer_MatchingRelationships_ReturnsTrue() { - // Arrange: Create three relationships with different properties + // Arrange: Create two relationships that differ only in Comment var r1 = new SpdxRelationship { Id = "SPDXRef-Package1", @@ -59,6 +58,32 @@ public void SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqua RelatedSpdxElement = "SPDXRef-Package2", Comment = "Package 1 contains Package 2" }; + + // Act: Compare the two relationships + var result = SpdxRelationship.Same.Equals(r1, r2); + + // Assert: Verify the relationships are considered equal + Assert.IsTrue(result); + Assert.IsTrue(SpdxRelationship.Same.Equals(r2, r1)); + } + + /// + /// Tests that the comparer identifies different relationships as not equal. + /// + /// + /// Verifies that two relationships with different Id, RelationshipType, or + /// RelatedSpdxElement values are considered distinct. + /// + [TestMethod] + public void SpdxRelationship_SameComparer_DifferentRelationships_ReturnsFalse() + { + // Arrange: Create two relationships with different key fields + var r1 = new SpdxRelationship + { + Id = "SPDXRef-Package1", + RelationshipType = SpdxRelationshipType.Contains, + RelatedSpdxElement = "SPDXRef-Package2" + }; var r3 = new SpdxRelationship { Id = "SPDXRef-Package3", @@ -66,35 +91,58 @@ public void SpdxRelationship_SameComparer_SameFieldsDifferentComment_ReturnsEqua RelatedSpdxElement = "SPDXRef-Package4" }; - // Act / Assert: Verify relationships compare to themselves - Assert.IsTrue(SpdxRelationship.Same.Equals(r1, r1)); - Assert.IsTrue(SpdxRelationship.Same.Equals(r2, r2)); - Assert.IsTrue(SpdxRelationship.Same.Equals(r3, r3)); + // Act: Compare the two relationships + var result = SpdxRelationship.Same.Equals(r1, r3); - // Assert: Verify relationships compare correctly - Assert.IsTrue(SpdxRelationship.Same.Equals(r1, r2)); - Assert.IsTrue(SpdxRelationship.Same.Equals(r2, r1)); - Assert.IsFalse(SpdxRelationship.Same.Equals(r1, r3)); + // Assert: Verify the relationships are considered distinct + Assert.IsFalse(result); Assert.IsFalse(SpdxRelationship.Same.Equals(r3, r1)); - Assert.IsFalse(SpdxRelationship.Same.Equals(r2, r3)); - Assert.IsFalse(SpdxRelationship.Same.Equals(r3, r2)); + } - // Assert: Verify same relationships have identical hashes - Assert.AreEqual(SpdxRelationship.Same.GetHashCode(r1), SpdxRelationship.Same.GetHashCode(r2)); + /// + /// Tests that the comparer produces the same hash code for equal relationships. + /// + /// + /// Verifies that two relationships considered equal by produce + /// identical hash codes, satisfying the hash/equality contract. + /// + [TestMethod] + public void SpdxRelationship_SameComparer_MatchingRelationships_ReturnsSameHashCode() + { + // Arrange: Create two relationships that differ only in Comment + var r1 = new SpdxRelationship + { + Id = "SPDXRef-Package1", + RelationshipType = SpdxRelationshipType.Contains, + RelatedSpdxElement = "SPDXRef-Package2" + }; + var r2 = new SpdxRelationship + { + Id = "SPDXRef-Package1", + RelationshipType = SpdxRelationshipType.Contains, + RelatedSpdxElement = "SPDXRef-Package2", + Comment = "Package 1 contains Package 2" + }; + + // Act: Compute hash codes for both relationships + var hash1 = SpdxRelationship.Same.GetHashCode(r1); + var hash2 = SpdxRelationship.Same.GetHashCode(r2); + + // Assert: Verify the hash codes are identical + Assert.AreEqual(hash1, hash2); } /// - /// Tests the comparer compares relationships with the same elements - /// correctly, + /// Tests that the comparer identifies matching elements as equal. /// /// /// Verifies that two relationships with the same Id and RelatedSpdxElement are considered equal - /// even when RelationshipType differs, and that relationships with different element IDs are distinct. + /// even when RelationshipType differs. /// [TestMethod] - public void SpdxRelationship_SameElementsComparer_SameElementsDifferentType_ReturnsEqual() + public void SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsTrue() { - // Arrange: Create three relationships with different properties + // Arrange: Create two relationships that differ only in RelationshipType var r1 = new SpdxRelationship { Id = "SPDXRef-Package1", @@ -108,6 +156,32 @@ public void SpdxRelationship_SameElementsComparer_SameElementsDifferentType_Retu RelatedSpdxElement = "SPDXRef-Package2", Comment = "Package 1 builds Package 2" }; + + // Act: Compare the two relationships + var result = SpdxRelationship.SameElements.Equals(r1, r2); + + // Assert: Verify the relationships are considered equal + Assert.IsTrue(result); + Assert.IsTrue(SpdxRelationship.SameElements.Equals(r2, r1)); + } + + /// + /// Tests that the comparer identifies different elements as not equal. + /// + /// + /// Verifies that two relationships with different Id or RelatedSpdxElement are considered distinct, + /// regardless of their RelationshipType. + /// + [TestMethod] + public void SpdxRelationship_SameElementsComparer_DifferentElements_ReturnsFalse() + { + // Arrange: Create two relationships with different element IDs + var r1 = new SpdxRelationship + { + Id = "SPDXRef-Package1", + RelationshipType = SpdxRelationshipType.Contains, + RelatedSpdxElement = "SPDXRef-Package2" + }; var r3 = new SpdxRelationship { Id = "SPDXRef-Package3", @@ -115,21 +189,45 @@ public void SpdxRelationship_SameElementsComparer_SameElementsDifferentType_Retu RelatedSpdxElement = "SPDXRef-Package4" }; - // Act / Assert: Verifies relationships compare to themselves - Assert.IsTrue(SpdxRelationship.SameElements.Equals(r1, r1)); - Assert.IsTrue(SpdxRelationship.SameElements.Equals(r2, r2)); - Assert.IsTrue(SpdxRelationship.SameElements.Equals(r3, r3)); + // Act: Compare the two relationships + var result = SpdxRelationship.SameElements.Equals(r1, r3); - // Assert: Verifies relationships compare correctly - Assert.IsTrue(SpdxRelationship.SameElements.Equals(r1, r2)); - Assert.IsTrue(SpdxRelationship.SameElements.Equals(r2, r1)); - Assert.IsFalse(SpdxRelationship.SameElements.Equals(r1, r3)); + // Assert: Verify the relationships are considered distinct + Assert.IsFalse(result); Assert.IsFalse(SpdxRelationship.SameElements.Equals(r3, r1)); - Assert.IsFalse(SpdxRelationship.SameElements.Equals(r2, r3)); - Assert.IsFalse(SpdxRelationship.SameElements.Equals(r3, r2)); + } - // Assert: Verifies same relationships have identical hashes - Assert.AreEqual(SpdxRelationship.SameElements.GetHashCode(r1), SpdxRelationship.SameElements.GetHashCode(r2)); + /// + /// Tests that the comparer produces the same hash code for equal + /// relationships. + /// + /// + /// Verifies that two relationships considered equal by produce + /// identical hash codes, satisfying the hash/equality contract. + /// + [TestMethod] + public void SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsSameHashCode() + { + // Arrange: Create two relationships that differ only in RelationshipType + var r1 = new SpdxRelationship + { + Id = "SPDXRef-Package1", + RelationshipType = SpdxRelationshipType.Contains, + RelatedSpdxElement = "SPDXRef-Package2" + }; + var r2 = new SpdxRelationship + { + Id = "SPDXRef-Package1", + RelationshipType = SpdxRelationshipType.BuildToolOf, + RelatedSpdxElement = "SPDXRef-Package2" + }; + + // Act: Compute hash codes for both relationships + var hash1 = SpdxRelationship.SameElements.GetHashCode(r1); + var hash2 = SpdxRelationship.SameElements.GetHashCode(r2); + + // Assert: Verify the hash codes are identical + Assert.AreEqual(hash1, hash2); } /// @@ -301,7 +399,7 @@ public void SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue() /// type. /// /// - /// Verifies that all 44 recognized SPDX relationship type tokens (including case-insensitive variants) are + /// Verifies that all 45 recognized SPDX relationship type tokens (including case-insensitive variants) are /// correctly parsed to their corresponding enum values, and that an empty /// string maps to . /// @@ -399,7 +497,7 @@ public void SpdxRelationshipTypeExtensions_FromText_UnknownText_ThrowsInvalidOpe /// relationship type. /// /// - /// Verifies that all 44 recognized enum values are correctly serialized to + /// Verifies that all 45 recognized enum values are correctly serialized to /// their canonical SPDX text representations (uppercase, underscore-separated tokens). /// [TestMethod] @@ -455,6 +553,27 @@ public void SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText() Assert.AreEqual("OTHER", SpdxRelationshipType.Other.ToText()); } + /// + /// Tests the method for the + /// sentinel value. + /// + /// + /// Verifies that attempting to serialize the sentinel throws an + /// with a descriptive error message, since the sentinel is not a valid + /// SPDX relationship type token. + /// + [TestMethod] + public void SpdxRelationshipTypeExtensions_ToText_MissingSentinel_ThrowsInvalidOperationException() + { + // Arrange: (none required) + + // Act: Attempt to convert the Missing sentinel to text + var exception = Assert.ThrowsExactly(() => SpdxRelationshipType.Missing.ToText()); + + // Assert: Verify the exception has the expected message + Assert.AreEqual("Attempt to serialize missing SPDX Relationship Type", exception.Message); + } + /// /// Tests the method for an invalid /// relationship type. diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs index 60c8f5a..27ab418 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs @@ -72,7 +72,7 @@ public void SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual() Assert.IsTrue(SpdxSnippet.Same.Equals(s2, s2)); Assert.IsTrue(SpdxSnippet.Same.Equals(s3, s3)); - // Assert snippets compare correctly + // Assert: snippets compare correctly Assert.IsTrue(SpdxSnippet.Same.Equals(s1, s2)); Assert.IsTrue(SpdxSnippet.Same.Equals(s2, s1)); Assert.IsFalse(SpdxSnippet.Same.Equals(s1, s3)); @@ -80,7 +80,7 @@ public void SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual() Assert.IsFalse(SpdxSnippet.Same.Equals(s2, s3)); Assert.IsFalse(SpdxSnippet.Same.Equals(s3, s2)); - // Assert same snippets have identical hashes + // Assert: same snippets have identical hashes Assert.AreEqual(SpdxSnippet.Same.GetHashCode(s1), SpdxSnippet.Same.GetHashCode(s2)); } @@ -189,7 +189,9 @@ public void SpdxSnippet_Validate_InvalidSnippetId_ReportsIssue() Id = "Invalid_ID", SnippetFromFile = "SPDXRef-File1", SnippetByteStart = 100, - SnippetByteEnd = 200 + SnippetByteEnd = 200, + ConcludedLicense = "MIT", + CopyrightText = "Copyright(c) 2024 DEMA Consulting" }; // Act: Validate the snippet diff --git a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs index 3b2b315..fd9c53b 100644 --- a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs @@ -26,12 +26,22 @@ namespace DemaConsulting.SpdxModel.Tests.Transforms; /// /// Integration tests for the SpdxModel Transform subsystem. /// +/// +/// Integration-scope tests: each test deserializes a real SPDX 2.3 JSON document from +/// an embedded resource and exercises the transform +/// against that document. MSTest is the approved framework for this repository. +/// [TestClass] public class SpdxModelTransformTests { /// /// Tests that a relationship added to an SPDX document persists in the document. /// + /// + /// Happy path: a well-formed relationship whose source and target both exist in the + /// document is added, the relationship count increases by one, and the document + /// remains valid after the transform. + /// [TestMethod] public void SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists() { @@ -66,6 +76,11 @@ public void SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists() /// /// Tests that adding a relationship with an invalid source element ID throws . /// + /// + /// Error path: a source element ID that does not exist in the document causes an + /// to be thrown. The document is expected to remain + /// unmodified because the validation happens before any mutation. + /// [TestMethod] public void SpdxModelTransform_AddRelationship_InvalidSourceId_ThrowsArgumentException() { @@ -91,6 +106,11 @@ public void SpdxModelTransform_AddRelationship_InvalidSourceId_ThrowsArgumentExc /// /// Tests that adding a relationship with an invalid target element ID throws . /// + /// + /// Error path: a target element ID that does not exist in the document, and is neither + /// NOASSERTION nor prefixed with DocumentRef-, causes an + /// to be thrown. + /// [TestMethod] public void SpdxModelTransform_AddRelationship_InvalidTargetId_ThrowsArgumentException() { @@ -116,6 +136,11 @@ public void SpdxModelTransform_AddRelationship_InvalidTargetId_ThrowsArgumentExc /// /// Tests that adding a duplicate relationship enhances the existing entry rather than duplicating it. /// + /// + /// Idempotency path: adding the same relationship a second time merges (enhances) the + /// existing entry rather than appending a duplicate. The relationship count after two + /// adds must equal the count after one add. + /// [TestMethod] public void SpdxModelTransform_AddRelationship_Duplicate_EnhancesExistingRelationship() { @@ -150,6 +175,11 @@ public void SpdxModelTransform_AddRelationship_Duplicate_EnhancesExistingRelatio /// /// Tests that the batch Add with replace=true removes pre-existing matching relationships. /// + /// + /// Replace-mode path: the batch overload with replace: true removes any + /// existing relationships between the same pair of elements before adding the new ones. + /// This allows changing the relationship type between a fixed pair of elements. + /// [TestMethod] public void SpdxModelTransform_AddRelationship_Replace_RemovesPreExistingRelationships() { @@ -197,6 +227,10 @@ public void SpdxModelTransform_AddRelationship_Replace_RemovesPreExistingRelatio /// /// Tests that the batch Add with multiple relationships adds all of them. /// + /// + /// Batch path: the batch overload with two distinct relationships adds both in a single + /// call, increasing the relationship count by exactly two. + /// [TestMethod] public void SpdxModelTransform_AddRelationship_BatchMultiple_AddsAllRelationships() { @@ -231,6 +265,11 @@ public void SpdxModelTransform_AddRelationship_BatchMultiple_AddsAllRelationship /// /// Tests that a relationship with NOASSERTION as the target element is accepted as valid. /// + /// + /// Boundary path: NOASSERTION is a valid target element value (it means the + /// related element is intentionally unspecified). The transform must accept it without + /// throwing, regardless of whether any element with ID "NOASSERTION" exists. + /// [TestMethod] public void SpdxModelTransform_AddRelationship_NoAssertionTarget_AddsRelationship() { @@ -262,6 +301,11 @@ public void SpdxModelTransform_AddRelationship_NoAssertionTarget_AddsRelationshi /// /// Tests that a relationship with a DocumentRef- prefixed target is accepted as valid. /// + /// + /// Boundary path: a target element prefixed with DocumentRef- refers to an + /// element in an external document. The transform must accept it without throwing, + /// regardless of whether the external document reference resolves locally. + /// [TestMethod] public void SpdxModelTransform_AddRelationship_DocumentRefTarget_AddsRelationship() { From ef5e941041d881388c5b92938ec7a35d31c18222 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Tue, 26 May 2026 20:17:48 -0400 Subject: [PATCH 03/12] docs/tests: Cycle 4 formal review fixes across all 5 groups Group 1 fixes: - spdx-model.md (design): Added 7 missing Mermaid edges - spdx-model.md (verification): Added 2 missing test scenarios - spdx-annotation/external-document-reference/external-reference/extracted-licensing-info.md: Added/expanded SpdxHelpers dependencies - spdx-element.md (design): Fixed SpdxRefRegex to protected static readonly - ots/mstest.yaml: Fixed 3 stale test name references - spdx-helpers.yaml: Fixed 1 stale test name reference - spdx-model.yaml: Linked 14 orphaned requirements; fixed 3 semantic misplacements - io.yaml: Linked SpdxModel-IO-Constants Group 2 fixes: - Spdx2JsonDeserializerTests.cs: Fixed missing AAA colons - Spdx2JsonDeserializeAnnotation.cs: Fixed wrong see-cref reference - Spdx2JsonDeserialize23.cs: Renamed variable json22Example to json23Example - Spdx2JsonSerializePackageVerificationCode.cs: Renamed test method to match implementation - spdx-2-json-serializer.yaml/md: Updated test ref and scenario heading - transform.yaml: Fixed cross-level test linkage - SpdxRelationshipsTests.cs: Added remarks to class and all 10 methods Group 3 fixes: - spdx-element.md (verification): Removed redundant trailing test-name sentences - SpdxElementTests.cs: Added remarks; fixed Act comment - SpdxChecksumTests.cs: Added AAA labels; differentiated duplicate summaries Group 4 fixes: - SpdxExtractedLicensingInfo.cs: Fixed remarks ordering on 3 methods - spdx-extracted-licensing-info.md (verification): Updated 2 stale 3-part scenario names - SpdxFileType.cs: Improved exception tag descriptions on FromText/ToText - spdx-file.md (design): Fixed Enhance postcondition fitness semantics; fixed param names - SpdxHelpers.cs: Improved EnhanceString summary and returns documentation - spdx-helpers.md (design): Fixed parameter name candidates to values - SpdxExternalReferenceTests.cs: Renamed 3 ReturnsNull tests to ThrowsInvalidOperationException; renamed 3 tests from 3-part to 4-part names - spdx-external-reference.yaml/md: Updated all 6 renamed test references - SpdxLicenseElementTests.cs: New file with 5 tests covering LicenseElement Enhance fitness selection - spdx-license-element.yaml: Added 5 new test method references Group 5 fixes: - SpdxRelationshipType.cs: Updated FromText remarks count from 44 to 45 - spdx-snippet.md (design): Fixed static Enhance parameter names array/others - SpdxSnippetTests.cs: Fixed assertion style; fixed inaccurate Arrange comment - spdx-external-reference.md (design): Documented PACKAGE_MANAGER underscore alias Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/design/spdx-model/spdx-annotation.md | 2 +- docs/design/spdx-model/spdx-element.md | 2 +- .../spdx-external-document-reference.md | 1 + .../spdx-model/spdx-external-reference.md | 3 + .../spdx-extracted-licensing-info.md | 2 +- docs/design/spdx-model/spdx-file.md | 7 +- docs/design/spdx-model/spdx-helpers.md | 2 +- docs/design/spdx-model/spdx-model.md | 7 + docs/design/spdx-model/spdx-snippet.md | 2 +- docs/reqstream/ots/mstest.yaml | 6 +- docs/reqstream/spdx-model/io/io.yaml | 1 + .../spdx-model/io/spdx-2-json-serializer.yaml | 2 +- .../spdx-model/spdx-external-reference.yaml | 12 +- docs/reqstream/spdx-model/spdx-helpers.yaml | 2 +- .../spdx-model/spdx-license-element.yaml | 5 + docs/reqstream/spdx-model/spdx-model.yaml | 24 +- .../spdx-model/transform/transform.yaml | 2 +- .../spdx-model/io/spdx-2-json-serializer.md | 4 +- docs/verification/spdx-model/spdx-element.md | 2 - .../spdx-model/spdx-external-reference.md | 24 +- .../spdx-extracted-licensing-info.md | 8 +- docs/verification/spdx-model/spdx-model.md | 10 + .../SpdxExtractedLicensingInfo.cs | 10 +- src/DemaConsulting.SpdxModel/SpdxFileType.cs | 4 +- src/DemaConsulting.SpdxModel/SpdxHelpers.cs | 8 +- .../SpdxRelationshipType.cs | 2 +- .../IO/Spdx2JsonDeserialize23.cs | 4 +- .../IO/Spdx2JsonDeserializeAnnotation.cs | 2 +- .../IO/Spdx2JsonDeserializerTests.cs | 4 +- ...dx2JsonSerializePackageVerificationCode.cs | 2 +- .../SpdxChecksumTests.cs | 14 +- .../SpdxElementTests.cs | 12 +- .../SpdxExternalReferenceTests.cs | 12 +- .../SpdxLicenseElementTests.cs | 245 ++++++++++++++++++ .../SpdxSnippetTests.cs | 4 +- .../Transforms/SpdxRelationshipsTests.cs | 64 +++++ 36 files changed, 441 insertions(+), 76 deletions(-) create mode 100644 test/DemaConsulting.SpdxModel.Tests/SpdxLicenseElementTests.cs diff --git a/docs/design/spdx-model/spdx-annotation.md b/docs/design/spdx-model/spdx-annotation.md index e6a7c31..d14db94 100644 --- a/docs/design/spdx-model/spdx-annotation.md +++ b/docs/design/spdx-model/spdx-annotation.md @@ -68,7 +68,7 @@ issues. No exceptions are thrown by `DeepCopy` or `Enhance`. - **SpdxElement** — base class providing the `Id` property. - **SpdxAnnotationType** — enumeration for annotation type values. -- **SpdxHelpers** — `IsValidSpdxDateTime` used in `Validate`. +- **SpdxHelpers** — `IsValidSpdxDateTime` used in `Validate`; `EnhanceString` used in `Enhance`. ### SpdxAnnotationType diff --git a/docs/design/spdx-model/spdx-element.md b/docs/design/spdx-model/spdx-element.md index e644e2a..8c6fa8a 100644 --- a/docs/design/spdx-model/spdx-element.md +++ b/docs/design/spdx-model/spdx-element.md @@ -14,7 +14,7 @@ a document. **NoAssertion**: `const string` — The sentinel value `"NOASSERTION"` used by optional fields to indicate that the value was intentionally omitted or is not known. -**SpdxRefRegex**: `protected static Regex` — Pre-compiled regular expression that validates +**SpdxRefRegex**: `protected static readonly Regex` — Pre-compiled regular expression that validates the `SPDXRef-…` format; used by subclass `Validate` methods. Matches the full pattern `^SPDXRef-[a-zA-Z0-9.-]+$`. The 100 ms timeout is a ReDoS protection measure against pathological input strings from untrusted SPDX sources. diff --git a/docs/design/spdx-model/spdx-external-document-reference.md b/docs/design/spdx-model/spdx-external-document-reference.md index 688c9cf..ed5c73f 100644 --- a/docs/design/spdx-model/spdx-external-document-reference.md +++ b/docs/design/spdx-model/spdx-external-document-reference.md @@ -59,6 +59,7 @@ static merge method. ### Dependencies - **SpdxChecksum** — integrity checksum for the referenced document. +- **SpdxHelpers** — `EnhanceString` used in `Enhance`. ### Callers diff --git a/docs/design/spdx-model/spdx-external-reference.md b/docs/design/spdx-model/spdx-external-reference.md index 684604b..3dff70c 100644 --- a/docs/design/spdx-model/spdx-external-reference.md +++ b/docs/design/spdx-model/spdx-external-reference.md @@ -61,6 +61,7 @@ thrown by `DeepCopy`, `Enhance`, or the static merge method. ### Dependencies - **SpdxReferenceCategory** — enumeration of supported reference categories. +- **SpdxHelpers** — `EnhanceString` used in `Enhance`. ### SpdxReferenceCategory @@ -89,6 +90,8 @@ value. - *Postconditions*: none. - *Exceptions*: `InvalidOperationException` — thrown when `category` is not a recognized SPDX reference category string. +- *Note*: `PACKAGE_MANAGER` (with underscore) is accepted as a backward-compatibility alias for + `PACKAGE-MANAGER` (with hyphen). No equivalent underscore alias exists for any other category. #### ToText diff --git a/docs/design/spdx-model/spdx-extracted-licensing-info.md b/docs/design/spdx-model/spdx-extracted-licensing-info.md index e53c88d..4a7a398 100644 --- a/docs/design/spdx-model/spdx-extracted-licensing-info.md +++ b/docs/design/spdx-model/spdx-extracted-licensing-info.md @@ -59,7 +59,7 @@ thrown by `DeepCopy`, `Enhance`, or the static merge method. ### Dependencies -N/A - no external dependencies beyond base .NET BCL types. +- **SpdxHelpers** — `EnhanceString` used in `Enhance`. ### Callers diff --git a/docs/design/spdx-model/spdx-file.md b/docs/design/spdx-model/spdx-file.md index 4131bc6..d9e4328 100644 --- a/docs/design/spdx-model/spdx-file.md +++ b/docs/design/spdx-model/spdx-file.md @@ -41,11 +41,14 @@ key when merging file arrays. - *Parameters*: `SpdxFile other` — source of additional field values. - *Returns*: `void` - *Preconditions*: none. -- *Postconditions*: Empty fields and empty arrays in this instance are populated from `other`. +- *Postconditions*: Fields in this instance are updated when the current value has lower fitness + than the source value, following the hierarchy: concrete value > NOASSERTION > empty string > + null. Array fields are merged by concatenation and deduplication (AttributionText) or + identity-match and append (other arrays). **Enhance (static array merge)**: Merges two file arrays, matching on `FileName`. -- *Parameters*: `SpdxFile[] base`, `SpdxFile[] additions`. +- *Parameters*: `SpdxFile[] array`, `SpdxFile[] others`. - *Returns*: `SpdxFile[]` — merged array. - *Preconditions*: none. - *Postconditions*: Matching entries are enhanced; new entries are appended. diff --git a/docs/design/spdx-model/spdx-helpers.md b/docs/design/spdx-model/spdx-helpers.md index 368cc8a..42384dc 100644 --- a/docs/design/spdx-model/spdx-helpers.md +++ b/docs/design/spdx-model/spdx-helpers.md @@ -23,7 +23,7 @@ format required by SPDX. **EnhanceString**: Returns the highest-fitness string from the supplied candidates. -- *Parameters*: `params string?[] candidates` — ordered list of candidate values. +- *Parameters*: `params string?[] values` — ordered list of candidate values. - *Returns*: `string?` — the best candidate: concrete (non-empty, non-NOASSERTION) > `NOASSERTION` > empty string > `null`. - *Preconditions*: none. diff --git a/docs/design/spdx-model/spdx-model.md b/docs/design/spdx-model/spdx-model.md index 4f5f9b7..970f34b 100644 --- a/docs/design/spdx-model/spdx-model.md +++ b/docs/design/spdx-model/spdx-model.md @@ -46,8 +46,15 @@ flowchart TD Spdx2JsonSerializer --> SpdxConstants SpdxRelationships --> SpdxDocument SpdxRelationships --> SpdxRelationship + SpdxDocument --> SpdxPackage + SpdxDocument --> SpdxFile + SpdxDocument --> SpdxSnippet + SpdxDocument --> SpdxRelationship + SpdxDocument --> SpdxAnnotation + SpdxDocument --> SpdxExternalDocumentReference SpdxDocument --> SpdxCreationInformation SpdxDocument --> SpdxExtractedLicensingInfo + SpdxFile --> SpdxChecksum SpdxPackage --> SpdxChecksum SpdxPackage --> SpdxExternalReference SpdxPackage --> SpdxPackageVerificationCode diff --git a/docs/design/spdx-model/spdx-snippet.md b/docs/design/spdx-model/spdx-snippet.md index 4fc27ab..20bd6fd 100644 --- a/docs/design/spdx-model/spdx-snippet.md +++ b/docs/design/spdx-model/spdx-snippet.md @@ -46,7 +46,7 @@ the file, enabling granular compliance tracking for reused code segments. **Enhance (static array merge)**: Merges two snippet arrays by matching on file SPDX ID and byte range. -- *Parameters*: `SpdxSnippet[] base`, `SpdxSnippet[] additions`. +- *Parameters*: `SpdxSnippet[] array`, `SpdxSnippet[] others`. - *Returns*: `SpdxSnippet[]` — merged array. - *Preconditions*: none. - *Postconditions*: Matching entries are enhanced; new entries are appended. diff --git a/docs/reqstream/ots/mstest.yaml b/docs/reqstream/ots/mstest.yaml index fbc8b8f..6712972 100644 --- a/docs/reqstream/ots/mstest.yaml +++ b/docs/reqstream/ots/mstest.yaml @@ -15,6 +15,6 @@ sections: framework is functioning correctly. tags: [ots] tests: - - Spdx2JsonDeserializer_Deserialize_ValidSpdx22JsonReturnsExpectedDocument - - Spdx2JsonDeserializer_Deserialize_ValidSpdx23JsonReturnsExpectedDocument - - Spdx2JsonSerializer_SerializeDocument_CorrectResults + - Spdx2JsonDeserializer_Deserialize_ValidSpdx22Json_ReturnsExpectedDocument + - Spdx2JsonDeserializer_Deserialize_ValidSpdx23Json_ReturnsExpectedDocument + - Spdx2JsonSerializer_SerializeDocument_ValidInput_CorrectResults diff --git a/docs/reqstream/spdx-model/io/io.yaml b/docs/reqstream/spdx-model/io/io.yaml index 9116dc4..ae192c9 100644 --- a/docs/reqstream/spdx-model/io/io.yaml +++ b/docs/reqstream/spdx-model/io/io.yaml @@ -19,6 +19,7 @@ sections: - SpdxModel-IO-Spdx2JsonDeserializer-Deserialize22Json - SpdxModel-IO-Spdx2JsonDeserializer-Deserialize23Json - SpdxModel-IO-Spdx2JsonDeserializer-DeserializeElements + - SpdxModel-IO-Constants tests: - SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidDocument - SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidDocument diff --git a/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml b/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml index 1aa31f6..3ca51fc 100644 --- a/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml +++ b/docs/reqstream/spdx-model/io/spdx-2-json-serializer.yaml @@ -46,7 +46,7 @@ sections: - Spdx2JsonSerializer_SerializeFiles_ValidInput_CorrectResults - Spdx2JsonSerializer_SerializePackage_ValidInput_CorrectResults - Spdx2JsonSerializer_SerializePackages_ValidInput_CorrectResults - - Spdx2JsonSerializer_SerializePackageVerificationCode_ValidInput_CorrectResults + - Spdx2JsonSerializer_SerializeVerificationCode_ValidInput_CorrectResults - Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults - Spdx2JsonSerializer_SerializeRelationships_ValidInput_CorrectResults - Spdx2JsonSerializer_SerializeSnippet_ValidInput_CorrectResults diff --git a/docs/reqstream/spdx-model/spdx-external-reference.yaml b/docs/reqstream/spdx-model/spdx-external-reference.yaml index dc7a8c2..27ee42b 100644 --- a/docs/reqstream/spdx-model/spdx-external-reference.yaml +++ b/docs/reqstream/spdx-model/spdx-external-reference.yaml @@ -15,14 +15,14 @@ sections: registries, vulnerability databases, and documentation. This enriches SBOMs with contextual information from authoritative sources. tests: - - SpdxExternalReference_SameComparer_ComparesCorrectly - - SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance - - SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxExternalReference_SameComparer_EqualAndUnequalInstances_ComparesCorrectly + - SpdxExternalReference_DeepCopy_WithAllFields_CreatesEqualButDistinctInstance + - SpdxExternalReference_Enhance_WithMatchingAndNewEntries_MergesCorrectly - SpdxExternalReference_Validate_InvalidCategory_ReportsIssue - SpdxExternalReference_Validate_InvalidType_ReportsIssue - SpdxExternalReference_Validate_InvalidLocator_ReportsIssue - SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly - - SpdxReferenceCategoryExtensions_FromText_InvalidInput_ReturnsNull + - SpdxReferenceCategoryExtensions_FromText_InvalidInput_ThrowsInvalidOperationException - SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrectly - - SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull - - SpdxReferenceCategoryExtensions_ToText_MissingCategory_ReturnsNull + - SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ThrowsInvalidOperationException + - SpdxReferenceCategoryExtensions_ToText_MissingCategory_ThrowsInvalidOperationException diff --git a/docs/reqstream/spdx-model/spdx-helpers.yaml b/docs/reqstream/spdx-model/spdx-helpers.yaml index 5554023..94c811b 100644 --- a/docs/reqstream/spdx-model/spdx-helpers.yaml +++ b/docs/reqstream/spdx-model/spdx-helpers.yaml @@ -18,7 +18,7 @@ sections: Absent or empty date-time values (null, empty string) shall be treated as not-set and shall be considered valid. tests: - - SpdxCreationInformation_Validate_InvalidCreatedDate + - SpdxCreationInformation_Validate_InvalidCreatedDate_ReportsIssue - SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue - SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue - SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue diff --git a/docs/reqstream/spdx-model/spdx-license-element.yaml b/docs/reqstream/spdx-model/spdx-license-element.yaml index fbf7477..d196088 100644 --- a/docs/reqstream/spdx-model/spdx-license-element.yaml +++ b/docs/reqstream/spdx-model/spdx-license-element.yaml @@ -56,3 +56,8 @@ sections: - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly - SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly - SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly + - SpdxLicenseElement_Enhance_EmptyAndNullFields_ReplacedByConcreteValues + - SpdxLicenseElement_Enhance_NoAssertionFields_ReplacedByConcreteValues + - SpdxLicenseElement_Enhance_ConcreteFields_NotReplacedBySecondaryValues + - SpdxLicenseElement_Enhance_AttributionText_MergedByDeduplication + - SpdxLicenseElement_Enhance_Annotations_MergedByIdentityAndAppend diff --git a/docs/reqstream/spdx-model/spdx-model.yaml b/docs/reqstream/spdx-model/spdx-model.yaml index 9e17d8b..4a87b6b 100644 --- a/docs/reqstream/spdx-model/spdx-model.yaml +++ b/docs/reqstream/spdx-model/spdx-model.yaml @@ -18,10 +18,13 @@ sections: - SpdxModel_ReadSpdxJson_Spdx23Example_ParsesSuccessfully children: - SpdxModel-Data-Document-Ntia + - SpdxModel-Data-Document-ElementQuery - SpdxModel-Data-ExternalDocumentReferences - SpdxModel-Data-ExternalReferences - SpdxModel-Data-ExtractedLicensingInformation - SpdxModel-Data-Snippet-DataModel + - SpdxModel-Data-LicenseElement + - SpdxModel-Data-RelationshipType-Conversion - id: SpdxModel-Data-Helpers title: The library shall provide helper utilities for SPDX data processing. tags: @@ -34,9 +37,12 @@ sections: children: - SpdxModel-Data-Helpers-DateTime - SpdxModel-Data-Helpers-EnhanceString - - SpdxModel-Data-LicenseElement - SpdxModel-Data-LicenseElement-Enhance - - SpdxModel-Data-PackageVerificationCode-Validate + - SpdxModel-Data-Package-Enhance + - SpdxModel-Data-PackageVerificationCode-Enhance + - SpdxModel-Data-Checksum-Enhance + - SpdxModel-Data-Relationship-Enhance + - SpdxModel-Data-Snippet-Enhance - title: Data Model requirements: @@ -64,6 +70,10 @@ sections: children: - SpdxModel-Data-Document - SpdxModel-Data-Package-DeepCopy + - SpdxModel-Data-PackageVerificationCode-DeepCopy + - SpdxModel-Data-Checksum-DeepCopy + - SpdxModel-Data-Relationship-DeepCopy + - SpdxModel-Data-Snippet-DeepCopy - SpdxModel-Data-Files tests: - SpdxModel_ReadSpdxJson_Spdx23Example_DeepCopyProducesEquivalentDocument @@ -93,6 +103,8 @@ sections: children: - SpdxModel-Data-Document - SpdxModel-Data-Package-Compare + - SpdxModel-Data-PackageVerificationCode-Compare + - SpdxModel-Data-Checksum-Compare - SpdxModel-Data-Files - SpdxModel-Data-Relationship-Compare - SpdxModel-Data-Snippet-DataModel @@ -112,13 +124,15 @@ sections: children: - SpdxModel-Data-Document-Validate - SpdxModel-Data-Annotations - - SpdxModel-Data-Checksum-Compare - - SpdxModel-Data-Checksum-DeepCopy - - SpdxModel-Data-Checksum-Enhance - SpdxModel-Data-Checksum-Validate - SpdxModel-Data-Checksum-FromText - SpdxModel-Data-Checksum-ToText - SpdxModel-Data-CreationInformation + - SpdxModel-Data-Package-Validate + - SpdxModel-Data-PackageVerificationCode-Validate + - SpdxModel-Data-Package-ValidateNtia + - SpdxModel-Data-Relationship-Validate + - SpdxModel-Data-Snippet-Validate tests: - SpdxModel_ReadSpdxJson_Spdx22Example_PassesValidation - SpdxModel_ReadSpdxJson_Spdx23Example_PassesValidation diff --git a/docs/reqstream/spdx-model/transform/transform.yaml b/docs/reqstream/spdx-model/transform/transform.yaml index b7027d9..4f4f33c 100644 --- a/docs/reqstream/spdx-model/transform/transform.yaml +++ b/docs/reqstream/spdx-model/transform/transform.yaml @@ -20,4 +20,4 @@ sections: - SpdxModel-Transform-RelationshipUtilities-Validate - SpdxModel-Transform-RelationshipUtilities-Atomicity tests: - - SpdxModel_Transform_AddRelationship_IsObservableThroughDocumentModel + - SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists diff --git a/docs/verification/spdx-model/io/spdx-2-json-serializer.md b/docs/verification/spdx-model/io/spdx-2-json-serializer.md index 4b86681..98ddffd 100644 --- a/docs/verification/spdx-model/io/spdx-2-json-serializer.md +++ b/docs/verification/spdx-model/io/spdx-2-json-serializer.md @@ -102,10 +102,10 @@ This scenario is tested by `Spdx2JsonSerializer_SerializePackage_ValidInput_Corr SpdxPackage instances is serialized to the expected JSON array. This scenario is tested by `Spdx2JsonSerializer_SerializePackages_ValidInput_CorrectResults`. -**Spdx2JsonSerializer_SerializePackageVerificationCode_ValidInput_CorrectResults**: Verifies that an +**Spdx2JsonSerializer_SerializeVerificationCode_ValidInput_CorrectResults**: Verifies that an SpdxPackageVerificationCode is serialized to the expected JSON structure. This scenario is tested by -`Spdx2JsonSerializer_SerializePackageVerificationCode_ValidInput_CorrectResults`. +`Spdx2JsonSerializer_SerializeVerificationCode_ValidInput_CorrectResults`. **Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults**: Verifies that a single SpdxRelationship is serialized to the expected JSON structure with all relationship fields diff --git a/docs/verification/spdx-model/spdx-element.md b/docs/verification/spdx-model/spdx-element.md index 5e6f33a..0907385 100644 --- a/docs/verification/spdx-model/spdx-element.md +++ b/docs/verification/spdx-model/spdx-element.md @@ -19,12 +19,10 @@ All automated tests pass with zero failures. **SpdxElement_Id_ValidFormat_PassesValidation**: Verifies that an element with a properly formatted SPDX-ID (matching the SPDXRef- prefix pattern) passes validation without reporting any issues. -This scenario is tested by `SpdxElement_Id_ValidFormat_PassesValidation`. **SpdxElement_Id_InvalidFormat_ReportsValidationIssue**: Verifies that an element with a malformed SPDX-ID (missing the SPDXRef- prefix or containing invalid characters) is reported as a validation issue. -This scenario is tested by `SpdxElement_Id_InvalidFormat_ReportsValidationIssue`. ### Methods Without Direct Test Scenarios diff --git a/docs/verification/spdx-model/spdx-external-reference.md b/docs/verification/spdx-model/spdx-external-reference.md index f4bed53..d15a977 100644 --- a/docs/verification/spdx-model/spdx-external-reference.md +++ b/docs/verification/spdx-model/spdx-external-reference.md @@ -18,20 +18,20 @@ All automated tests pass with zero failures. ### Test Scenarios -**SpdxExternalReference_SameComparer_ComparesCorrectly**: Verifies that SameComparer +**SpdxExternalReference_SameComparer_EqualAndUnequalInstances_ComparesCorrectly**: Verifies that SameComparer correctly identifies two SpdxExternalReference instances as equal when all fields match and as distinct when any field differs. -This scenario is tested by `SpdxExternalReference_SameComparer_ComparesCorrectly`. +This scenario is tested by `SpdxExternalReference_SameComparer_EqualAndUnequalInstances_ComparesCorrectly`. -**SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance**: Verifies that a deep copy +**SpdxExternalReference_DeepCopy_WithAllFields_CreatesEqualButDistinctInstance**: Verifies that a deep copy produces a new SpdxExternalReference with equal field values but a distinct object reference. -This scenario is tested by `SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance`. +This scenario is tested by `SpdxExternalReference_DeepCopy_WithAllFields_CreatesEqualButDistinctInstance`. -**SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly**: Verifies that Enhance +**SpdxExternalReference_Enhance_WithMatchingAndNewEntries_MergesCorrectly**: Verifies that Enhance merges external reference data by adding missing fields from the source while preserving existing values on the target. This scenario is tested by -`SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly`. +`SpdxExternalReference_Enhance_WithMatchingAndNewEntries_MergesCorrectly`. **SpdxExternalReference_Validate_InvalidCategory_ReportsIssue**: Verifies that validation reports an issue when the reference category is set to an unrecognized value. @@ -49,20 +49,20 @@ This scenario is tested by `SpdxExternalReference_Validate_InvalidLocator_Report recognized reference category string to its corresponding enum value. This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly`. -**SpdxReferenceCategoryExtensions_FromText_InvalidInput_ReturnsNull**: Verifies that `FromText` throws +**SpdxReferenceCategoryExtensions_FromText_InvalidInput_ThrowsInvalidOperationException**: Verifies that `FromText` throws `InvalidOperationException` with a message identifying the unsupported value when given an unrecognized reference category string. -This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_InvalidInput_ReturnsNull`. +This scenario is tested by `SpdxReferenceCategoryExtensions_FromText_InvalidInput_ThrowsInvalidOperationException`. **SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrectly**: Verifies that ToText correctly converts a recognized reference category enum value to its SPDX text representation. This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrectly`. -**SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull**: Verifies that ToText throws +**SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ThrowsInvalidOperationException**: Verifies that ToText throws `InvalidOperationException` with the unsupported-category message when called with an unrecognized enum value. -This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull`. +This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ThrowsInvalidOperationException`. -**SpdxReferenceCategoryExtensions_ToText_MissingCategory_ReturnsNull**: Verifies that `ToText` throws +**SpdxReferenceCategoryExtensions_ToText_MissingCategory_ThrowsInvalidOperationException**: Verifies that `ToText` throws `InvalidOperationException` with a specific message when called with `SpdxReferenceCategory.Missing`. -This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_MissingCategory_ReturnsNull`. +This scenario is tested by `SpdxReferenceCategoryExtensions_ToText_MissingCategory_ThrowsInvalidOperationException`. diff --git a/docs/verification/spdx-model/spdx-extracted-licensing-info.md b/docs/verification/spdx-model/spdx-extracted-licensing-info.md index 2b2ad7e..5ff9813 100644 --- a/docs/verification/spdx-model/spdx-extracted-licensing-info.md +++ b/docs/verification/spdx-model/spdx-extracted-licensing-info.md @@ -39,10 +39,10 @@ This scenario is tested by extracted licensing info with both LicenseId and ExtractedText populated returns no issues. This scenario is tested by `SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues`. -**SpdxExtractedLicensingInfo_Validate_InvalidLicenseId**: Verifies that validation reports an +**SpdxExtractedLicensingInfo_Validate_InvalidLicenseId_ReportsIssue**: Verifies that validation reports an issue when the LicenseId field is empty. -This scenario is tested by `SpdxExtractedLicensingInfo_Validate_InvalidLicenseId`. +This scenario is tested by `SpdxExtractedLicensingInfo_Validate_InvalidLicenseId_ReportsIssue`. -**SpdxExtractedLicensingInfo_Validate_InvalidExtractedText**: Verifies that validation reports +**SpdxExtractedLicensingInfo_Validate_InvalidExtractedText_ReportsIssue**: Verifies that validation reports an issue when the extracted license text field is missing or empty. -This scenario is tested by `SpdxExtractedLicensingInfo_Validate_InvalidExtractedText`. +This scenario is tested by `SpdxExtractedLicensingInfo_Validate_InvalidExtractedText_ReportsIssue`. diff --git a/docs/verification/spdx-model/spdx-model.md b/docs/verification/spdx-model/spdx-model.md index 08077c2..d727de7 100644 --- a/docs/verification/spdx-model/spdx-model.md +++ b/docs/verification/spdx-model/spdx-model.md @@ -54,3 +54,13 @@ validation error messages. **SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNullable**: Verifies that required fields on key SPDX data model types are non-nullable string types with default empty values, and that optional fields are nullable. + +**SpdxModel_Helpers_DateTimeValidation_IsObservableThroughDocumentModel**: Verifies that the +date-time validation utility (`SpdxHelpers.IsValidSpdxDateTime`) is exercised through the +document model by confirming that an invalid creation date is caught by document-level +validation. Linked from `SpdxModel-Data-Helpers`. + +**SpdxModel_Transform_AddRelationship_IsObservableThroughDocumentModel**: Verifies that the +`AddRelationship` transform utility correctly adds a new relationship to an SPDX document and +that the addition is observable through the document model's relationship collection. Linked +from `SpdxModel-Transform`. diff --git a/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs b/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs index 6e74f00..3c47130 100644 --- a/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs +++ b/src/DemaConsulting.SpdxModel/SpdxExtractedLicensingInfo.cs @@ -103,11 +103,11 @@ public SpdxExtractedLicensingInfo DeepCopy() /// /// Enhance missing fields in the extracted licensing info /// - /// Other extracted licensing info to enhance with /// /// Populates LicenseId, ExtractedText, Name, and Comment using fitness-based selection. /// CrossReferences are merged by concatenation and deduplication. /// + /// Other extracted licensing info to enhance with public void Enhance(SpdxExtractedLicensingInfo other) { // Populate the license-id field if missing @@ -129,13 +129,13 @@ public void Enhance(SpdxExtractedLicensingInfo other) /// /// Enhance missing extracted licensing info in array /// - /// Array to enhance - /// Other array to enhance with - /// Updated array /// /// Matches existing entries by ExtractedText (via the Same comparer) and enhances them; /// entries with no match are appended as deep copies. /// + /// Array to enhance + /// Other array to enhance with + /// Updated array public static SpdxExtractedLicensingInfo[] Enhance(SpdxExtractedLicensingInfo[] array, SpdxExtractedLicensingInfo[] others) { @@ -166,11 +166,11 @@ public static SpdxExtractedLicensingInfo[] Enhance(SpdxExtractedLicensingInfo[] /// /// Perform validation of information /// - /// List to populate with issues /// /// Validates that LicenseId is non-empty and ExtractedText is non-empty. /// Issues are appended to ; no exceptions are thrown. /// + /// List to populate with issues public void Validate(List issues) { // Validate Extracted License ID Field diff --git a/src/DemaConsulting.SpdxModel/SpdxFileType.cs b/src/DemaConsulting.SpdxModel/SpdxFileType.cs index d703fc0..faf67c0 100644 --- a/src/DemaConsulting.SpdxModel/SpdxFileType.cs +++ b/src/DemaConsulting.SpdxModel/SpdxFileType.cs @@ -137,7 +137,7 @@ public static class SpdxFileTypeExtensions /// /// File Type text /// SpdxFileType - /// on error + /// Thrown when does not match any known SPDX file type string. public static SpdxFileType FromText(string fileType) { return fileType.ToUpperInvariant() switch @@ -166,7 +166,7 @@ public static SpdxFileType FromText(string fileType) /// /// SpdxFileType /// File Type text - /// on error + /// Thrown when is not a supported enum value. public static string ToText(this SpdxFileType fileType) { return fileType switch diff --git a/src/DemaConsulting.SpdxModel/SpdxHelpers.cs b/src/DemaConsulting.SpdxModel/SpdxHelpers.cs index 61298f2..8c55e92 100644 --- a/src/DemaConsulting.SpdxModel/SpdxHelpers.cs +++ b/src/DemaConsulting.SpdxModel/SpdxHelpers.cs @@ -78,7 +78,7 @@ internal static bool IsValidSpdxDateTime(string? value) } /// - /// This method picks the best string. + /// Returns the highest-fitness string from the supplied candidates. /// /// /// Fitness ranking: null=0, empty string=1, NOASSERTION=2, any other concrete value=3. @@ -86,7 +86,11 @@ internal static bool IsValidSpdxDateTime(string? value) /// array is empty), returns null. /// /// String values to pick from - /// Best string + /// + /// The highest-fitness candidate, selected by: concrete value (non-empty, non-NOASSERTION) + /// > NOASSERTION > empty string > null. Returns null when + /// all candidates are null or the array is empty. + /// internal static string? EnhanceString(params string?[] values) { // Return the value with the highest fitness diff --git a/src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs b/src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs index 4870114..0c568af 100644 --- a/src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs +++ b/src/DemaConsulting.SpdxModel/SpdxRelationshipType.cs @@ -417,7 +417,7 @@ public static class SpdxRelationshipTypeExtensions /// /// /// Comparison is case-insensitive. An empty or null input maps to . - /// All 44 SPDX 2.x relationship type tokens are recognized. + /// All 45 SPDX 2.x relationship type tokens are recognized. /// /// Relationship Type text /// SpdxRelationshipType diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs index 75a9e0c..f855924 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs @@ -44,11 +44,11 @@ public class Spdx2JsonDeserialize23 public void Spdx2JsonDeserializer_Deserialize_ValidSpdx23Json_ReturnsExpectedDocument() { // Arrange: Get the SPDX 2.3 JSON example from embedded resources - var json22Example = SpdxTestHelpers.GetEmbeddedResource( + var json23Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); // Act: Deserialize the JSON document - var doc = Spdx2JsonDeserializer.Deserialize(json22Example); + var doc = Spdx2JsonDeserializer.Deserialize(json23Example); // Assert: Verify that the document is valid Assert.IsNotNull(doc); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs index 61c284c..9e326fb 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs @@ -24,7 +24,7 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// -/// Tests for deserializing SPDX annotations to classes. +/// Tests for deserializing SPDX annotations to classes. /// /// /// Exercises deserialization of SPDX annotation elements using MSTest as the approved diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs index 4e47926..db7f82c 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs @@ -43,10 +43,10 @@ public class Spdx2JsonDeserializerTests [TestMethod] public void Spdx2JsonDeserializer_Deserialize_MalformedJson_ThrowsJsonException() { - // Arrange + // Arrange: const string malformedJson = "{ not valid json"; - // Act / Assert + // Act / Assert: try { Spdx2JsonDeserializer.Deserialize(malformedJson); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs index 4daead2..f25dbc5 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs @@ -32,7 +32,7 @@ public class Spdx2JsonSerializePackageVerificationCode /// Tests serializing a package verification code. /// [TestMethod] - public void Spdx2JsonSerializer_SerializePackageVerificationCode_ValidInput_CorrectResults() + public void Spdx2JsonSerializer_SerializeVerificationCode_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxPackageVerificationCode object var code = new SpdxPackageVerificationCode diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs index 15dedc7..3655599 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs @@ -62,7 +62,7 @@ public void SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquali Value = "624c1abb3664f4b35547e7c73864ad24" }; - // Assert: Verify checksums compare to themselves + // Act / Assert: Verify checksums compare to themselves Assert.IsTrue(SpdxChecksum.Same.Equals(c1, c1)); Assert.IsTrue(SpdxChecksum.Same.Equals(c2, c2)); Assert.IsTrue(SpdxChecksum.Same.Equals(c3, c3)); @@ -234,7 +234,7 @@ public void SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue( } /// - /// Tests the method. + /// Tests that FromText maps every known SPDX algorithm string to the correct enum value. /// /// /// Exercises every known algorithm string defined by the SPDX 2.x specification, plus @@ -246,7 +246,7 @@ public void SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_Retur { // Arrange: Known algorithm strings are implicit in the Act/Assert pairs below - // Assert: Verify each known algorithm string maps to the correct enum value + // Act / Assert: Verify each known algorithm string maps to the correct enum value Assert.AreEqual(SpdxChecksumAlgorithm.Missing, SpdxChecksumAlgorithmExtensions.FromText("")); Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("SHA1")); Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("sha1")); @@ -270,7 +270,7 @@ public void SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_Retur } /// - /// Tests the method. + /// Tests that FromText throws InvalidOperationException for an unrecognized algorithm string. /// /// /// Passes the unrecognized string "unknown" to confirm that @@ -291,7 +291,7 @@ public void SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_Thro } /// - /// Tests the method. + /// Tests that ToText returns the correct SPDX string for every serializable algorithm enum value. /// /// /// Exercises every serializable enum value to confirm @@ -303,7 +303,7 @@ public void SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCo { // Arrange: Known algorithm enum values are implicit in the Act/Assert pairs below - // Assert: Verify each known algorithm enum maps to the correct string + // Act / Assert: Verify each known algorithm enum maps to the correct string Assert.AreEqual("SHA1", SpdxChecksumAlgorithm.Sha1.ToText()); Assert.AreEqual("SHA224", SpdxChecksumAlgorithm.Sha224.ToText()); Assert.AreEqual("SHA256", SpdxChecksumAlgorithm.Sha256.ToText()); @@ -324,7 +324,7 @@ public void SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCo } /// - /// Tests the method. + /// Tests that ToText throws InvalidOperationException for an out-of-range numeric enum value. /// /// /// Casts the integer literal 1000 to to produce an diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs index b251cb4..e8d9ebd 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs @@ -34,6 +34,11 @@ public class SpdxElementTests /// /// Tests that an element with a valid SPDXRef-<name> identifier passes identity validation. /// + /// + /// Uses SPDXRef-valid as the identifier because it is a minimal, well-formed + /// value that satisfies the SPDXRef- prefix pattern and contains only allowed characters, + /// making it the simplest positive example to confirm the happy-path acceptance. + /// [TestMethod] public void SpdxElement_Id_ValidFormat_PassesValidation() { @@ -52,13 +57,18 @@ public void SpdxElement_Id_ValidFormat_PassesValidation() /// Tests that an element with an ID that does not follow the SPDXRef-<name> format /// reports an identity validation issue. /// + /// + /// Uses BadId as the identifier because it is a concise, obviously invalid value + /// that omits the required SPDXRef- prefix entirely, making the expected failure + /// unambiguous and the diagnostic message easy to verify. + /// [TestMethod] public void SpdxElement_Id_InvalidFormat_ReportsValidationIssue() { // Arrange: Create a minimal package element with a bad ID var element = new SpdxPackage { Id = "BadId", Name = "test-package", Version = "1.0" }; - // Act: Validate + // Act: Validate the element var issues = new List(); element.Validate(issues, null); diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs index 7719f27..161b3f4 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs @@ -39,7 +39,7 @@ public class SpdxExternalReferenceTests /// and that equal references produce identical hash codes. /// [TestMethod] - public void SpdxExternalReference_SameComparer_ComparesCorrectly() + public void SpdxExternalReference_SameComparer_EqualAndUnequalInstances_ComparesCorrectly() { // Arrange: Create three external references with different properties var r1 = new SpdxExternalReference @@ -87,7 +87,7 @@ public void SpdxExternalReference_SameComparer_ComparesCorrectly() /// object reference (not the same instance). /// [TestMethod] - public void SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance() + public void SpdxExternalReference_DeepCopy_WithAllFields_CreatesEqualButDistinctInstance() { // Arrange: Create an external reference var r1 = new SpdxExternalReference @@ -121,7 +121,7 @@ public void SpdxExternalReference_DeepCopy_CreatesEqualButDistinctInstance() /// source array are appended as new independent copies. /// [TestMethod] - public void SpdxExternalReference_Enhance_AddsOrUpdatesInformationCorrectly() + public void SpdxExternalReference_Enhance_WithMatchingAndNewEntries_MergesCorrectly() { // Arrange: Create an array of external references var references = new[] @@ -276,7 +276,7 @@ public void SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly( /// identifying the unsupported value when given an unrecognized category string. /// [TestMethod] - public void SpdxReferenceCategoryExtensions_FromText_InvalidInput_ReturnsNull() + public void SpdxReferenceCategoryExtensions_FromText_InvalidInput_ThrowsInvalidOperationException() { // Arrange: (no external state needed) @@ -312,7 +312,7 @@ public void SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrect /// unsupported-category message when called with an unrecognized enum value. /// [TestMethod] - public void SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull() + public void SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ThrowsInvalidOperationException() { // Arrange: Create an invalid reference category var invalidCategory = (SpdxReferenceCategory)1000; @@ -330,7 +330,7 @@ public void SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ReturnsNull() /// message when called with . /// [TestMethod] - public void SpdxReferenceCategoryExtensions_ToText_MissingCategory_ReturnsNull() + public void SpdxReferenceCategoryExtensions_ToText_MissingCategory_ThrowsInvalidOperationException() { // Arrange: Use Missing reference category var category = SpdxReferenceCategory.Missing; diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxLicenseElementTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxLicenseElementTests.cs new file mode 100644 index 0000000..b569d90 --- /dev/null +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxLicenseElementTests.cs @@ -0,0 +1,245 @@ +// Copyright(c) 2024 DEMA Consulting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace DemaConsulting.SpdxModel.Tests; + +/// +/// Tests for the abstract base class. +/// +/// +/// Exercises the fitness-based field merging implemented in EnhanceLicenseElement +/// through the concrete subclass. Covers all five inherited +/// fields: ConcludedLicense, LicenseComments, CopyrightText, +/// AttributionText, and Annotations. +/// +[TestClass] +public class SpdxLicenseElementTests +{ + /// + /// Tests that empty and null license-element fields are replaced by concrete source values. + /// + /// + /// Verifies the lowest-fitness case: an empty ConcludedLicense, empty + /// CopyrightText, and null LicenseComments are all replaced when the + /// source carries concrete (rank-3) values. + /// + [TestMethod] + public void SpdxLicenseElement_Enhance_EmptyAndNullFields_ReplacedByConcreteValues() + { + // Arrange: Create a package with empty/null license-element fields + var primary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + ConcludedLicense = "", + CopyrightText = "", + LicenseComments = null + }; + var secondary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + ConcludedLicense = "MIT", + CopyrightText = "Copyright 2024 DEMA Consulting", + LicenseComments = "License determined from source headers" + }; + + // Act: Enhance the primary package with the secondary + primary.Enhance(secondary); + + // Assert: Verify that empty/null fields were replaced with concrete values + Assert.AreEqual("MIT", primary.ConcludedLicense); + Assert.AreEqual("Copyright 2024 DEMA Consulting", primary.CopyrightText); + Assert.AreEqual("License determined from source headers", primary.LicenseComments); + } + + /// + /// Tests that NOASSERTION license-element fields are replaced by concrete source values. + /// + /// + /// Verifies the mid-fitness case: ConcludedLicense and CopyrightText set to + /// NOASSERTION (rank 2) are replaced when the source carries concrete (rank-3) + /// values. LicenseComments set to NOASSERTION is similarly replaced. + /// + [TestMethod] + public void SpdxLicenseElement_Enhance_NoAssertionFields_ReplacedByConcreteValues() + { + // Arrange: Create a package with NOASSERTION license-element fields + var primary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + ConcludedLicense = SpdxElement.NoAssertion, + CopyrightText = SpdxElement.NoAssertion, + LicenseComments = SpdxElement.NoAssertion + }; + var secondary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + ConcludedLicense = "Apache-2.0", + CopyrightText = "Copyright 2024 DEMA Consulting", + LicenseComments = "Apache license confirmed" + }; + + // Act: Enhance the primary package with the secondary + primary.Enhance(secondary); + + // Assert: Verify that NOASSERTION fields were replaced with concrete values + Assert.AreEqual("Apache-2.0", primary.ConcludedLicense); + Assert.AreEqual("Copyright 2024 DEMA Consulting", primary.CopyrightText); + Assert.AreEqual("Apache license confirmed", primary.LicenseComments); + } + + /// + /// Tests that concrete license-element fields are not replaced by secondary values. + /// + /// + /// Verifies the highest-fitness case: once a field holds a concrete (rank-3) value it + /// must not be overwritten by any secondary value regardless of the secondary's fitness + /// level (null, empty, NOASSERTION, or another concrete value). + /// + [TestMethod] + public void SpdxLicenseElement_Enhance_ConcreteFields_NotReplacedBySecondaryValues() + { + // Arrange: Create a package with concrete license-element fields + var primary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + ConcludedLicense = "MIT", + CopyrightText = "Copyright 2024 DEMA Consulting", + LicenseComments = "MIT license confirmed" + }; + var secondary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + ConcludedLicense = "Apache-2.0", + CopyrightText = "Copyright 2024 Other Corp", + LicenseComments = "Different comment" + }; + + // Act: Enhance the primary package with the secondary + primary.Enhance(secondary); + + // Assert: Verify that concrete fields were not replaced + Assert.AreEqual("MIT", primary.ConcludedLicense); + Assert.AreEqual("Copyright 2024 DEMA Consulting", primary.CopyrightText); + Assert.AreEqual("MIT license confirmed", primary.LicenseComments); + } + + /// + /// Tests that attribution text entries are merged by concatenation and deduplication. + /// + /// + /// Verifies that unique entries from the source are appended to the target's + /// AttributionText array while duplicate entries are discarded so that each + /// attribution notice appears exactly once in the merged result. + /// + [TestMethod] + public void SpdxLicenseElement_Enhance_AttributionText_MergedByDeduplication() + { + // Arrange: Create packages with overlapping and unique attribution texts + var primary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + AttributionText = ["Attribution A", "Attribution B"] + }; + var secondary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + AttributionText = ["Attribution B", "Attribution C"] + }; + + // Act: Enhance the primary package with the secondary + primary.Enhance(secondary); + + // Assert: Verify that attribution texts were merged with deduplication + Assert.HasCount(3, primary.AttributionText); + Assert.Contains("Attribution A", primary.AttributionText); + Assert.Contains("Attribution B", primary.AttributionText); + Assert.Contains("Attribution C", primary.AttributionText); + } + + /// + /// Tests that annotations are merged by identity-match and append. + /// + /// + /// Verifies that annotations matching an existing entry are recognized as the same + /// (identity-match on all four fields) and that annotations with no matching entry in + /// the primary are appended as new independent copies, leaving the total annotation + /// count equal to the number of distinct annotations across both sources. + /// + [TestMethod] + public void SpdxLicenseElement_Enhance_Annotations_MergedByIdentityAndAppend() + { + // Arrange: Create packages where primary has one annotation and secondary adds a new one + var primary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + Annotations = + [ + new SpdxAnnotation + { + Annotator = "Tool: tool-a", + Date = "2024-01-01T00:00:00Z", + Type = SpdxAnnotationType.Review, + Comment = "Initial review" + } + ] + }; + var secondary = new SpdxPackage + { + Name = "TestPackage", + Version = "1.0.0", + Annotations = + [ + new SpdxAnnotation + { + Annotator = "Tool: tool-a", + Date = "2024-01-01T00:00:00Z", + Type = SpdxAnnotationType.Review, + Comment = "Initial review" + }, + new SpdxAnnotation + { + Annotator = "Tool: tool-b", + Date = "2024-02-01T00:00:00Z", + Type = SpdxAnnotationType.Other, + Comment = "Additional review" + } + ] + }; + + // Act: Enhance the primary package with the secondary + primary.Enhance(secondary); + + // Assert: Verify that annotations were merged by identity-match and append + Assert.HasCount(2, primary.Annotations); + Assert.AreEqual("Tool: tool-a", primary.Annotations[0].Annotator); + Assert.AreEqual("Initial review", primary.Annotations[0].Comment); + Assert.AreEqual("Tool: tool-b", primary.Annotations[1].Annotator); + Assert.AreEqual("Additional review", primary.Annotations[1].Comment); + } +} diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs index 27ab418..e2d6dd1 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs @@ -43,7 +43,7 @@ public class SpdxSnippetTests [TestMethod] public void SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual() { - // Arrange: Create three snippet instances with different properties + // Arrange: Create two snippets with the same byte range and one distinct snippet var s1 = new SpdxSnippet { SnippetFromFile = "SPDXRef-File1", @@ -268,7 +268,7 @@ public void SpdxSnippet_Validate_InvalidAnnotation_ReportsIssue() snippet.Validate(issues); // Assert: Verify the annotation issue is reported with the correct prefix - Assert.Contains("Snippet 'SPDXRef-Snippet' Invalid Annotator Field - Empty", issues); + Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Annotator Field - Empty"), issues); } /// diff --git a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs index d016d9d..73e4e61 100644 --- a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs @@ -6,6 +6,13 @@ namespace DemaConsulting.SpdxModel.Tests.Transforms; /// /// Tests for the transforms. /// +/// +/// Uses MSTest as the approved test framework for this repository (see csharp-testing.md). +/// Every test deserializes a fresh copy of to prevent +/// inter-test state leakage. The class covers the full scope of +/// operations: adding single and multiple relationships, deduplication, replacement, and +/// atomicity on failure. +/// [TestClass] public class SpdxRelationshipsTests { @@ -49,6 +56,12 @@ public class SpdxRelationshipsTests /// /// Tests that adding a relationship with a missing source ID throws . /// + /// + /// Verifies the error path where the source element ID does not exist in the document. + /// A fresh document is deserialized to ensure baseline state contains no relationships. + /// Confirms the exception carries the correct parameter name and that the document + /// relationships collection remains empty after the failed call. + /// [TestMethod] public void SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException() { @@ -77,6 +90,12 @@ public void SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException() /// /// Tests that adding a relationship with a missing related element throws . /// + /// + /// Verifies the error path where the target element ID does not exist in the document. + /// A fresh document is deserialized to ensure baseline state contains no relationships. + /// Confirms the exception carries the correct parameter name and that the document + /// relationships collection remains empty after the failed call. + /// [TestMethod] public void SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentException() { @@ -105,6 +124,11 @@ public void SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentExce /// /// Tests that adding a valid relationship appends it to the document. /// + /// + /// Verifies the happy path where both source and target elements exist in the document. + /// A fresh document is deserialized so the initial relationships collection is empty, + /// making the single post-add entry the definitive proof of a successful append. + /// [TestMethod] public void SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship() { @@ -131,6 +155,12 @@ public void SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship() /// /// Tests that adding a duplicate relationship enhances the existing entry rather than duplicating it. /// + /// + /// Verifies the deduplication behaviour: a second call with an identical relationship + /// must not create a second entry. A fresh document is deserialized to isolate the + /// count assertion; the final count of one proves that the second add merged into the + /// existing entry rather than appending a new one. + /// [TestMethod] public void SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRelationship() { @@ -165,6 +195,12 @@ public void SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRe /// /// Tests that a relationship with a NOASSERTION target is accepted as valid. /// + /// + /// Verifies that is treated as a sentinel value + /// that bypasses the element-existence check on the target. A fresh document is + /// deserialized so the single resulting entry proves the add succeeded without requiring + /// the target to be present in the document's element collections. + /// [TestMethod] public void SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship() { @@ -191,6 +227,12 @@ public void SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship() /// /// Tests that a relationship with a DocumentRef- prefixed target is accepted as valid. /// + /// + /// Verifies that a target ID beginning with DocumentRef- is treated as an + /// external-document reference and bypasses the local element-existence check. A fresh + /// document is deserialized so the single resulting entry proves the add succeeded + /// without requiring the external element to appear in the local document. + /// [TestMethod] public void SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship() { @@ -217,6 +259,11 @@ public void SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship() /// /// Tests that the batch Add with a single relationship appends it to the document. /// + /// + /// Verifies the batch overload behaves identically to the single-item overload for a + /// one-element array. A fresh document is deserialized so the count assertion unambiguously + /// reflects the result of the batch call rather than any pre-existing state. + /// [TestMethod] public void SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship() { @@ -245,6 +292,11 @@ public void SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship() /// /// Tests that adding multiple duplicate relationships deduplicates them. /// + /// + /// Verifies that a batch containing two identical entries results in only one relationship + /// in the document. A fresh document is deserialized so the final count of one is solely + /// the result of the batch call; no pre-existing entries could mask a deduplication failure. + /// [TestMethod] public void SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRelationships() { @@ -279,6 +331,12 @@ public void SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRel /// /// Tests that the batch Add with replace=true removes pre-existing matching relationships. /// + /// + /// Verifies the replace flag: when replace: true is passed, any pre-existing + /// relationships whose source-target pair matches an entry in the batch are removed + /// before the new relationships are inserted. A fresh document with a single pre-seeded + /// relationship is used so the type change from the replacement is unambiguous. + /// [TestMethod] public void SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRelationships() { @@ -314,6 +372,12 @@ public void SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRela /// /// Tests that a batch Add with replace=true and an invalid relationship leaves the document unmodified. /// + /// + /// Verifies atomicity: when the batch contains an invalid relationship (missing source ID), + /// the entire operation is rolled back and the document's relationships are unchanged. + /// A pre-seeded relationship is added before the batch call so the assertion on the + /// unchanged collection proves that even the valid first entry in the batch was not committed. + /// [TestMethod] public void SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified() { From c31216d2de8b99b37a20bc7f46db130926a713ab Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Tue, 26 May 2026 20:35:02 -0400 Subject: [PATCH 04/12] chore: Fix lint issues after Cycle 4 formal reviews Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .cspell.yaml | 9 ++ .github/workflows/build.yaml | 152 +++++++++++++----- docs/design/spdx-model/spdx-annotation.md | 16 +- docs/design/spdx-model/spdx-checksum.md | 7 +- .../transform/spdx-relationships.yaml | 4 +- docs/verification/spdx-model/spdx-document.md | 11 +- .../transform/spdx-relationships.md | 59 ++++--- 7 files changed, 181 insertions(+), 77 deletions(-) diff --git a/.cspell.yaml b/.cspell.yaml index 8e90699..f4fc488 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -17,7 +17,9 @@ words: - acmecorp - acmenator - Alsos + - behaviour - buildmark + - centralised - Cyper - Dema - Doap @@ -41,6 +43,13 @@ words: - sonarmark - SPDXID - SPDXJSON + - subclassing + - subfolders + - throwingly + - uninitialised + - unioned + - Unrecognised + - unvalidated - versionmark - weasyprint - Weasy diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3dbebb9..36b8e53 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -356,9 +356,18 @@ jobs: dotnet versionmark --capture --job-id "build-docs" \ --output "artifacts/versionmark-build-docs.json" -- \ dotnet git node npm pandoc weasyprint sarifmark sonarmark reqstream \ - buildmark versionmark reviewmark fileassert + buildmark versionmark reviewmark fileassert echo "✓ Tool versions captured" + # === PREPARE DOCUMENT OUTPUT === + # Creates the shared docs/generated/ folder that all document sections write PDFs into. + # This step is intentionally separate from the document sections so any individual + # section can be commented out without breaking the shared output directory. + + - name: Create documents output directory + shell: bash + run: mkdir -p docs/generated + # === COMPILE BUILD NOTES === # This section generates the Build Notes document. BuildMark and VersionMark self-validations # run here to co-locate their evidence with the document that depends on their output. @@ -366,6 +375,10 @@ jobs: # validates the outputs contain expected content. # Downstream projects: Add any additional build notes steps here. + - name: Create build notes output directories + shell: bash + run: mkdir -p docs/build_notes/generated + - name: Run BuildMark self-validation run: > dotnet buildmark @@ -385,20 +398,20 @@ jobs: run: > dotnet buildmark --build-version ${{ inputs.version }} - --report docs/build_notes.md + --report docs/build_notes/generated/build_notes.md --report-depth 1 - name: Display Build Notes Report shell: bash run: | echo "=== Build Notes Report ===" - cat docs/build_notes.md + cat docs/build_notes/generated/build_notes.md - name: Publish Tool Versions shell: bash run: | echo "Publishing tool versions..." - dotnet versionmark --publish --report docs/build_notes/versions.md --report-depth 1 \ + dotnet versionmark --publish --report docs/build_notes/generated/versions.md --report-depth 1 \ -- "artifacts/**/versionmark-*.json" echo "✓ Tool versions published" @@ -406,7 +419,7 @@ jobs: shell: bash run: | echo "=== Tool Versions Report ===" - cat docs/build_notes/versions.md + cat docs/build_notes/generated/versions.md - name: Generate Build Notes HTML with Pandoc shell: bash @@ -416,14 +429,14 @@ jobs: --filter node_modules/.bin/mermaid-filter.cmd --metadata version="${{ inputs.version }}" --metadata date="$(date +'%Y-%m-%d')" - --output docs/build_notes/build_notes.html + --output docs/build_notes/generated/build_notes.html - name: Generate Build Notes PDF with WeasyPrint run: > dotnet weasyprint --pdf-variant pdf/a-3u - docs/build_notes/build_notes.html - "docs/SpdxModel Build Notes.pdf" + docs/build_notes/generated/build_notes.html + "docs/generated/SpdxModel Build Notes.pdf" - name: Assert Build Notes Documents with FileAssert run: > @@ -431,6 +444,10 @@ jobs: --results artifacts/fileassert-build-notes.trx build-notes + - name: Copy Build Notes report to docs/generated + shell: bash + run: cp docs/build_notes/generated/build_notes.md docs/generated/build_notes.md + # === COMPILE CODE QUALITY REPORT === # This section generates the Code Quality document. SarifMark and SonarMark self-validations # run here to co-locate their evidence with the document that depends on their output. @@ -438,6 +455,10 @@ jobs: # validates the outputs contain expected content. # Downstream projects: Add any additional code quality steps here. + - name: Create code quality output directory + shell: bash + run: mkdir -p docs/code_quality/generated + - name: Run SarifMark self-validation run: > dotnet sarifmark @@ -454,7 +475,7 @@ jobs: run: > dotnet sarifmark --sarif artifacts/csharp.sarif - --report docs/code_quality/codeql-quality.md + --report docs/code_quality/generated/codeql-quality.md --heading "SpdxModel CodeQL Analysis" --report-depth 1 @@ -462,7 +483,7 @@ jobs: shell: bash run: | echo "=== CodeQL Quality Report ===" - cat docs/code_quality/codeql-quality.md + cat docs/code_quality/generated/codeql-quality.md - name: Generate SonarCloud Quality Report shell: bash @@ -474,14 +495,14 @@ jobs: --project-key demaconsulting_SpdxModel --branch ${{ github.ref_name }} --token "$SONAR_TOKEN" - --report docs/code_quality/sonar-quality.md + --report docs/code_quality/generated/sonar-quality.md --report-depth 1 - name: Display SonarCloud Quality Report shell: bash run: | echo "=== SonarCloud Quality Report ===" - cat docs/code_quality/sonar-quality.md + cat docs/code_quality/generated/sonar-quality.md - name: Generate Code Quality HTML with Pandoc shell: bash @@ -491,14 +512,14 @@ jobs: --filter node_modules/.bin/mermaid-filter.cmd --metadata version="${{ inputs.version }}" --metadata date="$(date +'%Y-%m-%d')" - --output docs/code_quality/quality.html + --output docs/code_quality/generated/quality.html - name: Generate Code Quality PDF with WeasyPrint run: > dotnet weasyprint --pdf-variant pdf/a-3u - docs/code_quality/quality.html - "docs/SpdxModel Code Quality.pdf" + docs/code_quality/generated/quality.html + "docs/generated/SpdxModel Code Quality.pdf" - name: Assert Code Quality Documents with FileAssert run: > @@ -513,6 +534,10 @@ jobs: # PDF, and FileAssert validates the outputs contain expected content. # Downstream projects: Add any additional code review steps here. + - name: Create code review output directories + shell: bash + run: mkdir -p docs/code_review_plan/generated docs/code_review_report/generated + - name: Run ReviewMark self-validation run: > dotnet reviewmark @@ -524,22 +549,22 @@ jobs: # TODO: Add --enforce once reviews branch is populated with review evidence PDFs and index.json run: > dotnet reviewmark - --plan docs/code_review_plan/plan.md + --plan docs/code_review_plan/generated/plan.md --plan-depth 1 - --report docs/code_review_report/report.md + --report docs/code_review_report/generated/report.md --report-depth 1 - name: Display Review Plan shell: bash run: | echo "=== Review Plan ===" - cat docs/code_review_plan/plan.md + cat docs/code_review_plan/generated/plan.md - name: Display Review Report shell: bash run: | echo "=== Review Report ===" - cat docs/code_review_report/report.md + cat docs/code_review_report/generated/report.md - name: Generate Review Plan HTML with Pandoc shell: bash @@ -549,14 +574,14 @@ jobs: --filter node_modules/.bin/mermaid-filter.cmd --metadata version="${{ inputs.version }}" --metadata date="$(date +'%Y-%m-%d')" - --output docs/code_review_plan/plan.html + --output docs/code_review_plan/generated/plan.html - name: Generate Review Plan PDF with WeasyPrint run: > dotnet weasyprint --pdf-variant pdf/a-3u - docs/code_review_plan/plan.html - "docs/SpdxModel Review Plan.pdf" + docs/code_review_plan/generated/plan.html + "docs/generated/SpdxModel Review Plan.pdf" - name: Generate Review Report HTML with Pandoc shell: bash @@ -566,14 +591,14 @@ jobs: --filter node_modules/.bin/mermaid-filter.cmd --metadata version="${{ inputs.version }}" --metadata date="$(date +'%Y-%m-%d')" - --output docs/code_review_report/report.html + --output docs/code_review_report/generated/report.html - name: Generate Review Report PDF with WeasyPrint run: > dotnet weasyprint --pdf-variant pdf/a-3u - docs/code_review_report/report.html - "docs/SpdxModel Review Report.pdf" + docs/code_review_report/generated/report.html + "docs/generated/SpdxModel Review Report.pdf" - name: Assert Code Review Documents with FileAssert run: > @@ -586,6 +611,10 @@ jobs: # FileAssert validates that the HTML and PDF outputs contain expected content. # Downstream projects: Add any additional design document steps here. + - name: Create design output directory + shell: bash + run: mkdir -p docs/design/generated + - name: Generate Design HTML with Pandoc shell: bash run: > @@ -594,14 +623,14 @@ jobs: --filter node_modules/.bin/mermaid-filter.cmd --metadata version="${{ inputs.version }}" --metadata date="$(date +'%Y-%m-%d')" - --output docs/design/design.html + --output docs/design/generated/design.html - name: Generate Design PDF with WeasyPrint run: > dotnet weasyprint --pdf-variant pdf/a-3u - docs/design/design.html - "docs/SpdxModel Software Design.pdf" + docs/design/generated/design.html + "docs/generated/SpdxModel Software Design.pdf" - name: Assert Design Documents with FileAssert run: > @@ -609,11 +638,46 @@ jobs: --results artifacts/fileassert-design.trx design + # === COMPILE VERIFICATION DOCUMENT === + # This section generates the Verification document using Pandoc and WeasyPrint. + # FileAssert validates that the HTML and PDF outputs contain expected content. + # Downstream projects: Add any additional verification steps here. + + - name: Create verification output directory + shell: bash + run: mkdir -p docs/verification/generated + + - name: Generate Verification HTML with Pandoc + shell: bash + run: > + dotnet pandoc + --defaults docs/verification/definition.yaml + --metadata version="${{ inputs.version }}" + --metadata date="$(date +'%Y-%m-%d')" + --output docs/verification/generated/verification.html + + - name: Generate Verification PDF with WeasyPrint + run: > + dotnet weasyprint + --pdf-variant pdf/a-3u + docs/verification/generated/verification.html + "docs/generated/SpdxModel Software Verification Design.pdf" + + - name: Assert Verification Documents with FileAssert + run: > + dotnet fileassert + --results artifacts/fileassert-verification.trx + verification + # === COMPILE USER GUIDE === # This section generates the User Guide document using Pandoc and WeasyPrint. # FileAssert validates that the HTML and PDF outputs contain expected content. # Downstream projects: Add any additional user guide steps here. + - name: Create user guide output directory + shell: bash + run: mkdir -p docs/user_guide/generated + - name: Generate User Guide HTML with Pandoc shell: bash run: > @@ -622,14 +686,14 @@ jobs: --filter node_modules/.bin/mermaid-filter.cmd --metadata version="${{ inputs.version }}" --metadata date="$(date +'%Y-%m-%d')" - --output docs/user_guide/user_guide.html + --output docs/user_guide/generated/user_guide.html - name: Generate User Guide PDF with WeasyPrint run: > dotnet weasyprint --pdf-variant pdf/a-3u - docs/user_guide/user_guide.html - "docs/SpdxModel User Guide.pdf" + docs/user_guide/generated/user_guide.html + "docs/generated/SpdxModel User Guide.pdf" - name: Assert User Guide Documents with FileAssert run: > @@ -638,8 +702,8 @@ jobs: user-guide # === FILEASSERT SELF-VALIDATION === - # By this point Pandoc and WeasyPrint have each produced 6 validated documents - # (Build Notes, Code Quality, Review Plan, Review Report, Design, User Guide), + # By this point Pandoc and WeasyPrint have each produced 7 validated documents + # (Build Notes, Code Quality, Review Plan, Review Report, Design, Verification, User Guide), # providing strong OTS evidence for both tools before ReqStream runs. FileAssert # self-validation confirms the assertion tool itself is operational. # Downstream projects: Add any additional FileAssert self-validation steps here. @@ -659,6 +723,10 @@ jobs: # confirm the requirements pipeline produced well-formed documents. # Downstream projects: Add any additional requirements steps here. + - name: Create requirements output directories + shell: bash + run: mkdir -p docs/requirements_doc/generated docs/requirements_report/generated + - name: Run ReqStream self-validation run: > dotnet reqstream @@ -670,9 +738,9 @@ jobs: dotnet reqstream --requirements requirements.yaml --tests "artifacts/**/*.trx" - --report docs/requirements_doc/requirements.md - --justifications docs/requirements_doc/justifications.md - --matrix docs/requirements_report/trace_matrix.md + --report docs/requirements_doc/generated/requirements.md + --justifications docs/requirements_doc/generated/justifications.md + --matrix docs/requirements_report/generated/trace_matrix.md --enforce - name: Generate Requirements HTML with Pandoc @@ -683,14 +751,14 @@ jobs: --filter node_modules/.bin/mermaid-filter.cmd --metadata version="${{ inputs.version }}" --metadata date="$(date +'%Y-%m-%d')" - --output docs/requirements_doc/requirements.html + --output docs/requirements_doc/generated/requirements.html - name: Generate Requirements PDF with WeasyPrint run: > dotnet weasyprint --pdf-variant pdf/a-3u - docs/requirements_doc/requirements.html - "docs/SpdxModel Requirements.pdf" + docs/requirements_doc/generated/requirements.html + "docs/generated/SpdxModel Requirements.pdf" - name: Generate Trace Matrix HTML with Pandoc shell: bash @@ -700,14 +768,14 @@ jobs: --filter node_modules/.bin/mermaid-filter.cmd --metadata version="${{ inputs.version }}" --metadata date="$(date +'%Y-%m-%d')" - --output docs/requirements_report/trace_matrix.html + --output docs/requirements_report/generated/trace_matrix.html - name: Generate Trace Matrix PDF with WeasyPrint run: > dotnet weasyprint --pdf-variant pdf/a-3u - docs/requirements_report/trace_matrix.html - "docs/SpdxModel Trace Matrix.pdf" + docs/requirements_report/generated/trace_matrix.html + "docs/generated/SpdxModel Trace Matrix.pdf" - name: Assert Requirements Documents with FileAssert run: > diff --git a/docs/design/spdx-model/spdx-annotation.md b/docs/design/spdx-model/spdx-annotation.md index d14db94..41ea388 100644 --- a/docs/design/spdx-model/spdx-annotation.md +++ b/docs/design/spdx-model/spdx-annotation.md @@ -75,20 +75,20 @@ issues. No exceptions are thrown by `DeepCopy` or `Enhance`. `SpdxAnnotationType` is an enumeration of the SPDX annotation type tokens, with round-trip text conversion provided by `SpdxAnnotationTypeExtensions`. -#### Purpose +#### SpdxAnnotationType Purpose Enumerate the valid SPDX annotation type strings and provide lossless conversion between the enum representation used in the in-memory model and the text representation used in SPDX JSON documents. -#### Data Model +#### SpdxAnnotationType Data Model -| Enum Value | Integer | SPDX Text Form | -|------------|---------|----------------| +| Enum Value | Integer | SPDX Text Form | +|------------|---------|-------------------------------------------------| | `Missing` | -1 | `""` (sentinel; indicates no type has been set) | -| `Review` | 0 | `REVIEW` | -| `Other` | 1 | `OTHER` | +| `Review` | 0 | `REVIEW` | +| `Other` | 1 | `OTHER` | -#### Key Methods +#### SpdxAnnotationType Key Methods **FromText**: Converts an SPDX annotation type text string to its enum value. @@ -107,7 +107,7 @@ representation used in the in-memory model and the text representation used in S - *Exceptions*: `InvalidOperationException` — thrown when the value is `Missing` or is a numeric value that does not correspond to any named enum member. -#### Error Handling +#### SpdxAnnotationType Error Handling - **`FromText`**: throws `InvalidOperationException` with a message identifying the unsupported value when given a non-empty string that is not a recognized annotation type token. diff --git a/docs/design/spdx-model/spdx-checksum.md b/docs/design/spdx-model/spdx-checksum.md index 867cd16..ca11369 100644 --- a/docs/design/spdx-model/spdx-checksum.md +++ b/docs/design/spdx-model/spdx-checksum.md @@ -95,7 +95,8 @@ different security policies and tooling ecosystems. - *Preconditions*: none. - *Postconditions*: Missing or malformed fields are recorded in `issues`. Specifically: - If `Algorithm` is `Missing`, the message `"{parent} Invalid Checksum Algorithm Field - Missing"` is appended. - - If `Algorithm` is a numeric value not defined in the enumeration, the message `"{parent} Invalid Checksum Algorithm Field - Unknown"` is appended. + - If `Algorithm` is a numeric value not defined in the enumeration, the message + `"{parent} Invalid Checksum Algorithm Field - Unknown"` is appended. - If `Value` is empty, the message `"{parent} Invalid Checksum Value Field - Empty"` is appended. **Same**: `static IEqualityComparer` — compares checksums by algorithm and value. @@ -110,7 +111,9 @@ thrown by `DeepCopy` or `Enhance`. unrecognized. - **`ToText`**: throws `InvalidOperationException` when the algorithm value is `Missing` or is an out-of-range numeric value not corresponding to any named enum member. -- **`Validate` unknown-algorithm branch**: when `Algorithm` holds a numeric value that is not a named member of `SpdxChecksumAlgorithm`, `Validate` appends `"{parent} Invalid Checksum Algorithm Field - Unknown"` to the issues list. +- **`Validate` unknown-algorithm branch**: when `Algorithm` holds a numeric value that is not + a named member of `SpdxChecksumAlgorithm`, `Validate` appends + `"{parent} Invalid Checksum Algorithm Field - Unknown"` to the issues list. ### Dependencies diff --git a/docs/reqstream/spdx-model/transform/spdx-relationships.yaml b/docs/reqstream/spdx-model/transform/spdx-relationships.yaml index 6ec0813..5b15933 100644 --- a/docs/reqstream/spdx-model/transform/spdx-relationships.yaml +++ b/docs/reqstream/spdx-model/transform/spdx-relationships.yaml @@ -22,7 +22,9 @@ sections: - SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship - id: SpdxModel-Transform-RelationshipUtilities-AddMultiple - title: The library shall provide a utility to batch-add relationships to an SPDX document with optional replacement. + title: >- + The library shall provide a utility to batch-add relationships to an + SPDX document with optional replacement. tags: - data-model justification: | diff --git a/docs/verification/spdx-model/spdx-document.md b/docs/verification/spdx-model/spdx-document.md index eaf4c2d..2e9d2a5 100644 --- a/docs/verification/spdx-model/spdx-document.md +++ b/docs/verification/spdx-model/spdx-document.md @@ -17,8 +17,9 @@ All automated tests pass with zero failures. ### Test Scenarios -**SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCorrectPackages**: Verifies that GetRootPackages returns only -the packages that are the targets of DESCRIBES relationships from the document element. +**SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCorrectPackages**: Verifies that +GetRootPackages returns only the packages that are the targets of DESCRIBES relationships from the +document element. This scenario is tested by `SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCorrectPackages`. **SpdxDocument_Same_DocumentsWithMatchingRootPackages_AreEqual**: Verifies that SameComparer correctly @@ -67,9 +68,9 @@ This scenario is tested by `SpdxDocument_Validate_InvalidRelationship_ReportsIss document does not satisfy NTIA minimum element requirements for an SBOM. This scenario is tested by `SpdxDocument_Validate_NtiaMinimumElements_ReportsIssues`. -**SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelationshipElements**: Verifies that `GetAllElements` returns the combined -collection of all packages, files, snippets, annotations, and the document element itself, and -that `SpdxRelationship` elements are excluded. +**SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelationshipElements**: Verifies that +`GetAllElements` returns the combined collection of all packages, files, snippets, annotations, +and the document element itself, and that `SpdxRelationship` elements are excluded. This scenario is tested by `SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelationshipElements`. **SpdxDocument_GetElement_Document_ReturnsDocumentElement**: Verifies that GetElement returns diff --git a/docs/verification/spdx-model/transform/spdx-relationships.md b/docs/verification/spdx-model/transform/spdx-relationships.md index 58559e1..d2f3e2c 100644 --- a/docs/verification/spdx-model/transform/spdx-relationships.md +++ b/docs/verification/spdx-model/transform/spdx-relationships.md @@ -17,22 +17,43 @@ All automated tests pass with zero failures. #### Test Scenarios -**SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException**: Verifies that attempting to add a single relationship where the source element ID does not exist in the document throws `ArgumentException` with a message identifying the missing element and leaves the document unmodified. - -**SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentException**: Verifies that attempting to add a single relationship where the related element ID does not exist in the document (and is neither NOASSERTION nor DocumentRef-prefixed) throws `ArgumentException` and leaves the document unmodified. - -**SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship**: Verifies that adding a single valid relationship between two existing elements results in the relationship being appended to the document's relationships collection. - -**SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRelationship**: Verifies that adding a relationship that is identical to one already present in the document enhances the existing entry rather than creating a duplicate. - -**SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship**: Verifies that a relationship whose target element is `NOASSERTION` is accepted as valid and added to the document without error. - -**SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship**: Verifies that a relationship whose target element uses the `DocumentRef-` external-reference prefix is accepted as valid and added to the document without error. - -**SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship**: Verifies that the batch Add overload with a single-element array appends the relationship to the document. - -**SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRelationships**: Verifies that passing duplicate relationships in a single batch call results in only one entry being added to the document. - -**SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRelationships**: Verifies that invoking the batch Add with `replace=true` removes pre-existing relationships between the same source and target elements before adding the new ones. - -**SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified**: Verifies that when any relationship in a batch is invalid (e.g., missing source ID), an `ArgumentException` is thrown and the document's relationships collection is left in its original state — no relationships are added or removed. +**SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException**: Verifies that attempting to add +a single relationship where the source element ID does not exist in the document throws +`ArgumentException` with a message identifying the missing element and leaves the document +unmodified. + +**SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentException**: Verifies that +attempting to add a single relationship where the related element ID does not exist in the document +(and is neither NOASSERTION nor DocumentRef-prefixed) throws `ArgumentException` and leaves the +document unmodified. + +**SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship**: Verifies that adding a single +valid relationship between two existing elements results in the relationship being appended to the +document's relationships collection. + +**SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRelationship**: Verifies that +adding a relationship that is identical to one already present in the document enhances the existing +entry rather than creating a duplicate. + +**SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship**: Verifies that a relationship +whose target element is `NOASSERTION` is accepted as valid and added to the document without error. + +**SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship**: Verifies that a relationship +whose target element uses the `DocumentRef-` external-reference prefix is accepted as valid and +added to the document without error. + +**SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship**: Verifies that the batch Add +overload with a single-element array appends the relationship to the document. + +**SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRelationships**: Verifies that +passing duplicate relationships in a single batch call results in only one entry being added to the +document. + +**SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRelationships**: Verifies that +invoking the batch Add with `replace=true` removes pre-existing relationships between the same +source and target elements before adding the new ones. + +**SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified**: Verifies that when +any relationship in a batch is invalid (e.g., missing source ID), an `ArgumentException` is thrown +and the document's relationships collection is left in its original state — no relationships are +added or removed. From cdd233e48b2681702eaf9167a0cac017a2bb4eee Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Tue, 26 May 2026 20:35:46 -0400 Subject: [PATCH 05/12] Fix CI build pipeline. --- .github/workflows/build.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 36b8e53..a973cfd 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -791,6 +791,4 @@ jobs: uses: actions/upload-artifact@v7 with: name: documents - path: |- - docs/*.pdf - docs/build_notes.md + path: docs/generated/* From db6e0754d399ed692d28c35f7a18c48fe89b0cd0 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Wed, 27 May 2026 09:09:22 -0400 Subject: [PATCH 06/12] Synchronize to template, migrate to xUnit, review-cleanups. --- .cspell.yaml | 2 +- .github/standards/csharp-testing.md | 9 +- CONTRIBUTING.md | 10 +- README.md | 1 + docs/design/spdx-model.md | 30 ++ docs/design/spdx-model/spdx-document.md | 13 +- docs/design/spdx-model/spdx-element.md | 2 +- .../spdx-external-document-reference.md | 6 +- .../spdx-model/spdx-external-reference.md | 2 +- .../spdx-extracted-licensing-info.md | 3 +- docs/design/spdx-model/spdx-file.md | 5 +- docs/design/spdx-model/spdx-helpers.md | 2 + docs/design/spdx-model/spdx-model.md | 3 +- docs/design/spdx-model/spdx-package.md | 6 +- docs/design/spdx-model/spdx-relationship.md | 11 +- docs/design/spdx-model/spdx-snippet.md | 4 +- docs/design/spdx-model/transform/transform.md | 2 + .../reqstream/ots/{mstest.yaml => xunit.yaml} | 10 +- docs/reqstream/spdx-model/spdx-package.yaml | 18 +- .../transform/spdx-relationships.yaml | 8 +- .../spdx-model/transform/transform.yaml | 8 +- docs/user_guide/troubleshooting.md | 4 + docs/user_guide/usage.md | 1 + docs/verification/introduction.md | 12 +- docs/verification/spdx-model.md | 12 +- docs/verification/spdx-model/io/io.md | 2 +- .../spdx-model/io/spdx-2-json-deserializer.md | 2 +- .../spdx-model/io/spdx-2-json-serializer.md | 2 +- .../spdx-model/spdx-annotation.md | 2 +- docs/verification/spdx-model/spdx-checksum.md | 2 +- .../spdx-model/spdx-creation-information.md | 2 +- docs/verification/spdx-model/spdx-document.md | 2 +- docs/verification/spdx-model/spdx-element.md | 2 +- .../spdx-external-document-reference.md | 2 +- .../spdx-model/spdx-external-reference.md | 4 +- .../spdx-extracted-licensing-info.md | 2 +- docs/verification/spdx-model/spdx-file.md | 2 +- docs/verification/spdx-model/spdx-helpers.md | 9 +- .../spdx-model/spdx-license-element.md | 39 ++- docs/verification/spdx-model/spdx-model.md | 2 +- .../spdx-package-verification-code.md | 2 +- docs/verification/spdx-model/spdx-package.md | 38 +-- .../spdx-model/spdx-relationship.md | 2 +- docs/verification/spdx-model/spdx-snippet.md | 2 +- .../transform/spdx-relationships.md | 2 +- .../spdx-model/transform/transform.md | 2 +- requirements.yaml | 2 +- .../IO/Spdx2JsonSerializer.cs | 149 +++++---- .../SpdxAnnotation.cs | 8 +- src/DemaConsulting.SpdxModel/SpdxChecksum.cs | 8 +- .../SpdxChecksumAlgorithm.cs | 2 +- src/DemaConsulting.SpdxModel/SpdxElement.cs | 8 +- src/DemaConsulting.SpdxModel/SpdxFile.cs | 8 +- src/DemaConsulting.SpdxModel/SpdxPackage.cs | 6 + .../SpdxPackageVerificationCode.cs | 2 +- .../SpdxRelationship.cs | 8 +- src/DemaConsulting.SpdxModel/SpdxSnippet.cs | 9 +- .../AssemblyInfo.cs | 3 - .../DemaConsulting.SpdxModel.Tests.csproj | 10 +- .../IO/Spdx2JsonDeserialize22.cs | 225 +++++++------- .../IO/Spdx2JsonDeserialize23.cs | 203 +++++++------ .../IO/Spdx2JsonDeserializeAnnotation.cs | 35 ++- .../IO/Spdx2JsonDeserializeChecksum.cs | 23 +- ...Spdx2JsonDeserializeCreationInformation.cs | 21 +- .../IO/Spdx2JsonDeserializeDocument.cs | 39 ++- ...sonDeserializeExternalDocumentReference.cs | 27 +- .../Spdx2JsonDeserializeExternalReference.cs | 35 ++- ...x2JsonDeserializeExtractedLicensingInfo.cs | 33 +- .../IO/Spdx2JsonDeserializeFile.cs | 63 ++-- .../IO/Spdx2JsonDeserializePackage.cs | 103 ++++--- ...2JsonDeserializePackageVerificationCode.cs | 17 +- .../IO/Spdx2JsonDeserializeRelationship.cs | 35 ++- .../IO/Spdx2JsonDeserializeSnippet.cs | 73 +++-- .../IO/Spdx2JsonDeserializerTests.cs | 13 +- .../IO/Spdx2JsonSerializeAnnotation.cs | 13 +- .../IO/Spdx2JsonSerializeChecksum.cs | 11 +- .../Spdx2JsonSerializeCreationInformation.cs | 3 +- .../IO/Spdx2JsonSerializeDocument.cs | 7 +- ...2JsonSerializeExternalDocumentReference.cs | 9 +- .../IO/Spdx2JsonSerializeExternalReference.cs | 11 +- ...pdx2JsonSerializeExtractedLicensingInfo.cs | 9 +- .../IO/Spdx2JsonSerializeFile.cs | 9 +- .../IO/Spdx2JsonSerializePackage.cs | 11 +- ...dx2JsonSerializePackageVerificationCode.cs | 5 +- .../IO/Spdx2JsonSerializeRelationship.cs | 11 +- .../IO/Spdx2JsonSerializeSnippet.cs | 37 ++- .../IO/SpdxJsonHelpers.cs | 4 +- .../IO/SpdxModelIOTests.cs | 38 +-- .../SpdxAnnotationTests.cs | 116 ++++--- .../SpdxChecksumTests.cs | 174 ++++++----- .../SpdxCreationInformationTests.cs | 65 ++-- .../SpdxDocumentTests.cs | 176 ++++++----- .../SpdxElementTests.cs | 9 +- .../SpdxExternalDocumentReferenceTests.cs | 74 +++-- .../SpdxExternalReferenceTests.cs | 113 ++++--- .../SpdxExtractedLicensingInfoTests.cs | 69 +++-- .../SpdxFileTests.cs | 191 ++++++------ .../SpdxHelpersTests.cs | 25 +- .../SpdxLicenseElementTests.cs | 41 ++- .../SpdxModelTests.cs | 122 ++++---- .../SpdxPackageTests.cs | 148 ++++----- .../SpdxPackageVerificationCodeTests.cs | 61 ++-- .../SpdxRelationshipTests.cs | 285 +++++++++--------- .../SpdxSnippetTests.cs | 135 +++++---- .../TestHelpers.cs | 23 ++ .../Transforms/SpdxModelTransformTests.cs | 47 ++- .../Transforms/SpdxRelationshipsTests.cs | 103 ++++--- 107 files changed, 1879 insertions(+), 1752 deletions(-) rename docs/reqstream/ots/{mstest.yaml => xunit.yaml} (71%) diff --git a/.cspell.yaml b/.cspell.yaml index f4fc488..773150c 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -25,7 +25,6 @@ words: - Doap - fileassert - hotspots - - mstest - Neko - netstandard - NOASSERTION @@ -53,6 +52,7 @@ words: - versionmark - weasyprint - Weasy + - xunit - yamlfix # Exclude common build artifacts, dependencies, and vendored third-party code diff --git a/.github/standards/csharp-testing.md b/.github/standards/csharp-testing.md index e6970ab..629ab9b 100644 --- a/.github/standards/csharp-testing.md +++ b/.github/standards/csharp-testing.md @@ -66,13 +66,8 @@ These are non-obvious v3 behaviors that differ from v2 or common assumptions: # Repository-Specific Exceptions -## MSTest Approval (SpdxModel) - -The `DemaConsulting.SpdxModel.Tests` project uses **MSTest** instead of xUnit v3. This is -an approved exception for this repository: the test project predates this standard and a -full migration to xUnit v3 would be high risk with low value. MSTest remains the approved -test framework for this repository's existing test suite. New test projects in this repository -should follow the xUnit v3 standard unless this section is updated. +No active exceptions. The `DemaConsulting.SpdxModel.Tests` project was migrated from MSTest +to xUnit v3 and now follows this standard. # Quality Checks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfb0f58..a617ade 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,7 +126,7 @@ Note the spaces after `///` for proper indentation in summary blocks. ### Test Framework -We use MSTest v4 for unit and integration tests. +We use xUnit v3 for unit and integration tests. ### Test Naming Convention @@ -141,10 +141,10 @@ Examples: ### Writing Tests - Write tests that are clear and focused -- Use modern MSTest v4 assertions: - - `Assert.HasCount(expectedCount, collection)` - - `Assert.IsEmpty(collection)` - - `Assert.DoesNotContain(item, collection)` +- Use xUnit v3 assertions: + - `Assert.Equal(expected, actual)` + - `Assert.Contains(expected, collection)` + - `Assert.Throws(() => action())` - Always clean up resources (use `try/finally` for console redirection) - Link tests to requirements in `requirements.yaml` when applicable diff --git a/README.md b/README.md index 85604e0..fd169c2 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ comprehensive in-memory model for reading, manipulating, and writing SPDX Softwa - 🔍 **Transform Support** - Built-in utilities for manipulating SPDX relationships - 🔁 **Deep Copy Support** - Deep copy any SPDX element or entire documents - ⚖️ **Comparison Utilities** - Compare and check equality of SPDX elements +- ✅ **Validation Support** - Validate SPDX documents and elements against the specification - ⚡ **Multi-Target** - Supports .NET Standard 2.0, .NET 8, 9, and 10 - 🖥️ **Multi-Platform** - Builds and runs on Windows, Linux, and macOS - 🧪 **Well-Tested** - Comprehensive test suite with high code coverage diff --git a/docs/design/spdx-model.md b/docs/design/spdx-model.md index 9420262..64bc7fc 100644 --- a/docs/design/spdx-model.md +++ b/docs/design/spdx-model.md @@ -46,6 +46,36 @@ flowchart TD Spdx2JsonSerializer --> SpdxConstants SpdxRelationships --> SpdxDocument SpdxRelationships --> SpdxRelationship + SpdxDocument --> SpdxPackage + SpdxDocument --> SpdxFile + SpdxDocument --> SpdxSnippet + SpdxDocument --> SpdxRelationship + SpdxDocument --> SpdxAnnotation + SpdxDocument --> SpdxExternalDocumentReference + SpdxDocument --> SpdxCreationInformation + SpdxDocument --> SpdxExtractedLicensingInfo + SpdxFile --> SpdxChecksum + SpdxPackage --> SpdxChecksum + SpdxPackage --> SpdxExternalReference + SpdxPackage --> SpdxPackageVerificationCode + SpdxExternalDocumentReference --> SpdxChecksum + Spdx2JsonDeserializer --> SpdxHelpers + Spdx2JsonSerializer --> SpdxHelpers + SpdxDocument --> SpdxPackage + SpdxDocument --> SpdxFile + SpdxDocument --> SpdxSnippet + SpdxDocument --> SpdxRelationship + SpdxDocument --> SpdxAnnotation + SpdxDocument --> SpdxExternalDocumentReference + SpdxDocument --> SpdxCreationInformation + SpdxDocument --> SpdxExtractedLicensingInfo + SpdxFile --> SpdxChecksum + SpdxPackage --> SpdxChecksum + SpdxPackage --> SpdxExternalReference + SpdxPackage --> SpdxPackageVerificationCode + SpdxExternalDocumentReference --> SpdxChecksum + Spdx2JsonDeserializer --> SpdxHelpers + Spdx2JsonSerializer --> SpdxHelpers ``` ## External Interfaces diff --git a/docs/design/spdx-model/spdx-document.md b/docs/design/spdx-model/spdx-document.md index 92b7af5..c36c2f6 100644 --- a/docs/design/spdx-model/spdx-document.md +++ b/docs/design/spdx-model/spdx-document.md @@ -62,12 +62,13 @@ when `DESCRIBES` relationships are not present). - *Postconditions*: All validation issues found in the document and its elements are appended to `issues`. -**GetRootPackages**: Returns packages directly described by the document via `DESCRIBES` -relationships. +**GetRootPackages**: Returns packages directly described by the document. - *Parameters*: none. -- *Returns*: `IEnumerable` — packages whose SPDX ID appears in a `DESCRIBES` - relationship from this document. +- *Returns*: `SpdxPackage[]` — packages whose SPDX ID appears in the `Describes` array, is the + target of a `DESCRIBES` relationship from the document element, or is the source of a + `DESCRIBED_BY` relationship pointing at the document element. All three mechanisms are checked + and the results are unioned. - *Preconditions*: none. - *Postconditions*: none. @@ -90,8 +91,8 @@ relationships. **Same**: `static IEqualityComparer` — compares documents by `Name` and root-package identity. Two documents are considered the same when their `Name` values match AND their sets of -root packages (as returned by `GetRootPackages`) are sequence-equal under `SpdxPackage.Same`. -Used for deduplication scenarios. +root packages (as returned by `GetRootPackages`) contain the same packages in any order under +`SpdxPackage.Same`. Used for deduplication scenarios. ### Error Handling diff --git a/docs/design/spdx-model/spdx-element.md b/docs/design/spdx-model/spdx-element.md index 8c6fa8a..911d076 100644 --- a/docs/design/spdx-model/spdx-element.md +++ b/docs/design/spdx-model/spdx-element.md @@ -25,7 +25,7 @@ pathological input strings from untrusted SPDX sources. - *Parameters*: `SpdxElement other` — source element. - *Returns*: `void` -- *Preconditions*: none. +- *Preconditions*: `other` must not be null. - *Postconditions*: If `Id` is empty or null, it is set to `other.Id` via `SpdxHelpers.EnhanceString`. ### Error Handling diff --git a/docs/design/spdx-model/spdx-external-document-reference.md b/docs/design/spdx-model/spdx-external-document-reference.md index ed5c73f..cc4aaec 100644 --- a/docs/design/spdx-model/spdx-external-document-reference.md +++ b/docs/design/spdx-model/spdx-external-document-reference.md @@ -11,11 +11,11 @@ includes a checksum to verify the integrity of the referenced document. **ExternalDocumentId**: `string` — Local identifier for the referenced document within this document (e.g., `DocumentRef-tools`). Used as a prefix when referencing elements across documents. -**Document**: `string` — URI of the referenced SPDX document. - **Checksum**: `SpdxChecksum` — Cryptographic checksum of the referenced document for integrity verification. +**Document**: `string` — URI of the referenced SPDX document. + ### Key Methods **DeepCopy**: Returns a new instance with all fields deep-copied. @@ -35,7 +35,7 @@ verification. **Enhance (static array merge)**: Merges two external document reference arrays by matching on `Document` URI. -- *Parameters*: `SpdxExternalDocumentReference[] base`, `SpdxExternalDocumentReference[] additions`. +- *Parameters*: `SpdxExternalDocumentReference[] array`, `SpdxExternalDocumentReference[] others`. - *Returns*: `SpdxExternalDocumentReference[]` — merged array. - *Preconditions*: none. - *Postconditions*: Entries present in both arrays (matched by `Document` URI via `Same.Equals`) diff --git a/docs/design/spdx-model/spdx-external-reference.md b/docs/design/spdx-model/spdx-external-reference.md index 3dff70c..8cba75f 100644 --- a/docs/design/spdx-model/spdx-external-reference.md +++ b/docs/design/spdx-model/spdx-external-reference.md @@ -37,7 +37,7 @@ enrich SBOMs with contextual information from authoritative sources. **Enhance (static array merge)**: Merges two external reference arrays by matching on category, type, and locator. -- *Parameters*: `SpdxExternalReference[] base`, `SpdxExternalReference[] additions`. +- *Parameters*: `SpdxExternalReference[] array`, `SpdxExternalReference[] others`. - *Returns*: `SpdxExternalReference[]` — merged array. - *Preconditions*: none. - *Postconditions*: Matching entries are enhanced; new entries are appended. diff --git a/docs/design/spdx-model/spdx-extracted-licensing-info.md b/docs/design/spdx-model/spdx-extracted-licensing-info.md index 4a7a398..a80f564 100644 --- a/docs/design/spdx-model/spdx-extracted-licensing-info.md +++ b/docs/design/spdx-model/spdx-extracted-licensing-info.md @@ -33,7 +33,8 @@ external locations. - *Parameters*: `SpdxExtractedLicensingInfo other` — source of additional field values. - *Returns*: `void` - *Preconditions*: none. -- *Postconditions*: Empty or null fields are populated from `other`. +- *Postconditions*: Empty or null fields are populated from `other`; CrossReferences are merged by + concatenation and deduplication. **Enhance (static array merge)**: Merges two extracted licensing info arrays by matching on `ExtractedText`. diff --git a/docs/design/spdx-model/spdx-file.md b/docs/design/spdx-model/spdx-file.md index d9e4328..7bf5bca 100644 --- a/docs/design/spdx-model/spdx-file.md +++ b/docs/design/spdx-model/spdx-file.md @@ -43,8 +43,9 @@ key when merging file arrays. - *Preconditions*: none. - *Postconditions*: Fields in this instance are updated when the current value has lower fitness than the source value, following the hierarchy: concrete value > NOASSERTION > empty string > - null. Array fields are merged by concatenation and deduplication (AttributionText) or - identity-match and append (other arrays). + null. `FileTypes`, `LicenseInfoInFiles`, `Contributors`, and `AttributionText` (inherited) are + merged by concatenation and deduplication. `Checksums` are merged using identity-match and + enhance via `SpdxChecksum.Enhance`. **Enhance (static array merge)**: Merges two file arrays, matching on `FileName`. diff --git a/docs/design/spdx-model/spdx-helpers.md b/docs/design/spdx-model/spdx-helpers.md index 42384dc..01a1b98 100644 --- a/docs/design/spdx-model/spdx-helpers.md +++ b/docs/design/spdx-model/spdx-helpers.md @@ -37,6 +37,8 @@ for invalid input rather than throwing. ### Dependencies +- **SpdxElement** — `SpdxElement.NoAssertion` constant used in `EnhanceString` fitness ranking to + identify `NOASSERTION` values. - **System.Text.RegularExpressions** — date-time validation regex. On .NET 7 and later, a source-generated `[GeneratedRegex]` is used for AOT safety; earlier targets use a cached `Regex` instance. diff --git a/docs/design/spdx-model/spdx-model.md b/docs/design/spdx-model/spdx-model.md index 970f34b..a9c7ecd 100644 --- a/docs/design/spdx-model/spdx-model.md +++ b/docs/design/spdx-model/spdx-model.md @@ -129,7 +129,8 @@ flowchart LR ### Design Constraints -- Targets `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0` simultaneously. +- Targets `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0` simultaneously; the library builds + and runs on Windows, Linux, and macOS. - Minimal runtime dependencies: relies on BCL/framework APIs where possible; compatibility NuGet packages used on older targets. - Nullable reference types enabled: all public API members declare nullability explicitly. diff --git a/docs/design/spdx-model/spdx-package.md b/docs/design/spdx-model/spdx-package.md index 446e351..4ce7b40 100644 --- a/docs/design/spdx-model/spdx-package.md +++ b/docs/design/spdx-model/spdx-package.md @@ -83,7 +83,11 @@ considered valid, in SPDX date-time format (`YYYY-MM-DDThh:mm:ssZ`); `null` if n - *Parameters*: `SpdxPackage other` — source of additional field values. - *Returns*: `void` - *Preconditions*: none. -- *Postconditions*: Empty or null fields in this instance are populated from `other`. +- *Postconditions*: Empty or null fields in this instance are populated from `other`. The nullable + `FilesAnalyzed` field is populated from `other` when null. Array fields `LicenseInfoFromFiles`, + `Checksums`, and `ExternalReferences` are merged by deduplication. The `HasFiles` array is + intentionally not merged because it contains document-scoped SPDX element IDs that may not be + valid across documents. **Enhance (static array merge)**: Merges two package arrays, matching on `Name` and `Version`. diff --git a/docs/design/spdx-model/spdx-relationship.md b/docs/design/spdx-model/spdx-relationship.md index 6910a4c..e1c8521 100644 --- a/docs/design/spdx-model/spdx-relationship.md +++ b/docs/design/spdx-model/spdx-relationship.md @@ -42,14 +42,17 @@ ID, and type. - *Preconditions*: none. - *Postconditions*: Matching entries are enhanced; new entries are appended. -**Validate**: Validates that the referenced element IDs exist in the owning document. +**Validate**: Validates the relationship fields and, when a document is provided, verifies that +referenced element IDs exist in that document. - *Parameters*: `List issues` — list to append issues to; `SpdxDocument? document` — - document for element ID resolution. + optional document for element ID resolution. - *Returns*: `void` - *Preconditions*: none. -- *Postconditions*: References to non-existent elements (that are not `NOASSERTION` or - `DocumentRef-`) are recorded in `issues`. +- *Postconditions*: An empty source ID, an empty related element ID, or a `Missing` relationship + type each produce a validation issue. When `document` is provided, source and related element + IDs that do not resolve to an element in that document also produce issues, unless the related + element is `NOASSERTION` or uses a `DocumentRef-` cross-document prefix. **Same**: `static IEqualityComparer` — compares by source ID, target ID, and relationship type. diff --git a/docs/design/spdx-model/spdx-snippet.md b/docs/design/spdx-model/spdx-snippet.md index 20bd6fd..c69c78b 100644 --- a/docs/design/spdx-model/spdx-snippet.md +++ b/docs/design/spdx-model/spdx-snippet.md @@ -41,7 +41,9 @@ the file, enabling granular compliance tracking for reused code segments. - *Parameters*: `SpdxSnippet other` — source of additional field values. - *Returns*: `void` - *Preconditions*: none. -- *Postconditions*: Empty or null fields are populated from `other`. +- *Postconditions*: Empty or null string fields are populated from `other`; integer fields with value + `0` are replaced by the corresponding non-zero value from `other`; `LicenseInfoInSnippet` is merged + by deduplication from both instances. **Enhance (static array merge)**: Merges two snippet arrays by matching on file SPDX ID and byte range. diff --git a/docs/design/spdx-model/transform/transform.md b/docs/design/spdx-model/transform/transform.md index c21ef40..71347a9 100644 --- a/docs/design/spdx-model/transform/transform.md +++ b/docs/design/spdx-model/transform/transform.md @@ -33,6 +33,8 @@ ones. (same source, target, and type) already exists it is enhanced; otherwise a deep copy is appended. - *Constraints*: The source element ID must exist in the document. The target element ID must either exist in the document, be `NOASSERTION`, or use the `DocumentRef-` prefix. +- *Atomicity*: When `ArgumentException` is thrown, the document is left in its original state — + no relationship is added or removed. #### Design diff --git a/docs/reqstream/ots/mstest.yaml b/docs/reqstream/ots/xunit.yaml similarity index 71% rename from docs/reqstream/ots/mstest.yaml rename to docs/reqstream/ots/xunit.yaml index 6712972..ec30389 100644 --- a/docs/reqstream/ots/mstest.yaml +++ b/docs/reqstream/ots/xunit.yaml @@ -1,15 +1,15 @@ --- -# MSTest OTS requirements +# xUnit v3 OTS requirements sections: - title: OTS Software Requirements sections: - - title: MSTest Requirements + - title: xUnit v3 Requirements requirements: - - id: SpdxModel-OTS-MSTest - title: MSTest shall execute unit tests and report results. + - id: SpdxModel-OTS-xUnit + title: xUnit v3 shall execute unit tests and report results. justification: | - MSTest (MSTest.TestFramework and MSTest.TestAdapter) is the unit-testing framework used + xUnit v3 (xunit.v3 and xunit.runner.visualstudio) is the unit-testing framework used by the project. It discovers and runs all test methods and writes TRX result files that feed into coverage reporting and requirements traceability. Passing tests confirm the framework is functioning correctly. diff --git a/docs/reqstream/spdx-model/spdx-package.yaml b/docs/reqstream/spdx-model/spdx-package.yaml index f295c71..5199cd4 100644 --- a/docs/reqstream/spdx-model/spdx-package.yaml +++ b/docs/reqstream/spdx-model/spdx-package.yaml @@ -42,15 +42,15 @@ sections: serialization or use by downstream consumers. tests: - SpdxPackage_Validate_Success - - SpdxPackage_Validate_MissingPackageName - - SpdxPackage_Validate_InvalidPackageId - - SpdxPackage_Validate_MissingDownload - - SpdxPackage_Validate_InvalidSupplier - - SpdxPackage_Validate_InvalidOriginator - - SpdxPackage_Validate_InvalidReleaseDate - - SpdxPackage_Validate_InvalidBuiltDate - - SpdxPackage_Validate_InvalidValidUntilDate - - SpdxPackage_Validate_InvalidAnnotation + - SpdxPackage_Validate_MissingPackageName_ReportsIssue + - SpdxPackage_Validate_InvalidPackageId_ReportsIssue + - SpdxPackage_Validate_MissingDownload_ReportsIssue + - SpdxPackage_Validate_InvalidSupplier_ReportsIssue + - SpdxPackage_Validate_InvalidOriginator_ReportsIssue + - SpdxPackage_Validate_InvalidReleaseDate_ReportsIssue + - SpdxPackage_Validate_InvalidBuiltDate_ReportsIssue + - SpdxPackage_Validate_InvalidValidUntilDate_ReportsIssue + - SpdxPackage_Validate_InvalidAnnotation_ReportsIssue - SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue - id: SpdxModel-Data-Package-ValidateNtia title: The library shall validate NTIA minimum element requirements for SPDX packages when requested. diff --git a/docs/reqstream/spdx-model/transform/spdx-relationships.yaml b/docs/reqstream/spdx-model/transform/spdx-relationships.yaml index 5b15933..0772bde 100644 --- a/docs/reqstream/spdx-model/transform/spdx-relationships.yaml +++ b/docs/reqstream/spdx-model/transform/spdx-relationships.yaml @@ -8,7 +8,7 @@ sections: sections: - title: SpdxRelationships Requirements requirements: - - id: SpdxModel-Transform-RelationshipUtilities-AddSingle + - id: SpdxModel-Transform-SpdxRelationships-AddSingle title: The library shall provide a utility to add a single relationship to an SPDX document. tags: - data-model @@ -21,7 +21,7 @@ sections: - SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship - SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship - - id: SpdxModel-Transform-RelationshipUtilities-AddMultiple + - id: SpdxModel-Transform-SpdxRelationships-AddMultiple title: >- The library shall provide a utility to batch-add relationships to an SPDX document with optional replacement. @@ -35,7 +35,7 @@ sections: - SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRelationships - SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRelationships - - id: SpdxModel-Transform-RelationshipUtilities-Validate + - id: SpdxModel-Transform-SpdxRelationships-Validate title: The library shall validate relationship element IDs before adding them to an SPDX document. tags: - data-model @@ -47,7 +47,7 @@ sections: - SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentException - SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified - - id: SpdxModel-Transform-RelationshipUtilities-Atomicity + - id: SpdxModel-Transform-SpdxRelationships-Atomicity title: The library shall guarantee that a failed batch-add leaves the document in its original state. tags: - data-model diff --git a/docs/reqstream/spdx-model/transform/transform.yaml b/docs/reqstream/spdx-model/transform/transform.yaml index 4f4f33c..e5a6b20 100644 --- a/docs/reqstream/spdx-model/transform/transform.yaml +++ b/docs/reqstream/spdx-model/transform/transform.yaml @@ -15,9 +15,9 @@ sections: relationship management. This supports use cases where documents need to be modified or enriched after initial creation. children: - - SpdxModel-Transform-RelationshipUtilities-AddSingle - - SpdxModel-Transform-RelationshipUtilities-AddMultiple - - SpdxModel-Transform-RelationshipUtilities-Validate - - SpdxModel-Transform-RelationshipUtilities-Atomicity + - SpdxModel-Transform-SpdxRelationships-AddSingle + - SpdxModel-Transform-SpdxRelationships-AddMultiple + - SpdxModel-Transform-SpdxRelationships-Validate + - SpdxModel-Transform-SpdxRelationships-Atomicity tests: - SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists diff --git a/docs/user_guide/troubleshooting.md b/docs/user_guide/troubleshooting.md index e088d4b..0d5dcb7 100644 --- a/docs/user_guide/troubleshooting.md +++ b/docs/user_guide/troubleshooting.md @@ -35,6 +35,10 @@ var document = new SpdxDocument Always handle potential errors when reading SPDX documents from untrusted sources: ```csharp +using System.IO; +using System.Text.Json; +using DemaConsulting.SpdxModel.IO; + try { var json = File.ReadAllText("sbom.spdx.json"); diff --git a/docs/user_guide/usage.md b/docs/user_guide/usage.md index 89d2f10..0d4bc7d 100644 --- a/docs/user_guide/usage.md +++ b/docs/user_guide/usage.md @@ -36,6 +36,7 @@ var document = new SpdxDocument Id = "SPDXRef-DOCUMENT", Name = "My Software", Version = "SPDX-2.3", + DataLicense = "CC0-1.0", DocumentNamespace = "https://example.com/my-software/1.0.0", CreationInformation = new SpdxCreationInformation { diff --git a/docs/verification/introduction.md b/docs/verification/introduction.md index ba3e1ec..0aec151 100644 --- a/docs/verification/introduction.md +++ b/docs/verification/introduction.md @@ -20,7 +20,7 @@ The following items are in scope for this document: The following items are out of scope: -- OTS item verification: MSTest, ReqStream, BuildMark, VersionMark, SarifMark, SonarMark, +- OTS item verification: xUnit v3, ReqStream, BuildMark, VersionMark, SarifMark, SonarMark, ReviewMark, FileAssert, Pandoc, WeasyPrint - Test infrastructure and test helpers @@ -33,6 +33,16 @@ The following companion artifacts are related to this verification document: - Production source code is located in `src/DemaConsulting.SpdxModel/` - Automated test suite is located in `test/DemaConsulting.SpdxModel.Tests/` +## Structural Deviation + +The companion artifact layout described in this document places subsystem verification files at +the subsystem level (e.g., `docs/verification/spdx-model/io/` for the IO subsystem). In practice, +subsystem verification files (`transform.md`, `io.md`) and their children are located inside their +respective subsystem subfolders rather than at the parent `spdx-model/` level. This deviation +mirrors the layout adopted in the design documentation and is accepted as a project-wide structural +deviation. Existing file references in review-sets and traceability tooling reflect the actual folder +layout and do not require updating. + ## References - [REF-1] SpdxModel releases, diff --git a/docs/verification/spdx-model.md b/docs/verification/spdx-model.md index 4876851..66cf42b 100644 --- a/docs/verification/spdx-model.md +++ b/docs/verification/spdx-model.md @@ -2,7 +2,7 @@ ## Verification Approach -The SpdxModel library is verified through automated unit and integration tests using the MSTest +The SpdxModel library is verified through automated unit and integration tests using the xUnit v3 framework. Tests are organized in `test/DemaConsulting.SpdxModel.Tests/`. Unit tests verify individual data model classes in isolation; integration tests verify the IO subsystem end-to-end and the Transform subsystem with real document instances. No external dependencies are mocked. @@ -54,3 +54,13 @@ validation error messages. **SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNullable**: Verifies that required fields on key SPDX data model types are non-nullable string types with default empty values, and that optional fields are nullable. + +**SpdxModel_Helpers_DateTimeValidation_IsObservableThroughDocumentModel**: Verifies that the +date-time validation utility (`SpdxHelpers.IsValidSpdxDateTime`) is exercised through the +document model by confirming that an invalid creation date is caught by document-level +validation. Linked from `SpdxModel-Data-Helpers`. + +**SpdxModel_Transform_AddRelationship_IsObservableThroughDocumentModel**: Verifies that the +`AddRelationship` transform utility correctly adds a new relationship to an SPDX document and +that the addition is observable through the document model's relationship collection. Linked +from `SpdxModel-Transform`. diff --git a/docs/verification/spdx-model/io/io.md b/docs/verification/spdx-model/io/io.md index e391f8b..440011c 100644 --- a/docs/verification/spdx-model/io/io.md +++ b/docs/verification/spdx-model/io/io.md @@ -2,7 +2,7 @@ #### Verification Approach -The IO subsystem is verified through automated integration tests using the MSTest framework. +The IO subsystem is verified through automated integration tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs`. Integration tests verify the IO subsystem end-to-end using real JSON input and output. System.Text.Json is not mocked as JSON parsing is part of the verification scope. diff --git a/docs/verification/spdx-model/io/spdx-2-json-deserializer.md b/docs/verification/spdx-model/io/spdx-2-json-deserializer.md index a8545f1..7887ae4 100644 --- a/docs/verification/spdx-model/io/spdx-2-json-deserializer.md +++ b/docs/verification/spdx-model/io/spdx-2-json-deserializer.md @@ -2,7 +2,7 @@ #### Verification Approach -Spdx2JsonDeserializer is verified through automated unit tests using the MSTest framework. +Spdx2JsonDeserializer is verified through automated unit tests using the xUnit v3 framework. Tests are located in the IO subdirectory under `test/DemaConsulting.SpdxModel.Tests/`. Each test provides representative JSON input and verifies the deserialized SPDX model objects match the expected values. System.Text.Json diff --git a/docs/verification/spdx-model/io/spdx-2-json-serializer.md b/docs/verification/spdx-model/io/spdx-2-json-serializer.md index 98ddffd..3352121 100644 --- a/docs/verification/spdx-model/io/spdx-2-json-serializer.md +++ b/docs/verification/spdx-model/io/spdx-2-json-serializer.md @@ -2,7 +2,7 @@ #### Verification Approach -Spdx2JsonSerializer is verified through automated unit tests using the MSTest framework. +Spdx2JsonSerializer is verified through automated unit tests using the xUnit v3 framework. Tests are located in the IO subdirectory under `test/DemaConsulting.SpdxModel.Tests/`. Each test constructs the relevant SPDX model objects directly and verifies the serialized JSON output. System.Text.Json is not mocked diff --git a/docs/verification/spdx-model/spdx-annotation.md b/docs/verification/spdx-model/spdx-annotation.md index 7d27868..6890ee2 100644 --- a/docs/verification/spdx-model/spdx-annotation.md +++ b/docs/verification/spdx-model/spdx-annotation.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxAnnotation is verified through automated unit tests using the MSTest framework. Tests are +SpdxAnnotation is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs`. Each test constructs an SpdxAnnotation instance directly and exercises the method under test with no mocked dependencies. diff --git a/docs/verification/spdx-model/spdx-checksum.md b/docs/verification/spdx-model/spdx-checksum.md index 2daa9dd..811c00f 100644 --- a/docs/verification/spdx-model/spdx-checksum.md +++ b/docs/verification/spdx-model/spdx-checksum.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxChecksum is verified through automated unit tests using the MSTest framework. Tests are +SpdxChecksum is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs`. Each test constructs an SpdxChecksum instance directly and exercises the method under test with no mocked dependencies. diff --git a/docs/verification/spdx-model/spdx-creation-information.md b/docs/verification/spdx-model/spdx-creation-information.md index ad5a239..12b20c3 100644 --- a/docs/verification/spdx-model/spdx-creation-information.md +++ b/docs/verification/spdx-model/spdx-creation-information.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxCreationInformation is verified through automated unit tests using the MSTest framework. +SpdxCreationInformation is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs`. Each test constructs an SpdxCreationInformation instance directly and exercises the method under test with no diff --git a/docs/verification/spdx-model/spdx-document.md b/docs/verification/spdx-model/spdx-document.md index 2e9d2a5..5d3324c 100644 --- a/docs/verification/spdx-model/spdx-document.md +++ b/docs/verification/spdx-model/spdx-document.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxDocument is verified through automated unit tests using the MSTest framework. Tests are +SpdxDocument is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs`. Each test constructs an SpdxDocument instance directly and exercises the method under test with no mocked dependencies. diff --git a/docs/verification/spdx-model/spdx-element.md b/docs/verification/spdx-model/spdx-element.md index 0907385..11a9af4 100644 --- a/docs/verification/spdx-model/spdx-element.md +++ b/docs/verification/spdx-model/spdx-element.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxElement is verified through automated unit tests using the MSTest framework. Tests are +SpdxElement is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs`. Each test exercises the element ID validation logic directly with no mocked dependencies. diff --git a/docs/verification/spdx-model/spdx-external-document-reference.md b/docs/verification/spdx-model/spdx-external-document-reference.md index 9655ae3..55b4c97 100644 --- a/docs/verification/spdx-model/spdx-external-document-reference.md +++ b/docs/verification/spdx-model/spdx-external-document-reference.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxExternalDocumentReference is verified through automated unit tests using the MSTest +SpdxExternalDocumentReference is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs`. Each test constructs an SpdxExternalDocumentReference instance directly and exercises the method under diff --git a/docs/verification/spdx-model/spdx-external-reference.md b/docs/verification/spdx-model/spdx-external-reference.md index d15a977..b9fad8c 100644 --- a/docs/verification/spdx-model/spdx-external-reference.md +++ b/docs/verification/spdx-model/spdx-external-reference.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxExternalReference is verified through automated unit tests using the MSTest framework. +SpdxExternalReference is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs`. Each test constructs an SpdxExternalReference instance directly and exercises the method under test with no @@ -34,7 +34,7 @@ This scenario is tested by `SpdxExternalReference_Enhance_WithMatchingAndNewEntries_MergesCorrectly`. **SpdxExternalReference_Validate_InvalidCategory_ReportsIssue**: Verifies that validation reports an issue -when the reference category is set to an unrecognized value. +when the reference category is `SpdxReferenceCategory.Missing`. This scenario is tested by `SpdxExternalReference_Validate_InvalidCategory_ReportsIssue`. **SpdxExternalReference_Validate_InvalidType_ReportsIssue**: Verifies that validation reports an issue when diff --git a/docs/verification/spdx-model/spdx-extracted-licensing-info.md b/docs/verification/spdx-model/spdx-extracted-licensing-info.md index 5ff9813..cf4c20c 100644 --- a/docs/verification/spdx-model/spdx-extracted-licensing-info.md +++ b/docs/verification/spdx-model/spdx-extracted-licensing-info.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxExtractedLicensingInfo is verified through automated unit tests using the MSTest +SpdxExtractedLicensingInfo is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs`. Each test constructs an SpdxExtractedLicensingInfo instance directly and exercises the method under diff --git a/docs/verification/spdx-model/spdx-file.md b/docs/verification/spdx-model/spdx-file.md index 6af1761..ae43866 100644 --- a/docs/verification/spdx-model/spdx-file.md +++ b/docs/verification/spdx-model/spdx-file.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxFile is verified through automated unit tests using the MSTest framework. Tests are +SpdxFile is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs`. Each test constructs an SpdxFile instance directly and exercises the method under test with no mocked dependencies. diff --git a/docs/verification/spdx-model/spdx-helpers.md b/docs/verification/spdx-model/spdx-helpers.md index 056f4eb..10df47f 100644 --- a/docs/verification/spdx-model/spdx-helpers.md +++ b/docs/verification/spdx-model/spdx-helpers.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxHelpers is verified through automated unit tests using the MSTest framework. +SpdxHelpers is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs`. Each test exercises the helper method directly with no mocked dependencies. Additional coverage is provided implicitly @@ -20,29 +20,22 @@ All automated tests pass with zero failures. **SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue**: Verifies that `IsValidSpdxDateTime` returns `true` when passed a null value, since null represents a not-set field which is valid. -This scenario is tested by `SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue`. **SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue**: Verifies that `IsValidSpdxDateTime` returns `true` when passed an empty string, since an empty string represents a not-set field which is valid. -This scenario is tested by `SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue`. **SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue**: Verifies that `IsValidSpdxDateTime` returns `true` when passed a correctly formatted ISO 8601 UTC timestamp such as `"2024-01-01T00:00:00Z"`. -This scenario is tested by `SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue`. **SpdxHelpers_IsValidSpdxDateTime_InvalidFormat_ReturnsFalse**: Verifies that `IsValidSpdxDateTime` returns `false` when passed a string that does not match the ISO 8601 UTC format. -This scenario is tested by `SpdxHelpers_IsValidSpdxDateTime_InvalidFormat_ReturnsFalse`. **SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue**: Verifies that `EnhanceString` returns the concrete value when given a mix of a concrete value and `NOASSERTION`. -This scenario is tested by -`SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue`. **SpdxHelpers_EnhanceString_NullInputs_ReturnsNull**: Verifies that `EnhanceString` returns `null` when all inputs are null. -This scenario is tested by `SpdxHelpers_EnhanceString_NullInputs_ReturnsNull`. diff --git a/docs/verification/spdx-model/spdx-license-element.md b/docs/verification/spdx-model/spdx-license-element.md index 23732ff..d9c7486 100644 --- a/docs/verification/spdx-model/spdx-license-element.md +++ b/docs/verification/spdx-model/spdx-license-element.md @@ -2,20 +2,43 @@ ### Verification Approach -N/A — SpdxLicenseElement is an abstract base class that defines common license-related -properties shared by concrete license classes. No dedicated unit test file exists for -SpdxLicenseElement. Correctness is verified implicitly through the unit tests of its -concrete subclasses. +`SpdxLicenseElement` is an abstract base class; it cannot be instantiated directly. Its +logic is exercised through `SpdxPackage`, the simplest concrete subclass. All unit tests +live in `SpdxLicenseElementTests` within the `DemaConsulting.SpdxModel.Tests` project. +Subclass-level deep-copy tests (for `SpdxPackage`, `SpdxFile`, and `SpdxSnippet`) provide +additional evidence that the inherited fields are stored and copied correctly. ### Test Environment -N/A - standard test environment. +Standard test environment — no external services, files, or special configuration required. +Tests run with `dotnet test` under the standard CI pipeline. ### Acceptance Criteria -All automated tests for subclass units pass with zero failures. +All automated tests in `SpdxLicenseElementTests` pass with zero failures, and all +subclass tests that exercise the inherited license-element fields (deep-copy and enhance) +also pass with zero failures. ### Test Scenarios -N/A — no dedicated unit tests exist; SpdxLicenseElement is verified implicitly through -higher-level tests of its concrete subclasses. +The following scenarios are covered by `SpdxLicenseElementTests`: + +- **Empty and null fields replaced by concrete values**: verifies that `ConcludedLicense`, + `CopyrightText`, and `LicenseComments` set to empty string or null (fitness 0/1) are + replaced when the source carries concrete (rank-3) values. + +- **NOASSERTION fields replaced by concrete values**: verifies that `ConcludedLicense`, + `CopyrightText`, and `LicenseComments` set to `NOASSERTION` (fitness 2) are replaced + when the source carries concrete (rank-3) values. + +- **Concrete fields not replaced by secondary values**: verifies that `ConcludedLicense`, + `CopyrightText`, and `LicenseComments` already holding concrete values are not overwritten + by any secondary value regardless of its fitness level. + +- **Attribution text merged by deduplication**: verifies that unique entries from the + secondary `AttributionText` array are appended while duplicate entries are discarded, so + each attribution notice appears exactly once in the merged result. + +- **Annotations merged by identity-match and append**: verifies that annotations matching an + existing entry (same annotator, date, type, and comment) are recognized as duplicates and + that annotations with no matching entry are appended as independent deep copies. diff --git a/docs/verification/spdx-model/spdx-model.md b/docs/verification/spdx-model/spdx-model.md index d727de7..173b2ab 100644 --- a/docs/verification/spdx-model/spdx-model.md +++ b/docs/verification/spdx-model/spdx-model.md @@ -2,7 +2,7 @@ ### Verification Approach -The SpdxModel library is verified through automated unit and integration tests using the MSTest +The SpdxModel library is verified through automated unit and integration tests using the xUnit v3 framework. Tests are organized in `test/DemaConsulting.SpdxModel.Tests/`. Unit tests verify individual data model classes in isolation; integration tests verify the IO subsystem end-to-end and the Transform subsystem with real document instances. No external dependencies are mocked. diff --git a/docs/verification/spdx-model/spdx-package-verification-code.md b/docs/verification/spdx-model/spdx-package-verification-code.md index 91688b8..6e3ab06 100644 --- a/docs/verification/spdx-model/spdx-package-verification-code.md +++ b/docs/verification/spdx-model/spdx-package-verification-code.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxPackageVerificationCode is verified through automated unit tests using the MSTest +SpdxPackageVerificationCode is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs`. Each test constructs an SpdxPackageVerificationCode instance directly and exercises the method under diff --git a/docs/verification/spdx-model/spdx-package.md b/docs/verification/spdx-model/spdx-package.md index 0382ffc..9714b5f 100644 --- a/docs/verification/spdx-model/spdx-package.md +++ b/docs/verification/spdx-model/spdx-package.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxPackage is verified through automated unit tests using the MSTest framework. Tests are +SpdxPackage is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs`. Each test constructs an SpdxPackage instance directly and exercises the method under test with no mocked dependencies. @@ -36,42 +36,42 @@ This scenario is tested by `SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly`. all validation checks without reporting any issues. This scenario is tested by `SpdxPackage_Validate_Success`. -**SpdxPackage_Validate_MissingPackageName**: Verifies that validation reports an issue when +**SpdxPackage_Validate_MissingPackageName_ReportsIssue**: Verifies that validation reports an issue when the package name field is missing or empty. -This scenario is tested by `SpdxPackage_Validate_MissingPackageName`. +This scenario is tested by `SpdxPackage_Validate_MissingPackageName_ReportsIssue`. -**SpdxPackage_Validate_InvalidPackageId**: Verifies that validation reports an issue when +**SpdxPackage_Validate_InvalidPackageId_ReportsIssue**: Verifies that validation reports an issue when the package SPDX-ID does not conform to the required SPDXRef- prefix format. -This scenario is tested by `SpdxPackage_Validate_InvalidPackageId`. +This scenario is tested by `SpdxPackage_Validate_InvalidPackageId_ReportsIssue`. -**SpdxPackage_Validate_MissingDownload**: Verifies that validation reports an issue when the +**SpdxPackage_Validate_MissingDownload_ReportsIssue**: Verifies that validation reports an issue when the download location field is missing or empty. -This scenario is tested by `SpdxPackage_Validate_MissingDownload`. +This scenario is tested by `SpdxPackage_Validate_MissingDownload_ReportsIssue`. -**SpdxPackage_Validate_InvalidSupplier**: Verifies that validation reports an issue when the +**SpdxPackage_Validate_InvalidSupplier_ReportsIssue**: Verifies that validation reports an issue when the supplier field is present but does not conform to the required organization or tool format. -This scenario is tested by `SpdxPackage_Validate_InvalidSupplier`. +This scenario is tested by `SpdxPackage_Validate_InvalidSupplier_ReportsIssue`. -**SpdxPackage_Validate_InvalidOriginator**: Verifies that validation reports an issue when +**SpdxPackage_Validate_InvalidOriginator_ReportsIssue**: Verifies that validation reports an issue when the originator field is present but does not conform to the required organization or tool format. -This scenario is tested by `SpdxPackage_Validate_InvalidOriginator`. +This scenario is tested by `SpdxPackage_Validate_InvalidOriginator_ReportsIssue`. -**SpdxPackage_Validate_InvalidReleaseDate**: Verifies that validation reports an issue when +**SpdxPackage_Validate_InvalidReleaseDate_ReportsIssue**: Verifies that validation reports an issue when the release date field is present but does not conform to ISO 8601 date-time format. -This scenario is tested by `SpdxPackage_Validate_InvalidReleaseDate`. +This scenario is tested by `SpdxPackage_Validate_InvalidReleaseDate_ReportsIssue`. -**SpdxPackage_Validate_InvalidBuiltDate**: Verifies that validation reports an issue when the +**SpdxPackage_Validate_InvalidBuiltDate_ReportsIssue**: Verifies that validation reports an issue when the built date field is present but does not conform to ISO 8601 date-time format. -This scenario is tested by `SpdxPackage_Validate_InvalidBuiltDate`. +This scenario is tested by `SpdxPackage_Validate_InvalidBuiltDate_ReportsIssue`. -**SpdxPackage_Validate_InvalidValidUntilDate**: Verifies that validation reports an issue when +**SpdxPackage_Validate_InvalidValidUntilDate_ReportsIssue**: Verifies that validation reports an issue when the valid-until date field is present but does not conform to ISO 8601 date-time format. -This scenario is tested by `SpdxPackage_Validate_InvalidValidUntilDate`. +This scenario is tested by `SpdxPackage_Validate_InvalidValidUntilDate_ReportsIssue`. -**SpdxPackage_Validate_InvalidAnnotation**: Verifies that validation reports an issue when an +**SpdxPackage_Validate_InvalidAnnotation_ReportsIssue**: Verifies that validation reports an issue when an annotation within the package contains invalid fields. -This scenario is tested by `SpdxPackage_Validate_InvalidAnnotation`. +This scenario is tested by `SpdxPackage_Validate_InvalidAnnotation_ReportsIssue`. **SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue**: Verifies that validation reports an issue when the HasFiles array references a file ID that does not exist in the diff --git a/docs/verification/spdx-model/spdx-relationship.md b/docs/verification/spdx-model/spdx-relationship.md index 4e4fac7..bc59678 100644 --- a/docs/verification/spdx-model/spdx-relationship.md +++ b/docs/verification/spdx-model/spdx-relationship.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxRelationship is verified through automated unit tests using the MSTest framework. Tests +SpdxRelationship is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs`. Each test constructs an SpdxRelationship instance directly and exercises the method under test with no mocked dependencies. diff --git a/docs/verification/spdx-model/spdx-snippet.md b/docs/verification/spdx-model/spdx-snippet.md index 23946b7..ef957dd 100644 --- a/docs/verification/spdx-model/spdx-snippet.md +++ b/docs/verification/spdx-model/spdx-snippet.md @@ -2,7 +2,7 @@ ### Verification Approach -SpdxSnippet is verified through automated unit tests using the MSTest framework. Tests are +SpdxSnippet is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs`. Each test constructs an SpdxSnippet instance directly and exercises the method under test with no mocked dependencies. diff --git a/docs/verification/spdx-model/transform/spdx-relationships.md b/docs/verification/spdx-model/transform/spdx-relationships.md index d2f3e2c..ae9126d 100644 --- a/docs/verification/spdx-model/transform/spdx-relationships.md +++ b/docs/verification/spdx-model/transform/spdx-relationships.md @@ -2,7 +2,7 @@ #### Verification Approach -SpdxRelationships is verified through automated unit tests using the MSTest framework. Tests +SpdxRelationships is verified through automated unit tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs`. Each test constructs an SpdxDocument with a known set of relationships and exercises the SpdxRelationships methods directly with no mocked dependencies. diff --git a/docs/verification/spdx-model/transform/transform.md b/docs/verification/spdx-model/transform/transform.md index 93b4110..2081a62 100644 --- a/docs/verification/spdx-model/transform/transform.md +++ b/docs/verification/spdx-model/transform/transform.md @@ -2,7 +2,7 @@ #### Verification Approach -The Transform subsystem is verified through automated integration tests using the MSTest +The Transform subsystem is verified through automated integration tests using the xUnit v3 framework. Tests are located in `test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs`. Integration tests verify Transform operations using real SpdxDocument instances with no mocked dependencies. diff --git a/requirements.yaml b/requirements.yaml index 3287605..2b297a3 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -28,7 +28,7 @@ includes: - docs/reqstream/spdx-model/spdx-package-verification-code.yaml - docs/reqstream/spdx-model/spdx-relationship.yaml - docs/reqstream/spdx-model/spdx-snippet.yaml - - docs/reqstream/ots/mstest.yaml + - docs/reqstream/ots/xunit.yaml - docs/reqstream/ots/reqstream.yaml - docs/reqstream/ots/buildmark.yaml - docs/reqstream/ots/versionmark.yaml diff --git a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs index 7e94151..8fbcaed 100644 --- a/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs +++ b/src/DemaConsulting.SpdxModel/IO/Spdx2JsonSerializer.cs @@ -37,14 +37,17 @@ namespace DemaConsulting.SpdxModel.IO; public static class Spdx2JsonSerializer { /// - /// Serialize SPDX Document + /// Serialize an SPDX Document to an indented JSON string. /// /// /// Delegates to then converts the resulting /// to an indented JSON string. /// - /// SPDX Document - /// Json string + /// SPDX Document to serialize. Must not be null. + /// + /// Indented JSON string conforming to the SPDX 2.3 schema. All optional fields + /// absent from the model are omitted from the output. + /// public static string Serialize(SpdxDocument document) { // Serialize the document @@ -60,7 +63,7 @@ public static string Serialize(SpdxDocument document) } /// - /// Serialize SPDX Document + /// Serialize an SPDX Document to a JSON object DOM. /// /// /// Top-level arrays (files, packages, snippets, relationships) @@ -68,8 +71,11 @@ public static string Serialize(SpdxDocument document) /// Optional arrays (e.g., externalDocumentRefs, annotations) are omitted /// when empty. /// - /// SPDX Document - /// Json object + /// SPDX Document to serialize. Must not be null. + /// + /// A representing the root SPDX document + /// element with all mandatory top-level arrays populated. + /// public static JsonObject SerializeDocument(SpdxDocument document) { var json = new JsonObject(); @@ -112,8 +118,8 @@ public static JsonObject SerializeDocument(SpdxDocument document) /// Serializes the creation information object including optional creators array, /// creation date, and optional comment and license list version fields. /// - /// SPDX Creation Information - /// JSON object + /// SPDX Creation Information to serialize. Must not be null. + /// A containing the creation information fields. public static JsonObject SerializeCreationInformation(SpdxCreationInformation info) { var json = new JsonObject(); @@ -131,8 +137,11 @@ public static JsonObject SerializeCreationInformation(SpdxCreationInformation in /// Delegates to for each element. /// Returns a of external document reference objects. /// - /// SPDX External Document References - /// JSON array + /// SPDX External Document References to serialize. Must not be null. + /// + /// A containing one serialized + /// per external document reference. + /// public static JsonArray SerializeExternalDocumentReferences(SpdxExternalDocumentReference[] references) { var json = new JsonArray(); @@ -150,8 +159,8 @@ public static JsonArray SerializeExternalDocumentReferences(SpdxExternalDocument /// /// Serializes a single external document reference including its nested checksum object. /// - /// SPDX External Document Reference - /// JSON object + /// SPDX External Document Reference to serialize. Must not be null. + /// A containing the external document reference fields. public static JsonObject SerializeExternalDocumentReference(SpdxExternalDocumentReference reference) { var json = new JsonObject(); @@ -167,8 +176,11 @@ public static JsonObject SerializeExternalDocumentReference(SpdxExternalDocument /// /// Delegates to for each element. /// - /// SPDX Extracted Licensing Infos - /// JSON array + /// SPDX Extracted Licensing Infos to serialize. Must not be null. + /// + /// A containing one serialized + /// per extracted licensing info entry. + /// public static JsonArray SerializeExtractedLicensingInfos(SpdxExtractedLicensingInfo[] infos) { var json = new JsonArray(); @@ -186,8 +198,8 @@ public static JsonArray SerializeExtractedLicensingInfos(SpdxExtractedLicensingI /// /// Serializes a single extracted licensing info entry; optional fields are omitted when null or empty. /// - /// SPDX Extracted Licensing Info - /// JSON object + /// SPDX Extracted Licensing Info to serialize. Must not be null. + /// A containing the extracted licensing info fields. public static JsonObject SerializeExtractedLicensingInfo(SpdxExtractedLicensingInfo info) { var json = new JsonObject(); @@ -206,8 +218,11 @@ public static JsonObject SerializeExtractedLicensingInfo(SpdxExtractedLicensingI /// Delegates to for each element. Always emits at the /// document level even when the array is empty. /// - /// SPDX Files - /// JSON array + /// SPDX Files to serialize. Must not be null. + /// + /// A containing one serialized + /// per file entry. + /// public static JsonArray SerializeFiles(SpdxFile[] files) { var json = new JsonArray(); @@ -226,8 +241,8 @@ public static JsonArray SerializeFiles(SpdxFile[] files) /// Serializes a single SPDX file entry including file types, checksums, and optional /// annotations. The annotations sub-array is omitted when empty. /// - /// SPDX File - /// JSON object + /// SPDX File to serialize. Must not be null. + /// A containing all file fields. public static JsonObject SerializeFile(SpdxFile file) { var json = new JsonObject(); @@ -259,8 +274,11 @@ public static JsonObject SerializeFile(SpdxFile file) /// Delegates to for each element. Always emits at the /// document level even when the array is empty. /// - /// SPDX Packages - /// JSON array + /// SPDX Packages to serialize. Must not be null. + /// + /// A containing one serialized + /// per package entry. + /// public static JsonArray SerializePackages(SpdxPackage[] packages) { var json = new JsonArray(); @@ -280,8 +298,8 @@ public static JsonArray SerializePackages(SpdxPackage[] packages) /// when null; the verification code, external references, and annotations sub-objects /// are omitted when absent or empty. /// - /// SPDX Package - /// JSON object + /// SPDX Package to serialize. Must not be null. + /// A containing all package fields. public static JsonObject SerializePackage(SpdxPackage package) { var json = new JsonObject(); @@ -339,8 +357,11 @@ public static JsonObject SerializePackage(SpdxPackage package) /// Delegates to for each element. Always emits at the /// document level even when the array is empty. /// - /// SPDX Snippets - /// JSON array + /// SPDX Snippets to serialize. Must not be null. + /// + /// A containing one serialized + /// per snippet entry. + /// public static JsonArray SerializeSnippets(SpdxSnippet[] snippets) { var json = new JsonArray(); @@ -361,8 +382,11 @@ public static JsonArray SerializeSnippets(SpdxSnippet[] snippets) /// are non-zero; if either is zero the /// line-range entry is omitted entirely. /// - /// SPDX Snippet - /// JSON object + /// SPDX Snippet to serialize. Must not be null. + /// + /// A containing all snippet fields including + /// the ranges array. + /// public static JsonObject SerializeSnippet(SpdxSnippet snippet) { var json = new JsonObject(); @@ -425,8 +449,11 @@ public static JsonObject SerializeSnippet(SpdxSnippet snippet) /// Delegates to for each element. Always emits at the /// document level even when the array is empty. /// - /// SPDX Relationships - /// JSON array + /// SPDX Relationships to serialize. Must not be null. + /// + /// A containing one serialized + /// per relationship entry. + /// public static JsonArray SerializeRelationships(SpdxRelationship[] relationships) { var json = new JsonArray(); @@ -445,8 +472,8 @@ public static JsonArray SerializeRelationships(SpdxRelationship[] relationships) /// Serializes a single SPDX relationship. The optional comment field is omitted when null /// or empty. /// - /// SPDX Relationship - /// JSON object + /// SPDX Relationship to serialize. Must not be null. + /// A containing the relationship fields. public static JsonObject SerializeRelationship(SpdxRelationship relationship) { var json = new JsonObject(); @@ -464,8 +491,11 @@ public static JsonObject SerializeRelationship(SpdxRelationship relationship) /// Serializes the package verification code object including the hash value and optional /// excluded files array. /// - /// SPDX Package Verification Code - /// JSON object + /// SPDX Package Verification Code to serialize. Must not be null. + /// + /// A containing the verification code value + /// and optional excluded files array. + /// public static JsonObject SerializeVerificationCode(SpdxPackageVerificationCode code) { var json = new JsonObject(); @@ -480,8 +510,11 @@ public static JsonObject SerializeVerificationCode(SpdxPackageVerificationCode c /// /// Delegates to for each element. /// - /// SPDX External References - /// JSON array + /// SPDX External References to serialize. Must not be null. + /// + /// A containing one serialized + /// per external reference entry. + /// public static JsonArray SerializeExternalReferences(SpdxExternalReference[] references) { var json = new JsonArray(); @@ -500,8 +533,8 @@ public static JsonArray SerializeExternalReferences(SpdxExternalReference[] refe /// Serializes a single external reference entry including category, type, locator, and /// optional comment. /// - /// SPDX External Reference - /// JSON object + /// SPDX External Reference to serialize. Must not be null. + /// A containing the external reference fields. public static JsonObject SerializeExternalReference(SpdxExternalReference reference) { var json = new JsonObject(); @@ -518,8 +551,11 @@ public static JsonObject SerializeExternalReference(SpdxExternalReference refere /// /// Delegates to for each element. /// - /// SPDX Checksums - /// JSON array + /// SPDX Checksums to serialize. Must not be null. + /// + /// A containing one serialized + /// per checksum entry. + /// public static JsonArray SerializeChecksums(SpdxChecksum[] checksums) { var json = new JsonArray(); @@ -537,8 +573,8 @@ public static JsonArray SerializeChecksums(SpdxChecksum[] checksums) /// /// Serializes a single checksum entry with its algorithm and value fields. /// - /// SPDX Checksum - /// JSON object + /// SPDX Checksum to serialize. Must not be null. + /// A containing the algorithm and checksumValue fields. public static JsonObject SerializeChecksum(SpdxChecksum checksum) { var json = new JsonObject(); @@ -553,8 +589,11 @@ public static JsonObject SerializeChecksum(SpdxChecksum checksum) /// /// Delegates to for each element. /// - /// SPDX Annotations - /// JSON array + /// SPDX Annotations to serialize. Must not be null. + /// + /// A containing one serialized + /// per annotation entry. + /// public static JsonArray SerializeAnnotations(SpdxAnnotation[] annotations) { var json = new JsonArray(); @@ -574,8 +613,8 @@ public static JsonArray SerializeAnnotations(SpdxAnnotation[] annotations) /// is null or empty, because annotations on sub-elements often do not carry their own /// SPDX ID. /// - /// SPDX Annotation to serialize - /// JSON object + /// SPDX Annotation to serialize. Must not be null. + /// A containing all annotation fields. public static JsonObject SerializeAnnotation(SpdxAnnotation annotation) { var json = new JsonObject(); @@ -594,9 +633,9 @@ public static JsonObject SerializeAnnotation(SpdxAnnotation annotation) /// Always writes the property, even for empty strings. Use /// for fields that should be omitted when empty. /// - /// JSON object - /// Property name - /// Property value + /// JSON object to write the property into. Must not be null. + /// Property name key. Must not be null or empty. + /// Property value to write. private static void EmitString(JsonNode json, string name, string value) { json[name] = value; @@ -609,9 +648,9 @@ private static void EmitString(JsonNode json, string name, string value) /// Omits the property entirely when is null or empty, consistent /// with the serializer convention of not writing null or empty optional fields. /// - /// JSON object - /// Property name - /// Optional property value + /// JSON object to write the property into. Must not be null. + /// Property name key. Must not be null or empty. + /// Optional property value; null or empty causes the property to be omitted. private static void EmitOptionalString(JsonNode json, string name, string? value) { // Skip if empty @@ -630,9 +669,9 @@ private static void EmitOptionalString(JsonNode json, string name, string? value /// Omits the property entirely when is empty, consistent /// with the serializer convention of not writing null or empty optional fields. /// - /// JSON object to write into - /// Property name - /// Array of values; omitted when empty + /// JSON object to write into. Must not be null. + /// Property name key. Must not be null or empty. + /// Array of string values; an empty array causes the property to be omitted. private static void EmitOptionalStrings(JsonNode json, string name, string[] values) { // Skip if empty diff --git a/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs b/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs index 626ad4f..69c0943 100644 --- a/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs +++ b/src/DemaConsulting.SpdxModel/SpdxAnnotation.cs @@ -21,10 +21,14 @@ namespace DemaConsulting.SpdxModel; /// -/// SPDX Annotation class +/// Represents an SPDX annotation — a review or informational comment attached to an SPDX element. /// /// -/// An Annotation is a comment on an SpdxItem by an agent. +/// Annotations support compliance workflows where reviewers document findings and decisions about +/// software components. Each annotation records who made it (), when +/// (), what category it belongs to (), and the free-text +/// content (). See the SpdxAnnotation Design for the full data +/// model and method descriptions. /// public sealed class SpdxAnnotation : SpdxElement { diff --git a/src/DemaConsulting.SpdxModel/SpdxChecksum.cs b/src/DemaConsulting.SpdxModel/SpdxChecksum.cs index f46aaa7..f8a1d07 100644 --- a/src/DemaConsulting.SpdxModel/SpdxChecksum.cs +++ b/src/DemaConsulting.SpdxModel/SpdxChecksum.cs @@ -83,7 +83,7 @@ public SpdxChecksum DeepCopy() /// fields that are absent in this instance. Fields that are already populated /// are preserved; only missing (default/empty) values are replaced. /// - /// Other checksum to enhance with + /// Other checksum to enhance with. Must not be null. public void Enhance(SpdxChecksum other) { // Populate the algorithm if missing @@ -105,8 +105,8 @@ public void Enhance(SpdxChecksum other) /// instance method. Entries with no match are /// deep-copied before being appended to preserve independence from the source array. /// - /// Array to enhance - /// Other array to enhance with + /// Array to enhance. Must not be null. + /// Other array to enhance with. Must not be null. /// Updated array public static SpdxChecksum[] Enhance(SpdxChecksum[] array, SpdxChecksum[] others) { @@ -143,7 +143,7 @@ public static SpdxChecksum[] Enhance(SpdxChecksum[] array, SpdxChecksum[] others /// string is prepended to each issue message to provide context in diagnostic output. /// /// Associated parent node - /// List to populate with issues + /// List to populate with issues. Must not be null. public void Validate(string parent, List issues) { // Validate Algorithm Field diff --git a/src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs b/src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs index 7ebc2ee..2dedca0 100644 --- a/src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs +++ b/src/DemaConsulting.SpdxModel/SpdxChecksumAlgorithm.cs @@ -164,7 +164,7 @@ public static class SpdxChecksumAlgorithmExtensions /// string maps to rather than throwing; /// any other unrecognized value throws . /// - /// Checksum algorithm text + /// Checksum algorithm text. Must not be null. /// SpdxChecksumAlgorithm /// /// Thrown when is a non-empty string that does not match any diff --git a/src/DemaConsulting.SpdxModel/SpdxElement.cs b/src/DemaConsulting.SpdxModel/SpdxElement.cs index 5717c68..673b3a1 100644 --- a/src/DemaConsulting.SpdxModel/SpdxElement.cs +++ b/src/DemaConsulting.SpdxModel/SpdxElement.cs @@ -61,8 +61,10 @@ public abstract class SpdxElement /// Gets or sets the Element ID /// /// - /// Uniquely identify any element in an SPDX document which may be - /// referenced by other elements. + /// Uniquely identifies any element in an SPDX document. The value must follow the + /// SPDXRef-<name> format (validated by ) and must + /// be unique within the document. Elements with duplicate or malformed identifiers are + /// reported as validation issues by the concrete subclass Validate methods. /// public string Id { get; set; } = ""; @@ -74,7 +76,7 @@ public abstract class SpdxElement /// when the current is empty. This is a no-op if /// 's is also empty. /// - /// Other element to enhance with + /// Source element whose supplies the fallback value. Must not be null. protected void EnhanceElement(SpdxElement other) { // Populate the ID if missing diff --git a/src/DemaConsulting.SpdxModel/SpdxFile.cs b/src/DemaConsulting.SpdxModel/SpdxFile.cs index 5a2d641..be090b6 100644 --- a/src/DemaConsulting.SpdxModel/SpdxFile.cs +++ b/src/DemaConsulting.SpdxModel/SpdxFile.cs @@ -142,9 +142,11 @@ public SpdxFile DeepCopy() /// /// /// Non-destructive merge: existing non-empty fields are preserved. String fields use - /// fitness-based selection (concrete value > NOASSERTION > empty > null). Array fields - /// (, , ) - /// are merged by concatenation and deduplication. + /// fitness-based selection (concrete value > NOASSERTION > empty > null). + /// , , and + /// are merged by concatenation and deduplication. + /// are merged by identity-match and enhance via + /// . /// /// Other file to enhance with public void Enhance(SpdxFile other) diff --git a/src/DemaConsulting.SpdxModel/SpdxPackage.cs b/src/DemaConsulting.SpdxModel/SpdxPackage.cs index 3ecd285..1649f84 100644 --- a/src/DemaConsulting.SpdxModel/SpdxPackage.cs +++ b/src/DemaConsulting.SpdxModel/SpdxPackage.cs @@ -299,6 +299,9 @@ public SpdxPackage DeepCopy() /// Field fitness ranking used when choosing which value wins: null < "" < /// "NOASSERTION" < any concrete value. Array fields such as , /// , and are merged by deduplication. + /// The nullable field is populated from when null. + /// The array is intentionally not merged: it contains SPDX element IDs that are + /// document-scoped and may not be valid across documents. /// /// Other package to enhance with public void Enhance(SpdxPackage other) @@ -324,6 +327,9 @@ public void Enhance(SpdxPackage other) // Populate the download-location field if missing DownloadLocation = SpdxHelpers.EnhanceString(DownloadLocation, other.DownloadLocation) ?? ""; + // Populate the files-analyzed field if missing + FilesAnalyzed ??= other.FilesAnalyzed; + // Enhance or populate the verification code if (VerificationCode != null && other.VerificationCode != null) { diff --git a/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs b/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs index d1eed13..288c893 100644 --- a/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs +++ b/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs @@ -45,7 +45,7 @@ public sealed class SpdxPackageVerificationCode /// Excluded Files Field /// /// - /// Files that was excluded when calculating the package verification code. + /// Files that were excluded when calculating the package verification code. /// This is usually a file containing SPDX data regarding the package. /// If a package contains more than one SPDX file all SPDX files must be /// excluded from the package verification code. If this is not done it diff --git a/src/DemaConsulting.SpdxModel/SpdxRelationship.cs b/src/DemaConsulting.SpdxModel/SpdxRelationship.cs index 0062161..6c23f2c 100644 --- a/src/DemaConsulting.SpdxModel/SpdxRelationship.cs +++ b/src/DemaConsulting.SpdxModel/SpdxRelationship.cs @@ -21,10 +21,14 @@ namespace DemaConsulting.SpdxModel; /// -/// SPDX Relationship class +/// Represents a directed relationship between two SPDX elements in an SPDX document. /// /// -/// Relationships referenced in the SPDX document. +/// Relationships define the dependency graph, containment hierarchy, and other associations +/// between packages, files, and snippets. This class provides equality comparison via the +/// and comparers to support array merging and +/// deduplication. Instances are mutable; see for producing independent +/// copies. Not thread-safe for concurrent mutation. /// public sealed class SpdxRelationship : SpdxElement { diff --git a/src/DemaConsulting.SpdxModel/SpdxSnippet.cs b/src/DemaConsulting.SpdxModel/SpdxSnippet.cs index c41372e..7ad4b3b 100644 --- a/src/DemaConsulting.SpdxModel/SpdxSnippet.cs +++ b/src/DemaConsulting.SpdxModel/SpdxSnippet.cs @@ -21,10 +21,13 @@ namespace DemaConsulting.SpdxModel; /// -/// SPDX Snippet Class +/// Represents a portion of a file in an SPDX document with distinct licensing or provenance. /// /// -/// Snippets referenced in the SPDX document +/// Snippets are used when a specific byte or line range within a file has different licensing +/// or provenance from the rest of the file, enabling granular compliance tracking for reused +/// code segments. This class is not thread-safe; callers must synchronize access when sharing +/// instances across threads. /// public sealed class SpdxSnippet : SpdxLicenseElement { @@ -102,7 +105,7 @@ public sealed class SpdxSnippet : SpdxLicenseElement /// Snippet Name Field (optional) /// /// - /// Identify name of this snippet. + /// Optional human-readable name for this snippet. /// public string? Name { get; set; } diff --git a/test/DemaConsulting.SpdxModel.Tests/AssemblyInfo.cs b/test/DemaConsulting.SpdxModel.Tests/AssemblyInfo.cs index 0562131..7fd3270 100644 --- a/test/DemaConsulting.SpdxModel.Tests/AssemblyInfo.cs +++ b/test/DemaConsulting.SpdxModel.Tests/AssemblyInfo.cs @@ -18,6 +18,3 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using Microsoft.VisualStudio.TestTools.UnitTesting; - -[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] diff --git a/test/DemaConsulting.SpdxModel.Tests/DemaConsulting.SpdxModel.Tests.csproj b/test/DemaConsulting.SpdxModel.Tests/DemaConsulting.SpdxModel.Tests.csproj index 4812d0d..16c11ec 100644 --- a/test/DemaConsulting.SpdxModel.Tests/DemaConsulting.SpdxModel.Tests.csproj +++ b/test/DemaConsulting.SpdxModel.Tests/DemaConsulting.SpdxModel.Tests.csproj @@ -6,6 +6,7 @@ latest enable enable + Exe false @@ -30,8 +31,11 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -68,7 +72,7 @@ - + diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs index 1f92630..2d6d09c 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize22.cs @@ -27,9 +27,8 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// /// Exercises end-to-end deserialization of a real SPDX 2.2 JSON example document -/// (embedded resource) using MSTest as the approved test framework for this repository. +/// (embedded resource) using xUnit v3 as the test framework. /// -[TestClass] public class Spdx2JsonDeserialize22 { /// @@ -40,7 +39,7 @@ public class Spdx2JsonDeserialize22 /// document-level fields, external document references, extracted licensing info, /// annotations, files, snippets, and relationships are all correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_Deserialize_ValidSpdx22Json_ReturnsExpectedDocument() { // Arrange: Load the SPDX 2.2 JSON example from embedded resources @@ -51,145 +50,145 @@ public void Spdx2JsonDeserializer_Deserialize_ValidSpdx22Json_ReturnsExpectedDoc var doc = Spdx2JsonDeserializer.Deserialize(json22Example); // Assert: Verify that the document is valid - Assert.IsNotNull(doc); + Assert.NotNull(doc); var issues = new List(); doc.Validate(issues); - Assert.IsEmpty(issues); + Assert.Empty(issues); // Assert: Verify the document properties - Assert.AreEqual("SPDX-Tools-v2.0", doc.Name); - Assert.AreEqual("SPDX-2.2", doc.Version); - Assert.AreEqual("http://spdx.org/spdxdocs/spdx-example-json-2.2-444504E0-4F89-41D3-9A0C-0305E82C3301", + Assert.Equal("SPDX-Tools-v2.0", doc.Name); + Assert.Equal("SPDX-2.2", doc.Version); + Assert.Equal("http://spdx.org/spdxdocs/spdx-example-json-2.2-444504E0-4F89-41D3-9A0C-0305E82C3301", doc.DocumentNamespace); - Assert.AreEqual("This document was created using SPDX 2.0 using licenses from the web site.", doc.Comment); - Assert.HasCount(3, doc.CreationInformation.Creators); - Assert.AreEqual("Tool: LicenseFind-1.0", doc.CreationInformation.Creators[0]); - Assert.AreEqual("Organization: ExampleCodeInspect ()", doc.CreationInformation.Creators[1]); - Assert.AreEqual("Person: Jane Doe ()", doc.CreationInformation.Creators[2]); - Assert.AreEqual("2010-01-29T18:30:22Z", doc.CreationInformation.Created); + Assert.Equal("This document was created using SPDX 2.0 using licenses from the web site.", doc.Comment); + Assert.Equal(3, doc.CreationInformation.Creators.Length); + Assert.Equal("Tool: LicenseFind-1.0", doc.CreationInformation.Creators[0]); + Assert.Equal("Organization: ExampleCodeInspect ()", doc.CreationInformation.Creators[1]); + Assert.Equal("Person: Jane Doe ()", doc.CreationInformation.Creators[2]); + Assert.Equal("2010-01-29T18:30:22Z", doc.CreationInformation.Created); Assert.StartsWith("This package has been shipped in source and", doc.CreationInformation.Comment); - Assert.AreEqual("3.9", doc.CreationInformation.LicenseListVersion); + Assert.Equal("3.9", doc.CreationInformation.LicenseListVersion); // Assert: Verify external document references - Assert.HasCount(1, doc.ExternalDocumentReferences); - Assert.AreEqual("DocumentRef-spdx-tool-1.2", doc.ExternalDocumentReferences[0].ExternalDocumentId); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, doc.ExternalDocumentReferences[0].Checksum.Algorithm); - Assert.AreEqual("d6a770ba38583ed4bb4525bd96e50461655d2759", doc.ExternalDocumentReferences[0].Checksum.Value); - Assert.AreEqual("http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", + Assert.Single(doc.ExternalDocumentReferences); + Assert.Equal("DocumentRef-spdx-tool-1.2", doc.ExternalDocumentReferences[0].ExternalDocumentId); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, doc.ExternalDocumentReferences[0].Checksum.Algorithm); + Assert.Equal("d6a770ba38583ed4bb4525bd96e50461655d2759", doc.ExternalDocumentReferences[0].Checksum.Value); + Assert.Equal("http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", doc.ExternalDocumentReferences[0].Document); // Assert: Verify extracted licensing info - Assert.HasCount(5, doc.ExtractedLicensingInfo); - Assert.AreEqual("LicenseRef-Beerware-4.2", doc.ExtractedLicensingInfo[0].LicenseId); + Assert.Equal(5, doc.ExtractedLicensingInfo.Length); + Assert.Equal("LicenseRef-Beerware-4.2", doc.ExtractedLicensingInfo[0].LicenseId); Assert.StartsWith("\"THE BEER-WARE LICENSE\"", doc.ExtractedLicensingInfo[0].ExtractedText); - Assert.AreEqual("LicenseRef-4", doc.ExtractedLicensingInfo[1].LicenseId); + Assert.Equal("LicenseRef-4", doc.ExtractedLicensingInfo[1].LicenseId); Assert.StartsWith("/*\n * (c) Copyright 2009 University of Bristol", doc.ExtractedLicensingInfo[1].ExtractedText); - Assert.AreEqual("LicenseRef-3", doc.ExtractedLicensingInfo[2].LicenseId); + Assert.Equal("LicenseRef-3", doc.ExtractedLicensingInfo[2].LicenseId); Assert.StartsWith("The CyberNeko Software License", doc.ExtractedLicensingInfo[2].ExtractedText); - Assert.AreEqual("CyberNeko License", doc.ExtractedLicensingInfo[2].Name); - Assert.HasCount(2, doc.ExtractedLicensingInfo[2].CrossReferences); - Assert.AreEqual("http://people.apache.org/~andyc/neko/LICENSE", + Assert.Equal("CyberNeko License", doc.ExtractedLicensingInfo[2].Name); + Assert.Equal(2, doc.ExtractedLicensingInfo[2].CrossReferences.Length); + Assert.Equal("http://people.apache.org/~andyc/neko/LICENSE", doc.ExtractedLicensingInfo[2].CrossReferences[0]); - Assert.AreEqual("http://justasample.url.com", doc.ExtractedLicensingInfo[2].CrossReferences[1]); - Assert.AreEqual("This is the CyperNeko License", doc.ExtractedLicensingInfo[2].Comment); - Assert.AreEqual("LicenseRef-2", doc.ExtractedLicensingInfo[3].LicenseId); + Assert.Equal("http://justasample.url.com", doc.ExtractedLicensingInfo[2].CrossReferences[1]); + Assert.Equal("This is the CyperNeko License", doc.ExtractedLicensingInfo[2].Comment); + Assert.Equal("LicenseRef-2", doc.ExtractedLicensingInfo[3].LicenseId); Assert.StartsWith("This package includes the", doc.ExtractedLicensingInfo[3].ExtractedText); - Assert.AreEqual("LicenseRef-1", doc.ExtractedLicensingInfo[4].LicenseId); + Assert.Equal("LicenseRef-1", doc.ExtractedLicensingInfo[4].LicenseId); Assert.StartsWith("/*\n * (c) Copyright 2000, 2001, 2002", doc.ExtractedLicensingInfo[4].ExtractedText); // Assert: Verify annotations - Assert.HasCount(3, doc.Annotations); - Assert.AreEqual("Person: Jane Doe ()", doc.Annotations[0].Annotator); - Assert.AreEqual("2010-01-29T18:30:22Z", doc.Annotations[0].Date); - Assert.AreEqual(SpdxAnnotationType.Other, doc.Annotations[0].Type); - Assert.AreEqual("Document level annotation", doc.Annotations[0].Comment); - Assert.AreEqual("Person: Suzanne Reviewer", doc.Annotations[1].Annotator); - Assert.AreEqual("2011-03-13T00:00:00Z", doc.Annotations[1].Date); - Assert.AreEqual(SpdxAnnotationType.Review, doc.Annotations[1].Type); - Assert.AreEqual("Another example reviewer.", doc.Annotations[1].Comment); - Assert.AreEqual("Person: Joe Reviewer", doc.Annotations[2].Annotator); - Assert.AreEqual("2010-02-10T00:00:00Z", doc.Annotations[2].Date); - Assert.AreEqual(SpdxAnnotationType.Review, doc.Annotations[2].Type); + Assert.Equal(3, doc.Annotations.Length); + Assert.Equal("Person: Jane Doe ()", doc.Annotations[0].Annotator); + Assert.Equal("2010-01-29T18:30:22Z", doc.Annotations[0].Date); + Assert.Equal(SpdxAnnotationType.Other, doc.Annotations[0].Type); + Assert.Equal("Document level annotation", doc.Annotations[0].Comment); + Assert.Equal("Person: Suzanne Reviewer", doc.Annotations[1].Annotator); + Assert.Equal("2011-03-13T00:00:00Z", doc.Annotations[1].Date); + Assert.Equal(SpdxAnnotationType.Review, doc.Annotations[1].Type); + Assert.Equal("Another example reviewer.", doc.Annotations[1].Comment); + Assert.Equal("Person: Joe Reviewer", doc.Annotations[2].Annotator); + Assert.Equal("2010-02-10T00:00:00Z", doc.Annotations[2].Date); + Assert.Equal(SpdxAnnotationType.Review, doc.Annotations[2].Type); Assert.StartsWith("This is just an example", doc.Annotations[2].Comment); // Assert: Verify files - Assert.HasCount(4, doc.Files); - Assert.AreEqual("SPDXRef-DoapSource", doc.Files[0].Id); - Assert.AreEqual("./src/org/spdx/parser/DOAPProject.java", doc.Files[0].FileName); - Assert.HasCount(1, doc.Files[0].FileTypes); - Assert.IsTrue(doc.Files[0].FileTypes.Contains(SpdxFileType.Source)); - Assert.HasCount(1, doc.Files[0].Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, doc.Files[0].Checksums[0].Algorithm); - Assert.AreEqual("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", doc.Files[0].Checksums[0].Value); - Assert.AreEqual("Apache-2.0", doc.Files[0].ConcludedLicense); - Assert.HasCount(1, doc.Files[0].LicenseInfoInFiles); - Assert.AreEqual("Apache-2.0", doc.Files[0].LicenseInfoInFiles[0]); - Assert.AreEqual("Copyright 2010, 2011 Source Auditor Inc.", doc.Files[0].CopyrightText); - Assert.HasCount(5, doc.Files[0].Contributors); - Assert.AreEqual("Protecode Inc.", doc.Files[0].Contributors[0]); - Assert.AreEqual("SPDX Technical Team Members", doc.Files[0].Contributors[1]); - Assert.AreEqual("Open Logic Inc.", doc.Files[0].Contributors[2]); - Assert.AreEqual("Source Auditor Inc.", doc.Files[0].Contributors[3]); - Assert.AreEqual("Black Duck Software Inc.", doc.Files[0].Contributors[4]); - Assert.AreEqual("This file is used by Jena", doc.Files[1].Comment); + Assert.Equal(4, doc.Files.Length); + Assert.Equal("SPDXRef-DoapSource", doc.Files[0].Id); + Assert.Equal("./src/org/spdx/parser/DOAPProject.java", doc.Files[0].FileName); + Assert.Single(doc.Files[0].FileTypes); + Assert.Contains(SpdxFileType.Source, doc.Files[0].FileTypes); + Assert.Single(doc.Files[0].Checksums); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, doc.Files[0].Checksums[0].Algorithm); + Assert.Equal("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", doc.Files[0].Checksums[0].Value); + Assert.Equal("Apache-2.0", doc.Files[0].ConcludedLicense); + Assert.Single(doc.Files[0].LicenseInfoInFiles); + Assert.Equal("Apache-2.0", doc.Files[0].LicenseInfoInFiles[0]); + Assert.Equal("Copyright 2010, 2011 Source Auditor Inc.", doc.Files[0].CopyrightText); + Assert.Equal(5, doc.Files[0].Contributors.Length); + Assert.Equal("Protecode Inc.", doc.Files[0].Contributors[0]); + Assert.Equal("SPDX Technical Team Members", doc.Files[0].Contributors[1]); + Assert.Equal("Open Logic Inc.", doc.Files[0].Contributors[2]); + Assert.Equal("Source Auditor Inc.", doc.Files[0].Contributors[3]); + Assert.Equal("Black Duck Software Inc.", doc.Files[0].Contributors[4]); + Assert.Equal("This file is used by Jena", doc.Files[1].Comment); Assert.StartsWith("Apache Commons Lang\nCopyright 2001-2011", doc.Files[1].Notice); - Assert.AreEqual("This license is used by Jena", doc.Files[2].LicenseComments); - Assert.HasCount(1, doc.Files[3].Annotations); - Assert.AreEqual("Person: File Commenter", doc.Files[3].Annotations[0].Annotator); - Assert.AreEqual("2011-01-29T18:30:22Z", doc.Files[3].Annotations[0].Date); - Assert.AreEqual(SpdxAnnotationType.Other, doc.Files[3].Annotations[0].Type); - Assert.AreEqual("File level annotation", doc.Files[3].Annotations[0].Comment); - Assert.HasCount(2, doc.Files[3].Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Md5, doc.Files[3].Checksums[0].Algorithm); - Assert.AreEqual("624c1abb3664f4b35547e7c73864ad24", doc.Files[3].Checksums[0].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, doc.Files[3].Checksums[1].Algorithm); - Assert.AreEqual("d6a770ba38583ed4bb4525bd96e50461655d2758", doc.Files[3].Checksums[1].Value); + Assert.Equal("This license is used by Jena", doc.Files[2].LicenseComments); + Assert.Single(doc.Files[3].Annotations); + Assert.Equal("Person: File Commenter", doc.Files[3].Annotations[0].Annotator); + Assert.Equal("2011-01-29T18:30:22Z", doc.Files[3].Annotations[0].Date); + Assert.Equal(SpdxAnnotationType.Other, doc.Files[3].Annotations[0].Type); + Assert.Equal("File level annotation", doc.Files[3].Annotations[0].Comment); + Assert.Equal(2, doc.Files[3].Checksums.Length); + Assert.Equal(SpdxChecksumAlgorithm.Md5, doc.Files[3].Checksums[0].Algorithm); + Assert.Equal("624c1abb3664f4b35547e7c73864ad24", doc.Files[3].Checksums[0].Value); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, doc.Files[3].Checksums[1].Algorithm); + Assert.Equal("d6a770ba38583ed4bb4525bd96e50461655d2758", doc.Files[3].Checksums[1].Value); // Assert: Verify snippets - Assert.HasCount(1, doc.Snippets); - Assert.AreEqual("SPDXRef-Snippet", doc.Snippets[0].Id); - Assert.AreEqual("SPDXRef-DoapSource", doc.Snippets[0].SnippetFromFile); - Assert.AreEqual(310, doc.Snippets[0].SnippetByteStart); - Assert.AreEqual(420, doc.Snippets[0].SnippetByteEnd); - Assert.AreEqual(5, doc.Snippets[0].SnippetLineStart); - Assert.AreEqual(23, doc.Snippets[0].SnippetLineEnd); - Assert.AreEqual("GPL-2.0-only", doc.Snippets[0].ConcludedLicense); - Assert.HasCount(1, doc.Snippets[0].LicenseInfoInSnippet); - Assert.AreEqual("GPL-2.0-only", doc.Snippets[0].LicenseInfoInSnippet[0]); + Assert.Single(doc.Snippets); + Assert.Equal("SPDXRef-Snippet", doc.Snippets[0].Id); + Assert.Equal("SPDXRef-DoapSource", doc.Snippets[0].SnippetFromFile); + Assert.Equal(310, doc.Snippets[0].SnippetByteStart); + Assert.Equal(420, doc.Snippets[0].SnippetByteEnd); + Assert.Equal(5, doc.Snippets[0].SnippetLineStart); + Assert.Equal(23, doc.Snippets[0].SnippetLineEnd); + Assert.Equal("GPL-2.0-only", doc.Snippets[0].ConcludedLicense); + Assert.Single(doc.Snippets[0].LicenseInfoInSnippet); + Assert.Equal("GPL-2.0-only", doc.Snippets[0].LicenseInfoInSnippet[0]); Assert.StartsWith("The concluded license was taken", doc.Snippets[0].LicenseComments); - Assert.AreEqual("Copyright 2008-2010 John Smith", doc.Snippets[0].CopyrightText); + Assert.Equal("Copyright 2008-2010 John Smith", doc.Snippets[0].CopyrightText); Assert.StartsWith("This snippet was identified as significant", doc.Snippets[0].Comment); - Assert.AreEqual("from linux kernel", doc.Snippets[0].Name); + Assert.Equal("from linux kernel", doc.Snippets[0].Name); // Assert: Verify relationships - Assert.HasCount(9, doc.Relationships); - Assert.AreEqual("SPDXRef-DOCUMENT", doc.Relationships[0].Id); - Assert.AreEqual("SPDXRef-Package", doc.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Contains, doc.Relationships[0].RelationshipType); - Assert.AreEqual("SPDXRef-DOCUMENT", doc.Relationships[1].Id); - Assert.AreEqual("SPDXRef-File", doc.Relationships[1].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Describes, doc.Relationships[1].RelationshipType); - Assert.AreEqual("SPDXRef-DOCUMENT", doc.Relationships[2].Id); - Assert.AreEqual("DocumentRef-spdx-tool-1.2:SPDXRef-ToolsElement", doc.Relationships[2].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.CopyOf, doc.Relationships[2].RelationshipType); - Assert.AreEqual("SPDXRef-DOCUMENT", doc.Relationships[3].Id); - Assert.AreEqual("SPDXRef-Package", doc.Relationships[3].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Describes, doc.Relationships[3].RelationshipType); - Assert.AreEqual("SPDXRef-Package", doc.Relationships[4].Id); - Assert.AreEqual("SPDXRef-Saxon", doc.Relationships[4].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DynamicLink, doc.Relationships[4].RelationshipType); - Assert.AreEqual("SPDXRef-Package", doc.Relationships[5].Id); - Assert.AreEqual("SPDXRef-JenaLib", doc.Relationships[5].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Contains, doc.Relationships[5].RelationshipType); - Assert.AreEqual("SPDXRef-CommonsLangSrc", doc.Relationships[6].Id); - Assert.AreEqual("NOASSERTION", doc.Relationships[6].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.GeneratedFrom, doc.Relationships[6].RelationshipType); - Assert.AreEqual("SPDXRef-JenaLib", doc.Relationships[7].Id); - Assert.AreEqual("SPDXRef-Package", doc.Relationships[7].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Contains, doc.Relationships[7].RelationshipType); - Assert.AreEqual("SPDXRef-File", doc.Relationships[8].Id); - Assert.AreEqual("SPDXRef-fromDoap-0", doc.Relationships[8].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.GeneratedFrom, doc.Relationships[8].RelationshipType); + Assert.Equal(9, doc.Relationships.Length); + Assert.Equal("SPDXRef-DOCUMENT", doc.Relationships[0].Id); + Assert.Equal("SPDXRef-Package", doc.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Contains, doc.Relationships[0].RelationshipType); + Assert.Equal("SPDXRef-DOCUMENT", doc.Relationships[1].Id); + Assert.Equal("SPDXRef-File", doc.Relationships[1].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Describes, doc.Relationships[1].RelationshipType); + Assert.Equal("SPDXRef-DOCUMENT", doc.Relationships[2].Id); + Assert.Equal("DocumentRef-spdx-tool-1.2:SPDXRef-ToolsElement", doc.Relationships[2].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.CopyOf, doc.Relationships[2].RelationshipType); + Assert.Equal("SPDXRef-DOCUMENT", doc.Relationships[3].Id); + Assert.Equal("SPDXRef-Package", doc.Relationships[3].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Describes, doc.Relationships[3].RelationshipType); + Assert.Equal("SPDXRef-Package", doc.Relationships[4].Id); + Assert.Equal("SPDXRef-Saxon", doc.Relationships[4].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DynamicLink, doc.Relationships[4].RelationshipType); + Assert.Equal("SPDXRef-Package", doc.Relationships[5].Id); + Assert.Equal("SPDXRef-JenaLib", doc.Relationships[5].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Contains, doc.Relationships[5].RelationshipType); + Assert.Equal("SPDXRef-CommonsLangSrc", doc.Relationships[6].Id); + Assert.Equal("NOASSERTION", doc.Relationships[6].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.GeneratedFrom, doc.Relationships[6].RelationshipType); + Assert.Equal("SPDXRef-JenaLib", doc.Relationships[7].Id); + Assert.Equal("SPDXRef-Package", doc.Relationships[7].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Contains, doc.Relationships[7].RelationshipType); + Assert.Equal("SPDXRef-File", doc.Relationships[8].Id); + Assert.Equal("SPDXRef-fromDoap-0", doc.Relationships[8].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.GeneratedFrom, doc.Relationships[8].RelationshipType); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs index f855924..bfe48fa 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserialize23.cs @@ -27,9 +27,8 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// /// Exercises end-to-end deserialization of a real SPDX 2.3 JSON example document -/// (embedded resource) using MSTest as the approved test framework for this repository. +/// (embedded resource) using xUnit v3 as the test framework. /// -[TestClass] public class Spdx2JsonDeserialize23 { /// @@ -40,7 +39,7 @@ public class Spdx2JsonDeserialize23 /// document-level fields, external document references, extracted licensing info, /// annotations, files, snippets, and relationships are all correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_Deserialize_ValidSpdx23Json_ReturnsExpectedDocument() { // Arrange: Get the SPDX 2.3 JSON example from embedded resources @@ -51,140 +50,140 @@ public void Spdx2JsonDeserializer_Deserialize_ValidSpdx23Json_ReturnsExpectedDoc var doc = Spdx2JsonDeserializer.Deserialize(json23Example); // Assert: Verify that the document is valid - Assert.IsNotNull(doc); + Assert.NotNull(doc); var issues = new List(); doc.Validate(issues); - Assert.IsEmpty(issues); + Assert.Empty(issues); // Assert: Verify document - Assert.AreEqual("SPDX-Tools-v2.0", doc.Name); - Assert.AreEqual("SPDX-2.3", doc.Version); - Assert.AreEqual("http://spdx.org/spdxdocs/spdx-example-json-2.3-444504E0-4F89-41D3-9A0C-0305E82C3301", + Assert.Equal("SPDX-Tools-v2.0", doc.Name); + Assert.Equal("SPDX-2.3", doc.Version); + Assert.Equal("http://spdx.org/spdxdocs/spdx-example-json-2.3-444504E0-4F89-41D3-9A0C-0305E82C3301", doc.DocumentNamespace); - Assert.AreEqual("This document was created using SPDX 2.0 using licenses from the web site.", doc.Comment); - Assert.HasCount(3, doc.CreationInformation.Creators); - Assert.AreEqual("Tool: LicenseFind-1.0", doc.CreationInformation.Creators[0]); - Assert.AreEqual("Organization: ExampleCodeInspect ()", doc.CreationInformation.Creators[1]); - Assert.AreEqual("Person: Jane Doe ()", doc.CreationInformation.Creators[2]); - Assert.AreEqual("2010-01-29T18:30:22Z", doc.CreationInformation.Created); + Assert.Equal("This document was created using SPDX 2.0 using licenses from the web site.", doc.Comment); + Assert.Equal(3, doc.CreationInformation.Creators.Length); + Assert.Equal("Tool: LicenseFind-1.0", doc.CreationInformation.Creators[0]); + Assert.Equal("Organization: ExampleCodeInspect ()", doc.CreationInformation.Creators[1]); + Assert.Equal("Person: Jane Doe ()", doc.CreationInformation.Creators[2]); + Assert.Equal("2010-01-29T18:30:22Z", doc.CreationInformation.Created); Assert.StartsWith("This package has been shipped in source and", doc.CreationInformation.Comment); - Assert.AreEqual("3.17", doc.CreationInformation.LicenseListVersion); + Assert.Equal("3.17", doc.CreationInformation.LicenseListVersion); // Assert: Verify external document references - Assert.HasCount(1, doc.ExternalDocumentReferences); - Assert.AreEqual("DocumentRef-spdx-tool-1.2", doc.ExternalDocumentReferences[0].ExternalDocumentId); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, doc.ExternalDocumentReferences[0].Checksum.Algorithm); - Assert.AreEqual("d6a770ba38583ed4bb4525bd96e50461655d2759", doc.ExternalDocumentReferences[0].Checksum.Value); - Assert.AreEqual("http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", + Assert.Single(doc.ExternalDocumentReferences); + Assert.Equal("DocumentRef-spdx-tool-1.2", doc.ExternalDocumentReferences[0].ExternalDocumentId); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, doc.ExternalDocumentReferences[0].Checksum.Algorithm); + Assert.Equal("d6a770ba38583ed4bb4525bd96e50461655d2759", doc.ExternalDocumentReferences[0].Checksum.Value); + Assert.Equal("http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", doc.ExternalDocumentReferences[0].Document); // Assert: Verify extracted licensing info - Assert.HasCount(5, doc.ExtractedLicensingInfo); - Assert.AreEqual("LicenseRef-1", doc.ExtractedLicensingInfo[0].LicenseId); + Assert.Equal(5, doc.ExtractedLicensingInfo.Length); + Assert.Equal("LicenseRef-1", doc.ExtractedLicensingInfo[0].LicenseId); Assert.StartsWith("/*\n * (c) Copyright 2000, 2001, 2002, 2003", doc.ExtractedLicensingInfo[0].ExtractedText); - Assert.AreEqual("LicenseRef-2", doc.ExtractedLicensingInfo[1].LicenseId); + Assert.Equal("LicenseRef-2", doc.ExtractedLicensingInfo[1].LicenseId); Assert.StartsWith("This package includes the", doc.ExtractedLicensingInfo[1].ExtractedText); - Assert.AreEqual("LicenseRef-4", doc.ExtractedLicensingInfo[2].LicenseId); + Assert.Equal("LicenseRef-4", doc.ExtractedLicensingInfo[2].LicenseId); Assert.StartsWith("/*\n * (c) Copyright 2009 University of Bristol", doc.ExtractedLicensingInfo[2].ExtractedText); - Assert.AreEqual("LicenseRef-Beerware-4.2", doc.ExtractedLicensingInfo[3].LicenseId); + Assert.Equal("LicenseRef-Beerware-4.2", doc.ExtractedLicensingInfo[3].LicenseId); Assert.StartsWith( "\"THE BEER-WARE LICENSE\" (Revision 42)", doc.ExtractedLicensingInfo[3].ExtractedText); - Assert.AreEqual("Beer-Ware License (Version 42)", doc.ExtractedLicensingInfo[3].Name); - Assert.HasCount(1, doc.ExtractedLicensingInfo[3].CrossReferences); - Assert.AreEqual("http://people.freebsd.org/~phk/", doc.ExtractedLicensingInfo[3].CrossReferences[0]); + Assert.Equal("Beer-Ware License (Version 42)", doc.ExtractedLicensingInfo[3].Name); + Assert.Single(doc.ExtractedLicensingInfo[3].CrossReferences); + Assert.Equal("http://people.freebsd.org/~phk/", doc.ExtractedLicensingInfo[3].CrossReferences[0]); Assert.StartsWith("The beerware license has", doc.ExtractedLicensingInfo[3].Comment); - Assert.AreEqual("LicenseRef-3", doc.ExtractedLicensingInfo[4].LicenseId); + Assert.Equal("LicenseRef-3", doc.ExtractedLicensingInfo[4].LicenseId); Assert.StartsWith("The CyberNeko Software License", doc.ExtractedLicensingInfo[4].ExtractedText); - Assert.AreEqual("CyberNeko License", doc.ExtractedLicensingInfo[4].Name); - Assert.HasCount(2, doc.ExtractedLicensingInfo[4].CrossReferences); - Assert.AreEqual("http://people.apache.org/~andyc/neko/LICENSE", + Assert.Equal("CyberNeko License", doc.ExtractedLicensingInfo[4].Name); + Assert.Equal(2, doc.ExtractedLicensingInfo[4].CrossReferences.Length); + Assert.Equal("http://people.apache.org/~andyc/neko/LICENSE", doc.ExtractedLicensingInfo[4].CrossReferences[0]); - Assert.AreEqual("http://justasample.url.com", doc.ExtractedLicensingInfo[4].CrossReferences[1]); + Assert.Equal("http://justasample.url.com", doc.ExtractedLicensingInfo[4].CrossReferences[1]); Assert.StartsWith("This is the CyperNeko License", doc.ExtractedLicensingInfo[4].Comment); // Assert: Verify annotations - Assert.HasCount(3, doc.Annotations); - Assert.AreEqual("Person: Jane Doe ()", doc.Annotations[0].Annotator); - Assert.AreEqual("2010-01-29T18:30:22Z", doc.Annotations[0].Date); - Assert.AreEqual(SpdxAnnotationType.Other, doc.Annotations[0].Type); + Assert.Equal(3, doc.Annotations.Length); + Assert.Equal("Person: Jane Doe ()", doc.Annotations[0].Annotator); + Assert.Equal("2010-01-29T18:30:22Z", doc.Annotations[0].Date); + Assert.Equal(SpdxAnnotationType.Other, doc.Annotations[0].Type); Assert.StartsWith("Document level annotation", doc.Annotations[0].Comment); - Assert.AreEqual("Person: Joe Reviewer", doc.Annotations[1].Annotator); - Assert.AreEqual("2010-02-10T00:00:00Z", doc.Annotations[1].Date); - Assert.AreEqual(SpdxAnnotationType.Review, doc.Annotations[1].Type); + Assert.Equal("Person: Joe Reviewer", doc.Annotations[1].Annotator); + Assert.Equal("2010-02-10T00:00:00Z", doc.Annotations[1].Date); + Assert.Equal(SpdxAnnotationType.Review, doc.Annotations[1].Type); Assert.StartsWith("This is just an example", doc.Annotations[1].Comment); - Assert.AreEqual("Person: Suzanne Reviewer", doc.Annotations[2].Annotator); - Assert.AreEqual("2011-03-13T00:00:00Z", doc.Annotations[2].Date); - Assert.AreEqual(SpdxAnnotationType.Review, doc.Annotations[2].Type); + Assert.Equal("Person: Suzanne Reviewer", doc.Annotations[2].Annotator); + Assert.Equal("2011-03-13T00:00:00Z", doc.Annotations[2].Date); + Assert.Equal(SpdxAnnotationType.Review, doc.Annotations[2].Type); Assert.StartsWith("Another example reviewer.", doc.Annotations[2].Comment); // Assert: Verify files - Assert.HasCount(5, doc.Files); - Assert.AreEqual("SPDXRef-DoapSource", doc.Files[0].Id); - Assert.AreEqual("./src/org/spdx/parser/DOAPProject.java", doc.Files[0].FileName); - Assert.HasCount(1, doc.Files[0].FileTypes); - Assert.IsTrue(doc.Files[0].FileTypes.Contains(SpdxFileType.Source)); - Assert.HasCount(1, doc.Files[0].Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, doc.Files[0].Checksums[0].Algorithm); - Assert.AreEqual("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", doc.Files[0].Checksums[0].Value); - Assert.AreEqual("Apache-2.0", doc.Files[0].ConcludedLicense); - Assert.HasCount(1, doc.Files[0].LicenseInfoInFiles); - Assert.AreEqual("Apache-2.0", doc.Files[0].LicenseInfoInFiles[0]); - Assert.AreEqual("Copyright 2010, 2011 Source Auditor Inc.", doc.Files[0].CopyrightText); - Assert.HasCount(5, doc.Files[0].Contributors); - Assert.AreEqual("Protecode Inc.", doc.Files[0].Contributors[0]); - Assert.AreEqual("SPDX Technical Team Members", doc.Files[0].Contributors[1]); - Assert.AreEqual("Open Logic Inc.", doc.Files[0].Contributors[2]); - Assert.AreEqual("Source Auditor Inc.", doc.Files[0].Contributors[3]); - Assert.AreEqual("Black Duck Software Inc.", doc.Files[0].Contributors[4]); - Assert.AreEqual("This file is used by Jena", doc.Files[1].Comment); + Assert.Equal(5, doc.Files.Length); + Assert.Equal("SPDXRef-DoapSource", doc.Files[0].Id); + Assert.Equal("./src/org/spdx/parser/DOAPProject.java", doc.Files[0].FileName); + Assert.Single(doc.Files[0].FileTypes); + Assert.Contains(SpdxFileType.Source, doc.Files[0].FileTypes); + Assert.Single(doc.Files[0].Checksums); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, doc.Files[0].Checksums[0].Algorithm); + Assert.Equal("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", doc.Files[0].Checksums[0].Value); + Assert.Equal("Apache-2.0", doc.Files[0].ConcludedLicense); + Assert.Single(doc.Files[0].LicenseInfoInFiles); + Assert.Equal("Apache-2.0", doc.Files[0].LicenseInfoInFiles[0]); + Assert.Equal("Copyright 2010, 2011 Source Auditor Inc.", doc.Files[0].CopyrightText); + Assert.Equal(5, doc.Files[0].Contributors.Length); + Assert.Equal("Protecode Inc.", doc.Files[0].Contributors[0]); + Assert.Equal("SPDX Technical Team Members", doc.Files[0].Contributors[1]); + Assert.Equal("Open Logic Inc.", doc.Files[0].Contributors[2]); + Assert.Equal("Source Auditor Inc.", doc.Files[0].Contributors[3]); + Assert.Equal("Black Duck Software Inc.", doc.Files[0].Contributors[4]); + Assert.Equal("This file is used by Jena", doc.Files[1].Comment); Assert.StartsWith("Apache Commons Lang\nCopyright 2001-2011", doc.Files[1].Notice); - Assert.AreEqual("This license is used by Jena", doc.Files[2].LicenseComments); - Assert.HasCount(2, doc.Files[4].Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, doc.Files[4].Checksums[0].Algorithm); - Assert.AreEqual("d6a770ba38583ed4bb4525bd96e50461655d2758", doc.Files[4].Checksums[0].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Md5, doc.Files[4].Checksums[1].Algorithm); - Assert.AreEqual("624c1abb3664f4b35547e7c73864ad24", doc.Files[4].Checksums[1].Value); + Assert.Equal("This license is used by Jena", doc.Files[2].LicenseComments); + Assert.Equal(2, doc.Files[4].Checksums.Length); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, doc.Files[4].Checksums[0].Algorithm); + Assert.Equal("d6a770ba38583ed4bb4525bd96e50461655d2758", doc.Files[4].Checksums[0].Value); + Assert.Equal(SpdxChecksumAlgorithm.Md5, doc.Files[4].Checksums[1].Algorithm); + Assert.Equal("624c1abb3664f4b35547e7c73864ad24", doc.Files[4].Checksums[1].Value); // Assert: Verify snippets - Assert.HasCount(1, doc.Snippets); - Assert.AreEqual("SPDXRef-Snippet", doc.Snippets[0].Id); - Assert.AreEqual("SPDXRef-DoapSource", doc.Snippets[0].SnippetFromFile); - Assert.AreEqual(310, doc.Snippets[0].SnippetByteStart); - Assert.AreEqual(420, doc.Snippets[0].SnippetByteEnd); - Assert.AreEqual(5, doc.Snippets[0].SnippetLineStart); - Assert.AreEqual(23, doc.Snippets[0].SnippetLineEnd); - Assert.AreEqual("GPL-2.0-only", doc.Snippets[0].ConcludedLicense); - Assert.HasCount(1, doc.Snippets[0].LicenseInfoInSnippet); - Assert.AreEqual("GPL-2.0-only", doc.Snippets[0].LicenseInfoInSnippet[0]); + Assert.Single(doc.Snippets); + Assert.Equal("SPDXRef-Snippet", doc.Snippets[0].Id); + Assert.Equal("SPDXRef-DoapSource", doc.Snippets[0].SnippetFromFile); + Assert.Equal(310, doc.Snippets[0].SnippetByteStart); + Assert.Equal(420, doc.Snippets[0].SnippetByteEnd); + Assert.Equal(5, doc.Snippets[0].SnippetLineStart); + Assert.Equal(23, doc.Snippets[0].SnippetLineEnd); + Assert.Equal("GPL-2.0-only", doc.Snippets[0].ConcludedLicense); + Assert.Single(doc.Snippets[0].LicenseInfoInSnippet); + Assert.Equal("GPL-2.0-only", doc.Snippets[0].LicenseInfoInSnippet[0]); Assert.StartsWith("The concluded license was taken", doc.Snippets[0].LicenseComments); - Assert.AreEqual("Copyright 2008-2010 John Smith", doc.Snippets[0].CopyrightText); + Assert.Equal("Copyright 2008-2010 John Smith", doc.Snippets[0].CopyrightText); Assert.StartsWith("This snippet was identified as significant", doc.Snippets[0].Comment); - Assert.AreEqual("from linux kernel", doc.Snippets[0].Name); + Assert.Equal("from linux kernel", doc.Snippets[0].Name); // Assert: Verify relationships - Assert.HasCount(7, doc.Relationships); - Assert.AreEqual("SPDXRef-DOCUMENT", doc.Relationships[0].Id); - Assert.AreEqual("SPDXRef-Package", doc.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Contains, doc.Relationships[0].RelationshipType); - Assert.AreEqual("SPDXRef-DOCUMENT", doc.Relationships[1].Id); - Assert.AreEqual("DocumentRef-spdx-tool-1.2:SPDXRef-ToolsElement", doc.Relationships[1].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.CopyOf, doc.Relationships[1].RelationshipType); - Assert.AreEqual("SPDXRef-Package", doc.Relationships[2].Id); - Assert.AreEqual("SPDXRef-Saxon", doc.Relationships[2].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DynamicLink, doc.Relationships[2].RelationshipType); - Assert.AreEqual("SPDXRef-CommonsLangSrc", doc.Relationships[3].Id); - Assert.AreEqual("NOASSERTION", doc.Relationships[3].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.GeneratedFrom, doc.Relationships[3].RelationshipType); - Assert.AreEqual("SPDXRef-JenaLib", doc.Relationships[4].Id); - Assert.AreEqual("SPDXRef-Package", doc.Relationships[4].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Contains, doc.Relationships[4].RelationshipType); - Assert.AreEqual("SPDXRef-Specification", doc.Relationships[5].Id); - Assert.AreEqual("SPDXRef-fromDoap-0", doc.Relationships[5].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.SpecificationFor, doc.Relationships[5].RelationshipType); - Assert.AreEqual("SPDXRef-File", doc.Relationships[6].Id); - Assert.AreEqual("SPDXRef-fromDoap-0", doc.Relationships[6].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.GeneratedFrom, doc.Relationships[6].RelationshipType); + Assert.Equal(7, doc.Relationships.Length); + Assert.Equal("SPDXRef-DOCUMENT", doc.Relationships[0].Id); + Assert.Equal("SPDXRef-Package", doc.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Contains, doc.Relationships[0].RelationshipType); + Assert.Equal("SPDXRef-DOCUMENT", doc.Relationships[1].Id); + Assert.Equal("DocumentRef-spdx-tool-1.2:SPDXRef-ToolsElement", doc.Relationships[1].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.CopyOf, doc.Relationships[1].RelationshipType); + Assert.Equal("SPDXRef-Package", doc.Relationships[2].Id); + Assert.Equal("SPDXRef-Saxon", doc.Relationships[2].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DynamicLink, doc.Relationships[2].RelationshipType); + Assert.Equal("SPDXRef-CommonsLangSrc", doc.Relationships[3].Id); + Assert.Equal("NOASSERTION", doc.Relationships[3].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.GeneratedFrom, doc.Relationships[3].RelationshipType); + Assert.Equal("SPDXRef-JenaLib", doc.Relationships[4].Id); + Assert.Equal("SPDXRef-Package", doc.Relationships[4].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Contains, doc.Relationships[4].RelationshipType); + Assert.Equal("SPDXRef-Specification", doc.Relationships[5].Id); + Assert.Equal("SPDXRef-fromDoap-0", doc.Relationships[5].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.SpecificationFor, doc.Relationships[5].RelationshipType); + Assert.Equal("SPDXRef-File", doc.Relationships[6].Id); + Assert.Equal("SPDXRef-fromDoap-0", doc.Relationships[6].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.GeneratedFrom, doc.Relationships[6].RelationshipType); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs index 9e326fb..40f4420 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeAnnotation.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX annotations to classes. /// /// -/// Exercises deserialization of SPDX annotation elements using MSTest as the approved -/// test framework for this repository. Each test constructs inline JSON and verifies +/// Exercises deserialization of SPDX annotation elements using xUnit v3 as the test +/// framework. Each test constructs inline JSON and verifies /// the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeAnnotation { /// @@ -42,7 +41,7 @@ public class Spdx2JsonDeserializeAnnotation /// comment) are mapped to the correct properties when a /// single JSON object is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeAnnotation_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing an annotation @@ -58,10 +57,10 @@ public void Spdx2JsonDeserializer_DeserializeAnnotation_ValidInput_CorrectResult var annotation = Spdx2JsonDeserializer.DeserializeAnnotation(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("2010-01-29T18:30:22Z", annotation.Date); - Assert.AreEqual(SpdxAnnotationType.Other, annotation.Type); - Assert.AreEqual("Person: Jane Doe ()", annotation.Annotator); - Assert.AreEqual("Document level annotation", annotation.Comment); + Assert.Equal("2010-01-29T18:30:22Z", annotation.Date); + Assert.Equal(SpdxAnnotationType.Other, annotation.Type); + Assert.Equal("Person: Jane Doe ()", annotation.Annotator); + Assert.Equal("Document level annotation", annotation.Comment); } /// @@ -71,7 +70,7 @@ public void Spdx2JsonDeserializer_DeserializeAnnotation_ValidInput_CorrectResult /// Verifies that a JSON array of two annotation objects is deserialized to an array /// of two instances with fields correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeAnnotations_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple annotations @@ -98,15 +97,15 @@ public void Spdx2JsonDeserializer_DeserializeAnnotations_ValidInput_CorrectResul var annotations = Spdx2JsonDeserializer.DeserializeAnnotations(json); // Assert: Verify the deserialized array has the expected number of annotations and their properties - Assert.HasCount(2, annotations); - Assert.AreEqual("2010-01-29T18:30:22Z", annotations[0].Date); - Assert.AreEqual(SpdxAnnotationType.Other, annotations[0].Type); - Assert.AreEqual("Person: Jane Doe ()", annotations[0].Annotator); - Assert.AreEqual("Document level annotation", annotations[0].Comment); - Assert.AreEqual("2010-02-10T00:00:00Z", annotations[1].Date); - Assert.AreEqual(SpdxAnnotationType.Review, annotations[1].Type); - Assert.AreEqual("Person: Joe Reviewer", annotations[1].Annotator); - Assert.AreEqual( + Assert.Equal(2, annotations.Length); + Assert.Equal("2010-01-29T18:30:22Z", annotations[0].Date); + Assert.Equal(SpdxAnnotationType.Other, annotations[0].Type); + Assert.Equal("Person: Jane Doe ()", annotations[0].Annotator); + Assert.Equal("Document level annotation", annotations[0].Comment); + Assert.Equal("2010-02-10T00:00:00Z", annotations[1].Date); + Assert.Equal(SpdxAnnotationType.Review, annotations[1].Type); + Assert.Equal("Person: Joe Reviewer", annotations[1].Annotator); + Assert.Equal( "This is just an example. Some of the non-standard licenses look like they are actually BSD 3 clause licenses", annotations[1].Comment); } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeChecksum.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeChecksum.cs index f4fec83..467f24f 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeChecksum.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeChecksum.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX checksums to classes. /// /// -/// Exercises deserialization of SPDX checksum elements using MSTest as the approved -/// test framework for this repository. Each test constructs inline JSON and verifies +/// Exercises deserialization of SPDX checksum elements using xUnit v3 as the test +/// framework. Each test constructs inline JSON and verifies /// the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeChecksum { /// @@ -42,7 +41,7 @@ public class Spdx2JsonDeserializeChecksum /// corresponding properties when a single checksum object /// is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeChecksum_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a checksum @@ -56,8 +55,8 @@ public void Spdx2JsonDeserializer_DeserializeChecksum_ValidInput_CorrectResults( var checksum = Spdx2JsonDeserializer.DeserializeChecksum(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, checksum.Algorithm); - Assert.AreEqual("2fd4e1c67a2d28f123849ee1bb76e7391b93eb12", checksum.Value); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, checksum.Algorithm); + Assert.Equal("2fd4e1c67a2d28f123849ee1bb76e7391b93eb12", checksum.Value); } /// @@ -68,7 +67,7 @@ public void Spdx2JsonDeserializer_DeserializeChecksum_ValidInput_CorrectResults( /// to an array of two instances with correct algorithm and /// value mappings. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeChecksums_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple checksums @@ -90,10 +89,10 @@ public void Spdx2JsonDeserializer_DeserializeChecksums_ValidInput_CorrectResults var checksums = Spdx2JsonDeserializer.DeserializeChecksums(json); // Assert: Verify the deserialized array has the expected properties - Assert.HasCount(2, checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, checksums[0].Algorithm); - Assert.AreEqual("2fd4e1c67a2d28f123849ee1bb76e7391b93eb12", checksums[0].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Md5, checksums[1].Algorithm); - Assert.AreEqual("d41d8cd98f00b204e9800998ecf8427e", checksums[1].Value); + Assert.Equal(2, checksums.Length); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, checksums[0].Algorithm); + Assert.Equal("2fd4e1c67a2d28f123849ee1bb76e7391b93eb12", checksums[0].Value); + Assert.Equal(SpdxChecksumAlgorithm.Md5, checksums[1].Algorithm); + Assert.Equal("d41d8cd98f00b204e9800998ecf8427e", checksums[1].Value); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeCreationInformation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeCreationInformation.cs index 9e0c0c7..23c6444 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeCreationInformation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeCreationInformation.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX creation information to classes. /// /// -/// Exercises deserialization of SPDX creation information using MSTest as the approved -/// test framework for this repository. Constructs inline JSON and verifies the resulting +/// Exercises deserialization of SPDX creation information using xUnit v3 as the test +/// framework. Constructs inline JSON and verifies the resulting /// fields. /// -[TestClass] public class Spdx2JsonDeserializeCreationInformation { /// @@ -42,7 +41,7 @@ public class Spdx2JsonDeserializeCreationInformation /// licenseListVersion) are correctly mapped to the /// properties. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeCreationInformation_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing creation information @@ -64,14 +63,14 @@ public void Spdx2JsonDeserializer_DeserializeCreationInformation_ValidInput_Corr var creationInformation = Spdx2JsonDeserializer.DeserializeCreationInformation(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual( + Assert.Equal( "This package has been shipped in source and binary form.\nThe binaries were created with gcc 4.5.1 and expect to link to\ncompatible system run time libraries.", creationInformation.Comment); - Assert.AreEqual("2010-01-29T18:30:22Z", creationInformation.Created); - Assert.HasCount(3, creationInformation.Creators); - Assert.AreEqual("Tool: LicenseFind-1.0", creationInformation.Creators[0]); - Assert.AreEqual("Organization: ExampleCodeInspect ()", creationInformation.Creators[1]); - Assert.AreEqual("Person: Jane Doe ()", creationInformation.Creators[2]); - Assert.AreEqual("3.17", creationInformation.LicenseListVersion); + Assert.Equal("2010-01-29T18:30:22Z", creationInformation.Created); + Assert.Equal(3, creationInformation.Creators.Length); + Assert.Equal("Tool: LicenseFind-1.0", creationInformation.Creators[0]); + Assert.Equal("Organization: ExampleCodeInspect ()", creationInformation.Creators[1]); + Assert.Equal("Person: Jane Doe ()", creationInformation.Creators[2]); + Assert.Equal("3.17", creationInformation.LicenseListVersion); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeDocument.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeDocument.cs index cd33191..9b0b4e9 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeDocument.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeDocument.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX documents to classes. /// /// -/// Exercises deserialization of SPDX document-level elements using MSTest as the -/// approved test framework for this repository. Constructs inline JSON and verifies +/// Exercises deserialization of SPDX document-level elements using xUnit v3 as the +/// test framework. Constructs inline JSON and verifies /// the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeDocument { /// @@ -42,7 +41,7 @@ public class Spdx2JsonDeserializeDocument /// comment, documentNamespace, documentDescribes) and empty collection fields are /// correctly mapped when a full document JSON object is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeDocument_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a document @@ -86,22 +85,22 @@ public void Spdx2JsonDeserializer_DeserializeDocument_ValidInput_CorrectResults( var document = Spdx2JsonDeserializer.DeserializeDocument(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("SPDXRef-DOCUMENT", document.Id); - Assert.AreEqual("SPDX-2.3", document.Version); - Assert.AreEqual("SPDX-Tools-v2.0", document.Name); - Assert.AreEqual("CC0-1.0", document.DataLicense); - Assert.AreEqual("This document was created using SPDX 2.0 using licenses from the web site.", document.Comment); - Assert.AreEqual("http://spdx.org/spdxdocs/spdx-example-json-2.3-444504E0-4F89-41D3-9A0C-0305E82C3301", + Assert.Equal("SPDXRef-DOCUMENT", document.Id); + Assert.Equal("SPDX-2.3", document.Version); + Assert.Equal("SPDX-Tools-v2.0", document.Name); + Assert.Equal("CC0-1.0", document.DataLicense); + Assert.Equal("This document was created using SPDX 2.0 using licenses from the web site.", document.Comment); + Assert.Equal("http://spdx.org/spdxdocs/spdx-example-json-2.3-444504E0-4F89-41D3-9A0C-0305E82C3301", document.DocumentNamespace); - Assert.HasCount(3, document.Describes); - Assert.AreEqual("SPDXRef-File", document.Describes[0]); - Assert.AreEqual("SPDXRef-File", document.Describes[1]); - Assert.AreEqual("SPDXRef-Package", document.Describes[2]); - Assert.IsEmpty(document.ExternalDocumentReferences); - Assert.IsEmpty(document.ExtractedLicensingInfo); - Assert.IsEmpty(document.Packages); - Assert.IsEmpty(document.Files); - Assert.IsEmpty(document.Snippets); - Assert.IsEmpty(document.Relationships); + Assert.Equal(3, document.Describes.Length); + Assert.Equal("SPDXRef-File", document.Describes[0]); + Assert.Equal("SPDXRef-File", document.Describes[1]); + Assert.Equal("SPDXRef-Package", document.Describes[2]); + Assert.Empty(document.ExternalDocumentReferences); + Assert.Empty(document.ExtractedLicensingInfo); + Assert.Empty(document.Packages); + Assert.Empty(document.Files); + Assert.Empty(document.Snippets); + Assert.Empty(document.Relationships); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalDocumentReference.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalDocumentReference.cs index 5199659..f94bc11 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalDocumentReference.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalDocumentReference.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX external document references to classes. /// /// -/// Exercises deserialization of SPDX external document reference elements using MSTest -/// as the approved test framework for this repository. Each test constructs inline JSON +/// Exercises deserialization of SPDX external document reference elements using xUnit v3 +/// as the test framework. Each test constructs inline JSON /// and verifies the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeExternalDocumentReference { /// @@ -42,7 +41,7 @@ public class Spdx2JsonDeserializeExternalDocumentReference /// JSON fields are correctly mapped to the /// properties when a single object is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeExternalDocumentReference_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing an external document reference @@ -62,10 +61,10 @@ public void Spdx2JsonDeserializer_DeserializeExternalDocumentReference_ValidInpu var externalDocumentReference = Spdx2JsonDeserializer.DeserializeExternalDocumentReference(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("DocumentRef-1", externalDocumentReference.ExternalDocumentId); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, externalDocumentReference.Checksum.Algorithm); - Assert.AreEqual("d6a770ba38583ed4bb4525bd96e50461655d2759", externalDocumentReference.Checksum.Value); - Assert.AreEqual("SPDXRef-Document", externalDocumentReference.Document); + Assert.Equal("DocumentRef-1", externalDocumentReference.ExternalDocumentId); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, externalDocumentReference.Checksum.Algorithm); + Assert.Equal("d6a770ba38583ed4bb4525bd96e50461655d2759", externalDocumentReference.Checksum.Value); + Assert.Equal("SPDXRef-Document", externalDocumentReference.Document); } /// @@ -75,7 +74,7 @@ public void Spdx2JsonDeserializer_DeserializeExternalDocumentReference_ValidInpu /// Verifies that a JSON array containing one external document reference object is /// deserialized to a single-element array with all fields correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple external document references @@ -98,10 +97,10 @@ public void Spdx2JsonDeserializer_DeserializeExternalDocumentReferences_ValidInp var externalDocumentReferences = Spdx2JsonDeserializer.DeserializeExternalDocumentReferences(json); // Assert: Verify the deserialized array has the expected number of references and their properties - Assert.HasCount(1, externalDocumentReferences); - Assert.AreEqual("DocumentRef-1", externalDocumentReferences[0].ExternalDocumentId); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, externalDocumentReferences[0].Checksum.Algorithm); - Assert.AreEqual("d6a770ba38583ed4bb4525bd96e50461655d2759", externalDocumentReferences[0].Checksum.Value); - Assert.AreEqual("SPDXRef-Document", externalDocumentReferences[0].Document); + Assert.Single(externalDocumentReferences); + Assert.Equal("DocumentRef-1", externalDocumentReferences[0].ExternalDocumentId); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, externalDocumentReferences[0].Checksum.Algorithm); + Assert.Equal("d6a770ba38583ed4bb4525bd96e50461655d2759", externalDocumentReferences[0].Checksum.Value); + Assert.Equal("SPDXRef-Document", externalDocumentReferences[0].Document); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalReference.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalReference.cs index fe72e64..9ca5375 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalReference.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExternalReference.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX external references to classes. /// /// -/// Exercises deserialization of SPDX external reference elements using MSTest as the -/// approved test framework for this repository. Each test constructs inline JSON and +/// Exercises deserialization of SPDX external reference elements using xUnit v3 as the +/// test framework. Each test constructs inline JSON and /// verifies the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeExternalReference { /// @@ -42,7 +41,7 @@ public class Spdx2JsonDeserializeExternalReference /// fields are correctly mapped to the properties /// when a single SECURITY-category reference is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeExternalReference_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing an external reference @@ -58,10 +57,10 @@ public void Spdx2JsonDeserializer_DeserializeExternalReference_ValidInput_Correc var reference = Spdx2JsonDeserializer.DeserializeExternalReference(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("This is just an example", reference.Comment); - Assert.AreEqual("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", reference.Locator); - Assert.AreEqual("cpe23Type", reference.Type); - Assert.AreEqual(SpdxReferenceCategory.Security, reference.Category); + Assert.Equal("This is just an example", reference.Comment); + Assert.Equal("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", reference.Locator); + Assert.Equal("cpe23Type", reference.Type); + Assert.Equal(SpdxReferenceCategory.Security, reference.Category); } /// @@ -71,7 +70,7 @@ public void Spdx2JsonDeserializer_DeserializeExternalReference_ValidInput_Correc /// Verifies that a JSON array of two external reference objects (one SECURITY, one OTHER /// category) is deserialized to a two-element array with all fields correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeExternalReferences_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple external references @@ -98,16 +97,16 @@ public void Spdx2JsonDeserializer_DeserializeExternalReferences_ValidInput_Corre var references = Spdx2JsonDeserializer.DeserializeExternalReferences(json); // Assert: Verify the deserialized array has the expected number of references and their properties - Assert.HasCount(2, references); - Assert.AreEqual("This is just an example", references[0].Comment); - Assert.AreEqual("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", references[0].Locator); - Assert.AreEqual("cpe23Type", references[0].Type); - Assert.AreEqual(SpdxReferenceCategory.Security, references[0].Category); - Assert.AreEqual("This is the external ref for Acme", references[1].Comment); - Assert.AreEqual("acmecorp/acmenator/4.1.3-alpha", references[1].Locator); - Assert.AreEqual( + Assert.Equal(2, references.Length); + Assert.Equal("This is just an example", references[0].Comment); + Assert.Equal("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", references[0].Locator); + Assert.Equal("cpe23Type", references[0].Type); + Assert.Equal(SpdxReferenceCategory.Security, references[0].Category); + Assert.Equal("This is the external ref for Acme", references[1].Comment); + Assert.Equal("acmecorp/acmenator/4.1.3-alpha", references[1].Locator); + Assert.Equal( "http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301#LocationRef-acmeforge", references[1].Type); - Assert.AreEqual(SpdxReferenceCategory.Other, references[1].Category); + Assert.Equal(SpdxReferenceCategory.Other, references[1].Category); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExtractedLicensingInfo.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExtractedLicensingInfo.cs index f7a6fed..2d95447 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExtractedLicensingInfo.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeExtractedLicensingInfo.cs @@ -28,10 +28,9 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// /// Exercises deserialization of SPDX extracted licensing information elements using -/// MSTest as the approved test framework for this repository. Each test constructs +/// xUnit v3 as the test framework. Each test constructs /// inline JSON and verifies the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeExtractedLicensingInfo { /// @@ -43,7 +42,7 @@ public class Spdx2JsonDeserializeExtractedLicensingInfo /// properties when a single object is /// deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing extracted licensing information @@ -60,12 +59,12 @@ public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_ValidInput_C var extractedLicensingInfo = Spdx2JsonDeserializer.DeserializeExtractedLicensingInfo(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("MIT", extractedLicensingInfo.LicenseId); - Assert.AreEqual("This is the MIT license", extractedLicensingInfo.ExtractedText); - Assert.AreEqual("MIT License", extractedLicensingInfo.Name); - Assert.HasCount(1, extractedLicensingInfo.CrossReferences); - Assert.AreEqual("https://opensource.org/licenses/MIT", extractedLicensingInfo.CrossReferences[0]); - Assert.AreEqual("This is a comment", extractedLicensingInfo.Comment); + Assert.Equal("MIT", extractedLicensingInfo.LicenseId); + Assert.Equal("This is the MIT license", extractedLicensingInfo.ExtractedText); + Assert.Equal("MIT License", extractedLicensingInfo.Name); + Assert.Single(extractedLicensingInfo.CrossReferences); + Assert.Equal("https://opensource.org/licenses/MIT", extractedLicensingInfo.CrossReferences[0]); + Assert.Equal("This is a comment", extractedLicensingInfo.Comment); } /// @@ -75,7 +74,7 @@ public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfo_ValidInput_C /// Verifies that a JSON array containing one extracted licensing info object is /// deserialized to a single-element array with all fields correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple extracted licensing information @@ -95,12 +94,12 @@ public void Spdx2JsonDeserializer_DeserializeExtractedLicensingInfos_ValidInput_ var extractedLicensingInfos = Spdx2JsonDeserializer.DeserializeExtractedLicensingInfos(json); // Assert: Verify the deserialized array has the expected properties - Assert.HasCount(1, extractedLicensingInfos); - Assert.AreEqual("MIT", extractedLicensingInfos[0].LicenseId); - Assert.AreEqual("This is the MIT license", extractedLicensingInfos[0].ExtractedText); - Assert.AreEqual("MIT License", extractedLicensingInfos[0].Name); - Assert.HasCount(1, extractedLicensingInfos[0].CrossReferences); - Assert.AreEqual("https://opensource.org/licenses/MIT", extractedLicensingInfos[0].CrossReferences[0]); - Assert.AreEqual("This is a comment", extractedLicensingInfos[0].Comment); + Assert.Single(extractedLicensingInfos); + Assert.Equal("MIT", extractedLicensingInfos[0].LicenseId); + Assert.Equal("This is the MIT license", extractedLicensingInfos[0].ExtractedText); + Assert.Equal("MIT License", extractedLicensingInfos[0].Name); + Assert.Single(extractedLicensingInfos[0].CrossReferences); + Assert.Equal("https://opensource.org/licenses/MIT", extractedLicensingInfos[0].CrossReferences[0]); + Assert.Equal("This is a comment", extractedLicensingInfos[0].Comment); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeFile.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeFile.cs index 085c99d..7909af2 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeFile.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeFile.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX files to classes. /// /// -/// Exercises deserialization of SPDX file elements using MSTest as the approved test -/// framework for this repository. Each test constructs inline JSON and verifies the +/// Exercises deserialization of SPDX file elements using xUnit v3 as the test +/// framework. Each test constructs inline JSON and verifies the /// resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeFile { /// @@ -43,7 +42,7 @@ public class Spdx2JsonDeserializeFile /// correctly mapped to properties when a single file JSON /// object is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeFile_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a file @@ -71,19 +70,19 @@ public void Spdx2JsonDeserializer_DeserializeFile_ValidInput_CorrectResults() var file = Spdx2JsonDeserializer.DeserializeFile(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("SPDXRef-File", file.Id); - Assert.AreEqual("src/DemaConsulting.SpdxModel/SpdxFile.cs", file.FileName); - Assert.HasCount(1, file.FileTypes); - Assert.AreEqual(SpdxFileType.Source, file.FileTypes[0]); - Assert.HasCount(1, file.Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, file.Checksums[0].Algorithm); - Assert.AreEqual("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", file.Checksums[0].Value); - Assert.AreEqual("MIT", file.ConcludedLicense); - Assert.HasCount(1, file.LicenseInfoInFiles); - Assert.AreEqual("MIT", file.LicenseInfoInFiles[0]); - Assert.AreEqual("This is the MIT license", file.LicenseComments); - Assert.AreEqual("This is a comment", file.Comment); - Assert.AreEqual("This is a notice", file.Notice); + Assert.Equal("SPDXRef-File", file.Id); + Assert.Equal("src/DemaConsulting.SpdxModel/SpdxFile.cs", file.FileName); + Assert.Single(file.FileTypes); + Assert.Equal(SpdxFileType.Source, file.FileTypes[0]); + Assert.Single(file.Checksums); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, file.Checksums[0].Algorithm); + Assert.Equal("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", file.Checksums[0].Value); + Assert.Equal("MIT", file.ConcludedLicense); + Assert.Single(file.LicenseInfoInFiles); + Assert.Equal("MIT", file.LicenseInfoInFiles[0]); + Assert.Equal("This is the MIT license", file.LicenseComments); + Assert.Equal("This is a comment", file.Comment); + Assert.Equal("This is a notice", file.Notice); } /// @@ -93,7 +92,7 @@ public void Spdx2JsonDeserializer_DeserializeFile_ValidInput_CorrectResults() /// Verifies that a JSON array containing one file object is deserialized to a /// single-element array with all fields correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeFiles_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple files @@ -124,19 +123,19 @@ public void Spdx2JsonDeserializer_DeserializeFiles_ValidInput_CorrectResults() var files = Spdx2JsonDeserializer.DeserializeFiles(json); // Assert: Verify the deserialized array has the expected properties - Assert.HasCount(1, files); - Assert.AreEqual("SPDXRef-File", files[0].Id); - Assert.AreEqual("src/DemaConsulting.SpdxModel/SpdxFile.cs", files[0].FileName); - Assert.HasCount(1, files[0].FileTypes); - Assert.AreEqual(SpdxFileType.Source, files[0].FileTypes[0]); - Assert.HasCount(1, files[0].Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, files[0].Checksums[0].Algorithm); - Assert.AreEqual("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", files[0].Checksums[0].Value); - Assert.AreEqual("MIT", files[0].ConcludedLicense); - Assert.HasCount(1, files[0].LicenseInfoInFiles); - Assert.AreEqual("MIT", files[0].LicenseInfoInFiles[0]); - Assert.AreEqual("This is the MIT license", files[0].LicenseComments); - Assert.AreEqual("This is a comment", files[0].Comment); - Assert.AreEqual("This is a notice", files[0].Notice); + Assert.Single(files); + Assert.Equal("SPDXRef-File", files[0].Id); + Assert.Equal("src/DemaConsulting.SpdxModel/SpdxFile.cs", files[0].FileName); + Assert.Single(files[0].FileTypes); + Assert.Equal(SpdxFileType.Source, files[0].FileTypes[0]); + Assert.Single(files[0].Checksums); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, files[0].Checksums[0].Algorithm); + Assert.Equal("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", files[0].Checksums[0].Value); + Assert.Equal("MIT", files[0].ConcludedLicense); + Assert.Single(files[0].LicenseInfoInFiles); + Assert.Equal("MIT", files[0].LicenseInfoInFiles[0]); + Assert.Equal("This is the MIT license", files[0].LicenseComments); + Assert.Equal("This is a comment", files[0].Comment); + Assert.Equal("This is a notice", files[0].Notice); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackage.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackage.cs index 9630bd9..9f7d526 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackage.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackage.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX packages to classes. /// /// -/// Exercises deserialization of SPDX package elements using MSTest as the approved -/// test framework for this repository. Each test constructs inline JSON and verifies +/// Exercises deserialization of SPDX package elements using xUnit v3 as the test +/// framework. Each test constructs inline JSON and verifies /// the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializePackage { /// @@ -43,7 +42,7 @@ public class Spdx2JsonDeserializePackage /// are correctly mapped to properties when a single package /// JSON object is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializePackage_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a package @@ -102,34 +101,34 @@ public void Spdx2JsonDeserializer_DeserializePackage_ValidInput_CorrectResults() var package = Spdx2JsonDeserializer.DeserializePackage(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("SPDXRef-Package", package.Id); - Assert.HasCount(1, package.Annotations); - Assert.AreEqual("2011-01-29T18:30:22Z", package.Annotations[0].Date); - Assert.AreEqual(SpdxAnnotationType.Other, package.Annotations[0].Type); - Assert.AreEqual("Person: Package Commenter", package.Annotations[0].Annotator); - Assert.AreEqual("Package level annotation", package.Annotations[0].Comment); - Assert.HasCount(1, package.AttributionText); - Assert.AreEqual( + Assert.Equal("SPDXRef-Package", package.Id); + Assert.Single(package.Annotations); + Assert.Equal("2011-01-29T18:30:22Z", package.Annotations[0].Date); + Assert.Equal(SpdxAnnotationType.Other, package.Annotations[0].Type); + Assert.Equal("Person: Package Commenter", package.Annotations[0].Annotator); + Assert.Equal("Package level annotation", package.Annotations[0].Comment); + Assert.Single(package.AttributionText); + Assert.Equal( "The GNU C Library is free software. See the file COPYING.LIB for copying conditions, and LICENSES for notices about a few contributions that require these additional notices to be distributed. License copyright years may be listed using range notation, e.g., 1996-2015, indicating that every year in the range, inclusive, is a copyrightable year that would otherwise be listed individually.", package.AttributionText[0]); - Assert.AreEqual("2011-01-29T18:30:22Z", package.BuiltDate); - Assert.HasCount(3, package.Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Md5, package.Checksums[0].Algorithm); - Assert.AreEqual("624c1abb3664f4b35547e7c73864ad24", package.Checksums[0].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, package.Checksums[1].Algorithm); - Assert.AreEqual("85ed0817af83a24ad8da68c2b5094de69833983c", package.Checksums[1].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha256, package.Checksums[2].Algorithm); - Assert.AreEqual("11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd", package.Checksums[2].Value); - Assert.AreEqual("Copyright 2008-2010 John Smith", package.CopyrightText); - Assert.AreEqual( + Assert.Equal("2011-01-29T18:30:22Z", package.BuiltDate); + Assert.Equal(3, package.Checksums.Length); + Assert.Equal(SpdxChecksumAlgorithm.Md5, package.Checksums[0].Algorithm); + Assert.Equal("624c1abb3664f4b35547e7c73864ad24", package.Checksums[0].Value); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, package.Checksums[1].Algorithm); + Assert.Equal("85ed0817af83a24ad8da68c2b5094de69833983c", package.Checksums[1].Value); + Assert.Equal(SpdxChecksumAlgorithm.Sha256, package.Checksums[2].Algorithm); + Assert.Equal("11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd", package.Checksums[2].Value); + Assert.Equal("Copyright 2008-2010 John Smith", package.CopyrightText); + Assert.Equal( "The GNU C Library defines functions that are specified by the ISO C standard, as well as additional features specific to POSIX and other derivatives of the Unix operating system, and extensions specific to GNU systems.", package.Description); - Assert.AreEqual("http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz", package.DownloadLocation); - Assert.HasCount(1, package.ExternalReferences); - Assert.AreEqual(SpdxReferenceCategory.Security, package.ExternalReferences[0].Category); - Assert.AreEqual("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", + Assert.Equal("http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz", package.DownloadLocation); + Assert.Single(package.ExternalReferences); + Assert.Equal(SpdxReferenceCategory.Security, package.ExternalReferences[0].Category); + Assert.Equal("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", package.ExternalReferences[0].Locator); - Assert.AreEqual("cpe23Type", package.ExternalReferences[0].Type); + Assert.Equal("cpe23Type", package.ExternalReferences[0].Type); } /// @@ -139,7 +138,7 @@ public void Spdx2JsonDeserializer_DeserializePackage_ValidInput_CorrectResults() /// Verifies that a JSON array containing one package object is deserialized to a /// single-element array with all fields correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializePackages_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple packages @@ -201,35 +200,35 @@ public void Spdx2JsonDeserializer_DeserializePackages_ValidInput_CorrectResults( var packages = Spdx2JsonDeserializer.DeserializePackages(json); // Assert: Verify the deserialized array has the expected number of packages and their properties - Assert.HasCount(1, packages); - Assert.AreEqual("SPDXRef-Package", packages[0].Id); - Assert.HasCount(1, packages[0].Annotations); - Assert.AreEqual("2011-01-29T18:30:22Z", packages[0].Annotations[0].Date); - Assert.AreEqual(SpdxAnnotationType.Other, packages[0].Annotations[0].Type); - Assert.AreEqual("Person: Package Commenter", packages[0].Annotations[0].Annotator); - Assert.AreEqual("Package level annotation", packages[0].Annotations[0].Comment); - Assert.HasCount(1, packages[0].AttributionText); - Assert.AreEqual( + Assert.Single(packages); + Assert.Equal("SPDXRef-Package", packages[0].Id); + Assert.Single(packages[0].Annotations); + Assert.Equal("2011-01-29T18:30:22Z", packages[0].Annotations[0].Date); + Assert.Equal(SpdxAnnotationType.Other, packages[0].Annotations[0].Type); + Assert.Equal("Person: Package Commenter", packages[0].Annotations[0].Annotator); + Assert.Equal("Package level annotation", packages[0].Annotations[0].Comment); + Assert.Single(packages[0].AttributionText); + Assert.Equal( "The GNU C Library is free software. See the file COPYING.LIB for copying conditions, and LICENSES for notices about a few contributions that require these additional notices to be distributed. License copyright years may be listed using range notation, e.g., 1996-2015, indicating that every year in the range, inclusive, is a copyrightable year that would otherwise be listed individually.", packages[0].AttributionText[0]); - Assert.AreEqual("2011-01-29T18:30:22Z", packages[0].BuiltDate); - Assert.HasCount(3, packages[0].Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Md5, packages[0].Checksums[0].Algorithm); - Assert.AreEqual("624c1abb3664f4b35547e7c73864ad24", packages[0].Checksums[0].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, packages[0].Checksums[1].Algorithm); - Assert.AreEqual("85ed0817af83a24ad8da68c2b5094de69833983c", packages[0].Checksums[1].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha256, packages[0].Checksums[2].Algorithm); - Assert.AreEqual("11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd", + Assert.Equal("2011-01-29T18:30:22Z", packages[0].BuiltDate); + Assert.Equal(3, packages[0].Checksums.Length); + Assert.Equal(SpdxChecksumAlgorithm.Md5, packages[0].Checksums[0].Algorithm); + Assert.Equal("624c1abb3664f4b35547e7c73864ad24", packages[0].Checksums[0].Value); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, packages[0].Checksums[1].Algorithm); + Assert.Equal("85ed0817af83a24ad8da68c2b5094de69833983c", packages[0].Checksums[1].Value); + Assert.Equal(SpdxChecksumAlgorithm.Sha256, packages[0].Checksums[2].Algorithm); + Assert.Equal("11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd", packages[0].Checksums[2].Value); - Assert.AreEqual("Copyright 2008-2010 John Smith", packages[0].CopyrightText); - Assert.AreEqual( + Assert.Equal("Copyright 2008-2010 John Smith", packages[0].CopyrightText); + Assert.Equal( "The GNU C Library defines functions that are specified by the ISO C standard, as well as additional features specific to POSIX and other derivatives of the Unix operating system, and extensions specific to GNU systems.", packages[0].Description); - Assert.AreEqual("http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz", packages[0].DownloadLocation); - Assert.HasCount(1, packages[0].ExternalReferences); - Assert.AreEqual(SpdxReferenceCategory.Security, packages[0].ExternalReferences[0].Category); - Assert.AreEqual("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", + Assert.Equal("http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz", packages[0].DownloadLocation); + Assert.Single(packages[0].ExternalReferences); + Assert.Equal(SpdxReferenceCategory.Security, packages[0].ExternalReferences[0].Category); + Assert.Equal("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", packages[0].ExternalReferences[0].Locator); - Assert.AreEqual("cpe23Type", packages[0].ExternalReferences[0].Type); + Assert.Equal("cpe23Type", packages[0].ExternalReferences[0].Type); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackageVerificationCode.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackageVerificationCode.cs index 876cadc..0e0227d 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackageVerificationCode.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializePackageVerificationCode.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX package verification codes to classes. /// /// -/// Exercises deserialization of SPDX package verification code elements using MSTest -/// as the approved test framework for this repository. Constructs inline JSON and +/// Exercises deserialization of SPDX package verification code elements using xUnit v3 +/// as the test framework. Constructs inline JSON and /// verifies the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializePackageVerificationCode { /// @@ -42,7 +41,7 @@ public class Spdx2JsonDeserializePackageVerificationCode /// JSON fields are correctly mapped to the /// properties and that the result is non-null. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializePackageVerificationCode_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a package verification code @@ -60,10 +59,10 @@ public void Spdx2JsonDeserializer_DeserializePackageVerificationCode_ValidInput_ var packageVerificationCode = Spdx2JsonDeserializer.DeserializeVerificationCode(json); // Assert: Verify the deserialized object has the expected properties - Assert.IsNotNull(packageVerificationCode); - Assert.AreEqual("d3b07384d113edec49eaa6238ad5ff00", packageVerificationCode.Value); - Assert.HasCount(2, packageVerificationCode.ExcludedFiles); - Assert.AreEqual("file1.txt", packageVerificationCode.ExcludedFiles[0]); - Assert.AreEqual("file2.txt", packageVerificationCode.ExcludedFiles[1]); + Assert.NotNull(packageVerificationCode); + Assert.Equal("d3b07384d113edec49eaa6238ad5ff00", packageVerificationCode.Value); + Assert.Equal(2, packageVerificationCode.ExcludedFiles.Length); + Assert.Equal("file1.txt", packageVerificationCode.ExcludedFiles[0]); + Assert.Equal("file2.txt", packageVerificationCode.ExcludedFiles[1]); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeRelationship.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeRelationship.cs index bd5aace..de6daa6 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeRelationship.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeRelationship.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX relationships to classes. /// /// -/// Exercises deserialization of SPDX relationship elements using MSTest as the approved -/// test framework for this repository. Each test constructs inline JSON and verifies +/// Exercises deserialization of SPDX relationship elements using xUnit v3 as the test +/// framework. Each test constructs inline JSON and verifies /// the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeRelationship { /// @@ -42,7 +41,7 @@ public class Spdx2JsonDeserializeRelationship /// fields are correctly mapped to the properties when a /// single relationship object is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeRelationship_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a relationship @@ -58,10 +57,10 @@ public void Spdx2JsonDeserializer_DeserializeRelationship_ValidInput_CorrectResu var relationship = Spdx2JsonDeserializer.DeserializeRelationship(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("SPDXRef-DOCUMENT", relationship.Id); - Assert.AreEqual("SPDXRef-Package", relationship.RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Describes, relationship.RelationshipType); - Assert.AreEqual("This is just an example", relationship.Comment); + Assert.Equal("SPDXRef-DOCUMENT", relationship.Id); + Assert.Equal("SPDXRef-Package", relationship.RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Describes, relationship.RelationshipType); + Assert.Equal("This is just an example", relationship.Comment); } /// @@ -71,7 +70,7 @@ public void Spdx2JsonDeserializer_DeserializeRelationship_ValidInput_CorrectResu /// Verifies that a JSON array of two relationship objects (DESCRIBES and DESCRIBED_BY) /// is deserialized to a two-element array with all fields correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeRelationships_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple relationships @@ -97,14 +96,14 @@ public void Spdx2JsonDeserializer_DeserializeRelationships_ValidInput_CorrectRes var relationships = Spdx2JsonDeserializer.DeserializeRelationships(json); // Assert: Verify the deserialized objects have the expected properties - Assert.HasCount(2, relationships); - Assert.AreEqual("SPDXRef-DOCUMENT", relationships[0].Id); - Assert.AreEqual("SPDXRef-Package", relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.Describes, relationships[0].RelationshipType); - Assert.AreEqual("This is just an example", relationships[0].Comment); - Assert.AreEqual("SPDXRef-Package", relationships[1].Id); - Assert.AreEqual("SPDXRef-DOCUMENT", relationships[1].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DescribedBy, relationships[1].RelationshipType); - Assert.AreEqual("This is just an example", relationships[1].Comment); + Assert.Equal(2, relationships.Length); + Assert.Equal("SPDXRef-DOCUMENT", relationships[0].Id); + Assert.Equal("SPDXRef-Package", relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.Describes, relationships[0].RelationshipType); + Assert.Equal("This is just an example", relationships[0].Comment); + Assert.Equal("SPDXRef-Package", relationships[1].Id); + Assert.Equal("SPDXRef-DOCUMENT", relationships[1].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DescribedBy, relationships[1].RelationshipType); + Assert.Equal("This is just an example", relationships[1].Comment); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs index f55d2ec..085d347 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializeSnippet.cs @@ -27,11 +27,10 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Tests for deserializing SPDX snippets to classes. /// /// -/// Exercises deserialization of SPDX snippet elements using MSTest as the approved -/// test framework for this repository. Each test constructs inline JSON and verifies +/// Exercises deserialization of SPDX snippet elements using xUnit v3 as the test +/// framework. Each test constructs inline JSON and verifies /// the resulting fields. /// -[TestClass] public class Spdx2JsonDeserializeSnippet { /// @@ -43,7 +42,7 @@ public class Spdx2JsonDeserializeSnippet /// snippetFromFile) are correctly mapped to properties when /// a single snippet JSON object with both byte and line ranges is deserialized. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeSnippet_ValidInput_CorrectResults() { // Arrange: Create a JSON object representing a snippet @@ -97,23 +96,23 @@ public void Spdx2JsonDeserializer_DeserializeSnippet_ValidInput_CorrectResults() var snippet = Spdx2JsonDeserializer.DeserializeSnippet(json); // Assert: Verify the deserialized object has the expected properties - Assert.AreEqual("SPDXRef-Snippet", snippet.Id); - Assert.AreEqual( + Assert.Equal("SPDXRef-Snippet", snippet.Id); + Assert.Equal( "This snippet was identified as significant and highlighted in this Apache-2.0 file, when a commercial scanner identified it as being derived from file foo.c in package xyz which is licensed under GPL-2.0.", snippet.Comment); - Assert.AreEqual("Copyright 2008-2010 John Smith", snippet.CopyrightText); - Assert.AreEqual( + Assert.Equal("Copyright 2008-2010 John Smith", snippet.CopyrightText); + Assert.Equal( "The concluded license was taken from package xyz, from which the snippet was copied into the current file. The concluded license information was found in the COPYING.txt file in package xyz.", snippet.LicenseComments); - Assert.AreEqual("GPL-2.0-only", snippet.ConcludedLicense); - Assert.HasCount(1, snippet.LicenseInfoInSnippet); - Assert.AreEqual("GPL-2.0-only", snippet.LicenseInfoInSnippet[0]); - Assert.AreEqual("from linux kernel", snippet.Name); - Assert.AreEqual(420, snippet.SnippetByteEnd); - Assert.AreEqual(310, snippet.SnippetByteStart); - Assert.AreEqual(23, snippet.SnippetLineEnd); - Assert.AreEqual(5, snippet.SnippetLineStart); - Assert.AreEqual("SPDXRef-DoapSource", snippet.SnippetFromFile); + Assert.Equal("GPL-2.0-only", snippet.ConcludedLicense); + Assert.Single(snippet.LicenseInfoInSnippet); + Assert.Equal("GPL-2.0-only", snippet.LicenseInfoInSnippet[0]); + Assert.Equal("from linux kernel", snippet.Name); + Assert.Equal(420, snippet.SnippetByteEnd); + Assert.Equal(310, snippet.SnippetByteStart); + Assert.Equal(23, snippet.SnippetLineEnd); + Assert.Equal(5, snippet.SnippetLineStart); + Assert.Equal("SPDXRef-DoapSource", snippet.SnippetFromFile); } /// @@ -126,7 +125,7 @@ public void Spdx2JsonDeserializer_DeserializeSnippet_ValidInput_CorrectResults() /// lineNumber pointers are present, and /// must default to zero rather than throwing. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsToZero() { // Arrange: Create a JSON snippet with only byte ranges (no lineNumber entries) @@ -159,10 +158,10 @@ public void Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsT var snippet = Spdx2JsonDeserializer.DeserializeSnippet(json); // Assert: Byte ranges are correct and absent line ranges default to 0 - Assert.AreEqual(310, snippet.SnippetByteStart); - Assert.AreEqual(420, snippet.SnippetByteEnd); - Assert.AreEqual(0, snippet.SnippetLineStart); - Assert.AreEqual(0, snippet.SnippetLineEnd); + Assert.Equal(310, snippet.SnippetByteStart); + Assert.Equal(420, snippet.SnippetByteEnd); + Assert.Equal(0, snippet.SnippetLineStart); + Assert.Equal(0, snippet.SnippetLineEnd); } /// @@ -172,7 +171,7 @@ public void Spdx2JsonDeserializer_DeserializeSnippet_WithoutLineRanges_DefaultsT /// Verifies that a JSON array containing one snippet object with both byte and line /// ranges is deserialized to a single-element array with all fields correctly populated. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_DeserializeSnippets_ValidInput_CorrectResults() { // Arrange: Create a JSON array representing multiple snippets @@ -229,23 +228,23 @@ public void Spdx2JsonDeserializer_DeserializeSnippets_ValidInput_CorrectResults( var snippets = Spdx2JsonDeserializer.DeserializeSnippets(json); // Assert: Verify the deserialized array has the expected properties - Assert.HasCount(1, snippets); - Assert.AreEqual("SPDXRef-Snippet", snippets[0].Id); - Assert.AreEqual( + Assert.Single(snippets); + Assert.Equal("SPDXRef-Snippet", snippets[0].Id); + Assert.Equal( "This snippet was identified as significant and highlighted in this Apache-2.0 file, when a commercial scanner identified it as being derived from file foo.c in package xyz which is licensed under GPL-2.0.", snippets[0].Comment); - Assert.AreEqual("Copyright 2008-2010 John Smith", snippets[0].CopyrightText); - Assert.AreEqual( + Assert.Equal("Copyright 2008-2010 John Smith", snippets[0].CopyrightText); + Assert.Equal( "The concluded license was taken from package xyz, from which the snippet was copied into the current file. The concluded license information was found in the COPYING.txt file in package xyz.", snippets[0].LicenseComments); - Assert.AreEqual("GPL-2.0-only", snippets[0].ConcludedLicense); - Assert.HasCount(1, snippets[0].LicenseInfoInSnippet); - Assert.AreEqual("GPL-2.0-only", snippets[0].LicenseInfoInSnippet[0]); - Assert.AreEqual("from linux kernel", snippets[0].Name); - Assert.AreEqual(420, snippets[0].SnippetByteEnd); - Assert.AreEqual(310, snippets[0].SnippetByteStart); - Assert.AreEqual(23, snippets[0].SnippetLineEnd); - Assert.AreEqual(5, snippets[0].SnippetLineStart); - Assert.AreEqual("SPDXRef-DoapSource", snippets[0].SnippetFromFile); + Assert.Equal("GPL-2.0-only", snippets[0].ConcludedLicense); + Assert.Single(snippets[0].LicenseInfoInSnippet); + Assert.Equal("GPL-2.0-only", snippets[0].LicenseInfoInSnippet[0]); + Assert.Equal("from linux kernel", snippets[0].Name); + Assert.Equal(420, snippets[0].SnippetByteEnd); + Assert.Equal(310, snippets[0].SnippetByteStart); + Assert.Equal(23, snippets[0].SnippetLineEnd); + Assert.Equal(5, snippets[0].SnippetLineStart); + Assert.Equal("SPDXRef-DoapSource", snippets[0].SnippetFromFile); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs index db7f82c..30dcc2e 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonDeserializerTests.cs @@ -30,7 +30,6 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// Covers the error-handling paths of : invalid JSON /// input that should throw rather than return a partially-populated document. /// -[TestClass] public class Spdx2JsonDeserializerTests { /// @@ -40,21 +39,13 @@ public class Spdx2JsonDeserializerTests /// Confirms that syntactically broken JSON (missing closing brace) causes a /// rather than a silent failure. /// - [TestMethod] + [Fact] public void Spdx2JsonDeserializer_Deserialize_MalformedJson_ThrowsJsonException() { // Arrange: const string malformedJson = "{ not valid json"; // Act / Assert: - try - { - Spdx2JsonDeserializer.Deserialize(malformedJson); - Assert.Fail("Expected JsonException was not thrown"); - } - catch (JsonException) - { - // Pass - } + Assert.ThrowsAny(() => Spdx2JsonDeserializer.Deserialize(malformedJson)); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs index 7eb6205..d236ebd 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeAnnotation.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeAnnotation { /// /// Tests serializing an annotation. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeAnnotation_ValidInput_CorrectResults() { // Arrange: Create a sample annotation @@ -58,7 +57,7 @@ public void Spdx2JsonSerializer_SerializeAnnotation_ValidInput_CorrectResults() /// /// Tests serializing multiple annotations. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeAnnotations_ValidInput_CorrectResults() { // Arrange: Create a sample list of annotations @@ -86,8 +85,8 @@ public void Spdx2JsonSerializer_SerializeAnnotations_ValidInput_CorrectResults() var json = Spdx2JsonSerializer.SerializeAnnotations(annotations); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); - Assert.AreEqual(2, json.Count); + Assert.NotNull(json); + Assert.Equal(2, json.Count); SpdxJsonHelpers.AssertEqual("SPDXRef-Annotation1", json[0]?["SPDXID"]); SpdxJsonHelpers.AssertEqual("John Doe", json[0]?["annotator"]); SpdxJsonHelpers.AssertEqual("2021-09-01T12:00:00Z", json[0]?["annotationDate"]); @@ -103,7 +102,7 @@ public void Spdx2JsonSerializer_SerializeAnnotations_ValidInput_CorrectResults() /// /// Tests that an annotation with no ID omits the SPDXID field from the serialized JSON. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeAnnotation_NoId_OmitsSpdxId() { // Arrange: Create an annotation with no ID (empty string) @@ -120,7 +119,7 @@ public void Spdx2JsonSerializer_SerializeAnnotation_NoId_OmitsSpdxId() var json = Spdx2JsonSerializer.SerializeAnnotation(annotation); // Assert: SPDXID is absent and other fields are present - Assert.IsNull(json["SPDXID"]); + Assert.Null(json["SPDXID"]); SpdxJsonHelpers.AssertEqual("John Doe", json["annotator"]); SpdxJsonHelpers.AssertEqual("2021-09-01T12:00:00Z", json["annotationDate"]); SpdxJsonHelpers.AssertEqual("REVIEW", json["annotationType"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeChecksum.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeChecksum.cs index e851335..f1fc68c 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeChecksum.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeChecksum.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeChecksum { /// /// Tests serializing a checksum. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeChecksum_ValidInput_CorrectResults() { // Arrange: Create a sample checksum @@ -45,7 +44,7 @@ public void Spdx2JsonSerializer_SerializeChecksum_ValidInput_CorrectResults() var json = Spdx2JsonSerializer.SerializeChecksum(checksum); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); + Assert.NotNull(json); SpdxJsonHelpers.AssertEqual("SHA1", json["algorithm"]); SpdxJsonHelpers.AssertEqual("2fd4e1c67a2d28f123849ee1bb76e7391b93eb12", json["checksumValue"]); } @@ -53,7 +52,7 @@ public void Spdx2JsonSerializer_SerializeChecksum_ValidInput_CorrectResults() /// /// Tests serializing multiple checksums. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeChecksums_ValidInput_CorrectResults() { // Arrange: Create sample checksums @@ -75,8 +74,8 @@ public void Spdx2JsonSerializer_SerializeChecksums_ValidInput_CorrectResults() var json = Spdx2JsonSerializer.SerializeChecksums(checksums); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); - Assert.AreEqual(2, json.Count); + Assert.NotNull(json); + Assert.Equal(2, json.Count); SpdxJsonHelpers.AssertEqual("SHA1", json[0]?["algorithm"]); SpdxJsonHelpers.AssertEqual("2fd4e1c67a2d28f123849ee1bb76e7391b93eb12", json[0]?["checksumValue"]); SpdxJsonHelpers.AssertEqual("MD5", json[1]?["algorithm"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeCreationInformation.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeCreationInformation.cs index b19e100..c58c705 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeCreationInformation.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeCreationInformation.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeCreationInformation { /// /// Tests serializing creation information. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeCreationInformation_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxCreationInformation object diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs index 6514c4e..625b77d 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeDocument.cs @@ -26,13 +26,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeDocument { /// /// Tests serializing a document to JSON. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeDocument_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxDocument object @@ -99,7 +98,7 @@ public void Spdx2JsonSerializer_SerializeDocument_ValidInput_CorrectResults() /// /// Tests serializing a document to text /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_Serialize_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxDocument object @@ -143,7 +142,7 @@ public void Spdx2JsonSerializer_Serialize_ValidInput_CorrectResults() var json = JsonNode.Parse(jsonText) as JsonObject; // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); + Assert.NotNull(json); SpdxJsonHelpers.AssertEqual("SPDXRef-DOCUMENT", json["SPDXID"]); SpdxJsonHelpers.AssertEqual("SPDX-2.3", json["spdxVersion"]); SpdxJsonHelpers.AssertEqual("SPDX-Tools-v2.0", json["name"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalDocumentReference.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalDocumentReference.cs index 75c29f8..683eefe 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalDocumentReference.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalDocumentReference.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeExternalDocumentReference { /// /// Tests serializing an external document reference. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeExternalDocumentReference_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxExternalDocumentReference object @@ -60,7 +59,7 @@ public void Spdx2JsonSerializer_SerializeExternalDocumentReference_ValidInput_Co /// /// Tests serializing multiple external document references. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeExternalDocumentReferences_ValidInput_CorrectResults() { // Arrange: Create a sample array of SpdxExternalDocumentReference objects @@ -82,8 +81,8 @@ public void Spdx2JsonSerializer_SerializeExternalDocumentReferences_ValidInput_C var json = Spdx2JsonSerializer.SerializeExternalDocumentReferences(references); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); - Assert.AreEqual(1, json.Count); + Assert.NotNull(json); + Assert.Single(json); SpdxJsonHelpers.AssertEqual("DocumentRef-spdx-tool-1.2", json[0]?["externalDocumentId"]); SpdxJsonHelpers.AssertEqual("SHA1", json[0]?["checksum"]?["algorithm"]); SpdxJsonHelpers.AssertEqual("d6a770ba38583ed4bb4525bd96e50461655d2759", json[0]?["checksum"]?["checksumValue"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalReference.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalReference.cs index 5bcc58c..98f062a 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalReference.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExternalReference.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeExternalReference { /// /// Tests serializing an external reference. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeExternalReference_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxExternalReference object @@ -47,7 +46,7 @@ public void Spdx2JsonSerializer_SerializeExternalReference_ValidInput_CorrectRes var json = Spdx2JsonSerializer.SerializeExternalReference(reference); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); + Assert.NotNull(json); SpdxJsonHelpers.AssertEqual("SECURITY", json["referenceCategory"]); SpdxJsonHelpers.AssertEqual("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", json["referenceLocator"]); @@ -58,7 +57,7 @@ public void Spdx2JsonSerializer_SerializeExternalReference_ValidInput_CorrectRes /// /// Tests serializing multiple external references. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeExternalReferences_ValidInput_CorrectResults() { // Arrange: Create sample SpdxExternalReference objects @@ -85,8 +84,8 @@ public void Spdx2JsonSerializer_SerializeExternalReferences_ValidInput_CorrectRe var json = Spdx2JsonSerializer.SerializeExternalReferences(references); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); - Assert.AreEqual(2, json.Count); + Assert.NotNull(json); + Assert.Equal(2, json.Count); SpdxJsonHelpers.AssertEqual("SECURITY", json[0]?["referenceCategory"]); SpdxJsonHelpers.AssertEqual("cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*", json[0]?["referenceLocator"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExtractedLicensingInfo.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExtractedLicensingInfo.cs index b7cece5..2dcca1d 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExtractedLicensingInfo.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeExtractedLicensingInfo.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeExtractedLicensingInfo { /// /// Tests serializing an extracted licensing info. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeExtractedLicensingInfo_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxExtractedLicensingInfo object @@ -58,7 +57,7 @@ public void Spdx2JsonSerializer_SerializeExtractedLicensingInfo_ValidInput_Corre /// /// Tests serializing multiple extracted licensing infos. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeExtractedLicensingInfos_ValidInput_CorrectResults() { // Arrange: Create a sample array of SpdxExtractedLicensingInfo objects @@ -78,8 +77,8 @@ public void Spdx2JsonSerializer_SerializeExtractedLicensingInfos_ValidInput_Corr var json = Spdx2JsonSerializer.SerializeExtractedLicensingInfos(info); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); - Assert.AreEqual(1, json.Count); + Assert.NotNull(json); + Assert.Single(json); SpdxJsonHelpers.AssertEqual("MIT", json[0]?["licenseId"]); SpdxJsonHelpers.AssertEqual("This is the MIT license", json[0]?["extractedText"]); SpdxJsonHelpers.AssertEqual("MIT License", json[0]?["name"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs index 5663fe4..7936a88 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeFile.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeFile { /// /// Tests serializing a file. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeFile_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxFile object @@ -111,7 +110,7 @@ public void Spdx2JsonSerializer_SerializeFile_ValidInput_CorrectResults() /// /// Tests serializing multiple files. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeFiles_ValidInput_CorrectResults() { // Arrange: Create a sample array containing a single SpdxFile with all fields populated @@ -165,8 +164,8 @@ public void Spdx2JsonSerializer_SerializeFiles_ValidInput_CorrectResults() var json = Spdx2JsonSerializer.SerializeFiles(file); // Assert: Verify the JSON output has the expected structure and values - Assert.IsNotNull(json); - Assert.AreEqual(1, json.Count); + Assert.NotNull(json); + Assert.Single(json); SpdxJsonHelpers.AssertEqual("SPDXRef-DoapSource", json[0]?["SPDXID"]); SpdxJsonHelpers.AssertEqual("./src/org/spdx/parser/DOAPProject.java", json[0]?["fileName"]); SpdxJsonHelpers.AssertEqual("SOURCE", json[0]?["fileTypes"]?[0]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackage.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackage.cs index 174c977..8d7db16 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackage.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackage.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializePackage { /// /// Tests serializing a package. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializePackage_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxPackage object @@ -97,7 +96,7 @@ public void Spdx2JsonSerializer_SerializePackage_ValidInput_CorrectResults() var json = Spdx2JsonSerializer.SerializePackage(package); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); + Assert.NotNull(json); SpdxJsonHelpers.AssertEqual("SPDXRef-Package", json["SPDXID"]); SpdxJsonHelpers.AssertEqual("glibc", json["name"]); SpdxJsonHelpers.AssertEqual("2.11.1", json["versionInfo"]); @@ -136,7 +135,7 @@ public void Spdx2JsonSerializer_SerializePackage_ValidInput_CorrectResults() /// /// Tests serializing multiple packages. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializePackages_ValidInput_CorrectResults() { // Arrange: Create a sample array of SpdxPackage objects @@ -205,8 +204,8 @@ public void Spdx2JsonSerializer_SerializePackages_ValidInput_CorrectResults() var json = Spdx2JsonSerializer.SerializePackages(packages); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); - Assert.AreEqual(1, json.Count); + Assert.NotNull(json); + Assert.Single(json); SpdxJsonHelpers.AssertEqual("SPDXRef-Package", json[0]?["SPDXID"]); SpdxJsonHelpers.AssertEqual("glibc", json[0]?["name"]); SpdxJsonHelpers.AssertEqual("2.11.1", json[0]?["versionInfo"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs index f25dbc5..7d62d32 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializePackageVerificationCode.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializePackageVerificationCode { /// /// Tests serializing a package verification code. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeVerificationCode_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxPackageVerificationCode object @@ -49,7 +48,7 @@ public void Spdx2JsonSerializer_SerializeVerificationCode_ValidInput_CorrectResu var json = Spdx2JsonSerializer.SerializeVerificationCode(code); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); + Assert.NotNull(json); SpdxJsonHelpers.AssertEqual("d3b07384d113edec49eaa6238ad5ff00", json["packageVerificationCodeValue"]); SpdxJsonHelpers.AssertEqual("file1.txt", json["packageVerificationCodeExcludedFiles"]?[0]); SpdxJsonHelpers.AssertEqual("file2.txt", json["packageVerificationCodeExcludedFiles"]?[1]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeRelationship.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeRelationship.cs index 3fe7eb4..21a5e96 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeRelationship.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeRelationship.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeRelationship { /// /// Tests serializing a relationship. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxRelationship object @@ -47,7 +46,7 @@ public void Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults( var json = Spdx2JsonSerializer.SerializeRelationship(relationship); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); + Assert.NotNull(json); SpdxJsonHelpers.AssertEqual("SPDXRef-DOCUMENT", json["spdxElementId"]); SpdxJsonHelpers.AssertEqual("SPDXRef-Package", json["relatedSpdxElement"]); SpdxJsonHelpers.AssertEqual("DESCRIBES", json["relationshipType"]); @@ -57,7 +56,7 @@ public void Spdx2JsonSerializer_SerializeRelationship_ValidInput_CorrectResults( /// /// Tests serializing multiple relationships. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeRelationships_ValidInput_CorrectResults() { // Arrange: Create an array of sample SpdxRelationship objects @@ -83,8 +82,8 @@ public void Spdx2JsonSerializer_SerializeRelationships_ValidInput_CorrectResults var json = Spdx2JsonSerializer.SerializeRelationships(relationships); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); - Assert.AreEqual(2, json.Count); + Assert.NotNull(json); + Assert.Equal(2, json.Count); SpdxJsonHelpers.AssertEqual("SPDXRef-DOCUMENT", json[0]?["spdxElementId"]); SpdxJsonHelpers.AssertEqual("SPDXRef-Package", json[0]?["relatedSpdxElement"]); SpdxJsonHelpers.AssertEqual("DESCRIBES", json[0]?["relationshipType"]); diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs index ef7b1b4..1b77748 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/Spdx2JsonSerializeSnippet.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Tests for serializing to JSON. /// -[TestClass] public class Spdx2JsonSerializeSnippet { /// /// Tests serializing a snippet. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeSnippet_ValidInput_CorrectResults() { // Arrange: Create a sample SpdxSnippet object @@ -56,7 +55,7 @@ public void Spdx2JsonSerializer_SerializeSnippet_ValidInput_CorrectResults() var json = Spdx2JsonSerializer.SerializeSnippet(snippet); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); + Assert.NotNull(json); SpdxJsonHelpers.AssertEqual("SPDXRef-Snippet", json["SPDXID"]); SpdxJsonHelpers.AssertEqual("SnippetFromFile", json["snippetFromFile"]); SpdxJsonHelpers.AssertEqual("Name", json["name"]); @@ -79,7 +78,7 @@ public void Spdx2JsonSerializer_SerializeSnippet_ValidInput_CorrectResults() /// /// Tests serializing multiple snippets. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeSnippets_ValidInput_CorrectResults() { // Arrange: Create a sample array of SpdxSnippet objects @@ -107,8 +106,8 @@ public void Spdx2JsonSerializer_SerializeSnippets_ValidInput_CorrectResults() var json = Spdx2JsonSerializer.SerializeSnippets(snippets); // Assert: Verify the JSON is not null and has the expected structure - Assert.IsNotNull(json); - Assert.AreEqual(1, json.Count); + Assert.NotNull(json); + Assert.Single(json); SpdxJsonHelpers.AssertEqual("SPDXRef-Snippet", json[0]?["SPDXID"]); SpdxJsonHelpers.AssertEqual("SnippetFromFile", json[0]?["snippetFromFile"]); SpdxJsonHelpers.AssertEqual("Name", json[0]?["name"]); @@ -131,7 +130,7 @@ public void Spdx2JsonSerializer_SerializeSnippets_ValidInput_CorrectResults() /// /// Tests serializing a snippet that includes an annotation, covering the annotation branch. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeSnippet_WithAnnotation_IncludesAnnotation() { // Arrange: Create a snippet with a single annotation @@ -160,8 +159,8 @@ public void Spdx2JsonSerializer_SerializeSnippet_WithAnnotation_IncludesAnnotati var json = Spdx2JsonSerializer.SerializeSnippet(snippet); // Assert: Verify the annotation is present in the serialized output - Assert.IsNotNull(json); - Assert.IsNotNull(json["annotations"], "annotations array should be present"); + Assert.NotNull(json); + Assert.NotNull(json["annotations"]); SpdxJsonHelpers.AssertEqual("Tool: TestTool", json["annotations"]?[0]?["annotator"]); SpdxJsonHelpers.AssertEqual("2024-01-01T00:00:00Z", json["annotations"]?[0]?["annotationDate"]); SpdxJsonHelpers.AssertEqual("REVIEW", json["annotations"]?[0]?["annotationType"]); @@ -171,7 +170,7 @@ public void Spdx2JsonSerializer_SerializeSnippet_WithAnnotation_IncludesAnnotati /// /// Tests that a snippet with both line values set to zero emits only the byte-range entry. /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeSnippet_NoLineRange_EmitsByteRangeOnly() { // Arrange: Create a snippet with zero line values @@ -192,19 +191,19 @@ public void Spdx2JsonSerializer_SerializeSnippet_NoLineRange_EmitsByteRangeOnly( var json = Spdx2JsonSerializer.SerializeSnippet(snippet); // Assert: Only one ranges entry (byte-range) is present — no line-range - Assert.IsNotNull(json); - Assert.IsNotNull(json["ranges"]); - Assert.AreEqual(1, json["ranges"]!.AsArray().Count); + Assert.NotNull(json); + Assert.NotNull(json["ranges"]); + Assert.Single(json["ranges"]!.AsArray()); SpdxJsonHelpers.AssertEqual("10", json["ranges"]?[0]?["startPointer"]?["offset"]); SpdxJsonHelpers.AssertEqual("20", json["ranges"]?[0]?["endPointer"]?["offset"]); - Assert.IsNull(json["ranges"]?[0]?["startPointer"]?["lineNumber"]); + Assert.Null(json["ranges"]?[0]?["startPointer"]?["lineNumber"]); } /// /// Tests that a snippet with only one line value non-zero emits only the byte-range entry /// (verifies AND logic — both values must be non-zero for line-range to be emitted). /// - [TestMethod] + [Fact] public void Spdx2JsonSerializer_SerializeSnippet_PartialLineRange_EmitsByteRangeOnly() { // Arrange: Create a snippet where only one line value is non-zero @@ -225,11 +224,11 @@ public void Spdx2JsonSerializer_SerializeSnippet_PartialLineRange_EmitsByteRange var json = Spdx2JsonSerializer.SerializeSnippet(snippet); // Assert: Only one ranges entry (byte-range) is present — partial line-range is not emitted - Assert.IsNotNull(json); - Assert.IsNotNull(json["ranges"]); - Assert.AreEqual(1, json["ranges"]!.AsArray().Count); + Assert.NotNull(json); + Assert.NotNull(json["ranges"]); + Assert.Single(json["ranges"]!.AsArray()); SpdxJsonHelpers.AssertEqual("10", json["ranges"]?[0]?["startPointer"]?["offset"]); SpdxJsonHelpers.AssertEqual("20", json["ranges"]?[0]?["endPointer"]?["offset"]); - Assert.IsNull(json["ranges"]?[0]?["startPointer"]?["lineNumber"]); + Assert.Null(json["ranges"]?[0]?["startPointer"]?["lineNumber"]); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/SpdxJsonHelpers.cs b/test/DemaConsulting.SpdxModel.Tests/IO/SpdxJsonHelpers.cs index efd800a..ed8074b 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/SpdxJsonHelpers.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/SpdxJsonHelpers.cs @@ -34,7 +34,7 @@ internal static class SpdxJsonHelpers /// JSON node public static void AssertEqual(string expected, JsonNode? node) { - Assert.IsNotNull(node); - Assert.AreEqual(expected, node.ToString()); + Assert.NotNull(node); + Assert.Equal(expected, node.ToString()); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs b/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs index 05e62b6..57190e6 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests.IO; /// /// Integration tests for the SpdxModel IO subsystem. /// -[TestClass] public class SpdxModelIOTests { /// /// Tests that an SPDX 2.2 document survives a JSON serialization round trip. /// - [TestMethod] + [Fact] public void SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidDocument() { // Arrange: Load the SPDX 2.2 JSON example from embedded resources @@ -44,18 +43,18 @@ public void SpdxModelIO_ReadWriteSpdxJson_Spdx22Document_RoundTripProducesValidD var roundTripped = Spdx2JsonDeserializer.Deserialize(serialized); // Assert: Verify the round-tripped document is valid and matches the original - Assert.IsNotNull(roundTripped); - Assert.AreEqual(original.Name, roundTripped.Name); - Assert.AreEqual(original.Version, roundTripped.Version); + Assert.NotNull(roundTripped); + Assert.Equal(original.Name, roundTripped.Name); + Assert.Equal(original.Version, roundTripped.Version); var issues = new List(); roundTripped.Validate(issues); - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// /// Tests that an SPDX 2.3 document survives a JSON serialization round trip. /// - [TestMethod] + [Fact] public void SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidDocument() { // Arrange: Load the SPDX 2.3 JSON example from embedded resources @@ -68,34 +67,27 @@ public void SpdxModelIO_ReadWriteSpdxJson_Spdx23Document_RoundTripProducesValidD var roundTripped = Spdx2JsonDeserializer.Deserialize(serialized); // Assert: Verify the round-tripped document is valid and matches the original - Assert.IsNotNull(roundTripped); - Assert.AreEqual(original.Name, roundTripped.Name); - Assert.AreEqual(original.Version, roundTripped.Version); + Assert.NotNull(roundTripped); + Assert.Equal(original.Name, roundTripped.Name); + Assert.Equal(original.Version, roundTripped.Version); var issues = new List(); roundTripped.Validate(issues); - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// /// Tests that malformed JSON throws a JsonException during deserialization. /// - [TestMethod] + [Fact] public void SpdxModelIO_ReadSpdxJson_InvalidJson_ThrowsJsonException() { // Arrange: Prepare malformed JSON text const string malformedJson = "{ not valid json at all }"; - // Act / Assert: Deserialize should throw a JsonException or derived type - var threw = false; - try - { - Spdx2JsonDeserializer.Deserialize(malformedJson); - } - catch (System.Text.Json.JsonException) - { - threw = true; - } + // Act: Capture any exception thrown by the deserializer + var exception = Record.Exception(() => Spdx2JsonDeserializer.Deserialize(malformedJson)); - Assert.IsTrue(threw, "Expected JsonException was not thrown."); + // Assert: The exception must be a JsonException (or derived type such as JsonReaderException) + Assert.IsAssignableFrom(exception); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs index 6811b1b..2ef119f 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxAnnotationTests.cs @@ -24,12 +24,10 @@ namespace DemaConsulting.SpdxModel.Tests; /// Tests for the class. /// /// -/// Uses MSTest as the approved test framework for this repository (formal exception to -/// xUnit documented in csharp-testing.md). Each test method is fully isolated: +/// Uses xUnit v3 as the test framework. Each test method is fully isolated: /// no shared state is maintained between tests and all test inputs are constructed /// inline within the method body. /// -[TestClass] public class SpdxAnnotationTests { /// @@ -40,7 +38,7 @@ public class SpdxAnnotationTests /// annotations with identical fields but different IDs are equal), and that hash codes /// match for equal annotations. /// - [TestMethod] + [Fact] public void SpdxAnnotation_SameComparer_ComparesCorrectly() { // Arrange: Create three annotations with different properties @@ -67,20 +65,20 @@ public void SpdxAnnotation_SameComparer_ComparesCorrectly() }; // Act / Assert: Verify annotations compare to themselves - Assert.IsTrue(SpdxAnnotation.Same.Equals(a1, a1)); - Assert.IsTrue(SpdxAnnotation.Same.Equals(a2, a2)); - Assert.IsTrue(SpdxAnnotation.Same.Equals(a3, a3)); + Assert.True(SpdxAnnotation.Same.Equals(a1, a1)); + Assert.True(SpdxAnnotation.Same.Equals(a2, a2)); + Assert.True(SpdxAnnotation.Same.Equals(a3, a3)); // Act / Assert: Verify annotations compare correctly - Assert.IsTrue(SpdxAnnotation.Same.Equals(a1, a2)); - Assert.IsTrue(SpdxAnnotation.Same.Equals(a2, a1)); - Assert.IsFalse(SpdxAnnotation.Same.Equals(a1, a3)); - Assert.IsFalse(SpdxAnnotation.Same.Equals(a3, a1)); - Assert.IsFalse(SpdxAnnotation.Same.Equals(a2, a3)); - Assert.IsFalse(SpdxAnnotation.Same.Equals(a3, a2)); + Assert.True(SpdxAnnotation.Same.Equals(a1, a2)); + Assert.True(SpdxAnnotation.Same.Equals(a2, a1)); + Assert.False(SpdxAnnotation.Same.Equals(a1, a3)); + Assert.False(SpdxAnnotation.Same.Equals(a3, a1)); + Assert.False(SpdxAnnotation.Same.Equals(a2, a3)); + Assert.False(SpdxAnnotation.Same.Equals(a3, a2)); // Act / Assert: Verify same annotations have identical hashes - Assert.AreEqual(SpdxAnnotation.Same.GetHashCode(a1), SpdxAnnotation.Same.GetHashCode(a2)); + Assert.Equal(SpdxAnnotation.Same.GetHashCode(a1), SpdxAnnotation.Same.GetHashCode(a2)); } /// @@ -91,7 +89,7 @@ public void SpdxAnnotation_SameComparer_ComparesCorrectly() /// object (not reference-equal), confirming no shared mutable references exist between /// original and copy. /// - [TestMethod] + [Fact] public void SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance() { // Arrange: Create an original SpdxAnnotation object @@ -107,14 +105,14 @@ public void SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance() var a2 = a1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(a1, a2, SpdxAnnotation.Same); - Assert.AreEqual(a1.Annotator, a2.Annotator); - Assert.AreEqual(a1.Date, a2.Date); - Assert.AreEqual(a1.Type, a2.Type); - Assert.AreEqual(a1.Comment, a2.Comment); + Assert.Equal(a1, a2, SpdxAnnotation.Same); + Assert.Equal(a1.Annotator, a2.Annotator); + Assert.Equal(a1.Date, a2.Date); + Assert.Equal(a1.Type, a2.Type); + Assert.Equal(a1.Comment, a2.Comment); // Assert: Verify deep-copy has distinct instance - Assert.IsFalse(ReferenceEquals(a1, a2)); + Assert.False(ReferenceEquals(a1, a2)); } /// @@ -127,7 +125,7 @@ public void SpdxAnnotation_DeepCopy_CreatesEqualButDistinctInstance() /// annotation is appended when no match exists. Both sub-scenarios use the /// comparer for matching. /// - [TestMethod] + [Fact] public void SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly() { // Arrange: Create an array of annotations with one annotation @@ -164,15 +162,15 @@ public void SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly() ]); // Assert: Verify the annotations array has correct information - Assert.HasCount(2, annotations); - Assert.AreEqual("SPDXRef-Annotation1", annotations[0].Id); - Assert.AreEqual("Person: Malcolm Nixon", annotations[0].Annotator); - Assert.AreEqual("2024-05-28T01:30:00Z", annotations[0].Date); - Assert.AreEqual(SpdxAnnotationType.Review, annotations[0].Type); - Assert.AreEqual("Looks good", annotations[0].Comment); - Assert.AreEqual("Person: John Doe", annotations[1].Annotator); - Assert.AreEqual("2023-11-20T12:34:23Z", annotations[1].Date); - Assert.AreEqual(SpdxAnnotationType.Other, annotations[1].Type); + Assert.Equal(2, annotations.Length); + Assert.Equal("SPDXRef-Annotation1", annotations[0].Id); + Assert.Equal("Person: Malcolm Nixon", annotations[0].Annotator); + Assert.Equal("2024-05-28T01:30:00Z", annotations[0].Date); + Assert.Equal(SpdxAnnotationType.Review, annotations[0].Type); + Assert.Equal("Looks good", annotations[0].Comment); + Assert.Equal("Person: John Doe", annotations[1].Annotator); + Assert.Equal("2023-11-20T12:34:23Z", annotations[1].Date); + Assert.Equal(SpdxAnnotationType.Other, annotations[1].Type); } /// @@ -183,7 +181,7 @@ public void SpdxAnnotation_Enhance_AddsOrUpdatesInformationCorrectly() /// field; all other fields are valid so that the issue list contains exactly one entry /// for the annotator. /// - [TestMethod] + [Fact] public void SpdxAnnotation_Validate_InvalidAnnotator() { // Arrange: Create a bad annotation @@ -200,7 +198,7 @@ public void SpdxAnnotation_Validate_InvalidAnnotator() annotation.Validate("Test", issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Test Invalid Annotator Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Test Invalid Annotator Field - Empty")); } /// @@ -210,7 +208,7 @@ public void SpdxAnnotation_Validate_InvalidAnnotator() /// Boundary: a non-ISO-8601 date string is the only invalid field; all other fields are /// valid so that the issue list contains exactly one entry for the date. /// - [TestMethod] + [Fact] public void SpdxAnnotation_Validate_InvalidDate() { // Arrange: Create a bad annotation @@ -227,7 +225,7 @@ public void SpdxAnnotation_Validate_InvalidDate() annotation.Validate("Test", issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Test Invalid Annotation Date Field 'BadDate'"), issues); + Assert.Contains(issues, issue => issue.Contains("Test Invalid Annotation Date Field 'BadDate'")); } /// @@ -237,7 +235,7 @@ public void SpdxAnnotation_Validate_InvalidDate() /// Boundary: is the only invalid field; all /// other fields are valid so that the issue list contains exactly one entry for the type. /// - [TestMethod] + [Fact] public void SpdxAnnotation_Validate_InvalidType() { // Arrange: Create a bad annotation @@ -254,7 +252,7 @@ public void SpdxAnnotation_Validate_InvalidType() annotation.Validate("Test", issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Test Invalid Annotation Type Field - Missing"), issues); + Assert.Contains(issues, issue => issue.Contains("Test Invalid Annotation Type Field - Missing")); } /// @@ -266,7 +264,7 @@ public void SpdxAnnotation_Validate_InvalidType() /// (valid) so that the issue list contains /// exactly one entry for the comment. /// - [TestMethod] + [Fact] public void SpdxAnnotation_Validate_InvalidComment() { // Arrange: Create a bad annotation @@ -283,7 +281,7 @@ public void SpdxAnnotation_Validate_InvalidComment() annotation.Validate("Test", issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Test Invalid Annotation Comment - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Test Invalid Annotation Comment - Empty")); } /// @@ -294,19 +292,19 @@ public void SpdxAnnotation_Validate_InvalidComment() /// ; similarly for "OTHER". An empty string maps /// to . /// - [TestMethod] + [Fact] public void SpdxAnnotationTypeExtensions_FromText_Valid() { // Arrange: no setup needed - testing pure string-to-enum conversion // Act / Assert: each recognized text converts to the correct enum value - Assert.AreEqual(SpdxAnnotationType.Missing, SpdxAnnotationTypeExtensions.FromText("")); - Assert.AreEqual(SpdxAnnotationType.Review, SpdxAnnotationTypeExtensions.FromText("REVIEW")); - Assert.AreEqual(SpdxAnnotationType.Review, SpdxAnnotationTypeExtensions.FromText("review")); - Assert.AreEqual(SpdxAnnotationType.Review, SpdxAnnotationTypeExtensions.FromText("Review")); - Assert.AreEqual(SpdxAnnotationType.Other, SpdxAnnotationTypeExtensions.FromText("OTHER")); - Assert.AreEqual(SpdxAnnotationType.Other, SpdxAnnotationTypeExtensions.FromText("other")); - Assert.AreEqual(SpdxAnnotationType.Other, SpdxAnnotationTypeExtensions.FromText("Other")); + Assert.Equal(SpdxAnnotationType.Missing, SpdxAnnotationTypeExtensions.FromText("")); + Assert.Equal(SpdxAnnotationType.Review, SpdxAnnotationTypeExtensions.FromText("REVIEW")); + Assert.Equal(SpdxAnnotationType.Review, SpdxAnnotationTypeExtensions.FromText("review")); + Assert.Equal(SpdxAnnotationType.Review, SpdxAnnotationTypeExtensions.FromText("Review")); + Assert.Equal(SpdxAnnotationType.Other, SpdxAnnotationTypeExtensions.FromText("OTHER")); + Assert.Equal(SpdxAnnotationType.Other, SpdxAnnotationTypeExtensions.FromText("other")); + Assert.Equal(SpdxAnnotationType.Other, SpdxAnnotationTypeExtensions.FromText("Other")); } /// @@ -316,15 +314,15 @@ public void SpdxAnnotationTypeExtensions_FromText_Valid() /// Boundary: an unrecognized string causes with /// the expected message, confirming the error path is not silently swallowed. /// - [TestMethod] + [Fact] public void SpdxAnnotationTypeExtensions_FromText_Invalid() { // Arrange: no setup needed — testing pure string-to-enum conversion error path // Act / Assert: var exception = - Assert.ThrowsExactly(() => SpdxAnnotationTypeExtensions.FromText("invalid")); - Assert.AreEqual("Unsupported SPDX Annotation Type 'invalid'", exception.Message); + Assert.Throws(() => SpdxAnnotationTypeExtensions.FromText("invalid")); + Assert.Equal("Unsupported SPDX Annotation Type 'invalid'", exception.Message); } /// @@ -334,14 +332,14 @@ public void SpdxAnnotationTypeExtensions_FromText_Invalid() /// Verifies that produces "REVIEW" and /// produces "OTHER". /// - [TestMethod] + [Fact] public void SpdxAnnotationTypeExtensions_ToText_Valid() { // Arrange: no setup needed - testing pure enum-to-string conversion // Act / Assert: each recognized enum value converts to the expected text - Assert.AreEqual("REVIEW", SpdxAnnotationType.Review.ToText()); - Assert.AreEqual("OTHER", SpdxAnnotationType.Other.ToText()); + Assert.Equal("REVIEW", SpdxAnnotationType.Review.ToText()); + Assert.Equal("OTHER", SpdxAnnotationType.Other.ToText()); } /// @@ -353,14 +351,14 @@ public void SpdxAnnotationTypeExtensions_ToText_Valid() /// member) causes /// with the expected message. /// - [TestMethod] + [Fact] public void SpdxAnnotationTypeExtensions_ToText_Invalid() { // Arrange: no setup needed - testing pure enum-to-string conversion error path // Act / Assert: an unknown numeric enum value throws - var exception = Assert.ThrowsExactly(() => ((SpdxAnnotationType)1000).ToText()); - Assert.AreEqual("Unsupported SPDX Annotation Type '1000'", exception.Message); + var exception = Assert.Throws(() => ((SpdxAnnotationType)1000).ToText()); + Assert.Equal("Unsupported SPDX Annotation Type '1000'", exception.Message); } /// @@ -373,14 +371,14 @@ public void SpdxAnnotationTypeExtensions_ToText_Invalid() /// with the expected "Attempt to serialize /// missing SPDX Annotation Type" message. /// - [TestMethod] + [Fact] public void SpdxAnnotationTypeExtensions_ToText_Missing() { // Arrange: no setup needed - testing the Missing sentinel value error path // Act / Assert: Missing throws with the expected message - var exception = Assert.ThrowsExactly( + var exception = Assert.Throws( () => SpdxAnnotationType.Missing.ToText()); - Assert.AreEqual("Attempt to serialize missing SPDX Annotation Type", exception.Message); + Assert.Equal("Attempt to serialize missing SPDX Annotation Type", exception.Message); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs index 3655599..c6b4d51 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxChecksumTests.cs @@ -25,11 +25,9 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// /// Tests the class and the -/// extension methods. Uses MSTest as the -/// test framework (approved exception: xUnit adoption is deferred for this project). -/// Each test method is fully self-contained with no shared fixture state. +/// extension methods. Uses xUnit v3 as the +/// test framework. Each test method is fully self-contained with no shared fixture state. /// -[TestClass] public class SpdxChecksumTests { /// @@ -40,7 +38,7 @@ public class SpdxChecksumTests /// with a different algorithm and value (c3). Verifies reflexive, symmetric, and /// cross-inequality comparisons, and that equal checksums produce identical hash codes. /// - [TestMethod] + [Fact] public void SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquality() { // Arrange: Create three checksums with different algorithm/value combinations @@ -63,20 +61,20 @@ public void SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquali }; // Act / Assert: Verify checksums compare to themselves - Assert.IsTrue(SpdxChecksum.Same.Equals(c1, c1)); - Assert.IsTrue(SpdxChecksum.Same.Equals(c2, c2)); - Assert.IsTrue(SpdxChecksum.Same.Equals(c3, c3)); + Assert.True(SpdxChecksum.Same.Equals(c1, c1)); + Assert.True(SpdxChecksum.Same.Equals(c2, c2)); + Assert.True(SpdxChecksum.Same.Equals(c3, c3)); // Assert: Verify checksums compare correctly - Assert.IsTrue(SpdxChecksum.Same.Equals(c1, c2)); - Assert.IsTrue(SpdxChecksum.Same.Equals(c2, c1)); - Assert.IsFalse(SpdxChecksum.Same.Equals(c1, c3)); - Assert.IsFalse(SpdxChecksum.Same.Equals(c3, c1)); - Assert.IsFalse(SpdxChecksum.Same.Equals(c2, c3)); - Assert.IsFalse(SpdxChecksum.Same.Equals(c3, c2)); + Assert.True(SpdxChecksum.Same.Equals(c1, c2)); + Assert.True(SpdxChecksum.Same.Equals(c2, c1)); + Assert.False(SpdxChecksum.Same.Equals(c1, c3)); + Assert.False(SpdxChecksum.Same.Equals(c3, c1)); + Assert.False(SpdxChecksum.Same.Equals(c2, c3)); + Assert.False(SpdxChecksum.Same.Equals(c3, c2)); // Assert: same checksums have identical hashes - Assert.AreEqual(SpdxChecksum.Same.GetHashCode(c1), SpdxChecksum.Same.GetHashCode(c2)); + Assert.Equal(SpdxChecksum.Same.GetHashCode(c1), SpdxChecksum.Same.GetHashCode(c2)); } /// @@ -87,7 +85,7 @@ public void SpdxChecksum_SameComparer_SameOrDifferentValues_ReturnsCorrectEquali /// copy has equal field values (Algorithm and Value) but is a distinct object reference /// from the original, confirming no shallow aliasing. /// - [TestMethod] + [Fact] public void SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInstance() { // Arrange: Create a checksum instance @@ -101,12 +99,12 @@ public void SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInsta var c2 = c1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(c1, c2, SpdxChecksum.Same); - Assert.AreEqual(c1.Algorithm, c2.Algorithm); - Assert.AreEqual(c1.Value, c2.Value); + Assert.Equal(c1, c2, SpdxChecksum.Same); + Assert.Equal(c1.Algorithm, c2.Algorithm); + Assert.Equal(c1.Value, c2.Value); // Assert: Verify deep-copy has distinct instance - Assert.IsFalse(ReferenceEquals(c1, c2)); + Assert.False(ReferenceEquals(c1, c2)); } /// @@ -118,7 +116,7 @@ public void SpdxChecksum_DeepCopy_PopulatedChecksum_CreatesEqualButDistinctInsta /// entry and a new MD5 entry. Verifies that the existing entry is preserved and the new /// entry is appended, resulting in exactly two checksums. /// - [TestMethod] + [Fact] public void SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformation() { // Arrange: Create an original checksum @@ -148,11 +146,11 @@ public void SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformati ]); // Assert: Verify checksums contain the expected values - Assert.HasCount(2, checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, checksums[0].Algorithm); - Assert.AreEqual("c2b4e1c67a2d28fced849ee1bb76e7391b93f125", checksums[0].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Md5, checksums[1].Algorithm); - Assert.AreEqual("624c1abb3664f4b35547e7c73864ad24", checksums[1].Value); + Assert.Equal(2, checksums.Length); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, checksums[0].Algorithm); + Assert.Equal("c2b4e1c67a2d28fced849ee1bb76e7391b93f125", checksums[0].Value); + Assert.Equal(SpdxChecksumAlgorithm.Md5, checksums[1].Algorithm); + Assert.Equal("624c1abb3664f4b35547e7c73864ad24", checksums[1].Value); } /// @@ -163,7 +161,7 @@ public void SpdxChecksum_Enhance_ExistingAndNewAlgorithms_AddsOrUpdatesInformati /// that the validator catches the absent algorithm and includes the expected description /// string in the reported issue. /// - [TestMethod] + [Fact] public void SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue() { // Arrange: Create a bad instance @@ -178,7 +176,7 @@ public void SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue() checksum.Validate("Test", issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Test Invalid Checksum Algorithm Field - Missing"), issues); + Assert.Contains(issues, issue => issue.Contains("Test Invalid Checksum Algorithm Field - Missing")); } /// @@ -189,7 +187,7 @@ public void SpdxChecksum_Validate_MissingAlgorithm_ReportsAlgorithmIssue() /// that the validator catches the empty value and includes the expected description string /// in the reported issue. /// - [TestMethod] + [Fact] public void SpdxChecksum_Validate_EmptyValue_ReportsValueIssue() { // Arrange: Create a bad instance @@ -204,7 +202,7 @@ public void SpdxChecksum_Validate_EmptyValue_ReportsValueIssue() checksum.Validate("Test", issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Test Invalid Checksum Value Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Test Invalid Checksum Value Field - Empty")); } /// @@ -215,7 +213,7 @@ public void SpdxChecksum_Validate_EmptyValue_ReportsValueIssue() /// out-of-range enum value. Verifies that the validator treats it as an unknown algorithm /// and reports the expected diagnostic message. /// - [TestMethod] + [Fact] public void SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue() { // Arrange: Create a checksum with an out-of-range numeric algorithm value @@ -230,7 +228,7 @@ public void SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue( checksum.Validate("Test", issues); // Assert: Verify that the validation reports the unknown algorithm - Assert.Contains(issue => issue.Contains("Test Invalid Checksum Algorithm Field - Unknown"), issues); + Assert.Contains(issues, issue => issue.Contains("Test Invalid Checksum Algorithm Field - Unknown")); } /// @@ -241,32 +239,32 @@ public void SpdxChecksum_Validate_UnknownNumericAlgorithm_ReportsAlgorithmIssue( /// case-variant inputs for SHA1, to confirm that /// maps each string to the correct enum value. /// - [TestMethod] + [Fact] public void SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_ReturnsCorrectEnumValues() { // Arrange: Known algorithm strings are implicit in the Act/Assert pairs below // Act / Assert: Verify each known algorithm string maps to the correct enum value - Assert.AreEqual(SpdxChecksumAlgorithm.Missing, SpdxChecksumAlgorithmExtensions.FromText("")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("SHA1")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("sha1")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("Sha1")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha224, SpdxChecksumAlgorithmExtensions.FromText("SHA224")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha256, SpdxChecksumAlgorithmExtensions.FromText("SHA256")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha384, SpdxChecksumAlgorithmExtensions.FromText("SHA384")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha512, SpdxChecksumAlgorithmExtensions.FromText("SHA512")); - Assert.AreEqual(SpdxChecksumAlgorithm.Md2, SpdxChecksumAlgorithmExtensions.FromText("MD2")); - Assert.AreEqual(SpdxChecksumAlgorithm.Md4, SpdxChecksumAlgorithmExtensions.FromText("MD4")); - Assert.AreEqual(SpdxChecksumAlgorithm.Md5, SpdxChecksumAlgorithmExtensions.FromText("MD5")); - Assert.AreEqual(SpdxChecksumAlgorithm.Md6, SpdxChecksumAlgorithmExtensions.FromText("MD6")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha3256, SpdxChecksumAlgorithmExtensions.FromText("SHA3-256")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha3384, SpdxChecksumAlgorithmExtensions.FromText("SHA3-384")); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha3512, SpdxChecksumAlgorithmExtensions.FromText("SHA3-512")); - Assert.AreEqual(SpdxChecksumAlgorithm.Blake2B256, SpdxChecksumAlgorithmExtensions.FromText("BLAKE2b-256")); - Assert.AreEqual(SpdxChecksumAlgorithm.Blake2B384, SpdxChecksumAlgorithmExtensions.FromText("BLAKE2b-384")); - Assert.AreEqual(SpdxChecksumAlgorithm.Blake2B512, SpdxChecksumAlgorithmExtensions.FromText("BLAKE2b-512")); - Assert.AreEqual(SpdxChecksumAlgorithm.Blake3, SpdxChecksumAlgorithmExtensions.FromText("BLAKE3")); - Assert.AreEqual(SpdxChecksumAlgorithm.Adler32, SpdxChecksumAlgorithmExtensions.FromText("ADLER32")); + Assert.Equal(SpdxChecksumAlgorithm.Missing, SpdxChecksumAlgorithmExtensions.FromText("")); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("SHA1")); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("sha1")); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, SpdxChecksumAlgorithmExtensions.FromText("Sha1")); + Assert.Equal(SpdxChecksumAlgorithm.Sha224, SpdxChecksumAlgorithmExtensions.FromText("SHA224")); + Assert.Equal(SpdxChecksumAlgorithm.Sha256, SpdxChecksumAlgorithmExtensions.FromText("SHA256")); + Assert.Equal(SpdxChecksumAlgorithm.Sha384, SpdxChecksumAlgorithmExtensions.FromText("SHA384")); + Assert.Equal(SpdxChecksumAlgorithm.Sha512, SpdxChecksumAlgorithmExtensions.FromText("SHA512")); + Assert.Equal(SpdxChecksumAlgorithm.Md2, SpdxChecksumAlgorithmExtensions.FromText("MD2")); + Assert.Equal(SpdxChecksumAlgorithm.Md4, SpdxChecksumAlgorithmExtensions.FromText("MD4")); + Assert.Equal(SpdxChecksumAlgorithm.Md5, SpdxChecksumAlgorithmExtensions.FromText("MD5")); + Assert.Equal(SpdxChecksumAlgorithm.Md6, SpdxChecksumAlgorithmExtensions.FromText("MD6")); + Assert.Equal(SpdxChecksumAlgorithm.Sha3256, SpdxChecksumAlgorithmExtensions.FromText("SHA3-256")); + Assert.Equal(SpdxChecksumAlgorithm.Sha3384, SpdxChecksumAlgorithmExtensions.FromText("SHA3-384")); + Assert.Equal(SpdxChecksumAlgorithm.Sha3512, SpdxChecksumAlgorithmExtensions.FromText("SHA3-512")); + Assert.Equal(SpdxChecksumAlgorithm.Blake2B256, SpdxChecksumAlgorithmExtensions.FromText("BLAKE2b-256")); + Assert.Equal(SpdxChecksumAlgorithm.Blake2B384, SpdxChecksumAlgorithmExtensions.FromText("BLAKE2b-384")); + Assert.Equal(SpdxChecksumAlgorithm.Blake2B512, SpdxChecksumAlgorithmExtensions.FromText("BLAKE2b-512")); + Assert.Equal(SpdxChecksumAlgorithm.Blake3, SpdxChecksumAlgorithmExtensions.FromText("BLAKE3")); + Assert.Equal(SpdxChecksumAlgorithm.Adler32, SpdxChecksumAlgorithmExtensions.FromText("ADLER32")); } /// @@ -278,7 +276,7 @@ public void SpdxChecksumAlgorithmExtensions_FromText_KnownAlgorithmStrings_Retur /// with the expected message rather than /// returning a default value or silently succeeding. /// - [TestMethod] + [Fact] public void SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_ThrowsInvalidOperationException() { // Arrange: Use an algorithm string that is not in the known-algorithm list @@ -286,8 +284,8 @@ public void SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_Thro // Act / Assert: Verify that FromText throws for an unrecognized algorithm string var exception = - Assert.ThrowsExactly(() => SpdxChecksumAlgorithmExtensions.FromText("unknown")); - Assert.AreEqual("Unsupported SPDX Checksum Algorithm 'unknown'", exception.Message); + Assert.Throws(() => SpdxChecksumAlgorithmExtensions.FromText("unknown")); + Assert.Equal("Unsupported SPDX Checksum Algorithm 'unknown'", exception.Message); } /// @@ -298,29 +296,29 @@ public void SpdxChecksumAlgorithmExtensions_FromText_UnknownAlgorithmString_Thro /// that returns the canonical /// SPDX 2.x algorithm string for each value. /// - [TestMethod] + [Fact] public void SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCorrectStrings() { // Arrange: Known algorithm enum values are implicit in the Act/Assert pairs below // Act / Assert: Verify each known algorithm enum maps to the correct string - Assert.AreEqual("SHA1", SpdxChecksumAlgorithm.Sha1.ToText()); - Assert.AreEqual("SHA224", SpdxChecksumAlgorithm.Sha224.ToText()); - Assert.AreEqual("SHA256", SpdxChecksumAlgorithm.Sha256.ToText()); - Assert.AreEqual("SHA384", SpdxChecksumAlgorithm.Sha384.ToText()); - Assert.AreEqual("SHA512", SpdxChecksumAlgorithm.Sha512.ToText()); - Assert.AreEqual("MD2", SpdxChecksumAlgorithm.Md2.ToText()); - Assert.AreEqual("MD4", SpdxChecksumAlgorithm.Md4.ToText()); - Assert.AreEqual("MD5", SpdxChecksumAlgorithm.Md5.ToText()); - Assert.AreEqual("MD6", SpdxChecksumAlgorithm.Md6.ToText()); - Assert.AreEqual("SHA3-256", SpdxChecksumAlgorithm.Sha3256.ToText()); - Assert.AreEqual("SHA3-384", SpdxChecksumAlgorithm.Sha3384.ToText()); - Assert.AreEqual("SHA3-512", SpdxChecksumAlgorithm.Sha3512.ToText()); - Assert.AreEqual("BLAKE2b-256", SpdxChecksumAlgorithm.Blake2B256.ToText()); - Assert.AreEqual("BLAKE2b-384", SpdxChecksumAlgorithm.Blake2B384.ToText()); - Assert.AreEqual("BLAKE2b-512", SpdxChecksumAlgorithm.Blake2B512.ToText()); - Assert.AreEqual("BLAKE3", SpdxChecksumAlgorithm.Blake3.ToText()); - Assert.AreEqual("ADLER32", SpdxChecksumAlgorithm.Adler32.ToText()); + Assert.Equal("SHA1", SpdxChecksumAlgorithm.Sha1.ToText()); + Assert.Equal("SHA224", SpdxChecksumAlgorithm.Sha224.ToText()); + Assert.Equal("SHA256", SpdxChecksumAlgorithm.Sha256.ToText()); + Assert.Equal("SHA384", SpdxChecksumAlgorithm.Sha384.ToText()); + Assert.Equal("SHA512", SpdxChecksumAlgorithm.Sha512.ToText()); + Assert.Equal("MD2", SpdxChecksumAlgorithm.Md2.ToText()); + Assert.Equal("MD4", SpdxChecksumAlgorithm.Md4.ToText()); + Assert.Equal("MD5", SpdxChecksumAlgorithm.Md5.ToText()); + Assert.Equal("MD6", SpdxChecksumAlgorithm.Md6.ToText()); + Assert.Equal("SHA3-256", SpdxChecksumAlgorithm.Sha3256.ToText()); + Assert.Equal("SHA3-384", SpdxChecksumAlgorithm.Sha3384.ToText()); + Assert.Equal("SHA3-512", SpdxChecksumAlgorithm.Sha3512.ToText()); + Assert.Equal("BLAKE2b-256", SpdxChecksumAlgorithm.Blake2B256.ToText()); + Assert.Equal("BLAKE2b-384", SpdxChecksumAlgorithm.Blake2B384.ToText()); + Assert.Equal("BLAKE2b-512", SpdxChecksumAlgorithm.Blake2B512.ToText()); + Assert.Equal("BLAKE3", SpdxChecksumAlgorithm.Blake3.ToText()); + Assert.Equal("ADLER32", SpdxChecksumAlgorithm.Adler32.ToText()); } /// @@ -331,15 +329,15 @@ public void SpdxChecksumAlgorithmExtensions_ToText_KnownAlgorithmEnums_ReturnsCo /// out-of-range value. Verifies that /// throws with the expected message. /// - [TestMethod] + [Fact] public void SpdxChecksumAlgorithmExtensions_ToText_OutOfRangeEnum_ThrowsInvalidOperationException() { // Arrange: Use a numeric enum value that has no named member in SpdxChecksumAlgorithm // (No variable needed — the value is inlined directly into the Act / Assert.) // Act / Assert: Verify that ToText throws for an out-of-range enum value - var exception = Assert.ThrowsExactly(() => ((SpdxChecksumAlgorithm)1000).ToText()); - Assert.AreEqual("Unsupported SPDX Checksum Algorithm '1000'", exception.Message); + var exception = Assert.Throws(() => ((SpdxChecksumAlgorithm)1000).ToText()); + Assert.Equal("Unsupported SPDX Checksum Algorithm '1000'", exception.Message); } /// @@ -351,15 +349,15 @@ public void SpdxChecksumAlgorithmExtensions_ToText_OutOfRangeEnum_ThrowsInvalidO /// never be serialized — to confirm that /// throws with the expected message. /// - [TestMethod] + [Fact] public void SpdxChecksumAlgorithmExtensions_ToText_MissingAlgorithm_ThrowsInvalidOperationException() { // Arrange: Use the Missing sentinel value, which must never be serialized // Act / Assert: Verify that ToText throws for the Missing sentinel - var exception = Assert.ThrowsExactly( + var exception = Assert.Throws( () => SpdxChecksumAlgorithm.Missing.ToText()); - Assert.AreEqual("Attempt to serialize missing SPDX Checksum Algorithm", exception.Message); + Assert.Equal("Attempt to serialize missing SPDX Checksum Algorithm", exception.Message); } /// @@ -370,7 +368,7 @@ public void SpdxChecksumAlgorithmExtensions_ToText_MissingAlgorithm_ThrowsInvali /// Verifies that the comparer returns false rather than throwing, exercising the null-guard /// on the left-hand operand. /// - [TestMethod] + [Fact] public void SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse() { // Arrange: Create one valid checksum and one null reference @@ -385,7 +383,7 @@ public void SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse() var result = SpdxChecksum.Same.Equals(nullChecksum!, c1); // Assert: Verify null-first comparison returns false - Assert.IsFalse(result); + Assert.False(result); } /// @@ -396,7 +394,7 @@ public void SpdxChecksum_SameComparer_NullFirstArgument_ReturnsFalse() /// Verifies that the comparer returns false rather than throwing, exercising the null-guard /// on the right-hand operand. /// - [TestMethod] + [Fact] public void SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse() { // Arrange: Create one valid checksum and one null reference @@ -411,7 +409,7 @@ public void SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse() var result = SpdxChecksum.Same.Equals(c1, nullChecksum!); // Assert: Verify null-second comparison returns false - Assert.IsFalse(result); + Assert.False(result); } /// @@ -421,7 +419,7 @@ public void SpdxChecksum_SameComparer_NullSecondArgument_ReturnsFalse() /// Passes two null references to confirm that the comparer returns true when both /// operands are null, consistent with standard equality-comparer semantics. /// - [TestMethod] + [Fact] public void SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue() { // Arrange: Two null references @@ -432,7 +430,7 @@ public void SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue() var result = SpdxChecksum.Same.Equals(c1!, c2!); // Assert: Verify null-null comparison returns true - Assert.IsTrue(result); + Assert.True(result); } /// @@ -443,7 +441,7 @@ public void SpdxChecksum_SameComparer_BothArgumentsNull_ReturnsTrue() /// returns rather than throwing, treating /// the empty string as the absent-algorithm sentinel. /// - [TestMethod] + [Fact] public void SpdxChecksumAlgorithmExtensions_FromText_EmptyString_ReturnsMissing() { // Arrange: An empty string @@ -453,6 +451,6 @@ public void SpdxChecksumAlgorithmExtensions_FromText_EmptyString_ReturnsMissing( var result = SpdxChecksumAlgorithmExtensions.FromText(input); // Assert: Verify empty string maps to Missing - Assert.AreEqual(SpdxChecksumAlgorithm.Missing, result); + Assert.Equal(SpdxChecksumAlgorithm.Missing, result); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs index 50970dc..a60eb1f 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxCreationInformationTests.cs @@ -28,7 +28,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// with no shared state and no external dependencies. Tests cover deep copy, enhance, /// validate, and edge-case behaviors. /// -[TestClass] public class SpdxCreationInformationTests { /// @@ -40,7 +39,7 @@ public class SpdxCreationInformationTests /// the top-level reference and the Creators array reference are distinct confirms that /// no shallow-copy aliasing occurs. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_DeepCopy_WithAllFieldsPopulated_CreatesEqualButDistinctInstance() { // Arrange: Create an instance of SpdxCreationInformation with multiple creators @@ -56,14 +55,14 @@ public void SpdxCreationInformation_DeepCopy_WithAllFieldsPopulated_CreatesEqual var c2 = c1.DeepCopy(); // Assert: Verify deep-copy is equal to original - CollectionAssert.AreEqual(c1.Creators, c2.Creators); - Assert.AreEqual(c1.Created, c2.Created); - Assert.AreEqual(c1.Comment, c2.Comment); - Assert.AreEqual(c1.LicenseListVersion, c2.LicenseListVersion); + Assert.Equal(c1.Creators, c2.Creators); + Assert.Equal(c1.Created, c2.Created); + Assert.Equal(c1.Comment, c2.Comment); + Assert.Equal(c1.LicenseListVersion, c2.LicenseListVersion); // Assert: Verify deep-copy has distinct instances - Assert.IsFalse(ReferenceEquals(c1, c2)); - Assert.IsFalse(ReferenceEquals(c1.Creators, c2.Creators)); + Assert.False(ReferenceEquals(c1, c2)); + Assert.False(ReferenceEquals(c1.Creators, c2.Creators)); } /// @@ -74,7 +73,7 @@ public void SpdxCreationInformation_DeepCopy_WithAllFieldsPopulated_CreatesEqual /// LicenseListVersion field. The source instance provides both, allowing the test to /// confirm additive merging of creators and fill-if-absent semantics for scalar fields. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_Enhance_WithMissingFieldsInBase_AddsOrUpdatesInformationCorrectly() { // Arrange: Create an instance of SpdxCreationInformation with initial values @@ -94,13 +93,13 @@ public void SpdxCreationInformation_Enhance_WithMissingFieldsInBase_AddsOrUpdate }); // Assert: Verify the enhanced information - Assert.HasCount(3, info.Creators); - Assert.AreEqual("Tool: LicenseFind-1.0", info.Creators[0]); - Assert.AreEqual("Organization: ExampleCodeInspect ()", info.Creators[1]); - Assert.AreEqual("Person: Jane Doe ()", info.Creators[2]); - Assert.AreEqual("2010-01-29T18:30:22Z", info.Created); - Assert.AreEqual("This package has been shipped in source and binary form.", info.Comment); - Assert.AreEqual("3.9", info.LicenseListVersion); + Assert.Equal(3, info.Creators.Length); + Assert.Equal("Tool: LicenseFind-1.0", info.Creators[0]); + Assert.Equal("Organization: ExampleCodeInspect ()", info.Creators[1]); + Assert.Equal("Person: Jane Doe ()", info.Creators[2]); + Assert.Equal("2010-01-29T18:30:22Z", info.Created); + Assert.Equal("This package has been shipped in source and binary form.", info.Comment); + Assert.Equal("3.9", info.LicenseListVersion); } /// @@ -111,7 +110,7 @@ public void SpdxCreationInformation_Enhance_WithMissingFieldsInBase_AddsOrUpdate /// confirm that the absence of any creator entry is caught independently of other /// field values. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_Validate_MissingCreators_ReportsIssue() { // Arrange: Create creation information with empty creators array @@ -127,7 +126,7 @@ public void SpdxCreationInformation_Validate_MissingCreators_ReportsIssue() info.Validate(issues); // Assert: Verify that the validation reports the missing creators - Assert.Contains(issue => issue.Contains("Document Invalid Creator Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Document Invalid Creator Field - Empty")); } /// @@ -138,7 +137,7 @@ public void SpdxCreationInformation_Validate_MissingCreators_ReportsIssue() /// Person:, Organization:, or Tool:. The input "BadCreator" /// fails all three prefixes, making the expected issue deterministic. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_Validate_InvalidCreator_ReportsIssue() { // Arrange: Create creation information with invalid creator format @@ -154,7 +153,7 @@ public void SpdxCreationInformation_Validate_InvalidCreator_ReportsIssue() info.Validate(issues); // Assert: Verify that the validation reports the invalid creator - Assert.Contains(issue => issue.Contains("Document Invalid Creator Entry 'BadCreator'"), issues); + Assert.Contains(issues, issue => issue.Contains("Document Invalid Creator Entry 'BadCreator'")); } /// @@ -165,7 +164,7 @@ public void SpdxCreationInformation_Validate_InvalidCreator_ReportsIssue() /// chosen because it is unambiguously non-empty and non-conforming, confirming that /// the regex/helper rejects it without false negatives. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_Validate_InvalidCreatedDate_ReportsIssue() { // Arrange: Create creation information with invalid created date @@ -181,7 +180,7 @@ public void SpdxCreationInformation_Validate_InvalidCreatedDate_ReportsIssue() info.Validate(issues); // Assert: Verify that the validation reports the invalid created date - Assert.Contains(issue => issue.Contains("Document Invalid Created Field 'BadDate'"), issues); + Assert.Contains(issues, issue => issue.Contains("Document Invalid Created Field 'BadDate'")); } /// @@ -192,7 +191,7 @@ public void SpdxCreationInformation_Validate_InvalidCreatedDate_ReportsIssue() /// "BadVersion" does not match the \d+\.\d+ pattern and confirms /// that the regex rejects non-numeric version strings. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_Validate_InvalidVersion_ReportsIssue() { // Arrange: Create creation information with invalid license list version @@ -209,7 +208,7 @@ public void SpdxCreationInformation_Validate_InvalidVersion_ReportsIssue() info.Validate(issues); // Assert: Verify that the validation reports the invalid license list version - Assert.Contains(issue => issue.Contains("Document Invalid License List Version Field 'BadVersion'"), issues); + Assert.Contains(issues, issue => issue.Contains("Document Invalid License List Version Field 'BadVersion'")); } /// @@ -220,7 +219,7 @@ public void SpdxCreationInformation_Validate_InvalidVersion_ReportsIssue() /// spurious validation issues are reported when all fields satisfy their /// respective rules. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_Validate_ValidInformation_NoIssues() { // Arrange: Create valid creation information @@ -237,7 +236,7 @@ public void SpdxCreationInformation_Validate_ValidInformation_NoIssues() info.Validate(issues); // Assert: Verify that no issues are reported - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// @@ -248,7 +247,7 @@ public void SpdxCreationInformation_Validate_ValidInformation_NoIssues() /// documents. Confirms that the validator does not report a date-format issue when /// the field is intentionally left blank. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue() { // Arrange: Create creation information with an empty Created field @@ -263,7 +262,7 @@ public void SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue() info.Validate(issues); // Assert: Verify that no date-related issue is reported (empty Created is permitted) - Assert.IsFalse(issues.Any(issue => issue.Contains("Invalid Created Field"))); + Assert.DoesNotContain(issues, issue => issue.Contains("Invalid Created Field")); } /// @@ -274,7 +273,7 @@ public void SpdxCreationInformation_Validate_EmptyCreatedField_NoDateIssue() /// instances share one common creator, allowing the test to confirm that the merged /// Creators array contains exactly three distinct entries without duplicates. /// - [TestMethod] + [Fact] public void SpdxCreationInformation_Enhance_DuplicateCreators_DeduplicatesCreators() { // Arrange: Create creation information with an initial creator list that contains a duplicate @@ -292,9 +291,9 @@ public void SpdxCreationInformation_Enhance_DuplicateCreators_DeduplicatesCreato }); // Assert: Verify that duplicate creators are removed and unique entries are preserved - Assert.HasCount(3, info.Creators); - Assert.IsTrue(info.Creators.Contains("Tool: LicenseFind-1.0")); - Assert.IsTrue(info.Creators.Contains("Organization: ExampleCodeInspect ()")); - Assert.IsTrue(info.Creators.Contains("Person: Jane Doe ()")); + Assert.Equal(3, info.Creators.Length); + Assert.True(info.Creators.Contains("Tool: LicenseFind-1.0")); + Assert.True(info.Creators.Contains("Organization: ExampleCodeInspect ()")); + Assert.True(info.Creators.Contains("Person: Jane Doe ()")); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs index 80c8225..535ed91 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxDocumentTests.cs @@ -26,12 +26,10 @@ namespace DemaConsulting.SpdxModel.Tests; /// Tests for the class. /// /// -/// Tests the class using MSTest (approved exception: xUnit -/// adoption is deferred for this project). Each test constructs its own document state +/// Tests the class using xUnit v3. Each test constructs its own document state /// from scratch or deserializes the embedded JSON fixture /// SPDXJSONExample-v2.3.spdx.json; no shared instance state is used. /// -[TestClass] public class SpdxDocumentTests { /// @@ -42,7 +40,7 @@ public class SpdxDocumentTests /// Describes list and one via a DescribedBy relationship) to verify that GetRootPackages /// returns exactly the two packages named as roots and excludes the third. /// - [TestMethod] + [Fact] public void SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCorrectPackages() { // Arrange: Create a sample SPDX document with multiple packages and relationships @@ -88,9 +86,9 @@ public void SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCo var packages = document.GetRootPackages(); // Assert: Verify the correct root packages are returned - Assert.HasCount(2, packages); - Assert.IsTrue(Array.Exists(packages, p => p.Id == "SPDXRef-Package1")); - Assert.IsTrue(Array.Exists(packages, p => p.Id == "SPDXRef-Package2")); + Assert.Equal(2, packages.Length); + Assert.True(Array.Exists(packages, p => p.Id == "SPDXRef-Package1")); + Assert.True(Array.Exists(packages, p => p.Id == "SPDXRef-Package2")); } /// @@ -102,7 +100,7 @@ public void SpdxDocument_GetRootPackages_WithDescribesAndRelationships_ReturnsCo /// Verifies reflexive, symmetric, cross-inequality comparisons, and hash-code consistency /// for equal documents. /// - [TestMethod] + [Fact] public void SpdxDocument_Same_DocumentsWithMatchingRootPackages_AreEqual() { // Arrange: Create three documents with different properties @@ -161,20 +159,20 @@ public void SpdxDocument_Same_DocumentsWithMatchingRootPackages_AreEqual() }; // Act / Assert: Verify documents compare to themselves - Assert.IsTrue(SpdxDocument.Same.Equals(d1, d1)); - Assert.IsTrue(SpdxDocument.Same.Equals(d2, d2)); - Assert.IsTrue(SpdxDocument.Same.Equals(d3, d3)); + Assert.True(SpdxDocument.Same.Equals(d1, d1)); + Assert.True(SpdxDocument.Same.Equals(d2, d2)); + Assert.True(SpdxDocument.Same.Equals(d3, d3)); // Act / Assert: Verify documents compare correctly - Assert.IsTrue(SpdxDocument.Same.Equals(d1, d2)); - Assert.IsTrue(SpdxDocument.Same.Equals(d2, d1)); - Assert.IsFalse(SpdxDocument.Same.Equals(d1, d3)); - Assert.IsFalse(SpdxDocument.Same.Equals(d3, d1)); - Assert.IsFalse(SpdxDocument.Same.Equals(d2, d3)); - Assert.IsFalse(SpdxDocument.Same.Equals(d3, d2)); + Assert.True(SpdxDocument.Same.Equals(d1, d2)); + Assert.True(SpdxDocument.Same.Equals(d2, d1)); + Assert.False(SpdxDocument.Same.Equals(d1, d3)); + Assert.False(SpdxDocument.Same.Equals(d3, d1)); + Assert.False(SpdxDocument.Same.Equals(d2, d3)); + Assert.False(SpdxDocument.Same.Equals(d3, d2)); // Assert: Verify same documents have identical hashes - Assert.AreEqual(SpdxDocument.Same.GetHashCode(d1), SpdxDocument.Same.GetHashCode(d2)); + Assert.Equal(SpdxDocument.Same.GetHashCode(d1), SpdxDocument.Same.GetHashCode(d2)); } /// @@ -186,7 +184,7 @@ public void SpdxDocument_Same_DocumentsWithMatchingRootPackages_AreEqual() /// list, then deep-copies it. Verifies that every collection and scalar field is equal /// but that all array references are distinct from the original. /// - [TestMethod] + [Fact] public void SpdxDocument_DeepCopy_WithPopulatedDocument_CreatesEqualButDistinctInstance() { // Arrange: Create a sample SPDX document with various elements @@ -278,31 +276,31 @@ public void SpdxDocument_DeepCopy_WithPopulatedDocument_CreatesEqualButDistinctI var d2 = d1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(d1, d2, SpdxDocument.Same); - Assert.AreEqual(d1.Id, d2.Id); - Assert.AreEqual(d1.Version, d2.Version); - Assert.AreEqual(d1.Name, d2.Name); - CollectionAssert.AreEquivalent(d1.ExternalDocumentReferences, d2.ExternalDocumentReferences, + Assert.Equal(d1, d2, SpdxDocument.Same); + Assert.Equal(d1.Id, d2.Id); + Assert.Equal(d1.Version, d2.Version); + Assert.Equal(d1.Name, d2.Name); + SpdxTestHelpers.AssertEquivalent(d1.ExternalDocumentReferences, d2.ExternalDocumentReferences, SpdxExternalDocumentReference.Same); - CollectionAssert.AreEquivalent(d1.ExtractedLicensingInfo, d2.ExtractedLicensingInfo, + SpdxTestHelpers.AssertEquivalent(d1.ExtractedLicensingInfo, d2.ExtractedLicensingInfo, SpdxExtractedLicensingInfo.Same); - CollectionAssert.AreEquivalent(d1.Annotations, d2.Annotations, SpdxAnnotation.Same); - CollectionAssert.AreEquivalent(d1.Files, d2.Files, SpdxFile.Same); - CollectionAssert.AreEquivalent(d1.Packages, d2.Packages, SpdxPackage.Same); - CollectionAssert.AreEquivalent(d1.Snippets, d2.Snippets, SpdxSnippet.Same); - CollectionAssert.AreEquivalent(d1.Relationships, d2.Relationships, SpdxRelationship.Same); - CollectionAssert.AreEqual(d1.Describes, d2.Describes); + SpdxTestHelpers.AssertEquivalent(d1.Annotations, d2.Annotations, SpdxAnnotation.Same); + SpdxTestHelpers.AssertEquivalent(d1.Files, d2.Files, SpdxFile.Same); + SpdxTestHelpers.AssertEquivalent(d1.Packages, d2.Packages, SpdxPackage.Same); + SpdxTestHelpers.AssertEquivalent(d1.Snippets, d2.Snippets, SpdxSnippet.Same); + SpdxTestHelpers.AssertEquivalent(d1.Relationships, d2.Relationships, SpdxRelationship.Same); + Assert.Equal(d1.Describes, d2.Describes); // Assert: Verify deep-copy has distinct instances - Assert.IsFalse(ReferenceEquals(d1, d2)); - Assert.IsFalse(ReferenceEquals(d1.ExternalDocumentReferences, d2.ExternalDocumentReferences)); - Assert.IsFalse(ReferenceEquals(d1.ExtractedLicensingInfo, d2.ExtractedLicensingInfo)); - Assert.IsFalse(ReferenceEquals(d1.Annotations, d2.Annotations)); - Assert.IsFalse(ReferenceEquals(d1.Files, d2.Files)); - Assert.IsFalse(ReferenceEquals(d1.Packages, d2.Packages)); - Assert.IsFalse(ReferenceEquals(d1.Snippets, d2.Snippets)); - Assert.IsFalse(ReferenceEquals(d1.Relationships, d2.Relationships)); - Assert.IsFalse(ReferenceEquals(d1.Describes, d2.Describes)); + Assert.False(ReferenceEquals(d1, d2)); + Assert.False(ReferenceEquals(d1.ExternalDocumentReferences, d2.ExternalDocumentReferences)); + Assert.False(ReferenceEquals(d1.ExtractedLicensingInfo, d2.ExtractedLicensingInfo)); + Assert.False(ReferenceEquals(d1.Annotations, d2.Annotations)); + Assert.False(ReferenceEquals(d1.Files, d2.Files)); + Assert.False(ReferenceEquals(d1.Packages, d2.Packages)); + Assert.False(ReferenceEquals(d1.Snippets, d2.Snippets)); + Assert.False(ReferenceEquals(d1.Relationships, d2.Relationships)); + Assert.False(ReferenceEquals(d1.Describes, d2.Describes)); } /// @@ -313,21 +311,21 @@ public void SpdxDocument_DeepCopy_WithPopulatedDocument_CreatesEqualButDistinctI /// to obtain a fully valid document and verifies that the validator reports no issues. /// Using the embedded JSON fixture ensures the document satisfies all field constraints. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_ValidDocument_ReportsNoIssues() { // Arrange: Load a valid SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Act: Perform validation on the document var issues = new List(); doc.Validate(issues); // Assert: Verify no validation issues are reported - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// @@ -338,14 +336,14 @@ public void SpdxDocument_Validate_ValidDocument_ReportsNoIssues() /// overwrites its SPDX-ID with "BadId". Verifies that the validator reports /// the expected diagnostic for a malformed SPDX identifier. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_InvalidId_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Arrange: Corrupt the document with invalid ID doc.Id = "BadId"; @@ -366,14 +364,14 @@ public void SpdxDocument_Validate_InvalidId_ReportsIssue() /// clears its name field. Verifies that the validator reports the expected diagnostic /// for an empty document name. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_InvalidName_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Arrange: Corrupt the document with empty name doc.Name = ""; @@ -394,14 +392,14 @@ public void SpdxDocument_Validate_InvalidName_ReportsIssue() /// overwrites the version with "BadVersion". Verifies that the validator reports /// the expected diagnostic for a version string that does not match the SPDX-2.x pattern. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_InvalidVersion_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Arrange: Corrupt the document with invalid version doc.Version = "BadVersion"; @@ -422,14 +420,14 @@ public void SpdxDocument_Validate_InvalidVersion_ReportsIssue() /// overwrites the data license with "BadLicense". Verifies that the validator /// reports the expected diagnostic for a value other than the mandatory CC0-1.0 license. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_InvalidDataLicense_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Arrange: Corrupt the document with invalid data license doc.DataLicense = "BadLicense"; @@ -450,14 +448,14 @@ public void SpdxDocument_Validate_InvalidDataLicense_ReportsIssue() /// clears the namespace URI. Verifies that the validator reports the expected diagnostic /// for an empty document namespace. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_InvalidNameSpace_ReportsIssue() { // Arrange: Load and deserialize a valid SPDX document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Arrange: Corrupt the document with empty namespace doc.DocumentNamespace = ""; @@ -478,7 +476,7 @@ public void SpdxDocument_Validate_InvalidNameSpace_ReportsIssue() /// that the validator reports the expected duplicate-ID diagnostic rather than silently /// accepting the malformed document. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_DuplicatePackageIds_ReportsIssue() { // Arrange: Create a sample SPDX document with duplicate package IDs @@ -526,7 +524,7 @@ public void SpdxDocument_Validate_DuplicatePackageIds_ReportsIssue() /// RelatedSpdxElement references an ID that does not exist in the document. /// Verifies that the validator reports the expected dangling-reference diagnostic. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_InvalidRelationship_ReportsIssue() { // Arrange: Create a sample SPDX document with a relationship to a non-existent package @@ -570,14 +568,14 @@ public void SpdxDocument_Validate_InvalidRelationship_ReportsIssue() /// for some packages (supplier and version for Apache Commons Lang; supplier for Jena and /// Saxon). Verifies that the NTIA validation mode reports exactly those expected issues. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_NtiaMinimumElements_ReportsIssues() { // Arrange: Load a sample SPDX JSON document with known NTIA issues var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Act: Perform validation var issues = new List(); @@ -599,36 +597,36 @@ public void SpdxDocument_Validate_NtiaMinimumElements_ReportsIssues() /// returns exactly those elements and that entries are /// excluded from the result. /// - [TestMethod] + [Fact] public void SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelationshipElements() { // Arrange: Load a sample SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Act: Get all elements var elements = doc.GetAllElements().ToList(); // Assert: Verify the document is in the list - Assert.AreEqual(1, elements.OfType().Count()); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-DOCUMENT")); + Assert.Single(elements.OfType()); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-DOCUMENT")); // Assert: Verify all packages are in the list - Assert.AreEqual(4, elements.OfType().Count()); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-Package")); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-fromDoap-1")); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-fromDoap-0")); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-Saxon")); + Assert.Equal(4, elements.OfType().Count()); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-Package")); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-fromDoap-1")); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-fromDoap-0")); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-Saxon")); // Assert: Verify all files are in the list - Assert.AreEqual(5, elements.OfType().Count()); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-DoapSource")); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-CommonsLangSrc")); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-JenaLib")); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-Specification")); - Assert.IsNotNull(elements.Find(e => e.Id == "SPDXRef-File")); + Assert.Equal(5, elements.OfType().Count()); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-DoapSource")); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-CommonsLangSrc")); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-JenaLib")); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-Specification")); + Assert.NotNull(elements.Find(e => e.Id == "SPDXRef-File")); } /// @@ -639,21 +637,21 @@ public void SpdxDocument_GetAllElements_WithMixedElements_ReturnsAllNonRelations /// SPDX-ID. Verifies that the returned object is the document itself and has the expected /// document name. /// - [TestMethod] + [Fact] public void SpdxDocument_GetElement_Document_ReturnsDocumentElement() { // Arrange: Load a sample SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Act: Find the document element by ID var foundDoc = doc.GetElement("SPDXRef-DOCUMENT"); // Assert: Verify the document element is correct - Assert.IsNotNull(foundDoc); - Assert.AreEqual("SPDX-Tools-v2.0", foundDoc.Name); + Assert.NotNull(foundDoc); + Assert.Equal("SPDX-Tools-v2.0", foundDoc.Name); } /// @@ -663,21 +661,21 @@ public void SpdxDocument_GetElement_Document_ReturnsDocumentElement() /// Deserializes the embedded JSON fixture and queries for a file element by its SPDX-ID. /// Verifies that the returned object is the correct file and has the expected file name. /// - [TestMethod] + [Fact] public void SpdxDocument_GetElement_File_ReturnsFileElement() { // Arrange: Load a sample SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Act: Find a file element by ID var foundFile = doc.GetElement("SPDXRef-JenaLib"); // Assert: Verify the file element is correct - Assert.IsNotNull(foundFile); - Assert.AreEqual("./lib-source/jena-2.6.3-sources.jar", foundFile.FileName); + Assert.NotNull(foundFile); + Assert.Equal("./lib-source/jena-2.6.3-sources.jar", foundFile.FileName); } /// @@ -688,21 +686,21 @@ public void SpdxDocument_GetElement_File_ReturnsFileElement() /// SPDX-ID. Verifies that the returned object is the correct package and has the /// expected file name property. /// - [TestMethod] + [Fact] public void SpdxDocument_GetElement_Package_ReturnsPackageElement() { // Arrange: Load a sample SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Act: Find a package element by ID var foundPackage = doc.GetElement("SPDXRef-Saxon"); // Assert: Verify the package element is correct - Assert.IsNotNull(foundPackage); - Assert.AreEqual("saxonB-8.8.zip", foundPackage.FileName); + Assert.NotNull(foundPackage); + Assert.Equal("saxonB-8.8.zip", foundPackage.FileName); } /// @@ -713,21 +711,21 @@ public void SpdxDocument_GetElement_Package_ReturnsPackageElement() /// SPDX-ID. Verifies that the returned object is the correct snippet and references /// the expected source file SPDX-ID. /// - [TestMethod] + [Fact] public void SpdxDocument_GetElement_Snippet_ReturnsSnippetElement() { // Arrange: Load a sample SPDX JSON document var json22Example = SpdxTestHelpers.GetEmbeddedResource( "DemaConsulting.SpdxModel.Tests.IO.Examples.SPDXJSONExample-v2.3.spdx.json"); var doc = Spdx2JsonDeserializer.Deserialize(json22Example); - Assert.IsNotNull(doc); + Assert.NotNull(doc); // Act: Find a snippet element by ID var foundSnippet = doc.GetElement("SPDXRef-Snippet"); // Assert: Verify the snippet element is correct - Assert.IsNotNull(foundSnippet); - Assert.AreEqual("SPDXRef-DoapSource", foundSnippet.SnippetFromFile); + Assert.NotNull(foundSnippet); + Assert.Equal("SPDXRef-DoapSource", foundSnippet.SnippetFromFile); } /// @@ -738,7 +736,7 @@ public void SpdxDocument_GetElement_Snippet_ReturnsSnippetElement() /// Annotator field. Verifies that the validator reports the annotation-level issue using /// the document element prefix so the issue can be attributed to the correct context. /// - [TestMethod] + [Fact] public void SpdxDocument_Validate_InvalidAnnotation_ReportsIssue() { // Arrange: Create a document with an invalid annotation diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs index e8d9ebd..7017fe0 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxElementTests.cs @@ -28,7 +28,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// concrete subclass to exercise base-class identity behavior, as its Validate method /// uses the standard SpdxRefRegex check. /// -[TestClass] public class SpdxElementTests { /// @@ -39,7 +38,7 @@ public class SpdxElementTests /// value that satisfies the SPDXRef- prefix pattern and contains only allowed characters, /// making it the simplest positive example to confirm the happy-path acceptance. /// - [TestMethod] + [Fact] public void SpdxElement_Id_ValidFormat_PassesValidation() { // Arrange: Create a minimal package element with a valid SPDXRef- ID @@ -50,7 +49,7 @@ public void SpdxElement_Id_ValidFormat_PassesValidation() element.Validate(issues, null); // Assert: No issue about the SPDX Identifier field - Assert.IsFalse(issues.Any(i => i.Contains("Invalid SPDX Identifier"))); + Assert.DoesNotContain(issues, i => i.Contains("Invalid SPDX Identifier")); } /// @@ -62,7 +61,7 @@ public void SpdxElement_Id_ValidFormat_PassesValidation() /// that omits the required SPDXRef- prefix entirely, making the expected failure /// unambiguous and the diagnostic message easy to verify. /// - [TestMethod] + [Fact] public void SpdxElement_Id_InvalidFormat_ReportsValidationIssue() { // Arrange: Create a minimal package element with a bad ID @@ -73,6 +72,6 @@ public void SpdxElement_Id_InvalidFormat_ReportsValidationIssue() element.Validate(issues, null); // Assert: The invalid identifier is reported - Assert.IsTrue(issues.Any(i => i.Contains("Invalid SPDX Identifier Field 'BadId'"))); + Assert.Contains(issues, i => i.Contains("Invalid SPDX Identifier Field 'BadId'")); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs index 5cd6e2b..2cbc35d 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalDocumentReferenceTests.cs @@ -24,12 +24,10 @@ namespace DemaConsulting.SpdxModel.Tests; /// Tests for the class. /// /// -/// Tests the class using MSTest (approved -/// exception: xUnit adoption is deferred for this project). Each test method constructs +/// Tests the class using xUnit v3. Each test method constructs /// its own instance state with no shared fixture, covering the Same comparer, DeepCopy, /// Enhance, and Validate methods. /// -[TestClass] public class SpdxExternalDocumentReferenceTests { /// @@ -42,7 +40,7 @@ public class SpdxExternalDocumentReferenceTests /// symmetric, and cross-inequality comparisons, and that equal references produce /// identical hash codes. /// - [TestMethod] + [Fact] public void SpdxExternalDocumentReference_SameComparer_SameDocument_ReturnsEqual() { // Arrange: Create three external document references with different properties @@ -78,20 +76,20 @@ public void SpdxExternalDocumentReference_SameComparer_SameDocument_ReturnsEqual }; // Act / Assert: Verify external-document-references compare to themselves - Assert.IsTrue(SpdxExternalDocumentReference.Same.Equals(r1, r1)); - Assert.IsTrue(SpdxExternalDocumentReference.Same.Equals(r2, r2)); - Assert.IsTrue(SpdxExternalDocumentReference.Same.Equals(r3, r3)); + Assert.True(SpdxExternalDocumentReference.Same.Equals(r1, r1)); + Assert.True(SpdxExternalDocumentReference.Same.Equals(r2, r2)); + Assert.True(SpdxExternalDocumentReference.Same.Equals(r3, r3)); // Assert: Verify external-document-references compare correctly - Assert.IsTrue(SpdxExternalDocumentReference.Same.Equals(r1, r2)); - Assert.IsTrue(SpdxExternalDocumentReference.Same.Equals(r2, r1)); - Assert.IsFalse(SpdxExternalDocumentReference.Same.Equals(r1, r3)); - Assert.IsFalse(SpdxExternalDocumentReference.Same.Equals(r3, r1)); - Assert.IsFalse(SpdxExternalDocumentReference.Same.Equals(r2, r3)); - Assert.IsFalse(SpdxExternalDocumentReference.Same.Equals(r3, r2)); + Assert.True(SpdxExternalDocumentReference.Same.Equals(r1, r2)); + Assert.True(SpdxExternalDocumentReference.Same.Equals(r2, r1)); + Assert.False(SpdxExternalDocumentReference.Same.Equals(r1, r3)); + Assert.False(SpdxExternalDocumentReference.Same.Equals(r3, r1)); + Assert.False(SpdxExternalDocumentReference.Same.Equals(r2, r3)); + Assert.False(SpdxExternalDocumentReference.Same.Equals(r3, r2)); // Assert: Verify same external-document-references have identical hashes - Assert.AreEqual(SpdxExternalDocumentReference.Same.GetHashCode(r1), + Assert.Equal(SpdxExternalDocumentReference.Same.GetHashCode(r1), SpdxExternalDocumentReference.Same.GetHashCode(r2)); } @@ -103,7 +101,7 @@ public void SpdxExternalDocumentReference_SameComparer_SameDocument_ReturnsEqual /// it. Verifies that the copy has equal field values but that both the top-level reference /// and the nested Checksum are distinct object references from the original. /// - [TestMethod] + [Fact] public void SpdxExternalDocumentReference_DeepCopy_ValidInstance_ReturnsEqualButDistinctInstance() { // Arrange: Create an external document reference with a checksum @@ -122,14 +120,14 @@ public void SpdxExternalDocumentReference_DeepCopy_ValidInstance_ReturnsEqualBut var r2 = r1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(r1, r2, SpdxExternalDocumentReference.Same); - Assert.AreEqual(r1.ExternalDocumentId, r2.ExternalDocumentId); - Assert.AreEqual(r1.Checksum, r2.Checksum, SpdxChecksum.Same); - Assert.AreEqual(r1.Document, r2.Document); + Assert.Equal(r1, r2, SpdxExternalDocumentReference.Same); + Assert.Equal(r1.ExternalDocumentId, r2.ExternalDocumentId); + Assert.Equal(r1.Checksum, r2.Checksum, SpdxChecksum.Same); + Assert.Equal(r1.Document, r2.Document); // Assert: Verify deep-copy has distinct instances - Assert.IsFalse(ReferenceEquals(r1, r2)); - Assert.IsFalse(ReferenceEquals(r1.Checksum, r2.Checksum)); + Assert.False(ReferenceEquals(r1, r2)); + Assert.False(ReferenceEquals(r1.Checksum, r2.Checksum)); } /// @@ -143,7 +141,7 @@ public void SpdxExternalDocumentReference_DeepCopy_ValidInstance_ReturnsEqualBut /// new reference. Verifies that the merged array has exactly two entries with the correct /// checksum data and new reference details. /// - [TestMethod] + [Fact] public void SpdxExternalDocumentReference_Enhance_WithNewAndMatchingEntries_MergesAndAppendsCorrectly() { // Arrange: Create an array of external document references @@ -182,16 +180,16 @@ public void SpdxExternalDocumentReference_Enhance_WithNewAndMatchingEntries_Merg ]); // Assert: Verify the references array has correct information - Assert.HasCount(2, references); - Assert.AreEqual("DocumentRef-spdx-tool-1.2", references[0].ExternalDocumentId); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, references[0].Checksum.Algorithm); - Assert.AreEqual("d6a770ba38583ed4bb4525bd96e50461655d2759", references[0].Checksum.Value); - Assert.AreEqual("http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", + Assert.Equal(2, references.Length); + Assert.Equal("DocumentRef-spdx-tool-1.2", references[0].ExternalDocumentId); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, references[0].Checksum.Algorithm); + Assert.Equal("d6a770ba38583ed4bb4525bd96e50461655d2759", references[0].Checksum.Value); + Assert.Equal("http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", references[0].Document); - Assert.AreEqual("DocumentRef-OtherDoc", references[1].ExternalDocumentId); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, references[1].Checksum.Algorithm); - Assert.AreEqual("c2b4e1c67a2d28fced849ee1bb76e7391b93f125", references[1].Checksum.Value); - Assert.AreEqual("http://demo.com/some-document", references[1].Document); + Assert.Equal("DocumentRef-OtherDoc", references[1].ExternalDocumentId); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, references[1].Checksum.Algorithm); + Assert.Equal("c2b4e1c67a2d28fced849ee1bb76e7391b93f125", references[1].Checksum.Value); + Assert.Equal("http://demo.com/some-document", references[1].Document); } /// @@ -202,7 +200,7 @@ public void SpdxExternalDocumentReference_Enhance_WithNewAndMatchingEntries_Merg /// that the validator catches the absent ID and includes the expected description string /// in the reported issue. /// - [TestMethod] + [Fact] public void SpdxExternalDocumentReference_Validate_MissingId_ReportsIssue() { // Arrange: Create a bad reference @@ -217,7 +215,7 @@ public void SpdxExternalDocumentReference_Validate_MissingId_ReportsIssue() reference.Validate(issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("External Document Reference Invalid External Document ID Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("External Document Reference Invalid External Document ID Field - Empty")); } /// @@ -228,7 +226,7 @@ public void SpdxExternalDocumentReference_Validate_MissingId_ReportsIssue() /// that the validator catches the absent URI and includes the expected description string /// (including the reference ID) in the reported issue. /// - [TestMethod] + [Fact] public void SpdxExternalDocumentReference_Validate_MissingDocument_ReportsIssue() { // Arrange: Create a bad reference @@ -243,7 +241,7 @@ public void SpdxExternalDocumentReference_Validate_MissingDocument_ReportsIssue( reference.Validate(issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("External Document Reference 'DocumentRef-spdx-tool-1.2' Invalid SPDX Document URI Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("External Document Reference 'DocumentRef-spdx-tool-1.2' Invalid SPDX Document URI Field - Empty")); } /// @@ -254,7 +252,7 @@ public void SpdxExternalDocumentReference_Validate_MissingDocument_ReportsIssue( /// and an empty checksum value to confirm that the validator delegates to the checksum /// validator and surfaces the algorithm-missing diagnostic in the reported issues. /// - [TestMethod] + [Fact] public void SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue() { // Arrange: Create a reference with a missing-algorithm checksum @@ -275,7 +273,7 @@ public void SpdxExternalDocumentReference_Validate_InvalidChecksum_ReportsIssue( // Assert: Verify that the checksum algorithm issue is reported Assert.Contains( - issue => issue.Contains("Invalid Checksum Algorithm Field - Missing"), - issues); + issues, + issue => issue.Contains("Invalid Checksum Algorithm Field - Missing")); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs index 161b3f4..374b176 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExternalReferenceTests.cs @@ -27,7 +27,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// Covers the Same equality comparer, DeepCopy, Enhance merge, Validate, and the /// SpdxReferenceCategory text-conversion extension methods (FromText/ToText). /// -[TestClass] public class SpdxExternalReferenceTests { /// @@ -38,7 +37,7 @@ public class SpdxExternalReferenceTests /// regardless of Comment differences, that differing field values produce inequality, /// and that equal references produce identical hash codes. /// - [TestMethod] + [Fact] public void SpdxExternalReference_SameComparer_EqualAndUnequalInstances_ComparesCorrectly() { // Arrange: Create three external references with different properties @@ -63,20 +62,20 @@ public void SpdxExternalReference_SameComparer_EqualAndUnequalInstances_Compares }; // Act / Assert: Verify external-references compare to themselves - Assert.IsTrue(SpdxExternalReference.Same.Equals(r1, r1)); - Assert.IsTrue(SpdxExternalReference.Same.Equals(r2, r2)); - Assert.IsTrue(SpdxExternalReference.Same.Equals(r3, r3)); + Assert.True(SpdxExternalReference.Same.Equals(r1, r1)); + Assert.True(SpdxExternalReference.Same.Equals(r2, r2)); + Assert.True(SpdxExternalReference.Same.Equals(r3, r3)); // Assert: Verify external-references compare correctly - Assert.IsTrue(SpdxExternalReference.Same.Equals(r1, r2)); - Assert.IsTrue(SpdxExternalReference.Same.Equals(r2, r1)); - Assert.IsFalse(SpdxExternalReference.Same.Equals(r1, r3)); - Assert.IsFalse(SpdxExternalReference.Same.Equals(r3, r1)); - Assert.IsFalse(SpdxExternalReference.Same.Equals(r2, r3)); - Assert.IsFalse(SpdxExternalReference.Same.Equals(r3, r2)); + Assert.True(SpdxExternalReference.Same.Equals(r1, r2)); + Assert.True(SpdxExternalReference.Same.Equals(r2, r1)); + Assert.False(SpdxExternalReference.Same.Equals(r1, r3)); + Assert.False(SpdxExternalReference.Same.Equals(r3, r1)); + Assert.False(SpdxExternalReference.Same.Equals(r2, r3)); + Assert.False(SpdxExternalReference.Same.Equals(r3, r2)); // Assert: Verify same external-references have identical hashes - Assert.AreEqual(SpdxExternalReference.Same.GetHashCode(r1), SpdxExternalReference.Same.GetHashCode(r2)); + Assert.Equal(SpdxExternalReference.Same.GetHashCode(r1), SpdxExternalReference.Same.GetHashCode(r2)); } /// @@ -86,7 +85,7 @@ public void SpdxExternalReference_SameComparer_EqualAndUnequalInstances_Compares /// Verifies that the copy has equal field values to the original and that it is a distinct /// object reference (not the same instance). /// - [TestMethod] + [Fact] public void SpdxExternalReference_DeepCopy_WithAllFields_CreatesEqualButDistinctInstance() { // Arrange: Create an external reference @@ -102,14 +101,14 @@ public void SpdxExternalReference_DeepCopy_WithAllFields_CreatesEqualButDistinct var r2 = r1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(r1, r2, SpdxExternalReference.Same); - Assert.AreEqual(r1.Category, r2.Category); - Assert.AreEqual(r1.Type, r2.Type); - Assert.AreEqual(r1.Locator, r2.Locator); - Assert.AreEqual(r1.Comment, r2.Comment); + Assert.Equal(r1, r2, SpdxExternalReference.Same); + Assert.Equal(r1.Category, r2.Category); + Assert.Equal(r1.Type, r2.Type); + Assert.Equal(r1.Locator, r2.Locator); + Assert.Equal(r1.Comment, r2.Comment); // Assert: Verify deep-copy has distinct instance - Assert.IsFalse(ReferenceEquals(r1, r2)); + Assert.False(ReferenceEquals(r1, r2)); } /// @@ -120,7 +119,7 @@ public void SpdxExternalReference_DeepCopy_WithAllFields_CreatesEqualButDistinct /// Verifies that matching entries are enhanced in place and unmatched entries from the /// source array are appended as new independent copies. /// - [TestMethod] + [Fact] public void SpdxExternalReference_Enhance_WithMatchingAndNewEntries_MergesCorrectly() { // Arrange: Create an array of external references @@ -154,14 +153,14 @@ public void SpdxExternalReference_Enhance_WithMatchingAndNewEntries_MergesCorrec ]); // Assert: Verify the references array has correct information - Assert.HasCount(2, references); - Assert.AreEqual(SpdxReferenceCategory.Security, references[0].Category); - Assert.AreEqual("cpe23Type", references[0].Type); - Assert.AreEqual("cpe:2.3:a:company:product:0.0.0:*:*:*:*:*:*:*", references[0].Locator); - Assert.AreEqual("CPE23 Standard Identifier", references[0].Comment); - Assert.AreEqual(SpdxReferenceCategory.PackageManager, references[1].Category); - Assert.AreEqual("purl", references[1].Type); - Assert.AreEqual("pkg:nuget/SomePackage@0.0.0", references[1].Locator); + Assert.Equal(2, references.Length); + Assert.Equal(SpdxReferenceCategory.Security, references[0].Category); + Assert.Equal("cpe23Type", references[0].Type); + Assert.Equal("cpe:2.3:a:company:product:0.0.0:*:*:*:*:*:*:*", references[0].Locator); + Assert.Equal("CPE23 Standard Identifier", references[0].Comment); + Assert.Equal(SpdxReferenceCategory.PackageManager, references[1].Category); + Assert.Equal("purl", references[1].Type); + Assert.Equal("pkg:nuget/SomePackage@0.0.0", references[1].Locator); } /// @@ -171,7 +170,7 @@ public void SpdxExternalReference_Enhance_WithMatchingAndNewEntries_MergesCorrec /// Verifies that Validate appends an issue message when the Category field is /// . /// - [TestMethod] + [Fact] public void SpdxExternalReference_Validate_InvalidCategory_ReportsIssue() { // Arrange: Create an external reference with invalid category @@ -188,7 +187,7 @@ public void SpdxExternalReference_Validate_InvalidCategory_ReportsIssue() reference.Validate("Test", issues); // Assert: Verify that the validation reports the invalid category - Assert.Contains(issue => issue.Contains("Package 'Test' Invalid External Reference Category Field - Missing"), issues); + Assert.Contains(issues, issue => issue.Contains("Package 'Test' Invalid External Reference Category Field - Missing")); } /// @@ -197,7 +196,7 @@ public void SpdxExternalReference_Validate_InvalidCategory_ReportsIssue() /// /// Verifies that Validate appends an issue message when the Type field is empty. /// - [TestMethod] + [Fact] public void SpdxExternalReference_Validate_InvalidType_ReportsIssue() { // Arrange: Create an external reference with invalid type @@ -214,7 +213,7 @@ public void SpdxExternalReference_Validate_InvalidType_ReportsIssue() reference.Validate("Test", issues); // Assert: Verify that the validation reports the invalid type - Assert.Contains(issue => issue.Contains("Package 'Test' Invalid External Reference Type Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Package 'Test' Invalid External Reference Type Field - Empty")); } /// @@ -223,7 +222,7 @@ public void SpdxExternalReference_Validate_InvalidType_ReportsIssue() /// /// Verifies that Validate appends an issue message when the Locator field is empty. /// - [TestMethod] + [Fact] public void SpdxExternalReference_Validate_InvalidLocator_ReportsIssue() { // Arrange: Create an external reference with invalid locator @@ -240,7 +239,7 @@ public void SpdxExternalReference_Validate_InvalidLocator_ReportsIssue() reference.Validate("Test", issues); // Assert: Verify that the validation reports the invalid locator - Assert.Contains(issue => issue.Contains("Package 'Test' Invalid External Reference Locator Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Package 'Test' Invalid External Reference Locator Field - Empty")); } /// @@ -250,22 +249,22 @@ public void SpdxExternalReference_Validate_InvalidLocator_ReportsIssue() /// Verifies that all recognized category strings, including case variants, map to the /// expected enum values, and that an empty string maps to Missing. /// - [TestMethod] + [Fact] public void SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly() { // Arrange: (no external state needed) // Act / Assert: Verify all recognized category strings map to expected enum values - Assert.AreEqual(SpdxReferenceCategory.Missing, SpdxReferenceCategoryExtensions.FromText("")); - Assert.AreEqual(SpdxReferenceCategory.Security, SpdxReferenceCategoryExtensions.FromText("SECURITY")); - Assert.AreEqual(SpdxReferenceCategory.Security, SpdxReferenceCategoryExtensions.FromText("security")); - Assert.AreEqual(SpdxReferenceCategory.Security, SpdxReferenceCategoryExtensions.FromText("Security")); - Assert.AreEqual(SpdxReferenceCategory.PackageManager, + Assert.Equal(SpdxReferenceCategory.Missing, SpdxReferenceCategoryExtensions.FromText("")); + Assert.Equal(SpdxReferenceCategory.Security, SpdxReferenceCategoryExtensions.FromText("SECURITY")); + Assert.Equal(SpdxReferenceCategory.Security, SpdxReferenceCategoryExtensions.FromText("security")); + Assert.Equal(SpdxReferenceCategory.Security, SpdxReferenceCategoryExtensions.FromText("Security")); + Assert.Equal(SpdxReferenceCategory.PackageManager, SpdxReferenceCategoryExtensions.FromText("PACKAGE-MANAGER")); - Assert.AreEqual(SpdxReferenceCategory.PackageManager, + Assert.Equal(SpdxReferenceCategory.PackageManager, SpdxReferenceCategoryExtensions.FromText("PACKAGE_MANAGER")); - Assert.AreEqual(SpdxReferenceCategory.PersistentId, SpdxReferenceCategoryExtensions.FromText("PERSISTENT-ID")); - Assert.AreEqual(SpdxReferenceCategory.Other, SpdxReferenceCategoryExtensions.FromText("OTHER")); + Assert.Equal(SpdxReferenceCategory.PersistentId, SpdxReferenceCategoryExtensions.FromText("PERSISTENT-ID")); + Assert.Equal(SpdxReferenceCategory.Other, SpdxReferenceCategoryExtensions.FromText("OTHER")); } /// @@ -275,15 +274,15 @@ public void SpdxReferenceCategoryExtensions_FromText_ValidInput_ParsesCorrectly( /// Verifies that FromText throws with a message /// identifying the unsupported value when given an unrecognized category string. /// - [TestMethod] + [Fact] public void SpdxReferenceCategoryExtensions_FromText_InvalidInput_ThrowsInvalidOperationException() { // Arrange: (no external state needed) // Act / Assert: Verify that FromText throws for an unrecognized category string var exception = - Assert.ThrowsExactly(() => SpdxReferenceCategoryExtensions.FromText("invalid")); - Assert.AreEqual("Unsupported SPDX Reference Category 'invalid'", exception.Message); + Assert.Throws(() => SpdxReferenceCategoryExtensions.FromText("invalid")); + Assert.Equal("Unsupported SPDX Reference Category 'invalid'", exception.Message); } /// @@ -292,16 +291,16 @@ public void SpdxReferenceCategoryExtensions_FromText_InvalidInput_ThrowsInvalidO /// /// Verifies that all known enum values map to their expected SPDX text representations. /// - [TestMethod] + [Fact] public void SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrectly() { // Arrange: (no external state needed) // Act / Assert: Verify all known enum values map to expected text representations - Assert.AreEqual("SECURITY", SpdxReferenceCategory.Security.ToText()); - Assert.AreEqual("PACKAGE-MANAGER", SpdxReferenceCategory.PackageManager.ToText()); - Assert.AreEqual("PERSISTENT-ID", SpdxReferenceCategory.PersistentId.ToText()); - Assert.AreEqual("OTHER", SpdxReferenceCategory.Other.ToText()); + Assert.Equal("SECURITY", SpdxReferenceCategory.Security.ToText()); + Assert.Equal("PACKAGE-MANAGER", SpdxReferenceCategory.PackageManager.ToText()); + Assert.Equal("PERSISTENT-ID", SpdxReferenceCategory.PersistentId.ToText()); + Assert.Equal("OTHER", SpdxReferenceCategory.Other.ToText()); } /// @@ -311,15 +310,15 @@ public void SpdxReferenceCategoryExtensions_ToText_ValidReference_FormatsCorrect /// Verifies that ToText throws with the /// unsupported-category message when called with an unrecognized enum value. /// - [TestMethod] + [Fact] public void SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ThrowsInvalidOperationException() { // Arrange: Create an invalid reference category var invalidCategory = (SpdxReferenceCategory)1000; // Act / Assert: Verify that ToText throws an exception for unsupported category - var exception = Assert.ThrowsExactly(() => invalidCategory.ToText()); - Assert.AreEqual("Unsupported SPDX Reference Category '1000'", exception.Message); + var exception = Assert.Throws(() => invalidCategory.ToText()); + Assert.Equal("Unsupported SPDX Reference Category '1000'", exception.Message); } /// @@ -329,14 +328,14 @@ public void SpdxReferenceCategoryExtensions_ToText_InvalidCategory_ThrowsInvalid /// Verifies that ToText throws with a specific /// message when called with . /// - [TestMethod] + [Fact] public void SpdxReferenceCategoryExtensions_ToText_MissingCategory_ThrowsInvalidOperationException() { // Arrange: Use Missing reference category var category = SpdxReferenceCategory.Missing; // Act / Assert: Verify that ToText throws an exception for Missing category - var exception = Assert.ThrowsExactly(() => category.ToText()); - Assert.AreEqual("Attempt to serialize missing SPDX Reference Category", exception.Message); + var exception = Assert.Throws(() => category.ToText()); + Assert.Equal("Attempt to serialize missing SPDX Reference Category", exception.Message); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs index 81a5ae0..9c71339 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxExtractedLicensingInfoTests.cs @@ -26,7 +26,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Covers the Same equality comparer, DeepCopy, Enhance merge, and Validate methods. /// -[TestClass] public class SpdxExtractedLicensingInfoTests { /// @@ -38,7 +37,7 @@ public class SpdxExtractedLicensingInfoTests /// distinct, handles reference equality, null arguments, and produces consistent hash codes /// for equal instances. /// - [TestMethod] + [Fact] public void SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly() { // Arrange: Create three extracted licensing infos with different properties @@ -60,25 +59,25 @@ public void SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly() }; // Act / Assert: Verify extracted-licensing-infos compare to themselves - Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(l1, l1)); - Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(l2, l2)); - Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(l3, l3)); + Assert.True(SpdxExtractedLicensingInfo.Same.Equals(l1, l1)); + Assert.True(SpdxExtractedLicensingInfo.Same.Equals(l2, l2)); + Assert.True(SpdxExtractedLicensingInfo.Same.Equals(l3, l3)); // Assert: Verify extracted-licensing-infos compare correctly - Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(l1, l2)); - Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(l2, l1)); - Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(l1, l3)); - Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(l3, l1)); - Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(l2, l3)); - Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(l3, l2)); + Assert.True(SpdxExtractedLicensingInfo.Same.Equals(l1, l2)); + Assert.True(SpdxExtractedLicensingInfo.Same.Equals(l2, l1)); + Assert.False(SpdxExtractedLicensingInfo.Same.Equals(l1, l3)); + Assert.False(SpdxExtractedLicensingInfo.Same.Equals(l3, l1)); + Assert.False(SpdxExtractedLicensingInfo.Same.Equals(l2, l3)); + Assert.False(SpdxExtractedLicensingInfo.Same.Equals(l3, l2)); // Assert: Verify null handling - Assert.IsTrue(SpdxExtractedLicensingInfo.Same.Equals(null!, null!)); - Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(null!, l1)); - Assert.IsFalse(SpdxExtractedLicensingInfo.Same.Equals(l1, null!)); + Assert.True(SpdxExtractedLicensingInfo.Same.Equals(null!, null!)); + Assert.False(SpdxExtractedLicensingInfo.Same.Equals(null!, l1)); + Assert.False(SpdxExtractedLicensingInfo.Same.Equals(l1, null!)); // Assert: Verify same extracted-licensing-infos have identical hashes - Assert.AreEqual(SpdxExtractedLicensingInfo.Same.GetHashCode(l1), + Assert.Equal(SpdxExtractedLicensingInfo.Same.GetHashCode(l1), SpdxExtractedLicensingInfo.Same.GetHashCode(l2)); } @@ -89,7 +88,7 @@ public void SpdxExtractedLicensingInfo_SameComparer_ComparesCorrectly() /// Validates that DeepCopy produces a new instance with field values equal to the original /// and that arrays are independently copied (no shared references between original and copy). /// - [TestMethod] + [Fact] public void SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance() { // Arrange: Create an extracted licensing info object @@ -105,14 +104,14 @@ public void SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance( var l2 = l1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(l1, l2, SpdxExtractedLicensingInfo.Same); - Assert.AreEqual(l1.LicenseId, l2.LicenseId); - Assert.AreEqual(l1.ExtractedText, l2.ExtractedText); - Assert.AreEqual(l1.Comment, l2.Comment); + Assert.Equal(l1, l2, SpdxExtractedLicensingInfo.Same); + Assert.Equal(l1.LicenseId, l2.LicenseId); + Assert.Equal(l1.ExtractedText, l2.ExtractedText); + Assert.Equal(l1.Comment, l2.Comment); // Assert: Verify deep-copy has distinct instance - Assert.IsFalse(ReferenceEquals(l1, l2)); - Assert.IsFalse(ReferenceEquals(l1.CrossReferences, l2.CrossReferences)); + Assert.False(ReferenceEquals(l1, l2)); + Assert.False(ReferenceEquals(l1.CrossReferences, l2.CrossReferences)); } /// @@ -124,7 +123,7 @@ public void SpdxExtractedLicensingInfo_DeepCopy_CreatesEqualButDistinctInstance( /// Validates that the static Enhance merges arrays by enhancing matching entries (matched /// by ExtractedText) and appending unmatched entries as new deep copies. /// - [TestMethod] + [Fact] public void SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly() { // Arrange: Create an array of extracted licensing infos @@ -155,12 +154,12 @@ public void SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly ]); // Assert: Verify the infos array has correct information - Assert.HasCount(2, infos); - Assert.AreEqual("LicenseRef-1", infos[0].LicenseId); - Assert.AreEqual("The CyberNeko Software License", infos[0].ExtractedText); - Assert.AreEqual("Extracted from files", infos[0].Comment); - Assert.AreEqual("LicenseRef-2", infos[1].LicenseId); - Assert.AreEqual("Some Random License", infos[1].ExtractedText); + Assert.Equal(2, infos.Length); + Assert.Equal("LicenseRef-1", infos[0].LicenseId); + Assert.Equal("The CyberNeko Software License", infos[0].ExtractedText); + Assert.Equal("Extracted from files", infos[0].Comment); + Assert.Equal("LicenseRef-2", infos[1].LicenseId); + Assert.Equal("Some Random License", infos[1].ExtractedText); } /// @@ -170,7 +169,7 @@ public void SpdxExtractedLicensingInfo_Enhance_AddsOrUpdatesInformationCorrectly /// Validates that Validate reports no issues when both LicenseId and ExtractedText are /// non-empty, confirming the happy-path behavior of the validation logic. /// - [TestMethod] + [Fact] public void SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues() { // Arrange: Create a valid extracted licensing info @@ -185,7 +184,7 @@ public void SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues() info.Validate(issues); // Assert: Verify that the validation reports no issues - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// @@ -195,7 +194,7 @@ public void SpdxExtractedLicensingInfo_Validate_ValidInput_ReturnsNoIssues() /// Validates that Validate appends an issue message to the supplied list when LicenseId is /// empty, confirming the LicenseId validation path. /// - [TestMethod] + [Fact] public void SpdxExtractedLicensingInfo_Validate_InvalidLicenseId_ReportsIssue() { // Arrange: Create a bad licensing info @@ -210,7 +209,7 @@ public void SpdxExtractedLicensingInfo_Validate_InvalidLicenseId_ReportsIssue() info.Validate(issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Extracted License Information Invalid License ID Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Extracted License Information Invalid License ID Field - Empty")); } /// @@ -220,7 +219,7 @@ public void SpdxExtractedLicensingInfo_Validate_InvalidLicenseId_ReportsIssue() /// Validates that Validate appends an issue message to the supplied list when ExtractedText /// is empty, confirming the ExtractedText validation path. /// - [TestMethod] + [Fact] public void SpdxExtractedLicensingInfo_Validate_InvalidExtractedText_ReportsIssue() { // Arrange: Create a bad licensing info @@ -235,6 +234,6 @@ public void SpdxExtractedLicensingInfo_Validate_InvalidExtractedText_ReportsIssu info.Validate(issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Extracted License Information 'LicenseRef-1' Invalid Extracted Text Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Extracted License Information 'LicenseRef-1' Invalid Extracted Text Field - Empty")); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs index 82610a1..76557e2 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxFileTests.cs @@ -27,7 +27,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// Covers the Same equality comparer, DeepCopy, Enhance merge, Validate, and the /// SpdxFileType text-conversion extension methods (FromText/ToText). /// -[TestClass] public class SpdxFileTests { /// @@ -38,7 +37,7 @@ public class SpdxFileTests /// considered equal, that differing SHA1 checksums or file names produce inequality, /// and that equal files produce identical hash codes. /// - [TestMethod] + [Fact] public void SpdxFile_SameComparer_MatchingAndDistinctFiles_ComparesCorrectly() { // Arrange: Create several SpdxFile instances with different IDs, names, and checksums @@ -91,25 +90,25 @@ public void SpdxFile_SameComparer_MatchingAndDistinctFiles_ComparesCorrectly() // no checksums — should still match f1/f2 by FileName }; - // Assert: Verify files compare to themselves - Assert.IsTrue(SpdxFile.Same.Equals(f1, f1)); - Assert.IsTrue(SpdxFile.Same.Equals(f2, f2)); - Assert.IsTrue(SpdxFile.Same.Equals(f3, f3)); - - // Assert: Verify files compare correctly - Assert.IsTrue(SpdxFile.Same.Equals(f1, f2)); - Assert.IsTrue(SpdxFile.Same.Equals(f2, f1)); - Assert.IsFalse(SpdxFile.Same.Equals(f1, f3)); - Assert.IsFalse(SpdxFile.Same.Equals(f3, f1)); - Assert.IsFalse(SpdxFile.Same.Equals(f2, f3)); - Assert.IsFalse(SpdxFile.Same.Equals(f3, f2)); - - // Assert: Verify one-sided SHA1 boundary — same FileName, one has SHA1, other does not - Assert.IsTrue(SpdxFile.Same.Equals(f1, f4)); - Assert.IsTrue(SpdxFile.Same.Equals(f4, f1)); - - // Assert: Verify same files have identical hashes - Assert.AreEqual(SpdxFile.Same.GetHashCode(f1), SpdxFile.Same.GetHashCode(f2)); + // Act / Assert: Verify files compare to themselves + Assert.True(SpdxFile.Same.Equals(f1, f1)); + Assert.True(SpdxFile.Same.Equals(f2, f2)); + Assert.True(SpdxFile.Same.Equals(f3, f3)); + + // Act / Assert: Verify files compare correctly + Assert.True(SpdxFile.Same.Equals(f1, f2)); + Assert.True(SpdxFile.Same.Equals(f2, f1)); + Assert.False(SpdxFile.Same.Equals(f1, f3)); + Assert.False(SpdxFile.Same.Equals(f3, f1)); + Assert.False(SpdxFile.Same.Equals(f2, f3)); + Assert.False(SpdxFile.Same.Equals(f3, f2)); + + // Act / Assert: Verify one-sided SHA1 boundary — same FileName, one has SHA1, other does not + Assert.True(SpdxFile.Same.Equals(f1, f4)); + Assert.True(SpdxFile.Same.Equals(f4, f1)); + + // Act / Assert: Verify same files have identical hashes + Assert.Equal(SpdxFile.Same.GetHashCode(f1), SpdxFile.Same.GetHashCode(f2)); } /// @@ -119,7 +118,7 @@ public void SpdxFile_SameComparer_MatchingAndDistinctFiles_ComparesCorrectly() /// Verifies that the copy has equal field values to the original and that all array /// fields are independently copied with no shared references between original and copy. /// - [TestMethod] + [Fact] public void SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy() { // Arrange: Create an SpdxFile instance with all deep-copied fields populated @@ -165,28 +164,28 @@ public void SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy() var f2 = f1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(f1, f2, SpdxFile.Same); - Assert.AreEqual(f1.Id, f2.Id); - Assert.AreEqual(f1.FileName, f2.FileName); - CollectionAssert.AreEquivalent(f1.FileTypes, f2.FileTypes); - CollectionAssert.AreEquivalent(f1.Checksums, f2.Checksums, SpdxChecksum.Same); - CollectionAssert.AreEquivalent(f1.LicenseInfoInFiles, f2.LicenseInfoInFiles); - Assert.AreEqual(f1.LicenseComments, f2.LicenseComments); - Assert.AreEqual(f1.ConcludedLicense, f2.ConcludedLicense); - Assert.AreEqual(f1.CopyrightText, f2.CopyrightText); - Assert.AreEqual(f1.Comment, f2.Comment); - Assert.AreEqual(f1.Notice, f2.Notice); - CollectionAssert.AreEquivalent(f1.Contributors, f2.Contributors); - CollectionAssert.AreEquivalent(f1.AttributionText, f2.AttributionText); + Assert.Equal(f1, f2, SpdxFile.Same); + Assert.Equal(f1.Id, f2.Id); + Assert.Equal(f1.FileName, f2.FileName); + Assert.Equal(f1.FileTypes, f2.FileTypes); + SpdxTestHelpers.AssertEquivalent(f1.Checksums, f2.Checksums, SpdxChecksum.Same); + Assert.Equal(f1.LicenseInfoInFiles, f2.LicenseInfoInFiles); + Assert.Equal(f1.LicenseComments, f2.LicenseComments); + Assert.Equal(f1.ConcludedLicense, f2.ConcludedLicense); + Assert.Equal(f1.CopyrightText, f2.CopyrightText); + Assert.Equal(f1.Comment, f2.Comment); + Assert.Equal(f1.Notice, f2.Notice); + Assert.Equal(f1.Contributors, f2.Contributors); + Assert.Equal(f1.AttributionText, f2.AttributionText); // Assert: Verify deep-copy has distinct instances - Assert.IsFalse(ReferenceEquals(f1, f2)); - Assert.IsFalse(ReferenceEquals(f1.Checksums, f2.Checksums)); - Assert.IsFalse(ReferenceEquals(f1.FileTypes, f2.FileTypes)); - Assert.IsFalse(ReferenceEquals(f1.LicenseInfoInFiles, f2.LicenseInfoInFiles)); - Assert.IsFalse(ReferenceEquals(f1.Contributors, f2.Contributors)); - Assert.IsFalse(ReferenceEquals(f1.AttributionText, f2.AttributionText)); - Assert.IsFalse(ReferenceEquals(f1.Annotations, f2.Annotations)); + Assert.False(ReferenceEquals(f1, f2)); + Assert.False(ReferenceEquals(f1.Checksums, f2.Checksums)); + Assert.False(ReferenceEquals(f1.FileTypes, f2.FileTypes)); + Assert.False(ReferenceEquals(f1.LicenseInfoInFiles, f2.LicenseInfoInFiles)); + Assert.False(ReferenceEquals(f1.Contributors, f2.Contributors)); + Assert.False(ReferenceEquals(f1.AttributionText, f2.AttributionText)); + Assert.False(ReferenceEquals(f1.Annotations, f2.Annotations)); } /// @@ -196,7 +195,7 @@ public void SpdxFile_DeepCopy_FullyPopulatedFile_CreatesEqualButDistinctCopy() /// Verifies that matching entries are enhanced in place and unmatched entries from the /// source array are appended as new independent copies. /// - [TestMethod] + [Fact] public void SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly() { // Arrange: Create an array of SpdxFile objects with one file @@ -254,19 +253,19 @@ public void SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly() ]); // Assert: Verify the files array has been enhanced correctly - Assert.HasCount(2, files); - Assert.AreEqual("SPDXRef-File1", files[0].Id); - Assert.AreEqual("./file1.txt", files[0].FileName); - Assert.HasCount(2, files[0].Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, files[0].Checksums[0].Algorithm); - Assert.AreEqual("85ed0817af83a24ad8da68c2b5094de69833983c", files[0].Checksums[0].Value); - Assert.AreEqual(SpdxChecksumAlgorithm.Md5, files[0].Checksums[1].Algorithm); - Assert.AreEqual("624c1abb3664f4b35547e7c73864ad24", files[0].Checksums[1].Value); - Assert.AreEqual("File 1", files[0].Comment); - Assert.AreEqual("./file2.txt", files[1].FileName); - Assert.HasCount(1, files[1].Checksums); - Assert.AreEqual(SpdxChecksumAlgorithm.Sha1, files[1].Checksums[0].Algorithm); - Assert.AreEqual("c2b4e1c67a2d28fced849ee1bb76e7391b93f125", files[1].Checksums[0].Value); + Assert.Equal(2, files.Length); + Assert.Equal("SPDXRef-File1", files[0].Id); + Assert.Equal("./file1.txt", files[0].FileName); + Assert.Equal(2, files[0].Checksums.Length); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, files[0].Checksums[0].Algorithm); + Assert.Equal("85ed0817af83a24ad8da68c2b5094de69833983c", files[0].Checksums[0].Value); + Assert.Equal(SpdxChecksumAlgorithm.Md5, files[0].Checksums[1].Algorithm); + Assert.Equal("624c1abb3664f4b35547e7c73864ad24", files[0].Checksums[1].Value); + Assert.Equal("File 1", files[0].Comment); + Assert.Equal("./file2.txt", files[1].FileName); + Assert.Single(files[1].Checksums); + Assert.Equal(SpdxChecksumAlgorithm.Sha1, files[1].Checksums[0].Algorithm); + Assert.Equal("c2b4e1c67a2d28fced849ee1bb76e7391b93f125", files[1].Checksums[0].Value); } /// @@ -276,7 +275,7 @@ public void SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly() /// Verifies that Validate appends an issue message when the SPDX-ID does not conform /// to the required SPDXRef- prefix format. /// - [TestMethod] + [Fact] public void SpdxFile_Validate_InvalidFileId_ReportsIssue() { // Arrange: Create an SpdxFile instance with an invalid ID format @@ -299,7 +298,7 @@ public void SpdxFile_Validate_InvalidFileId_ReportsIssue() spdxFile.Validate(issues); // Assert: Verify that the validation fails and the error message includes the invalid ID. - Assert.Contains(issue => issue.Contains("File './file1.txt' Invalid SPDX Identifier Field"), issues); + Assert.Contains(issues, issue => issue.Contains("File './file1.txt' Invalid SPDX Identifier Field")); } /// @@ -309,7 +308,7 @@ public void SpdxFile_Validate_InvalidFileId_ReportsIssue() /// Verifies that Validate appends an issue message when FileName does not start with /// the required "./" prefix. /// - [TestMethod] + [Fact] public void SpdxFile_Validate_InvalidFileName_ReportsIssue() { // Arrange: Create an SpdxFile instance with a FileName that has no "./" prefix @@ -332,7 +331,7 @@ public void SpdxFile_Validate_InvalidFileName_ReportsIssue() spdxFile.Validate(issues); // Assert: Verify that the validation reports the invalid file name. - Assert.Contains(issue => issue.Contains("Invalid File Name Field"), issues); + Assert.Contains(issues, issue => issue.Contains("Invalid File Name Field")); } /// @@ -342,7 +341,7 @@ public void SpdxFile_Validate_InvalidFileName_ReportsIssue() /// Verifies that Validate appends an issue message when no SHA1 checksum is present /// in the Checksums array. /// - [TestMethod] + [Fact] public void SpdxFile_Validate_MissingSha1Checksum_ReportsIssue() { // Arrange: Create an SpdxFile instance with only an MD5 checksum (no SHA1) @@ -365,7 +364,7 @@ public void SpdxFile_Validate_MissingSha1Checksum_ReportsIssue() spdxFile.Validate(issues); // Assert: Verify that the validation reports the missing SHA1. - Assert.Contains(issue => issue.Contains("missing SHA1"), issues); + Assert.Contains(issues, issue => issue.Contains("missing SHA1")); } /// @@ -375,7 +374,7 @@ public void SpdxFile_Validate_MissingSha1Checksum_ReportsIssue() /// Verifies that a fully populated valid SpdxFile passes all validation checks /// without reporting any issues. /// - [TestMethod] + [Fact] public void SpdxFile_Validate_ValidFile_ReportsNoIssues() { // Arrange: Create a valid SpdxFile instance @@ -403,7 +402,7 @@ public void SpdxFile_Validate_ValidFile_ReportsNoIssues() spdxFile.Validate(issues); // Assert: Verify that the validation reports no issues. - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// @@ -413,25 +412,25 @@ public void SpdxFile_Validate_ValidFile_ReportsNoIssues() /// Verifies that all recognized file type strings, including case variants, map to the /// expected enum values. /// - [TestMethod] + [Fact] public void SpdxFileTypeExtensions_FromText_ValidInput_ParsesCorrectly() { // Arrange: (no external state needed) // Act / Assert: Verify all recognized file type strings map to expected enum values - Assert.AreEqual(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("SOURCE")); - Assert.AreEqual(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("source")); - Assert.AreEqual(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("Source")); - Assert.AreEqual(SpdxFileType.Binary, SpdxFileTypeExtensions.FromText("BINARY")); - Assert.AreEqual(SpdxFileType.Archive, SpdxFileTypeExtensions.FromText("ARCHIVE")); - Assert.AreEqual(SpdxFileType.Application, SpdxFileTypeExtensions.FromText("APPLICATION")); - Assert.AreEqual(SpdxFileType.Audio, SpdxFileTypeExtensions.FromText("AUDIO")); - Assert.AreEqual(SpdxFileType.Image, SpdxFileTypeExtensions.FromText("IMAGE")); - Assert.AreEqual(SpdxFileType.Text, SpdxFileTypeExtensions.FromText("TEXT")); - Assert.AreEqual(SpdxFileType.Video, SpdxFileTypeExtensions.FromText("VIDEO")); - Assert.AreEqual(SpdxFileType.Documentation, SpdxFileTypeExtensions.FromText("DOCUMENTATION")); - Assert.AreEqual(SpdxFileType.Spdx, SpdxFileTypeExtensions.FromText("SPDX")); - Assert.AreEqual(SpdxFileType.Other, SpdxFileTypeExtensions.FromText("OTHER")); + Assert.Equal(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("SOURCE")); + Assert.Equal(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("source")); + Assert.Equal(SpdxFileType.Source, SpdxFileTypeExtensions.FromText("Source")); + Assert.Equal(SpdxFileType.Binary, SpdxFileTypeExtensions.FromText("BINARY")); + Assert.Equal(SpdxFileType.Archive, SpdxFileTypeExtensions.FromText("ARCHIVE")); + Assert.Equal(SpdxFileType.Application, SpdxFileTypeExtensions.FromText("APPLICATION")); + Assert.Equal(SpdxFileType.Audio, SpdxFileTypeExtensions.FromText("AUDIO")); + Assert.Equal(SpdxFileType.Image, SpdxFileTypeExtensions.FromText("IMAGE")); + Assert.Equal(SpdxFileType.Text, SpdxFileTypeExtensions.FromText("TEXT")); + Assert.Equal(SpdxFileType.Video, SpdxFileTypeExtensions.FromText("VIDEO")); + Assert.Equal(SpdxFileType.Documentation, SpdxFileTypeExtensions.FromText("DOCUMENTATION")); + Assert.Equal(SpdxFileType.Spdx, SpdxFileTypeExtensions.FromText("SPDX")); + Assert.Equal(SpdxFileType.Other, SpdxFileTypeExtensions.FromText("OTHER")); } /// @@ -441,15 +440,15 @@ public void SpdxFileTypeExtensions_FromText_ValidInput_ParsesCorrectly() /// Verifies that FromText throws with a message /// identifying the unsupported value when given an unrecognized file type string. /// - [TestMethod] + [Fact] public void SpdxFileTypeExtensions_FromText_InvalidInput_ThrowsException() { // Arrange: An unrecognized file type string // Act / Assert: Verify that FromText throws with a message identifying the unsupported value var exception = - Assert.ThrowsExactly(() => SpdxFileTypeExtensions.FromText("invalid")); - Assert.AreEqual("Unsupported SPDX File Type 'invalid'", exception.Message); + Assert.Throws(() => SpdxFileTypeExtensions.FromText("invalid")); + Assert.Equal("Unsupported SPDX File Type 'invalid'", exception.Message); } /// @@ -459,23 +458,23 @@ public void SpdxFileTypeExtensions_FromText_InvalidInput_ThrowsException() /// Verifies that all known file type enum values map to their expected SPDX text /// representations. /// - [TestMethod] + [Fact] public void SpdxFileTypeExtensions_ToText_ValidEnum_FormatsCorrectly() { // Arrange: (no external state needed) // Act / Assert: Verify all known enum values map to expected text representations - Assert.AreEqual("SOURCE", SpdxFileType.Source.ToText()); - Assert.AreEqual("BINARY", SpdxFileType.Binary.ToText()); - Assert.AreEqual("ARCHIVE", SpdxFileType.Archive.ToText()); - Assert.AreEqual("APPLICATION", SpdxFileType.Application.ToText()); - Assert.AreEqual("AUDIO", SpdxFileType.Audio.ToText()); - Assert.AreEqual("IMAGE", SpdxFileType.Image.ToText()); - Assert.AreEqual("TEXT", SpdxFileType.Text.ToText()); - Assert.AreEqual("VIDEO", SpdxFileType.Video.ToText()); - Assert.AreEqual("DOCUMENTATION", SpdxFileType.Documentation.ToText()); - Assert.AreEqual("SPDX", SpdxFileType.Spdx.ToText()); - Assert.AreEqual("OTHER", SpdxFileType.Other.ToText()); + Assert.Equal("SOURCE", SpdxFileType.Source.ToText()); + Assert.Equal("BINARY", SpdxFileType.Binary.ToText()); + Assert.Equal("ARCHIVE", SpdxFileType.Archive.ToText()); + Assert.Equal("APPLICATION", SpdxFileType.Application.ToText()); + Assert.Equal("AUDIO", SpdxFileType.Audio.ToText()); + Assert.Equal("IMAGE", SpdxFileType.Image.ToText()); + Assert.Equal("TEXT", SpdxFileType.Text.ToText()); + Assert.Equal("VIDEO", SpdxFileType.Video.ToText()); + Assert.Equal("DOCUMENTATION", SpdxFileType.Documentation.ToText()); + Assert.Equal("SPDX", SpdxFileType.Spdx.ToText()); + Assert.Equal("OTHER", SpdxFileType.Other.ToText()); } /// @@ -485,13 +484,13 @@ public void SpdxFileTypeExtensions_ToText_ValidEnum_FormatsCorrectly() /// Verifies that ToText throws when given an /// unsupported file type enum value. /// - [TestMethod] + [Fact] public void SpdxFileTypeExtensions_ToText_InvalidEnum_ThrowsException() { // Arrange: An unsupported file type enum value // Act / Assert: Verify that ToText throws when given an unsupported enum value - var exception = Assert.ThrowsExactly(() => ((SpdxFileType)1000).ToText()); - Assert.AreEqual("Unsupported SPDX File Type '1000'", exception.Message); + var exception = Assert.Throws(() => ((SpdxFileType)1000).ToText()); + Assert.Equal("Unsupported SPDX File Type '1000'", exception.Message); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs index f0644f0..9d2a785 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxHelpersTests.cs @@ -23,13 +23,12 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// Tests for the class. /// -[TestClass] public class SpdxHelpersTests { /// /// Tests that returns true for null input. /// - [TestMethod] + [Fact] public void SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue() { // Arrange: null represents a not-set date-time field @@ -38,13 +37,13 @@ public void SpdxHelpers_IsValidSpdxDateTime_NullInput_ReturnsTrue() var result = SpdxHelpers.IsValidSpdxDateTime(null); // Assert - Assert.IsTrue(result); + Assert.True(result); } /// /// Tests that returns true for an empty string. /// - [TestMethod] + [Fact] public void SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue() { // Arrange: empty string represents a not-set date-time field @@ -53,13 +52,13 @@ public void SpdxHelpers_IsValidSpdxDateTime_EmptyInput_ReturnsTrue() var result = SpdxHelpers.IsValidSpdxDateTime(""); // Assert - Assert.IsTrue(result); + Assert.True(result); } /// /// Tests that returns true for a valid ISO 8601 UTC timestamp. /// - [TestMethod] + [Fact] public void SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue() { // Arrange @@ -69,13 +68,13 @@ public void SpdxHelpers_IsValidSpdxDateTime_ValidFormat_ReturnsTrue() var result = SpdxHelpers.IsValidSpdxDateTime(validDateTime); // Assert - Assert.IsTrue(result); + Assert.True(result); } /// /// Tests that returns false for an invalid format. /// - [TestMethod] + [Fact] public void SpdxHelpers_IsValidSpdxDateTime_InvalidFormat_ReturnsFalse() { // Arrange @@ -85,14 +84,14 @@ public void SpdxHelpers_IsValidSpdxDateTime_InvalidFormat_ReturnsFalse() var result = SpdxHelpers.IsValidSpdxDateTime(invalidDateTime); // Assert - Assert.IsFalse(result); + Assert.False(result); } /// /// Tests that returns the concrete value when given a /// mix of concrete value and NOASSERTION. /// - [TestMethod] + [Fact] public void SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue() { // Arrange @@ -103,13 +102,13 @@ public void SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsCo var result = SpdxHelpers.EnhanceString(noAssertion, concrete); // Assert - Assert.AreEqual(concrete, result); + Assert.Equal(concrete, result); } /// /// Tests that returns null when all inputs are null. /// - [TestMethod] + [Fact] public void SpdxHelpers_EnhanceString_NullInputs_ReturnsNull() { // Arrange: all candidates are null @@ -118,6 +117,6 @@ public void SpdxHelpers_EnhanceString_NullInputs_ReturnsNull() var result = SpdxHelpers.EnhanceString(null, null); // Assert - Assert.IsNull(result); + Assert.Null(result); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxLicenseElementTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxLicenseElementTests.cs index b569d90..22b71f6 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxLicenseElementTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxLicenseElementTests.cs @@ -29,7 +29,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// fields: ConcludedLicense, LicenseComments, CopyrightText, /// AttributionText, and Annotations. /// -[TestClass] public class SpdxLicenseElementTests { /// @@ -40,7 +39,7 @@ public class SpdxLicenseElementTests /// CopyrightText, and null LicenseComments are all replaced when the /// source carries concrete (rank-3) values. /// - [TestMethod] + [Fact] public void SpdxLicenseElement_Enhance_EmptyAndNullFields_ReplacedByConcreteValues() { // Arrange: Create a package with empty/null license-element fields @@ -65,9 +64,9 @@ public void SpdxLicenseElement_Enhance_EmptyAndNullFields_ReplacedByConcreteValu primary.Enhance(secondary); // Assert: Verify that empty/null fields were replaced with concrete values - Assert.AreEqual("MIT", primary.ConcludedLicense); - Assert.AreEqual("Copyright 2024 DEMA Consulting", primary.CopyrightText); - Assert.AreEqual("License determined from source headers", primary.LicenseComments); + Assert.Equal("MIT", primary.ConcludedLicense); + Assert.Equal("Copyright 2024 DEMA Consulting", primary.CopyrightText); + Assert.Equal("License determined from source headers", primary.LicenseComments); } /// @@ -78,7 +77,7 @@ public void SpdxLicenseElement_Enhance_EmptyAndNullFields_ReplacedByConcreteValu /// NOASSERTION (rank 2) are replaced when the source carries concrete (rank-3) /// values. LicenseComments set to NOASSERTION is similarly replaced. /// - [TestMethod] + [Fact] public void SpdxLicenseElement_Enhance_NoAssertionFields_ReplacedByConcreteValues() { // Arrange: Create a package with NOASSERTION license-element fields @@ -103,9 +102,9 @@ public void SpdxLicenseElement_Enhance_NoAssertionFields_ReplacedByConcreteValue primary.Enhance(secondary); // Assert: Verify that NOASSERTION fields were replaced with concrete values - Assert.AreEqual("Apache-2.0", primary.ConcludedLicense); - Assert.AreEqual("Copyright 2024 DEMA Consulting", primary.CopyrightText); - Assert.AreEqual("Apache license confirmed", primary.LicenseComments); + Assert.Equal("Apache-2.0", primary.ConcludedLicense); + Assert.Equal("Copyright 2024 DEMA Consulting", primary.CopyrightText); + Assert.Equal("Apache license confirmed", primary.LicenseComments); } /// @@ -116,7 +115,7 @@ public void SpdxLicenseElement_Enhance_NoAssertionFields_ReplacedByConcreteValue /// must not be overwritten by any secondary value regardless of the secondary's fitness /// level (null, empty, NOASSERTION, or another concrete value). /// - [TestMethod] + [Fact] public void SpdxLicenseElement_Enhance_ConcreteFields_NotReplacedBySecondaryValues() { // Arrange: Create a package with concrete license-element fields @@ -141,9 +140,9 @@ public void SpdxLicenseElement_Enhance_ConcreteFields_NotReplacedBySecondaryValu primary.Enhance(secondary); // Assert: Verify that concrete fields were not replaced - Assert.AreEqual("MIT", primary.ConcludedLicense); - Assert.AreEqual("Copyright 2024 DEMA Consulting", primary.CopyrightText); - Assert.AreEqual("MIT license confirmed", primary.LicenseComments); + Assert.Equal("MIT", primary.ConcludedLicense); + Assert.Equal("Copyright 2024 DEMA Consulting", primary.CopyrightText); + Assert.Equal("MIT license confirmed", primary.LicenseComments); } /// @@ -154,7 +153,7 @@ public void SpdxLicenseElement_Enhance_ConcreteFields_NotReplacedBySecondaryValu /// AttributionText array while duplicate entries are discarded so that each /// attribution notice appears exactly once in the merged result. /// - [TestMethod] + [Fact] public void SpdxLicenseElement_Enhance_AttributionText_MergedByDeduplication() { // Arrange: Create packages with overlapping and unique attribution texts @@ -175,7 +174,7 @@ public void SpdxLicenseElement_Enhance_AttributionText_MergedByDeduplication() primary.Enhance(secondary); // Assert: Verify that attribution texts were merged with deduplication - Assert.HasCount(3, primary.AttributionText); + Assert.Equal(3, primary.AttributionText.Length); Assert.Contains("Attribution A", primary.AttributionText); Assert.Contains("Attribution B", primary.AttributionText); Assert.Contains("Attribution C", primary.AttributionText); @@ -190,7 +189,7 @@ public void SpdxLicenseElement_Enhance_AttributionText_MergedByDeduplication() /// the primary are appended as new independent copies, leaving the total annotation /// count equal to the number of distinct annotations across both sources. /// - [TestMethod] + [Fact] public void SpdxLicenseElement_Enhance_Annotations_MergedByIdentityAndAppend() { // Arrange: Create packages where primary has one annotation and secondary adds a new one @@ -236,10 +235,10 @@ public void SpdxLicenseElement_Enhance_Annotations_MergedByIdentityAndAppend() primary.Enhance(secondary); // Assert: Verify that annotations were merged by identity-match and append - Assert.HasCount(2, primary.Annotations); - Assert.AreEqual("Tool: tool-a", primary.Annotations[0].Annotator); - Assert.AreEqual("Initial review", primary.Annotations[0].Comment); - Assert.AreEqual("Tool: tool-b", primary.Annotations[1].Annotator); - Assert.AreEqual("Additional review", primary.Annotations[1].Comment); + Assert.Equal(2, primary.Annotations.Length); + Assert.Equal("Tool: tool-a", primary.Annotations[0].Annotator); + Assert.Equal("Initial review", primary.Annotations[0].Comment); + Assert.Equal("Tool: tool-b", primary.Annotations[1].Annotator); + Assert.Equal("Additional review", primary.Annotations[1].Comment); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs index 1747929..e84c97b 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxModelTests.cs @@ -25,13 +25,12 @@ namespace DemaConsulting.SpdxModel.Tests; /// /// System-level integration tests for the SpdxModel library. /// -[TestClass] public class SpdxModelTests { /// /// Tests that an SPDX 2.2 JSON document can be read by the library. /// - [TestMethod] + [Fact] public void SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully() { // Arrange: Load the SPDX 2.2 JSON example from embedded resources @@ -42,16 +41,16 @@ public void SpdxModel_ReadSpdxJson_Spdx22Example_ParsesSuccessfully() var document = Spdx2JsonDeserializer.Deserialize(json); // Assert: Verify the document was read correctly - Assert.IsNotNull(document); - Assert.AreEqual("SPDX-Tools-v2.0", document.Name); - Assert.AreEqual("SPDX-2.2", document.Version); - Assert.AreEqual("CC0-1.0", document.DataLicense); + Assert.NotNull(document); + Assert.Equal("SPDX-Tools-v2.0", document.Name); + Assert.Equal("SPDX-2.2", document.Version); + Assert.Equal("CC0-1.0", document.DataLicense); } /// /// Tests that an SPDX 2.3 JSON document can be read by the library. /// - [TestMethod] + [Fact] public void SpdxModel_ReadSpdxJson_Spdx23Example_ParsesSuccessfully() { // Arrange: Load the SPDX 2.3 JSON example from embedded resources @@ -62,16 +61,16 @@ public void SpdxModel_ReadSpdxJson_Spdx23Example_ParsesSuccessfully() var document = Spdx2JsonDeserializer.Deserialize(json); // Assert: Verify the document was read correctly - Assert.IsNotNull(document); - Assert.AreEqual("SPDX-Tools-v2.0", document.Name); - Assert.AreEqual("SPDX-2.3", document.Version); - Assert.AreEqual("CC0-1.0", document.DataLicense); + Assert.NotNull(document); + Assert.Equal("SPDX-Tools-v2.0", document.Name); + Assert.Equal("SPDX-2.3", document.Version); + Assert.Equal("CC0-1.0", document.DataLicense); } /// /// Tests that an SPDX 2.2 document loaded by the library passes validation. /// - [TestMethod] + [Fact] public void SpdxModel_ReadSpdxJson_Spdx22Example_PassesValidation() { // Arrange: Load and deserialize the SPDX 2.2 JSON example @@ -84,13 +83,13 @@ public void SpdxModel_ReadSpdxJson_Spdx22Example_PassesValidation() document.Validate(issues); // Assert: Verify no validation issues were found - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// /// Tests that an SPDX 2.3 document loaded by the library passes validation. /// - [TestMethod] + [Fact] public void SpdxModel_ReadSpdxJson_Spdx23Example_PassesValidation() { // Arrange: Load and deserialize the SPDX 2.3 JSON example @@ -103,13 +102,13 @@ public void SpdxModel_ReadSpdxJson_Spdx23Example_PassesValidation() document.Validate(issues); // Assert: Verify no validation issues were found - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// /// Tests that root packages can be identified from a loaded SPDX document. /// - [TestMethod] + [Fact] public void SpdxModel_ReadSpdxJson_Spdx23Example_RootPackagesIdentified() { // Arrange: Load and deserialize the SPDX 2.3 JSON example @@ -121,15 +120,15 @@ public void SpdxModel_ReadSpdxJson_Spdx23Example_RootPackagesIdentified() var rootPackages = document.GetRootPackages(); // Assert: Verify that root packages were identified - Assert.IsNotNull(rootPackages); - Assert.IsTrue(rootPackages.Length > 0); - Assert.IsTrue(Array.Exists(rootPackages, p => p.Id == "SPDXRef-Package")); + Assert.NotNull(rootPackages); + Assert.True(rootPackages.Length > 0); + Assert.True(Array.Exists(rootPackages, p => p.Id == "SPDXRef-Package")); } /// /// Tests that a deep copy of a loaded SPDX document produces an equivalent document. /// - [TestMethod] + [Fact] public void SpdxModel_ReadSpdxJson_Spdx23Example_DeepCopyProducesEquivalentDocument() { // Arrange: Load and deserialize the SPDX 2.3 JSON example @@ -141,15 +140,15 @@ public void SpdxModel_ReadSpdxJson_Spdx23Example_DeepCopyProducesEquivalentDocum var copy = original.DeepCopy(); // Assert: Verify the copy is equivalent but a distinct instance - Assert.IsNotNull(copy); - Assert.AreNotSame(original, copy); - Assert.IsTrue(SpdxDocument.Same.Equals(original, copy)); + Assert.NotNull(copy); + Assert.NotSame(original, copy); + Assert.True(SpdxDocument.Same.Equals(original, copy)); } /// /// Tests that an SPDX document can be written and read back in a complete round trip. /// - [TestMethod] + [Fact] public void SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds() { // Arrange: Load and deserialize the SPDX 2.3 JSON example @@ -162,39 +161,31 @@ public void SpdxModel_WriteReadSpdxJson_Spdx23Example_RoundTripSucceeds() var roundTripped = Spdx2JsonDeserializer.Deserialize(serialized); // Assert: Verify the round-tripped document matches the original and passes validation - Assert.IsNotNull(roundTripped); - Assert.AreEqual(original.Name, roundTripped.Name); - Assert.AreEqual(original.Version, roundTripped.Version); + Assert.NotNull(roundTripped); + Assert.Equal(original.Name, roundTripped.Name); + Assert.Equal(original.Version, roundTripped.Version); var issues = new List(); roundTripped.Validate(issues); - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// /// Tests that malformed JSON throws a JsonException when deserialized. /// - [TestMethod] + [Fact] public void SpdxModel_Deserialize_MalformedJson_ThrowsJsonException() { // Arrange: Prepare malformed JSON text const string malformedJson = "{ this is not valid json }"; // Act / Assert: malformed JSON throws a JsonException - try - { - Spdx2JsonDeserializer.Deserialize(malformedJson); - Assert.Fail("Expected JsonException was not thrown."); - } - catch (System.Text.Json.JsonException) - { - // Expected — pass - } + Assert.ThrowsAny(() => Spdx2JsonDeserializer.Deserialize(malformedJson)); } /// /// Tests that an invalid SPDX document reports specific validation issues. /// - [TestMethod] + [Fact] public void SpdxModel_Validate_InvalidDocument_ReportsIssues() { // Arrange: Create a deliberately incomplete SPDX document @@ -212,8 +203,8 @@ public void SpdxModel_Validate_InvalidDocument_ReportsIssues() document.Validate(issues); // Assert: Verify that specific validation issues are reported - Assert.IsTrue(issues.Count > 0, "Expected validation issues but none were reported."); - Assert.IsTrue(issues.Exists(i => i.Contains("SPDX Version")), + Assert.True(issues.Count > 0, "Expected validation issues but none were reported."); + Assert.True(issues.Exists(i => i.Contains("SPDX Version")), "Expected a SPDX Version validation issue."); } @@ -221,7 +212,7 @@ public void SpdxModel_Validate_InvalidDocument_ReportsIssues() /// Tests that required fields on SPDX data model types are non-nullable, /// and optional fields are nullable. /// - [TestMethod] + [Fact] public void SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNullable() { // Arrange: Create a default instance of key data model types @@ -233,25 +224,25 @@ public void SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNulla // Act / Assert: default-constructed instances have the expected field nullability // Assert: Required fields are non-nullable (strings default to empty, not null) - Assert.IsNotNull(document.Id); - Assert.IsNotNull(document.Name); - Assert.IsNotNull(document.Version); - Assert.IsNotNull(document.DataLicense); - Assert.IsNotNull(document.DocumentNamespace); - Assert.IsNotNull(package.Id); - Assert.IsNotNull(package.Name); - Assert.IsNotNull(package.DownloadLocation); - Assert.IsNotNull(file.Id); - Assert.IsNotNull(file.FileName); - Assert.IsNotNull(relationship.Id); - Assert.IsNotNull(relationship.RelatedSpdxElement); + Assert.NotNull(document.Id); + Assert.NotNull(document.Name); + Assert.NotNull(document.Version); + Assert.NotNull(document.DataLicense); + Assert.NotNull(document.DocumentNamespace); + Assert.NotNull(package.Id); + Assert.NotNull(package.Name); + Assert.NotNull(package.DownloadLocation); + Assert.NotNull(file.Id); + Assert.NotNull(file.FileName); + Assert.NotNull(relationship.Id); + Assert.NotNull(relationship.RelatedSpdxElement); // Assert: Optional fields are nullable - Assert.IsNull(document.Comment); - Assert.IsNull(package.Comment); - Assert.IsNull(package.Version); - Assert.IsNull(file.Comment); - Assert.IsNull(relationship.Comment); + Assert.Null(document.Comment); + Assert.Null(package.Comment); + Assert.Null(package.Version); + Assert.Null(file.Comment); + Assert.Null(relationship.Comment); } /// @@ -262,7 +253,7 @@ public void SpdxModel_FieldOptionality_RequiredFieldsNotNull_OptionalFieldsNulla /// a real SPDX document is validated, satisfying the system-level observability requirement for /// helper utilities. /// - [TestMethod] + [Fact] public void SpdxModel_Helpers_DateTimeValidation_IsObservableThroughDocumentModel() { // Arrange: Load and deserialize a real SPDX 2.3 document @@ -276,8 +267,8 @@ public void SpdxModel_Helpers_DateTimeValidation_IsObservableThroughDocumentMode document.Validate(issues); // Assert: The document is valid and the Created timestamp is a non-empty, well-formed value - Assert.IsEmpty(issues); - Assert.IsFalse(string.IsNullOrEmpty(document.CreationInformation.Created), + Assert.Empty(issues); + Assert.False(string.IsNullOrEmpty(document.CreationInformation.Created), "Expected the Created field to be non-empty after deserialization."); } @@ -289,7 +280,7 @@ public void SpdxModel_Helpers_DateTimeValidation_IsObservableThroughDocumentMode /// the public transform API, and verify the relationship is present in the document model. /// This satisfies the system-level observability requirement for the transform subsystem. /// - [TestMethod] + [Fact] public void SpdxModel_Transform_AddRelationship_IsObservableThroughDocumentModel() { // Arrange: Load and deserialize a real SPDX 2.3 document @@ -308,9 +299,8 @@ public void SpdxModel_Transform_AddRelationship_IsObservableThroughDocumentModel DemaConsulting.SpdxModel.Transform.SpdxRelationships.Add(document, newRelationship); // Assert: The relationship is now present in the document - Assert.AreEqual(initialCount + 1, document.Relationships.Length, - "Expected document.Relationships to grow by one after Add."); - Assert.IsTrue( + Assert.Equal(initialCount + 1, document.Relationships.Length); + Assert.True( Array.Exists(document.Relationships, r => r.Id == "SPDXRef-DOCUMENT" && r.RelationshipType == SpdxRelationshipType.DependsOn && diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs index 7f18718..81d3fc2 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxPackageTests.cs @@ -29,7 +29,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// validation including NTIA minimum-elements checks. Each test exercises a single scenario or /// boundary condition in isolation with no shared state between tests. /// -[TestClass] public class SpdxPackageTests { /// @@ -40,7 +39,7 @@ public class SpdxPackageTests /// regardless of differing Id values, that packages with different names or versions are /// distinct, and that null arguments are handled correctly. /// - [TestMethod] + [Fact] public void SpdxPackage_SameComparer_ComparesCorrectly() { // Arrange: Create several SpdxPackage instances with different IDs, names, and versions @@ -64,25 +63,25 @@ public void SpdxPackage_SameComparer_ComparesCorrectly() }; // Act / Assert: Verify packages compare to themselves - Assert.IsTrue(SpdxPackage.Same.Equals(p1, p1)); - Assert.IsTrue(SpdxPackage.Same.Equals(p2, p2)); - Assert.IsTrue(SpdxPackage.Same.Equals(p3, p3)); + Assert.True(SpdxPackage.Same.Equals(p1, p1)); + Assert.True(SpdxPackage.Same.Equals(p2, p2)); + Assert.True(SpdxPackage.Same.Equals(p3, p3)); // Assert: Verify packages compare correctly - Assert.IsTrue(SpdxPackage.Same.Equals(p1, p2)); - Assert.IsTrue(SpdxPackage.Same.Equals(p2, p1)); - Assert.IsFalse(SpdxPackage.Same.Equals(p1, p3)); - Assert.IsFalse(SpdxPackage.Same.Equals(p3, p1)); - Assert.IsFalse(SpdxPackage.Same.Equals(p2, p3)); - Assert.IsFalse(SpdxPackage.Same.Equals(p3, p2)); + Assert.True(SpdxPackage.Same.Equals(p1, p2)); + Assert.True(SpdxPackage.Same.Equals(p2, p1)); + Assert.False(SpdxPackage.Same.Equals(p1, p3)); + Assert.False(SpdxPackage.Same.Equals(p3, p1)); + Assert.False(SpdxPackage.Same.Equals(p2, p3)); + Assert.False(SpdxPackage.Same.Equals(p3, p2)); // Assert: Verify same packages have identical hashes - Assert.AreEqual(SpdxPackage.Same.GetHashCode(p1), SpdxPackage.Same.GetHashCode(p2)); + Assert.Equal(SpdxPackage.Same.GetHashCode(p1), SpdxPackage.Same.GetHashCode(p2)); // Assert: Verify null handling - Assert.IsFalse(SpdxPackage.Same.Equals(null!, p1)); - Assert.IsFalse(SpdxPackage.Same.Equals(p1, null!)); - Assert.IsTrue(SpdxPackage.Same.Equals(null!, null!)); + Assert.False(SpdxPackage.Same.Equals(null!, p1)); + Assert.False(SpdxPackage.Same.Equals(p1, null!)); + Assert.True(SpdxPackage.Same.Equals(null!, null!)); } /// @@ -92,7 +91,7 @@ public void SpdxPackage_SameComparer_ComparesCorrectly() /// Verifies that the returned instance has equal field values, that all array and nested object /// fields are new independent instances, and that mutating the copy does not affect the original. /// - [TestMethod] + [Fact] public void SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance() { // Arrange: Create a SpdxPackage with various properties @@ -138,28 +137,28 @@ public void SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance() var p2 = p1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(p1, p2, SpdxPackage.Same); - Assert.AreEqual(p1.Id, p2.Id); - Assert.AreEqual(p1.Name, p2.Name); - Assert.AreEqual(p1.Version, p2.Version); - CollectionAssert.AreEquivalent(p1.HasFiles, p2.HasFiles); - CollectionAssert.AreEquivalent(p1.Checksums, p2.Checksums, SpdxChecksum.Same); - CollectionAssert.AreEquivalent(p1.LicenseInfoFromFiles, p2.LicenseInfoFromFiles); - CollectionAssert.AreEquivalent(p1.ExternalReferences, p2.ExternalReferences, SpdxExternalReference.Same); - CollectionAssert.AreEquivalent(p1.AttributionText, p2.AttributionText); - CollectionAssert.AreEquivalent(p1.Annotations, p2.Annotations, SpdxAnnotation.Same); - Assert.IsNotNull(p2.VerificationCode); - Assert.AreEqual(p1.VerificationCode!.Value, p2.VerificationCode.Value); + Assert.Equal(p1, p2, SpdxPackage.Same); + Assert.Equal(p1.Id, p2.Id); + Assert.Equal(p1.Name, p2.Name); + Assert.Equal(p1.Version, p2.Version); + Assert.Equal(p1.HasFiles, p2.HasFiles); + SpdxTestHelpers.AssertEquivalent(p1.Checksums, p2.Checksums, SpdxChecksum.Same); + SpdxTestHelpers.AssertEquivalent(p1.LicenseInfoFromFiles, p2.LicenseInfoFromFiles, StringComparer.Ordinal); + SpdxTestHelpers.AssertEquivalent(p1.ExternalReferences, p2.ExternalReferences, SpdxExternalReference.Same); + Assert.Equal(p1.AttributionText, p2.AttributionText); + SpdxTestHelpers.AssertEquivalent(p1.Annotations, p2.Annotations, SpdxAnnotation.Same); + Assert.NotNull(p2.VerificationCode); + Assert.Equal(p1.VerificationCode!.Value, p2.VerificationCode.Value); // Assert: Verify deep-copy has distinct instances - Assert.IsFalse(ReferenceEquals(p1, p2)); - Assert.IsFalse(ReferenceEquals(p1.HasFiles, p2.HasFiles)); - Assert.IsFalse(ReferenceEquals(p1.Checksums, p2.Checksums)); - Assert.IsFalse(ReferenceEquals(p1.LicenseInfoFromFiles, p2.LicenseInfoFromFiles)); - Assert.IsFalse(ReferenceEquals(p1.ExternalReferences, p2.ExternalReferences)); - Assert.IsFalse(ReferenceEquals(p1.AttributionText, p2.AttributionText)); - Assert.IsFalse(ReferenceEquals(p1.Annotations, p2.Annotations)); - Assert.IsFalse(ReferenceEquals(p1.VerificationCode, p2.VerificationCode)); + Assert.False(ReferenceEquals(p1, p2)); + Assert.False(ReferenceEquals(p1.HasFiles, p2.HasFiles)); + Assert.False(ReferenceEquals(p1.Checksums, p2.Checksums)); + Assert.False(ReferenceEquals(p1.LicenseInfoFromFiles, p2.LicenseInfoFromFiles)); + Assert.False(ReferenceEquals(p1.ExternalReferences, p2.ExternalReferences)); + Assert.False(ReferenceEquals(p1.AttributionText, p2.AttributionText)); + Assert.False(ReferenceEquals(p1.Annotations, p2.Annotations)); + Assert.False(ReferenceEquals(p1.VerificationCode, p2.VerificationCode)); } /// @@ -167,10 +166,11 @@ public void SpdxPackage_DeepCopy_CreatesEqualButDistinctInstance() /// packages. /// /// - /// Verifies that a matching package (same name and version) is enhanced in place and that a non-matching - /// package from the source array is deep-copied and appended, resulting in an array of length two. + /// Verifies that a matching package (same name and version) is enhanced in place — including populating a null + /// FilesAnalyzed field from the source — and that a non-matching package from the source array is + /// deep-copied and appended, resulting in an array of length two. /// - [TestMethod] + [Fact] public void SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly() { // Arrange: Set up the initial packages and the packages to enhance with. @@ -192,7 +192,8 @@ public void SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly() { Id = "SPDXRef-Package-SpdxModel", Name = "DemaConsulting.SpdxModel", - Version = "0.0.0" + Version = "0.0.0", + FilesAnalyzed = true }, new SpdxPackage { @@ -203,13 +204,14 @@ public void SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly() ]); // Assert: Verify the resulting packages are as expected. - Assert.HasCount(2, packages); - Assert.AreEqual("SPDXRef-Package1", packages[0].Id); - Assert.AreEqual("DemaConsulting.SpdxModel", packages[0].Name); - Assert.AreEqual("0.0.0", packages[0].Version); - Assert.AreEqual("SPDXRef-Package1", packages[1].Id); - Assert.AreEqual("SomePackage", packages[1].Name); - Assert.AreEqual("1.2.3", packages[1].Version); + Assert.Equal(2, packages.Length); + Assert.Equal("SPDXRef-Package1", packages[0].Id); + Assert.Equal("DemaConsulting.SpdxModel", packages[0].Name); + Assert.Equal("0.0.0", packages[0].Version); + Assert.True(packages[0].FilesAnalyzed); + Assert.Equal("SPDXRef-Package1", packages[1].Id); + Assert.Equal("SomePackage", packages[1].Name); + Assert.Equal("1.2.3", packages[1].Version); } /// @@ -219,7 +221,7 @@ public void SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly() /// Exercises the happy-path: a fully populated package with a valid SPDX ID, non-empty name, download /// location, and a conforming supplier string passes all validation checks including NTIA minimum elements. /// - [TestMethod] + [Fact] public void SpdxPackage_Validate_Success() { // Arrange: Construct a valid SpdxPackage @@ -237,7 +239,7 @@ public void SpdxPackage_Validate_Success() package.Validate(issues, null, true); // Assert: Verify that the validation reports no issues. - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// @@ -247,8 +249,8 @@ public void SpdxPackage_Validate_Success() /// Verifies the boundary condition where Name is empty: validation must report the /// "Invalid Package Name Field - Empty" issue. /// - [TestMethod] - public void SpdxPackage_Validate_MissingPackageName() + [Fact] + public void SpdxPackage_Validate_MissingPackageName_ReportsIssue() { // Arrange: Construct a bad SpdxPackage var package = new SpdxPackage @@ -275,8 +277,8 @@ public void SpdxPackage_Validate_MissingPackageName() /// Verifies that an Id not starting with SPDXRef- is flagged as an invalid SPDX /// identifier field. /// - [TestMethod] - public void SpdxPackage_Validate_InvalidPackageId() + [Fact] + public void SpdxPackage_Validate_InvalidPackageId_ReportsIssue() { // Arrange: Construct a bad SpdxPackage var package = new SpdxPackage @@ -303,8 +305,8 @@ public void SpdxPackage_Validate_InvalidPackageId() /// Verifies that an empty DownloadLocation causes the "Invalid Package Download Location Field - Empty" /// issue to be reported. /// - [TestMethod] - public void SpdxPackage_Validate_MissingDownload() + [Fact] + public void SpdxPackage_Validate_MissingDownload_ReportsIssue() { // Arrange: Construct a bad SpdxPackage var package = new SpdxPackage @@ -331,8 +333,8 @@ public void SpdxPackage_Validate_MissingDownload() /// Verifies that a supplier value that does not start with Person:, Organization:, or equal /// NOASSERTION is flagged as an invalid supplier field. /// - [TestMethod] - public void SpdxPackage_Validate_InvalidSupplier() + [Fact] + public void SpdxPackage_Validate_InvalidSupplier_ReportsIssue() { // Arrange: Construct a package with invalid supplier format var package = new SpdxPackage @@ -359,8 +361,8 @@ public void SpdxPackage_Validate_InvalidSupplier() /// Verifies that an originator value that does not start with Person:, Organization:, or equal /// NOASSERTION is flagged as an invalid originator field. /// - [TestMethod] - public void SpdxPackage_Validate_InvalidOriginator() + [Fact] + public void SpdxPackage_Validate_InvalidOriginator_ReportsIssue() { // Arrange: Construct a package with invalid originator format var package = new SpdxPackage @@ -388,8 +390,8 @@ public void SpdxPackage_Validate_InvalidOriginator() /// Verifies that a ReleaseDate that does not conform to the SPDX date-time format causes the /// "Invalid Release Date Field" issue to be reported. /// - [TestMethod] - public void SpdxPackage_Validate_InvalidReleaseDate() + [Fact] + public void SpdxPackage_Validate_InvalidReleaseDate_ReportsIssue() { // Arrange: Construct a package with invalid release date format var package = new SpdxPackage @@ -417,8 +419,8 @@ public void SpdxPackage_Validate_InvalidReleaseDate() /// Verifies that a BuiltDate that does not conform to the SPDX date-time format causes the /// "Invalid Built Date Field" issue to be reported. /// - [TestMethod] - public void SpdxPackage_Validate_InvalidBuiltDate() + [Fact] + public void SpdxPackage_Validate_InvalidBuiltDate_ReportsIssue() { // Arrange: Construct a package with invalid built date format var package = new SpdxPackage @@ -446,8 +448,8 @@ public void SpdxPackage_Validate_InvalidBuiltDate() /// Verifies that a ValidUntilDate that does not conform to the SPDX date-time format causes the /// "Invalid Valid Until Date Field" issue to be reported. /// - [TestMethod] - public void SpdxPackage_Validate_InvalidValidUntilDate() + [Fact] + public void SpdxPackage_Validate_InvalidValidUntilDate_ReportsIssue() { // Arrange: Construct a bad SpdxPackage var package = new SpdxPackage @@ -475,8 +477,8 @@ public void SpdxPackage_Validate_InvalidValidUntilDate() /// Verifies that an annotation with an empty Annotator field causes the /// "Invalid Annotator Field - Empty" issue to be reported with the correct package prefix. /// - [TestMethod] - public void SpdxPackage_Validate_InvalidAnnotation() + [Fact] + public void SpdxPackage_Validate_InvalidAnnotation_ReportsIssue() { // Arrange: Construct a package with an invalid annotation var package = new SpdxPackage @@ -513,7 +515,7 @@ public void SpdxPackage_Validate_InvalidAnnotation() /// Verifies that when a document is provided and HasFiles references a file ID that does not /// exist in doc.Files, the "HasFiles references missing files" issue is reported. /// - [TestMethod] + [Fact] public void SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue() { // Arrange: Create a package that references a file that does not exist in the document @@ -536,7 +538,7 @@ public void SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue() package.Validate(issues, doc); // Assert: Verify the HasFiles reference issue is reported - Assert.Contains(issue => issue.Contains("Package 'DemaConsulting.SpdxModel' HasFiles references missing files"), issues); + Assert.Contains(issues, issue => issue.Contains("Package 'DemaConsulting.SpdxModel' HasFiles references missing files")); } /// @@ -546,7 +548,7 @@ public void SpdxPackage_Validate_HasFilesReferencesMissingFile_ReportsIssue() /// Verifies that when NTIA validation is enabled, a package without a supplier causes the /// "NTIA: Package Missing Supplier" issue to be reported. /// - [TestMethod] + [Fact] public void SpdxPackage_ValidateNtia_MissingSupplier_ReportsIssue() { // Arrange: Create a package with no supplier @@ -563,7 +565,7 @@ public void SpdxPackage_ValidateNtia_MissingSupplier_ReportsIssue() package.Validate(issues, null, ntia: true); // Assert: Verify the missing supplier issue is reported - Assert.Contains(issue => issue.Contains("NTIA: Package 'DemaConsulting.SpdxModel' Missing Supplier"), issues); + Assert.Contains(issues, issue => issue.Contains("NTIA: Package 'DemaConsulting.SpdxModel' Missing Supplier")); } /// @@ -573,7 +575,7 @@ public void SpdxPackage_ValidateNtia_MissingSupplier_ReportsIssue() /// Verifies that when NTIA validation is enabled, a package without a version string causes the /// "NTIA: Package Missing Version" issue to be reported. /// - [TestMethod] + [Fact] public void SpdxPackage_ValidateNtia_MissingVersion_ReportsIssue() { // Arrange: Create a package with no version @@ -590,6 +592,6 @@ public void SpdxPackage_ValidateNtia_MissingVersion_ReportsIssue() package.Validate(issues, null, ntia: true); // Assert: Verify the missing version issue is reported - Assert.Contains(issue => issue.Contains("NTIA: Package 'DemaConsulting.SpdxModel' Missing Version"), issues); + Assert.Contains(issues, issue => issue.Contains("NTIA: Package 'DemaConsulting.SpdxModel' Missing Version")); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs index 4d542bb..3e7b0dd 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxPackageVerificationCodeTests.cs @@ -28,7 +28,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// deep-copy independence, field merging via , /// and SHA1 hex digest validation via . /// -[TestClass] public class SpdxPackageVerificationCodeTests { /// @@ -38,7 +37,7 @@ public class SpdxPackageVerificationCodeTests /// Verifies that two codes with the same Value but different ExcludedFiles are considered equal, /// while codes with different values are considered distinct. Also validates null handling and hash consistency. /// - [TestMethod] + [Fact] public void SpdxPackageVerificationCode_SameComparer_SameValueDifferentExcludedFiles_ReturnsEqual() { // Arrange: Create three package verification codes with different properties @@ -57,26 +56,26 @@ public void SpdxPackageVerificationCode_SameComparer_SameValueDifferentExcludedF }; // Act / Assert: Verify package-verification-codes compare to themselves - Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(v1, v1)); - Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(v2, v2)); - Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(v3, v3)); + Assert.True(SpdxPackageVerificationCode.Same.Equals(v1, v1)); + Assert.True(SpdxPackageVerificationCode.Same.Equals(v2, v2)); + Assert.True(SpdxPackageVerificationCode.Same.Equals(v3, v3)); // Assert: Verify package-verification-codes compare correctly - Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(v1, v2)); - Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(v2, v1)); - Assert.IsFalse(SpdxPackageVerificationCode.Same.Equals(v1, v3)); - Assert.IsFalse(SpdxPackageVerificationCode.Same.Equals(v3, v1)); - Assert.IsFalse(SpdxPackageVerificationCode.Same.Equals(v2, v3)); - Assert.IsFalse(SpdxPackageVerificationCode.Same.Equals(v3, v2)); + Assert.True(SpdxPackageVerificationCode.Same.Equals(v1, v2)); + Assert.True(SpdxPackageVerificationCode.Same.Equals(v2, v1)); + Assert.False(SpdxPackageVerificationCode.Same.Equals(v1, v3)); + Assert.False(SpdxPackageVerificationCode.Same.Equals(v3, v1)); + Assert.False(SpdxPackageVerificationCode.Same.Equals(v2, v3)); + Assert.False(SpdxPackageVerificationCode.Same.Equals(v3, v2)); // Assert: Verify same package-verification-codes have identical hashes - Assert.AreEqual(SpdxPackageVerificationCode.Same.GetHashCode(v1), + Assert.Equal(SpdxPackageVerificationCode.Same.GetHashCode(v1), SpdxPackageVerificationCode.Same.GetHashCode(v2)); // Assert: Verify null handling - Assert.IsTrue(SpdxPackageVerificationCode.Same.Equals(null!, null!)); - Assert.IsFalse(SpdxPackageVerificationCode.Same.Equals(null!, v1)); - Assert.IsFalse(SpdxPackageVerificationCode.Same.Equals(v1, null!)); + Assert.True(SpdxPackageVerificationCode.Same.Equals(null!, null!)); + Assert.False(SpdxPackageVerificationCode.Same.Equals(null!, v1)); + Assert.False(SpdxPackageVerificationCode.Same.Equals(v1, null!)); } /// @@ -86,7 +85,7 @@ public void SpdxPackageVerificationCode_SameComparer_SameValueDifferentExcludedF /// Verifies that the deep copy has equal field values and that both the code object and its /// ExcludedFiles array are distinct instances that can be mutated independently. /// - [TestMethod] + [Fact] public void SpdxPackageVerificationCode_DeepCopy_FullyPopulatedCode_CreatesEqualButDistinctCopy() { // Arrange: Create a package verification code with excluded files and a value @@ -100,13 +99,13 @@ public void SpdxPackageVerificationCode_DeepCopy_FullyPopulatedCode_CreatesEqual var v2 = v1.DeepCopy(); // Assert: Verify deep-copy is equal to original - Assert.AreEqual(v1, v2, SpdxPackageVerificationCode.Same); - CollectionAssert.AreEqual(v1.ExcludedFiles, v2.ExcludedFiles); - Assert.AreEqual(v1.Value, v2.Value); + Assert.Equal(v1, v2, SpdxPackageVerificationCode.Same); + Assert.Equal(v1.ExcludedFiles, v2.ExcludedFiles); + Assert.Equal(v1.Value, v2.Value); // Assert: Verify deep-copy has distinct instances - Assert.IsFalse(ReferenceEquals(v1, v2)); - Assert.IsFalse(ReferenceEquals(v1.ExcludedFiles, v2.ExcludedFiles)); + Assert.False(ReferenceEquals(v1, v2)); + Assert.False(ReferenceEquals(v1.ExcludedFiles, v2.ExcludedFiles)); } /// @@ -116,7 +115,7 @@ public void SpdxPackageVerificationCode_DeepCopy_FullyPopulatedCode_CreatesEqual /// Verifies that enhancing a code with excluded files merges them by deduplication, and that an /// existing non-empty Value is not overwritten by the source. /// - [TestMethod] + [Fact] public void SpdxPackageVerificationCode_Enhance_MissingFields_MergesCorrectly() { // Arrange: Create a package verification code with a value @@ -134,9 +133,9 @@ public void SpdxPackageVerificationCode_Enhance_MissingFields_MergesCorrectly() }); // Assert: Verify the excluded files and value are updated correctly - Assert.HasCount(1, info.ExcludedFiles); - Assert.AreEqual("./package.spdx", info.ExcludedFiles[0]); - Assert.AreEqual("d6a770ba38583ed4bb4525bd96e50461655d2758", info.Value); + Assert.Single(info.ExcludedFiles); + Assert.Equal("./package.spdx", info.ExcludedFiles[0]); + Assert.Equal("d6a770ba38583ed4bb4525bd96e50461655d2758", info.Value); } /// @@ -146,7 +145,7 @@ public void SpdxPackageVerificationCode_Enhance_MissingFields_MergesCorrectly() /// Exercises the short-string boundary condition: a value shorter than 40 characters is not a valid /// SHA1 hex digest and must produce a validation issue. /// - [TestMethod] + [Fact] public void SpdxPackageVerificationCode_Validate_InvalidValue_ReportsIssue() { // Arrange: Create a bad package verification code @@ -160,7 +159,7 @@ public void SpdxPackageVerificationCode_Validate_InvalidValue_ReportsIssue() info.Validate("Test", issues); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Package 'Test' Invalid Package Verification Code Value 'BadValue'"), issues); + Assert.Contains(issues, issue => issue.Contains("Package 'Test' Invalid Package Verification Code Value 'BadValue'")); } /// @@ -170,7 +169,7 @@ public void SpdxPackageVerificationCode_Validate_InvalidValue_ReportsIssue() /// Verifies the happy-path: a well-formed 40-character lowercase hex SHA1 digest passes all validation /// checks without reporting any issues. /// - [TestMethod] + [Fact] public void SpdxPackageVerificationCode_Validate_ValidValue_ReportsNoIssues() { // Arrange: Create a package verification code with a valid SHA1 value @@ -184,7 +183,7 @@ public void SpdxPackageVerificationCode_Validate_ValidValue_ReportsNoIssues() info.Validate("Test", issues); // Assert: Verify that the validation reports no issues - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// @@ -194,7 +193,7 @@ public void SpdxPackageVerificationCode_Validate_ValidValue_ReportsNoIssues() /// Exercises the non-hex boundary condition: a string that is exactly 40 characters long but contains /// characters outside the hexadecimal alphabet (0–9, a–f, A–F) must produce a validation issue. /// - [TestMethod] + [Fact] public void SpdxPackageVerificationCode_Validate_NonHexValue_ReportsIssue() { // Arrange: Create a package verification code with 40 chars but invalid hex @@ -208,6 +207,6 @@ public void SpdxPackageVerificationCode_Validate_NonHexValue_ReportsIssue() info.Validate("Test", issues); // Assert: Verify that the validation fails - Assert.Contains(issue => issue.Contains("Package 'Test' Invalid Package Verification Code Value"), issues); + Assert.Contains(issues, issue => issue.Contains("Package 'Test' Invalid Package Verification Code Value")); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs index 4b2f507..91941be 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxRelationshipTests.cs @@ -31,7 +31,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// . Each test exercises a single scenario or /// boundary condition in isolation with no shared state between tests. /// -[TestClass] public class SpdxRelationshipTests { /// @@ -41,7 +40,7 @@ public class SpdxRelationshipTests /// Verifies that two relationships with the same Id, RelationshipType, and /// RelatedSpdxElement are considered equal even when Comment differs. /// - [TestMethod] + [Fact] public void SpdxRelationship_SameComparer_MatchingRelationships_ReturnsTrue() { // Arrange: Create two relationships that differ only in Comment @@ -63,8 +62,8 @@ public void SpdxRelationship_SameComparer_MatchingRelationships_ReturnsTrue() var result = SpdxRelationship.Same.Equals(r1, r2); // Assert: Verify the relationships are considered equal - Assert.IsTrue(result); - Assert.IsTrue(SpdxRelationship.Same.Equals(r2, r1)); + Assert.True(result); + Assert.True(SpdxRelationship.Same.Equals(r2, r1)); } /// @@ -74,7 +73,7 @@ public void SpdxRelationship_SameComparer_MatchingRelationships_ReturnsTrue() /// Verifies that two relationships with different Id, RelationshipType, or /// RelatedSpdxElement values are considered distinct. /// - [TestMethod] + [Fact] public void SpdxRelationship_SameComparer_DifferentRelationships_ReturnsFalse() { // Arrange: Create two relationships with different key fields @@ -95,8 +94,8 @@ public void SpdxRelationship_SameComparer_DifferentRelationships_ReturnsFalse() var result = SpdxRelationship.Same.Equals(r1, r3); // Assert: Verify the relationships are considered distinct - Assert.IsFalse(result); - Assert.IsFalse(SpdxRelationship.Same.Equals(r3, r1)); + Assert.False(result); + Assert.False(SpdxRelationship.Same.Equals(r3, r1)); } /// @@ -106,7 +105,7 @@ public void SpdxRelationship_SameComparer_DifferentRelationships_ReturnsFalse() /// Verifies that two relationships considered equal by produce /// identical hash codes, satisfying the hash/equality contract. /// - [TestMethod] + [Fact] public void SpdxRelationship_SameComparer_MatchingRelationships_ReturnsSameHashCode() { // Arrange: Create two relationships that differ only in Comment @@ -129,7 +128,7 @@ public void SpdxRelationship_SameComparer_MatchingRelationships_ReturnsSameHashC var hash2 = SpdxRelationship.Same.GetHashCode(r2); // Assert: Verify the hash codes are identical - Assert.AreEqual(hash1, hash2); + Assert.Equal(hash1, hash2); } /// @@ -139,7 +138,7 @@ public void SpdxRelationship_SameComparer_MatchingRelationships_ReturnsSameHashC /// Verifies that two relationships with the same Id and RelatedSpdxElement are considered equal /// even when RelationshipType differs. /// - [TestMethod] + [Fact] public void SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsTrue() { // Arrange: Create two relationships that differ only in RelationshipType @@ -161,8 +160,8 @@ public void SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsTrue() var result = SpdxRelationship.SameElements.Equals(r1, r2); // Assert: Verify the relationships are considered equal - Assert.IsTrue(result); - Assert.IsTrue(SpdxRelationship.SameElements.Equals(r2, r1)); + Assert.True(result); + Assert.True(SpdxRelationship.SameElements.Equals(r2, r1)); } /// @@ -172,7 +171,7 @@ public void SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsTrue() /// Verifies that two relationships with different Id or RelatedSpdxElement are considered distinct, /// regardless of their RelationshipType. /// - [TestMethod] + [Fact] public void SpdxRelationship_SameElementsComparer_DifferentElements_ReturnsFalse() { // Arrange: Create two relationships with different element IDs @@ -193,8 +192,8 @@ public void SpdxRelationship_SameElementsComparer_DifferentElements_ReturnsFalse var result = SpdxRelationship.SameElements.Equals(r1, r3); // Assert: Verify the relationships are considered distinct - Assert.IsFalse(result); - Assert.IsFalse(SpdxRelationship.SameElements.Equals(r3, r1)); + Assert.False(result); + Assert.False(SpdxRelationship.SameElements.Equals(r3, r1)); } /// @@ -205,7 +204,7 @@ public void SpdxRelationship_SameElementsComparer_DifferentElements_ReturnsFalse /// Verifies that two relationships considered equal by produce /// identical hash codes, satisfying the hash/equality contract. /// - [TestMethod] + [Fact] public void SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsSameHashCode() { // Arrange: Create two relationships that differ only in RelationshipType @@ -227,7 +226,7 @@ public void SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsSameHa var hash2 = SpdxRelationship.SameElements.GetHashCode(r2); // Assert: Verify the hash codes are identical - Assert.AreEqual(hash1, hash2); + Assert.Equal(hash1, hash2); } /// @@ -237,7 +236,7 @@ public void SpdxRelationship_SameElementsComparer_MatchingElements_ReturnsSameHa /// Verifies that the returned instance has equal field values for all scalar properties and /// is a distinct object reference from the original. /// - [TestMethod] + [Fact] public void SpdxRelationship_DeepCopy_FullyPopulatedRelationship_CreatesEqualButDistinctCopy() { // Arrange: Create a relationship with properties @@ -253,14 +252,14 @@ public void SpdxRelationship_DeepCopy_FullyPopulatedRelationship_CreatesEqualBut var r2 = r1.DeepCopy(); // Assert: Verifies deep-copy is equal to original - Assert.AreEqual(r1, r2, SpdxRelationship.Same); - Assert.AreEqual(r1.Id, r2.Id); - Assert.AreEqual(r1.RelationshipType, r2.RelationshipType); - Assert.AreEqual(r1.RelatedSpdxElement, r2.RelatedSpdxElement); - Assert.AreEqual(r1.Comment, r2.Comment); + Assert.Equal(r1, r2, SpdxRelationship.Same); + Assert.Equal(r1.Id, r2.Id); + Assert.Equal(r1.RelationshipType, r2.RelationshipType); + Assert.Equal(r1.RelatedSpdxElement, r2.RelatedSpdxElement); + Assert.Equal(r1.Comment, r2.Comment); // Assert: Verifies deep-copy has distinct instance - Assert.IsFalse(ReferenceEquals(r1, r2)); + Assert.False(ReferenceEquals(r1, r2)); } /// @@ -272,7 +271,7 @@ public void SpdxRelationship_DeepCopy_FullyPopulatedRelationship_CreatesEqualBut /// and that a non-matching relationship from the source array is deep-copied and appended, resulting in /// an array of length two. /// - [TestMethod] + [Fact] public void SpdxRelationship_Enhance_MatchingAndNewRelationships_MergesCorrectly() { // Arrange: Create an array of relationships @@ -306,14 +305,14 @@ public void SpdxRelationship_Enhance_MatchingAndNewRelationships_MergesCorrectly ]); // Assert: Verify the relationships array has correct information - Assert.HasCount(2, relationships); - Assert.AreEqual("SPDXRef-Package1", relationships[0].Id); - Assert.AreEqual(SpdxRelationshipType.Contains, relationships[0].RelationshipType); - Assert.AreEqual("SPDXRef-Package2", relationships[0].RelatedSpdxElement); - Assert.AreEqual("Package 1 contains Package 2", relationships[0].Comment); - Assert.AreEqual("SPDXRef-Package3", relationships[1].Id); - Assert.AreEqual(SpdxRelationshipType.DevToolOf, relationships[1].RelationshipType); - Assert.AreEqual("SPDXRef-Package4", relationships[1].RelatedSpdxElement); + Assert.Equal(2, relationships.Length); + Assert.Equal("SPDXRef-Package1", relationships[0].Id); + Assert.Equal(SpdxRelationshipType.Contains, relationships[0].RelationshipType); + Assert.Equal("SPDXRef-Package2", relationships[0].RelatedSpdxElement); + Assert.Equal("Package 1 contains Package 2", relationships[0].Comment); + Assert.Equal("SPDXRef-Package3", relationships[1].Id); + Assert.Equal(SpdxRelationshipType.DevToolOf, relationships[1].RelationshipType); + Assert.Equal("SPDXRef-Package4", relationships[1].RelatedSpdxElement); } /// @@ -323,7 +322,7 @@ public void SpdxRelationship_Enhance_MatchingAndNewRelationships_MergesCorrectly /// Verifies that an empty Id causes the "Relationship Invalid SPDX Element ID Field - Empty" /// issue to be reported. /// - [TestMethod] + [Fact] public void SpdxRelationship_Validate_MissingRelationshipId_ReportsIssue() { // Arrange: Create a bad relationship @@ -339,7 +338,7 @@ public void SpdxRelationship_Validate_MissingRelationshipId_ReportsIssue() relationship.Validate(issues, null); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Relationship Invalid SPDX Element ID Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Relationship Invalid SPDX Element ID Field - Empty")); } /// @@ -349,7 +348,7 @@ public void SpdxRelationship_Validate_MissingRelationshipId_ReportsIssue() /// Verifies that an empty RelatedSpdxElement causes the "Relationship Invalid Related SPDX Element /// Field - Empty" issue to be reported. /// - [TestMethod] + [Fact] public void SpdxRelationship_Validate_MissingRelatedElementId_ReportsIssue() { // Arrange: Create a bad relationship @@ -365,7 +364,7 @@ public void SpdxRelationship_Validate_MissingRelatedElementId_ReportsIssue() relationship.Validate(issues, null); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Relationship Invalid Related SPDX Element Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Relationship Invalid Related SPDX Element Field - Empty")); } /// @@ -375,7 +374,7 @@ public void SpdxRelationship_Validate_MissingRelatedElementId_ReportsIssue() /// Verifies that a RelationshipType of causes the /// "Relationship Invalid Relationship Type Field - Missing" issue to be reported. /// - [TestMethod] + [Fact] public void SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue() { // Arrange: Create a bad relationship @@ -391,7 +390,7 @@ public void SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue() relationship.Validate(issues, null); // Assert: Verify that the validation fails and the error message includes the description - Assert.Contains(issue => issue.Contains("Relationship Invalid Relationship Type Field - Missing"), issues); + Assert.Contains(issues, issue => issue.Contains("Relationship Invalid Relationship Type Field - Missing")); } /// @@ -403,75 +402,75 @@ public void SpdxRelationship_Validate_MissingRelationshipType_ReportsIssue() /// correctly parsed to their corresponding enum values, and that an empty /// string maps to . /// - [TestMethod] + [Fact] public void SpdxRelationshipTypeExtensions_FromText_KnownText_ReturnsMappedEnum() { // Arrange: (none required) // Act / Assert: Verify all recognized relationship type strings parse to their enum values - Assert.AreEqual(SpdxRelationshipType.Missing, SpdxRelationshipTypeExtensions.FromText("")); - Assert.AreEqual(SpdxRelationshipType.Describes, SpdxRelationshipTypeExtensions.FromText("DESCRIBES")); - Assert.AreEqual(SpdxRelationshipType.Describes, SpdxRelationshipTypeExtensions.FromText("describes")); - Assert.AreEqual(SpdxRelationshipType.Describes, SpdxRelationshipTypeExtensions.FromText("Describes")); - Assert.AreEqual(SpdxRelationshipType.DescribedBy, SpdxRelationshipTypeExtensions.FromText("DESCRIBED_BY")); - Assert.AreEqual(SpdxRelationshipType.Contains, SpdxRelationshipTypeExtensions.FromText("CONTAINS")); - Assert.AreEqual(SpdxRelationshipType.ContainedBy, SpdxRelationshipTypeExtensions.FromText("CONTAINED_BY")); - Assert.AreEqual(SpdxRelationshipType.DependsOn, SpdxRelationshipTypeExtensions.FromText("DEPENDS_ON")); - Assert.AreEqual(SpdxRelationshipType.DependencyOf, SpdxRelationshipTypeExtensions.FromText("DEPENDENCY_OF")); - Assert.AreEqual(SpdxRelationshipType.DependencyManifestOf, + Assert.Equal(SpdxRelationshipType.Missing, SpdxRelationshipTypeExtensions.FromText("")); + Assert.Equal(SpdxRelationshipType.Describes, SpdxRelationshipTypeExtensions.FromText("DESCRIBES")); + Assert.Equal(SpdxRelationshipType.Describes, SpdxRelationshipTypeExtensions.FromText("describes")); + Assert.Equal(SpdxRelationshipType.Describes, SpdxRelationshipTypeExtensions.FromText("Describes")); + Assert.Equal(SpdxRelationshipType.DescribedBy, SpdxRelationshipTypeExtensions.FromText("DESCRIBED_BY")); + Assert.Equal(SpdxRelationshipType.Contains, SpdxRelationshipTypeExtensions.FromText("CONTAINS")); + Assert.Equal(SpdxRelationshipType.ContainedBy, SpdxRelationshipTypeExtensions.FromText("CONTAINED_BY")); + Assert.Equal(SpdxRelationshipType.DependsOn, SpdxRelationshipTypeExtensions.FromText("DEPENDS_ON")); + Assert.Equal(SpdxRelationshipType.DependencyOf, SpdxRelationshipTypeExtensions.FromText("DEPENDENCY_OF")); + Assert.Equal(SpdxRelationshipType.DependencyManifestOf, SpdxRelationshipTypeExtensions.FromText("DEPENDENCY_MANIFEST_OF")); - Assert.AreEqual(SpdxRelationshipType.BuildDependencyOf, + Assert.Equal(SpdxRelationshipType.BuildDependencyOf, SpdxRelationshipTypeExtensions.FromText("BUILD_DEPENDENCY_OF")); - Assert.AreEqual(SpdxRelationshipType.DevDependencyOf, + Assert.Equal(SpdxRelationshipType.DevDependencyOf, SpdxRelationshipTypeExtensions.FromText("DEV_DEPENDENCY_OF")); - Assert.AreEqual(SpdxRelationshipType.OptionalDependencyOf, + Assert.Equal(SpdxRelationshipType.OptionalDependencyOf, SpdxRelationshipTypeExtensions.FromText("OPTIONAL_DEPENDENCY_OF")); - Assert.AreEqual(SpdxRelationshipType.ProvidedDependencyOf, + Assert.Equal(SpdxRelationshipType.ProvidedDependencyOf, SpdxRelationshipTypeExtensions.FromText("PROVIDED_DEPENDENCY_OF")); - Assert.AreEqual(SpdxRelationshipType.TestDependencyOf, + Assert.Equal(SpdxRelationshipType.TestDependencyOf, SpdxRelationshipTypeExtensions.FromText("TEST_DEPENDENCY_OF")); - Assert.AreEqual(SpdxRelationshipType.RuntimeDependencyOf, + Assert.Equal(SpdxRelationshipType.RuntimeDependencyOf, SpdxRelationshipTypeExtensions.FromText("RUNTIME_DEPENDENCY_OF")); - Assert.AreEqual(SpdxRelationshipType.ExampleOf, SpdxRelationshipTypeExtensions.FromText("EXAMPLE_OF")); - Assert.AreEqual(SpdxRelationshipType.Generates, SpdxRelationshipTypeExtensions.FromText("GENERATES")); - Assert.AreEqual(SpdxRelationshipType.GeneratedFrom, SpdxRelationshipTypeExtensions.FromText("GENERATED_FROM")); - Assert.AreEqual(SpdxRelationshipType.AncestorOf, SpdxRelationshipTypeExtensions.FromText("ANCESTOR_OF")); - Assert.AreEqual(SpdxRelationshipType.DescendantOf, SpdxRelationshipTypeExtensions.FromText("DESCENDANT_OF")); - Assert.AreEqual(SpdxRelationshipType.VariantOf, SpdxRelationshipTypeExtensions.FromText("VARIANT_OF")); - Assert.AreEqual(SpdxRelationshipType.DistributionArtifact, + Assert.Equal(SpdxRelationshipType.ExampleOf, SpdxRelationshipTypeExtensions.FromText("EXAMPLE_OF")); + Assert.Equal(SpdxRelationshipType.Generates, SpdxRelationshipTypeExtensions.FromText("GENERATES")); + Assert.Equal(SpdxRelationshipType.GeneratedFrom, SpdxRelationshipTypeExtensions.FromText("GENERATED_FROM")); + Assert.Equal(SpdxRelationshipType.AncestorOf, SpdxRelationshipTypeExtensions.FromText("ANCESTOR_OF")); + Assert.Equal(SpdxRelationshipType.DescendantOf, SpdxRelationshipTypeExtensions.FromText("DESCENDANT_OF")); + Assert.Equal(SpdxRelationshipType.VariantOf, SpdxRelationshipTypeExtensions.FromText("VARIANT_OF")); + Assert.Equal(SpdxRelationshipType.DistributionArtifact, SpdxRelationshipTypeExtensions.FromText("DISTRIBUTION_ARTIFACT")); - Assert.AreEqual(SpdxRelationshipType.PatchFor, SpdxRelationshipTypeExtensions.FromText("PATCH_FOR")); - Assert.AreEqual(SpdxRelationshipType.PatchApplied, SpdxRelationshipTypeExtensions.FromText("PATCH_APPLIED")); - Assert.AreEqual(SpdxRelationshipType.CopyOf, SpdxRelationshipTypeExtensions.FromText("COPY_OF")); - Assert.AreEqual(SpdxRelationshipType.FileAdded, SpdxRelationshipTypeExtensions.FromText("FILE_ADDED")); - Assert.AreEqual(SpdxRelationshipType.FileDeleted, SpdxRelationshipTypeExtensions.FromText("FILE_DELETED")); - Assert.AreEqual(SpdxRelationshipType.FileModified, SpdxRelationshipTypeExtensions.FromText("FILE_MODIFIED")); - Assert.AreEqual(SpdxRelationshipType.ExpandedFromArchive, + Assert.Equal(SpdxRelationshipType.PatchFor, SpdxRelationshipTypeExtensions.FromText("PATCH_FOR")); + Assert.Equal(SpdxRelationshipType.PatchApplied, SpdxRelationshipTypeExtensions.FromText("PATCH_APPLIED")); + Assert.Equal(SpdxRelationshipType.CopyOf, SpdxRelationshipTypeExtensions.FromText("COPY_OF")); + Assert.Equal(SpdxRelationshipType.FileAdded, SpdxRelationshipTypeExtensions.FromText("FILE_ADDED")); + Assert.Equal(SpdxRelationshipType.FileDeleted, SpdxRelationshipTypeExtensions.FromText("FILE_DELETED")); + Assert.Equal(SpdxRelationshipType.FileModified, SpdxRelationshipTypeExtensions.FromText("FILE_MODIFIED")); + Assert.Equal(SpdxRelationshipType.ExpandedFromArchive, SpdxRelationshipTypeExtensions.FromText("EXPANDED_FROM_ARCHIVE")); - Assert.AreEqual(SpdxRelationshipType.DynamicLink, SpdxRelationshipTypeExtensions.FromText("DYNAMIC_LINK")); - Assert.AreEqual(SpdxRelationshipType.StaticLink, SpdxRelationshipTypeExtensions.FromText("STATIC_LINK")); - Assert.AreEqual(SpdxRelationshipType.DataFileOf, SpdxRelationshipTypeExtensions.FromText("DATA_FILE_OF")); - Assert.AreEqual(SpdxRelationshipType.TestCaseOf, SpdxRelationshipTypeExtensions.FromText("TEST_CASE_OF")); - Assert.AreEqual(SpdxRelationshipType.BuildToolOf, SpdxRelationshipTypeExtensions.FromText("BUILD_TOOL_OF")); - Assert.AreEqual(SpdxRelationshipType.DevToolOf, SpdxRelationshipTypeExtensions.FromText("DEV_TOOL_OF")); - Assert.AreEqual(SpdxRelationshipType.TestOf, SpdxRelationshipTypeExtensions.FromText("TEST_OF")); - Assert.AreEqual(SpdxRelationshipType.TestToolOf, SpdxRelationshipTypeExtensions.FromText("TEST_TOOL_OF")); - Assert.AreEqual(SpdxRelationshipType.DocumentationOf, + Assert.Equal(SpdxRelationshipType.DynamicLink, SpdxRelationshipTypeExtensions.FromText("DYNAMIC_LINK")); + Assert.Equal(SpdxRelationshipType.StaticLink, SpdxRelationshipTypeExtensions.FromText("STATIC_LINK")); + Assert.Equal(SpdxRelationshipType.DataFileOf, SpdxRelationshipTypeExtensions.FromText("DATA_FILE_OF")); + Assert.Equal(SpdxRelationshipType.TestCaseOf, SpdxRelationshipTypeExtensions.FromText("TEST_CASE_OF")); + Assert.Equal(SpdxRelationshipType.BuildToolOf, SpdxRelationshipTypeExtensions.FromText("BUILD_TOOL_OF")); + Assert.Equal(SpdxRelationshipType.DevToolOf, SpdxRelationshipTypeExtensions.FromText("DEV_TOOL_OF")); + Assert.Equal(SpdxRelationshipType.TestOf, SpdxRelationshipTypeExtensions.FromText("TEST_OF")); + Assert.Equal(SpdxRelationshipType.TestToolOf, SpdxRelationshipTypeExtensions.FromText("TEST_TOOL_OF")); + Assert.Equal(SpdxRelationshipType.DocumentationOf, SpdxRelationshipTypeExtensions.FromText("DOCUMENTATION_OF")); - Assert.AreEqual(SpdxRelationshipType.OptionalComponentOf, + Assert.Equal(SpdxRelationshipType.OptionalComponentOf, SpdxRelationshipTypeExtensions.FromText("OPTIONAL_COMPONENT_OF")); - Assert.AreEqual(SpdxRelationshipType.MetafileOf, SpdxRelationshipTypeExtensions.FromText("METAFILE_OF")); - Assert.AreEqual(SpdxRelationshipType.PackageOf, SpdxRelationshipTypeExtensions.FromText("PACKAGE_OF")); - Assert.AreEqual(SpdxRelationshipType.Amends, SpdxRelationshipTypeExtensions.FromText("AMENDS")); - Assert.AreEqual(SpdxRelationshipType.PrerequisiteFor, + Assert.Equal(SpdxRelationshipType.MetafileOf, SpdxRelationshipTypeExtensions.FromText("METAFILE_OF")); + Assert.Equal(SpdxRelationshipType.PackageOf, SpdxRelationshipTypeExtensions.FromText("PACKAGE_OF")); + Assert.Equal(SpdxRelationshipType.Amends, SpdxRelationshipTypeExtensions.FromText("AMENDS")); + Assert.Equal(SpdxRelationshipType.PrerequisiteFor, SpdxRelationshipTypeExtensions.FromText("PREREQUISITE_FOR")); - Assert.AreEqual(SpdxRelationshipType.HasPrerequisite, + Assert.Equal(SpdxRelationshipType.HasPrerequisite, SpdxRelationshipTypeExtensions.FromText("HAS_PREREQUISITE")); - Assert.AreEqual(SpdxRelationshipType.RequirementDescriptionFor, + Assert.Equal(SpdxRelationshipType.RequirementDescriptionFor, SpdxRelationshipTypeExtensions.FromText("REQUIREMENT_DESCRIPTION_FOR")); - Assert.AreEqual(SpdxRelationshipType.SpecificationFor, + Assert.Equal(SpdxRelationshipType.SpecificationFor, SpdxRelationshipTypeExtensions.FromText("SPECIFICATION_FOR")); - Assert.AreEqual(SpdxRelationshipType.Other, SpdxRelationshipTypeExtensions.FromText("OTHER")); + Assert.Equal(SpdxRelationshipType.Other, SpdxRelationshipTypeExtensions.FromText("OTHER")); } /// @@ -481,15 +480,15 @@ public void SpdxRelationshipTypeExtensions_FromText_KnownText_ReturnsMappedEnum( /// Verifies that an unrecognized relationship type string throws an /// with a descriptive error message. /// - [TestMethod] + [Fact] public void SpdxRelationshipTypeExtensions_FromText_UnknownText_ThrowsInvalidOperationException() { // Arrange: (none required) // Act / Assert: Verify that an unknown type throws InvalidOperationException var exception = - Assert.ThrowsExactly(() => SpdxRelationshipTypeExtensions.FromText("invalid")); - Assert.AreEqual("Unsupported SPDX Relationship Type 'invalid'", exception.Message); + Assert.Throws(() => SpdxRelationshipTypeExtensions.FromText("invalid")); + Assert.Equal("Unsupported SPDX Relationship Type 'invalid'", exception.Message); } /// @@ -500,57 +499,57 @@ public void SpdxRelationshipTypeExtensions_FromText_UnknownText_ThrowsInvalidOpe /// Verifies that all 45 recognized enum values are correctly serialized to /// their canonical SPDX text representations (uppercase, underscore-separated tokens). /// - [TestMethod] + [Fact] public void SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText() { // Arrange: (none required) // Act / Assert: Verify all relationship type enum values serialize to their SPDX text representations - Assert.AreEqual("DESCRIBES", SpdxRelationshipType.Describes.ToText()); - Assert.AreEqual("DESCRIBED_BY", SpdxRelationshipType.DescribedBy.ToText()); - Assert.AreEqual("CONTAINS", SpdxRelationshipType.Contains.ToText()); - Assert.AreEqual("CONTAINED_BY", SpdxRelationshipType.ContainedBy.ToText()); - Assert.AreEqual("DEPENDS_ON", SpdxRelationshipType.DependsOn.ToText()); - Assert.AreEqual("DEPENDENCY_OF", SpdxRelationshipType.DependencyOf.ToText()); - Assert.AreEqual("DEPENDENCY_MANIFEST_OF", SpdxRelationshipType.DependencyManifestOf.ToText()); - Assert.AreEqual("BUILD_DEPENDENCY_OF", SpdxRelationshipType.BuildDependencyOf.ToText()); - Assert.AreEqual("DEV_DEPENDENCY_OF", SpdxRelationshipType.DevDependencyOf.ToText()); - Assert.AreEqual("OPTIONAL_DEPENDENCY_OF", SpdxRelationshipType.OptionalDependencyOf.ToText()); - Assert.AreEqual("PROVIDED_DEPENDENCY_OF", SpdxRelationshipType.ProvidedDependencyOf.ToText()); - Assert.AreEqual("TEST_DEPENDENCY_OF", SpdxRelationshipType.TestDependencyOf.ToText()); - Assert.AreEqual("RUNTIME_DEPENDENCY_OF", SpdxRelationshipType.RuntimeDependencyOf.ToText()); - Assert.AreEqual("EXAMPLE_OF", SpdxRelationshipType.ExampleOf.ToText()); - Assert.AreEqual("GENERATES", SpdxRelationshipType.Generates.ToText()); - Assert.AreEqual("GENERATED_FROM", SpdxRelationshipType.GeneratedFrom.ToText()); - Assert.AreEqual("ANCESTOR_OF", SpdxRelationshipType.AncestorOf.ToText()); - Assert.AreEqual("DESCENDANT_OF", SpdxRelationshipType.DescendantOf.ToText()); - Assert.AreEqual("VARIANT_OF", SpdxRelationshipType.VariantOf.ToText()); - Assert.AreEqual("DISTRIBUTION_ARTIFACT", SpdxRelationshipType.DistributionArtifact.ToText()); - Assert.AreEqual("PATCH_FOR", SpdxRelationshipType.PatchFor.ToText()); - Assert.AreEqual("PATCH_APPLIED", SpdxRelationshipType.PatchApplied.ToText()); - Assert.AreEqual("COPY_OF", SpdxRelationshipType.CopyOf.ToText()); - Assert.AreEqual("FILE_ADDED", SpdxRelationshipType.FileAdded.ToText()); - Assert.AreEqual("FILE_DELETED", SpdxRelationshipType.FileDeleted.ToText()); - Assert.AreEqual("FILE_MODIFIED", SpdxRelationshipType.FileModified.ToText()); - Assert.AreEqual("EXPANDED_FROM_ARCHIVE", SpdxRelationshipType.ExpandedFromArchive.ToText()); - Assert.AreEqual("DYNAMIC_LINK", SpdxRelationshipType.DynamicLink.ToText()); - Assert.AreEqual("STATIC_LINK", SpdxRelationshipType.StaticLink.ToText()); - Assert.AreEqual("DATA_FILE_OF", SpdxRelationshipType.DataFileOf.ToText()); - Assert.AreEqual("TEST_CASE_OF", SpdxRelationshipType.TestCaseOf.ToText()); - Assert.AreEqual("BUILD_TOOL_OF", SpdxRelationshipType.BuildToolOf.ToText()); - Assert.AreEqual("DEV_TOOL_OF", SpdxRelationshipType.DevToolOf.ToText()); - Assert.AreEqual("TEST_OF", SpdxRelationshipType.TestOf.ToText()); - Assert.AreEqual("TEST_TOOL_OF", SpdxRelationshipType.TestToolOf.ToText()); - Assert.AreEqual("DOCUMENTATION_OF", SpdxRelationshipType.DocumentationOf.ToText()); - Assert.AreEqual("OPTIONAL_COMPONENT_OF", SpdxRelationshipType.OptionalComponentOf.ToText()); - Assert.AreEqual("METAFILE_OF", SpdxRelationshipType.MetafileOf.ToText()); - Assert.AreEqual("PACKAGE_OF", SpdxRelationshipType.PackageOf.ToText()); - Assert.AreEqual("AMENDS", SpdxRelationshipType.Amends.ToText()); - Assert.AreEqual("PREREQUISITE_FOR", SpdxRelationshipType.PrerequisiteFor.ToText()); - Assert.AreEqual("HAS_PREREQUISITE", SpdxRelationshipType.HasPrerequisite.ToText()); - Assert.AreEqual("REQUIREMENT_DESCRIPTION_FOR", SpdxRelationshipType.RequirementDescriptionFor.ToText()); - Assert.AreEqual("SPECIFICATION_FOR", SpdxRelationshipType.SpecificationFor.ToText()); - Assert.AreEqual("OTHER", SpdxRelationshipType.Other.ToText()); + Assert.Equal("DESCRIBES", SpdxRelationshipType.Describes.ToText()); + Assert.Equal("DESCRIBED_BY", SpdxRelationshipType.DescribedBy.ToText()); + Assert.Equal("CONTAINS", SpdxRelationshipType.Contains.ToText()); + Assert.Equal("CONTAINED_BY", SpdxRelationshipType.ContainedBy.ToText()); + Assert.Equal("DEPENDS_ON", SpdxRelationshipType.DependsOn.ToText()); + Assert.Equal("DEPENDENCY_OF", SpdxRelationshipType.DependencyOf.ToText()); + Assert.Equal("DEPENDENCY_MANIFEST_OF", SpdxRelationshipType.DependencyManifestOf.ToText()); + Assert.Equal("BUILD_DEPENDENCY_OF", SpdxRelationshipType.BuildDependencyOf.ToText()); + Assert.Equal("DEV_DEPENDENCY_OF", SpdxRelationshipType.DevDependencyOf.ToText()); + Assert.Equal("OPTIONAL_DEPENDENCY_OF", SpdxRelationshipType.OptionalDependencyOf.ToText()); + Assert.Equal("PROVIDED_DEPENDENCY_OF", SpdxRelationshipType.ProvidedDependencyOf.ToText()); + Assert.Equal("TEST_DEPENDENCY_OF", SpdxRelationshipType.TestDependencyOf.ToText()); + Assert.Equal("RUNTIME_DEPENDENCY_OF", SpdxRelationshipType.RuntimeDependencyOf.ToText()); + Assert.Equal("EXAMPLE_OF", SpdxRelationshipType.ExampleOf.ToText()); + Assert.Equal("GENERATES", SpdxRelationshipType.Generates.ToText()); + Assert.Equal("GENERATED_FROM", SpdxRelationshipType.GeneratedFrom.ToText()); + Assert.Equal("ANCESTOR_OF", SpdxRelationshipType.AncestorOf.ToText()); + Assert.Equal("DESCENDANT_OF", SpdxRelationshipType.DescendantOf.ToText()); + Assert.Equal("VARIANT_OF", SpdxRelationshipType.VariantOf.ToText()); + Assert.Equal("DISTRIBUTION_ARTIFACT", SpdxRelationshipType.DistributionArtifact.ToText()); + Assert.Equal("PATCH_FOR", SpdxRelationshipType.PatchFor.ToText()); + Assert.Equal("PATCH_APPLIED", SpdxRelationshipType.PatchApplied.ToText()); + Assert.Equal("COPY_OF", SpdxRelationshipType.CopyOf.ToText()); + Assert.Equal("FILE_ADDED", SpdxRelationshipType.FileAdded.ToText()); + Assert.Equal("FILE_DELETED", SpdxRelationshipType.FileDeleted.ToText()); + Assert.Equal("FILE_MODIFIED", SpdxRelationshipType.FileModified.ToText()); + Assert.Equal("EXPANDED_FROM_ARCHIVE", SpdxRelationshipType.ExpandedFromArchive.ToText()); + Assert.Equal("DYNAMIC_LINK", SpdxRelationshipType.DynamicLink.ToText()); + Assert.Equal("STATIC_LINK", SpdxRelationshipType.StaticLink.ToText()); + Assert.Equal("DATA_FILE_OF", SpdxRelationshipType.DataFileOf.ToText()); + Assert.Equal("TEST_CASE_OF", SpdxRelationshipType.TestCaseOf.ToText()); + Assert.Equal("BUILD_TOOL_OF", SpdxRelationshipType.BuildToolOf.ToText()); + Assert.Equal("DEV_TOOL_OF", SpdxRelationshipType.DevToolOf.ToText()); + Assert.Equal("TEST_OF", SpdxRelationshipType.TestOf.ToText()); + Assert.Equal("TEST_TOOL_OF", SpdxRelationshipType.TestToolOf.ToText()); + Assert.Equal("DOCUMENTATION_OF", SpdxRelationshipType.DocumentationOf.ToText()); + Assert.Equal("OPTIONAL_COMPONENT_OF", SpdxRelationshipType.OptionalComponentOf.ToText()); + Assert.Equal("METAFILE_OF", SpdxRelationshipType.MetafileOf.ToText()); + Assert.Equal("PACKAGE_OF", SpdxRelationshipType.PackageOf.ToText()); + Assert.Equal("AMENDS", SpdxRelationshipType.Amends.ToText()); + Assert.Equal("PREREQUISITE_FOR", SpdxRelationshipType.PrerequisiteFor.ToText()); + Assert.Equal("HAS_PREREQUISITE", SpdxRelationshipType.HasPrerequisite.ToText()); + Assert.Equal("REQUIREMENT_DESCRIPTION_FOR", SpdxRelationshipType.RequirementDescriptionFor.ToText()); + Assert.Equal("SPECIFICATION_FOR", SpdxRelationshipType.SpecificationFor.ToText()); + Assert.Equal("OTHER", SpdxRelationshipType.Other.ToText()); } /// @@ -562,16 +561,16 @@ public void SpdxRelationshipTypeExtensions_ToText_KnownEnum_ReturnsMappedText() /// with a descriptive error message, since the sentinel is not a valid /// SPDX relationship type token. /// - [TestMethod] + [Fact] public void SpdxRelationshipTypeExtensions_ToText_MissingSentinel_ThrowsInvalidOperationException() { // Arrange: (none required) // Act: Attempt to convert the Missing sentinel to text - var exception = Assert.ThrowsExactly(() => SpdxRelationshipType.Missing.ToText()); + var exception = Assert.Throws(() => SpdxRelationshipType.Missing.ToText()); // Assert: Verify the exception has the expected message - Assert.AreEqual("Attempt to serialize missing SPDX Relationship Type", exception.Message); + Assert.Equal("Attempt to serialize missing SPDX Relationship Type", exception.Message); } /// @@ -583,13 +582,13 @@ public void SpdxRelationshipTypeExtensions_ToText_MissingSentinel_ThrowsInvalidO /// sentinel) throws an /// with a descriptive error message. /// - [TestMethod] + [Fact] public void SpdxRelationshipTypeExtensions_ToText_UnknownEnum_ThrowsInvalidOperationException() { // Arrange: (none required) // Act / Assert: Verify that an unknown type throws InvalidOperationException - var exception = Assert.ThrowsExactly(() => ((SpdxRelationshipType)1000).ToText()); - Assert.AreEqual("Unsupported SPDX Relationship Type '1000'", exception.Message); + var exception = Assert.Throws(() => ((SpdxRelationshipType)1000).ToText()); + Assert.Equal("Unsupported SPDX Relationship Type '1000'", exception.Message); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs b/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs index e2d6dd1..aa4e593 100644 --- a/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/SpdxSnippetTests.cs @@ -29,7 +29,6 @@ namespace DemaConsulting.SpdxModel.Tests; /// validation of required fields and byte range constraints. Each test exercises a single scenario or /// boundary condition in isolation with no shared state between tests. /// -[TestClass] public class SpdxSnippetTests { /// @@ -40,7 +39,7 @@ public class SpdxSnippetTests /// SnippetByteEnd are considered equal even when other fields differ, and that snippets /// with different file or byte range are distinct. /// - [TestMethod] + [Fact] public void SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual() { // Arrange: Create two snippets with the same byte range and one distinct snippet @@ -68,55 +67,85 @@ public void SpdxSnippet_SameComparer_SameFileAndByteRange_ReturnsEqual() }; // Act / Assert: Verify snippets compare to themselves - Assert.IsTrue(SpdxSnippet.Same.Equals(s1, s1)); - Assert.IsTrue(SpdxSnippet.Same.Equals(s2, s2)); - Assert.IsTrue(SpdxSnippet.Same.Equals(s3, s3)); + Assert.True(SpdxSnippet.Same.Equals(s1, s1)); + Assert.True(SpdxSnippet.Same.Equals(s2, s2)); + Assert.True(SpdxSnippet.Same.Equals(s3, s3)); // Assert: snippets compare correctly - Assert.IsTrue(SpdxSnippet.Same.Equals(s1, s2)); - Assert.IsTrue(SpdxSnippet.Same.Equals(s2, s1)); - Assert.IsFalse(SpdxSnippet.Same.Equals(s1, s3)); - Assert.IsFalse(SpdxSnippet.Same.Equals(s3, s1)); - Assert.IsFalse(SpdxSnippet.Same.Equals(s2, s3)); - Assert.IsFalse(SpdxSnippet.Same.Equals(s3, s2)); + Assert.True(SpdxSnippet.Same.Equals(s1, s2)); + Assert.True(SpdxSnippet.Same.Equals(s2, s1)); + Assert.False(SpdxSnippet.Same.Equals(s1, s3)); + Assert.False(SpdxSnippet.Same.Equals(s3, s1)); + Assert.False(SpdxSnippet.Same.Equals(s2, s3)); + Assert.False(SpdxSnippet.Same.Equals(s3, s2)); // Assert: same snippets have identical hashes - Assert.AreEqual(SpdxSnippet.Same.GetHashCode(s1), SpdxSnippet.Same.GetHashCode(s2)); + Assert.Equal(SpdxSnippet.Same.GetHashCode(s1), SpdxSnippet.Same.GetHashCode(s2)); } /// /// Tests the method successfully creates a deep copy. /// /// - /// Verifies that the returned instance has equal field values for all scalar properties and - /// is a distinct object reference from the original. + /// Verifies that the returned instance has equal field values for all properties including + /// array fields, is a distinct object reference from the original, and that all array fields + /// are independent instances so that mutating the copy does not affect the original. /// - [TestMethod] + [Fact] public void SpdxSnippet_DeepCopy_FullyPopulatedSnippet_CreatesEqualButDistinctCopy() { - // Arrange: Create a SpdxSnippet instance with various properties + // Arrange: Create a fully-populated SpdxSnippet instance with all fields set var s1 = new SpdxSnippet { + Id = "SPDXRef-Snippet", SnippetFromFile = "SPDXRef-File1", SnippetByteStart = 100, SnippetByteEnd = 200, + SnippetLineStart = 5, + SnippetLineEnd = 10, + ConcludedLicense = "MIT", + LicenseInfoInSnippet = ["MIT", "Apache-2.0"], + LicenseComments = "License comment", + CopyrightText = "Copyright(c) 2024 DEMA Consulting", Comment = "Found snippet", - ConcludedLicense = "MIT" + Name = "MySnippet", + AttributionText = ["Attribution text"], + Annotations = + [ + new SpdxAnnotation + { + Annotator = "Tool: test-tool", + Date = "2024-05-28T01:30:00Z", + Type = SpdxAnnotationType.Review, + Comment = "Reviewed" + } + ] }; // Act: Create a deep copy of the SpdxSnippet instance var s2 = s1.DeepCopy(); - // Assert: Verify the deep-copy is equal to the original - Assert.AreEqual(s1, s2, SpdxSnippet.Same); - Assert.AreEqual(s1.SnippetFromFile, s2.SnippetFromFile); - Assert.AreEqual(s1.SnippetByteStart, s2.SnippetByteStart); - Assert.AreEqual(s1.SnippetByteEnd, s2.SnippetByteEnd); - Assert.AreEqual(s1.Comment, s2.Comment); - Assert.AreEqual(s1.ConcludedLicense, s2.ConcludedLicense); - - // Assert: Verify the deep-copy is a distinct instance - Assert.IsFalse(ReferenceEquals(s1, s2)); + // Assert: Verify the deep-copy has equal field values to the original + Assert.Equal(s1, s2, SpdxSnippet.Same); + Assert.Equal(s1.Id, s2.Id); + Assert.Equal(s1.SnippetFromFile, s2.SnippetFromFile); + Assert.Equal(s1.SnippetByteStart, s2.SnippetByteStart); + Assert.Equal(s1.SnippetByteEnd, s2.SnippetByteEnd); + Assert.Equal(s1.SnippetLineStart, s2.SnippetLineStart); + Assert.Equal(s1.SnippetLineEnd, s2.SnippetLineEnd); + Assert.Equal(s1.ConcludedLicense, s2.ConcludedLicense); + Assert.Equal(s1.LicenseInfoInSnippet, s2.LicenseInfoInSnippet); + Assert.Equal(s1.LicenseComments, s2.LicenseComments); + Assert.Equal(s1.CopyrightText, s2.CopyrightText); + Assert.Equal(s1.Comment, s2.Comment); + Assert.Equal(s1.Name, s2.Name); + Assert.Equal(s1.AttributionText, s2.AttributionText); + + // Assert: Verify the deep-copy is a distinct instance with independent array references + Assert.False(ReferenceEquals(s1, s2)); + Assert.False(ReferenceEquals(s1.LicenseInfoInSnippet, s2.LicenseInfoInSnippet)); + Assert.False(ReferenceEquals(s1.AttributionText, s2.AttributionText)); + Assert.False(ReferenceEquals(s1.Annotations, s2.Annotations)); } /// @@ -127,7 +156,7 @@ public void SpdxSnippet_DeepCopy_FullyPopulatedSnippet_CreatesEqualButDistinctCo /// Verifies that a matching snippet (same file and byte range) is enhanced in place and that a non-matching /// snippet from the source array is deep-copied and appended, resulting in an array of length two. /// - [TestMethod] + [Fact] public void SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly() { // Arrange: Create an array of SpdxSnippet objects @@ -162,15 +191,15 @@ public void SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly() ]); // Assert: Check that the snippets array has been enhanced correctly - Assert.HasCount(2, snippets); - Assert.AreEqual("SPDXRef-File1", snippets[0].SnippetFromFile); - Assert.AreEqual(100, snippets[0].SnippetByteStart); - Assert.AreEqual(200, snippets[0].SnippetByteEnd); - Assert.AreEqual("Found snippet", snippets[0].Comment); - Assert.AreEqual("MIT", snippets[0].ConcludedLicense); - Assert.AreEqual("SPDXRef-File2", snippets[1].SnippetFromFile); - Assert.AreEqual(10, snippets[1].SnippetByteStart); - Assert.AreEqual(40, snippets[1].SnippetByteEnd); + Assert.Equal(2, snippets.Length); + Assert.Equal("SPDXRef-File1", snippets[0].SnippetFromFile); + Assert.Equal(100, snippets[0].SnippetByteStart); + Assert.Equal(200, snippets[0].SnippetByteEnd); + Assert.Equal("Found snippet", snippets[0].Comment); + Assert.Equal("MIT", snippets[0].ConcludedLicense); + Assert.Equal("SPDXRef-File2", snippets[1].SnippetFromFile); + Assert.Equal(10, snippets[1].SnippetByteStart); + Assert.Equal(40, snippets[1].SnippetByteEnd); } /// @@ -180,7 +209,7 @@ public void SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly() /// Verifies that an Id not matching the SPDXRef- prefix format causes the /// "Snippet Invalid SPDX Identifier Field" issue to be reported. /// - [TestMethod] + [Fact] public void SpdxSnippet_Validate_InvalidSnippetId_ReportsIssue() { // Arrange: Create a SpdxSnippet with an invalid ID @@ -199,7 +228,7 @@ public void SpdxSnippet_Validate_InvalidSnippetId_ReportsIssue() snippet.Validate(issues); // Assert: Check that the issues list contains the expected error message - Assert.Contains(issue => issue.Contains("Snippet Invalid SPDX Identifier Field 'Invalid_ID'"), issues); + Assert.Contains(issues, issue => issue.Contains("Snippet Invalid SPDX Identifier Field 'Invalid_ID'")); } /// @@ -210,7 +239,7 @@ public void SpdxSnippet_Validate_InvalidSnippetId_ReportsIssue() /// non-empty SnippetFromFile, byte range ≥ 1, non-empty license, and copyright) /// passes all validation checks without reporting any issues. /// - [TestMethod] + [Fact] public void SpdxSnippet_Validate_AllRequiredFieldsPresent_ReturnsNoIssues() { // Arrange: Create a valid SpdxSnippet @@ -229,7 +258,7 @@ public void SpdxSnippet_Validate_AllRequiredFieldsPresent_ReturnsNoIssues() snippet.Validate(issues); // Assert: Verify that the validation reports no issues. - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// @@ -239,7 +268,7 @@ public void SpdxSnippet_Validate_AllRequiredFieldsPresent_ReturnsNoIssues() /// Verifies that an annotation with an empty Annotator field causes the /// "Invalid Annotator Field - Empty" issue to be reported with the correct snippet prefix. /// - [TestMethod] + [Fact] public void SpdxSnippet_Validate_InvalidAnnotation_ReportsIssue() { // Arrange: Create a valid snippet with an invalid annotation @@ -268,7 +297,7 @@ public void SpdxSnippet_Validate_InvalidAnnotation_ReportsIssue() snippet.Validate(issues); // Assert: Verify the annotation issue is reported with the correct prefix - Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Annotator Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Annotator Field - Empty")); } /// @@ -278,7 +307,7 @@ public void SpdxSnippet_Validate_InvalidAnnotation_ReportsIssue() /// Verifies the boundary condition where SnippetFromFile is empty: validation must report /// the "Invalid Snippet From File Field - Empty" issue. /// - [TestMethod] + [Fact] public void SpdxSnippet_Validate_EmptySnippetFromFile_ReportsIssue() { // Arrange: Create a snippet with an empty SnippetFromFile @@ -297,7 +326,7 @@ public void SpdxSnippet_Validate_EmptySnippetFromFile_ReportsIssue() snippet.Validate(issues); // Assert: Verify the empty SnippetFromFile issue is reported - Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet From File Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet From File Field - Empty")); } /// @@ -307,7 +336,7 @@ public void SpdxSnippet_Validate_EmptySnippetFromFile_ReportsIssue() /// Verifies the lower boundary condition: a SnippetByteStart value of 0 (less than the /// required minimum of 1) causes the "Invalid Snippet Byte Range Start Field" issue to be reported. /// - [TestMethod] + [Fact] public void SpdxSnippet_Validate_InvalidByteStart_ReportsIssue() { // Arrange: Create a snippet with SnippetByteStart < 1 @@ -326,7 +355,7 @@ public void SpdxSnippet_Validate_InvalidByteStart_ReportsIssue() snippet.Validate(issues); // Assert: Verify the invalid byte start issue is reported - Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet Byte Range Start Field '0'"), issues); + Assert.Contains(issues, issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet Byte Range Start Field '0'")); } /// @@ -336,7 +365,7 @@ public void SpdxSnippet_Validate_InvalidByteStart_ReportsIssue() /// Verifies the range boundary condition: a SnippetByteEnd less than SnippetByteStart /// causes the "Invalid Snippet Byte Range End Field" issue to be reported. /// - [TestMethod] + [Fact] public void SpdxSnippet_Validate_InvalidByteEnd_ReportsIssue() { // Arrange: Create a snippet where SnippetByteEnd is less than SnippetByteStart @@ -355,7 +384,7 @@ public void SpdxSnippet_Validate_InvalidByteEnd_ReportsIssue() snippet.Validate(issues); // Assert: Verify the invalid byte end issue is reported - Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet Byte Range End Field '50' < '100'"), issues); + Assert.Contains(issues, issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Snippet Byte Range End Field '50' < '100'")); } /// @@ -365,7 +394,7 @@ public void SpdxSnippet_Validate_InvalidByteEnd_ReportsIssue() /// Verifies that an empty ConcludedLicense causes the "Invalid Concluded License Field - Empty" /// issue to be reported. /// - [TestMethod] + [Fact] public void SpdxSnippet_Validate_EmptyConcludedLicense_ReportsIssue() { // Arrange: Create a snippet with an empty ConcludedLicense @@ -384,7 +413,7 @@ public void SpdxSnippet_Validate_EmptyConcludedLicense_ReportsIssue() snippet.Validate(issues); // Assert: Verify the empty ConcludedLicense issue is reported - Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Concluded License Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Concluded License Field - Empty")); } /// @@ -394,7 +423,7 @@ public void SpdxSnippet_Validate_EmptyConcludedLicense_ReportsIssue() /// Verifies that an empty CopyrightText causes the "Invalid Copyright Text Field - Empty" /// issue to be reported. /// - [TestMethod] + [Fact] public void SpdxSnippet_Validate_EmptyCopyrightText_ReportsIssue() { // Arrange: Create a snippet with an empty CopyrightText @@ -413,6 +442,6 @@ public void SpdxSnippet_Validate_EmptyCopyrightText_ReportsIssue() snippet.Validate(issues); // Assert: Verify the empty CopyrightText issue is reported - Assert.Contains(issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Copyright Text Field - Empty"), issues); + Assert.Contains(issues, issue => issue.Contains("Snippet 'SPDXRef-Snippet' Invalid Copyright Text Field - Empty")); } } diff --git a/test/DemaConsulting.SpdxModel.Tests/TestHelpers.cs b/test/DemaConsulting.SpdxModel.Tests/TestHelpers.cs index 3d01c43..e306cbc 100644 --- a/test/DemaConsulting.SpdxModel.Tests/TestHelpers.cs +++ b/test/DemaConsulting.SpdxModel.Tests/TestHelpers.cs @@ -43,4 +43,27 @@ public static string GetEmbeddedResource(string resourceName) using var reader = new StreamReader(stream); return reader.ReadToEnd().ReplaceLineEndings(); } + + /// + /// Asserts that two collections contain the same elements in any order, + /// using the provided equality comparer for element matching. + /// + public static void AssertEquivalent( + IEnumerable expected, + IEnumerable actual, + IEqualityComparer comparer) + { + var expectedList = expected.ToList(); + var actualList = actual.ToList(); + Assert.Equal(expectedList.Count, actualList.Count); + foreach (var item in expectedList) + { + Assert.Contains(actualList, x => comparer.Equals(x, item)); + } + + foreach (var item in actualList) + { + Assert.Contains(expectedList, x => comparer.Equals(x, item)); + } + } } diff --git a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs index fd9c53b..c6dab48 100644 --- a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxModelTransformTests.cs @@ -29,9 +29,8 @@ namespace DemaConsulting.SpdxModel.Tests.Transforms; /// /// Integration-scope tests: each test deserializes a real SPDX 2.3 JSON document from /// an embedded resource and exercises the transform -/// against that document. MSTest is the approved framework for this repository. +/// against that document. xUnit v3 is the test framework. /// -[TestClass] public class SpdxModelTransformTests { /// @@ -42,7 +41,7 @@ public class SpdxModelTransformTests /// document is added, the relationship count increases by one, and the document /// remains valid after the transform. /// - [TestMethod] + [Fact] public void SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists() { // Arrange: Load the SPDX 2.3 JSON example as a real document to transform @@ -62,15 +61,15 @@ public void SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists() }); // Assert: Verify the relationship was added and the document remains valid - Assert.AreEqual(initialCount + 1, document.Relationships.Length); - Assert.IsTrue(Array.Exists( + Assert.Equal(initialCount + 1, document.Relationships.Length); + Assert.True(Array.Exists( document.Relationships, r => r.Id == "SPDXRef-Package" && r.RelatedSpdxElement == "SPDXRef-fromDoap-0" && r.RelationshipType == SpdxRelationshipType.DependsOn)); var issues = new List(); document.Validate(issues); - Assert.IsEmpty(issues); + Assert.Empty(issues); } /// @@ -81,7 +80,7 @@ public void SpdxModelTransform_AddRelationship_ToDocument_RelationshipPersists() /// to be thrown. The document is expected to remain /// unmodified because the validation happens before any mutation. /// - [TestMethod] + [Fact] public void SpdxModelTransform_AddRelationship_InvalidSourceId_ThrowsArgumentException() { // Arrange: Load the SPDX 2.3 JSON example as a real document to transform @@ -90,7 +89,7 @@ public void SpdxModelTransform_AddRelationship_InvalidSourceId_ThrowsArgumentExc var document = Spdx2JsonDeserializer.Deserialize(json); // Act / Assert: Adding with a non-existent source ID throws ArgumentException - Assert.ThrowsExactly(() => + Assert.Throws(() => { SpdxRelationships.Add( document, @@ -111,7 +110,7 @@ public void SpdxModelTransform_AddRelationship_InvalidSourceId_ThrowsArgumentExc /// NOASSERTION nor prefixed with DocumentRef-, causes an /// to be thrown. /// - [TestMethod] + [Fact] public void SpdxModelTransform_AddRelationship_InvalidTargetId_ThrowsArgumentException() { // Arrange: Load the SPDX 2.3 JSON example as a real document to transform @@ -120,7 +119,7 @@ public void SpdxModelTransform_AddRelationship_InvalidTargetId_ThrowsArgumentExc var document = Spdx2JsonDeserializer.Deserialize(json); // Act / Assert: Adding with a non-existent target that is neither NOASSERTION nor DocumentRef- throws - Assert.ThrowsExactly(() => + Assert.Throws(() => { SpdxRelationships.Add( document, @@ -141,7 +140,7 @@ public void SpdxModelTransform_AddRelationship_InvalidTargetId_ThrowsArgumentExc /// existing entry rather than appending a duplicate. The relationship count after two /// adds must equal the count after one add. /// - [TestMethod] + [Fact] public void SpdxModelTransform_AddRelationship_Duplicate_EnhancesExistingRelationship() { // Arrange: Load the SPDX 2.3 JSON example and add an initial relationship @@ -169,7 +168,7 @@ public void SpdxModelTransform_AddRelationship_Duplicate_EnhancesExistingRelatio }); // Assert: Only one new relationship was added (duplicate was merged, not appended) - Assert.AreEqual(initialCount + 1, document.Relationships.Length); + Assert.Equal(initialCount + 1, document.Relationships.Length); } /// @@ -180,7 +179,7 @@ public void SpdxModelTransform_AddRelationship_Duplicate_EnhancesExistingRelatio /// existing relationships between the same pair of elements before adding the new ones. /// This allows changing the relationship type between a fixed pair of elements. /// - [TestMethod] + [Fact] public void SpdxModelTransform_AddRelationship_Replace_RemovesPreExistingRelationships() { // Arrange: Load the SPDX 2.3 JSON example and add an initial relationship @@ -211,13 +210,13 @@ public void SpdxModelTransform_AddRelationship_Replace_RemovesPreExistingRelatio replace: true); // Assert: The count is unchanged (old removed, new added) and the type changed - Assert.AreEqual(countAfterFirstAdd, document.Relationships.Length); - Assert.IsTrue(Array.Exists( + Assert.Equal(countAfterFirstAdd, document.Relationships.Length); + Assert.True(Array.Exists( document.Relationships, r => r.Id == "SPDXRef-Package" && r.RelatedSpdxElement == "SPDXRef-fromDoap-0" && r.RelationshipType == SpdxRelationshipType.BuildToolOf)); - Assert.IsFalse(Array.Exists( + Assert.False(Array.Exists( document.Relationships, r => r.Id == "SPDXRef-Package" && r.RelatedSpdxElement == "SPDXRef-fromDoap-0" && @@ -231,7 +230,7 @@ public void SpdxModelTransform_AddRelationship_Replace_RemovesPreExistingRelatio /// Batch path: the batch overload with two distinct relationships adds both in a single /// call, increasing the relationship count by exactly two. /// - [TestMethod] + [Fact] public void SpdxModelTransform_AddRelationship_BatchMultiple_AddsAllRelationships() { // Arrange: Load the SPDX 2.3 JSON example @@ -259,7 +258,7 @@ public void SpdxModelTransform_AddRelationship_BatchMultiple_AddsAllRelationship ]); // Assert: Both relationships were added - Assert.AreEqual(initialCount + 2, document.Relationships.Length); + Assert.Equal(initialCount + 2, document.Relationships.Length); } /// @@ -270,7 +269,7 @@ public void SpdxModelTransform_AddRelationship_BatchMultiple_AddsAllRelationship /// related element is intentionally unspecified). The transform must accept it without /// throwing, regardless of whether any element with ID "NOASSERTION" exists. /// - [TestMethod] + [Fact] public void SpdxModelTransform_AddRelationship_NoAssertionTarget_AddsRelationship() { // Arrange: Load the SPDX 2.3 JSON example @@ -290,8 +289,8 @@ public void SpdxModelTransform_AddRelationship_NoAssertionTarget_AddsRelationshi }); // Assert: Relationship was added without an exception - Assert.AreEqual(initialCount + 1, document.Relationships.Length); - Assert.IsTrue(Array.Exists( + Assert.Equal(initialCount + 1, document.Relationships.Length); + Assert.True(Array.Exists( document.Relationships, r => r.Id == "SPDXRef-Package" && r.RelatedSpdxElement == SpdxElement.NoAssertion && @@ -306,7 +305,7 @@ public void SpdxModelTransform_AddRelationship_NoAssertionTarget_AddsRelationshi /// element in an external document. The transform must accept it without throwing, /// regardless of whether the external document reference resolves locally. /// - [TestMethod] + [Fact] public void SpdxModelTransform_AddRelationship_DocumentRefTarget_AddsRelationship() { // Arrange: Load the SPDX 2.3 JSON example @@ -326,8 +325,8 @@ public void SpdxModelTransform_AddRelationship_DocumentRefTarget_AddsRelationshi }); // Assert: Relationship was added without an exception - Assert.AreEqual(initialCount + 1, document.Relationships.Length); - Assert.IsTrue(Array.Exists( + Assert.Equal(initialCount + 1, document.Relationships.Length); + Assert.True(Array.Exists( document.Relationships, r => r.Id == "SPDXRef-Package" && r.RelatedSpdxElement == "DocumentRef-spdx-tool-1.2:SPDXRef-Package" && diff --git a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs index 73e4e61..e6b7e26 100644 --- a/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/Transforms/SpdxRelationshipsTests.cs @@ -4,16 +4,15 @@ namespace DemaConsulting.SpdxModel.Tests.Transforms; /// -/// Tests for the transforms. +/// Tests for the transforms. /// /// -/// Uses MSTest as the approved test framework for this repository (see csharp-testing.md). +/// Uses xUnit v3 as the test framework. /// Every test deserializes a fresh copy of to prevent /// inter-test state leakage. The class covers the full scope of /// operations: adding single and multiple relationships, deduplication, replacement, and /// atomicity on failure. /// -[TestClass] public class SpdxRelationshipsTests { /// @@ -62,14 +61,14 @@ public class SpdxRelationshipsTests /// Confirms the exception carries the correct parameter name and that the document /// relationships collection remains empty after the failed call. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException() { // Arrange: Deserialize the test document contents var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); // Act: Attempt to add a relationship with a non-existent ID - var ex = Assert.ThrowsExactly(() => + var ex = Assert.Throws(() => { SpdxRelationships.Add( document, @@ -83,8 +82,8 @@ public void SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException() // Assert: Verify the exception message and that no relationships were added Assert.StartsWith("Element SPDXRef-Package-Missing not found in SPDX document", ex.Message); - Assert.AreEqual("relationship", ex.ParamName); - Assert.IsEmpty(document.Relationships); + Assert.Equal("relationship", ex.ParamName); + Assert.Empty(document.Relationships); } /// @@ -96,14 +95,14 @@ public void SpdxRelationships_AddSingle_MissingId_ThrowsArgumentException() /// Confirms the exception carries the correct parameter name and that the document /// relationships collection remains empty after the failed call. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentException() { // Arrange: Deserialize the test document contents var document = Spdx2JsonDeserializer.Deserialize(TestDocumentContents); // Act: Attempt to add a relationship with a missing related element - var ex = Assert.ThrowsExactly(() => + var ex = Assert.Throws(() => { SpdxRelationships.Add( document, @@ -117,8 +116,8 @@ public void SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentExce // Assert: Verify the exception message and that no relationships were added Assert.StartsWith("Element SPDXRef-Package-Missing not found in SPDX document", ex.Message); - Assert.AreEqual("relationship", ex.ParamName); - Assert.IsEmpty(document.Relationships); + Assert.Equal("relationship", ex.ParamName); + Assert.Empty(document.Relationships); } /// @@ -129,7 +128,7 @@ public void SpdxRelationships_AddSingle_MissingRelatedElement_ThrowsArgumentExce /// A fresh document is deserialized so the initial relationships collection is empty, /// making the single post-add entry the definitive proof of a successful append. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship() { // Arrange: Deserialize the test document contents @@ -146,10 +145,10 @@ public void SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship() }); // Assert: Verify the relationship was added correctly - Assert.HasCount(1, document.Relationships); - Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); - Assert.AreEqual("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + Assert.Single(document.Relationships); + Assert.Equal("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.Equal("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); } /// @@ -161,7 +160,7 @@ public void SpdxRelationships_AddSingle_ValidRelationship_AddsRelationship() /// count assertion; the final count of one proves that the second add merged into the /// existing entry rather than appending a new one. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRelationship() { // Arrange: Deserialize the test document contents @@ -186,10 +185,10 @@ public void SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRe }); // Assert: Verify the relationship was added only once - Assert.HasCount(1, document.Relationships); - Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); - Assert.AreEqual("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + Assert.Single(document.Relationships); + Assert.Equal("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.Equal("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); } /// @@ -201,7 +200,7 @@ public void SpdxRelationships_AddSingle_DuplicateRelationship_EnhancesExistingRe /// deserialized so the single resulting entry proves the add succeeded without requiring /// the target to be present in the document's element collections. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship() { // Arrange: Deserialize the test document contents @@ -218,10 +217,10 @@ public void SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship() }); // Assert: Verify the relationship was added correctly - Assert.HasCount(1, document.Relationships); - Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); - Assert.AreEqual(SpdxElement.NoAssertion, document.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + Assert.Single(document.Relationships); + Assert.Equal("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.Equal(SpdxElement.NoAssertion, document.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); } /// @@ -233,7 +232,7 @@ public void SpdxRelationships_AddSingle_NoAssertionTarget_AddsRelationship() /// document is deserialized so the single resulting entry proves the add succeeded /// without requiring the external element to appear in the local document. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship() { // Arrange: Deserialize the test document contents @@ -250,10 +249,10 @@ public void SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship() }); // Assert: Verify the relationship was added correctly without requiring the element in the document - Assert.HasCount(1, document.Relationships); - Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); - Assert.AreEqual("DocumentRef-external:SPDXRef-Package-3", document.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + Assert.Single(document.Relationships); + Assert.Equal("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.Equal("DocumentRef-external:SPDXRef-Package-3", document.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); } /// @@ -264,7 +263,7 @@ public void SpdxRelationships_AddSingle_DocumentRefTarget_AddsRelationship() /// one-element array. A fresh document is deserialized so the count assertion unambiguously /// reflects the result of the batch call rather than any pre-existing state. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship() { // Arrange: Deserialize the test document contents @@ -283,10 +282,10 @@ public void SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship() ]); // Assert: Verify the relationship was added correctly - Assert.HasCount(1, document.Relationships); - Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); - Assert.AreEqual("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + Assert.Single(document.Relationships); + Assert.Equal("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.Equal("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); } /// @@ -297,7 +296,7 @@ public void SpdxRelationships_AddMultiple_SingleRelationship_AddsRelationship() /// in the document. A fresh document is deserialized so the final count of one is solely /// the result of the batch call; no pre-existing entries could mask a deduplication failure. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRelationships() { // Arrange: Deserialize the test document contents @@ -322,10 +321,10 @@ public void SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRel ]); // Assert: Verify the relationship was added only once - Assert.HasCount(1, document.Relationships); - Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); - Assert.AreEqual("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + Assert.Single(document.Relationships); + Assert.Equal("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.Equal("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); } /// @@ -337,7 +336,7 @@ public void SpdxRelationships_AddMultiple_DuplicateRelationships_DeduplicatesRel /// before the new relationships are inserted. A fresh document with a single pre-seeded /// relationship is used so the type change from the replacement is unambiguous. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRelationships() { // Arrange: Deserialize the test document contents and add an initial relationship @@ -363,10 +362,10 @@ public void SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRela true); // Assert: Verify the relationship was replaced with the new type - Assert.HasCount(1, document.Relationships); - Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); - Assert.AreEqual("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.BuildToolOf, document.Relationships[0].RelationshipType); + Assert.Single(document.Relationships); + Assert.Equal("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.Equal("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.BuildToolOf, document.Relationships[0].RelationshipType); } /// @@ -378,7 +377,7 @@ public void SpdxRelationships_AddMultiple_Replace_RemovesAndReplacesExistingRela /// A pre-seeded relationship is added before the batch call so the assertion on the /// unchanged collection proves that even the valid first entry in the batch was not committed. /// - [TestMethod] + [Fact] public void SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmodified() { // Arrange: Deserialize the test document and add an initial relationship @@ -394,7 +393,7 @@ public void SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmo var initialRelationships = document.Relationships.ToArray(); // Act: Attempt a batch-add with replace=true where the second relationship has an invalid source ID - Assert.ThrowsExactly(() => + Assert.Throws(() => { SpdxRelationships.Add( document, @@ -416,9 +415,9 @@ public void SpdxRelationships_AddMultiple_InvalidRelationship_LeavesDocumentUnmo }); // Assert: Document relationships are unchanged after the failed batch-add - Assert.HasCount(initialRelationships.Length, document.Relationships); - Assert.AreEqual("SPDXRef-Package-1", document.Relationships[0].Id); - Assert.AreEqual("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); - Assert.AreEqual(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); + Assert.Equal(initialRelationships.Length, document.Relationships.Length); + Assert.Equal("SPDXRef-Package-1", document.Relationships[0].Id); + Assert.Equal("SPDXRef-Package-2", document.Relationships[0].RelatedSpdxElement); + Assert.Equal(SpdxRelationshipType.DependsOn, document.Relationships[0].RelationshipType); } } From af8f58fdb027b7e259efb5e799c7d550f4a90bfb Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Wed, 27 May 2026 10:02:59 -0400 Subject: [PATCH 07/12] ReDoS cleanup --- .../SpdxPackageVerificationCode.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs b/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs index 288c893..933908f 100644 --- a/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs +++ b/src/DemaConsulting.SpdxModel/SpdxPackageVerificationCode.cs @@ -18,6 +18,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using System.Text.RegularExpressions; + namespace DemaConsulting.SpdxModel; /// @@ -32,6 +34,17 @@ namespace DemaConsulting.SpdxModel; /// public sealed class SpdxPackageVerificationCode { + /// + /// Regex for validating SHA-1 hex strings (40 lowercase or uppercase hex digits). + /// + /// + /// The 100 ms timeout guards against ReDoS on untrusted or malformed input. + /// + private static readonly Regex Sha1HexRegex = new( + "^[0-9a-fA-F]{40}$", + RegexOptions.None, + TimeSpan.FromMilliseconds(100)); + /// /// Equality comparer for the same package verification code /// @@ -108,7 +121,7 @@ public void Enhance(SpdxPackageVerificationCode other) public void Validate(string package, List issues) { // Validate Package Verification Code Value Field - if (Value.Length != 40 || !System.Text.RegularExpressions.Regex.IsMatch(Value, "^[0-9a-fA-F]{40}$")) + if (Value.Length != 40 || !Sha1HexRegex.IsMatch(Value)) { issues.Add($"Package '{package}' Invalid Package Verification Code Value '{Value}'"); } From 996147d5b99ffcb377b9dd26971ca4a9d8018396 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Wed, 27 May 2026 13:56:28 -0400 Subject: [PATCH 08/12] Fix file asserts and metadata issues. --- .fileassert.yaml | 243 ++++++++++++++++------------- docs/requirements_report/title.txt | 2 +- 2 files changed, 139 insertions(+), 106 deletions(-) diff --git a/.fileassert.yaml b/.fileassert.yaml index c0ad89c..53468d8 100644 --- a/.fileassert.yaml +++ b/.fileassert.yaml @@ -1,7 +1,7 @@ --- # FileAssert document validation tests for SpdxModel. # Tests are tagged by document group to allow per-group execution during the build pipeline. -# Tags: build-notes, code-quality, code-review, design, user-guide, requirements. +# Tags: build-notes, code-quality, code-review, design, verification, user-guide, requirements. # # NOTE: build-notes through user-guide tests provide OTS evidence for Pandoc and WeasyPrint # and run before ReqStream. The requirements tests run after ReqStream and validate the @@ -12,265 +12,298 @@ tests: # --- BUILD NOTES --- - name: Pandoc_BuildNotesHtml - description: "Build Notes HTML was generated by Pandoc" + description: Build Notes HTML was generated by Pandoc tags: [build-notes] files: - - pattern: "docs/build_notes/build_notes.html" + - pattern: docs/build_notes/generated/build_notes.html count: 1 html: - - query: "//head/title" + - query: //head/title count: 1 text: - - contains: "Build Notes" + - contains: Build Notes - name: WeasyPrint_BuildNotesPdf - description: "Build Notes PDF was generated by WeasyPrint" + description: Build Notes PDF was generated by WeasyPrint tags: [build-notes] files: - - pattern: "docs/SpdxModel Build Notes.pdf" + - pattern: docs/generated/SpdxModel Build Notes.pdf count: 1 pdf: metadata: - - field: "Title" - contains: "SpdxModel" - - field: "Author" - contains: "DEMA Consulting" - - field: "Subject" - contains: "Build notes" + - field: Title + contains: SpdxModel Build Notes + - field: Author + contains: DEMA Consulting + - field: Subject + contains: Build Notes pages: min: 1 text: - - contains: "Build Notes" + - contains: Build Notes # --- CODE QUALITY --- - name: Pandoc_CodeQualityHtml - description: "Code Quality HTML was generated by Pandoc" + description: Code Quality HTML was generated by Pandoc tags: [code-quality] files: - - pattern: "docs/code_quality/quality.html" + - pattern: docs/code_quality/generated/quality.html count: 1 html: - - query: "//head/title" + - query: //head/title count: 1 text: - - contains: "CodeQL" + - contains: Code Quality - name: WeasyPrint_CodeQualityPdf - description: "Code Quality PDF was generated by WeasyPrint" + description: Code Quality PDF was generated by WeasyPrint tags: [code-quality] files: - - pattern: "docs/SpdxModel Code Quality.pdf" + - pattern: docs/generated/SpdxModel Code Quality Report.pdf count: 1 pdf: metadata: - - field: "Title" - contains: "Code Quality" - - field: "Author" - contains: "DEMA Consulting" - - field: "Subject" - contains: "Code Quality" + - field: Title + contains: Code Quality + - field: Author + contains: DEMA Consulting + - field: Subject + contains: Code Quality pages: min: 1 text: - - contains: "CodeQL" + - contains: Code Quality # --- CODE REVIEW PLAN --- - name: Pandoc_ReviewPlanHtml - description: "Code Review Plan HTML was generated by Pandoc" + description: Code Review Plan HTML was generated by Pandoc tags: [code-review] files: - - pattern: "docs/code_review_plan/plan.html" + - pattern: docs/code_review_plan/generated/plan.html count: 1 html: - - query: "//head/title" + - query: //head/title count: 1 text: - - contains: "Review Plan" + - contains: Review Plan - name: WeasyPrint_ReviewPlanPdf - description: "Code Review Plan PDF was generated by WeasyPrint" + description: Code Review Plan PDF was generated by WeasyPrint tags: [code-review] files: - - pattern: "docs/SpdxModel Review Plan.pdf" + - pattern: docs/generated/SpdxModel Code Review Plan.pdf count: 1 pdf: metadata: - - field: "Title" - contains: "Review Plan" - - field: "Author" - contains: "DEMA Consulting" - - field: "Subject" - contains: "Review Plan" + - field: Title + contains: Review Plan + - field: Author + contains: DEMA Consulting + - field: Subject + contains: Review Plan pages: min: 1 text: - - contains: "Review Plan" + - contains: Review Plan # --- CODE REVIEW REPORT --- - name: Pandoc_ReviewReportHtml - description: "Code Review Report HTML was generated by Pandoc" + description: Code Review Report HTML was generated by Pandoc tags: [code-review] files: - - pattern: "docs/code_review_report/report.html" + - pattern: docs/code_review_report/generated/report.html count: 1 html: - - query: "//head/title" + - query: //head/title count: 1 text: - - contains: "Review Report" + - contains: Review Report - name: WeasyPrint_ReviewReportPdf - description: "Code Review Report PDF was generated by WeasyPrint" + description: Code Review Report PDF was generated by WeasyPrint tags: [code-review] files: - - pattern: "docs/SpdxModel Review Report.pdf" + - pattern: docs/generated/SpdxModel Code Review Report.pdf count: 1 pdf: metadata: - - field: "Title" - contains: "Review Report" - - field: "Author" - contains: "DEMA Consulting" - - field: "Subject" - contains: "Review Report" + - field: Title + contains: Review Report + - field: Author + contains: DEMA Consulting + - field: Subject + contains: Review Report pages: min: 1 text: - - contains: "Review Report" + - contains: Review Report # --- DESIGN DOCUMENT --- - name: Pandoc_DesignHtml - description: "Design HTML was generated by Pandoc" + description: Design HTML was generated by Pandoc tags: [design] files: - - pattern: "docs/design/design.html" + - pattern: docs/design/generated/design.html count: 1 html: - - query: "//head/title" + - query: //head/title count: 1 text: - - contains: "Design" + - contains: Design - name: WeasyPrint_DesignPdf - description: "Design PDF was generated by WeasyPrint" + description: Design PDF was generated by WeasyPrint tags: [design] files: - - pattern: "docs/SpdxModel Software Design.pdf" + - pattern: docs/generated/SpdxModel Software Design Document.pdf count: 1 pdf: metadata: - - field: "Title" - contains: "Design" - - field: "Author" - contains: "DEMA Consulting" - - field: "Subject" - contains: "design document" + - field: Title + contains: Design + - field: Author + contains: DEMA Consulting + - field: Subject + contains: Design pages: min: 3 text: - - contains: "Design" + - contains: Design + + # --- VERIFICATION --- + + - name: Pandoc_VerificationHtml + description: Verification HTML was generated by Pandoc + tags: [verification] + files: + - pattern: docs/verification/generated/verification.html + count: 1 + html: + - query: //head/title + count: 1 + text: + - contains: Verification + + - name: WeasyPrint_VerificationPdf + description: Verification PDF was generated by WeasyPrint + tags: [verification] + files: + - pattern: docs/generated/SpdxModel Verification Design Document.pdf + count: 1 + pdf: + metadata: + - field: Title + contains: Verification + - field: Author + contains: DEMA Consulting + - field: Subject + contains: Verification + pages: + min: 3 + text: + - contains: Verification # --- USER GUIDE --- - name: Pandoc_UserGuideHtml - description: "User Guide HTML was generated by Pandoc" + description: User Guide HTML was generated by Pandoc tags: [user-guide] files: - - pattern: "docs/user_guide/user_guide.html" + - pattern: docs/user_guide/generated/user_guide.html count: 1 html: - - query: "//head/title" + - query: //head/title count: 1 text: - - contains: "User Guide" + - contains: User Guide - name: WeasyPrint_UserGuidePdf - description: "User Guide PDF was generated by WeasyPrint" + description: User Guide PDF was generated by WeasyPrint tags: [user-guide] files: - - pattern: "docs/SpdxModel User Guide.pdf" + - pattern: docs/generated/SpdxModel User Guide.pdf count: 1 pdf: metadata: - - field: "Title" - contains: "User Guide" - - field: "Author" - contains: "DEMA Consulting" - - field: "Subject" - contains: "serializing" + - field: Title + contains: User Guide + - field: Author + contains: DEMA Consulting + - field: Subject + contains: User Guide pages: min: 3 text: - - contains: "User Guide" + - contains: User Guide # --- REQUIREMENTS DOCUMENT --- # Note: these tests run after ReqStream and do not contribute to OTS requirements evidence. - name: Pandoc_RequirementsHtml - description: "Requirements HTML was generated by Pandoc" + description: Requirements HTML was generated by Pandoc tags: [requirements] files: - - pattern: "docs/requirements_doc/requirements.html" + - pattern: docs/requirements_doc/generated/requirements.html count: 1 html: - - query: "//head/title" + - query: //head/title count: 1 text: - - contains: "Requirements" + - contains: Requirements - name: WeasyPrint_RequirementsPdf - description: "Requirements PDF was generated by WeasyPrint" + description: Requirements PDF was generated by WeasyPrint tags: [requirements] files: - - pattern: "docs/SpdxModel Requirements.pdf" + - pattern: docs/generated/SpdxModel Requirements Document.pdf count: 1 pdf: metadata: - - field: "Title" - contains: "Requirements" - - field: "Author" - contains: "DEMA Consulting" - - field: "Subject" - contains: "Requirements" + - field: Title + contains: Requirements + - field: Author + contains: DEMA Consulting + - field: Subject + contains: Requirements pages: min: 1 text: - - contains: "Requirements" + - contains: Requirements # --- TRACE MATRIX --- # Note: these tests run after ReqStream and do not contribute to OTS requirements evidence. - name: Pandoc_TraceMatrixHtml - description: "Trace Matrix HTML was generated by Pandoc" + description: Trace Matrix HTML was generated by Pandoc tags: [requirements] files: - - pattern: "docs/requirements_report/trace_matrix.html" + - pattern: docs/requirements_report/generated/trace_matrix.html count: 1 html: - - query: "//head/title" + - query: //head/title count: 1 text: - - contains: "Trace Matrix" + - contains: Trace Matrix - name: WeasyPrint_TraceMatrixPdf - description: "Trace Matrix PDF was generated by WeasyPrint" + description: Trace Matrix PDF was generated by WeasyPrint tags: [requirements] files: - - pattern: "docs/SpdxModel Trace Matrix.pdf" + - pattern: docs/generated/SpdxModel Trace Matrix.pdf count: 1 pdf: metadata: - - field: "Title" - contains: "Trace Matrix" - - field: "Author" - contains: "DEMA Consulting" - - field: "Subject" - contains: "Traceability" + - field: Title + contains: Trace Matrix + - field: Author + contains: DEMA Consulting + - field: Subject + contains: Trace Matrix pages: min: 1 text: - - contains: "Trace Matrix" + - contains: Trace Matrix diff --git a/docs/requirements_report/title.txt b/docs/requirements_report/title.txt index 26fae56..c011281 100644 --- a/docs/requirements_report/title.txt +++ b/docs/requirements_report/title.txt @@ -1,5 +1,5 @@ --- -title: SpdxModel Requirements Report +title: SpdxModel Trace Matrix subtitle: SPDX document model for .NET author: DEMA Consulting description: Trace Matrix for SpdxModel From 9ddd0fd6dbb457e3c8ef6c8cf47c7ce3ada55b14 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Wed, 27 May 2026 16:01:45 -0400 Subject: [PATCH 09/12] Fixes for generated paths and template consistency. --- docs/code_quality/definition.yaml | 8 +++----- docs/design/definition.yaml | 4 +--- docs/verification/definition.yaml | 4 +--- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/code_quality/definition.yaml b/docs/code_quality/definition.yaml index 68c58f2..f1dde0b 100644 --- a/docs/code_quality/definition.yaml +++ b/docs/code_quality/definition.yaml @@ -1,12 +1,10 @@ --- -resource-path: - - docs/code_quality - - docs/template +resource-path: [docs/code_quality, docs/template] input-files: - docs/code_quality/title.txt - docs/code_quality/introduction.md - - docs/code_quality/codeql-quality.md - - docs/code_quality/sonar-quality.md + - docs/code_quality/generated/codeql-quality.md + - docs/code_quality/generated/sonar-quality.md template: template.html table-of-contents: true number-sections: true diff --git a/docs/design/definition.yaml b/docs/design/definition.yaml index 9ce59f5..17f069a 100644 --- a/docs/design/definition.yaml +++ b/docs/design/definition.yaml @@ -1,7 +1,5 @@ --- -resource-path: - - docs/design - - docs/template +resource-path: [docs/design, docs/template] input-files: - docs/design/title.txt - docs/design/introduction.md diff --git a/docs/verification/definition.yaml b/docs/verification/definition.yaml index 60618c1..dbf7a98 100644 --- a/docs/verification/definition.yaml +++ b/docs/verification/definition.yaml @@ -1,7 +1,5 @@ --- -resource-path: - - docs/verification - - docs/template +resource-path: [docs/verification, docs/template] input-files: - docs/verification/title.txt - docs/verification/introduction.md From 647394bf8c70a0273748add236221b037b25bb6e Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Wed, 27 May 2026 19:35:32 -0400 Subject: [PATCH 10/12] Fix PDF file names. --- .github/workflows/build.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a973cfd..1ef2d02 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -519,7 +519,7 @@ jobs: dotnet weasyprint --pdf-variant pdf/a-3u docs/code_quality/generated/quality.html - "docs/generated/SpdxModel Code Quality.pdf" + "docs/generated/SpdxModel Code Quality Report.pdf" - name: Assert Code Quality Documents with FileAssert run: > @@ -581,7 +581,7 @@ jobs: dotnet weasyprint --pdf-variant pdf/a-3u docs/code_review_plan/generated/plan.html - "docs/generated/SpdxModel Review Plan.pdf" + "docs/generated/SpdxModel Code Review Plan.pdf" - name: Generate Review Report HTML with Pandoc shell: bash @@ -598,7 +598,7 @@ jobs: dotnet weasyprint --pdf-variant pdf/a-3u docs/code_review_report/generated/report.html - "docs/generated/SpdxModel Review Report.pdf" + "docs/generated/SpdxModel Code Review Report.pdf" - name: Assert Code Review Documents with FileAssert run: > @@ -630,7 +630,7 @@ jobs: dotnet weasyprint --pdf-variant pdf/a-3u docs/design/generated/design.html - "docs/generated/SpdxModel Software Design.pdf" + "docs/generated/SpdxModel Software Design Document.pdf" - name: Assert Design Documents with FileAssert run: > @@ -661,7 +661,7 @@ jobs: dotnet weasyprint --pdf-variant pdf/a-3u docs/verification/generated/verification.html - "docs/generated/SpdxModel Software Verification Design.pdf" + "docs/generated/SpdxModel Verification Design Document.pdf" - name: Assert Verification Documents with FileAssert run: > @@ -758,7 +758,7 @@ jobs: dotnet weasyprint --pdf-variant pdf/a-3u docs/requirements_doc/generated/requirements.html - "docs/generated/SpdxModel Requirements.pdf" + "docs/generated/SpdxModel Requirements Document.pdf" - name: Generate Trace Matrix HTML with Pandoc shell: bash From 1e34f2b18d8b02f92b90186106de2225c936c93b Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Wed, 27 May 2026 19:52:27 -0400 Subject: [PATCH 11/12] Fix test name reference. --- docs/reqstream/spdx-model/spdx-helpers.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reqstream/spdx-model/spdx-helpers.yaml b/docs/reqstream/spdx-model/spdx-helpers.yaml index 94c811b..43b3e89 100644 --- a/docs/reqstream/spdx-model/spdx-helpers.yaml +++ b/docs/reqstream/spdx-model/spdx-helpers.yaml @@ -38,6 +38,6 @@ sections: - SpdxPackage_Enhance_AddsOrUpdatesPackagesCorrectly - SpdxFile_Enhance_MatchingAndNewFiles_MergesCorrectly - SpdxSnippet_Enhance_MatchingAndNewSnippets_MergesCorrectly - - SpdxCreationInformation_Enhance_AddsOrUpdatesInformationCorrectly + - SpdxCreationInformation_Enhance_WithMissingFieldsInBase_AddsOrUpdatesInformationCorrectly - SpdxHelpers_EnhanceString_ConcretePreferredOverNoAssertion_ReturnsConcreteValue - SpdxHelpers_EnhanceString_NullInputs_ReturnsNull From eef1a978336b8824f9ff3c64d250015d0e2dc5a8 Mon Sep 17 00:00:00 2001 From: Malcolm Nixon Date: Wed, 27 May 2026 19:54:18 -0400 Subject: [PATCH 12/12] Sonar cleanup --- src/DemaConsulting.SpdxModel/SpdxChecksum.cs | 2 +- test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DemaConsulting.SpdxModel/SpdxChecksum.cs b/src/DemaConsulting.SpdxModel/SpdxChecksum.cs index f8a1d07..bf49502 100644 --- a/src/DemaConsulting.SpdxModel/SpdxChecksum.cs +++ b/src/DemaConsulting.SpdxModel/SpdxChecksum.cs @@ -151,7 +151,7 @@ public void Validate(string parent, List issues) { issues.Add($"{parent} Invalid Checksum Algorithm Field - Missing"); } - else if (!Enum.IsDefined(typeof(SpdxChecksumAlgorithm), Algorithm)) + else if (!Enum.IsDefined(Algorithm)) { issues.Add($"{parent} Invalid Checksum Algorithm Field - Unknown"); } diff --git a/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs b/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs index 57190e6..b681d39 100644 --- a/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs +++ b/test/DemaConsulting.SpdxModel.Tests/IO/SpdxModelIOTests.cs @@ -88,6 +88,6 @@ public void SpdxModelIO_ReadSpdxJson_InvalidJson_ThrowsJsonException() var exception = Record.Exception(() => Spdx2JsonDeserializer.Deserialize(malformedJson)); // Assert: The exception must be a JsonException (or derived type such as JsonReaderException) - Assert.IsAssignableFrom(exception); + Assert.IsType(exception, exactMatch: false); } }