Skip to content
Open
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
143 changes: 126 additions & 17 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ on:
push:
branches:
- '**'
pull_request:
branches:
- '**'
types: [ opened, reopened, synchronize ]
workflow_call:
inputs:
platforms:
Expand Down Expand Up @@ -34,20 +30,13 @@ name: ci-build
env:
REGISTRY: ghcr.io
DOTNET_VERSION: 10.0.x
COVERAGE_MIN_PERCENT: '85'

jobs:

tests:
name: Tests (${{ matrix.name }})
name: 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
Expand All @@ -57,10 +46,130 @@ jobs:
with:
dotnet-version: '${{ env.DOTNET_VERSION }}'

- name: Run ${{ matrix.name }} tests
- name: Run tests
run: |
set -euo pipefail
dotnet test -c Release --solution OpenShockBackend.slnx \
--results-directory "artifacts/test-results" \
-- \
--coverage \
--coverage-output coverage.cobertura.xml \
--coverage-output-format cobertura \
--report-trx \
--report-trx-filename test-results.trx

- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: artifacts/test-results
if-no-files-found: warn

coverage:
name: Coverage
runs-on: ubuntu-latest
needs: [tests]
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:
name: 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
dotnet test -c Release --project ${{ matrix.project }}
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 }})
Expand Down Expand Up @@ -101,7 +210,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:
Expand Down Expand Up @@ -180,4 +289,4 @@ jobs:
- uses: ./.github/actions/watchtower-update
with:
url: ${{ vars.WATCHTOWER_URL }}
token: ${{ secrets.WATCHTOWER_TOKEN }}
token: ${{ secrets.WATCHTOWER_TOKEN }}
4 changes: 2 additions & 2 deletions API.IntegrationTests/AssemblyAttributes.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading