Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .docfx/Dockerfile.docfx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG NGINX_VERSION=1.30.0-alpine
ARG NGINX_VERSION=1.31.0-alpine

FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base
RUN rm -rf /usr/share/nginx/html/*
Expand Down
22 changes: 22 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,28 @@ Internal classes and methods must be validated by exercising the public API that
- Public entry points provide sufficient coverage of internal code paths.
- The internal implementation exists solely as a helper or utility for public-facing functionality.

## 10. ExcludeFromCodeCoverage Prohibition

**Do not use `ExcludeFromCodeCoverage` attribute on any code.** This includes:

- Test classes or test methods
- Production code
- Configuration code
- Any other code path

### Rationale

- Excluding code from coverage hides gaps and creates false confidence in test completeness.
- If a code path cannot or should not be tested, refactor the code to eliminate that path rather than hiding it from metrics.
- Every executable line should be covered by tests or be genuinely unreachable (dead code to be removed).

### Alternative Approaches

- **Untestable code paths**: Refactor to separate concerns and eliminate the untestable path.
- **External dependencies**: Use test doubles (fakes, stubs, spies) instead of excluding from coverage.
- **Configuration-only code**: Move to configuration files or extract into testable methods.
- **Generated or third-party code**: These should not be in the primary codebase; use NuGet packages or dedicated vendor folders if necessary.

---
description: 'Writing Performance Tests'
applyTo: "tuning/**, **/*Benchmark*.cs"
Expand Down
87 changes: 80 additions & 7 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ on:
options:
- Debug
- Release
run_mac_tests:
type: boolean
description: Run the macOS test matrix despite the additional cost and runtime.
default: false

permissions:
contents: read
Expand All @@ -21,6 +25,7 @@ jobs:
name: initialize
runs-on: ubuntu-24.04
outputs:
run-mac-tests: ${{ steps.vars.outputs.run-mac-tests }}
run-privileged-jobs: ${{ steps.vars.outputs.run-privileged-jobs }}
strong-name-key-filename: ${{ steps.vars.outputs.strong-name-key-filename }}
build-switches: ${{ steps.vars.outputs.build-switches }}
Expand All @@ -29,6 +34,12 @@ jobs:
name: calculate workflow variables
shell: bash
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.run_mac_tests }}" == "true" ]]; then
echo "run-mac-tests=true" >> "$GITHUB_OUTPUT"
else
echo "run-mac-tests=false" >> "$GITHUB_OUTPUT"
fi

if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then
echo "run-privileged-jobs=false" >> "$GITHUB_OUTPUT"
echo "strong-name-key-filename=" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -101,10 +112,72 @@ jobs:
build: true # required for xunitv3
download-pattern: build-${{ matrix.configuration }}-${{ matrix.arch }}

test_mac:
if: ${{ needs.init.outputs.run-mac-tests == 'true' }}
name: call-test-mac
needs: [init, build]
strategy:
fail-fast: false
matrix:
arch: [X64, ARM64]
configuration: [Debug, Release]
uses: codebeltnet/jobs-dotnet-test/.github/workflows/default.yml@v3
with:
runs-on: ${{ matrix.arch == 'ARM64' && 'macos-26' || 'macos-26-intel' }}
configuration: ${{ matrix.configuration }}
build-switches: -p:SkipSignAssembly=true
restore: true
build: true # required for xunitv3
download-pattern: build-${{ matrix.configuration }}-${{ matrix.arch }}

test_qualitygate:
if: ${{ always() }}
name: test-qualitygate
needs: [init, test_linux, test_windows, test_mac]
runs-on: ubuntu-24.04
steps:
- name: Evaluate test results
shell: bash
env:
RUN_MAC_TESTS: ${{ needs.init.outputs.run-mac-tests }}
TEST_LINUX_RESULT: ${{ needs.test_linux.result }}
TEST_WINDOWS_RESULT: ${{ needs.test_windows.result }}
TEST_MAC_RESULT: ${{ needs.test_mac.result }}
run: |
require_success() {
local job_name="$1"
local job_result="$2"

if [[ "$job_result" != "success" ]]; then
echo "::error::$job_name finished with '$job_result'."
exit 1
fi
}

require_success_or_skip() {
local job_name="$1"
local job_enabled="$2"
local job_result="$3"

if [[ "$job_enabled" == "true" ]]; then
require_success "$job_name" "$job_result"
return
fi

if [[ "$job_result" != "success" && "$job_result" != "skipped" ]]; then
echo "::error::$job_name finished with '$job_result' while disabled."
exit 1
fi
}

require_success "test_linux" "$TEST_LINUX_RESULT"
require_success "test_windows" "$TEST_WINDOWS_RESULT"
require_success_or_skip "test_mac" "$RUN_MAC_TESTS" "$TEST_MAC_RESULT"

sonarcloud:
if: ${{ needs.init.outputs.run-privileged-jobs == 'true' }}
if: ${{always() && needs.init.outputs.run-privileged-jobs == 'true' && needs.build.result == 'success' && needs.test_qualitygate.result == 'success'}}
name: call-sonarcloud
needs: [init, build, test_linux, test_windows]
needs: [init, build, test_qualitygate]
uses: codebeltnet/jobs-sonarcloud/.github/workflows/default.yml@v3
with:
organization: geekle
Expand All @@ -113,26 +186,26 @@ jobs:
secrets: inherit

codecov:
if: ${{ needs.init.outputs.run-privileged-jobs == 'true' }}
if: ${{always() && needs.init.outputs.run-privileged-jobs == 'true' && needs.build.result == 'success' && needs.test_qualitygate.result == 'success'}}
name: call-codecov
needs: [init, build, test_linux, test_windows]
needs: [init, build, test_qualitygate]
uses: codebeltnet/jobs-codecov/.github/workflows/default.yml@v1
with:
repository: codebeltnet/shared-kernel
secrets: inherit

codeql:
if: ${{ needs.init.outputs.run-privileged-jobs == 'true' }}
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_linux, test_windows]
needs: [init, build, test_qualitygate]
uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v3
permissions:
security-events: write

deploy:
if: github.event_name != 'pull_request'
name: call-nuget
needs: [build, pack, test_linux, test_windows, sonarcloud, codecov, codeql]
needs: [build, pack, test_qualitygate, sonarcloud, codecov, codeql]
uses: codebeltnet/jobs-nuget-push/.github/workflows/default.yml@v3
with:
version: ${{ needs.build.outputs.version }}
Expand Down
6 changes: 6 additions & 0 deletions .nuget/Codebelt.SharedKernel/PackageReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version: 0.5.7
Availability: .NET 10 and .NET 9

# ALM
- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)

Version: 0.5.6
Availability: .NET 10 and .NET 9

Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ 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.5.7] - 2026-05-27

This is a patch release that improves test coverage, infrastructure resilience, and code quality through CI/CD enhancements, package updates, and developer guidance.

### Added

- Optional macOS test matrix in `ci-pipeline.yml` with conditional job gating, enabling ARM64 and X64 architecture testing when explicitly triggered.

### Changed

- `Microsoft.NET.Test.Sdk` upgraded to 18.6.0,
- `coverlet.collector` and `coverlet.msbuild` upgraded to 10.0.1,
- `.github/copilot-instructions.md` extended with comprehensive code coverage prohibition guidance and refactoring best practices,
- `ci-pipeline.yml` enhanced with test result evaluation quality gate and improved platform-specific test orchestration,
- Test coverage expanded across `TokenTest.cs`, `TimeToLiveTest.cs`, `CorrelationIdTest.cs`, `ClockSkewTest.cs`, and `SecretTest.cs` with additional test cases.

## [0.5.6] - 2026-04-18

This is a service update that focuses on package dependencies.
Expand Down Expand Up @@ -103,6 +119,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.5.7]: https://github.com/codebeltnet/shared-kernel/compare/v0.5.6...v0.5.7
[0.5.6]: https://github.com/codebeltnet/shared-kernel/compare/v0.5.5...v0.5.6
[0.5.5]: https://github.com/codebeltnet/shared-kernel/compare/v0.5.4...v0.5.5
[0.5.4]: https://github.com/codebeltnet/shared-kernel/compare/v0.5.3...v0.5.4
Expand Down
16 changes: 8 additions & 8 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Cuemon.Extensions.IO" Version="10.5.1" />
<PackageVersion Include="Codebelt.Extensions.Xunit.App" Version="11.0.9" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageVersion Include="Cuemon.Extensions.IO" Version="10.5.2" />
<PackageVersion Include="Codebelt.Extensions.Xunit.App" Version="11.0.10" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageVersion Include="MinVer" Version="7.0.0" />
<PackageVersion Include="Savvyio.Domain" Version="5.0.6" />
<PackageVersion Include="Savvyio.Extensions.Newtonsoft.Json" Version="5.0.6" />
<PackageVersion Include="Savvyio.Extensions.Text.Json" Version="5.0.6" />
<PackageVersion Include="coverlet.collector" Version="10.0.0" />
<PackageVersion Include="coverlet.msbuild" Version="10.0.0" />
<PackageVersion Include="Savvyio.Domain" Version="5.0.7" />
<PackageVersion Include="Savvyio.Extensions.Newtonsoft.Json" Version="5.0.7" />
<PackageVersion Include="Savvyio.Extensions.Text.Json" Version="5.0.7" />
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
<PackageVersion Include="coverlet.msbuild" Version="10.0.1" />
<PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="xunit.v3.runner.console" Version="3.2.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
Expand Down
10 changes: 10 additions & 0 deletions test/Codebelt.SharedKernel.Tests/ClockSkewTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ public void FromSeconds_ShouldHaveThirtySecondsSkew()
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()
{
Expand Down
11 changes: 11 additions & 0 deletions test/Codebelt.SharedKernel.Tests/CorrelationIdTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasConsi
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;

Assert.Equal("1acb4e4928a64206b22b2392ffd4e605", fromGuid.Value);
Assert.Equal(fromGuid, fromString);
}

[Fact]
public void Marshalling_ShouldRepresentCorrectly()
{
Expand Down
10 changes: 10 additions & 0 deletions test/Codebelt.SharedKernel.Tests/Security/SecretTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Text;
using Cuemon;
using Cuemon.Extensions.IO;
using Codebelt.Extensions.Xunit;
Expand Down Expand Up @@ -98,6 +99,15 @@ public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasExcee
Assert.Equal("129 > 128", sut.ActualValue);
}

[Fact]
public void StringAndByteRepresentation_ShouldRepresentCorrectly()
{
var sut = new Secret("1acb4e4928a64206b22b2392ffd4e605");

Assert.Equal("1acb4e4928a64206b22b2392ffd4e605", sut.ToString());
Assert.Equal(Encoding.UTF8.GetBytes(sut.Value), sut.ToByteArray());
}

[Fact]
public void Marshalling_ShouldRepresentCorrectly()
{
Expand Down
10 changes: 10 additions & 0 deletions test/Codebelt.SharedKernel.Tests/TimeToLiveTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ public void FromMinutes_ShouldHaveFifteenMinutesLifespan()
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()
{
Expand Down
10 changes: 10 additions & 0 deletions test/Codebelt.SharedKernel.Tests/TokenTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ public void Constructor_ShouldNotThrowArgumentOutOfRangeException_WhenValueHasEx
Assert.Equal(255, sut.Value.Length);
}

[Fact]
public void Constructor_ShouldNotThrowArgumentOutOfRangeException_WhenMaximumCharacterFrequencyIsDisabled()
{
var sut = new Token(new string('a', 40), o => o.MaximumCharacterFrequency = 0);

TestOutput.WriteLine(sut);

Assert.Equal(40, sut.Value.Length);
}

[Fact]
public void Constructor_ShouldThrowArgumentOutOfRangeException_WhenValueHasHighCharacterFrequency()
{
Expand Down
Loading