1- name : Dev - Branch Protection
1+ name : Dev - CI & Unit Tests
2+
23permissions :
34 contents : read
5+ pull-requests : write
46
57on :
68 pull_request :
79 branches :
810 - dev
911
1012jobs :
13+ # ---------------------------------------------------------------------
14+ # 1. Validate PR source branch
15+ # ---------------------------------------------------------------------
1116 check-dev-branch :
17+ runs-on : ubuntu-latest
18+ outputs :
19+ branch-allowed : ${{ steps.branch-check.outputs.allowed }}
20+ steps :
21+ - name : Validate PR source branch
22+ id : branch-check
23+ run : |
24+ echo "Source branch: ${GITHUB_HEAD_REF}"
25+ if [[ ${GITHUB_HEAD_REF} =~ ^feature/ ]] || \
26+ [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]] || \
27+ [[ ${GITHUB_HEAD_REF} =~ ^bugfix/ ]] || \
28+ [[ ${GITHUB_HEAD_REF} == "test" ]] || \
29+ [[ ${GITHUB_HEAD_REF} == "main" ]]; then
30+ echo "allowed=true" >> $GITHUB_OUTPUT
31+ else
32+ echo "❌ ERROR: PR into dev must come from:"
33+ echo " feature/*, hotfix/*, bugfix/*, test, or main"
34+ echo "allowed=false" >> $GITHUB_OUTPUT
35+ exit 1
36+ fi
37+ # ---------------------------------------------------------------------
38+ # 2. Discover all *Tests.csproj files and output into a matrix array
39+ # ---------------------------------------------------------------------
40+ discover-test-projects :
41+ needs : check-dev-branch
42+ if : needs.check-dev-branch.outputs.branch-allowed == 'true'
43+ runs-on : ubuntu-latest
44+ outputs :
45+ matrix : ${{ steps.discover.outputs.matrix }}
46+ steps :
47+ - uses : actions/checkout@v4
48+
49+ - id : discover
50+ run : |
51+ echo "🔍 Discovering test projects..."
52+ PROJECTS=$(find applications/Unity.GrantManager -name "*Tests.csproj")
53+ echo "$PROJECTS"
54+ # Convert list to JSON array for GitHub matrix
55+ MATRIX=$(printf '%s\n' $PROJECTS | jq -R -s -c 'split("\n")[:-1]')
56+ echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
57+ # ---------------------------------------------------------------------
58+ # 3. Run tests for each project in parallel (matrix)
59+ # ---------------------------------------------------------------------
60+ test-project :
61+ needs : discover-test-projects
62+ runs-on : ubuntu-latest
63+
64+ strategy :
65+ fail-fast : false
66+ matrix :
67+ project : ${{ fromJson(needs.discover-test-projects.outputs.matrix) }}
68+
69+ steps :
70+ - uses : actions/checkout@v4
71+
72+ - uses : actions/setup-dotnet@v4
73+ with :
74+ dotnet-version : " 9.0.x"
75+
76+ - name : Ensure TestResults folder exists
77+ run : mkdir -p TestResults
78+
79+ - name : Run unit tests
80+ run : |
81+ NAME=$(basename "${{ matrix.project }}" .csproj)
82+ TRX="TestResults/${NAME}.trx"
83+ echo "▶ Running: $NAME"
84+ dotnet test "${{ matrix.project }}" \
85+ --logger "trx;LogFileName=${NAME}.trx" \
86+ --results-directory TestResults
87+
88+ - uses : actions/upload-artifact@v4
89+ with :
90+ name : test-output-${{ strategy.job-index }}
91+ path : TestResults/
92+
93+ # ---------------------------------------------------------------------
94+ # 4. Merge all TRX and produce final results
95+ # ---------------------------------------------------------------------
96+ aggregate-results :
97+ needs : test-project
1298 runs-on : ubuntu-latest
1399 steps :
14- - name : Check branch
100+ - uses : actions/download-artifact@v4
101+ with :
102+ path : artifacts/
103+ - name : Install ReportGenerator
104+ run : dotnet tool install -g dotnet-reportgenerator-globaltool
105+
106+ - name : Add dotnet tools to PATH
107+ run : echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
108+
109+ - name : List TRX files
110+ run : |
111+ echo "🔍 TRX files:"
112+ find artifacts/ -name "*.trx"
113+
114+
115+ - name : Extract totals from merged trx files
116+ id : extract
15117 run : |
16- if [[ ${GITHUB_HEAD_REF} =~ ^feature/ ]] || [[ ${GITHUB_HEAD_REF} == 'test' ]] || [[ ${GITHUB_HEAD_REF} == 'main' ]] || [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]] || [[ ${GITHUB_HEAD_REF} =~ ^bugfix/ ]]; then
17- echo ""
18- echo "Notice: Pull Request into dev should come from a merged 'feature/*', 'hotfix/*', 'bugfix/*', 'test', or 'main' branch"
19- echo "Notice: The dev branch is for new features, hotfixes, bugfixes and accepts merge, squash or rebase commits"
20- exit 0
21- fi
118+ PASSED=0
119+ FAILED=0
120+ SKIPPED=0
121+ for file in artifacts/**/*.trx; do
122+ COUNTERS=$(grep "<Counters " "$file")
123+ PASSED_FILE=$(echo "$COUNTERS" | sed -n 's/.*passed="\([0-9]*\)".*/\1/p')
124+ FAILED_FILE=$(echo "$COUNTERS" | sed -n 's/.*failed="\([0-9]*\)".*/\1/p')
125+ NOT_EXECUTED_FILE=$(echo "$COUNTERS" | sed -n 's/.*notExecuted="\([0-9]*\)".*/\1/p')
126+ PASSED=$((PASSED + PASSED_FILE))
127+ FAILED=$((FAILED + FAILED_FILE))
128+ SKIPPED=$((SKIPPED + NOT_EXECUTED_FILE))
129+ done
130+
131+ echo "Passed: $PASSED, Failed: $FAILED, Skipped: $SKIPPED"
132+ echo "passed=$PASSED" >> $GITHUB_OUTPUT
133+ echo "failed=$FAILED" >> $GITHUB_OUTPUT
134+ echo "skipped=$SKIPPED" >> $GITHUB_OUTPUT
135+
136+ - uses : actions/upload-artifact@v4
137+ with :
138+ name : merged-test-results
139+ path : merged/
140+
141+ - name : Build test badge
142+ id : badge
143+ run : |
144+ if [[ "${{ steps.extract.outputs.failed }}" -gt 0 ]]; then
145+ echo "badge=" >> $GITHUB_OUTPUT
146+ else
147+ echo "badge=" >> $GITHUB_OUTPUT
148+ fi
149+ - name : Post PR Comment
150+ uses : peter-evans/create-or-update-comment@v4
151+ with :
152+ issue-number : ${{ github.event.pull_request.number }}
153+ body : |
154+ ## 🧪 Unit Test Results (Parallel Execution)
155+ ${{ steps.badge.outputs.badge }}
156+ ### 📊 Summary
157+ | Result | Count |
158+ |--------|-------|
159+ | ✅ Passed | `${{ steps.extract.outputs.passed }}` |
160+ | ❌ Failed | `${{ steps.extract.outputs.failed }}` |
161+ | ⚠️ Skipped | `${{ steps.extract.outputs.skipped }}` |
162+ ### 📄 HTML Reports
163+ - **Merged Tests (HTML):** Included in artifacts
164+ _Generated automatically by CI._
165+ - name : Send Microsoft Teams Notification
166+ env :
167+ WEBHOOK_URL : ${{ secrets.TEAMS_UNITY_NOTIFICATIONS_CHANNEL }} # NOSONAR
168+ PR_TITLE : ${{ github.event.pull_request.title }}
169+ PR_AUTHOR : ${{ github.event.pull_request.user.login }}
170+ PR_NUMBER : ${{ github.event.pull_request.number }}
171+ PASSED : ${{ steps.extract.outputs.failed == '0' && 'true' || 'false' }}
172+ PASSED_COUNT : ${{ steps.extract.outputs.passed }}
173+ FAILED_COUNT : ${{ steps.extract.outputs.failed }}
174+ SKIPPED_COUNT : ${{ steps.extract.outputs.skipped }}
175+ run : |
176+ if [[ "$PASSED" == "true" ]]; then
177+ COLOR="00FF00"
178+ STATUS="🟢 Tests Passed $PR_TITLE (by $PR_AUTHOR)"
179+ else
180+ COLOR="FF0000"
181+ STATUS="🔴 Tests Failed $PR_TITLE (by $PR_AUTHOR)"
182+ fi
183+ JSON=$(cat <<EOF
184+ {
185+ "@type": "MessageCard",
186+ "@context": "http://schema.org/extensions",
187+ "themeColor": "$COLOR",
188+ "activityImage": "https://bcgov.github.io/unity-docs/images/UnityLogo.png",
189+ "summary": "Unit Test Results",
190+ "title": "$STATUS - PR #$PR_NUMBER",
191+ "sections": [{
192+ "facts": [
193+ { "name": "Passed", "value": "$PASSED_COUNT" },
194+ { "name": "Failed", "value": "$FAILED_COUNT" },
195+ { "name": "Skipped", "value": "$SKIPPED_COUNT" }
196+ ],
197+ "markdown": true
198+ }]
199+ }
200+ EOF
201+ )
202+
203+ if [ -n "$WEBHOOK_URL" ]; then
204+ curl -H "Content-Type: application/json" -d "$JSON" "$WEBHOOK_URL"
205+ else
206+ echo "⚠️ TEAMS_NOTIFICATION_WEBHOOK_URL secret not configured, skipping notification"
207+ fi
208+ - name : Fail if tests failed
209+ if : steps.extract.outputs.failed != '0'
210+ run : exit 1
0 commit comments