diff --git a/.docfx/Dockerfile.docfx b/.docfx/Dockerfile.docfx index aa3281c83..ca8088664 100644 --- a/.docfx/Dockerfile.docfx +++ b/.docfx/Dockerfile.docfx @@ -1,4 +1,4 @@ -ARG NGINX_VERSION=1.29.5-alpine +ARG NGINX_VERSION=1.30.0-alpine FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base RUN rm -rf /usr/share/nginx/html/* diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index c0933e1c6..bbff926c1 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -17,6 +17,28 @@ permissions: contents: read jobs: + init: + name: initialize + runs-on: ubuntu-24.04 + outputs: + run-privileged-jobs: ${{ steps.vars.outputs.run-privileged-jobs }} + strong-name-key-filename: ${{ steps.vars.outputs.strong-name-key-filename }} + build-switches: ${{ steps.vars.outputs.build-switches }} + steps: + - id: vars + name: calculate workflow variables + shell: bash + run: | + if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then + echo "run-privileged-jobs=false" >> "$GITHUB_OUTPUT" + echo "strong-name-key-filename=" >> "$GITHUB_OUTPUT" + echo "build-switches=-p:SkipSignAssembly=true" >> "$GITHUB_OUTPUT" + else + echo "run-privileged-jobs=true" >> "$GITHUB_OUTPUT" + echo "strong-name-key-filename=cuemon.snk" >> "$GITHUB_OUTPUT" + echo "build-switches=" >> "$GITHUB_OUTPUT" + fi + prepare_test: name: 📜 Prepare Test runs-on: ubuntu-24.04 @@ -40,6 +62,7 @@ jobs: build: name: call-build + needs: [init] strategy: matrix: arch: [X64, ARM64] @@ -47,7 +70,8 @@ jobs: uses: codebeltnet/jobs-dotnet-build/.github/workflows/default.yml@v3 with: configuration: ${{ matrix.configuration }} - strong-name-key-filename: cuemon.snk + strong-name-key-filename: ${{ needs.init.outputs.strong-name-key-filename }} + build-switches: ${{ needs.init.outputs.build-switches }} runs-on: ${{ matrix.arch == 'ARM64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} upload-build-artifact-name: build-${{ matrix.configuration }}-${{ matrix.arch }} secrets: @@ -80,6 +104,7 @@ jobs: with: runs-on: ${{ matrix.arch == 'ARM64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} configuration: ${{ matrix.configuration }} + build-switches: -p:SkipSignAssembly=true projects: ${{ matrix.project }} build: true # we need to build due to xUnitv3 restore: true # we need to restore since we disabled caching @@ -98,6 +123,7 @@ jobs: with: runs-on: ${{ matrix.arch == 'ARM64' && 'windows-11-arm' || 'windows-2025' }} configuration: ${{ matrix.configuration }} + build-switches: -p:SkipSignAssembly=true projects: ${{ matrix.project }} test-arguments: -- RunConfiguration.DisableAppDomain=true build: true # we need to build for .net48 @@ -105,8 +131,9 @@ jobs: download-pattern: build-${{ matrix.configuration }}-${{ matrix.arch }} integration_test: + if: ${{ needs.init.outputs.run-privileged-jobs == 'true' }} name: ⚗️ Integration Test - needs: [build] + needs: [init, build] strategy: fail-fast: false matrix: @@ -244,8 +271,9 @@ jobs: command: down sonarcloud: + if: ${{ needs.init.outputs.run-privileged-jobs == 'true' }} name: call-sonarcloud - needs: [build, test_linux, test_windows, integration_test] + needs: [init, build, test_linux, test_windows, integration_test] uses: codebeltnet/jobs-sonarcloud/.github/workflows/default.yml@v3 with: organization: geekle @@ -255,8 +283,9 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} codecov: + if: ${{ needs.init.outputs.run-privileged-jobs == 'true' }} name: call-codecov - needs: [build, test_linux, test_windows, integration_test] + needs: [init, build, test_linux, test_windows, integration_test] uses: codebeltnet/jobs-codecov/.github/workflows/default.yml@v1 with: repository: codebeltnet/cuemon @@ -264,8 +293,9 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} codeql: + if: ${{ needs.init.outputs.run-privileged-jobs == 'true' }} name: call-codeql - needs: [build, test_linux, test_windows, integration_test] + needs: [init, build, test_linux, test_windows, integration_test] uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v3 with: timeout-minutes: 30 diff --git a/.nuget/Cuemon.AspNetCore.App/PackageReleaseNotes.txt b/.nuget/Cuemon.AspNetCore.App/PackageReleaseNotes.txt index f4c3d0e76..88cb5c88a 100644 --- a/.nuget/Cuemon.AspNetCore.App/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.AspNetCore.App/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.AspNetCore.Authentication/PackageReleaseNotes.txt b/.nuget/Cuemon.AspNetCore.Authentication/PackageReleaseNotes.txt index 47dcb0fd2..702f9f8b2 100644 --- a/.nuget/Cuemon.AspNetCore.Authentication/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.AspNetCore.Authentication/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.AspNetCore.Mvc/PackageReleaseNotes.txt b/.nuget/Cuemon.AspNetCore.Mvc/PackageReleaseNotes.txt index 69b177628..9cbb73cdc 100644 --- a/.nuget/Cuemon.AspNetCore.Mvc/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.AspNetCore.Mvc/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.AspNetCore.Razor.TagHelpers/PackageReleaseNotes.txt b/.nuget/Cuemon.AspNetCore.Razor.TagHelpers/PackageReleaseNotes.txt index f4c3d0e76..88cb5c88a 100644 --- a/.nuget/Cuemon.AspNetCore.Razor.TagHelpers/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.AspNetCore.Razor.TagHelpers/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.AspNetCore/PackageReleaseNotes.txt b/.nuget/Cuemon.AspNetCore/PackageReleaseNotes.txt index cb048a715..3d4018eb6 100644 --- a/.nuget/Cuemon.AspNetCore/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.AspNetCore/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Core.App/PackageReleaseNotes.txt b/.nuget/Cuemon.Core.App/PackageReleaseNotes.txt index 5f028037f..eb4976fd3 100644 --- a/.nuget/Cuemon.Core.App/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Core.App/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Core/PackageReleaseNotes.txt b/.nuget/Cuemon.Core/PackageReleaseNotes.txt index d8c5aa0c0..277b84e1f 100644 --- a/.nuget/Cuemon.Core/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Core/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Data.Integrity/PackageReleaseNotes.txt b/.nuget/Cuemon.Data.Integrity/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Data.Integrity/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Data.Integrity/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Data.SqlClient/PackageReleaseNotes.txt b/.nuget/Cuemon.Data.SqlClient/PackageReleaseNotes.txt index ad503555d..a5a58ed99 100644 --- a/.nuget/Cuemon.Data.SqlClient/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Data.SqlClient/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Data/PackageReleaseNotes.txt b/.nuget/Cuemon.Data/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Data/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Data/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Diagnostics/PackageReleaseNotes.txt b/.nuget/Cuemon.Diagnostics/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Diagnostics/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Diagnostics/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.AspNetCore.Authentication/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.AspNetCore.Authentication/PackageReleaseNotes.txt index f4c3d0e76..88cb5c88a 100644 --- a/.nuget/Cuemon.Extensions.AspNetCore.Authentication/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.AspNetCore.Authentication/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json/PackageReleaseNotes.txt index a1a7f9726..bd0259c32 100644 --- a/.nuget/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Text.Json/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/PackageReleaseNotes.txt index b3f038362..757eb65b6 100644 --- a/.nuget/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Xml/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Extensions.AspNetCore.Mvc.RazorPages/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.AspNetCore.Mvc.RazorPages/PackageReleaseNotes.txt index f4c3d0e76..88cb5c88a 100644 --- a/.nuget/Cuemon.Extensions.AspNetCore.Mvc.RazorPages/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.AspNetCore.Mvc.RazorPages/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Extensions.AspNetCore.Mvc/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.AspNetCore.Mvc/PackageReleaseNotes.txt index 00e5601f2..afe25f8b2 100644 --- a/.nuget/Cuemon.Extensions.AspNetCore.Mvc/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.AspNetCore.Mvc/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Extensions.AspNetCore.Text.Json/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.AspNetCore.Text.Json/PackageReleaseNotes.txt index 8716e4727..9056d9097 100644 --- a/.nuget/Cuemon.Extensions.AspNetCore.Text.Json/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.AspNetCore.Text.Json/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Extensions.AspNetCore.Xml/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.AspNetCore.Xml/PackageReleaseNotes.txt index 786d3bd8c..b4abf8c23 100644 --- a/.nuget/Cuemon.Extensions.AspNetCore.Xml/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.AspNetCore.Xml/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Extensions.AspNetCore/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.AspNetCore/PackageReleaseNotes.txt index 6e47635ba..ef54ae100 100644 --- a/.nuget/Cuemon.Extensions.AspNetCore/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.AspNetCore/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10 and .NET 9 # ALM diff --git a/.nuget/Cuemon.Extensions.Collections.Generic/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Collections.Generic/PackageReleaseNotes.txt index 98c27d617..a111af758 100644 --- a/.nuget/Cuemon.Extensions.Collections.Generic/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Collections.Generic/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Collections.Specialized/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Collections.Specialized/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Extensions.Collections.Specialized/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Collections.Specialized/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Core/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Core/PackageReleaseNotes.txt index 7cecb8979..221f46cec 100644 --- a/.nuget/Cuemon.Extensions.Core/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Core/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Data.Integrity/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Data.Integrity/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Extensions.Data.Integrity/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Data.Integrity/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Data/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Data/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Extensions.Data/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Data/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.DependencyInjection/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.DependencyInjection/PackageReleaseNotes.txt index 095ed03f2..fb2292e33 100644 --- a/.nuget/Cuemon.Extensions.DependencyInjection/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.DependencyInjection/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Diagnostics/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Diagnostics/PackageReleaseNotes.txt index 34b9ae1fc..53b4a5478 100644 --- a/.nuget/Cuemon.Extensions.Diagnostics/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Diagnostics/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Hosting/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Hosting/PackageReleaseNotes.txt index 00f219d52..b05966e3c 100644 --- a/.nuget/Cuemon.Extensions.Hosting/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Hosting/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.IO/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.IO/PackageReleaseNotes.txt index e6756089d..6fd8a9d93 100644 --- a/.nuget/Cuemon.Extensions.IO/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.IO/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9, .NET Standard 2.1 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Net/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Net/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Extensions.Net/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Net/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Reflection/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Reflection/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Extensions.Reflection/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Reflection/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Runtime.Caching/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Runtime.Caching/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Extensions.Runtime.Caching/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Runtime.Caching/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Text.Json/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Text.Json/PackageReleaseNotes.txt index 03816ccd6..40db7e0eb 100644 --- a/.nuget/Cuemon.Extensions.Text.Json/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Text.Json/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Text/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Text/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Extensions.Text/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Text/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Threading/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Threading/PackageReleaseNotes.txt index 9fb0aafa4..541baba06 100644 --- a/.nuget/Cuemon.Extensions.Threading/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Threading/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Extensions.Xml/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Xml/PackageReleaseNotes.txt index ee4f83ddc..4251ce8e7 100644 --- a/.nuget/Cuemon.Extensions.Xml/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Xml/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.IO/PackageReleaseNotes.txt b/.nuget/Cuemon.IO/PackageReleaseNotes.txt index a83d99e94..a6bcff0bf 100644 --- a/.nuget/Cuemon.IO/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.IO/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Kernel/PackageReleaseNotes.txt b/.nuget/Cuemon.Kernel/PackageReleaseNotes.txt index 45d0153b2..e88e64392 100644 --- a/.nuget/Cuemon.Kernel/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Kernel/PackageReleaseNotes.txt @@ -1,4 +1,13 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +# New Features +- ADDED additional features to the Kernel surface that provides the essential Cuemon.Core substrate + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Net/PackageReleaseNotes.txt b/.nuget/Cuemon.Net/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Net/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Net/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Resilience/PackageReleaseNotes.txt b/.nuget/Cuemon.Resilience/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Resilience/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Resilience/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Runtime.Caching/PackageReleaseNotes.txt b/.nuget/Cuemon.Runtime.Caching/PackageReleaseNotes.txt index 1e4007a55..28b410e66 100644 --- a/.nuget/Cuemon.Runtime.Caching/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Runtime.Caching/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Security.Cryptography/PackageReleaseNotes.txt b/.nuget/Cuemon.Security.Cryptography/PackageReleaseNotes.txt index 4d0301ad0..c1259369d 100644 --- a/.nuget/Cuemon.Security.Cryptography/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Security.Cryptography/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Threading/PackageReleaseNotes.txt b/.nuget/Cuemon.Threading/PackageReleaseNotes.txt index 206979be6..a9f8ef0f2 100644 --- a/.nuget/Cuemon.Threading/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Threading/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/.nuget/Cuemon.Xml/PackageReleaseNotes.txt b/.nuget/Cuemon.Xml/PackageReleaseNotes.txt index e53238b97..a4eaf1825 100644 --- a/.nuget/Cuemon.Xml/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Xml/PackageReleaseNotes.txt @@ -1,4 +1,14 @@ -Version: 10.5.0 +Version: 10.5.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + +# Bug Fixes +- FIXED HierarchyDecoratorExtensions in the Cuemon.Xml namespace where TryGetXmlRootAttribute now falls back to checking InstanceType when MemberReference is null +- FIXED XmlConverterDecoratorExtensions in the Cuemon.Xml.Serialization.Converters namespace to simplify collection writing logic + +Version: 10.5.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 # ALM diff --git a/CHANGELOG.md b/CHANGELOG.md index ff0ee1042..e112a247a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. +## [10.5.1] - 2026-04-16 + +This is a patch release focused on kernel assembly refinement, XML serialization reliability, and comprehensive test coverage expansion. The release relocates foundational configuration types into the kernel layer while maintaining full backward compatibility through type forwarding. + +### Changed + +- `IConfigurable` and `Configurable` types relocated from `Cuemon.Core` to `Cuemon.Kernel` for improved architectural layering; `Cuemon.Core` maintains backward compatibility through `[TypeForwardedTo]` attributes, +- `XmlConverterDecoratorExtensions` class in the Cuemon.Extensions.Xml.Serialization.Converters namespace with simplified collection writing logic for improved maintainability, +- All package dependencies updated to latest compatible versions. + +### Added + +- Comprehensive unit tests for XML serialization converters in `Cuemon.Xml.Tests` covering `XmlConverter`, `DefaultXmlConverter`, `ExceptionConverter`, `FailureConverter`, `DynamicXmlConverter`, and related functionality, +- Test assets `LinkResponse` and `WrapperResponse` in `Cuemon.Xml.Tests` for XML serialization validation. + +### Fixed + +- `DefaultXmlConverter` class in the `Cuemon.Xml.Serialization.Converters` namespace to add `XmlRootAttribute` fallback when `MemberReference` is null, improving robustness in edge-case serialization scenarios. + ## [10.5.0] - 2026-03-13 Okay, so this might look like a major release — and effort-wise it certainly felt like one — but it's actually a fully backward-compatible minor bump. Thanks to a helping hand from AI and a long-standing wish to carve out a small, lean core from Cuemon, we've introduced a brand-new assembly: **Cuemon.Kernel**. Think of it as the nano (or at least noticeably smaller) sibling of `Cuemon.Core`, shipping only the most essential and much-loved types — validators, conditions, decorators, disposable patterns, the options/parameter-object infrastructure, text encoding helpers and async primitives. @@ -1710,3 +1729,14 @@ This release was primarily focused on adapting a more modern way of performing C - XmlUtilityExtensions class from the Cuemon.Xml namespace - XmlWriterUtility class from Cuemon.Xml namespace - XmlWriterUtilityExtensions class from the Cuemon.Xml namespace + +[10.5.1]: https://github.com/codebeltnet/cuemon/compare/v10.5.0...v10.5.1 +[10.5.0]: https://github.com/codebeltnet/cuemon/compare/v10.4.0...v10.5.0 +[10.4.0]: https://github.com/codebeltnet/cuemon/compare/v10.3.0...v10.4.0 +[10.3.0]: https://github.com/codebeltnet/cuemon/compare/v10.2.1...v10.3.0 +[10.2.1]: https://github.com/codebeltnet/cuemon/compare/v10.2.0...v10.2.1 +[10.2.0]: https://github.com/codebeltnet/cuemon/compare/v10.1.2...v10.2.0 +[10.1.2]: https://github.com/codebeltnet/cuemon/compare/v10.1.1...v10.1.2 +[10.1.1]: https://github.com/codebeltnet/cuemon/compare/v10.1.0...v10.1.1 +[10.1.0]: https://github.com/codebeltnet/cuemon/compare/v10.0.0...v10.1.0 +[10.0.0]: https://github.com/codebeltnet/cuemon/releases/tag/v10.0.0 diff --git a/Directory.Packages.props b/Directory.Packages.props index d827e75f8..f963476f2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,49 +6,49 @@ - - - - + + + + - + - - + + - + - - - - - + + + + + - + - - - - - + + + + + - - - - - + + + + + - + \ No newline at end of file diff --git a/src/Cuemon.Core/Configuration/Configurable.cs b/src/Cuemon.Core/Configuration/Configurable.cs deleted file mode 100644 index 3afff80c4..000000000 --- a/src/Cuemon.Core/Configuration/Configurable.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace Cuemon.Configuration -{ - /// - /// Provides a generic way to support the options pattern on a class level. - /// - /// The type of the configured options. - /// - public abstract class Configurable : IConfigurable where TOptions : class, IParameterObject, new() - { - /// - /// Initializes a new instance of the class. - /// - /// The configured options of this instance. - /// - /// cannot be null. - /// - /// - /// are not in a valid state. - /// - protected Configurable(TOptions options) - { - Validator.ThrowIfInvalidOptions(options); - Options = options; - } - - /// - /// Gets the configured options of this instance. - /// - /// The configured options of this instance. - public TOptions Options { get; } - } -} diff --git a/src/Cuemon.Core/Configuration/IConfigurable.cs b/src/Cuemon.Core/Configuration/IConfigurable.cs deleted file mode 100644 index 3da5a7ae8..000000000 --- a/src/Cuemon.Core/Configuration/IConfigurable.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Cuemon.Configuration -{ - /// - /// Provides a generic way to support the options pattern on a class level. - /// - /// The type of the configured options. - public interface IConfigurable where TOptions : class, IParameterObject, new() - { - /// - /// Gets the configured options of this instance. - /// - /// The configured options of this instance. - TOptions Options { get; } - } -} \ No newline at end of file diff --git a/src/Cuemon.Core/Properties/AssemblyInfo.cs b/src/Cuemon.Core/Properties/AssemblyInfo.cs index fcf48d89a..8f5893525 100644 --- a/src/Cuemon.Core/Properties/AssemblyInfo.cs +++ b/src/Cuemon.Core/Properties/AssemblyInfo.cs @@ -79,6 +79,8 @@ [assembly: TypeForwardedTo(typeof(UriScheme))] [assembly: TypeForwardedTo(typeof(UriStringOptions))] [assembly: TypeForwardedTo(typeof(Validator))] +[assembly: TypeForwardedTo(typeof(IConfigurable<>))] +[assembly: TypeForwardedTo(typeof(Configurable<>))] #if !NETCOREAPP3_0_OR_GREATER [assembly: TypeForwardedTo(typeof(CallerArgumentExpressionAttribute))] #endif diff --git a/src/Cuemon.Kernel/Configuration/Configurable.cs b/src/Cuemon.Kernel/Configuration/Configurable.cs new file mode 100644 index 000000000..b7203b0fe --- /dev/null +++ b/src/Cuemon.Kernel/Configuration/Configurable.cs @@ -0,0 +1,33 @@ +using System; + +namespace Cuemon.Configuration; + +/// +/// Provides a generic way to support the options pattern on a class level. +/// +/// The type of the configured options. +/// +public abstract class Configurable : IConfigurable where TOptions : class, IParameterObject, new() +{ + /// + /// Initializes a new instance of the class. + /// + /// The configured options of this instance. + /// + /// cannot be null. + /// + /// + /// are not in a valid state. + /// + protected Configurable(TOptions options) + { + Validator.ThrowIfInvalidOptions(options); + Options = options; + } + + /// + /// Gets the configured options of this instance. + /// + /// The configured options of this instance. + public TOptions Options { get; } +} diff --git a/src/Cuemon.Kernel/Configuration/IConfigurable.cs b/src/Cuemon.Kernel/Configuration/IConfigurable.cs new file mode 100644 index 000000000..be3eecbf5 --- /dev/null +++ b/src/Cuemon.Kernel/Configuration/IConfigurable.cs @@ -0,0 +1,14 @@ +namespace Cuemon.Configuration; + +/// +/// Provides a generic way to support the options pattern on a class level. +/// +/// The type of the configured options. +public interface IConfigurable where TOptions : class, IParameterObject, new() +{ + /// + /// Gets the configured options of this instance. + /// + /// The configured options of this instance. + TOptions Options { get; } +} diff --git a/src/Cuemon.Xml/Extensions/HierarchyDecoratorExtensions.cs b/src/Cuemon.Xml/Extensions/HierarchyDecoratorExtensions.cs index 8c48a1345..4ff5e6812 100644 --- a/src/Cuemon.Xml/Extensions/HierarchyDecoratorExtensions.cs +++ b/src/Cuemon.Xml/Extensions/HierarchyDecoratorExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -124,7 +124,9 @@ public static bool TryGetXmlAttributeAttribute(this IDecoratortrue if underlying of the contains an , false otherwise. public static bool TryGetXmlRootAttribute(this IDecorator> decorator, out XmlRootAttribute xmlAttribute) { - xmlAttribute = decorator.Inner.HasMemberReference ? decorator.Inner.MemberReference.GetCustomAttribute(true) : null; + xmlAttribute = decorator.Inner.HasMemberReference + ? decorator.Inner.MemberReference.GetCustomAttribute(true) + : decorator.Inner.InstanceType.GetCustomAttribute(true); return xmlAttribute != null; } @@ -169,4 +171,4 @@ public static IEnumerable> OrderByXmlAttributes(this IDecorator return attributes.Concat(rest); } } -} \ No newline at end of file +} diff --git a/src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs b/src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs index f1fd4f50c..263be9c59 100644 --- a/src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs +++ b/src/Cuemon.Xml/Extensions/Serialization/Converters/XmlConverterDecoratorExtensions.cs @@ -138,16 +138,14 @@ public static IDecorator> AddEnumerableConverter(this IDecor } else { - var isDocumentRoot = w.WriteState == WriteState.Start; - if (isDocumentRoot) { w.WriteStartElement(q.Prefix, q.LocalName, q.Namespace); } - var qe = new XmlQualifiedEntity(q.Prefix, q.LocalName, q.Namespace); + w.WriteStartElement(q.Prefix, q.LocalName, q.Namespace); foreach (var item in o) { if (item == null) { continue; } var itemType = item.GetType(); if (Decorator.Enclose(itemType).IsComplex()) { - Decorator.Enclose(w).WriteObject(item, itemType, opts => opts.Settings.RootName = qe); + Decorator.Enclose(w).WriteObject(item, itemType); } else { @@ -156,7 +154,7 @@ public static IDecorator> AddEnumerableConverter(this IDecor w.WriteEndElement(); } } - if (isDocumentRoot) { w.WriteEndElement(); } + w.WriteEndElement(); } } else diff --git a/test/Cuemon.Core.Tests/Reflection/AssemblyDecoratorExtensionsTest.cs b/test/Cuemon.Core.Tests/Reflection/AssemblyDecoratorExtensionsTest.cs index 4eb0290c5..bfbd5aeb7 100644 --- a/test/Cuemon.Core.Tests/Reflection/AssemblyDecoratorExtensionsTest.cs +++ b/test/Cuemon.Core.Tests/Reflection/AssemblyDecoratorExtensionsTest.cs @@ -32,18 +32,18 @@ public void GetTypes_ShouldReturnAllTypesFromCuemonCore() var allTypes = Decorator.Enclose(a).GetTypes(); var disposableTypes = Decorator.Enclose(a).GetTypes(typeFilter: typeof(Disposable)); - var configurationTypes = Decorator.Enclose(a).GetTypes($"{nameof(Cuemon)}.{nameof(Configuration)}"); + var threadingTypes = Decorator.Enclose(a).GetTypes($"{nameof(Cuemon)}.{nameof(Threading)}"); var allTypesCount = Decorator.Enclose(allTypes).Inner.Count(); var disposableTypesCount = Decorator.Enclose(disposableTypes).Inner.Count(); - var configurationTypesCount = Decorator.Enclose(configurationTypes).Inner.Count(); + var configurationTypesCount = Decorator.Enclose(threadingTypes).Inner.Count(); TestOutput.WriteLine(disposableTypes.ToDelimitedString()); - TestOutput.WriteLine(configurationTypes.ToDelimitedString()); + TestOutput.WriteLine(threadingTypes.ToDelimitedString()); Assert.InRange(allTypesCount, 250, 300); // range because of tooling on CI adding dynamic types and high range of refactoring Assert.Equal(3, disposableTypesCount); - Assert.Equal(2, configurationTypesCount); + Assert.Equal(4, configurationTypesCount); } [Fact] diff --git a/test/Cuemon.Xml.Tests/Assets/LinkResponse.cs b/test/Cuemon.Xml.Tests/Assets/LinkResponse.cs new file mode 100644 index 000000000..ed8d1eefb --- /dev/null +++ b/test/Cuemon.Xml.Tests/Assets/LinkResponse.cs @@ -0,0 +1,21 @@ +using System.Xml.Serialization; + +namespace Cuemon.Xml.Assets; + +[XmlRoot("Link")] +public record LinkResponse +{ + public LinkResponse(string href, string rel) + { + Href = href; + Rel = rel; + } + + public string Href { get; set; } + + public string Rel { get; set; } + + public string Title { get; set; } + + public string Hreflang { get; set; } +} diff --git a/test/Cuemon.Xml.Tests/Assets/WrapperResponse.cs b/test/Cuemon.Xml.Tests/Assets/WrapperResponse.cs new file mode 100644 index 000000000..46568de89 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Assets/WrapperResponse.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Cuemon.Xml.Assets; + +public class WrapperResponse +{ + public WrapperResponse() + { + } + + public IEnumerable Links { get; set; } = new List() + { + { + new LinkResponse("https://example.com", "self") + }, + { + new LinkResponse("https://example.com/other", "related") + } + }; +} diff --git a/test/Cuemon.Xml.Tests/Extensions/Serialization/Converters/XmlConverterDecoratorExtensionsTest.cs b/test/Cuemon.Xml.Tests/Extensions/Serialization/Converters/XmlConverterDecoratorExtensionsTest.cs new file mode 100644 index 000000000..9619d6212 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Extensions/Serialization/Converters/XmlConverterDecoratorExtensionsTest.cs @@ -0,0 +1,435 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using Codebelt.Extensions.Xunit; +using Cuemon.Diagnostics; +using Cuemon.Extensions.IO; +using Xunit; + +namespace Cuemon.Xml.Serialization.Converters +{ + public class XmlConverterDecoratorExtensionsTest : Test + { + public XmlConverterDecoratorExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + private static string SerializeWithConverters(object value, Type type, Action>> configure) + { + var converters = new List(); + configure(Decorator.Enclose(converters)); + var options = new XmlSerializerOptions(); + foreach (var c in converters) { options.Converters.Add(c); } + var serializer = XmlSerializer.Create(options); + var result = serializer.Serialize(value, type); + return result.ToEncodedString(); + } + + [Fact] + public void FirstOrDefaultReaderConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.FirstOrDefaultReaderConverter(null, typeof(string))); + } + + [Fact] + public void FirstOrDefaultWriterConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.FirstOrDefaultWriterConverter(null, typeof(string))); + } + + [Fact] + public void FirstOrDefaultReaderConverter_ShouldReturnNullWhenNoConverterFound() + { + var converters = new List(); + var result = Decorator.Enclose(converters).FirstOrDefaultReaderConverter(typeof(string)); + Assert.Null(result); + } + + [Fact] + public void FirstOrDefaultWriterConverter_ShouldReturnNullWhenNoConverterFound() + { + var converters = new List(); + var result = Decorator.Enclose(converters).FirstOrDefaultWriterConverter(typeof(string)); + Assert.Null(result); + } + + [Fact] + public void FirstOrDefaultReaderConverter_ShouldReturnMatchingConverter() + { + var converters = new List(); + Decorator.Enclose(converters).AddExceptionConverter(false, false); + + var result = Decorator.Enclose(converters).FirstOrDefaultReaderConverter(typeof(InvalidOperationException)); + + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public void FirstOrDefaultWriterConverter_ShouldReturnMatchingConverter() + { + var converters = new List(); + Decorator.Enclose(converters).AddExceptionConverter(false, false); + + var result = Decorator.Enclose(converters).FirstOrDefaultWriterConverter(typeof(InvalidOperationException)); + + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public void FirstOrDefaultReaderConverter_ShouldSkipWriteOnlyConverter() + { + var converters = new List(); + Decorator.Enclose(converters).AddFailureConverter(); + + var result = Decorator.Enclose(converters).FirstOrDefaultReaderConverter(typeof(Failure)); + + Assert.Null(result); + } + + [Fact] + public void AddXmlConverter_ShouldAddGenericConverter() + { + var converters = new List(); + Decorator.Enclose(converters).AddXmlConverter(writer: (w, v, q) => { }); + + Assert.Single(converters); + } + + [Fact] + public void InsertXmlConverter_ShouldInsertAtSpecifiedIndex() + { + var converters = new List(); + Decorator.Enclose(converters).AddExceptionConverter(false, false); + Decorator.Enclose(converters).InsertXmlConverter(0, writer: (w, v, q) => { }); + + Assert.Equal(2, converters.Count); + Assert.IsType(converters[0]); + Assert.IsType(converters[1]); + } + + [Fact] + public void AddEnumerableConverter_ShouldSerializeEnumerable() + { + var xml = SerializeWithConverters( + new[] { 1, 2, 3 }, typeof(int[]), + d => d.AddEnumerableConverter()); + + TestOutput.WriteLine(xml); + Assert.Contains("1", xml); + Assert.Contains("2", xml); + Assert.Contains("3", xml); + } + + [Fact] + public void AddEnumerableConverter_ShouldSerializeDictionary() + { + var dict = new Dictionary { { "A", 1 }, { "B", 2 } }; + var xml = SerializeWithConverters(dict, typeof(Dictionary), d => d.AddEnumerableConverter()); + + TestOutput.WriteLine(xml); + Assert.Contains("name=\"A\"", xml); + Assert.Contains("name=\"B\"", xml); + Assert.Contains(">1<", xml); + Assert.Contains(">2<", xml); + } + + [Fact] + public void AddEnumerableConverter_WithFlattenItems_ShouldUseDictionaryKeyAsElementName() + { + var dict = new Dictionary { { "Population", 100 } }; + var options = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("Stats") }; + Decorator.Enclose(options.Converters).AddEnumerableConverter(flattenItems: true); + var serializer = XmlSerializer.Create(options); + var result = serializer.Serialize(dict, typeof(Dictionary)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.DoesNotContain("name=", xml); + Assert.Contains("100", xml); + } + + [Fact] + public void AddExceptionConverter_WithStackTrace_ShouldIncludeStack() + { + Exception caught = null; + try { throw new InvalidOperationException("Stack test"); } catch (Exception ex) { caught = ex; } + + var xml = SerializeWithConverters(caught, typeof(InvalidOperationException), + d => d.AddExceptionConverter(includeStackTrace: true, includeData: false)); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + } + + [Fact] + public void AddExceptionConverter_WithData_ShouldIncludeData() + { + var ex = new InvalidOperationException("Data test"); + ex.Data.Add("Key1", "Val1"); + + var xml = SerializeWithConverters(ex, typeof(InvalidOperationException), + d => d.AddExceptionConverter(includeStackTrace: false, includeData: true)); + + TestOutput.WriteLine(xml); + Assert.Contains("Val1", xml); + } + + [Fact] + public void AddFailureConverter_ShouldAddWriteOnlyConverter() + { + var converters = new List(); + Decorator.Enclose(converters).AddFailureConverter(); + + Assert.Single(converters); + Assert.IsType(converters[0]); + Assert.False(converters[0].CanRead); + Assert.True(converters[0].CanWrite); + } + + [Fact] + public void AddUriConverter_ShouldSerializeUri() + { + var xml = SerializeWithConverters( + new Uri("https://example.com/"), + typeof(Uri), + d => d.AddUriConverter()); + + TestOutput.WriteLine(xml); + Assert.Contains("https://example.com/", xml); + } + + [Fact] + public void AddUriConverter_ShouldDeserializeUri() + { + var converters = new List(); + Decorator.Enclose(converters).AddUriConverter(); + var options = new XmlSerializerOptions(); + foreach (var c in converters) { options.Converters.Add(c); } + var serializer = XmlSerializer.Create(options); + var stream = serializer.Serialize(new Uri("https://example.com/"), typeof(Uri)); + stream.Position = 0; + + var result = serializer.Deserialize(stream); + + Assert.Equal("https://example.com/", result.OriginalString); + } + + [Fact] + public void AddDateTimeConverter_ShouldSerializeDateTime() + { + var dt = new DateTime(2023, 6, 15, 0, 0, 0, DateTimeKind.Utc); + var xml = SerializeWithConverters(dt, typeof(DateTime), d => d.AddDateTimeConverter()); + + TestOutput.WriteLine(xml); + Assert.Contains("2023-06-15T00:00:00.0000000Z", xml); + } + + [Fact] + public void AddDateTimeConverter_ShouldDeserializeDateTime() + { + var converters = new List(); + Decorator.Enclose(converters).AddDateTimeConverter(); + var options = new XmlSerializerOptions(); + foreach (var c in converters) { options.Converters.Add(c); } + var serializer = XmlSerializer.Create(options); + var dt = new DateTime(2023, 6, 15, 0, 0, 0, DateTimeKind.Utc); + var stream = serializer.Serialize(dt, typeof(DateTime)); + stream.Position = 0; + + var result = serializer.Deserialize(stream); + + Assert.Equal(dt, result); + } + + [Fact] + public void AddTimeSpanConverter_ShouldSerializeTimeSpan() + { + var ts = new TimeSpan(1, 2, 3); + var xml = SerializeWithConverters(ts, typeof(TimeSpan), d => d.AddTimeSpanConverter()); + + TestOutput.WriteLine(xml); + Assert.Contains("01:02:03", xml); + } + + [Fact] + public void AddTimeSpanConverter_ShouldDeserializeTimeSpan() + { + var converters = new List(); + Decorator.Enclose(converters).AddTimeSpanConverter(); + var options = new XmlSerializerOptions(); + foreach (var c in converters) { options.Converters.Add(c); } + var serializer = XmlSerializer.Create(options); + var ts = new TimeSpan(1, 2, 3); + var stream = serializer.Serialize(ts, typeof(TimeSpan)); + stream.Position = 0; + + var result = serializer.Deserialize(stream); + + Assert.Equal(ts, result); + } + + [Fact] + public void AddStringConverter_ShouldSerializePlainString() + { + var xml = SerializeWithConverters("Hello World", typeof(string), d => d.AddStringConverter()); + + TestOutput.WriteLine(xml); + Assert.Contains("Hello World", xml); + } + + [Fact] + public void AddStringConverter_ShouldWrapXmlStringInCData() + { + var xmlContent = "value"; + var xml = SerializeWithConverters(xmlContent, typeof(string), d => d.AddStringConverter()); + + TestOutput.WriteLine(xml); + Assert.Contains("(); + Decorator.Enclose(converters).AddStringConverter(); + var options = new XmlSerializerOptions(); + foreach (var c in converters) { options.Converters.Add(c); } + var serializer = XmlSerializer.Create(options); + var result = serializer.Serialize(" ", typeof(string)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.DoesNotContain("", xml); + } + + [Fact] + public void AddExceptionDescriptorConverter_ShouldSerializeDescriptorWithError() + { + Exception caught = null; + try { throw new InvalidOperationException("Descriptor error"); } catch (Exception ex) { caught = ex; } + + var descriptor = new ExceptionDescriptor(caught, "ERR001", "An error occurred."); + var xml = SerializeWithConverters( + descriptor, + typeof(ExceptionDescriptor), + d => d.AddExceptionDescriptorConverter(o => o.SensitivityDetails = FaultSensitivityDetails.None)); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + Assert.Contains("ERR001", xml); + Assert.Contains("An error occurred.", xml); + Assert.DoesNotContain("", xml); + } + + [Fact] + public void AddExceptionDescriptorConverter_ShouldSerializeFailureWhenRequested() + { + Exception caught = null; + try { throw new InvalidOperationException("With failure"); } catch (Exception ex) { caught = ex; } + + var descriptor = new ExceptionDescriptor(caught, "ERR002", "Failure included."); + var xml = SerializeWithConverters( + descriptor, + typeof(ExceptionDescriptor), + d => d.AddExceptionDescriptorConverter(o => o.SensitivityDetails = FaultSensitivityDetails.Failure)); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + Assert.Contains(" v); + + var xml = SerializeWithConverters( + descriptor, + typeof(ExceptionDescriptor), + d => d.AddExceptionDescriptorConverter(o => o.SensitivityDetails = FaultSensitivityDetails.Evidence)); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + Assert.Contains("abc-123", xml); + } + + [Fact] + public void AddXmlConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddXmlConverter(null, writer: (w, v, q) => { })); + } + + [Fact] + public void InsertXmlConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.InsertXmlConverter(null, 0, writer: (w, v, q) => { })); + } + + [Fact] + public void AddEnumerableConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddEnumerableConverter(null)); + } + + [Fact] + public void AddExceptionConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddExceptionConverter(null, false, false)); + } + + [Fact] + public void AddFailureConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddFailureConverter(null)); + } + + [Fact] + public void AddUriConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddUriConverter(null)); + } + + [Fact] + public void AddDateTimeConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddDateTimeConverter(null)); + } + + [Fact] + public void AddTimeSpanConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddTimeSpanConverter(null)); + } + + [Fact] + public void AddStringConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddStringConverter(null)); + } + + [Fact] + public void AddExceptionDescriptorConverter_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlConverterDecoratorExtensions.AddExceptionDescriptorConverter(null, o => { })); + } + } +} diff --git a/test/Cuemon.Xml.Tests/Extensions/Serialization/XmlSerializerOptionsDecoratorExtensionsTest.cs b/test/Cuemon.Xml.Tests/Extensions/Serialization/XmlSerializerOptionsDecoratorExtensionsTest.cs new file mode 100644 index 000000000..3c9c44cb4 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Extensions/Serialization/XmlSerializerOptionsDecoratorExtensionsTest.cs @@ -0,0 +1,62 @@ +using Codebelt.Extensions.Xunit; +using Xunit; + +namespace Cuemon.Xml.Serialization +{ + [Collection(nameof(XmlConvertDefaultSettingsCollection))] + public class XmlSerializerOptionsDecoratorExtensionsTest : Test + { + public XmlSerializerOptionsDecoratorExtensionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void ApplyToDefaultSettings_ShouldSetXmlConvertDefaultSettings() + { + var original = XmlConvert.DefaultSettings; + try + { + var options = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("Applied") }; + + Decorator.Enclose(options).ApplyToDefaultSettings(); + + Assert.NotNull(XmlConvert.DefaultSettings); + var result = XmlConvert.DefaultSettings(); + Assert.Same(options, result); + Assert.Equal("Applied", result.RootName.LocalName); + } + finally + { + XmlConvert.DefaultSettings = original; + } + } + + [Fact] + public void ApplyToDefaultSettings_WithNullDecorator_ShouldThrowArgumentNullException() + { + Assert.Throws(() => + XmlSerializerOptionsDecoratorExtensions.ApplyToDefaultSettings(null)); + } + + [Fact] + public void ApplyToDefaultSettings_CalledTwice_ShouldOverwritePreviousSettings() + { + var original = XmlConvert.DefaultSettings; + try + { + var options1 = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("First") }; + var options2 = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("Second") }; + + Decorator.Enclose(options1).ApplyToDefaultSettings(); + Decorator.Enclose(options2).ApplyToDefaultSettings(); + + var result = XmlConvert.DefaultSettings(); + Assert.Equal("Second", result.RootName.LocalName); + } + finally + { + XmlConvert.DefaultSettings = original; + } + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/Converters/DefaultXmlConverterTest.cs b/test/Cuemon.Xml.Tests/Serialization/Converters/DefaultXmlConverterTest.cs new file mode 100644 index 000000000..171973ece --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/Converters/DefaultXmlConverterTest.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using Codebelt.Extensions.Xunit; +using Cuemon.Extensions.IO; +using Xunit; + +namespace Cuemon.Xml.Serialization.Converters +{ + public class DefaultXmlConverterTest : Test + { + public DefaultXmlConverterTest(ITestOutputHelper output) : base(output) + { + } + + private static string SerializeWithDefault(object value, Type type, XmlQualifiedEntity rootName = null) + { + var converter = new DefaultXmlConverter(rootName, new List()); + var ms = new MemoryStream(); + var settings = new XmlWriterSettings { OmitXmlDeclaration = true }; + using (var writer = XmlWriter.Create(ms, settings)) + { + converter.WriteXml(writer, value, null); + } + ms.Position = 0; + return ms.ToEncodedString(); + } + + private static object DeserializeWithDefault(string xml, Type type, XmlQualifiedEntity rootName = null) + { + var converter = new DefaultXmlConverter(rootName, new List()); + using var reader = XmlReader.Create(new StringReader(xml)); + return converter.ReadXml(reader, type); + } + + [Fact] + public void CanConvert_ShouldAlwaysReturnTrue() + { + var sut = new DefaultXmlConverter(null, new List()); + Assert.True(sut.CanConvert(typeof(string))); + Assert.True(sut.CanConvert(typeof(int))); + Assert.True(sut.CanConvert(typeof(object))); + } + + [Fact] + public void WriteXml_ShouldSerializePrimitive_Int() + { + var xml = SerializeWithDefault(42, typeof(int), new XmlQualifiedEntity("Int32")); + TestOutput.WriteLine(xml); + Assert.Contains("42", xml); + } + + [Fact] + public void WriteXml_ShouldSerializePrimitive_Bool() + { + var xml = SerializeWithDefault(true, typeof(bool), new XmlQualifiedEntity("Boolean")); + TestOutput.WriteLine(xml); + Assert.Contains("true", xml); + } + + [Fact] + public void WriteXml_ShouldSerializeString() + { + var xml = SerializeWithDefault("Hello World", typeof(string), new XmlQualifiedEntity("String")); + TestOutput.WriteLine(xml); + Assert.Contains("Hello World", xml); + } + + [Fact] + public void WriteXml_ShouldWrapXmlStringInCData() + { + var xmlString = "Test"; + var xml = SerializeWithDefault(xmlString, typeof(string), new XmlQualifiedEntity("String")); + TestOutput.WriteLine(xml); + Assert.Contains("Test", xml); + Assert.Contains("99", xml); + } + + [Fact] + public void WriteXml_ShouldRespectXmlIgnoreAttribute() + { + var obj = new ModelWithIgnored { Name = "Visible", Ignored = "NotVisible" }; + var xml = SerializeWithDefault(obj, typeof(ModelWithIgnored)); + TestOutput.WriteLine(xml); + Assert.Contains("Visible", xml); + Assert.DoesNotContain("NotVisible", xml); + } + + [Fact] + public void WriteXml_ShouldRespectXmlAttributeAttribute() + { + var obj = new ModelWithXmlAttribute { Name = "AttrValue" }; + var xml = SerializeWithDefault(obj, typeof(ModelWithXmlAttribute)); + TestOutput.WriteLine(xml); + Assert.Contains("Name=\"AttrValue\"", xml); + } + + [Fact] + public void WriteXml_ShouldSerializeIXmlSerializable() + { + var obj = new XmlSerializableModel("CustomValue"); + var xml = SerializeWithDefault(obj, typeof(XmlSerializableModel)); + TestOutput.WriteLine(xml); + Assert.Contains("CustomValue", xml); + } + + [Fact] + public void WriteXml_ShouldSkipNullProperties() + { + var obj = new ModelWithOptional { Name = "Present", Optional = null }; + var xml = SerializeWithDefault(obj, typeof(ModelWithOptional)); + TestOutput.WriteLine(xml); + Assert.Contains("Present", xml); + Assert.DoesNotContain("42", typeof(int)); + Assert.Equal(42, result); + } + + [Fact] + public void ReadXml_ShouldDeserializePrimitive_Bool() + { + var result = DeserializeWithDefault("true", typeof(bool)); + Assert.Equal(true, result); + } + + [Fact] + public void ReadXml_ShouldDeserializeGuid() + { + var guid = Guid.NewGuid(); + var result = DeserializeWithDefault($"{guid}", typeof(Guid)); + Assert.Equal(guid, result); + } + + [Fact] + public void ReadXml_ShouldDeserializeDecimal() + { + var result = DeserializeWithDefault("3.14", typeof(decimal)); + Assert.Equal(3.14m, result); + } + + [Fact] + public void ReadXml_ShouldDeserializeString() + { + var result = DeserializeWithDefault("Hello", typeof(string)); + Assert.Equal("Hello", result); + } + + [Fact] + public void ReadXml_ThrowsOnNull_Reader() + { + var sut = new DefaultXmlConverter(null, new List()); + Assert.Throws(() => sut.ReadXml((XmlReader)null, typeof(int))); + } + + [Fact] + public void ReadXml_ThrowsOnNull_ObjectType() + { + var sut = new DefaultXmlConverter(null, new List()); + using var reader = XmlReader.Create(new StringReader("1")); + Assert.Throws(() => sut.ReadXml(reader, null)); + } + + [Fact] + public void ReadXml_ShouldDeserializeComplexObject_WithDefaultConstructor() + { + var xml = "Parsed7"; + var result = (SimpleModel)DeserializeWithDefault(xml, typeof(SimpleModel)); + Assert.Equal("Parsed", result.Name); + Assert.Equal(7, result.Value); + } + + [Fact] + public void ReadXml_ShouldDeserializeList() + { + var xml = "123"; + var result = DeserializeWithDefault(xml, typeof(List)); + var list = Assert.IsType>(result); + Assert.Equal(new[] { 1, 2, 3 }, list); + } + + [Fact] + public void ReadXml_ShouldDeserializeEmptyDictionary() + { + var xml = ""; + var result = DeserializeWithDefault(xml, typeof(Dictionary)); + var dict = Assert.IsType>(result); + Assert.Empty(dict); + } + + [Fact] + public void ReadXml_ShouldDeserializeComplexList_ThrowsNotSupported() + { + var xml = "1"; + var sut = new DefaultXmlConverter(null, new List()); + using var reader = XmlReader.Create(new StringReader(xml)); + Assert.Throws(() => sut.ReadXml(reader, typeof(List))); + } + + [Fact] + public void ReadXml_ShouldDeserializeComplexObject_ViaStaticFactory() + { + var xml = $"42"; + var result = (ModelWithStaticFactory)DeserializeWithDefault(xml, typeof(ModelWithStaticFactory)); + Assert.Equal(42, result.Id); + Assert.Equal("Test", result.Label); + } + + [Fact] + public void ReadXml_ShouldThrowSerializationException_WhenNoSuitableConstructor() + { + var xml = "42"; + var sut = new DefaultXmlConverter(null, new List()); + using var reader = XmlReader.Create(new StringReader(xml)); + Assert.Throws(() => sut.ReadXml(reader, typeof(NoDefaultCtor))); + } + + // ---- Test assets ---- + + public class SimpleModel + { + public string Name { get; set; } + public int Value { get; set; } + } + + public class ModelWithIgnored + { + public string Name { get; set; } + [XmlIgnore] + public string Ignored { get; set; } + } + + public class ModelWithXmlAttribute + { + [XmlAttribute("Name")] + public string Name { get; set; } + } + + public class ModelWithList + { + public List Items { get; set; } + } + + public class ModelWithOptional + { + public string Name { get; set; } + public string Optional { get; set; } + } + + public class XmlSerializableModel : IXmlSerializable + { + private readonly string _value; + + public XmlSerializableModel(string value) + { + _value = value; + } + + public XmlSchema GetSchema() => null; + + public void ReadXml(XmlReader reader) { } + + public void WriteXml(XmlWriter writer) + { + writer.WriteString(_value); + } + } + + public class ModelWithStaticFactory + { + private ModelWithStaticFactory(int identifier, string name) + { + Id = identifier; + Label = name; + } + + public int Id { get; } + public string Label { get; } + + public static ModelWithStaticFactory Create(int id, string label) => new ModelWithStaticFactory(id, label); + } + + public class NoDefaultCtor + { + public NoDefaultCtor(int required) + { + Required = required; + } + + public int Required { get; } + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/Converters/ExceptionConverterTest.cs b/test/Cuemon.Xml.Tests/Serialization/Converters/ExceptionConverterTest.cs new file mode 100644 index 000000000..17952235b --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/Converters/ExceptionConverterTest.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; +using System.Xml; +using Codebelt.Extensions.Xunit; +using Xunit; + +namespace Cuemon.Xml.Serialization.Converters +{ + public class ExceptionConverterTest : Test + { + public ExceptionConverterTest(ITestOutputHelper output) : base(output) + { + } + + private static string WriteException(Exception exception, bool includeStackTrace = false, bool includeData = false) + { + var sut = new ExceptionConverter(includeStackTrace, includeData); + using var ms = new MemoryStream(); + using var writer = XmlWriter.Create(ms, new XmlWriterSettings { OmitXmlDeclaration = true }); + sut.WriteXml(writer, exception, null); + writer.Flush(); + ms.Position = 0; + return new StreamReader(ms).ReadToEnd(); + } + + [Fact] + public void Ctor_ShouldHaveExpectedDefaults() + { + var sut = new ExceptionConverter(); + Assert.False(sut.IncludeStackTrace); + Assert.False(sut.IncludeData); + } + + [Fact] + public void Ctor_WithParameters_ShouldSetProperties() + { + var sut = new ExceptionConverter(includeStackTrace: true, includeData: true); + Assert.True(sut.IncludeStackTrace); + Assert.True(sut.IncludeData); + } + + [Fact] + public void WriteXml_ShouldIncludeExceptionTypeAndNamespace() + { + var xml = WriteException(new InvalidOperationException("Oops")); + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + } + + [Fact] + public void WriteXml_ShouldIncludeMessage_WhenNotEmpty() + { + var xml = WriteException(new Exception("My message")); + TestOutput.WriteLine(xml); + Assert.Contains("My message", xml); + } + + [Fact] + public void WriteXml_ShouldNotIncludeStack_WhenIncludeStackTraceIsFalse() + { + Exception caught = null; + try { throw new Exception("x"); } catch (Exception ex) { caught = ex; } + var xml = WriteException(caught, includeStackTrace: false); + TestOutput.WriteLine(xml); + Assert.DoesNotContain("", xml); + } + + [Fact] + public void WriteXml_ShouldIncludeStack_WhenIncludeStackTraceIsTrue() + { + Exception caught = null; + try { throw new Exception("x"); } catch (Exception ex) { caught = ex; } + var xml = WriteException(caught, includeStackTrace: true); + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + Assert.Contains("", xml); + } + + [Fact] + public void WriteXml_ShouldNotIncludeData_WhenIncludeDataIsFalse() + { + var ex = new Exception("x"); + ex.Data.Add("Key", "Value"); + var xml = WriteException(ex, includeData: false); + TestOutput.WriteLine(xml); + Assert.DoesNotContain("", xml); + } + + [Fact] + public void WriteXml_ShouldIncludeData_WhenIncludeDataIsTrue() + { + var ex = new Exception("x"); + ex.Data.Add("MyKey", "MyValue"); + var xml = WriteException(ex, includeData: true); + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + Assert.Contains("MyValue", xml); + } + + [Fact] + public void WriteXml_ShouldIncludeInnerException() + { + var inner = new ArgumentNullException("param", "Inner message"); + var outer = new InvalidOperationException("Outer", inner); + var xml = WriteException(outer); + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + } + + [Fact] + public void WriteXml_ShouldIncludeAggregateExceptionInnerExceptions() + { + var agg = new AggregateException("Agg", new AccessViolationException("AV"), new ArithmeticException("Arith")); + var outer = new InvalidOperationException("Outer", agg); + var xml = WriteException(outer); + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + Assert.Contains("", xml); + Assert.Contains("", xml); + } + + [Fact] + public void ReadXml_ShouldDeserializeSimpleException() + { + var original = new InvalidOperationException("Round-trip message"); + var xml = WriteException(original); + TestOutput.WriteLine(xml); + + var sut = new ExceptionConverter(); + using var reader = XmlReader.Create(new StringReader(xml)); + var result = (Exception)sut.ReadXml(reader, typeof(InvalidOperationException)); + + Assert.IsAssignableFrom(result); + Assert.Contains("Round-trip message", result.Message); + } + + [Fact] + public void ReadXml_ShouldDeserializeExceptionWithInnerException() + { + var inner = new ArgumentNullException("param"); + var outer = new InvalidOperationException("Outer", inner); + var xml = WriteException(outer); + TestOutput.WriteLine(xml); + + var sut = new ExceptionConverter(); + using var reader = XmlReader.Create(new StringReader(xml)); + var result = (Exception)sut.ReadXml(reader, typeof(InvalidOperationException)); + + Assert.IsAssignableFrom(result); + Assert.NotNull(result.InnerException); + } + + [Fact] + public void CanConvert_ShouldReturnTrueForExceptionTypes() + { + var sut = new ExceptionConverter(); + Assert.True(sut.CanConvert(typeof(Exception))); + Assert.True(sut.CanConvert(typeof(InvalidOperationException))); + Assert.True(sut.CanConvert(typeof(ArgumentNullException))); + } + + [Fact] + public void CanConvert_ShouldReturnFalseForNonExceptionTypes() + { + var sut = new ExceptionConverter(); + Assert.False(sut.CanConvert(typeof(string))); + Assert.False(sut.CanConvert(typeof(int))); + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/Converters/FailureConverterTest.cs b/test/Cuemon.Xml.Tests/Serialization/Converters/FailureConverterTest.cs new file mode 100644 index 000000000..70c11aaf9 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/Converters/FailureConverterTest.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; +using System.Xml; +using Codebelt.Extensions.Xunit; +using Cuemon.Diagnostics; +using Xunit; + +namespace Cuemon.Xml.Serialization.Converters +{ + public class FailureConverterTest : Test + { + public FailureConverterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void CanRead_ShouldBeFalse() + { + var sut = new FailureConverter(); + Assert.False(sut.CanRead); + } + + [Fact] + public void CanWrite_ShouldBeTrue() + { + var sut = new FailureConverter(); + Assert.True(sut.CanWrite); + } + + [Fact] + public void ReadXml_ShouldThrowNotImplementedException() + { + var sut = new FailureConverter(); + using var reader = XmlReader.Create(new StringReader("")); + Assert.Throws(() => sut.ReadXml(typeof(Failure), reader)); + } + + [Fact] + public void WriteXml_ShouldSerializeFailure_WithMessage() + { + Exception caught = null; + try { throw new InvalidOperationException("Failure message"); } catch (Exception ex) { caught = ex; } + + var failure = new Failure(caught, FaultSensitivityDetails.None); + var sut = new FailureConverter(); + + using var ms = new MemoryStream(); + using var writer = XmlWriter.Create(ms, new XmlWriterSettings { OmitXmlDeclaration = true }); + sut.WriteXml(writer, failure, null); + writer.Flush(); + ms.Position = 0; + var xml = new StreamReader(ms).ReadToEnd(); + + TestOutput.WriteLine(xml); + Assert.Contains("Failure message", xml); + } + + [Fact] + public void WriteXml_ShouldSerializeFailure_WithStackTrace() + { + Exception caught = null; + try { throw new InvalidOperationException("Stack message"); } catch (Exception ex) { caught = ex; } + + var failure = new Failure(caught, FaultSensitivityDetails.StackTrace); + var sut = new FailureConverter(); + + using var ms = new MemoryStream(); + using var writer = XmlWriter.Create(ms, new XmlWriterSettings { OmitXmlDeclaration = true }); + sut.WriteXml(writer, failure, null); + writer.Flush(); + ms.Position = 0; + var xml = new StreamReader(ms).ReadToEnd(); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + Assert.Contains("", xml); + } + + [Fact] + public void WriteXml_ShouldSerializeFailure_WithData() + { + Exception caught = null; + try + { + var ex = new InvalidOperationException("Data message"); + ex.Data.Add("TestKey", "TestValue"); + throw ex; + } + catch (Exception ex) { caught = ex; } + + var failure = new Failure(caught, FaultSensitivityDetails.Data); + var sut = new FailureConverter(); + + using var ms = new MemoryStream(); + using var writer = XmlWriter.Create(ms, new XmlWriterSettings { OmitXmlDeclaration = true }); + sut.WriteXml(writer, failure, null); + writer.Flush(); + ms.Position = 0; + var xml = new StreamReader(ms).ReadToEnd(); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + Assert.Contains("TestValue", xml); + } + + [Fact] + public void WriteXml_ShouldSerializeFailure_WithInnerException() + { + var inner = new ArgumentNullException("param", "Inner"); + var outer = new InvalidOperationException("Outer", inner); + var failure = new Failure(outer, FaultSensitivityDetails.None); + var sut = new FailureConverter(); + + using var ms = new MemoryStream(); + using var writer = XmlWriter.Create(ms, new XmlWriterSettings { OmitXmlDeclaration = true }); + sut.WriteXml(writer, failure, null); + writer.Flush(); + ms.Position = 0; + var xml = new StreamReader(ms).ReadToEnd(); + + TestOutput.WriteLine(xml); + Assert.Contains("Test"; + using var reader = XmlReader.Create(new System.IO.StringReader(originalXml)); + var sut = new ExceptionConverter(); + + var result = sut.ReadXml(reader, typeof(InvalidOperationException)); + + Assert.IsType(result); + } + + [Fact] + public void FailureConverter_CanRead_ShouldBeFalse() + { + var sut = new FailureConverter(); + Assert.False(sut.CanRead); + } + + [Fact] + public void FailureConverter_CanWrite_ShouldBeTrue() + { + var sut = new FailureConverter(); + Assert.True(sut.CanWrite); + } + + [Fact] + public void FailureConverter_CanConvert_ShouldReturnTrueForFailure() + { + var sut = new FailureConverter(); + Assert.True(sut.CanConvert(typeof(Cuemon.Diagnostics.Failure))); + } + + [Fact] + public void FailureConverter_CanConvert_ShouldReturnFalseForUnrelatedType() + { + var sut = new FailureConverter(); + Assert.False(sut.CanConvert(typeof(string))); + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/DynamicXmlConverterTest.cs b/test/Cuemon.Xml.Tests/Serialization/DynamicXmlConverterTest.cs new file mode 100644 index 000000000..fe7b971ea --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/DynamicXmlConverterTest.cs @@ -0,0 +1,191 @@ +using System; +using System.IO; +using System.Xml; +using Codebelt.Extensions.Xunit; +using Cuemon.Extensions.IO; +using Xunit; + +namespace Cuemon.Xml.Serialization +{ + public class DynamicXmlConverterTest : Test + { + public DynamicXmlConverterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Create_Generic_WithWriter_ShouldProduceWriteableConverter() + { + var sut = DynamicXmlConverter.Create( + writer: (w, v, q) => + { + w.WriteStartElement("Custom"); + w.WriteString(v); + w.WriteEndElement(); + } + ); + + Assert.True(sut.CanWrite); + Assert.False(sut.CanRead); + } + + [Fact] + public void Create_Generic_WithReader_ShouldProduceReadableConverter() + { + var sut = DynamicXmlConverter.Create( + reader: (r, t) => "read-value" + ); + + Assert.False(sut.CanWrite); + Assert.True(sut.CanRead); + } + + [Fact] + public void Create_Generic_WithBothDelegates_ShouldProduceBothCapabilities() + { + var sut = DynamicXmlConverter.Create( + writer: (w, v, q) => { }, + reader: (r, t) => "value" + ); + + Assert.True(sut.CanWrite); + Assert.True(sut.CanRead); + } + + [Fact] + public void Create_NonGeneric_WithObjectType_ShouldProduceConverter() + { + var sut = DynamicXmlConverter.Create( + typeof(int), + writer: (w, v, q) => + { + w.WriteStartElement("Int32"); + w.WriteValue((int)v); + w.WriteEndElement(); + } + ); + + Assert.True(sut.CanWrite); + } + + [Fact] + public void CanConvert_ShouldReturnTrueForExactType() + { + var sut = DynamicXmlConverter.Create(writer: (w, v, q) => { }); + Assert.True(sut.CanConvert(typeof(string))); + } + + [Fact] + public void CanConvert_ShouldReturnFalseForUnrelatedType() + { + var sut = DynamicXmlConverter.Create(writer: (w, v, q) => { }); + Assert.False(sut.CanConvert(typeof(int))); + } + + [Fact] + public void CanConvert_WithPredicate_ShouldRespectPredicate() + { + var sut = DynamicXmlConverter.Create( + writer: (w, v, q) => { }, + canConvertPredicate: t => t == typeof(string) + ); + + Assert.True(sut.CanConvert(typeof(string))); + Assert.False(sut.CanConvert(typeof(object))); + } + + [Fact] + public void WriteXml_WithNullWriter_ShouldThrowInvalidOperationException() + { + var sut = DynamicXmlConverter.Create(); + + using var ms = new MemoryStream(); + using var writer = XmlWriter.Create(ms); + + Assert.Throws(() => sut.WriteXml(writer, "test", null)); + } + + [Fact] + public void ReadXml_WithNullReader_ShouldThrowInvalidOperationException() + { + var sut = DynamicXmlConverter.Create(); + + using var reader = XmlReader.Create(new StringReader("1")); + + Assert.Throws(() => sut.ReadXml(reader, typeof(string))); + } + + [Fact] + public void WriteXml_ShouldInvokeWriterDelegate() + { + var sut = DynamicXmlConverter.Create( + writer: (w, v, q) => + { + w.WriteStartElement("Result"); + w.WriteString(v); + w.WriteEndElement(); + } + ); + + var ms = new MemoryStream(); + using (var writer = XmlWriter.Create(ms, new XmlWriterSettings { OmitXmlDeclaration = true })) + { + sut.WriteXml(writer, "MyValue", null); + } + ms.Position = 0; + var xml = ms.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.Contains("MyValue", xml); + } + + [Fact] + public void ReadXml_ShouldInvokeReaderDelegate() + { + var sut = DynamicXmlConverter.Create( + reader: (r, t) => "from-reader" + ); + + using var reader = XmlReader.Create(new StringReader("anything")); + var result = sut.ReadXml(reader, typeof(string)); + + Assert.Equal("from-reader", result); + } + + [Fact] + public void RootName_WhenSetViaFactory_ShouldBeUsedInWriteXml() + { + var root = new XmlQualifiedEntity("MyRoot"); + var invoked = false; + XmlQualifiedEntity capturedEntity = null; + + var sut = DynamicXmlConverter.Create( + writer: (w, v, q) => + { + invoked = true; + capturedEntity = q; + }, + rootEntity: root + ); + + using var ms = new MemoryStream(); + using var writer = XmlWriter.Create(ms); + sut.WriteXml(writer, "test", null); + + Assert.True(invoked); + Assert.Equal("MyRoot", capturedEntity?.LocalName); + } + + [Fact] + public void DynamicXmlConverterCore_RootName_ShouldBeSettable() + { + var core = (DynamicXmlConverterCore)DynamicXmlConverter.Create( + writer: (w, v, q) => { } + ); + + core.RootName = new XmlQualifiedEntity("UpdatedRoot"); + + Assert.Equal("UpdatedRoot", core.RootName.LocalName); + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/DynamicXmlSerializableTest.cs b/test/Cuemon.Xml.Tests/Serialization/DynamicXmlSerializableTest.cs new file mode 100644 index 000000000..4286642d6 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/DynamicXmlSerializableTest.cs @@ -0,0 +1,93 @@ +using System; +using System.Xml; +using Codebelt.Extensions.Xunit; +using Xunit; + +namespace Cuemon.Xml.Serialization +{ + public class DynamicXmlSerializableTest : Test + { + public DynamicXmlSerializableTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Create_WithNullSource_ShouldThrowArgumentNullException() + { + Assert.Throws(() => DynamicXmlSerializable.Create(null, (w, v) => { })); + } + + [Fact] + public void Create_WithValidSource_ShouldReturnIXmlSerializable() + { + var sut = DynamicXmlSerializable.Create("Hello", (w, v) => w.WriteString(v)); + + Assert.NotNull(sut); + } + + [Fact] + public void WriteXml_ShouldInvokeWriterDelegate() + { + var written = string.Empty; + var sut = DynamicXmlSerializable.Create("TestValue", (w, v) => { written = v; }); + + using var ms = new System.IO.MemoryStream(); + using var writer = XmlWriter.Create(ms); + sut.WriteXml(writer); + + Assert.Equal("TestValue", written); + } + + [Fact] + public void WriteXml_WithNullWriterDelegate_ShouldThrowNotImplementedException() + { + var sut = DynamicXmlSerializable.Create("Source", null); + + using var ms = new System.IO.MemoryStream(); + using var writer = XmlWriter.Create(ms); + + Assert.Throws(() => sut.WriteXml(writer)); + } + + [Fact] + public void ReadXml_WithReaderDelegate_ShouldInvokeDelegate() + { + var readInvoked = false; + var sut = DynamicXmlSerializable.Create("Source", (w, v) => { }, reader: r => { readInvoked = true; }); + + using var reader = XmlReader.Create(new System.IO.StringReader("")); + sut.ReadXml(reader); + + Assert.True(readInvoked); + } + + [Fact] + public void ReadXml_WithNullReaderDelegate_ShouldThrowNotImplementedException() + { + var sut = DynamicXmlSerializable.Create("Source", (w, v) => { }); + + using var reader = XmlReader.Create(new System.IO.StringReader("")); + + Assert.Throws(() => sut.ReadXml(reader)); + } + + [Fact] + public void GetSchema_WithSchemaDelegate_ShouldReturnSchema() + { + var schema = new System.Xml.Schema.XmlSchema(); + var sut = DynamicXmlSerializable.Create("Source", (w, v) => { }, schema: () => schema); + + var result = sut.GetSchema(); + + Assert.Same(schema, result); + } + + [Fact] + public void GetSchema_WithNullSchemaDelegate_ShouldThrowNotImplementedException() + { + var sut = DynamicXmlSerializable.Create("Source", (w, v) => { }); + + Assert.Throws(() => sut.GetSchema()); + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/Formatters/XmlFormatterTest.cs b/test/Cuemon.Xml.Tests/Serialization/Formatters/XmlFormatterTest.cs index 33c4e8d82..ca948235f 100644 --- a/test/Cuemon.Xml.Tests/Serialization/Formatters/XmlFormatterTest.cs +++ b/test/Cuemon.Xml.Tests/Serialization/Formatters/XmlFormatterTest.cs @@ -197,6 +197,33 @@ public void Serialize_ShouldProduce_PascalCase_Structure_ForWorldNode() result.Dispose(); } + [Fact] + public void Serialize_ShouldProduce_PascalCase_Structure_ForWrapperResponse() + { + var wrapper = new WrapperResponse(); + + var sut = new XmlFormatter(o => + { + o.Settings.Writer.Indent = true; + o.Settings.FlattenCollectionItems = true; + }); + var result = sut.Serialize(wrapper); + var x = new XmlDocument(); + x.Load(result); + + TestOutput.WriteLine(Decorator.Enclose(result).ToEncodedString()); + + Assert.Equal("WrapperResponse", x.DocumentElement.Name); + Assert.Contains("", x.OuterXml); + Assert.Contains("", x.OuterXml); + Assert.Contains("https://example.com", x.OuterXml); + Assert.Contains("self", x.OuterXml); + Assert.Contains("https://example.com/other", x.OuterXml); + Assert.Contains("related", x.OuterXml); + + result.Dispose(); + } + [Fact] public void Serialize_ShouldSerializeUsingEnumerableConverter_DictionaryProperty() { diff --git a/test/Cuemon.Xml.Tests/Serialization/XmlConvertTest.cs b/test/Cuemon.Xml.Tests/Serialization/XmlConvertTest.cs new file mode 100644 index 000000000..a19a45bd3 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/XmlConvertTest.cs @@ -0,0 +1,72 @@ +using Codebelt.Extensions.Xunit; +using Cuemon.Extensions.IO; +using Xunit; + +namespace Cuemon.Xml.Serialization +{ + [Collection(nameof(XmlConvertDefaultSettingsCollection))] + public class XmlConvertTest : Test + { + public XmlConvertTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void DefaultSettings_ShouldBeNullByDefault() + { + var original = XmlConvert.DefaultSettings; + try + { + XmlConvert.DefaultSettings = null; + Assert.Null(XmlConvert.DefaultSettings); + } + finally + { + XmlConvert.DefaultSettings = original; + } + } + + [Fact] + public void DefaultSettings_ShouldReturnConfiguredOptionsWhenSet() + { + var original = XmlConvert.DefaultSettings; + try + { + var expected = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("Custom") }; + XmlConvert.DefaultSettings = () => expected; + + var result = XmlConvert.DefaultSettings?.Invoke(); + + Assert.NotNull(result); + Assert.Equal("Custom", result.RootName.LocalName); + } + finally + { + XmlConvert.DefaultSettings = original; + } + } + + [Fact] + public void DefaultSettings_ShouldBeUsedByXmlSerializerCreate_WhenSettingsArgumentIsNull() + { + var original = XmlConvert.DefaultSettings; + try + { + var expected = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("FromDefault") }; + XmlConvert.DefaultSettings = () => expected; + + var serializer = XmlSerializer.Create(null); + var result = serializer.Serialize("hello", typeof(string)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.NotNull(serializer); + Assert.Contains("", xml); + } + finally + { + XmlConvert.DefaultSettings = original; + } + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/XmlQualifiedEntityTest.cs b/test/Cuemon.Xml.Tests/Serialization/XmlQualifiedEntityTest.cs new file mode 100644 index 000000000..a34228fd6 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/XmlQualifiedEntityTest.cs @@ -0,0 +1,128 @@ +using System; +using System.Xml.Serialization; +using Codebelt.Extensions.Xunit; +using Xunit; + +namespace Cuemon.Xml.Serialization +{ + public class XmlQualifiedEntityTest : Test + { + public XmlQualifiedEntityTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Ctor_WithLocalName_ShouldSetLocalName() + { + var sut = new XmlQualifiedEntity("Root"); + + Assert.Equal("Root", sut.LocalName); + Assert.Null(sut.Namespace); + Assert.Null(sut.Prefix); + Assert.False(sut.HasXmlAttributeDecoration); + Assert.False(sut.HasXmlElementDecoration); + Assert.False(sut.HasXmlAnyElementDecoration); + Assert.False(sut.HasXmlRootDecoration); + } + + [Fact] + public void Ctor_WithLocalNameAndNamespace_ShouldSetBoth() + { + var sut = new XmlQualifiedEntity("Root", "https://example.com"); + + Assert.Equal("Root", sut.LocalName); + Assert.Equal("https://example.com", sut.Namespace); + Assert.Null(sut.Prefix); + } + + [Fact] + public void Ctor_WithPrefixLocalNameAndNamespace_ShouldSetAll() + { + var sut = new XmlQualifiedEntity("ex", "Root", "https://example.com"); + + Assert.Equal("ex", sut.Prefix); + Assert.Equal("Root", sut.LocalName); + Assert.Equal("https://example.com", sut.Namespace); + } + + [Fact] + public void Ctor_WithXmlElementAttribute_ShouldHaveXmlElementDecoration() + { + var attr = new XmlElementAttribute("ElementName", typeof(object)) { Namespace = "https://ns.example.com" }; + var sut = new XmlQualifiedEntity(attr); + + Assert.Equal("ElementName", sut.LocalName); + Assert.Equal("https://ns.example.com", sut.Namespace); + Assert.True(sut.HasXmlElementDecoration); + Assert.False(sut.HasXmlAttributeDecoration); + Assert.False(sut.HasXmlRootDecoration); + Assert.False(sut.HasXmlAnyElementDecoration); + } + + [Fact] + public void Ctor_WithXmlElementAttribute_ThrowsOnNull() + { + Assert.Throws(() => new XmlQualifiedEntity((XmlElementAttribute)null)); + } + + [Fact] + public void Ctor_WithXmlAttributeAttribute_ShouldHaveXmlAttributeDecoration() + { + var attr = new XmlAttributeAttribute("AttrName") { Namespace = "https://attr.example.com" }; + var sut = new XmlQualifiedEntity(attr); + + Assert.Equal("AttrName", sut.LocalName); + Assert.Equal("https://attr.example.com", sut.Namespace); + Assert.True(sut.HasXmlAttributeDecoration); + Assert.False(sut.HasXmlElementDecoration); + Assert.False(sut.HasXmlRootDecoration); + Assert.False(sut.HasXmlAnyElementDecoration); + } + + [Fact] + public void Ctor_WithXmlAttributeAttribute_ThrowsOnNull() + { + Assert.Throws(() => new XmlQualifiedEntity((XmlAttributeAttribute)null)); + } + + [Fact] + public void Ctor_WithXmlRootAttribute_ShouldHaveXmlRootDecoration() + { + var attr = new XmlRootAttribute("RootName") { Namespace = "https://root.example.com" }; + var sut = new XmlQualifiedEntity(attr); + + Assert.Equal("RootName", sut.LocalName); + Assert.Equal("https://root.example.com", sut.Namespace); + Assert.True(sut.HasXmlRootDecoration); + Assert.False(sut.HasXmlAttributeDecoration); + Assert.False(sut.HasXmlElementDecoration); + Assert.False(sut.HasXmlAnyElementDecoration); + } + + [Fact] + public void Ctor_WithXmlRootAttribute_ThrowsOnNull() + { + Assert.Throws(() => new XmlQualifiedEntity((XmlRootAttribute)null)); + } + + [Fact] + public void Ctor_WithXmlAnyElementAttribute_ShouldHaveXmlAnyElementDecoration() + { + var attr = new XmlAnyElementAttribute("AnyElement") { Namespace = "https://any.example.com" }; + var sut = new XmlQualifiedEntity(attr); + + Assert.Equal("AnyElement", sut.LocalName); + Assert.Equal("https://any.example.com", sut.Namespace); + Assert.True(sut.HasXmlAnyElementDecoration); + Assert.False(sut.HasXmlElementDecoration); + Assert.False(sut.HasXmlAttributeDecoration); + Assert.False(sut.HasXmlRootDecoration); + } + + [Fact] + public void Ctor_WithXmlAnyElementAttribute_ThrowsOnNull() + { + Assert.Throws(() => new XmlQualifiedEntity((XmlAnyElementAttribute)null)); + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/XmlSerializerOptionsTest.cs b/test/Cuemon.Xml.Tests/Serialization/XmlSerializerOptionsTest.cs new file mode 100644 index 000000000..c6cd0ebb4 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/XmlSerializerOptionsTest.cs @@ -0,0 +1,82 @@ +using System.Xml; +using Codebelt.Extensions.Xunit; +using Cuemon.Xml.Serialization.Converters; +using Xunit; + +namespace Cuemon.Xml.Serialization +{ + public class XmlSerializerOptionsTest : Test + { + public XmlSerializerOptionsTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Ctor_ShouldHaveExpectedDefaults() + { + var sut = new XmlSerializerOptions(); + + Assert.NotNull(sut.Writer); + Assert.NotNull(sut.Reader); + Assert.NotNull(sut.Converters); + Assert.Empty(sut.Converters); + Assert.Null(sut.RootName); + Assert.False(sut.FlattenCollectionItems); + Assert.Equal(Alphanumeric.Tab, sut.Writer.IndentChars); + Assert.Equal(DtdProcessing.Ignore, sut.Reader.DtdProcessing); + } + + [Fact] + public void Writer_ShouldBeAssignable() + { + var sut = new XmlSerializerOptions(); + var newSettings = new XmlWriterSettings { Indent = true }; + sut.Writer = newSettings; + + Assert.Same(newSettings, sut.Writer); + Assert.True(sut.Writer.Indent); + } + + [Fact] + public void Reader_ShouldBeAssignable() + { + var sut = new XmlSerializerOptions(); + var newSettings = new XmlReaderSettings { IgnoreComments = true }; + sut.Reader = newSettings; + + Assert.Same(newSettings, sut.Reader); + Assert.True(sut.Reader.IgnoreComments); + } + + [Fact] + public void RootName_ShouldBeAssignable() + { + var sut = new XmlSerializerOptions(); + var rootName = new XmlQualifiedEntity("MyRoot"); + sut.RootName = rootName; + + Assert.Same(rootName, sut.RootName); + Assert.Equal("MyRoot", sut.RootName.LocalName); + } + + [Fact] + public void Converters_ShouldAllowAddingConverters() + { + var sut = new XmlSerializerOptions(); + var converter = new ExceptionConverter(); + sut.Converters.Add(converter); + + Assert.Single(sut.Converters); + Assert.Same(converter, sut.Converters[0]); + } + + [Fact] + public void FlattenCollectionItems_ShouldBeSettable() + { + var sut = new XmlSerializerOptions(); + sut.FlattenCollectionItems = true; + + Assert.True(sut.FlattenCollectionItems); + } + } +} diff --git a/test/Cuemon.Xml.Tests/Serialization/XmlSerializerTest.cs b/test/Cuemon.Xml.Tests/Serialization/XmlSerializerTest.cs new file mode 100644 index 000000000..199f5f203 --- /dev/null +++ b/test/Cuemon.Xml.Tests/Serialization/XmlSerializerTest.cs @@ -0,0 +1,191 @@ +using System; +using System.IO; +using System.Xml; +using Codebelt.Extensions.Xunit; +using Cuemon.Extensions.IO; +using Cuemon.Xml.Serialization.Converters; +using Xunit; + +namespace Cuemon.Xml.Serialization +{ + public class XmlSerializerTest : Test + { + public XmlSerializerTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Create_WithNullSettings_AndNoDefaultSettings_ShouldProduceValidOutput() + { + var original = XmlConvert.DefaultSettings; + try + { + XmlConvert.DefaultSettings = null; + + var sut = XmlSerializer.Create(null); + var result = sut.Serialize("test", typeof(string)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.NotNull(sut); + Assert.Contains("test", xml); + } + finally + { + XmlConvert.DefaultSettings = original; + } + } + + [Fact] + public void Create_WithExplicitSettings_ShouldApplySettings() + { + var options = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("Explicit") }; + var sut = XmlSerializer.Create(options); + + var result = sut.Serialize("test", typeof(string)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + } + + [Fact] + public void Create_WithNullSettings_ShouldFallbackToDefaultSettings() + { + var original = XmlConvert.DefaultSettings; + try + { + var fromDefault = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("Default") }; + XmlConvert.DefaultSettings = () => fromDefault; + + var sut = XmlSerializer.Create(null); + var result = sut.Serialize("test", typeof(string)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + } + finally + { + XmlConvert.DefaultSettings = original; + } + } + + [Fact] + public void Serialize_ShouldProduceValidXmlStream_ForString() + { + var sut = XmlSerializer.Create(null); + + var result = sut.Serialize("Hello", typeof(string)); + + Assert.NotNull(result); + var xml = result.ToEncodedString(); + TestOutput.WriteLine(xml); + Assert.Contains("Hello", xml); + } + + [Fact] + public void Serialize_ShouldProduceValidXmlStream_ForInt() + { + var sut = XmlSerializer.Create(null); + var result = sut.Serialize(42, typeof(int)); + + Assert.NotNull(result); + var xml = result.ToEncodedString(); + TestOutput.WriteLine(xml); + Assert.Contains("42", xml); + } + + [Fact] + public void Serialize_WithCustomRootName_ShouldUseRootName() + { + var options = new XmlSerializerOptions { RootName = new XmlQualifiedEntity("Custom") }; + var sut = XmlSerializer.Create(options); + + var result = sut.Serialize("test", typeof(string)); + + Assert.NotNull(result); + var xml = result.ToEncodedString(); + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + } + + [Fact] + public void Deserialize_Generic_ShouldReturnTypedObject() + { + var sut = XmlSerializer.Create(null); + var stream = sut.Serialize("World", typeof(string)); + stream.Position = 0; + + var result = sut.Deserialize(stream); + + Assert.Equal("World", result); + } + + [Fact] + public void Deserialize_ShouldReturnPrimitive_Int() + { + var sut = XmlSerializer.Create(null); + var stream = sut.Serialize(99, typeof(int)); + stream.Position = 0; + + var result = (int)sut.Deserialize(stream, typeof(int)); + + Assert.Equal(99, result); + } + + [Fact] + public void Serialize_WithDynamicConverter_RootNameFromSerializer_ShouldPropagateToOutput() + { + // Verifies that a DynamicXmlConverterCore with no RootName gets the serializer's RootName. + var rootName = new XmlQualifiedEntity("Propagated"); + var options = new XmlSerializerOptions { RootName = rootName }; + + var dynamicConverter = DynamicXmlConverter.Create( + writer: (w, v, q) => + { + var elementName = q?.LocalName ?? "String"; + w.WriteStartElement(elementName); + w.WriteString(v); + w.WriteEndElement(); + }, + rootEntity: null + ); + options.Converters.Add(dynamicConverter); + + var sut = XmlSerializer.Create(options); + var result = sut.Serialize("value", typeof(string)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.Contains("value", xml); + } + + [Fact] + public void Serialize_WhenNoConverterMatches_ShouldUseDefaultXmlConverter() + { + var options = new XmlSerializerOptions(); + var sut = XmlSerializer.Create(options); + + var result = sut.Serialize(Guid.Empty, typeof(Guid)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.Contains("", xml); + } + + [Fact] + public void Serialize_UsesWriterSettingsFromOptions() + { + var options = new XmlSerializerOptions(); + options.Writer.OmitXmlDeclaration = true; + var sut = XmlSerializer.Create(options); + + var result = sut.Serialize("test", typeof(string)); + var xml = result.ToEncodedString(); + + TestOutput.WriteLine(xml); + Assert.DoesNotContain(" + { + } +}