Skip to content

Commit 6b9bf43

Browse files
committed
Add full test suite & CI/CD with coverage for all modules
- Introduce comprehensive unit tests for Auditing, Identity, and Multitenancy modules, including validators, options, services, and domain entities - Add new test projects: Auditing.Tests, Identity.Tests, Generic.Tests, and enhance Multitenacy.Tests - Enforce sealed classes for all validators and handlers; fix naming for consistency - Replace old build/publish workflows with unified ci.yml: build, test (matrix), code coverage, artifact upload, and container/NuGet publishing - Add coverlet.collector and NSubstitute for coverage and mocking; update Directory.Packages.props - Update solution file to include all test projects - Add InternalsVisibleTo for test access; document all test code - Major quality, maintainability, and release process improvement
1 parent bf30e81 commit 6b9bf43

49 files changed

Lines changed: 5780 additions & 201 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-and-push-containers.yml

Lines changed: 0 additions & 68 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
name: CI/CD Pipeline
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- develop
8+
tags:
9+
- 'v*'
10+
pull_request:
11+
branches:
12+
- main
13+
- develop
14+
workflow_dispatch:
15+
inputs:
16+
version:
17+
description: 'Package version (e.g., 10.0.0-rc.1)'
18+
required: false
19+
type: string
20+
21+
permissions:
22+
contents: read
23+
packages: write
24+
25+
env:
26+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
27+
DOTNET_CLI_TELEMETRY_OPTOUT: true
28+
29+
jobs:
30+
build:
31+
name: Build
32+
runs-on: ubuntu-latest
33+
34+
steps:
35+
- name: Checkout
36+
uses: actions/checkout@v4
37+
38+
- name: Setup .NET SDK
39+
uses: actions/setup-dotnet@v4
40+
with:
41+
dotnet-version: '10.0.x'
42+
43+
- name: Restore dependencies
44+
run: dotnet restore src/FSH.Framework.slnx
45+
46+
- name: Build
47+
run: dotnet build src/FSH.Framework.slnx -c Release --no-restore
48+
49+
test:
50+
name: Test - ${{ matrix.test-project.name }}
51+
runs-on: ubuntu-latest
52+
needs: build
53+
54+
strategy:
55+
fail-fast: false
56+
matrix:
57+
test-project:
58+
- name: Architecture.Tests
59+
path: src/Tests/Architecture.Tests
60+
- name: Auditing.Tests
61+
path: src/Tests/Auditing.Tests
62+
- name: Generic.Tests
63+
path: src/Tests/Generic.Tests
64+
- name: Identity.Tests
65+
path: src/Tests/Identity.Tests
66+
- name: Multitenancy.Tests
67+
path: src/Tests/Multitenacy.Tests
68+
69+
steps:
70+
- name: Checkout
71+
uses: actions/checkout@v4
72+
73+
- name: Setup .NET SDK
74+
uses: actions/setup-dotnet@v4
75+
with:
76+
dotnet-version: '10.0.x'
77+
78+
- name: Restore dependencies
79+
run: dotnet restore src/FSH.Framework.slnx
80+
81+
- name: Build
82+
run: dotnet build src/FSH.Framework.slnx -c Release --no-restore
83+
84+
- name: Run ${{ matrix.test-project.name }}
85+
run: dotnet test ${{ matrix.test-project.path }} -c Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx"
86+
87+
- name: Upload test results
88+
uses: actions/upload-artifact@v4
89+
if: always()
90+
with:
91+
name: test-results-${{ matrix.test-project.name }}
92+
path: ${{ matrix.test-project.path }}/TestResults/test-results.trx
93+
retention-days: 7
94+
95+
coverage:
96+
name: Code Coverage
97+
runs-on: ubuntu-latest
98+
needs: test
99+
100+
steps:
101+
- name: Checkout
102+
uses: actions/checkout@v4
103+
104+
- name: Setup .NET SDK
105+
uses: actions/setup-dotnet@v4
106+
with:
107+
dotnet-version: '10.0.x'
108+
109+
- name: Restore dependencies
110+
run: dotnet restore src/FSH.Framework.slnx
111+
112+
- name: Build
113+
run: dotnet build src/FSH.Framework.slnx -c Release --no-restore
114+
115+
- name: Run tests with coverage
116+
run: |
117+
dotnet test src/FSH.Framework.slnx -c Release --no-build \
118+
--collect:"XPlat Code Coverage" \
119+
--results-directory ./coverage
120+
121+
- name: Install ReportGenerator
122+
run: dotnet tool install -g dotnet-reportgenerator-globaltool
123+
124+
- name: Generate coverage report
125+
run: |
126+
reportgenerator \
127+
-reports:"./coverage/**/coverage.cobertura.xml" \
128+
-targetdir:"./coverage/report" \
129+
-reporttypes:"Cobertura;TextSummary"
130+
131+
- name: Upload coverage report
132+
uses: actions/upload-artifact@v4
133+
with:
134+
name: coverage-report
135+
path: ./coverage/report
136+
retention-days: 7
137+
138+
- name: Display coverage summary
139+
run: cat ./coverage/report/Summary.txt
140+
141+
# Build and push dev containers on develop branch
142+
publish-dev-containers:
143+
name: Publish Dev Containers
144+
runs-on: ubuntu-latest
145+
needs: test
146+
if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
147+
148+
steps:
149+
- name: Checkout
150+
uses: actions/checkout@v4
151+
152+
- name: Setup .NET SDK
153+
uses: actions/setup-dotnet@v4
154+
with:
155+
dotnet-version: '10.0.x'
156+
157+
- name: Login to GHCR
158+
uses: docker/login-action@v3
159+
with:
160+
registry: ghcr.io
161+
username: ${{ github.actor }}
162+
password: ${{ secrets.GITHUB_TOKEN }}
163+
164+
- name: Publish API container image
165+
run: |
166+
dotnet publish src/Playground/Playground.Api/Playground.Api.csproj \
167+
-c Release -r linux-x64 \
168+
-p:PublishProfile=DefaultContainer \
169+
-p:ContainerRepository=ghcr.io/${{ github.repository_owner }}/fsh-playground-api \
170+
-p:ContainerImageTags='"dev-${{ github.sha }};dev-latest"'
171+
172+
- name: Publish Blazor container image
173+
run: |
174+
dotnet publish src/Playground/Playground.Blazor/Playground.Blazor.csproj \
175+
-c Release -r linux-x64 \
176+
-p:PublishProfile=DefaultContainer \
177+
-p:ContainerRepository=ghcr.io/${{ github.repository_owner }}/fsh-playground-blazor \
178+
-p:ContainerImageTags='"dev-${{ github.sha }};dev-latest"'
179+
180+
- name: Push containers to GHCR
181+
run: |
182+
docker push ghcr.io/${{ github.repository_owner }}/fsh-playground-api:dev-${{ github.sha }}
183+
docker push ghcr.io/${{ github.repository_owner }}/fsh-playground-api:dev-latest
184+
docker push ghcr.io/${{ github.repository_owner }}/fsh-playground-blazor:dev-${{ github.sha }}
185+
docker push ghcr.io/${{ github.repository_owner }}/fsh-playground-blazor:dev-latest
186+
187+
# Publish NuGet packages and release containers on main branch (tags or manual)
188+
publish-release:
189+
name: Publish Release (NuGet + Containers)
190+
runs-on: ubuntu-latest
191+
needs: test
192+
if: |
193+
(github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch') ||
194+
startsWith(github.ref, 'refs/tags/v')
195+
196+
steps:
197+
- name: Checkout
198+
uses: actions/checkout@v4
199+
200+
- name: Setup .NET SDK
201+
uses: actions/setup-dotnet@v4
202+
with:
203+
dotnet-version: '10.0.x'
204+
205+
- name: Determine version
206+
id: version
207+
run: |
208+
if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ -n "${{ github.event.inputs.version }}" ]; then
209+
VERSION="${{ github.event.inputs.version }}"
210+
elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then
211+
VERSION="${GITHUB_REF#refs/tags/v}"
212+
else
213+
echo "No version specified and not a tag push"
214+
exit 1
215+
fi
216+
echo "version=$VERSION" >> $GITHUB_OUTPUT
217+
echo "Publishing version: $VERSION"
218+
219+
- name: Restore dependencies
220+
run: dotnet restore src/FSH.Framework.slnx
221+
222+
- name: Build in Release mode
223+
run: dotnet build src/FSH.Framework.slnx -c Release --no-restore -p:Version=${{ steps.version.outputs.version }}
224+
225+
- name: Pack BuildingBlocks
226+
run: |
227+
dotnet pack src/BuildingBlocks/Core/Core.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
228+
dotnet pack src/BuildingBlocks/Shared/Shared.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
229+
dotnet pack src/BuildingBlocks/Persistence/Persistence.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
230+
dotnet pack src/BuildingBlocks/Caching/Caching.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
231+
dotnet pack src/BuildingBlocks/Mailing/Mailing.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
232+
dotnet pack src/BuildingBlocks/Jobs/Jobs.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
233+
dotnet pack src/BuildingBlocks/Storage/Storage.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
234+
dotnet pack src/BuildingBlocks/Eventing/Eventing.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
235+
dotnet pack src/BuildingBlocks/Web/Web.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
236+
dotnet pack src/BuildingBlocks/Blazor.UI/Blazor.UI.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
237+
238+
- name: Pack Modules
239+
run: |
240+
dotnet pack src/Modules/Auditing/Modules.Auditing.Contracts/Modules.Auditing.Contracts.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
241+
dotnet pack src/Modules/Auditing/Modules.Auditing/Modules.Auditing.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
242+
dotnet pack src/Modules/Identity/Modules.Identity.Contracts/Modules.Identity.Contracts.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
243+
dotnet pack src/Modules/Identity/Modules.Identity/Modules.Identity.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
244+
dotnet pack src/Modules/Multitenancy/Modules.Multitenancy.Contracts/Modules.Multitenancy.Contracts.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
245+
dotnet pack src/Modules/Multitenancy/Modules.Multitenancy/Modules.Multitenancy.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
246+
247+
- name: Pack CLI Tool
248+
run: dotnet pack src/Tools/CLI/FSH.CLI.csproj -c Release --no-build -o ./nupkgs -p:PackageVersion=${{ steps.version.outputs.version }}
249+
250+
- name: List packages
251+
run: ls -la ./nupkgs
252+
253+
- name: Push to NuGet.org
254+
run: dotnet nuget push "./nupkgs/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
255+
256+
- name: Login to GHCR
257+
uses: docker/login-action@v3
258+
with:
259+
registry: ghcr.io
260+
username: ${{ github.actor }}
261+
password: ${{ secrets.GITHUB_TOKEN }}
262+
263+
- name: Build and push API container
264+
run: |
265+
dotnet publish src/Playground/Playground.Api/Playground.Api.csproj \
266+
-c Release -r linux-x64 \
267+
-p:PublishProfile=DefaultContainer \
268+
-p:ContainerRepository=ghcr.io/${{ github.repository_owner }}/fsh-playground-api \
269+
-p:ContainerImageTags='"${{ steps.version.outputs.version }};latest"'
270+
docker push ghcr.io/${{ github.repository_owner }}/fsh-playground-api:${{ steps.version.outputs.version }}
271+
docker push ghcr.io/${{ github.repository_owner }}/fsh-playground-api:latest
272+
273+
- name: Build and push Blazor container
274+
run: |
275+
dotnet publish src/Playground/Playground.Blazor/Playground.Blazor.csproj \
276+
-c Release -r linux-x64 \
277+
-p:PublishProfile=DefaultContainer \
278+
-p:ContainerRepository=ghcr.io/${{ github.repository_owner }}/fsh-playground-blazor \
279+
-p:ContainerImageTags='"${{ steps.version.outputs.version }};latest"'
280+
docker push ghcr.io/${{ github.repository_owner }}/fsh-playground-blazor:${{ steps.version.outputs.version }}
281+
docker push ghcr.io/${{ github.repository_owner }}/fsh-playground-blazor:latest

0 commit comments

Comments
 (0)