2626 outputs :
2727 backend : ${{ steps.decide.outputs.backend }}
2828 frontend : ${{ steps.decide.outputs.frontend }}
29+ enrollment : ${{ steps.decide.outputs.enrollment }}
2930 steps :
3031 - name : Filter changed paths (PRs only)
3132 id : filter
5657 - 'packages/**'
5758 - 'pnpm-lock.yaml'
5859 - 'pnpm-workspace.yaml'
60+ enrollment:
61+ - 'apps/portal/src/SEBT.EnrollmentChecker.Web/**'
62+ - 'packages/**'
63+ - 'pnpm-lock.yaml'
64+ - 'pnpm-workspace.yaml'
5965
6066 - name : Decide which areas run
6167 id : decide
@@ -64,13 +70,16 @@ jobs:
6470 INFRA : ${{ steps.filter.outputs.infra }}
6571 BACKEND : ${{ steps.filter.outputs.backend }}
6672 FRONTEND : ${{ steps.filter.outputs.frontend }}
73+ ENROLLMENT : ${{ steps.filter.outputs.enrollment }}
6774 run : |
6875 if [ "$EVENT_NAME" != "pull_request" ] || [ "$INFRA" = "true" ]; then
6976 echo "backend=true" >> "$GITHUB_OUTPUT"
7077 echo "frontend=true" >> "$GITHUB_OUTPUT"
78+ echo "enrollment=true" >> "$GITHUB_OUTPUT"
7179 else
7280 echo "backend=${BACKEND:-false}" >> "$GITHUB_OUTPUT"
7381 echo "frontend=${FRONTEND:-false}" >> "$GITHUB_OUTPUT"
82+ echo "enrollment=${ENROLLMENT:-false}" >> "$GITHUB_OUTPUT"
7483 fi
7584
7685 # E2E drives the web app alone (MSW mocks the API), so backend-only changes
@@ -139,6 +148,49 @@ jobs:
139148 retention-days : 7
140149
141150 # Integration E2E drives the full stack (real API + web), so it reacts to both areas.
151+ # The enrollment checker's suite mocks its API at the browser level, so it
152+ # only needs the Next dev server the Playwright config boots itself.
153+ e2e-checker :
154+ name : Enrollment Checker E2E (CO)
155+ needs : changes
156+ if : needs.changes.outputs.enrollment == 'true'
157+ runs-on : ubuntu-latest
158+ timeout-minutes : 15
159+
160+ steps :
161+ - name : Checkout code
162+ uses : actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
163+
164+ - name : Setup pnpm
165+ uses : pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
166+ with :
167+ version : " 10"
168+
169+ - name : Setup Node.js
170+ uses : actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
171+ with :
172+ node-version : " 24"
173+ cache : " pnpm"
174+
175+ - name : Install dependencies
176+ run : pnpm install --frozen-lockfile --prefer-offline
177+
178+ - name : Install Playwright browsers
179+ run : cd apps/portal/src/SEBT.EnrollmentChecker.Web && pnpm exec playwright install --with-deps chromium
180+
181+ - name : Run enrollment checker E2E tests
182+ env :
183+ CI : true
184+ run : pnpm ci:test:e2e:checker
185+
186+ - name : Upload Playwright report
187+ uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
188+ if : ${{ always() }}
189+ with :
190+ name : playwright-report-checker
191+ path : apps/portal/src/SEBT.EnrollmentChecker.Web/playwright-report/
192+ retention-days : 7
193+
142194 e2e-integration-dc :
143195 name : Playwright Integration E2E (DC)
144196 needs : changes
@@ -312,6 +364,189 @@ jobs:
312364 path : apps/portal/src/SEBT.Portal.Web/playwright-report/
313365 retention-days : 7
314366
367+ # Full-stack Colorado sign-in through the compose Keycloak stand-in for
368+ # myColorado: a real OIDC credential dance, not cookie injection. Keycloak
369+ # runs via docker compose (not a service container) because the realm import
370+ # needs the checked-out repo's fixture files.
371+ e2e-integration-co :
372+ name : Playwright Integration E2E (CO)
373+ needs : changes
374+ if : needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true'
375+ runs-on : ubuntu-latest
376+ timeout-minutes : 25
377+
378+ services :
379+ mssql :
380+ image : mcr.microsoft.com/mssql/server:2022-CU16-ubuntu-22.04@sha256:45a1a9d13ca5574cf8e0fe4ae73ab77248b66d9c3132ac9658fb6c16dd72a8af
381+ env :
382+ ACCEPT_EULA : Y
383+ MSSQL_SA_PASSWORD : YourStrong@Passw0rd
384+ ports :
385+ - 1433:1433
386+ options : >-
387+ --health-cmd="/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P YourStrong@Passw0rd -Q 'SELECT 1' -C -b -o /dev/null || exit 1"
388+ --health-interval=10s
389+ --health-timeout=5s
390+ --health-retries=10
391+ --health-start-period=10s
392+
393+ steps :
394+ - name : Checkout code
395+ uses : actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
396+
397+ - name : Start Keycloak (myColorado stand-in)
398+ run : |
399+ docker compose --profile keycloak up -d keycloak
400+ echo "Waiting for Keycloak discovery document..."
401+ for i in $(seq 1 60); do
402+ curl -sf http://localhost:8180/realms/sebt/.well-known/openid-configuration > /dev/null && echo "Keycloak ready" && break
403+ sleep 2
404+ done
405+ curl -sf http://localhost:8180/realms/sebt/.well-known/openid-configuration > /dev/null || (echo "Keycloak failed to start" && docker logs sebt_keycloak | tail -50 && exit 1)
406+
407+ - name : Setup .NET
408+ uses : actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
409+ with :
410+ global-json-file : global.json
411+
412+ - name : Setup pnpm
413+ uses : pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
414+ with :
415+ version : " 10"
416+
417+ - name : Setup Node.js
418+ uses : actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
419+ with :
420+ node-version : " 24"
421+ cache : " pnpm"
422+
423+ - name : Install dependencies
424+ run : pnpm install --frozen-lockfile --prefer-offline
425+
426+ - name : Build backend
427+ run : ./.github/workflows/scripts/build-backend.sh --configuration Release
428+
429+ - name : Build frontend for integration E2E
430+ env :
431+ STATE : co
432+ NEXT_PUBLIC_STATE : co
433+ run : ./.github/workflows/scripts/build-frontend.sh --production --skip-install
434+
435+ - name : Install Playwright browsers
436+ run : cd apps/portal/src/SEBT.Portal.Web && pnpm exec playwright install --with-deps chromium
437+
438+ - name : Install dotnet-ef
439+ run : |
440+ dotnet tool install --global dotnet-ef --version "$CI_DOTNET_EF_VERSION"
441+ echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
442+
443+ - name : Initialize SQL Server Database
444+ env :
445+ ASPNETCORE_ENVIRONMENT : Development
446+ STATE : co
447+ run : |
448+ set -euo pipefail
449+ export PATH="$PATH:$HOME/.dotnet/tools"
450+
451+ CONN="Server=localhost,1433;Database=SebtPortal;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;"
452+ SA_PASSWORD='YourStrong@Passw0rd'
453+ SQLCMD_IMAGE="$CI_MSSQL_IMAGE"
454+
455+ run_sqlcmd() {
456+ docker run --rm --network host "$SQLCMD_IMAGE" \
457+ /opt/mssql-tools18/bin/sqlcmd -S localhost,1433 -U sa -P "$SA_PASSWORD" -C -b "$@"
458+ }
459+
460+ for i in $(seq 1 30); do
461+ if run_sqlcmd -Q 'SELECT 1' -o /dev/null 2>&1; then
462+ echo "SQL Server ready"
463+ break
464+ fi
465+ if [ "$i" -eq 30 ]; then
466+ echo "SQL Server did not become ready in time"
467+ exit 1
468+ fi
469+ sleep 1
470+ done
471+
472+ echo "Creating SebtPortal database if missing..."
473+ run_sqlcmd -Q "IF DB_ID(N'SebtPortal') IS NULL CREATE DATABASE [SebtPortal];"
474+
475+ for attempt in $(seq 1 3); do
476+ echo "Applying EF migrations (attempt ${attempt}/3)..."
477+ if dotnet ef database update \
478+ --project apps/portal/src/SEBT.Portal.Infrastructure/SEBT.Portal.Infrastructure.csproj \
479+ --startup-project apps/portal/src/SEBT.Portal.Api/SEBT.Portal.Api.csproj \
480+ --configuration Release \
481+ --no-build \
482+ --connection "$CONN"; then
483+ break
484+ fi
485+ if [ "$attempt" -eq 3 ]; then
486+ echo "EF migrations failed after 3 attempts"
487+ exit 1
488+ fi
489+ sleep 5
490+ done
491+
492+ - name : Run Playwright integration E2E tests
493+ env :
494+ CI : true
495+ E2E_FULL_STACK : " 1"
496+ SKIP_WEB_SERVER : " 1"
497+ STATE : co
498+ NEXT_PUBLIC_STATE : co
499+ BACKEND_URL : http://localhost:5280
500+ # No Mailpit in the CO stack — the checker/DC OTP flow doesn't run here.
501+ MAILPIT_API_URL : " "
502+ KEYCLOAK_DISCOVERY_URL : http://localhost:8180/realms/sebt/.well-known/openid-configuration
503+ ASPNETCORE_ENVIRONMENT : Development
504+ ConnectionStrings__DefaultConnection : " Server=localhost,1433;Database=SebtPortal;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;"
505+ JwtSettings__SecretKey : " ci-e2e-jwt-secret-at-least-32-characters-long"
506+ IdentifierHasher__SecretKey : " ci-e2e-identifier-hasher-key-32chars"
507+ # The CO overlay expects TLS Redis; the single-instance e2e stack uses
508+ # the in-memory stores instead.
509+ Redis__Host : " "
510+ Oidc__DiscoveryEndpoint : http://localhost:8180/realms/sebt/.well-known/openid-configuration
511+ Oidc__ClientId : sebt-portal
512+ Oidc__ClientSecret : sebt-portal-dev-secret
513+ Oidc__CallbackRedirectUri : http://localhost:3000/callback
514+ Oidc__CompleteLoginSigningKey : " ci-e2e-oidc-signing-key-at-least-32-chars"
515+ Oidc__StepUp__DiscoveryEndpoint : http://localhost:8180/realms/sebt/.well-known/openid-configuration
516+ Oidc__StepUp__ClientId : sebt-portal-stepup
517+ Oidc__StepUp__ClientSecret : sebt-portal-stepup-dev-secret
518+ PluginAssemblyPaths__0 : " plugins-co"
519+ UseMockHouseholdData : " true"
520+ Cbms__UseMockResponses : " true"
521+ run : |
522+ # The CO overlay (IAL requirements, phone-based household ids, seeding)
523+ # ships as an example file; the env above overrides its Redis/OIDC bits.
524+ cp apps/portal/src/SEBT.Portal.Api/appsettings.co.example.json apps/portal/src/SEBT.Portal.Api/appsettings.co.json
525+ ASPNETCORE_ENVIRONMENT=Development dotnet run --project apps/portal/src/SEBT.Portal.Api --launch-profile http --configuration Release --no-build &
526+ (cd apps/portal/src/SEBT.Portal.Web && pnpm start) &
527+ echo "Waiting for API at http://localhost:5280/health..."
528+ for i in $(seq 1 90); do
529+ curl -sf --max-time 15 http://localhost:5280/health > /dev/null && echo "API ready" && break
530+ sleep 2
531+ done
532+ curl -sf --max-time 15 http://localhost:5280/health > /dev/null || (echo "API failed to start" && exit 1)
533+ echo "Waiting for web app at http://localhost:3000..."
534+ for i in $(seq 1 90); do
535+ curl -sf --max-time 15 http://localhost:3000 > /dev/null && echo "Web ready" && break
536+ sleep 2
537+ done
538+ curl -sf --max-time 15 http://localhost:3000 > /dev/null || (echo "Web failed to start" && exit 1)
539+ echo "Running Playwright integration E2E tests..."
540+ pnpm ci:test:e2e:integration:co
541+
542+ - name : Upload Playwright integration report
543+ uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
544+ if : ${{ always() }}
545+ with :
546+ name : playwright-integration-report-co
547+ path : apps/portal/src/SEBT.Portal.Web/playwright-report/
548+ retention-days : 7
549+
315550 # Pa11y runs the full stack (real API + web), so it reacts to both areas.
316551 a11y :
317552 name : Pa11y Accessibility Tests
0 commit comments