Fix a chain of QA CI issues: Docker build/health-check flakiness, NuGet vuln pins, then adds the post-deploy integration + Playwright smoke suite and works through everything needed to make it actually run on the self-hosted qa runner (musl/Alpine job container, no node/dotnet/curl preinstalled, docker-outside-of-docker networking).
Adds a fixed dev/QA seed admin user + fixed Development environment API key so integration tests and e2e specs have a stable target.
Pins Aspire's AppHost.cs ports/credentials to match the docker-compose local dev defaults.
Adds tests/api/MicCheck.Api.Tests.Integration coverage for disabled flags, environment-document bootstrap, identity override precedence, auth login, and unauthorized access; adds a Playwright e2e suite under src/admin/e2e (login, nav, context selection, features CRUD/toggle).
Adds a smoke-qa CI job that runs both suites against the just-deployed QA stack, working around: no curl/node/dotnet on the bare runner, musl vs glibc (Playwright browsers run via the official mcr.microsoft.com/playwright image instead), and the runner's job-container network isolation (reach the QA stack via the docker bridge gateway IP; docker cp instead of a bind mount to get files into the playwright container, since paths don't cross the docker-outside-of-docker boundary).
Test plan
Unit tests pass (dotnet test tests/api/MicCheck.Api.Tests.Unit, 243 passed)
API integration suite passes against the real QA stack in CI
Playwright e2e suite passes against the real QA stack in CI (verified 3/3 locally against a real dev API + vite server for the flakiest spec)
Full CI pipeline (build → deploy-qa → smoke-qa) green end to end on the qa runner
## Summary
- Fix a chain of QA CI issues: Docker build/health-check flakiness, NuGet vuln pins, then adds the post-deploy integration + Playwright smoke suite and works through everything needed to make it actually run on the self-hosted `qa` runner (musl/Alpine job container, no node/dotnet/curl preinstalled, docker-outside-of-docker networking).
- Adds a fixed dev/QA seed admin user + fixed Development environment API key so integration tests and e2e specs have a stable target.
- Pins Aspire's `AppHost.cs` ports/credentials to match the docker-compose local dev defaults.
- Adds `tests/api/MicCheck.Api.Tests.Integration` coverage for disabled flags, environment-document bootstrap, identity override precedence, auth login, and unauthorized access; adds a Playwright e2e suite under `src/admin/e2e` (login, nav, context selection, features CRUD/toggle).
- Adds a `smoke-qa` CI job that runs both suites against the just-deployed QA stack, working around: no curl/node/dotnet on the bare runner, musl vs glibc (Playwright browsers run via the official `mcr.microsoft.com/playwright` image instead), and the runner's job-container network isolation (reach the QA stack via the docker bridge gateway IP; `docker cp` instead of a bind mount to get files into the playwright container, since paths don't cross the docker-outside-of-docker boundary).
## Test plan
- [x] Unit tests pass (dotnet test tests/api/MicCheck.Api.Tests.Unit, 243 passed)
- [x] API integration suite passes against the real QA stack in CI
- [x] Playwright e2e suite passes against the real QA stack in CI (verified 3/3 locally against a real dev API + vite server for the flakiest spec)
- [x] Full CI pipeline (build → deploy-qa → smoke-qa) green end to end on the qa runner
Gitea runner build failed with "dotnet: command not found". Added
ensure_dotnet() to lib.sh, using the vendored dotnet-install.sh to
bootstrap the SDK into .dotnet/ when not already on PATH, called from
build.sh/test.sh/prepush.sh. Also trigger CI on build-runner-fix to
verify the fix.
Microsoft.AspNetCore.OpenApi 10.0.5 pulls in Microsoft.OpenApi 2.0.0
transitively, which has a known high-severity vulnerability
(GHSA-v5pm-xwqc-g5wc) and fails the build with TreatWarningsAsErrors.
Pinned a direct reference to the patched 2.9.0 (still 2.x, API-compatible).
Aspire.Hosting.PostgreSQL/JavaScript pull in MessagePack 2.5.192
transitively, which has multiple known vulnerabilities and fails the
build with TreatWarningsAsErrors. Pinned a direct reference to the
patched 2.5.302 (same major, no API break).
Dockerfile.ci COPYs full project directories after restoring inside
the container. Without a .dockerignore, the host's obj/bin (built
with a different local SDK than the container's) got copied over the
container's fresh restore output, corrupting project.assets.json and
crashing ResolvePackageAssets with a NullReferenceException on publish.
Self-hosted qa runner has no node in PATH, so the Node-based
actions/checkout@v4 action can't run there. Swap it for a bash git
fetch/checkout, matching the pipeline's no-marketplace-action convention.
aspnet base image ships neither curl nor wget, so the compose
healthcheck's wget call failed silently at the exec level (visible only
in docker's health-check log, not app stdout). Install curl in the
Dockerfile.ci final stage and switch the healthcheck to use it.
The app maps health checks at /health (ServiceDefaults), not /api/v1/health.
Compose healthcheck and deploy-qa.sh's wait-loop both hit the wrong path.
Also add an nginx location for /health since it lives outside the /api
prefix that the admin proxy otherwise forwards unchanged.
The self-hosted runner runs in its own container on a separate bridge
network, so curling localhost:$QA_ADMIN_PORT hit the runner's own loopback
instead of the docker host's published port, never the QA stack.
Exec into the admin container and curl its own localhost instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Existing coverage was one integration test and zero e2e tests. Adds the thin,
high-value integration/e2e layer unit tests can't reach (real Postgres wire
contract, real browser flows) and wires it as a post-deploy smoke gate.
- Seed a deterministic dev/QA admin user and fixed Development env key so
HTTP-only tests can authenticate without per-run registration.
- Expand the API integration suite: disabled-flag, unauthorized access,
environment-document bootstrap, identity-override precedence, and auth
login scenarios, reusing the existing black-box HTTP+Npgsql harness.
- Add a Playwright suite for the admin SPA: login, nav, project/environment
context selection, and feature create/toggle, against the real API.
- Bind QA Postgres to 127.0.0.1 for direct seeding, add smoke-qa.sh and an
ensure_node() bootstrap (the qa runner has no Node today), and wire a new
smoke-qa CI job after deploy-qa.
Aspire assigned random ports and an auto-generated Postgres password on each
run, so the fixed targets baked into local.runsettings and docker-compose
(localhost:5000/5173/5432, miccheck/password) didn't work when developing via
Aspire instead of docker compose. Pins Postgres user/password/port and the
api/admin HTTP endpoints so either workflow hits the same local addresses.
The self-hosted qa runner's minimal image lacked libstdc++/libgcc, which
dotnet fails on with an obscure symbol-relocation error rather than a
clear "missing dependency" message.
The runner executes each job in its own container on docker's default bridge
network, so "localhost" never resolves to the docker host - smoke-qa's direct
Postgres connection and Playwright/API HTTP calls need the bridge gateway IP
instead. Bind the db port to that same gateway IP (not 0.0.0.0) so it stays
reachable only from sibling containers, not off-box.
Same runner dotnet-install.sh detects as musl and picks linux-musl-x64 for -
the glibc node tarball can't even exec there ("env: can't execute 'node'").
Playwright's bundled Chromium is glibc-only and --with-deps assumes apt -
both fail on this musl/Alpine runner. Run the e2e leg in
mcr.microsoft.com/playwright (browsers preinstalled, pinned to the exact
resolved @playwright/test version) instead, reachable at the same bridge
gateway IP as the rest of the QA stack.
npx resolved the registry's generic "playwright" package instead of the
locally installed @playwright/test bin, triggering an unwanted fresh install
and "No tests found". npm run e2e uses the project's own node_modules/.bin
unambiguously.
This script runs inside the runner's own job container and talks to the
host's docker daemon over a mounted socket (docker-outside-of-docker). A
bind mount path only exists inside this job container, not on the host the
daemon resolves it against, so docker run -v failed with ENOENT. docker cp
copies file contents instead, sidestepping the path mismatch.
When the feature-state map isn't loaded yet for a row, the click handler
no-ops silently and the native checkbox briefly flashes toggled before Vue
snaps it back to the unchanged model-value - reading as success for an
instant even though nothing was sent. Wait for the actual PATCH response
so the test fails clearly instead of passing then flaking on reload.
Verified 3/3 locally against a real dev API + vite server.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
qarunner (musl/Alpine job container, no node/dotnet/curl preinstalled, docker-outside-of-docker networking).AppHost.csports/credentials to match the docker-compose local dev defaults.tests/api/MicCheck.Api.Tests.Integrationcoverage for disabled flags, environment-document bootstrap, identity override precedence, auth login, and unauthorized access; adds a Playwright e2e suite undersrc/admin/e2e(login, nav, context selection, features CRUD/toggle).smoke-qaCI job that runs both suites against the just-deployed QA stack, working around: no curl/node/dotnet on the bare runner, musl vs glibc (Playwright browsers run via the officialmcr.microsoft.com/playwrightimage instead), and the runner's job-container network isolation (reach the QA stack via the docker bridge gateway IP;docker cpinstead of a bind mount to get files into the playwright container, since paths don't cross the docker-outside-of-docker boundary).Test plan
Same runner dotnet-install.sh detects as musl and picks linux-musl-x64 for - the glibc node tarball can't even exec there ("env: can't execute 'node'").