diff --git a/.bot/README.md b/.bot/README.md index f7e13f0..9f9afda 100644 --- a/.bot/README.md +++ b/.bot/README.md @@ -7,4 +7,4 @@ This folder is reserved for local-only AI working material such as: - design alternatives - temporary agent state -Keep this folder out of source control. Move only finalized, non-confidential guidance into `AGENTS.md`. +The `.bot/README.md` file itself is intentionally tracked in source control. All other `.bot/` working material remains local-only and excluded from source control. Move only finalized, non-confidential guidance into `AGENTS.md`. diff --git a/.docfx/BuildDocfxImage.ps1 b/.docfx/BuildDocfxImage.ps1 index 0463932..91397ce 100644 --- a/.docfx/BuildDocfxImage.ps1 +++ b/.docfx/BuildDocfxImage.ps1 @@ -1,4 +1,4 @@ -$version = minver -i -t v -v w +$version = minver -i -t v -v w docfx metadata docfx.json docker buildx build -t sharedkernel-docfx:$version --platform linux/arm64,linux/amd64 --load -f Dockerfile.docfx . get-childItem -recurse -path api -include *.yml, .manifest | remove-item diff --git a/.docfx/filterConfig.yml b/.docfx/filterConfig.yml index 837050d..bab3762 100644 --- a/.docfx/filterConfig.yml +++ b/.docfx/filterConfig.yml @@ -1,4 +1,4 @@ -apiRules: +apiRules: - exclude: uidRegex: ^System\.Object - type: Type \ No newline at end of file + type: Type diff --git a/.docfx/includes/availability-modern.md b/.docfx/includes/availability-modern.md index af5cf4b..22cf496 100644 --- a/.docfx/includes/availability-modern.md +++ b/.docfx/includes/availability-modern.md @@ -1 +1 @@ -Availability: .NET 10 and .NET 9 \ No newline at end of file +Availability: .NET 10 and .NET 11 diff --git a/.docfx/nginx.conf b/.docfx/nginx.conf index 291f27a..975e107 100644 --- a/.docfx/nginx.conf +++ b/.docfx/nginx.conf @@ -18,6 +18,8 @@ server { # cached permanently because changed content is published under a different URL. location ~ "^/public/.+-[A-Z0-9]{8}(?:-[A-Z0-9]{8})?(?:\.min)?\.(?:(?:css|js)(?:\.map)?|woff2?)$" { try_files $uri =404; + # Range errors are generated after headers are set, so serve complete assets. + max_ranges 0; add_header Cache-Control $fingerprinted_asset_cache_control always; add_header Vary "Accept-Encoding" always; } diff --git a/.docfx/templates/shared-kernel/layout/_master.tmpl b/.docfx/templates/shared-kernel/layout/_master.tmpl index 17557b9..1c2aae6 100644 --- a/.docfx/templates/shared-kernel/layout/_master.tmpl +++ b/.docfx/templates/shared-kernel/layout/_master.tmpl @@ -137,4 +137,4 @@ {{/redirect_url}} - \ No newline at end of file + diff --git a/.docfx/templates/shared-kernel/partials/namespace.tmpl.partial b/.docfx/templates/shared-kernel/partials/namespace.tmpl.partial index 173b006..9d5f64c 100644 --- a/.docfx/templates/shared-kernel/partials/namespace.tmpl.partial +++ b/.docfx/templates/shared-kernel/partials/namespace.tmpl.partial @@ -11,4 +11,4 @@

{{{summary}}}
{{/children}} -{{/children}} \ No newline at end of file +{{/children}} diff --git a/.docfx/templates/shared-kernel/public/main.js b/.docfx/templates/shared-kernel/public/main.js index 54d0cc3..9edcd3b 100644 --- a/.docfx/templates/shared-kernel/public/main.js +++ b/.docfx/templates/shared-kernel/public/main.js @@ -6,4 +6,4 @@ export default { title: 'GitHub' } ] - } \ No newline at end of file + } diff --git a/.editorconfig b/.editorconfig index a083db5..886ea15 100644 --- a/.editorconfig +++ b/.editorconfig @@ -37,7 +37,7 @@ dotnet_diagnostic.IDE0063.severity = none dotnet_diagnostic.IDE0071.severity = none # S3267: Loops should be simplified with "LINQ" expressions -# https://rules.sonarsource.com/csharp/RSPEC-3267 +# https://sonarcloud.io/organizations/sonarcloud/rules?open=csharpsquid:S3267&rule_key=csharpsquid:S3267 dotnet_diagnostic.S3267.severity = none # CA1859: Use concrete types when possible for improved performance @@ -57,9 +57,10 @@ indent_size = 4 # IDE0161: Namespace declaration preferences # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0161 -# Prefer file-scoped namespaces for new files; existing block-scoped files should not be converted unless explicitly asked +# File-scoped namespaces are mandatory for new and existing C# files. [*.cs] -csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_namespace_declarations = file_scoped:error +dotnet_diagnostic.IDE0161.severity = error # Top-level statements: DO NOT USE # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0210-ide0211 diff --git a/.gitattributes b/.gitattributes index 1ff0c42..0ea8d1b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,7 @@ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### -* text=auto +* text=auto eol=lf ############################################################################### # Set default behavior for command prompt diff. @@ -17,7 +17,7 @@ # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following +# the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below @@ -46,9 +46,9 @@ ############################################################################### # diff behavior for common document formats -# +# # Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the +# is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index e27edb4..1fa464e 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ -# Contributor Covenant Code of Conduct +# Contributor Covenant Code of Conduct This document is adapted from the Contributor Covenant which is used by many open source projects, including those under the [.NET Foundation](https://dotnetfoundation.org/code-of-conduct). @@ -132,4 +132,4 @@ at [https://www.contributor-covenant.org/translations][translations]. [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file +[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index bf30585..42a8c9d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -5,7 +5,7 @@ This repository is part of the Codebelt .NET library estate. The instructions be ## Before you start - Read the repository `README.md` and open an issue before starting a non-trivial feature or behavioral change. -- Use an installed .NET SDK that can build the target frameworks listed below. This repository currently targets: **net10.0, net9.0**. +- Use an installed .NET SDK that can build the target frameworks listed below. This repository currently targets: **net10.0, net11.0**. - The solution is `Codebelt.SharedKernel.slnx`. Central package versions are maintained in `Directory.Packages.props`. - The shared build behavior is in `Directory.Build.props` and `Directory.Build.targets`; repository-specific TFMs, package references and metadata remain local to this library. @@ -43,8 +43,9 @@ The CI test plan currently runs **1** project(s) and excludes **0** project(s). ## Integration and infrastructure -- `WSL-Ubuntu` — WSL distribution `Ubuntu-24.04`. -- `Docker-Ubuntu` — Docker image `codebeltnet/ubuntu-testrunner:8-9-10-11`. +- `WSL-Ubuntu` — WSL distribution `Ubuntu-26.04`. +- `Docker-Ubuntu (net10.0)` — Docker image `codebeltnet/ubuntu-testrunner:10`. +- `Docker-Ubuntu (net11.0)` — Docker image `codebeltnet/ubuntu-testrunner:11`. This repository has no repository-local `docker-compose.yml` service dependency in the current checkout. Use the environments declared in `testenvironments.json` when you need the estate test runner. diff --git a/.github/codecov.yml b/.github/codecov.yml index c0a0037..b52d0c4 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,2 +1,2 @@ ignore: - - "test" \ No newline at end of file + - "test" diff --git a/.github/prompts/benchmark.prompt.md b/.github/prompts/benchmark.prompt.md deleted file mode 100644 index 22d1c9d..0000000 --- a/.github/prompts/benchmark.prompt.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -mode: agent -description: 'Writing Performance Benchmarks' ---- - -# Benchmark Fixture Prompt (Tuning Benchmarks) - -This prompt defines how to generate performance tests (“benchmarks”) for a project/solution using BenchmarkDotNet. -Benchmarks are *not* unit tests — they are micro- or component-level performance measurements that belong under the `tuning/` directory and follow strict conventions. - -Copilot must follow these guidelines when generating benchmark fixtures. - ---- - -## 1. Naming and Placement - -- All benchmark projects live under the `tuning/` folder. - Examples: - - `tuning/.Benchmarks/` - - `tuning/.Console.Benchmarks/` - -- **Namespaces must NOT end with `.Benchmarks`.** - They must mirror the production assembly’s namespace. - - Example: - If benchmarking a type inside `YourProject.Console`, then: - - ```csharp - namespace YourProject.Console - { - public class Sha512256Benchmark { … } - } - ``` - -* **Benchmark class names must end with `Benchmark`.** - Example: `DateSpanBenchmark`, `FowlerNollVoBenchmark`. - -* Benchmark files should be located in the matching benchmark project - (e.g., benchmarks for `YourProject.Console` go in `YourProject.Console.Benchmarks.csproj`). - -* In the `.csproj` for each benchmark project, set the root namespace to the production namespace, for example: - - ```xml - YourProject.Console - ``` - ---- - -## 2. Attributes and Configuration - -Each benchmark class should use: - -```csharp -[MemoryDiagnoser] -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -``` - -Optional but strongly recommended where meaningful: - -* `[Params(...)]` — define small, medium, large input sizes. -* `[GlobalSetup]` — deterministic initialization of benchmark data. -* `[Benchmark(Description = "...")]` — always add descriptions. -* `[Benchmark(Baseline = true)]` — when comparing two implementations. - -Avoid complex global configs; prefer explicit attributes inside the class. - ---- - -## 3. Structure and Best Practices - -A benchmark fixture must: - -* Measure a **single logical operation** per benchmark method. -* Avoid I/O, networking, disk access, logging, or side effects. -* Avoid expensive setup inside `[Benchmark]` methods. -* Use deterministic data (e.g., seeded RNG or predefined constants). -* Use `[GlobalSetup]` to allocate buffers, random payloads, or reusable test data only once. -* Avoid shared mutable state unless reset per iteration. - -Use representative input sizes such as: - -```csharp -[Params(8, 256, 4096)] -public int Count { get; set; } -``` - -BenchmarkDotNet will run each benchmark for each parameter value. - ---- - -## 4. Method Naming Conventions - -Use descriptive names that communicate intent: - -* `Parse_Short` -* `Parse_Long` -* `ComputeHash_Small` -* `ComputeHash_Large` -* `Serialize_Optimized` -* `Serialize_Baseline` - -When comparing approaches, always list them clearly and tag one as the baseline. - ---- - -## 5. Example Benchmark Fixture - -```csharp -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; - -namespace YourProject -{ - [MemoryDiagnoser] - [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] - public class SampleOperationBenchmark - { - [Params(8, 256, 4096)] - public int Count { get; set; } - - private byte[] _payload; - - [GlobalSetup] - public void Setup() - { - _payload = new byte[Count]; - // deterministic initialization - } - - [Benchmark(Baseline = true, Description = "Operation - baseline")] - public int Operation_Baseline() => SampleOperation.Process(_payload); - - [Benchmark(Description = "Operation - optimized")] - public int Operation_Optimized() => SampleOperation.ProcessOptimized(_payload); - } -} -``` - ---- - -## 6. Reporting and CI - -* Benchmark projects live exclusively under `tuning/`. They must not affect production builds. -* Heavy BenchmarkDotNet runs should *not* run in CI unless explicitly configured. -* Reports are produced by the benchmark runner and stored under the configured artifacts directory. - ---- - -## 7. Additional Guidelines - -* Keep benchmark fixtures focused and readable. -* Document non-obvious reasoning in short comments. -* Prefer realistic but deterministic data sets. -* When benchmarks reveal regressions or improvements, reference the associated PR or issue in a comment. -* Shared benchmark helpers belong in `tuning/` projects, not in production code. - ---- - -## Final Notes - -* Benchmarks are performance tests, not unit tests. -* Use `[Benchmark]` only for pure performance measurement. -* Avoid `MethodImplOptions.NoInlining` unless absolutely necessary. -* Use small sets of meaningful benchmark scenarios — avoid combinatorial explosion. diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 41b5aa3..49df964 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -23,7 +23,7 @@ permissions: jobs: init: name: initialize - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 outputs: run-mac-tests: ${{ steps.vars.outputs.run-mac-tests }} run-privileged-jobs: ${{ steps.vars.outputs.run-privileged-jobs }} @@ -60,9 +60,10 @@ jobs: uses: codebeltnet/jobs-dotnet-build/.github/workflows/default.yml@v3 with: configuration: ${{ matrix.configuration }} + include-preview: true 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' }} + runs-on: ${{ matrix.arch == 'ARM64' && 'ubuntu-26.04-arm' || 'ubuntu-26.04' }} upload-build-artifact-name: build-${{ matrix.configuration }}-${{ matrix.arch }} secrets: inherit @@ -75,6 +76,7 @@ jobs: uses: codebeltnet/jobs-dotnet-pack/.github/workflows/default.yml@v3 with: configuration: ${{ matrix.configuration }} + include-preview: true version: ${{ needs.build.outputs.version }} download-build-artifact-pattern: build-${{ matrix.configuration }}-X64 @@ -88,8 +90,9 @@ jobs: configuration: [Debug, Release] uses: codebeltnet/jobs-dotnet-test/.github/workflows/default.yml@v3 with: - runs-on: ${{ matrix.arch == 'ARM64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} + runs-on: ${{ matrix.arch == 'ARM64' && 'ubuntu-26.04-arm' || 'ubuntu-26.04' }} configuration: ${{ matrix.configuration }} + include-preview: true build-switches: -p:SkipSignAssembly=true restore: true build: true # required for xunitv3 @@ -107,6 +110,7 @@ jobs: with: runs-on: ${{ matrix.arch == 'ARM64' && 'windows-11-arm' || 'windows-2025' }} configuration: ${{ matrix.configuration }} + include-preview: true build-switches: -p:SkipSignAssembly=true restore: true build: true # required for xunitv3 @@ -125,6 +129,7 @@ jobs: with: runs-on: ${{ matrix.arch == 'ARM64' && 'macos-26' || 'macos-26-intel' }} configuration: ${{ matrix.configuration }} + include-preview: true build-switches: -p:SkipSignAssembly=true restore: true build: true # required for xunitv3 @@ -134,7 +139,7 @@ jobs: if: ${{ always() }} name: test-qualitygate needs: [init, test_linux, test_windows, test_mac] - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - name: Evaluate test results shell: bash @@ -180,6 +185,7 @@ jobs: needs: [init, build, test_qualitygate] uses: codebeltnet/jobs-sonarcloud/.github/workflows/default.yml@v3 with: + include-preview: true organization: geekle projectKey: sharedkernel version: ${{ needs.build.outputs.version }} @@ -193,12 +199,14 @@ jobs: with: repository: codebeltnet/shared-kernel secrets: inherit - + codeql: if: ${{always() && needs.init.outputs.run-privileged-jobs == 'true' && needs.build.result == 'success' && needs.test_qualitygate.result == 'success'}} name: call-codeql needs: [init, build, test_qualitygate] uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v3 + with: + include-preview: true permissions: security-events: write diff --git a/.github/workflows/service-update.yml b/.github/workflows/service-update.yml index 1f69586..88541d7 100644 --- a/.github/workflows/service-update.yml +++ b/.github/workflows/service-update.yml @@ -50,6 +50,7 @@ jobs: import re import subprocess import sys + from datetime import date tag_pattern = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$") tags = subprocess.check_output(["git", "tag", "--list"], text=True).splitlines() @@ -72,28 +73,37 @@ jobs: with open("CHANGELOG.md", encoding="utf-8") as changelog_file: changelog = changelog_file.read() - heading = re.search(r"(?m)^## \[(\d+\.\d+\.\d+)\] - ([^\r\n]+)[ \t]*$", changelog) + heading = re.search(r"(?m)^## \[(\d+\.\d+\.\d+)\]([^\r\n]*)$", changelog) if not heading: print("::error::CHANGELOG.md has no versioned heading to compare with Git tags.") sys.exit(1) - current_version, current_status = heading.groups() - current_status = current_status.strip() - if current_status.upper() != "TBD" and not re.fullmatch(r"\d{4}-\d{2}-\d{2}", current_status): - print(f"::error::Unsupported CHANGELOG.md version status: {current_status}") + current_version = heading.group(1) + status_match = re.fullmatch(r" - (TBD|\d{4}-\d{2}-\d{2})[ \t]*", heading.group(2), re.IGNORECASE) + if not status_match: + print(f"::error::Unsupported or missing CHANGELOG.md version status: {heading.group(2).strip()}") sys.exit(1) - - if current_version == next_version and current_status.upper() == "TBD": - # Reuse an already pending next release, such as [5.0.13] - TBD. - new_version = next_version - elif current_version == latest_version and current_status.upper() != "TBD": + current_status = status_match.group(1) + if current_status.upper() != "TBD": + try: + date.fromisoformat(current_status) + except ValueError: + print(f"::error::Invalid CHANGELOG.md release date: {current_status}") + sys.exit(1) + + pending_version = tuple(int(part) for part in current_version.split(".")) + released_version = (major, minor, patch) + if pending_version > released_version: + # Preserve an explicitly planned patch, minor, or major release. + new_version = current_version + elif pending_version == released_version and current_status.upper() != "TBD": # The changelog is at the latest released tag; start the next patch release. new_version = next_version else: print( f"::error::CHANGELOG.md starts at {current_version} ({current_status}), " f"but the latest stable Git tag is {latest_tag}. Expected either " - f"[{latest_version}] with a release date or [{next_version}] - TBD." + f"[{latest_version}] with a release date or a newer version with a date or TBD." ) sys.exit(1) @@ -105,6 +115,7 @@ jobs: output.write(f"current={current_version}\n") output.write(f"new={new_version}\n") output.write(f"branch={branch}\n") + PY - name: Generate codebelt-aicia token id: app-token @@ -125,7 +136,7 @@ jobs: NEW="${{ steps.newver.outputs.new }}" for f in .nuget/*/PackageReleaseNotes.txt; do [ -f "$f" ] || continue - TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0") + TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10 and .NET 11") ENTRY="Version: ${NEW}\nAvailability: ${TFM}\n\n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n\n" { printf "$ENTRY"; cat "$f"; } > "$f.tmp" && mv "$f.tmp" "$f" done @@ -140,9 +151,9 @@ jobs: service_note = "This release also includes a service update focused on package dependencies." with open("CHANGELOG.md", encoding="utf-8") as f: content = f.read() - heading = re.search(r"(?m)^## \[(\d+\.\d+\.\d+)\] - (TBD|\d{4}-\d{2}-\d{2})[ \t]*$", content) + heading = re.search(r"(?m)^## \[(\d+\.\d+\.\d+)\] - (TBD|\d{4}-\d{2}-\d{2})[ \t]*$", content, re.IGNORECASE) if heading and heading.group(1) == new_ver: - if heading.group(2) == "TBD": + if heading.group(2).upper() == "TBD": content = content[:heading.start(2)] + today + content[heading.end(2):] heading = re.search(r"(?m)^## \[(\d+\.\d+\.\d+)\] - \d{4}-\d{2}-\d{2}[ \t]*$", content) section_end = re.search(r"(?m)^## \[", content[heading.end():]) diff --git a/.gitignore b/.gitignore index 0af61f5..7128a42 100644 --- a/.gitignore +++ b/.gitignore @@ -377,4 +377,4 @@ FodyWeavers.xsd # Bot workspace (local-only AI agent ideation, PRDs, and agentic loop state) .bot/* -!.bot/README.md \ No newline at end of file +!.bot/README.md diff --git a/.nuget/Codebelt.SharedKernel/PackageReleaseNotes.txt b/.nuget/Codebelt.SharedKernel/PackageReleaseNotes.txt index 930b6b5..ed9971a 100644 --- a/.nuget/Codebelt.SharedKernel/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.SharedKernel/PackageReleaseNotes.txt @@ -1,3 +1,12 @@ +Version: 0.6.0 +Availability: .NET 11 and .NET 10 + +# ALM +- CHANGED Target frameworks by adding .NET 11 (net11.0) and removing .NET 9 (net9.0), so the package now targets .NET 11 and .NET 10 + +# Breaking Changes +- REMOVED .NET 9 support; consumers must target .NET 10 or later + Version: 0.5.13 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Codebelt.SharedKernel/README.md b/.nuget/Codebelt.SharedKernel/README.md index 6ed654d..7a7d46b 100644 --- a/.nuget/Codebelt.SharedKernel/README.md +++ b/.nuget/Codebelt.SharedKernel/README.md @@ -2,7 +2,7 @@ An open-source project (MIT license) offering a lightweight .NET library with essential components for implementing the shared kernel pattern within the context of Domain-Driven Design (DDD). It provides generic and reusable (Single) Value Objects utilizing the [Savvyio.Domain](https://www.nuget.org/packages/Savvyio.Domain) package for fundamental classes, records and interfaces. -Your versatile Shared Kernel companion for modern development with `.NET 9` and `.NET 10`. +Your versatile Shared Kernel companion for modern development with `.NET 10` and `.NET 11`. It is, by heart, free, flexible and built to extend and boost your agile codebelt. diff --git a/AGENTS.md b/AGENTS.md index 0d7aa61..089e16a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This document provides guidance for AI agents working in this repository. ## Project Overview -Codebelt.SharedKernel is a foundational .NET library providing domain-driven design building blocks for time, correlation, tokens, and security concerns. The solution targets .NET 10.0, .NET 9.0, and .NET 8.0. It includes: +Codebelt.SharedKernel is a foundational .NET library providing domain-driven design building blocks for time, correlation, tokens, and security concerns. The solution targets .NET 10.0 and .NET 11.0. It includes: - **Codebelt.SharedKernel** — Core types for temporal operations (ClockSkew, CoordinatedUniversalTime, TimeToLive), correlation tracking (CorrelationId), token management (Token, TokenOptions), and security (AccessKey, Secret). @@ -134,7 +134,7 @@ This document provides instructions for writing unit tests for a project/solutio ## 1. Base Class -**Always inherit from the `Test` base class** for all unit test classes. +**Always inherit from the `Test` base class** for all unit test classes.\ This ensures consistent setup, teardown, and output handling across all tests. > Important: Do NOT add `using Xunit.Abstractions`. xUnit v3 no longer exposes that namespace; including it is incorrect and will cause compilation errors. Use the `Codebelt.Extensions.Xunit` Test base class and `using Xunit;` as shown in the examples below. If you need access to test output, rely on the Test base class (which accepts the appropriate output helper) rather than importing `Xunit.Abstractions`. @@ -284,13 +284,13 @@ namespace YourProject **Pattern name:** Public Facade Testing (also referred to as *Public API Proxy Testing*) -**Description:** +**Description:**\ Internal classes and methods must be validated by exercising the public API that consumes them. Tests should assert observable behavior exposed by the public surface rather than targeting internal implementation details directly. ### Example Mapping -- **Internal helper:** `DelimitedString` (internal static class) -- **Public API:** `TestOutputHelperExtensions.WriteLines()` (public extension method) +- **Internal helper:** `DelimitedString` (internal static class)\ +- **Public API:** `TestOutputHelperExtensions.WriteLines()` (public extension method)\ - **Test strategy:** Write tests for `WriteLines()` and verify its public behavior. The internal call to `DelimitedString.Create()` is exercised implicitly. ### Benefits diff --git a/CHANGELOG.md b/CHANGELOG.md index d56ad40..14f9569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ 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. +## [0.6.0] - 2026-11-10 + +This is a major release that drops .NET 9 and adds .NET 11 as package target frameworks, alongside analyzer enforcement, source formatting and test environments. + +### Added + +- .NET 11 as a package target framework alongside .NET 10, +- Added SonarAnalyzer.CSharp 10.35.0.4138 as a private build dependency for production projects. + +### Changed + +- Required file-scoped namespaces for existing and new C# files and converted the remaining block-scoped declarations, +- Normalized tracked text to LF and UTF-8 without BOM, +- Split Docker test environments into .NET 10 and .NET 11 runners and updated the contributor guide, +- Updated the S3267 rule documentation link, +- Disabled range requests for fingerprinted DocFX assets so they are served in full, +- Upgraded the test dependencies Codebelt.Coverlet.MTP from 10.1.0 to 10.1.1, Cuemon.Extensions.IO from 10.8.1 to 10.8.2, and Microsoft.Testing.Extensions.HangDump from 2.5.1 to 2.5.2, +- Updated the service-update workflow to preserve planned minor and major versions and validate release dates. + +### Removed + +- .NET 9 from the package target frameworks. + ## [0.5.13] - 2026-10-18 This is a patch release focused on dependency maintenance, test diagnostics, documentation hosting, repository guidance, and a time-zone-independent UTC minimum. @@ -255,6 +278,7 @@ Purely an ALM release. No changes to the codebase. - CoordinatedUniversalTime record in the Codebelt.SharedKernel namespace that represents an object that can be used when you need a timestamp that is based on an absolute time (UTC) - TimeToLive record in the Codebelt.SharedKernel namespace that represents an object that can be used when issuing authentication tokens or similar (TTL) +[0.6.0]: https://github.com/codebeltnet/shared-kernel/compare/v0.5.13...v0.6.0 [0.5.13]: https://github.com/codebeltnet/shared-kernel/compare/v0.5.12...v0.5.13 [0.5.12]: https://github.com/codebeltnet/shared-kernel/compare/v0.5.11...v0.5.12 [0.5.11]: https://github.com/codebeltnet/shared-kernel/compare/v0.5.10...v0.5.11 diff --git a/Directory.Build.props b/Directory.Build.props index 8c05d58..53566bc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,7 +13,7 @@ - net10.0;net9.0 + net11.0;net10.0 Copyright © Geekle 2024-2026. All rights reserved. gimlichael Geekle @@ -46,12 +46,13 @@ + - net10.0;net9.0 + net11.0;net10.0 diff --git a/Directory.Build.targets b/Directory.Build.targets index 00bb68a..f6a6a00 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,4 +1,4 @@ - + $([System.IO.File]::ReadAllText('$(PackageReleaseNotesFile)')) diff --git a/Directory.Packages.props b/Directory.Packages.props index fcc583d..2eff4bc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,11 +3,11 @@ true - - + + - + @@ -15,5 +15,6 @@ + - \ No newline at end of file + diff --git a/LICENSE.md b/LICENSE.md index e9edecb..c83a0a2 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -4,4 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 728a953..44f624f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ An open-source project offering a lightweight .NET library with essential components for implementing the shared kernel pattern within the context of Domain-Driven Design (DDD). It provides generic and reusable (Single) Value Objects utilizing the [Savvyio.Domain](https://www.nuget.org/packages/Savvyio.Domain) package for fundamental classes, records and interfaces. -Your versatile Shared Kernel companion for modern development with `.NET 9` and `.NET 10`. +Your versatile Shared Kernel companion for modern development with `.NET 10` and `.NET 11`. It is, by heart, free, flexible and built to extend and boost your agile codebelt. diff --git a/src/Codebelt.SharedKernel/ClockSkew.cs b/src/Codebelt.SharedKernel/ClockSkew.cs index 222e1bf..d48317e 100644 --- a/src/Codebelt.SharedKernel/ClockSkew.cs +++ b/src/Codebelt.SharedKernel/ClockSkew.cs @@ -1,91 +1,90 @@ -using System; +using System; using System.Globalization; using Cuemon; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +/// +/// Represents a object that can be used to warrant for clock skew related scenarios such as authentication. +/// +/// https://martinfowler.com/eaaCatalog/valueObject.html +/// +public record ClockSkew : ComparableValueObject { /// - /// Represents a object that can be used to warrant for clock skew related scenarios such as authentication. + /// Performs an implicit conversion from to . /// - /// https://martinfowler.com/eaaCatalog/valueObject.html - /// - public record ClockSkew : ComparableValueObject + /// The value to convert. + /// A that is equivalent to . + public static implicit operator ClockSkew (TimeSpan value) { - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator ClockSkew (TimeSpan value) - { - return new ClockSkew(value); - } + return new ClockSkew(value); + } - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator TimeSpan (ClockSkew value) - { - return value.Value; - } + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// A that is equivalent to . + public static implicit operator TimeSpan (ClockSkew value) + { + return value.Value; + } - /// - /// Denotes the largest possible value of . - /// - public static readonly TimeSpan MaxValue = TimeSpan.FromHours(4); + /// + /// Denotes the largest possible value of . + /// + public static readonly TimeSpan MaxValue = TimeSpan.FromHours(4); - /// - /// Denotes the smallest possible value of . - /// - public static readonly TimeSpan MinValue = TimeSpan.Zero; + /// + /// Denotes the smallest possible value of . + /// + public static readonly TimeSpan MinValue = TimeSpan.Zero; - /// - /// Returns a that represents a specified number of minutes. - /// - /// A number of minutes. - /// A new instance of that represents . - public static ClockSkew FromMinutes(byte value) - { - var utcNow = DateTime.UtcNow; - return utcNow.AddMinutes(value) - utcNow; - } + /// + /// Returns a that represents a specified number of minutes. + /// + /// A number of minutes. + /// A new instance of that represents . + public static ClockSkew FromMinutes(byte value) + { + var utcNow = DateTime.UtcNow; + return utcNow.AddMinutes(value) - utcNow; + } - /// - /// Returns a that represents a specified number of seconds. - /// - /// A number of seconds. - /// A new instance of that represents . - public static ClockSkew FromSeconds(ushort value) - { - var utcNow = DateTime.UtcNow; - return utcNow.AddSeconds(value) - utcNow; - } + /// + /// Returns a that represents a specified number of seconds. + /// + /// A number of seconds. + /// A new instance of that represents . + public static ClockSkew FromSeconds(ushort value) + { + var utcNow = DateTime.UtcNow; + return utcNow.AddSeconds(value) - utcNow; + } - /// - /// Initializes a new instance of the class. - /// - /// The value to assign the role of . - /// - /// cannot be less than -or- - /// cannot exceed . - /// - public ClockSkew(TimeSpan value) : base(Validator.CheckParameter(value, () => - { - Validator.ThrowIfLowerThan(value.Ticks, MinValue.Ticks, nameof(value), "Value for clock skew cannot be negative."); - Validator.ThrowIfGreaterThan(value.Ticks, MaxValue.Ticks, nameof(value), "Value for clock skew was exceeded."); - })) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The value to assign the role of . + /// + /// cannot be less than -or- + /// cannot exceed . + /// + public ClockSkew(TimeSpan value) : base(Validator.CheckParameter(value, () => + { + Validator.ThrowIfLowerThan(value.Ticks, MinValue.Ticks, nameof(value), "Value for clock skew cannot be negative."); + Validator.ThrowIfGreaterThan(value.Ticks, MaxValue.Ticks, nameof(value), "Value for clock skew was exceeded."); + })) + { + } - /// - /// Returns a > that represents this instance. - /// - /// A > that represents this instance. - public override string ToString() - { - return Value.ToString("c", CultureInfo.InvariantCulture); - } + /// + /// Returns a > that represents this instance. + /// + /// A > that represents this instance. + public override string ToString() + { + return Value.ToString("c", CultureInfo.InvariantCulture); } } diff --git a/src/Codebelt.SharedKernel/Codebelt.SharedKernel.csproj b/src/Codebelt.SharedKernel/Codebelt.SharedKernel.csproj index 9270b7a..35ec25e 100644 --- a/src/Codebelt.SharedKernel/Codebelt.SharedKernel.csproj +++ b/src/Codebelt.SharedKernel/Codebelt.SharedKernel.csproj @@ -1,4 +1,4 @@ - + A lightweight .NET library with essential components for implementing the shared kernel pattern within the context of Domain-Driven Design (DDD). diff --git a/src/Codebelt.SharedKernel/ComparableValueObject.cs b/src/Codebelt.SharedKernel/ComparableValueObject.cs index 0c9122b..cf5e300 100644 --- a/src/Codebelt.SharedKernel/ComparableValueObject.cs +++ b/src/Codebelt.SharedKernel/ComparableValueObject.cs @@ -1,53 +1,52 @@ -using System; +using System; using System.Numerics; using Savvyio.Domain; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +/// +/// Provides an implementation of tailored for handling a single value that implements the interface. +/// +/// The type of the object that implements the interface. +public abstract record ComparableValueObject : SingleValueObject, IComparisonOperators, ComparableValueObject, bool> where T : IComparable { /// - /// Provides an implementation of tailored for handling a single value that implements the interface. + /// Compares two values to determine which is greater. /// - /// The type of the object that implements the interface. - public abstract record ComparableValueObject : SingleValueObject, IComparisonOperators, ComparableValueObject, bool> where T : IComparable - { - /// - /// Compares two values to determine which is greater. - /// - /// The value to compare with . - /// The value to compare with . - /// true if > is greater than >; otherwise, false. - public static bool operator >(ComparableValueObject left, ComparableValueObject right) => left.Value.CompareTo(right.Value) > 0; + /// The value to compare with . + /// The value to compare with . + /// true if > is greater than >; otherwise, false. + public static bool operator >(ComparableValueObject left, ComparableValueObject right) => left.Value.CompareTo(right.Value) > 0; - /// - /// Compares two values to determine which is less or equal. - /// - /// The value to compare with . - /// The value to compare with . - /// true if > is greater than or equal to >; otherwise, false. - public static bool operator >=(ComparableValueObject left, ComparableValueObject right) => left.Value.CompareTo(right.Value) >= 0; + /// + /// Compares two values to determine which is less or equal. + /// + /// The value to compare with . + /// The value to compare with . + /// true if > is greater than or equal to >; otherwise, false. + public static bool operator >=(ComparableValueObject left, ComparableValueObject right) => left.Value.CompareTo(right.Value) >= 0; - /// - /// Compares two values to determine which is less. - /// - /// The value to compare with . - /// The value to compare with . - /// true if > is less than >; otherwise, false. - public static bool operator <(ComparableValueObject left, ComparableValueObject right) => left.Value.CompareTo(right.Value) < 0; + /// + /// Compares two values to determine which is less. + /// + /// The value to compare with . + /// The value to compare with . + /// true if > is less than >; otherwise, false. + public static bool operator <(ComparableValueObject left, ComparableValueObject right) => left.Value.CompareTo(right.Value) < 0; - /// - /// Compares two values to determine which is less or equal. - /// - /// The value to compare with . - /// The value to compare with . - /// true if > is less than or equal to >; otherwise, false. - public static bool operator <=(ComparableValueObject left, ComparableValueObject right) => left.Value.CompareTo(right.Value) <= 0; + /// + /// Compares two values to determine which is less or equal. + /// + /// The value to compare with . + /// The value to compare with . + /// true if > is less than or equal to >; otherwise, false. + public static bool operator <=(ComparableValueObject left, ComparableValueObject right) => left.Value.CompareTo(right.Value) <= 0; - /// - /// Initializes a new instance of the class. - /// - /// The value to associate to . - protected ComparableValueObject(T value) : base(value) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The value to associate to . + protected ComparableValueObject(T value) : base(value) + { } } diff --git a/src/Codebelt.SharedKernel/CoordinatedUniversalTime.cs b/src/Codebelt.SharedKernel/CoordinatedUniversalTime.cs index f87c99a..3cf5346 100644 --- a/src/Codebelt.SharedKernel/CoordinatedUniversalTime.cs +++ b/src/Codebelt.SharedKernel/CoordinatedUniversalTime.cs @@ -1,96 +1,95 @@ -using System; +using System; using System.Globalization; using Cuemon; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +/// +/// Represents a (UTC) object that can be used when you need a timestamp that is based on an absolute time. +/// +/// https://martinfowler.com/eaaCatalog/valueObject.html +/// +public record CoordinatedUniversalTime : ComparableValueObject { /// - /// Represents a (UTC) object that can be used when you need a timestamp that is based on an absolute time. + /// Denotes the largest possible value of . /// - /// https://martinfowler.com/eaaCatalog/valueObject.html - /// - public record CoordinatedUniversalTime : ComparableValueObject - { - /// - /// Denotes the largest possible value of . - /// - public static readonly CoordinatedUniversalTime MaxValue = DateTime.ParseExact("9999-12-31T22:59:59.9999999Z", "O", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); // Cannot use DateTime.MaxValue.ToUniversalTime() as it becomes 9999-12-31T23:59:59.9999999Z on Ubuntu 22.04 (but weirdly enough works on WSL with same distro). + public static readonly CoordinatedUniversalTime MaxValue = DateTime.ParseExact("9999-12-31T22:59:59.9999999Z", "O", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal); // Cannot use DateTime.MaxValue.ToUniversalTime() as it becomes 9999-12-31T23:59:59.9999999Z on Ubuntu 22.04 (but weirdly enough works on WSL with same distro). - /// - /// Denotes the smallest possible value of , at midnight on January 1, 0001 UTC, regardless of the local time zone. - /// - public static readonly CoordinatedUniversalTime MinValue = new(DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc)); + /// + /// Denotes the smallest possible value of , at midnight on January 1, 0001 UTC, regardless of the local time zone. + /// + public static readonly CoordinatedUniversalTime MinValue = new(DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc)); - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator DateTime (CoordinatedUniversalTime value) - { - return value.Value; - } + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// A that is equivalent to . + public static implicit operator DateTime (CoordinatedUniversalTime value) + { + return value.Value; + } - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator CoordinatedUniversalTime (DateTime value) - { - return new CoordinatedUniversalTime(value); - } + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// A that is equivalent to . + public static implicit operator CoordinatedUniversalTime (DateTime value) + { + return new CoordinatedUniversalTime(value); + } - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator CoordinatedUniversalTime (DateTimeOffset value) - { - return new CoordinatedUniversalTime(value.UtcDateTime); - } + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// A that is equivalent to . + public static implicit operator CoordinatedUniversalTime (DateTimeOffset value) + { + return new CoordinatedUniversalTime(value.UtcDateTime); + } - /// - /// Converts the string representation of a date and time to its > equivalent. - /// - /// A string that contains a date and time to convert. - /// A new instance of that represents . - public static CoordinatedUniversalTime FromString(string value) - { - return new CoordinatedUniversalTime(DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); - } + /// + /// Converts the string representation of a date and time to its > equivalent. + /// + /// A string that contains a date and time to convert. + /// A new instance of that represents . + public static CoordinatedUniversalTime FromString(string value) + { + return new CoordinatedUniversalTime(DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); + } - /// - /// Returns a whose value is the current UTC date and time. - /// - /// A new instance of set to the current date and time on this computer. - public static CoordinatedUniversalTime Now() - { - return new CoordinatedUniversalTime(DateTime.UtcNow); - } + /// + /// Returns a whose value is the current UTC date and time. + /// + /// A new instance of set to the current date and time on this computer. + public static CoordinatedUniversalTime Now() + { + return new CoordinatedUniversalTime(DateTime.UtcNow); + } - /// - /// Initializes a new instance of the class. - /// - /// The value to assign the role of . - /// - /// is not in a valid state, e.g., it's different from . - /// - public CoordinatedUniversalTime(DateTime value) : base(Validator.CheckParameter(value, () => - { - Validator.ThrowIfTrue(value.Kind != DateTimeKind.Utc, nameof(value)); - })) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The value to assign the role of . + /// + /// is not in a valid state, e.g., it's different from . + /// + public CoordinatedUniversalTime(DateTime value) : base(Validator.CheckParameter(value, () => + { + Validator.ThrowIfTrue(value.Kind != DateTimeKind.Utc, nameof(value)); + })) + { + } - /// - /// Returns a > that represents this instance and complies with ISO 8601. - /// - /// A > that represents this instance and complies with ISO 8601. - public override string ToString() - { - return Value.ToString("O", CultureInfo.InvariantCulture); - } + /// + /// Returns a > that represents this instance and complies with ISO 8601. + /// + /// A > that represents this instance and complies with ISO 8601. + public override string ToString() + { + return Value.ToString("O", CultureInfo.InvariantCulture); } } diff --git a/src/Codebelt.SharedKernel/CorrelationId.cs b/src/Codebelt.SharedKernel/CorrelationId.cs index 9293122..4841efd 100644 --- a/src/Codebelt.SharedKernel/CorrelationId.cs +++ b/src/Codebelt.SharedKernel/CorrelationId.cs @@ -1,60 +1,59 @@ -using System; +using System; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +/// +/// Represents a object that can be used as unique identifier that help you trace requests across multiple services in a distributed system. +/// +/// https://martinfowler.com/eaaCatalog/valueObject.html +/// +public record CorrelationId : Token { /// - /// Represents a object that can be used as unique identifier that help you trace requests across multiple services in a distributed system. + /// Performs an implicit conversion from to . /// - /// https://martinfowler.com/eaaCatalog/valueObject.html - /// - public record CorrelationId : Token + /// The value to convert. + /// A that is equivalent to . + public static implicit operator CorrelationId (Guid value) { - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator CorrelationId (Guid value) - { - return new CorrelationId(value); - } + return new CorrelationId(value); + } - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator CorrelationId (string value) - { - return new CorrelationId(value); - } + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// A that is equivalent to . + public static implicit operator CorrelationId (string value) + { + return new CorrelationId(value); + } - /// - /// Initializes a new instance of the class. - /// - /// The value to assign the role of . - public CorrelationId(Guid value) : this(value.ToString("N")) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The value to assign the role of . + public CorrelationId(Guid value) : this(value.ToString("N")) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The value to assign the role of . - /// - /// cannot be null. - /// - /// - /// cannot be empty or consist only of white-space characters. - /// - /// - /// contained one or more white-space characters -or- - /// was less than 32 characters -or- - /// was greater than 128 characters -or- - /// consist only of same repeated character. - /// - public CorrelationId(string value) : base(value, o => o.MaximumCharacterFrequency = 1) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The value to assign the role of . + /// + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters. + /// + /// + /// contained one or more white-space characters -or- + /// was less than 32 characters -or- + /// was greater than 128 characters -or- + /// consist only of same repeated character. + /// + public CorrelationId(string value) : base(value, o => o.MaximumCharacterFrequency = 1) + { } } diff --git a/src/Codebelt.SharedKernel/GlobalSuppressions.cs b/src/Codebelt.SharedKernel/GlobalSuppressions.cs index 9516c38..e1940f6 100644 --- a/src/Codebelt.SharedKernel/GlobalSuppressions.cs +++ b/src/Codebelt.SharedKernel/GlobalSuppressions.cs @@ -1,4 +1,4 @@ -// This file is used by Code Analysis to maintain SuppressMessage +// This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. diff --git a/src/Codebelt.SharedKernel/Security/AccessKey.cs b/src/Codebelt.SharedKernel/Security/AccessKey.cs index 3d0ff4f..1532496 100644 --- a/src/Codebelt.SharedKernel/Security/AccessKey.cs +++ b/src/Codebelt.SharedKernel/Security/AccessKey.cs @@ -1,73 +1,72 @@ -using System; +using System; using Cuemon; using Savvyio.Domain; -namespace Codebelt.SharedKernel.Security +namespace Codebelt.SharedKernel.Security; + +/// +/// Represents an object that can be used for API key-based authentication and similar. +/// +/// https://martinfowler.com/eaaCatalog/valueObject.html +/// +public record AccessKey : ValueObject { /// - /// Represents an object that can be used for API key-based authentication and similar. + /// Issues a new instance of with an optional . /// - /// https://martinfowler.com/eaaCatalog/valueObject.html - /// - public record AccessKey : ValueObject + /// The secret used to generate the access key. + /// The lifespan of the . If null, the access key will not expire. + /// Returns a new > instance with the specified secret and lifespan. + public static AccessKey Issue(Secret secret, TimeToLive lifespan = null) { - /// - /// Issues a new instance of with an optional . - /// - /// The secret used to generate the access key. - /// The lifespan of the . If null, the access key will not expire. - /// Returns a new > instance with the specified secret and lifespan. - public static AccessKey Issue(Secret secret, TimeToLive lifespan = null) + return lifespan == null + ? new AccessKey(secret) + : new AccessKey(secret, o => { - return lifespan == null - ? new AccessKey(secret) - : new AccessKey(secret, o => - { - var utcNow = DateTime.UtcNow; - o.ValidFrom = utcNow; - o.Expires = utcNow.Add(lifespan.Value); - }); - } + var utcNow = DateTime.UtcNow; + o.ValidFrom = utcNow; + o.Expires = utcNow.Add(lifespan.Value); + }); + } - /// - /// Initializes a new instance of the class. - /// - /// The of the access key. - /// The which may be configured. - /// - /// failed to configure an instance of in a valid state. - /// - public AccessKey(Secret secret, Action setup = null) - { - Validator.ThrowIfInvalidConfigurator(setup, out var options); - Secret = secret; - ValidFrom = options.ValidFrom; - Expires = options.Expires; - DesiredTolerance = options.DesiredTolerance; - } + /// + /// Initializes a new instance of the class. + /// + /// The of the access key. + /// The which may be configured. + /// + /// failed to configure an instance of in a valid state. + /// + public AccessKey(Secret secret, Action setup = null) + { + Validator.ThrowIfInvalidConfigurator(setup, out var options); + Secret = secret; + ValidFrom = options.ValidFrom; + Expires = options.Expires; + DesiredTolerance = options.DesiredTolerance; + } - /// - /// Gets the secret of the access key. - /// - /// The secret of the access key. - public string Secret { get; } + /// + /// Gets the secret of the access key. + /// + /// The secret of the access key. + public string Secret { get; } - /// - /// Gets the date and time,expressed as Coordinated Universal Time (UTC), from which the access key is valid. - /// - /// The date and time, expressed as Coordinated Universal Time (UTC), from which the access key is valid. - public DateTime ValidFrom { get; } + /// + /// Gets the date and time,expressed as Coordinated Universal Time (UTC), from which the access key is valid. + /// + /// The date and time, expressed as Coordinated Universal Time (UTC), from which the access key is valid. + public DateTime ValidFrom { get; } - /// - /// Gets the expiry date and time of the access key expressed as Coordinated Universal Time (UTC). - /// - /// The expiry date and time of the access key expressed as Coordinated Universal Time (UTC). - public DateTime Expires { get; } + /// + /// Gets the expiry date and time of the access key expressed as Coordinated Universal Time (UTC). + /// + /// The expiry date and time of the access key expressed as Coordinated Universal Time (UTC). + public DateTime Expires { get; } - /// - /// Gets the desired tolerance for potential clock skew variations. - /// - /// The desired tolerance for potential clock skew variations. - public TimeSpan DesiredTolerance { get; } - } + /// + /// Gets the desired tolerance for potential clock skew variations. + /// + /// The desired tolerance for potential clock skew variations. + public TimeSpan DesiredTolerance { get; } } diff --git a/src/Codebelt.SharedKernel/Security/AccessKeyExtensions.cs b/src/Codebelt.SharedKernel/Security/AccessKeyExtensions.cs index 32de168..faa8799 100644 --- a/src/Codebelt.SharedKernel/Security/AccessKeyExtensions.cs +++ b/src/Codebelt.SharedKernel/Security/AccessKeyExtensions.cs @@ -1,29 +1,28 @@ -using System; +using System; using Cuemon; -namespace Codebelt.SharedKernel.Security +namespace Codebelt.SharedKernel.Security; + +/// +/// Extension methods for the record. +/// +public static class AccessKeyExtensions { /// - /// Extension methods for the record. + /// Returns true if ... is valid. /// - public static class AccessKeyExtensions + /// The to extend. + /// The optional to use instead of . + /// true if the specified is valid; otherwise, false. + /// + /// cannot be null. + /// + public static bool IsValid(this AccessKey accessKey, CoordinatedUniversalTime utc = null) { - /// - /// Returns true if ... is valid. - /// - /// The to extend. - /// The optional to use instead of . - /// true if the specified is valid; otherwise, false. - /// - /// cannot be null. - /// - public static bool IsValid(this AccessKey accessKey, CoordinatedUniversalTime utc = null) - { - Validator.ThrowIfNull(accessKey); - utc ??= CoordinatedUniversalTime.Now(); - var start = accessKey.ValidFrom; - var expires = accessKey.Expires.Add(accessKey.DesiredTolerance); - return utc >= start && utc <= expires; - } + Validator.ThrowIfNull(accessKey); + utc ??= CoordinatedUniversalTime.Now(); + var start = accessKey.ValidFrom; + var expires = accessKey.Expires.Add(accessKey.DesiredTolerance); + return utc >= start && utc <= expires; } } diff --git a/src/Codebelt.SharedKernel/Security/AccessKeyOptions.cs b/src/Codebelt.SharedKernel/Security/AccessKeyOptions.cs index 0f5841e..061cc78 100644 --- a/src/Codebelt.SharedKernel/Security/AccessKeyOptions.cs +++ b/src/Codebelt.SharedKernel/Security/AccessKeyOptions.cs @@ -1,59 +1,58 @@ -using System; +using System; using Cuemon; using Cuemon.Configuration; -namespace Codebelt.SharedKernel.Security +namespace Codebelt.SharedKernel.Security; + +/// +/// Provides options to be used with >. +/// +/// +public class AccessKeyOptions : IValidatableParameterObject { /// - /// Provides options to be used with >. + /// Initializes a new instance of the class. /// - /// - public class AccessKeyOptions : IValidatableParameterObject + public AccessKeyOptions() { - /// - /// Initializes a new instance of the class. - /// - public AccessKeyOptions() - { - ValidFrom = CoordinatedUniversalTime.MinValue; - Expires = CoordinatedUniversalTime.MaxValue; - DesiredTolerance = ClockSkew.FromSeconds(30); - } + ValidFrom = CoordinatedUniversalTime.MinValue; + Expires = CoordinatedUniversalTime.MaxValue; + DesiredTolerance = ClockSkew.FromSeconds(30); + } - /// - /// Gets or sets the date and time from which the access key is valid. Default is (e.g., always valid). - /// - /// The date and time from which the access key is valid. - public CoordinatedUniversalTime ValidFrom { get; set; } + /// + /// Gets or sets the date and time from which the access key is valid. Default is (e.g., always valid). + /// + /// The date and time from which the access key is valid. + public CoordinatedUniversalTime ValidFrom { get; set; } - /// - /// Gets or sets the expiry date and time of the access key. Default is (e.g., no expiration). - /// - /// The expiry date and time of the access key. - public CoordinatedUniversalTime Expires { get; set; } + /// + /// Gets or sets the expiry date and time of the access key. Default is (e.g., no expiration). + /// + /// The expiry date and time of the access key. + public CoordinatedUniversalTime Expires { get; set; } - /// - /// Gets or sets the desired tolerance for potential clock skew variations. Default is 30 seconds. - /// - /// The desired tolerance for potential clock skew variations. - public ClockSkew DesiredTolerance { get; set; } + /// + /// Gets or sets the desired tolerance for potential clock skew variations. Default is 30 seconds. + /// + /// The desired tolerance for potential clock skew variations. + public ClockSkew DesiredTolerance { get; set; } - /// - /// Determines whether the public read-write properties of this instance are in a valid state. - /// - /// This method is expected to throw exceptions when one or more conditions fails to be in a valid state. - /// - /// is null -or- - /// is null -or- - /// is null -or- - /// is greater than . - /// - public void ValidateOptions() - { - Validator.ThrowIfInvalidState(ValidFrom == null); - Validator.ThrowIfInvalidState(Expires == null); - Validator.ThrowIfInvalidState(DesiredTolerance == null); - Validator.ThrowIfInvalidState(ValidFrom > Expires); - } + /// + /// Determines whether the public read-write properties of this instance are in a valid state. + /// + /// This method is expected to throw exceptions when one or more conditions fails to be in a valid state. + /// + /// is null -or- + /// is null -or- + /// is null -or- + /// is greater than . + /// + public void ValidateOptions() + { + Validator.ThrowIfInvalidState(ValidFrom == null); + Validator.ThrowIfInvalidState(Expires == null); + Validator.ThrowIfInvalidState(DesiredTolerance == null); + Validator.ThrowIfInvalidState(ValidFrom > Expires); } } diff --git a/src/Codebelt.SharedKernel/Security/Secret.cs b/src/Codebelt.SharedKernel/Security/Secret.cs index e90d931..b2062c3 100644 --- a/src/Codebelt.SharedKernel/Security/Secret.cs +++ b/src/Codebelt.SharedKernel/Security/Secret.cs @@ -1,81 +1,80 @@ -using System; +using System; using Cuemon.Extensions; using Cuemon.Text; -namespace Codebelt.SharedKernel.Security +namespace Codebelt.SharedKernel.Security; + +/// +/// Represents a object that can be used for storing sensitive data. +/// +/// https://martinfowler.com/eaaCatalog/valueObject.html +/// +public record Secret : Token { /// - /// Represents a object that can be used for storing sensitive data. + /// Performs an implicit conversion from to . /// - /// https://martinfowler.com/eaaCatalog/valueObject.html - /// - public record Secret : Token + /// The value to convert. + /// A that is equivalent to . + public static implicit operator Secret (Guid value) { - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator Secret (Guid value) - { - return new Secret(value); - } + return new Secret(value); + } - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator Secret (string value) - { - return new Secret(value); - } + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// A that is equivalent to . + public static implicit operator Secret (string value) + { + return new Secret(value); + } - /// - /// Initializes a new instance of the class. - /// - /// The value to assign the role of . - public Secret(Guid value) : this(value.ToString("N")) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The value to assign the role of . + public Secret(Guid value) : this(value.ToString("N")) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The value to assign the role of . - /// - /// cannot be null. - /// - /// - /// cannot be empty or consist only of white-space characters. - /// - /// - /// contained one or more white-space characters -or- - /// was less than 32 characters -or- - /// was greater than 128 characters -or- - /// consist only of same repeated character. - /// - public Secret(string value) : base(value, o => o.MaximumCharacterFrequency = 1) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The value to assign the role of . + /// + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters. + /// + /// + /// contained one or more white-space characters -or- + /// was less than 32 characters -or- + /// was greater than 128 characters -or- + /// consist only of same repeated character. + /// + public Secret(string value) : base(value, o => o.MaximumCharacterFrequency = 1) + { + } - /// - /// Returns a > that represents this instance. - /// - /// A > that represents this instance. - public override string ToString() - { - return Value; - } + /// + /// Returns a > that represents this instance. + /// + /// A > that represents this instance. + public override string ToString() + { + return Value; + } - /// - /// Converts this instance to its equivalent [] representation. - /// - /// The which may be configured. - /// A [] that is equivalent to this instance. - public byte[] ToByteArray(Action setup = null) - { - return ToString().ToByteArray(setup); - } + /// + /// Converts this instance to its equivalent [] representation. + /// + /// The which may be configured. + /// A [] that is equivalent to this instance. + public byte[] ToByteArray(Action setup = null) + { + return ToString().ToByteArray(setup); } } diff --git a/src/Codebelt.SharedKernel/TimeToLive.cs b/src/Codebelt.SharedKernel/TimeToLive.cs index ed0d46f..90285c1 100644 --- a/src/Codebelt.SharedKernel/TimeToLive.cs +++ b/src/Codebelt.SharedKernel/TimeToLive.cs @@ -1,124 +1,123 @@ -using System; +using System; using System.Globalization; using Cuemon; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +/// +/// Represents a (TTL) object that can be used when issuing authentication tokens or similar. +/// +/// https://martinfowler.com/eaaCatalog/valueObject.html +/// +public record TimeToLive : ComparableValueObject { /// - /// Represents a (TTL) object that can be used when issuing authentication tokens or similar. + /// Performs an implicit conversion from to . /// - /// https://martinfowler.com/eaaCatalog/valueObject.html - /// - public record TimeToLive : ComparableValueObject + /// The value to convert. + /// A that is equivalent to . + public static implicit operator TimeSpan(TimeToLive value) { - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator TimeSpan(TimeToLive value) - { - return value.Value; - } + return value.Value; + } - /// - /// Performs an implicit conversion from to . - /// - /// The value to convert. - /// A that is equivalent to . - public static implicit operator TimeToLive(TimeSpan value) - { - return new TimeToLive(value); - } + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// A that is equivalent to . + public static implicit operator TimeToLive(TimeSpan value) + { + return new TimeToLive(value); + } - /// - /// Denotes the largest possible value of . - /// - public static readonly TimeSpan MaxValue = TimeSpan.MaxValue; + /// + /// Denotes the largest possible value of . + /// + public static readonly TimeSpan MaxValue = TimeSpan.MaxValue; - /// - /// Denotes the smallest possible value of . - /// - public static readonly TimeSpan MinValue = TimeSpan.Zero; + /// + /// Denotes the smallest possible value of . + /// + public static readonly TimeSpan MinValue = TimeSpan.Zero; - /// - /// Returns a that represents a specified number of minutes. - /// - /// A number of minutes. - /// A new instance of that represents . - public static TimeToLive FromMinutes(double value) - { - var utcNow = DateTime.UtcNow; - return utcNow.AddMinutes(value) - utcNow; - } + /// + /// Returns a that represents a specified number of minutes. + /// + /// A number of minutes. + /// A new instance of that represents . + public static TimeToLive FromMinutes(double value) + { + var utcNow = DateTime.UtcNow; + return utcNow.AddMinutes(value) - utcNow; + } - /// - /// Returns a that represents a specified number of hours. - /// - /// A number of hours. - /// A new instance of that represents . - public static TimeToLive FromHours(double value) - { - var utcNow = DateTime.UtcNow; - return utcNow.AddHours(value) - utcNow; - } + /// + /// Returns a that represents a specified number of hours. + /// + /// A number of hours. + /// A new instance of that represents . + public static TimeToLive FromHours(double value) + { + var utcNow = DateTime.UtcNow; + return utcNow.AddHours(value) - utcNow; + } - /// - /// Returns a that represents a specified number of days. - /// - /// A number of days. - /// A new instance of that represents . - public static TimeToLive FromDays(double value) - { - var utcNow = DateTime.UtcNow; - return utcNow.AddDays(value) - utcNow; - } + /// + /// Returns a that represents a specified number of days. + /// + /// A number of days. + /// A new instance of that represents . + public static TimeToLive FromDays(double value) + { + var utcNow = DateTime.UtcNow; + return utcNow.AddDays(value) - utcNow; + } - /// - /// Returns a that represents a specified number of months. - /// - /// A number of months. - /// A new instance of that represents . - public static TimeToLive FromMonths(int value) - { - var utcNow = DateTime.UtcNow; - return utcNow.AddMonths(value) - utcNow; - } + /// + /// Returns a that represents a specified number of months. + /// + /// A number of months. + /// A new instance of that represents . + public static TimeToLive FromMonths(int value) + { + var utcNow = DateTime.UtcNow; + return utcNow.AddMonths(value) - utcNow; + } - /// - /// Returns a that represents a specified number of years. - /// - /// A number of years. - /// A new instance of that represents . - public static TimeToLive FromYears(int value) - { - var utcNow = DateTime.UtcNow; - return utcNow.AddYears(value) - utcNow; - } + /// + /// Returns a that represents a specified number of years. + /// + /// A number of years. + /// A new instance of that represents . + public static TimeToLive FromYears(int value) + { + var utcNow = DateTime.UtcNow; + return utcNow.AddYears(value) - utcNow; + } - /// - /// Initializes a new instance of the class. - /// - /// The value to assign the role of . - /// - /// cannot be less than -or- - /// cannot exceed . - /// - public TimeToLive(TimeSpan value) : base(Validator.CheckParameter(value, () => - { - Validator.ThrowIfLowerThan(value.Ticks, MinValue.Ticks, nameof(value), "Value for lifespan cannot be negative."); - Validator.ThrowIfGreaterThan(value.Ticks, MaxValue.Ticks, nameof(value), "Value for lifespan was exceeded."); - })) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The value to assign the role of . + /// + /// cannot be less than -or- + /// cannot exceed . + /// + public TimeToLive(TimeSpan value) : base(Validator.CheckParameter(value, () => + { + Validator.ThrowIfLowerThan(value.Ticks, MinValue.Ticks, nameof(value), "Value for lifespan cannot be negative."); + Validator.ThrowIfGreaterThan(value.Ticks, MaxValue.Ticks, nameof(value), "Value for lifespan was exceeded."); + })) + { + } - /// - /// Returns a > that represents this instance. - /// - /// A > that represents this instance. - public override string ToString() - { - return Value.ToString("c", CultureInfo.InvariantCulture); - } + /// + /// Returns a > that represents this instance. + /// + /// A > that represents this instance. + public override string ToString() + { + return Value.ToString("c", CultureInfo.InvariantCulture); } } diff --git a/src/Codebelt.SharedKernel/Token.cs b/src/Codebelt.SharedKernel/Token.cs index c5f0406..cf63d18 100644 --- a/src/Codebelt.SharedKernel/Token.cs +++ b/src/Codebelt.SharedKernel/Token.cs @@ -1,52 +1,51 @@ -using Codebelt.SharedKernel.Security; +using Codebelt.SharedKernel.Security; using Savvyio.Domain; using System; using System.Linq; using Cuemon; using Cuemon.Extensions; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +/// +/// Represents a object that encapsulates an immutable value used for identification or access control. +/// +/// https://martinfowler.com/eaaCatalog/valueObject.html +/// +public record Token : SingleValueObject { + private static readonly char[] WhiteSpaceChars = Alphanumeric.WhiteSpace.ToCharArray(); + /// - /// Represents a object that encapsulates an immutable value used for identification or access control. + /// Initializes a new instance of the class. /// - /// https://martinfowler.com/eaaCatalog/valueObject.html - /// - public record Token : SingleValueObject + /// The value to assign the role of . + /// The which may be configured. + /// + /// cannot be null. + /// + /// + /// cannot be empty or consist only of white-space characters. + /// + /// + /// contained one or more white-space characters -or- + /// was less than characters -or- + /// was greater than characters -or- + /// had a character frequency greater than . + /// + public Token(string value, Action setup = null) : base(Validator.CheckParameter(value, () => { - private static readonly char[] WhiteSpaceChars = Alphanumeric.WhiteSpace.ToCharArray(); - - /// - /// Initializes a new instance of the class. - /// - /// The value to assign the role of . - /// The which may be configured. - /// - /// cannot be null. - /// - /// - /// cannot be empty or consist only of white-space characters. - /// - /// - /// contained one or more white-space characters -or- - /// was less than characters -or- - /// was greater than characters -or- - /// had a character frequency greater than . - /// - public Token(string value, Action setup = null) : base(Validator.CheckParameter(value, () => + Validator.ThrowIfInvalidConfigurator(setup, out var options); + Validator.ThrowIfNullOrWhitespace(value, message: "Value cannot be null, empty or consist only of white-space characters."); + Validator.ThrowIfLowerThan(value.Length, options.MinimumLength == 0 ? int.MinValue: options.MinimumLength, nameof(value), $"The minimum length of {nameof(value)} was not meet. {options.MinimumLength} characters are required."); + Validator.ThrowIfGreaterThan(value.Length, options.MaximumLength == 0 ? int.MaxValue: options.MaximumLength, nameof(value), $"The maximum length of {nameof(value)} was exceeded. {options.MaximumLength} characters are allowed."); + Validator.ThrowIfContainsAny(value, WhiteSpaceChars, message: $"White-space characters are not allowed inside {nameof(value)}."); + Validator.ThrowWhen(condition => condition.IsTrue((out string frequency) => { - Validator.ThrowIfInvalidConfigurator(setup, out var options); - Validator.ThrowIfNullOrWhitespace(value, message: "Value cannot be null, empty or consist only of white-space characters."); - Validator.ThrowIfLowerThan(value.Length, options.MinimumLength == 0 ? int.MinValue: options.MinimumLength, nameof(value), $"The minimum length of {nameof(value)} was not meet. {options.MinimumLength} characters are required."); - Validator.ThrowIfGreaterThan(value.Length, options.MaximumLength == 0 ? int.MaxValue: options.MaximumLength, nameof(value), $"The maximum length of {nameof(value)} was exceeded. {options.MaximumLength} characters are allowed."); - Validator.ThrowIfContainsAny(value, WhiteSpaceChars, message: $"White-space characters are not allowed inside {nameof(value)}."); - Validator.ThrowWhen(condition => condition.IsTrue((out string frequency) => - { - frequency = value.Distinct().Order().FromChars(); - return options.MaximumCharacterFrequency > 0 && frequency.Length <= options.MaximumCharacterFrequency; - }).Create(frequency => new ArgumentOutOfRangeException(nameof(value), DelimitedString.Create(frequency), $"Value suggest to high frequency of repeated characters. At least {options.MaximumCharacterFrequency} distinct characters are required.")).TryThrow()); - })) - { - } + frequency = value.Distinct().Order().FromChars(); + return options.MaximumCharacterFrequency > 0 && frequency.Length <= options.MaximumCharacterFrequency; + }).Create(frequency => new ArgumentOutOfRangeException(nameof(value), DelimitedString.Create(frequency), $"Value suggest to high frequency of repeated characters. At least {options.MaximumCharacterFrequency} distinct characters are required.")).TryThrow()); + })) + { } } diff --git a/src/Codebelt.SharedKernel/TokenOptions.cs b/src/Codebelt.SharedKernel/TokenOptions.cs index f025f43..5a47dcb 100644 --- a/src/Codebelt.SharedKernel/TokenOptions.cs +++ b/src/Codebelt.SharedKernel/TokenOptions.cs @@ -1,70 +1,69 @@ -using Cuemon.Configuration; +using Cuemon.Configuration; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +/// +/// Configuration options for . +/// +public class TokenOptions : IParameterObject { + private byte _maximumCharacterFrequency; + /// - /// Configuration options for . + /// Initializes a new instance of the class. /// - public class TokenOptions : IParameterObject + /// + /// The following table shows the initial property values for an instance of . + /// + /// + /// Property + /// Initial Value + /// + /// + /// + /// 32 + /// + /// + /// + /// 128 + /// + /// + /// + /// 4 + /// + /// + /// + public TokenOptions() { - private byte _maximumCharacterFrequency; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The following table shows the initial property values for an instance of . - /// - /// - /// Property - /// Initial Value - /// - /// - /// - /// 32 - /// - /// - /// - /// 128 - /// - /// - /// - /// 4 - /// - /// - /// - public TokenOptions() - { - MinimumLength = 32; - MaximumLength = 128; - MaximumCharacterFrequency = 4; - } + MinimumLength = 32; + MaximumLength = 128; + MaximumCharacterFrequency = 4; + } - /// - /// Gets or sets the minimum length of the . - /// - /// The minimum length of the . - public byte MinimumLength { get; set; } + /// + /// Gets or sets the minimum length of the . + /// + /// The minimum length of the . + public byte MinimumLength { get; set; } - /// - /// Gets or sets the maximum length of the . - /// - /// The maximum length of the . - public byte MaximumLength { get; set; } + /// + /// Gets or sets the maximum length of the . + /// + /// The maximum length of the . + public byte MaximumLength { get; set; } - /// - /// Gets or sets the maximum character frequency of the . - /// - /// The maximum character frequency of the . - /// In the event that is greater than the , it will be set to the . - public byte MaximumCharacterFrequency + /// + /// Gets or sets the maximum character frequency of the . + /// + /// The maximum character frequency of the . + /// In the event that is greater than the , it will be set to the . + public byte MaximumCharacterFrequency + { + get { - get - { - if (_maximumCharacterFrequency > MinimumLength) { _maximumCharacterFrequency = MinimumLength; } - return _maximumCharacterFrequency; - } - set => _maximumCharacterFrequency = value; + if (_maximumCharacterFrequency > MinimumLength) { _maximumCharacterFrequency = MinimumLength; } + return _maximumCharacterFrequency; } + set => _maximumCharacterFrequency = value; } } diff --git a/test/Codebelt.SharedKernel.Tests/ClockSkewTest.cs b/test/Codebelt.SharedKernel.Tests/ClockSkewTest.cs index 111e97d..54c3be5 100644 --- a/test/Codebelt.SharedKernel.Tests/ClockSkewTest.cs +++ b/test/Codebelt.SharedKernel.Tests/ClockSkewTest.cs @@ -1,103 +1,102 @@ -using System; +using System; using Cuemon.Extensions.IO; using Codebelt.Extensions.Xunit; using Savvyio.Extensions.Newtonsoft.Json; using Savvyio.Extensions.Text.Json; using Xunit; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +public class ClockSkewTest : Test { - public class ClockSkewTest : Test + public ClockSkewTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Comparison_LeftShouldBeGreaterThanRight() + { + var left = ClockSkew.MaxValue; + var right = ClockSkew.MinValue; + Assert.True(left > right); + } + + [Fact] + public void Comparison_RightShouldBeGreaterThanLeft() + { + var left = ClockSkew.MinValue; + var right = ClockSkew.MaxValue; + Assert.True(left < right); + } + + [Fact] + public void Comparison_LeftShouldBeGreaterThanOrEqualToRight() + { + var left = new ClockSkew(TimeSpan.FromHours(1)); + var right = new ClockSkew(TimeSpan.FromHours(1)); + Assert.True(left >= right); + } + + [Fact] + public void Comparison_RightShouldBeGreaterThanOrEqualToLeft() + { + var left = new ClockSkew(TimeSpan.FromHours(1)); + var right = new ClockSkew(TimeSpan.FromHours(2)); + Assert.True(left <= right); + } + + [Fact] + public void Comparison_ShouldBeEqual() + { + var left = new ClockSkew(TimeSpan.FromHours(1)); + var right = new ClockSkew(TimeSpan.FromHours(1)); + Assert.Equal(right, left); + } + + [Fact] + public void Comparison_ShouldBeDifferent() + { + var left = new ClockSkew(TimeSpan.FromTicks(1)); + var right = new ClockSkew(TimeSpan.FromHours(1)); + Assert.NotEqual(right, left); + } + + [Fact] + public void FromMinutes_ShouldHaveFifteenMinutesSkew() + { + var sut = ClockSkew.FromMinutes(15); + + Assert.Equal(15, sut.Value.TotalMinutes); + } + + [Fact] + public void FromSeconds_ShouldHaveThirtySecondsSkew() + { + var sut = ClockSkew.FromSeconds(30); + + Assert.Equal(30, sut.Value.TotalSeconds); + } + + [Fact] + public void ConversionAndStringRepresentation_ShouldRepresentCorrectly() { - public ClockSkewTest(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public void Comparison_LeftShouldBeGreaterThanRight() - { - var left = ClockSkew.MaxValue; - var right = ClockSkew.MinValue; - Assert.True(left > right); - } - - [Fact] - public void Comparison_RightShouldBeGreaterThanLeft() - { - var left = ClockSkew.MinValue; - var right = ClockSkew.MaxValue; - Assert.True(left < right); - } - - [Fact] - public void Comparison_LeftShouldBeGreaterThanOrEqualToRight() - { - var left = new ClockSkew(TimeSpan.FromHours(1)); - var right = new ClockSkew(TimeSpan.FromHours(1)); - Assert.True(left >= right); - } - - [Fact] - public void Comparison_RightShouldBeGreaterThanOrEqualToLeft() - { - var left = new ClockSkew(TimeSpan.FromHours(1)); - var right = new ClockSkew(TimeSpan.FromHours(2)); - Assert.True(left <= right); - } - - [Fact] - public void Comparison_ShouldBeEqual() - { - var left = new ClockSkew(TimeSpan.FromHours(1)); - var right = new ClockSkew(TimeSpan.FromHours(1)); - Assert.Equal(right, left); - } - - [Fact] - public void Comparison_ShouldBeDifferent() - { - var left = new ClockSkew(TimeSpan.FromTicks(1)); - var right = new ClockSkew(TimeSpan.FromHours(1)); - Assert.NotEqual(right, left); - } - - [Fact] - public void FromMinutes_ShouldHaveFifteenMinutesSkew() - { - var sut = ClockSkew.FromMinutes(15); - - Assert.Equal(15, sut.Value.TotalMinutes); - } - - [Fact] - public void FromSeconds_ShouldHaveThirtySecondsSkew() - { - var sut = ClockSkew.FromSeconds(30); - - Assert.Equal(30, sut.Value.TotalSeconds); - } - - [Fact] - public void ConversionAndStringRepresentation_ShouldRepresentCorrectly() - { - ClockSkew sut = TimeSpan.FromSeconds(30); - TimeSpan actual = sut; - - Assert.Equal(TimeSpan.FromSeconds(30), actual); - Assert.Equal("00:00:30", sut.ToString()); - } - - [Fact] - public void Marshalling_ShouldRepresentCorrectly() - { - var sut = ClockSkew.FromSeconds(30); - var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); - var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); - - TestOutput.WriteLine(json); - - Assert.Equal("\"00:00:30\"", json); - Assert.Equal(json, newtonsoftJson); - } + ClockSkew sut = TimeSpan.FromSeconds(30); + TimeSpan actual = sut; + + Assert.Equal(TimeSpan.FromSeconds(30), actual); + Assert.Equal("00:00:30", sut.ToString()); + } + + [Fact] + public void Marshalling_ShouldRepresentCorrectly() + { + var sut = ClockSkew.FromSeconds(30); + var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); + var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); + + TestOutput.WriteLine(json); + + Assert.Equal("\"00:00:30\"", json); + Assert.Equal(json, newtonsoftJson); } } diff --git a/test/Codebelt.SharedKernel.Tests/Codebelt.SharedKernel.Tests.csproj b/test/Codebelt.SharedKernel.Tests/Codebelt.SharedKernel.Tests.csproj index 23688fb..f8cac6e 100644 --- a/test/Codebelt.SharedKernel.Tests/Codebelt.SharedKernel.Tests.csproj +++ b/test/Codebelt.SharedKernel.Tests/Codebelt.SharedKernel.Tests.csproj @@ -1,4 +1,4 @@ - + Codebelt.SharedKernel diff --git a/test/Codebelt.SharedKernel.Tests/CoordinatedUniversalTimeTest.cs b/test/Codebelt.SharedKernel.Tests/CoordinatedUniversalTimeTest.cs index 8c924b7..8582810 100644 --- a/test/Codebelt.SharedKernel.Tests/CoordinatedUniversalTimeTest.cs +++ b/test/Codebelt.SharedKernel.Tests/CoordinatedUniversalTimeTest.cs @@ -1,136 +1,135 @@ -using System; +using System; using Cuemon.Extensions.IO; using Codebelt.Extensions.Xunit; using Savvyio.Extensions.Newtonsoft.Json; using Savvyio.Extensions.Text.Json; using Xunit; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +public class CoordinatedUniversalTimeTest : Test { - public class CoordinatedUniversalTimeTest : Test + public static TheoryData UtcCompliantDates => new() { - public static TheoryData UtcCompliantDates => new() - { - "2020-01-01T00:00:00Z", - "2024-03-17T15:27:30.0000000+08:00", - "2024-02-29T10:15:30+02:00", - CoordinatedUniversalTime.MaxValue.ToString(), - CoordinatedUniversalTime.MinValue.ToString() - }; - - public CoordinatedUniversalTimeTest(ITestOutputHelper output) : base(output) - { - } + "2020-01-01T00:00:00Z", + "2024-03-17T15:27:30.0000000+08:00", + "2024-02-29T10:15:30+02:00", + CoordinatedUniversalTime.MaxValue.ToString(), + CoordinatedUniversalTime.MinValue.ToString() + }; + + public CoordinatedUniversalTimeTest(ITestOutputHelper output) : base(output) + { + } - [Theory] - [MemberData(nameof(UtcCompliantDates))] - public void FromString_ShouldBeExpressedAsCoordinatedUniversalTime(string value) - { - var sut = CoordinatedUniversalTime.FromString(value); - Assert.Equal(DateTimeKind.Utc, sut.Value.Kind); - } + [Theory] + [MemberData(nameof(UtcCompliantDates))] + public void FromString_ShouldBeExpressedAsCoordinatedUniversalTime(string value) + { + var sut = CoordinatedUniversalTime.FromString(value); + Assert.Equal(DateTimeKind.Utc, sut.Value.Kind); + } + + [Fact] + public void FromString_ShouldThrowArgumentException_SinceNoLocalTimezoneOrCoordinatedUniversalTimeWasSpecified() + { - [Fact] - public void FromString_ShouldThrowArgumentException_SinceNoLocalTimezoneOrCoordinatedUniversalTimeWasSpecified() + var message = Assert.Throws(() => { + CoordinatedUniversalTime.FromString("2020-01-01T00:00:00"); + }).Message; - var message = Assert.Throws(() => - { - CoordinatedUniversalTime.FromString("2020-01-01T00:00:00"); - }).Message; + Assert.Equal("Value is not in a valid state. (Expression 'value.Kind != DateTimeKind.Utc') (Parameter 'value')", message); + } - Assert.Equal("Value is not in a valid state. (Expression 'value.Kind != DateTimeKind.Utc') (Parameter 'value')", message); - } + [Fact] + public void Comparison_LeftShouldBeGreaterThanRight() + { + var left = new CoordinatedUniversalTime(CoordinatedUniversalTime.MaxValue); + var right = new CoordinatedUniversalTime(CoordinatedUniversalTime.MinValue); + Assert.True(left > right); + } - [Fact] - public void Comparison_LeftShouldBeGreaterThanRight() - { - var left = new CoordinatedUniversalTime(CoordinatedUniversalTime.MaxValue); - var right = new CoordinatedUniversalTime(CoordinatedUniversalTime.MinValue); - Assert.True(left > right); - } + [Fact] + public void Comparison_RightShouldBeGreaterThanLeft() + { + var left = new CoordinatedUniversalTime(CoordinatedUniversalTime.MinValue); + var right = new CoordinatedUniversalTime(CoordinatedUniversalTime.MaxValue); + Assert.True(left < right); + } - [Fact] - public void Comparison_RightShouldBeGreaterThanLeft() - { - var left = new CoordinatedUniversalTime(CoordinatedUniversalTime.MinValue); - var right = new CoordinatedUniversalTime(CoordinatedUniversalTime.MaxValue); - Assert.True(left < right); - } + [Fact] + public void Comparison_LeftShouldBeGreaterThanOrEqualToRight() + { + var left = new CoordinatedUniversalTime(DateTime.UtcNow.Date); + var right = new CoordinatedUniversalTime(DateTime.UtcNow.Date); + Assert.True(left >= right); + } - [Fact] - public void Comparison_LeftShouldBeGreaterThanOrEqualToRight() - { - var left = new CoordinatedUniversalTime(DateTime.UtcNow.Date); - var right = new CoordinatedUniversalTime(DateTime.UtcNow.Date); - Assert.True(left >= right); - } + [Fact] + public void Comparison_RightShouldBeGreaterThanOrEqualToLeft() + { + var left = new CoordinatedUniversalTime(DateTime.UtcNow.Date); + var right = new CoordinatedUniversalTime(DateTime.UtcNow.Date); + Assert.True(left <= right); + } - [Fact] - public void Comparison_RightShouldBeGreaterThanOrEqualToLeft() - { - var left = new CoordinatedUniversalTime(DateTime.UtcNow.Date); - var right = new CoordinatedUniversalTime(DateTime.UtcNow.Date); - Assert.True(left <= right); - } - - [Fact] - public void Comparison_ShouldBeEqual() - { - var left = new CoordinatedUniversalTime(DateTime.UtcNow.Date); - var right = new CoordinatedUniversalTime(DateTime.UtcNow.Date); - Assert.Equal(right, left); - } + [Fact] + public void Comparison_ShouldBeEqual() + { + var left = new CoordinatedUniversalTime(DateTime.UtcNow.Date); + var right = new CoordinatedUniversalTime(DateTime.UtcNow.Date); + Assert.Equal(right, left); + } - [Fact] - public void Comparison_ShouldBeDifferent() - { - var left = new CoordinatedUniversalTime(DateTime.UtcNow); - var right = new CoordinatedUniversalTime(DateTime.UtcNow.AddHours(1)); - Assert.NotEqual(right, left); - } + [Fact] + public void Comparison_ShouldBeDifferent() + { + var left = new CoordinatedUniversalTime(DateTime.UtcNow); + var right = new CoordinatedUniversalTime(DateTime.UtcNow.AddHours(1)); + Assert.NotEqual(right, left); + } - [Fact] - public void DateTime_ShouldConvertFromCoordinatedUniversalTime() - { - var sut = CoordinatedUniversalTime.FromString("2024-03-17T15:27:30.0000000+08:00"); + [Fact] + public void DateTime_ShouldConvertFromCoordinatedUniversalTime() + { + var sut = CoordinatedUniversalTime.FromString("2024-03-17T15:27:30.0000000+08:00"); - DateTime equivalent = sut.Value; + DateTime equivalent = sut.Value; - Assert.Equal(sut.Value, equivalent); - } + Assert.Equal(sut.Value, equivalent); + } - [Fact] - public void ImplicitConversion_ShouldConvertFromDateTimeOffset() - { - var expected = DateTimeOffset.Parse("2024-03-17T15:27:30.0000000+08:00"); + [Fact] + public void ImplicitConversion_ShouldConvertFromDateTimeOffset() + { + var expected = DateTimeOffset.Parse("2024-03-17T15:27:30.0000000+08:00"); - CoordinatedUniversalTime sut = expected; + CoordinatedUniversalTime sut = expected; - Assert.Equal(expected, sut); - } + Assert.Equal(expected, sut); + } - [Fact] - public void ImplicitConversion_ShouldConvertFromDateTime() - { - var expected = DateTime.UtcNow; + [Fact] + public void ImplicitConversion_ShouldConvertFromDateTime() + { + var expected = DateTime.UtcNow; - CoordinatedUniversalTime sut = expected; + CoordinatedUniversalTime sut = expected; - Assert.Equal(expected, sut); - } + Assert.Equal(expected, sut); + } - [Fact] - public void Marshalling_ShouldRepresentCorrectly() - { - var sut = CoordinatedUniversalTime.FromString("2024-03-17T15:27:30.0000000+08:00"); - var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); - var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); + [Fact] + public void Marshalling_ShouldRepresentCorrectly() + { + var sut = CoordinatedUniversalTime.FromString("2024-03-17T15:27:30.0000000+08:00"); + var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); + var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); - TestOutput.WriteLine(json); + TestOutput.WriteLine(json); - Assert.Equal("\"2024-03-17T07:27:30.0000000Z\"", json); - Assert.Equal(json, newtonsoftJson); - } + Assert.Equal("\"2024-03-17T07:27:30.0000000Z\"", json); + Assert.Equal(json, newtonsoftJson); } } diff --git a/test/Codebelt.SharedKernel.Tests/CorrelationIdTest.cs b/test/Codebelt.SharedKernel.Tests/CorrelationIdTest.cs index 4f8e5c1..dca0db9 100644 --- a/test/Codebelt.SharedKernel.Tests/CorrelationIdTest.cs +++ b/test/Codebelt.SharedKernel.Tests/CorrelationIdTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Cuemon; using Cuemon.Extensions.IO; @@ -7,121 +7,120 @@ using Savvyio.Extensions.Text.Json; using Xunit; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +public class CorrelationIdTest : Test { - public class CorrelationIdTest : Test + public static TheoryData WhitespaceCharacters => new(Alphanumeric.WhiteSpace.ToCharArray().Where(c => c != 6158).Select(c => c.ToString())); + + public CorrelationIdTest(ITestOutputHelper output) : base(output) { - public static TheoryData WhitespaceCharacters => new(Alphanumeric.WhiteSpace.ToCharArray().Where(c => c != 6158).Select(c => c.ToString())); - - public CorrelationIdTest(ITestOutputHelper output) : base(output) - { - } + } - [Fact] - public void Constructor_ShouldThrowArgumentNullException() + [Fact] + public void Constructor_ShouldThrowArgumentNullException() + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new CorrelationId(null); - }).Message; + new CorrelationId(null); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Fact] - public void Constructor_ShouldThrowArgumentException_WhenValueIsEmpty() + [Fact] + public void Constructor_ShouldThrowArgumentException_WhenValueIsEmpty() + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new CorrelationId(""); - }).Message; + new CorrelationId(""); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Theory] - [MemberData(nameof(WhitespaceCharacters))] - public void Constructor_ShouldThrowArgumentException_WhenValueIsWhitespace(string value) + [Theory] + [MemberData(nameof(WhitespaceCharacters))] + public void Constructor_ShouldThrowArgumentException_WhenValueIsWhitespace(string value) + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new CorrelationId(value); - }).Message; + new CorrelationId(value); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Theory] - [MemberData(nameof(WhitespaceCharacters))] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueContainsNonAsciiValues(string value) + [Theory] + [MemberData(nameof(WhitespaceCharacters))] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueContainsNonAsciiValues(string value) + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new CorrelationId(string.Concat(Generate.RandomString(12), value, Generate.RandomString(22))); - }).Message; + new CorrelationId(string.Concat(Generate.RandomString(12), value, Generate.RandomString(22))); + }).Message; - Assert.StartsWith("White-space characters are not allowed inside value. (Parameter 'value')", message); - } + Assert.StartsWith("White-space characters are not allowed inside value. (Parameter 'value')", message); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasLessThan32Characters() + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasLessThan32Characters() + { + var sut = Assert.Throws(() => { - var sut = Assert.Throws(() => - { - new CorrelationId(Generate.RandomString(31)); - }); + new CorrelationId(Generate.RandomString(31)); + }); - TestOutput.WriteLine(sut.Message); + TestOutput.WriteLine(sut.Message); - Assert.StartsWith("The minimum length of value was not meet. 32 characters are required. (Parameter 'value')", sut.Message); - Assert.Equal("31 < 32", sut.ActualValue); - } + Assert.StartsWith("The minimum length of value was not meet. 32 characters are required. (Parameter 'value')", sut.Message); + Assert.Equal("31 < 32", sut.ActualValue); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasExceeded128Characters() + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasExceeded128Characters() + { + var sut = Assert.Throws(() => { - var sut = Assert.Throws(() => - { - new CorrelationId(Generate.RandomString(129)); - }); + new CorrelationId(Generate.RandomString(129)); + }); - Assert.StartsWith("The maximum length of value was exceeded. 128 characters are allowed. (Parameter 'value')", sut.Message); - Assert.Equal("129 > 128", sut.ActualValue); - } + Assert.StartsWith("The maximum length of value was exceeded. 128 characters are allowed. (Parameter 'value')", sut.Message); + Assert.Equal("129 > 128", sut.ActualValue); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasConsistOnlyOfSingleRepeatedCharacter() - { - var sut = Assert.Throws(() => new CorrelationId(Guid.Empty)); + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasConsistOnlyOfSingleRepeatedCharacter() + { + var sut = Assert.Throws(() => new CorrelationId(Guid.Empty)); - TestOutput.WriteLine(sut.Message); + TestOutput.WriteLine(sut.Message); - Assert.StartsWith("Value suggest to high frequency of repeated characters. At least 1 distinct characters are required. (Parameter 'value')", sut.Message); - Assert.Equal("0", sut.ActualValue); - } + Assert.StartsWith("Value suggest to high frequency of repeated characters. At least 1 distinct characters are required. (Parameter 'value')", sut.Message); + Assert.Equal("0", sut.ActualValue); + } - [Fact] - public void ConversionOperators_ShouldRepresentCorrectly() - { - var value = Guid.Parse("1acb4e49-28a6-4206-b22b-2392ffd4e605"); - CorrelationId fromGuid = value; - CorrelationId fromString = fromGuid.Value; + [Fact] + public void ConversionOperators_ShouldRepresentCorrectly() + { + var value = Guid.Parse("1acb4e49-28a6-4206-b22b-2392ffd4e605"); + CorrelationId fromGuid = value; + CorrelationId fromString = fromGuid.Value; - Assert.Equal("1acb4e4928a64206b22b2392ffd4e605", fromGuid.Value); - Assert.Equal(fromGuid, fromString); - } + Assert.Equal("1acb4e4928a64206b22b2392ffd4e605", fromGuid.Value); + Assert.Equal(fromGuid, fromString); + } - [Fact] - public void Marshalling_ShouldRepresentCorrectly() - { - var sut = new CorrelationId("1acb4e4928a64206b22b2392ffd4e605"); - var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); - var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); + [Fact] + public void Marshalling_ShouldRepresentCorrectly() + { + var sut = new CorrelationId("1acb4e4928a64206b22b2392ffd4e605"); + var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); + var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); - TestOutput.WriteLine(json); + TestOutput.WriteLine(json); - Assert.Equal("\"1acb4e4928a64206b22b2392ffd4e605\"", json); - Assert.Equal(json, newtonsoftJson); - } + Assert.Equal("\"1acb4e4928a64206b22b2392ffd4e605\"", json); + Assert.Equal(json, newtonsoftJson); } } diff --git a/test/Codebelt.SharedKernel.Tests/Security/AccessKeyExtensionsTest.cs b/test/Codebelt.SharedKernel.Tests/Security/AccessKeyExtensionsTest.cs index 7d5d70c..20f11d4 100644 --- a/test/Codebelt.SharedKernel.Tests/Security/AccessKeyExtensionsTest.cs +++ b/test/Codebelt.SharedKernel.Tests/Security/AccessKeyExtensionsTest.cs @@ -1,46 +1,45 @@ -using System; +using System; using Codebelt.Extensions.Xunit; using Xunit; -namespace Codebelt.SharedKernel.Security +namespace Codebelt.SharedKernel.Security; + +public class AccessKeyExtensionsTest : Test { - public class AccessKeyExtensionsTest : Test + public AccessKeyExtensionsTest(ITestOutputHelper output) : base(output) { - public AccessKeyExtensionsTest(ITestOutputHelper output) : base(output) - { - } + } - [Fact] - public void IsValid_ShouldReturnTrue_WhenAccessKeyIsValid() - { - var sut = AccessKey.Issue(Guid.NewGuid().ToString("N")); + [Fact] + public void IsValid_ShouldReturnTrue_WhenAccessKeyIsValid() + { + var sut = AccessKey.Issue(Guid.NewGuid().ToString("N")); - Assert.True(sut.IsValid()); - } + Assert.True(sut.IsValid()); + } - [Fact] - public void IsValid_ShouldReturnFalse_WhenAccessKeyIsInvalidDueToEitherExpiryOrValidFrom() - { - var utc1 = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)); - var sut1 = AccessKey.Issue(Guid.NewGuid(), TimeToLive.MinValue); - var sut2 = new AccessKey(Guid.NewGuid(), o => o.ValidFrom = DateTime.UtcNow); + [Fact] + public void IsValid_ShouldReturnFalse_WhenAccessKeyIsInvalidDueToEitherExpiryOrValidFrom() + { + var utc1 = DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)); + var sut1 = AccessKey.Issue(Guid.NewGuid(), TimeToLive.MinValue); + var sut2 = new AccessKey(Guid.NewGuid(), o => o.ValidFrom = DateTime.UtcNow); - Assert.False(sut1.IsValid(utc1)); - Assert.False(sut2.IsValid(utc1)); - } + Assert.False(sut1.IsValid(utc1)); + Assert.False(sut2.IsValid(utc1)); + } - [Fact] - public void IsValid_ShouldReturnTrue_WhenAccessKeyHasLargeDesiredClockSkew() + [Fact] + public void IsValid_ShouldReturnTrue_WhenAccessKeyHasLargeDesiredClockSkew() + { + var utc = DateTime.UtcNow.Subtract(TimeSpan.FromHours(1)); + var sut = new AccessKey(Guid.NewGuid(), o => { - var utc = DateTime.UtcNow.Subtract(TimeSpan.FromHours(1)); - var sut = new AccessKey(Guid.NewGuid(), o => - { - o.ValidFrom = CoordinatedUniversalTime.MinValue; - o.Expires = new CoordinatedUniversalTime(utc); - o.DesiredTolerance = TimeSpan.FromHours(2); - }); - - Assert.True(sut.IsValid()); - } + o.ValidFrom = CoordinatedUniversalTime.MinValue; + o.Expires = new CoordinatedUniversalTime(utc); + o.DesiredTolerance = TimeSpan.FromHours(2); + }); + + Assert.True(sut.IsValid()); } } diff --git a/test/Codebelt.SharedKernel.Tests/Security/AccessKeyOptionsTest.cs b/test/Codebelt.SharedKernel.Tests/Security/AccessKeyOptionsTest.cs index 5c35a5e..a7c74cf 100644 --- a/test/Codebelt.SharedKernel.Tests/Security/AccessKeyOptionsTest.cs +++ b/test/Codebelt.SharedKernel.Tests/Security/AccessKeyOptionsTest.cs @@ -1,90 +1,89 @@ -using System; +using System; using Codebelt.Extensions.Xunit; using Xunit; -namespace Codebelt.SharedKernel.Security +namespace Codebelt.SharedKernel.Security; + +public class AccessKeyOptionsTest : Test { - public class AccessKeyOptionsTest : Test + public AccessKeyOptionsTest(ITestOutputHelper output) : base(output) { - public AccessKeyOptionsTest(ITestOutputHelper output) : base(output) - { - } + } - [Fact] - public void ValidateOptions_ShouldThrownInvalidOperationException_WhenValidFromIsNull() + [Fact] + public void ValidateOptions_ShouldThrownInvalidOperationException_WhenValidFromIsNull() + { + var sut = new AccessKeyOptions { - var sut = new AccessKeyOptions - { - ValidFrom = null - }; + ValidFrom = null + }; - var ex = Assert.Throws(() => sut.ValidateOptions()); + var ex = Assert.Throws(() => sut.ValidateOptions()); - Assert.Equal("Operation is not valid due to the current state of the object. (Expression 'ValidFrom == null')", ex.Message); - } + Assert.Equal("Operation is not valid due to the current state of the object. (Expression 'ValidFrom == null')", ex.Message); + } - [Fact] - public void ValidateOptions_ShouldThrownInvalidOperationException_WhenExpiresIsNull() + [Fact] + public void ValidateOptions_ShouldThrownInvalidOperationException_WhenExpiresIsNull() + { + var sut = new AccessKeyOptions { - var sut = new AccessKeyOptions - { - Expires = null - }; + Expires = null + }; - var ex = Assert.Throws(() => sut.ValidateOptions()); + var ex = Assert.Throws(() => sut.ValidateOptions()); - Assert.Equal("Operation is not valid due to the current state of the object. (Expression 'Expires == null')", ex.Message); - } + Assert.Equal("Operation is not valid due to the current state of the object. (Expression 'Expires == null')", ex.Message); + } - [Fact] - public void ValidateOptions_ShouldThrownInvalidOperationException_WhenToleranceIsNull() + [Fact] + public void ValidateOptions_ShouldThrownInvalidOperationException_WhenToleranceIsNull() + { + var sut = new AccessKeyOptions { - var sut = new AccessKeyOptions - { - DesiredTolerance = null - }; + DesiredTolerance = null + }; - var ex = Assert.Throws(() => sut.ValidateOptions()); + var ex = Assert.Throws(() => sut.ValidateOptions()); - Assert.Equal("Operation is not valid due to the current state of the object. (Expression 'DesiredTolerance == null')", ex.Message); - } + Assert.Equal("Operation is not valid due to the current state of the object. (Expression 'DesiredTolerance == null')", ex.Message); + } - [Fact] - public void ValidateOptions_ShouldThrownInvalidOperationException_WhenValidFromIsGreaterThanExpires() + [Fact] + public void ValidateOptions_ShouldThrownInvalidOperationException_WhenValidFromIsGreaterThanExpires() + { + var sut = new AccessKeyOptions { - var sut = new AccessKeyOptions - { - Expires = CoordinatedUniversalTime.MinValue, - ValidFrom = CoordinatedUniversalTime.MaxValue - }; + Expires = CoordinatedUniversalTime.MinValue, + ValidFrom = CoordinatedUniversalTime.MaxValue + }; - var ex = Assert.Throws(() => sut.ValidateOptions()); + var ex = Assert.Throws(() => sut.ValidateOptions()); - Assert.Equal("Operation is not valid due to the current state of the object. (Expression 'ValidFrom > Expires')", ex.Message); - } + Assert.Equal("Operation is not valid due to the current state of the object. (Expression 'ValidFrom > Expires')", ex.Message); + } - [Fact] - public void ValidFrom_ShouldDefaultTo_CoordinatedUniversalTimeMinValue() - { - var sut = new AccessKeyOptions(); + [Fact] + public void ValidFrom_ShouldDefaultTo_CoordinatedUniversalTimeMinValue() + { + var sut = new AccessKeyOptions(); - Assert.Equal(CoordinatedUniversalTime.MinValue, sut.ValidFrom); - } + Assert.Equal(CoordinatedUniversalTime.MinValue, sut.ValidFrom); + } - [Fact] - public void Expires_ShouldDefaultTo_CoordinatedUniversalTimeMaxValue() - { - var sut = new AccessKeyOptions(); + [Fact] + public void Expires_ShouldDefaultTo_CoordinatedUniversalTimeMaxValue() + { + var sut = new AccessKeyOptions(); - Assert.Equal(CoordinatedUniversalTime.MaxValue, sut.Expires); - } + Assert.Equal(CoordinatedUniversalTime.MaxValue, sut.Expires); + } - [Fact] - public void DesiredTolerance_ShouldDefaultTo_ClockSkewFromSecondsThirty() - { - var sut = new AccessKeyOptions(); + [Fact] + public void DesiredTolerance_ShouldDefaultTo_ClockSkewFromSecondsThirty() + { + var sut = new AccessKeyOptions(); - Assert.Equal(ClockSkew.FromSeconds(30), sut.DesiredTolerance); - } + Assert.Equal(ClockSkew.FromSeconds(30), sut.DesiredTolerance); } } diff --git a/test/Codebelt.SharedKernel.Tests/Security/AccessKeyTest.cs b/test/Codebelt.SharedKernel.Tests/Security/AccessKeyTest.cs index ddb24da..0bf11fb 100644 --- a/test/Codebelt.SharedKernel.Tests/Security/AccessKeyTest.cs +++ b/test/Codebelt.SharedKernel.Tests/Security/AccessKeyTest.cs @@ -1,70 +1,69 @@ -using System; +using System; using Cuemon.Extensions.IO; using Codebelt.Extensions.Xunit; using Savvyio.Extensions.Newtonsoft.Json; using Savvyio.Extensions.Text.Json; using Xunit; -namespace Codebelt.SharedKernel.Security +namespace Codebelt.SharedKernel.Security; + +public class AccessKeyTest : Test { - public class AccessKeyTest : Test + public AccessKeyTest(ITestOutputHelper output) : base(output) { - public AccessKeyTest(ITestOutputHelper output) : base(output) - { - } + } - [Fact] - public void DesiredTolereance_ShouldBe_ThirtySeconds() - { - var sut = new AccessKey(Guid.NewGuid()); - Assert.Equal(TimeSpan.FromSeconds(30), sut.DesiredTolerance); - } + [Fact] + public void DesiredTolereance_ShouldBe_ThirtySeconds() + { + var sut = new AccessKey(Guid.NewGuid()); + Assert.Equal(TimeSpan.FromSeconds(30), sut.DesiredTolerance); + } - [Fact] - public void Expires_ShouldBe_MaxValue() - { - var sut = new AccessKey(Guid.NewGuid()); - Assert.Equal(CoordinatedUniversalTime.MaxValue, sut.Expires); - } + [Fact] + public void Expires_ShouldBe_MaxValue() + { + var sut = new AccessKey(Guid.NewGuid()); + Assert.Equal(CoordinatedUniversalTime.MaxValue, sut.Expires); + } - [Fact] - public void Secret_ShouldBe_UuidInNumberFormat() - { - var uuid = Guid.NewGuid(); - var sut = new AccessKey(uuid); - Assert.Equal(uuid.ToString("N"), sut.Secret); - } + [Fact] + public void Secret_ShouldBe_UuidInNumberFormat() + { + var uuid = Guid.NewGuid(); + var sut = new AccessKey(uuid); + Assert.Equal(uuid.ToString("N"), sut.Secret); + } - [Fact] - public void Secret_ShouldBe_AsSpecified() - { - var secret = "mysecretthatisatleastthirtytwocharacterslong"; - var sut = new AccessKey(secret); - Assert.Equal(secret, sut.Secret); - } + [Fact] + public void Secret_ShouldBe_AsSpecified() + { + var secret = "mysecretthatisatleastthirtytwocharacterslong"; + var sut = new AccessKey(secret); + Assert.Equal(secret, sut.Secret); + } - [Fact] - public void Expires_ShouldBeOneYearFromUtcNow() - { - var utc = DateTime.UtcNow; - var sut = AccessKey.Issue("bae9d2e09c9b5f65f9a7a3c5a55748b180ee65c1", TimeToLive.FromYears(1)); - var lifespan = sut.Expires - sut.ValidFrom; + [Fact] + public void Expires_ShouldBeOneYearFromUtcNow() + { + var utc = DateTime.UtcNow; + var sut = AccessKey.Issue("bae9d2e09c9b5f65f9a7a3c5a55748b180ee65c1", TimeToLive.FromYears(1)); + var lifespan = sut.Expires - sut.ValidFrom; - Assert.Equal("bae9d2e09c9b5f65f9a7a3c5a55748b180ee65c1", sut.Secret); - Assert.Equal(lifespan.Ticks, (utc.AddYears(1) - utc).Ticks); - } + Assert.Equal("bae9d2e09c9b5f65f9a7a3c5a55748b180ee65c1", sut.Secret); + Assert.Equal(lifespan.Ticks, (utc.AddYears(1) - utc).Ticks); + } - [Fact] - public void Marshalling_ShouldRepresentCorrectly() - { - var sut = new AccessKey("1acb4e4928a64206b22b2392ffd4e605"); - var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); - var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); + [Fact] + public void Marshalling_ShouldRepresentCorrectly() + { + var sut = new AccessKey("1acb4e4928a64206b22b2392ffd4e605"); + var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); + var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); - TestOutput.WriteLine(json); + TestOutput.WriteLine(json); - Assert.Equal("""{"secret":"1acb4e4928a64206b22b2392ffd4e605","validFrom":"0001-01-01T00:00:00.0000000Z","expires":"9999-12-31T22:59:59.9999999Z","desiredTolerance":"00:00:30"}""", json); - Assert.Equal(json, newtonsoftJson); - } + Assert.Equal("""{"secret":"1acb4e4928a64206b22b2392ffd4e605","validFrom":"0001-01-01T00:00:00.0000000Z","expires":"9999-12-31T22:59:59.9999999Z","desiredTolerance":"00:00:30"}""", json); + Assert.Equal(json, newtonsoftJson); } } diff --git a/test/Codebelt.SharedKernel.Tests/Security/SecretTest.cs b/test/Codebelt.SharedKernel.Tests/Security/SecretTest.cs index 7909568..fee9eaa 100644 --- a/test/Codebelt.SharedKernel.Tests/Security/SecretTest.cs +++ b/test/Codebelt.SharedKernel.Tests/Security/SecretTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Text; using Cuemon; @@ -8,117 +8,116 @@ using Savvyio.Extensions.Text.Json; using Xunit; -namespace Codebelt.SharedKernel.Security +namespace Codebelt.SharedKernel.Security; + +public class TokenTest : Test { - public class TokenTest : Test + public static TheoryData WhitespaceCharacters => new(Alphanumeric.WhiteSpace.ToCharArray().Where(c => c != 6158).Select(c => c.ToString())); + + public TokenTest(ITestOutputHelper output) : base(output) { - public static TheoryData WhitespaceCharacters => new(Alphanumeric.WhiteSpace.ToCharArray().Where(c => c != 6158).Select(c => c.ToString())); - - public TokenTest(ITestOutputHelper output) : base(output) - { - } + } - [Fact] - public void Constructor_ShouldThrowArgumentNullException() + [Fact] + public void Constructor_ShouldThrowArgumentNullException() + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new Secret(null); - }).Message; + new Secret(null); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Fact] - public void Constructor_ShouldThrowArgumentException_WhenValueIsEmpty() + [Fact] + public void Constructor_ShouldThrowArgumentException_WhenValueIsEmpty() + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new Secret(""); - }).Message; + new Secret(""); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Theory] - [MemberData(nameof(WhitespaceCharacters))] - public void Constructor_ShouldThrowArgumentException_WhenValueIsWhitespace(string value) + [Theory] + [MemberData(nameof(WhitespaceCharacters))] + public void Constructor_ShouldThrowArgumentException_WhenValueIsWhitespace(string value) + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new Secret(value); - }).Message; + new Secret(value); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Theory] - [MemberData(nameof(WhitespaceCharacters))] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueContainsNonAsciiValues(string value) + [Theory] + [MemberData(nameof(WhitespaceCharacters))] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueContainsNonAsciiValues(string value) + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new Secret(string.Concat(Generate.RandomString(12), value, Generate.RandomString(22))); - }).Message; + new Secret(string.Concat(Generate.RandomString(12), value, Generate.RandomString(22))); + }).Message; - Assert.StartsWith("White-space characters are not allowed inside value. (Parameter 'value')", message); - } + Assert.StartsWith("White-space characters are not allowed inside value. (Parameter 'value')", message); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasLessThan32Characters() + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasLessThan32Characters() + { + var sut = Assert.Throws(() => { - var sut = Assert.Throws(() => - { - new Secret(Generate.RandomString(31)); - }); + new Secret(Generate.RandomString(31)); + }); - Assert.StartsWith("The minimum length of value was not meet. 32 characters are required. (Parameter 'value')", sut.Message); - Assert.Equal("31 < 32", sut.ActualValue); - } + Assert.StartsWith("The minimum length of value was not meet. 32 characters are required. (Parameter 'value')", sut.Message); + Assert.Equal("31 < 32", sut.ActualValue); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasConsistOnlyOfSingleRepeatedCharacter() - { - var sut = Assert.Throws(() => new Secret(Guid.Empty)); + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasConsistOnlyOfSingleRepeatedCharacter() + { + var sut = Assert.Throws(() => new Secret(Guid.Empty)); - TestOutput.WriteLine(sut.Message); + TestOutput.WriteLine(sut.Message); - Assert.StartsWith("Value suggest to high frequency of repeated characters. At least 1 distinct characters are required. (Parameter 'value')", sut.Message); - Assert.Equal("0", sut.ActualValue); - } + Assert.StartsWith("Value suggest to high frequency of repeated characters. At least 1 distinct characters are required. (Parameter 'value')", sut.Message); + Assert.Equal("0", sut.ActualValue); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasExceeded128Characters() + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasExceeded128Characters() + { + var sut = Assert.Throws(() => { - var sut = Assert.Throws(() => - { - new Secret(Generate.RandomString(129)); - }); + new Secret(Generate.RandomString(129)); + }); - Assert.StartsWith("The maximum length of value was exceeded. 128 characters are allowed. (Parameter 'value')", sut.Message); - Assert.Equal("129 > 128", sut.ActualValue); - } + Assert.StartsWith("The maximum length of value was exceeded. 128 characters are allowed. (Parameter 'value')", sut.Message); + Assert.Equal("129 > 128", sut.ActualValue); + } - [Fact] - public void StringAndByteRepresentation_ShouldRepresentCorrectly() - { - var sut = new Secret("1acb4e4928a64206b22b2392ffd4e605"); + [Fact] + public void StringAndByteRepresentation_ShouldRepresentCorrectly() + { + var sut = new Secret("1acb4e4928a64206b22b2392ffd4e605"); - Assert.Equal("1acb4e4928a64206b22b2392ffd4e605", sut.ToString()); - Assert.Equal(Encoding.UTF8.GetBytes(sut.Value), sut.ToByteArray()); - } + Assert.Equal("1acb4e4928a64206b22b2392ffd4e605", sut.ToString()); + Assert.Equal(Encoding.UTF8.GetBytes(sut.Value), sut.ToByteArray()); + } - [Fact] - public void Marshalling_ShouldRepresentCorrectly() - { - var sut = new Secret("1acb4e4928a64206b22b2392ffd4e605"); - var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); - var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); + [Fact] + public void Marshalling_ShouldRepresentCorrectly() + { + var sut = new Secret("1acb4e4928a64206b22b2392ffd4e605"); + var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); + var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); - TestOutput.WriteLine(json); + TestOutput.WriteLine(json); - Assert.Equal("\"1acb4e4928a64206b22b2392ffd4e605\"", json); - Assert.Equal(json, newtonsoftJson); - } + Assert.Equal("\"1acb4e4928a64206b22b2392ffd4e605\"", json); + Assert.Equal(json, newtonsoftJson); } } diff --git a/test/Codebelt.SharedKernel.Tests/TimeToLiveTest.cs b/test/Codebelt.SharedKernel.Tests/TimeToLiveTest.cs index a747885..0675ab8 100644 --- a/test/Codebelt.SharedKernel.Tests/TimeToLiveTest.cs +++ b/test/Codebelt.SharedKernel.Tests/TimeToLiveTest.cs @@ -1,127 +1,126 @@ -using System; +using System; using Cuemon.Extensions.IO; using Codebelt.Extensions.Xunit; using Savvyio.Extensions.Newtonsoft.Json; using Savvyio.Extensions.Text.Json; using Xunit; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +public class TimeToLiveTest : Test { - public class TimeToLiveTest : Test + public TimeToLiveTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Comparison_LeftShouldBeGreaterThanRight() + { + var left = new TimeToLive(TimeSpan.FromDays(2)); + var right = new TimeToLive(TimeSpan.FromHours(1)); + Assert.True(left > right); + } + + [Fact] + public void Comparison_RightShouldBeGreaterThanLeft() + { + var left = new TimeToLive(TimeSpan.FromHours(1)); + var right = new TimeToLive(TimeSpan.FromDays(2)); + Assert.True(left < right); + } + + [Fact] + public void Comparison_LeftShouldBeGreaterThanOrEqualToRight() + { + var left = new TimeToLive(TimeSpan.FromDays(2)); + var right = new TimeToLive(TimeSpan.FromDays(2)); + Assert.True(left >= right); + } + + [Fact] + public void Comparison_RightShouldBeGreaterThanOrEqualToLeft() + { + var left = new TimeToLive(TimeSpan.FromHours(1)); + var right = new TimeToLive(TimeSpan.FromHours(2)); + Assert.True(left <= right); + } + + [Fact] + public void Comparison_ShouldBeEqual() + { + var left = new TimeToLive(TimeSpan.FromHours(1)); + var right = new TimeToLive(TimeSpan.FromHours(1)); + Assert.Equal(right, left); + } + + [Fact] + public void Comparison_ShouldBeDifferent() + { + var left = new TimeToLive(TimeSpan.FromTicks(1)); + var right = new TimeToLive(TimeSpan.FromHours(1)); + Assert.NotEqual(right, left); + } + + [Fact] + public void FromYears_ShouldHaveOneYearLifespan() + { + var sut = TimeToLive.FromYears(1); + + Assert.InRange(sut.Value.TotalDays, 365, 366); + } + + [Fact] + public void FromMonths_ShouldHaveSixMonthsLifespan() + { + var sut = TimeToLive.FromMonths(6); + + Assert.InRange(sut.Value.TotalDays, 181, 184); + } + + [Fact] + public void FromDays_ShouldHaveThirtyDaysLifespan() + { + var sut = TimeToLive.FromDays(30); + + Assert.Equal(30, sut.Value.TotalDays); + } + + [Fact] + public void FromHours_ShouldHaveTwentyFourHoursLifespan() + { + var sut = TimeToLive.FromHours(24); + + Assert.Equal(24, sut.Value.TotalHours); + } + + [Fact] + public void FromMinutes_ShouldHaveFifteenMinutesLifespan() + { + var sut = TimeToLive.FromMinutes(15); + + Assert.Equal(15, sut.Value.TotalMinutes); + } + + [Fact] + public void ConversionAndStringRepresentation_ShouldRepresentCorrectly() { - public TimeToLiveTest(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public void Comparison_LeftShouldBeGreaterThanRight() - { - var left = new TimeToLive(TimeSpan.FromDays(2)); - var right = new TimeToLive(TimeSpan.FromHours(1)); - Assert.True(left > right); - } - - [Fact] - public void Comparison_RightShouldBeGreaterThanLeft() - { - var left = new TimeToLive(TimeSpan.FromHours(1)); - var right = new TimeToLive(TimeSpan.FromDays(2)); - Assert.True(left < right); - } - - [Fact] - public void Comparison_LeftShouldBeGreaterThanOrEqualToRight() - { - var left = new TimeToLive(TimeSpan.FromDays(2)); - var right = new TimeToLive(TimeSpan.FromDays(2)); - Assert.True(left >= right); - } - - [Fact] - public void Comparison_RightShouldBeGreaterThanOrEqualToLeft() - { - var left = new TimeToLive(TimeSpan.FromHours(1)); - var right = new TimeToLive(TimeSpan.FromHours(2)); - Assert.True(left <= right); - } - - [Fact] - public void Comparison_ShouldBeEqual() - { - var left = new TimeToLive(TimeSpan.FromHours(1)); - var right = new TimeToLive(TimeSpan.FromHours(1)); - Assert.Equal(right, left); - } - - [Fact] - public void Comparison_ShouldBeDifferent() - { - var left = new TimeToLive(TimeSpan.FromTicks(1)); - var right = new TimeToLive(TimeSpan.FromHours(1)); - Assert.NotEqual(right, left); - } - - [Fact] - public void FromYears_ShouldHaveOneYearLifespan() - { - var sut = TimeToLive.FromYears(1); - - Assert.InRange(sut.Value.TotalDays, 365, 366); - } - - [Fact] - public void FromMonths_ShouldHaveSixMonthsLifespan() - { - var sut = TimeToLive.FromMonths(6); - - Assert.InRange(sut.Value.TotalDays, 181, 184); - } - - [Fact] - public void FromDays_ShouldHaveThirtyDaysLifespan() - { - var sut = TimeToLive.FromDays(30); - - Assert.Equal(30, sut.Value.TotalDays); - } - - [Fact] - public void FromHours_ShouldHaveTwentyFourHoursLifespan() - { - var sut = TimeToLive.FromHours(24); - - Assert.Equal(24, sut.Value.TotalHours); - } - - [Fact] - public void FromMinutes_ShouldHaveFifteenMinutesLifespan() - { - var sut = TimeToLive.FromMinutes(15); - - Assert.Equal(15, sut.Value.TotalMinutes); - } - - [Fact] - public void ConversionAndStringRepresentation_ShouldRepresentCorrectly() - { - TimeToLive sut = TimeSpan.FromMinutes(15); - TimeSpan actual = sut; - - Assert.Equal(TimeSpan.FromMinutes(15), actual); - Assert.Equal("00:15:00", sut.ToString()); - } - - [Fact] - public void Marshalling_ShouldRepresentCorrectly() - { - var sut = TimeToLive.FromYears(1); - var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); - var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); - - TestOutput.WriteLine(json); - - Assert.Equal("\"365.00:00:00\"", json); - Assert.Equal(json, newtonsoftJson); - } + TimeToLive sut = TimeSpan.FromMinutes(15); + TimeSpan actual = sut; + + Assert.Equal(TimeSpan.FromMinutes(15), actual); + Assert.Equal("00:15:00", sut.ToString()); + } + + [Fact] + public void Marshalling_ShouldRepresentCorrectly() + { + var sut = TimeToLive.FromYears(1); + var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); + var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); + + TestOutput.WriteLine(json); + + Assert.Equal("\"365.00:00:00\"", json); + Assert.Equal(json, newtonsoftJson); } } diff --git a/test/Codebelt.SharedKernel.Tests/TokenTest.cs b/test/Codebelt.SharedKernel.Tests/TokenTest.cs index dc18989..ce0e5dc 100644 --- a/test/Codebelt.SharedKernel.Tests/TokenTest.cs +++ b/test/Codebelt.SharedKernel.Tests/TokenTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Cuemon; using Cuemon.Extensions.IO; @@ -7,136 +7,135 @@ using Savvyio.Extensions.Text.Json; using Xunit; -namespace Codebelt.SharedKernel +namespace Codebelt.SharedKernel; + +public class TokenTest : Test { - public class TokenTest : Test + public static TheoryData WhitespaceCharacters => new(Alphanumeric.WhiteSpace.ToCharArray().Where(c => c != 6158).Select(c => c.ToString())); + + public TokenTest(ITestOutputHelper output) : base(output) { - public static TheoryData WhitespaceCharacters => new(Alphanumeric.WhiteSpace.ToCharArray().Where(c => c != 6158).Select(c => c.ToString())); - - public TokenTest(ITestOutputHelper output) : base(output) - { - } + } - [Fact] - public void Constructor_ShouldThrowArgumentNullException() + [Fact] + public void Constructor_ShouldThrowArgumentNullException() + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new Token(null); - }).Message; + new Token(null); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Fact] - public void Constructor_ShouldThrowArgumentException_WhenValueIsEmpty() + [Fact] + public void Constructor_ShouldThrowArgumentException_WhenValueIsEmpty() + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new Token(""); - }).Message; + new Token(""); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Theory] - [MemberData(nameof(WhitespaceCharacters))] - public void Constructor_ShouldThrowArgumentException_WhenValueIsWhitespace(string value) + [Theory] + [MemberData(nameof(WhitespaceCharacters))] + public void Constructor_ShouldThrowArgumentException_WhenValueIsWhitespace(string value) + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new Token(value); - }).Message; + new Token(value); + }).Message; - Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); - } + Assert.Equal("Value cannot be null, empty or consist only of white-space characters. (Parameter 'value')", message); + } - [Theory] - [MemberData(nameof(WhitespaceCharacters))] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueContainsNonAsciiValues(string value) + [Theory] + [MemberData(nameof(WhitespaceCharacters))] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueContainsNonAsciiValues(string value) + { + var message = Assert.Throws(() => { - var message = Assert.Throws(() => - { - new Token(string.Concat(Generate.RandomString(12), value, Generate.RandomString(22))); - }).Message; + new Token(string.Concat(Generate.RandomString(12), value, Generate.RandomString(22))); + }).Message; - Assert.StartsWith("White-space characters are not allowed inside value. (Parameter 'value')", message); - } + Assert.StartsWith("White-space characters are not allowed inside value. (Parameter 'value')", message); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasLessThan32Characters() - { - var sut = Assert.Throws(() => new Token(Generate.RandomString(31))); + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasLessThan32Characters() + { + var sut = Assert.Throws(() => new Token(Generate.RandomString(31))); - TestOutput.WriteLine(sut.Message); + TestOutput.WriteLine(sut.Message); - Assert.StartsWith("The minimum length of value was not meet. 32 characters are required. (Parameter 'value')", sut.Message); - Assert.Equal("31 < 32", sut.ActualValue); - } + Assert.StartsWith("The minimum length of value was not meet. 32 characters are required. (Parameter 'value')", sut.Message); + Assert.Equal("31 < 32", sut.ActualValue); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasExceeded128Characters() - { - var sut = Assert.Throws(() => new Token(Generate.RandomString(129))); + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasExceeded128Characters() + { + var sut = Assert.Throws(() => new Token(Generate.RandomString(129))); - TestOutput.WriteLine(sut.Message); + TestOutput.WriteLine(sut.Message); - Assert.StartsWith("The maximum length of value was exceeded. 128 characters are allowed. (Parameter 'value')", sut.Message); - Assert.Equal("129 > 128", sut.ActualValue); - } + Assert.StartsWith("The maximum length of value was exceeded. 128 characters are allowed. (Parameter 'value')", sut.Message); + Assert.Equal("129 > 128", sut.ActualValue); + } - [Fact] - public void Constructor_ShouldNotThrowArgumentOutOfRangeException_WhenValueHasLessThan32Characters() - { - var sut = new Token(Generate.RandomString(20), o => o.MinimumLength = 0); + [Fact] + public void Constructor_ShouldNotThrowArgumentOutOfRangeException_WhenValueHasLessThan32Characters() + { + var sut = new Token(Generate.RandomString(20), o => o.MinimumLength = 0); - TestOutput.WriteLine(sut); + TestOutput.WriteLine(sut); - Assert.Equal(20, sut.Value.Length); - } + Assert.Equal(20, sut.Value.Length); + } - [Fact] - public void Constructor_ShouldNotThrowArgumentOutOfRangeException_WhenValueHasExceededMaxCharacters() - { - var sut = new Token(Generate.RandomString(255), o => o.MaximumLength = 0); + [Fact] + public void Constructor_ShouldNotThrowArgumentOutOfRangeException_WhenValueHasExceededMaxCharacters() + { + var sut = new Token(Generate.RandomString(255), o => o.MaximumLength = 0); - TestOutput.WriteLine(sut); + TestOutput.WriteLine(sut); - Assert.Equal(255, sut.Value.Length); - } + Assert.Equal(255, sut.Value.Length); + } - [Fact] - public void Constructor_ShouldNotThrowArgumentOutOfRangeException_WhenMaximumCharacterFrequencyIsDisabled() - { - var sut = new Token(new string('a', 40), o => o.MaximumCharacterFrequency = 0); + [Fact] + public void Constructor_ShouldNotThrowArgumentOutOfRangeException_WhenMaximumCharacterFrequencyIsDisabled() + { + var sut = new Token(new string('a', 40), o => o.MaximumCharacterFrequency = 0); - TestOutput.WriteLine(sut); + TestOutput.WriteLine(sut); - Assert.Equal(40, sut.Value.Length); - } + Assert.Equal(40, sut.Value.Length); + } - [Fact] - public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasHighCharacterFrequency() - { - var sut = Assert.Throws(() => new Token(Generate.RandomString(32, "a", "b", "c", "d"))); + [Fact] + public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasHighCharacterFrequency() + { + var sut = Assert.Throws(() => new Token(Generate.RandomString(32, "a", "b", "c", "d"))); - TestOutput.WriteLine(sut.Message); + TestOutput.WriteLine(sut.Message); - Assert.StartsWith("Value suggest to high frequency of repeated characters. At least 4 distinct characters are required. (Parameter 'value')", sut.Message); - Assert.Equal("a,b,c,d", sut.ActualValue); - } + Assert.StartsWith("Value suggest to high frequency of repeated characters. At least 4 distinct characters are required. (Parameter 'value')", sut.Message); + Assert.Equal("a,b,c,d", sut.ActualValue); + } - [Fact] - public void Marshalling_ShouldRepresentCorrectly() - { - var sut = new Token("1acb4e4928a64206b22b2392ffd4e605"); - var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); - var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); + [Fact] + public void Marshalling_ShouldRepresentCorrectly() + { + var sut = new Token("1acb4e4928a64206b22b2392ffd4e605"); + var json = JsonMarshaller.Default.Serialize(sut).ToEncodedString(); + var newtonsoftJson = NewtonsoftJsonMarshaller.Default.Serialize(sut).ToEncodedString(); - TestOutput.WriteLine(json); + TestOutput.WriteLine(json); - Assert.Equal("\"1acb4e4928a64206b22b2392ffd4e605\"", json); - Assert.Equal(json, newtonsoftJson); - } + Assert.Equal("\"1acb4e4928a64206b22b2392ffd4e605\"", json); + Assert.Equal(json, newtonsoftJson); } } diff --git a/testenvironments.json b/testenvironments.json index cf8b99c..0651f28 100644 --- a/testenvironments.json +++ b/testenvironments.json @@ -1,15 +1,20 @@ { - "version": "1", - "environments": [ - { - "name": "WSL-Ubuntu", - "type": "wsl", - "wslDistribution": "Ubuntu-24.04" - }, - { - "name": "Docker-Ubuntu", - "type": "docker", - "dockerImage": "codebeltnet/ubuntu-testrunner:8-9-10-11" - } - ] + "version": "1", + "environments": [ + { + "name": "WSL-Ubuntu", + "type": "wsl", + "wslDistribution": "Ubuntu-26.04" + }, + { + "name": "Docker-Ubuntu (net10.0)", + "type": "docker", + "dockerImage": "codebeltnet/ubuntu-testrunner:10" + }, + { + "name": "Docker-Ubuntu (net11.0)", + "type": "docker", + "dockerImage": "codebeltnet/ubuntu-testrunner:11" + } + ] }