From 9c4bd98ae88e44299211bcf62955cc622a84ed47 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 28 Apr 2026 15:47:20 +0200 Subject: [PATCH 1/6] Add coverage publishing --- .github/workflows/ci-build.yml | 178 +++++++++++++++++++-- API.IntegrationTests/AssemblyAttributes.cs | 4 +- README.md | 2 + 3 files changed, 168 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 79e30526..f3835bc2 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -34,20 +34,13 @@ name: ci-build env: REGISTRY: ghcr.io DOTNET_VERSION: 10.0.x + COVERAGE_MIN_PERCENT: '85' jobs: - tests: - name: Tests (${{ matrix.name }}) + unit-tests: + name: Unit Tests runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - name: common-unit - project: Common.Tests/Common.Tests.csproj - - name: api-integration - project: API.IntegrationTests/API.IntegrationTests.csproj steps: - name: Checkout @@ -57,14 +50,171 @@ jobs: with: dotnet-version: '${{ env.DOTNET_VERSION }}' - - name: Run ${{ matrix.name }} tests + - name: Run unit tests run: | set -euo pipefail - dotnet test -c Release --project ${{ matrix.project }} + results_dir="artifacts/test-results/common-unit" + dotnet test -c Release --project Common.Tests/Common.Tests.csproj \ + --results-directory "$results_dir" \ + -- \ + --coverage \ + --coverage-output coverage.cobertura.xml \ + --coverage-output-format cobertura \ + --report-trx \ + --report-trx-filename test-results.trx + + - name: Upload unit test artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-common-unit + path: artifacts/test-results/common-unit + if-no-files-found: warn + + integration-tests: + name: Integration Tests + runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: '${{ env.DOTNET_VERSION }}' + + - name: Run integration tests + run: | + set -euo pipefail + results_dir="artifacts/test-results/api-integration" + dotnet test -c Release --project API.IntegrationTests/API.IntegrationTests.csproj \ + --results-directory "$results_dir" \ + -- \ + --coverage \ + --coverage-output coverage.cobertura.xml \ + --coverage-output-format cobertura \ + --report-trx \ + --report-trx-filename test-results.trx + + - name: Upload integration test artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-api-integration + path: artifacts/test-results/api-integration + if-no-files-found: warn + + coverage: + name: Coverage + runs-on: ubuntu-latest + needs: [unit-tests, integration-tests] + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} + outputs: + linecoverage: ${{ steps.enforce.outputs.linecoverage }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: '${{ env.DOTNET_VERSION }}' + + - name: Download test artifacts + uses: actions/download-artifact@v5 + with: + pattern: test-results-* + path: artifacts/test-results + + - name: Install ReportGenerator + run: dotnet tool install --tool-path ./.tools dotnet-reportgenerator-globaltool + + - name: Generate merged coverage report + run: | + set -euo pipefail + reports="$(find artifacts/test-results -name coverage.cobertura.xml -print | paste -sd ';' -)" + if [ -z "$reports" ]; then + echo "No coverage reports found" >&2 + exit 1 + fi + + ./.tools/reportgenerator \ + "-reports:${reports}" \ + "-targetdir:artifacts/coverage" \ + "-reporttypes:Html;MarkdownSummaryGithub;JsonSummary;Badges" + + - name: Add coverage summary + run: cat artifacts/coverage/SummaryGithub.md >> "$GITHUB_STEP_SUMMARY" + + - name: Enforce minimum coverage + id: enforce + env: + COVERAGE_MIN_PERCENT: ${{ env.COVERAGE_MIN_PERCENT }} + run: | + python - <<'PY' + import json + import os + + threshold = float(os.environ["COVERAGE_MIN_PERCENT"]) + with open("artifacts/coverage/Summary.json", "r", encoding="utf-8") as handle: + summary = json.load(handle)["summary"] + + line_coverage = float(summary["linecoverage"]) + print(f"Line coverage: {line_coverage:.1f}%") + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + output.write(f"linecoverage={line_coverage:.1f}\n") + + if line_coverage < threshold: + raise SystemExit( + f"Coverage threshold not met: {line_coverage:.1f}% < {threshold:.1f}%" + ) + PY + + - name: Upload merged coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: artifacts/coverage + if-no-files-found: error + + publish-coverage: + name: Publish coverage + runs-on: ubuntu-latest + needs: coverage + if: ${{ github.ref_type == 'branch' && github.event_name != 'pull_request' && github.ref_name == 'master' }} + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Download merged coverage report + uses: actions/download-artifact@v5 + with: + name: coverage-report + path: _site/coverage + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v4 + with: + path: _site + + - name: Deploy Pages site + id: deployment + uses: actions/deploy-pages@v4 build: name: Build (${{ matrix.image }}) runs-on: ubuntu-latest + needs: [coverage] strategy: fail-fast: false matrix: @@ -101,7 +251,7 @@ jobs: promote-image: name: Promote Image (${{ matrix.image }}) - needs: [build, tests] + needs: [build, coverage] runs-on: ubuntu-latest if: ${{ inputs.push || (github.ref_protected && github.event_name != 'pull_request') }} strategy: @@ -180,4 +330,4 @@ jobs: - uses: ./.github/actions/watchtower-update with: url: ${{ vars.WATCHTOWER_URL }} - token: ${{ secrets.WATCHTOWER_TOKEN }} \ No newline at end of file + token: ${{ secrets.WATCHTOWER_TOKEN }} diff --git a/API.IntegrationTests/AssemblyAttributes.cs b/API.IntegrationTests/AssemblyAttributes.cs index d7d85793..4cca6adb 100644 --- a/API.IntegrationTests/AssemblyAttributes.cs +++ b/API.IntegrationTests/AssemblyAttributes.cs @@ -1,10 +1,10 @@ using TUnit.Core; using TUnit.Core.Interfaces; -// Allow up to 3 minutes per test — integration tests can be slow in CI when Docker images +// Allow up to 5 minutes per test — integration tests can be slow in CI when Docker images // are cold-pulled and EF migrations run for the first time. The execution timer in TUnit // may include class-data-source initialization time for the first test that uses the factory. -[assembly: Timeout(3 * 60_000)] +[assembly: Timeout(5 * 60_000)] // Limit parallel test execution to avoid thread pool starvation on CI runners. // BCrypt password hashing in login/signup endpoints is synchronous and CPU-bound; diff --git a/README.md b/README.md index a28f262d..c7d67316 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # OpenShock API +[![Coverage](https://openshock.github.io/API/coverage/badge_linecoverage.svg)](https://openshock.github.io/API/coverage/) + OpenShock backend ### API Documentation From 6c52e4b3c519492010d8b1a5e7d5c9645d6ad9f9 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 28 Apr 2026 16:00:18 +0200 Subject: [PATCH 2/6] merge integration and unit tests --- .github/workflows/ci-build.yml | 56 +++++----------------- API.IntegrationTests/AssemblyAttributes.cs | 1 + 2 files changed, 12 insertions(+), 45 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index f3835bc2..af00afe4 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -38,8 +38,8 @@ env: jobs: - unit-tests: - name: Unit Tests + tests: + name: Tests runs-on: ubuntu-latest steps: @@ -50,65 +50,31 @@ jobs: with: dotnet-version: '${{ env.DOTNET_VERSION }}' - - name: Run unit tests + - name: Run tests run: | set -euo pipefail - results_dir="artifacts/test-results/common-unit" - dotnet test -c Release --project Common.Tests/Common.Tests.csproj \ - --results-directory "$results_dir" \ + dotnet test -c Release OpenShockBackend.slnx \ + --results-directory "artifacts/test-results" \ -- \ + ${{ (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master') && '' || '--treenode-filter "/*/*/*/*[Category!=Integration]"' }} \ --coverage \ --coverage-output coverage.cobertura.xml \ --coverage-output-format cobertura \ --report-trx \ --report-trx-filename test-results.trx - - name: Upload unit test artifacts + - name: Upload test artifacts if: always() uses: actions/upload-artifact@v4 with: - name: test-results-common-unit - path: artifacts/test-results/common-unit - if-no-files-found: warn - - integration-tests: - name: Integration Tests - runs-on: ubuntu-latest - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - uses: actions/setup-dotnet@v5 - with: - dotnet-version: '${{ env.DOTNET_VERSION }}' - - - name: Run integration tests - run: | - set -euo pipefail - results_dir="artifacts/test-results/api-integration" - dotnet test -c Release --project API.IntegrationTests/API.IntegrationTests.csproj \ - --results-directory "$results_dir" \ - -- \ - --coverage \ - --coverage-output coverage.cobertura.xml \ - --coverage-output-format cobertura \ - --report-trx \ - --report-trx-filename test-results.trx - - - name: Upload integration test artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results-api-integration - path: artifacts/test-results/api-integration + name: test-results + path: artifacts/test-results if-no-files-found: warn coverage: name: Coverage runs-on: ubuntu-latest - needs: [unit-tests, integration-tests] + needs: [tests] if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} outputs: linecoverage: ${{ steps.enforce.outputs.linecoverage }} @@ -124,7 +90,7 @@ jobs: - name: Download test artifacts uses: actions/download-artifact@v5 with: - pattern: test-results-* + name: test-results path: artifacts/test-results - name: Install ReportGenerator diff --git a/API.IntegrationTests/AssemblyAttributes.cs b/API.IntegrationTests/AssemblyAttributes.cs index 4cca6adb..9da64f98 100644 --- a/API.IntegrationTests/AssemblyAttributes.cs +++ b/API.IntegrationTests/AssemblyAttributes.cs @@ -4,6 +4,7 @@ // Allow up to 5 minutes per test — integration tests can be slow in CI when Docker images // are cold-pulled and EF migrations run for the first time. The execution timer in TUnit // may include class-data-source initialization time for the first test that uses the factory. +[assembly: Category("Integration")] [assembly: Timeout(5 * 60_000)] // Limit parallel test execution to avoid thread pool starvation on CI runners. From 982572aea7e4140e2e8895ee5f6c3a520655ce46 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 28 Apr 2026 16:03:10 +0200 Subject: [PATCH 3/6] fix stuff --- .github/workflows/ci-build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index af00afe4..fea66d6d 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -53,7 +53,7 @@ jobs: - name: Run tests run: | set -euo pipefail - dotnet test -c Release OpenShockBackend.slnx \ + dotnet test -c Release --solution OpenShockBackend.slnx \ --results-directory "artifacts/test-results" \ -- \ ${{ (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master') && '' || '--treenode-filter "/*/*/*/*[Category!=Integration]"' }} \ @@ -180,7 +180,6 @@ jobs: build: name: Build (${{ matrix.image }}) runs-on: ubuntu-latest - needs: [coverage] strategy: fail-fast: false matrix: From 74e2f3d7b1d356da2ab455ad6bcd1fd2ae1acb53 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 28 Apr 2026 16:08:41 +0200 Subject: [PATCH 4/6] maybe --- .github/workflows/ci-build.yml | 16 +++++++++------- API.IntegrationTests/AssemblyAttributes.cs | 1 - 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index fea66d6d..0fd32bfd 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -2,10 +2,6 @@ on: push: branches: - '**' - pull_request: - branches: - - '**' - types: [ opened, reopened, synchronize ] workflow_call: inputs: platforms: @@ -51,12 +47,18 @@ jobs: dotnet-version: '${{ env.DOTNET_VERSION }}' - name: Run tests + env: + RUN_INTEGRATION: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} run: | set -euo pipefail - dotnet test -c Release --solution OpenShockBackend.slnx \ + if [ "$RUN_INTEGRATION" = "true" ]; then + target="--solution OpenShockBackend.slnx" + else + target="Common.Tests/Common.Tests.csproj" + fi + dotnet test -c Release $target \ --results-directory "artifacts/test-results" \ -- \ - ${{ (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master') && '' || '--treenode-filter "/*/*/*/*[Category!=Integration]"' }} \ --coverage \ --coverage-output coverage.cobertura.xml \ --coverage-output-format cobertura \ @@ -75,7 +77,7 @@ jobs: name: Coverage runs-on: ubuntu-latest needs: [tests] - if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} outputs: linecoverage: ${{ steps.enforce.outputs.linecoverage }} diff --git a/API.IntegrationTests/AssemblyAttributes.cs b/API.IntegrationTests/AssemblyAttributes.cs index 9da64f98..4cca6adb 100644 --- a/API.IntegrationTests/AssemblyAttributes.cs +++ b/API.IntegrationTests/AssemblyAttributes.cs @@ -4,7 +4,6 @@ // Allow up to 5 minutes per test — integration tests can be slow in CI when Docker images // are cold-pulled and EF migrations run for the first time. The execution timer in TUnit // may include class-data-source initialization time for the first test that uses the factory. -[assembly: Category("Integration")] [assembly: Timeout(5 * 60_000)] // Limit parallel test execution to avoid thread pool starvation on CI runners. From bd1be4956eb3fa603576a80e5b6a0f7e6ff19158 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 28 Apr 2026 16:09:40 +0200 Subject: [PATCH 5/6] oops --- .github/workflows/ci-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 0fd32bfd..76cd7839 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -54,7 +54,7 @@ jobs: if [ "$RUN_INTEGRATION" = "true" ]; then target="--solution OpenShockBackend.slnx" else - target="Common.Tests/Common.Tests.csproj" + target="--project Common.Tests/Common.Tests.csproj" fi dotnet test -c Release $target \ --results-directory "artifacts/test-results" \ From 5dcf07c76dff5ec609fa5dd46e6fe253698f85f6 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 28 Apr 2026 16:12:34 +0200 Subject: [PATCH 6/6] nevermind, always run full test --- .github/workflows/ci-build.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 76cd7839..4278153e 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -47,16 +47,9 @@ jobs: dotnet-version: '${{ env.DOTNET_VERSION }}' - name: Run tests - env: - RUN_INTEGRATION: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} run: | set -euo pipefail - if [ "$RUN_INTEGRATION" = "true" ]; then - target="--solution OpenShockBackend.slnx" - else - target="--project Common.Tests/Common.Tests.csproj" - fi - dotnet test -c Release $target \ + dotnet test -c Release --solution OpenShockBackend.slnx \ --results-directory "artifacts/test-results" \ -- \ --coverage \ @@ -77,7 +70,6 @@ jobs: name: Coverage runs-on: ubuntu-latest needs: [tests] - if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.ref_name == 'develop' || github.ref_name == 'master' }} outputs: linecoverage: ${{ steps.enforce.outputs.linecoverage }}