Testing
Tests exercise public behavior, not private methods or framework implementation details. They run locally and in CI without starting Aspire, opening network ports, trusting certificates, using Docker, or supplying production credentials. See developer setup for SDK prerequisites.
Project boundaries
| Project | Responsibility |
|---|---|
tests\OpenGameBuilder.Api.Tests |
In-process HTTP integration tests of the real API, including a real API-client round trip |
tests\OpenGameBuilder.Api.Client.Tests |
Client registration, configuration, JSON contracts, HTTP/transport failures, and cancellation |
The existing deployment smoke below checks published frontend startup and a real API round trip in Chromium. Component tests and broader browser coverage for loading, success, network failure, and invalid responses accompany the first functional frontend feature. See the browser and accessibility matrix for recorded evidence, unverified targets, and the manual checks required as the editor develops.
tests/Directory.Build.props imports the
repository-wide build properties and supplies the common test flags, xUnit
reference, global using, and Visual Studio adapter. Each test project declares
only its own production references and additional dependencies. Package versions
remain in Directory.Packages.props, and the
same compiler warnings-as-errors policy applies to tests and production code.
Frontend asset cleanup evidence
On 2026-09-22, source inspection found no consumers for the removed form-validation,
Bootstrap placeholder, or code styles in
app.css.
Loading and error styles remain. C# and startup HTML changes were limited to
comments; active service defaults, endpoints, and launch profiles were preserved.
Restore, format verification, Release build (zero warnings), all 72 .NET tests,
frontend Release publish, and the published-configuration guard passed. Local
documentation paths and anchors also resolved. This establishes local cleanup
and packaging evidence, not a new browser or accessibility acceptance result.
CodeQL build coverage
The repository-owned CodeQL workflow traces
a real Release build of the solution for C#, including Razor-generated sources.
It installs the SDK selected by global.json, restores without a persistent
dependency cache, and forces a non-incremental build after initializing CodeQL.
This avoids the synthetic Razor compilation used by no-build extraction, which
logged a compiler exit-code error even in successful analyses. Actions analysis
still uses no-build mode because it does not compile C#.
A passing local Release build does not prove CodeQL extraction or upload. Check the first hosted PR run's C# build, analysis output, and source coverage after changing this workflow. Do not treat a green check as proof that its logs contain no extraction errors.
GitHub's separately managed CodeQL - Code Quality workflow is configured outside this repository. Its documented settings do not expose the same manual build-mode control. Keep that coverage enabled; if its synthetic Razor compiler diagnostic persists, retain the run URL for GitHub Support rather than disabling analysis or claiming this repository change fixed that managed workflow. See Code Quality configuration.
CI and deployment validation
CI and deployment validation use the same validation action and canonical commands. Start with the local equivalents:
pwsh ./scripts/check.ps1 quick
pwsh ./scripts/check.ps1 full
pwsh ./scripts/check.ps1 full -Serial
See first-party content checks for the pinned tool setup,
formatter ownership, offline link checks, and separate external-link reports.
content runs that gate without requiring .NET or deployment tools. full
includes it, its deliberate-defect regression tests, and the CI selection/gate
regressions in tests/ci-policy.
PR lanes and the required gate
CI starts for every PR, with no workflow-level path filter. Its selection job
compares the full Git merge-base-to-head range with rename detection disabled,
so both sides of a rename are checked. Only changes consisting entirely of
Markdown files at the repository root or under docs/ select the documentation
lane. An empty range, any other path, patch-branch push, or manual run selects
full validation. A failed comparison fails selection and the required gate;
it cannot silently skip validation. The selector logs the paths and decision.
| Selection | Required validation |
|---|---|
| Documentation | Ubuntu runs check.ps1 content and check-docs.ps1: source formatting, lint, workflow/shell checks, local Markdown links, and the DocFX site with rendered link/anchor and search checks. No application solution build or container runs. |
| Full | Windows runs check.ps1 quick -Serial for locked restore, C# format, Release build, and tests. Ubuntu runs check.ps1 full and check-docs.ps1, then builds and checks the API container. |
Both platforms restore and build the entire solution, including the AppHost,
and run both test projects. Only the shipped API and Web Client have committed
NuGet locks. Tests and the local-only AppHost resolve their dependencies normally;
their transitive graphs are not frozen. See the
lockfile policy.
The shared action installs the exact SDK in a fresh runner-temporary directory
using DOTNET_INSTALL_DIR.
This prevents preinstalled Visual Studio workload manifests from selecting an
older WebAssembly pack and breaking locked restore despite a matching SDK version.
Shell suites, frontend packaging, smoke-package checks, and Docker runtime checks
stay in the Linux lane. Deployment still uses the full shared action, with its
separate required frontend packaging job. Neither path starts Aspire.
The stable build-test check aggregates selection and all three possible
lanes. Its always() job condition lets it evaluate failed or skipped dependencies,
as described by GitHub's job dependency rules.
It requires selection and every selected lane to report success, and each
unselected lane to report skipped. Failure, cancellation, missing results, and
unexpected skips fail the gate. Whole-run cancellation or runner loss may stop
the gate itself; neither supplies a passing required check. Require build-test
in branch protection, not the conditional lane names.
Run the selector and aggregate regression checks independently with:
node --test tests/ci-policy/check.test.mjs
These tests exercise real temporary Git histories and lane-result combinations; they do not prove GitHub scheduling or merge enforcement. The hosted acceptance procedure is in GitHub setup.
Documentation site validation
Run pwsh ./scripts/check-docs.ps1 after installing the content prerequisites.
It restores the pinned DocFX tool, builds the existing guides with warnings as
errors, checks rendered links and anchors offline, and verifies search entries
and edit links against the original Markdown. Regression fixtures require an
unresolved document, missing rendered page, renamed anchor, missing stylesheet,
missing search entry, and accidentally included temporary plan to fail.
Additional fixtures check relative page links and repository-source links at two
different commits, reject omitted documentation and assets, and preserve the
source Markdown. See the link resolution rules.
Both selected Ubuntu lanes run this command through the documentation action;
the required build-test check cannot pass when it fails. The ci-docs artifact
retains the rendered site and available logs for seven days. The command does
not start an application or browser. See documentation maintenance
for preview, publication, and the separate hosted acceptance procedure. It is
separate from check.ps1 full, so application deployment validation does not
acquire an unrelated site build.
Recorded platform CI acceptance
The clean hosted run
at 1bb104c passed both Windows and Linux lanes and the required build-test
aggregate on 2026-09-22. Each platform passed locked restore, format verification,
a Release solution build, and all 72 .NET tests. Linux also passed content and
policy regressions, frontend packaging, all five shell suites, and the API image
runtime/liveness check. The temporary failing test is absent from this revision.
| Platform | Runner image | Selected SDK |
|---|---|---|
| Windows 10.0.26100 | win25-vs2026 20260907.229.1 |
10.0.401 |
| Ubuntu 24.04.5 LTS | ubuntu24 20260907.300.1 |
10.0.401 |
On 2026-09-22, the Windows restore failure
demonstrated that a failed required lane makes build-test fail even when Linux
passes. The pinned SDK was 10.0.401, but the preinstalled Windows workload
manifests requested WebAssembly pack 10.0.11 instead of the locked 10.0.12.
The shared action now installs the SDK in a fresh temporary directory to avoid
that machine-level input. Lockfiles were not regenerated to accept the mismatch.
The seven-day Windows diagnostic artifact retained the restore error.
The documentation-only run
passed content checks and build-test while both build lanes were skipped.
Its temporary PR
targeted the implementation branch and was closed without merging.
Thirteen local policy regression groups cover Git change selection, failures,
cancellation, missing or unexpected results, and workflow wiring. Cancellation
coverage is deterministic regression evidence, not a hosted cancellation rehearsal.
GitHub's main rule still requires build-test from Actions app ID 15368;
gh pr checks 117 --required reported the failed aggregate as required.
The draft PR also reported BLOCKED; draft status is an additional independent
merge blocker. These checks do not establish Visual Studio/VS Code F5 support,
browser acceptance, deployment behavior, or a new non-bypass contributor rehearsal.
Local Windows validation also passed restore, format, a zero-warning Release build, all 72 tests, the content gate and its seven regression groups, thirteen CI policy groups, frontend publish/portability, smoke-package installation/syntax, and all five isolated shell suites. This local evidence is separate from the hosted runs above.
Shared command coverage and diagnostics
quick is the normal solution gate: quick doctor checks, locked restore,
format verification, Release build, and the current 72 tests. full includes
that gate plus frontend Release publish and its portability guard, npm ci and
the smoke-script syntax check, and all five existing Bash script suites. It
requires Git for Windows/Git Bash, Git, Node.js 22/npm, and the Docker CLI with
Compose support. It does not require a Docker daemon, deployment credentials,
services, or a browser.
Use -Serial when Windows task-host or pipe contention affects validation. It
serializes restore, build, and publish work and disables MSBuild node reuse;
the checks and their scope are otherwise unchanged.
Full PR validation also publishes the frontend in Release using the completed build
and runs the portability guard. Deployment
disables this extra publish in the shared action because its required package
job already publishes and checks the frontend before any deployment job runs.
Both paths reject environment-specific configuration in the published artifact.
The Node portion of full installs the locked smoke dependencies and parses
smoke.mjs; it does not install or launch a browser. These build/package checks
need no deployment credentials or public URL and do not establish browser
acceptance. The deployment job installs its dependencies and Chromium on its own
runner before activation. Live browser smoke runs after activation and can
trigger recovery if it fails.
A failing phase fails its lane and the required gate. Available console logs and a formatting report
are uploaded on failure and retained for seven days; assertion details are in
test.log. The shared action also captures web-publish.log,
web-configuration.log, smoke-dependencies.log, and smoke-syntax.log for
these checks when they run. environment.log records the OS, architecture,
runner image when available, command mode, and serialization setting;
doctor.log records selected SDK, PowerShell, and relevant tool versions.
Artifacts are named per lane and run attempt. API image runtime failures retain
api-image.log separately because that check follows the shared action. The
test command uses the repository's
Microsoft.Testing.Platform runner without adding a separate test-reporting dependency.
The five Bash suites exercised by full are release-scripts, deploy-edge,
deploy-topology, deploy-app, and supply-chain. Run an individual suite
with Git Bash when working on it, for example:
& 'C:\Program Files\Git\bin\bash.exe' tests/release-scripts/run.sh
The release-script suite creates temporary local Git repositories, mocks GitHub CLI calls and Git pushes, and never publishes a branch, tag, or release. CI and deployment validation run it after the solution tests and upload its log on failure.
The host-edge apply script also has an isolated Bash test:
& 'C:\Program Files\Git\bin\bash.exe' tests/deploy-edge/run.sh
It mocks Docker and verifies invalid-candidate rejection, Caddyfile-only reload,
failed-reload restoration, intentional Compose updates, first-time setup, and
recovery of a stopped edge even when the candidate files are unchanged.
It also verifies environment/profile ownership and explicit migration approval.
CI runs it without SSH, Docker, deployment credentials, or service changes.
Live staging and production availability must still be checked by the deployment
smoke tests after the workflow change reaches main.
Host topology has a separate gate requiring the Docker Compose CLI, but not a running Docker daemon, SSH, or deployment credentials:
& 'C:\Program Files\Git\bin\bash.exe' tests/deploy-topology/run.sh
It renders shared, staging-only, and production-only candidates and checks real Compose normalization, image pins, stable certificate volumes, relative mounts, and absence of the other environment from isolated profiles. Invalid/missing profiles fail closed. Mocked curl tests ensure the edge readiness check targets the selected SSH host even before DNS cutover. CI/deployment validation run it; ordinary .NET tests still do not require Docker.
Application activation and rollback have an isolated host-script test:
& 'C:\Program Files\Git\bin\bash.exe' tests/deploy-app/run.sh
It uses temporary releases and a mocked Docker command to check legacy
migration, asset retention, rollback, API startup failure, and archive rejection.
First-deployment cases cover partial startup failure, recovery after activation
(the browser-smoke failure boundary), successful retry, cleanup failure, and
missing or corrupt expected predecessor state. These checks run without SSH or
Docker services; they do not establish live browser or staging acceptance.
Deployment additionally runs a Chromium smoke test from tests/deploy-smoke that
loads the published frontend, observes its API request, and checks the expected
source revision. That live test requires a deployed staging or production URL.
Reproducible command validation
On 2026-09-22, a fresh source snapshot passed pwsh ./scripts/check.ps1 full -Serial
with SDK 10.0.401, PowerShell 7.6.5, and Node.js 22.23.2/npm 10.9.9. This covered
locked restore, format verification, a Release build with zero warnings, all 72
.NET tests, frontend Release publish and its portability guard, locked smoke
dependencies and syntax, and all five isolated shell suites. The serialized
option avoided a Windows MSBuild task-host failure; it did not omit checks.
Deliberately missing shipped-application locks and changed package requirements stop restore before build. Doctor fixtures rejected a missing SDK, wrong SDK selection/policy, Node 24, and non-exact or mismatched Playwright pins. Missing smoke URL inputs failed before launching Chromium. PowerShell/YAML parsing, changed documentation targets/anchors, and diff checks passed.
These were local command checks. Later hosted platform evidence is recorded in platform CI acceptance. No services, deployments, certificate trust changes, or editor rehearsals were performed for this local validation.
Shipped-application lock policy validation
The narrower policy was tested against a fresh copy of the failing
PR #115 CI revision
with its OpenTelemetry 1.19.1 update. Removing the test and AppHost lock opt-ins
and their four locks made solution restore pass with both Windows and
Linux-selected SDK RIDs. These restores ran on Windows; they are not Linux-host
execution evidence. The same snapshot passed check.ps1 quick -Serial, including
format verification, a Release build with zero warnings, and all 72 tests.
The implementation also passed check.ps1 full -Serial locally: the solution
gate, content checks and regressions, frontend publish/portability checks,
smoke-package checks, and all five isolated shell suites. Both committed
production lockfiles remained unchanged.
Missing API and Web Client locks were each rejected before restore could regenerate them. A deliberately stale API dependency was rejected with NU1004. No test or AppHost lockfiles were regenerated. These checks verify that the shipped applications retain their lock guards while development graphs restore normally; they do not guarantee that every future Dependabot update succeeds.
Browser-smoke dependency updates
pwsh ./scripts/check.ps1 browser runs the existing
smoke.mjs Chromium test against an
authorized, already deployed HTTPS release. It requires the pinned Playwright
Chromium browser to be installed and these environment variables:
$env:SMOKE_TEST_BASE_URL = 'https://authorized-release.example'
$env:EXPECTED_SOURCE_SHA = '<source-sha>'
$env:EXPECTED_RELEASE_ID = '<release-id>'
pwsh ./scripts/check.ps1 browser
Install the pinned test dependency and Chromium explicitly when needed:
npm ci --prefix tests/deploy-smoke
node tests/deploy-smoke/node_modules/playwright/cli.js install chromium
The browser command never deploys, installs a browser, or installs operating
system packages. On Linux, --with-deps remains an explicit operating-system
installation decision, as in the workflow. This smoke is evidence for the
specified release only; it is not broader browser or hosted acceptance.
Dependabot checks /tests/deploy-smoke weekly,
using the repository's dependency-update cadence and cooldowns. Its npm entry
targets the directory containing both package.json and package-lock.json, as
described in GitHub's configuration reference.
After merging configuration changes, check GitHub's Dependabot update-job list
for that npm directory and inspect its first run for configuration errors.
For an intentional Playwright update, use the exact-version command below,
review the release notes and manifest/lockfile diff, then run full with Node.js 22. Dependency PRs receive the same required build-test validation.
npm install --save-dev --save-exact playwright@<version> --prefix tests/deploy-smoke
Package installation and
syntax checks do not establish compatibility with the updated Chromium build.
Review the browser smoke result from an authorized staging deployment: the
release URL and base path, successful /api/about request, expected source
revision, API-backed heading, and absence of page errors. Record that workflow
run separately from the package checks; if it has not run, browser acceptance
remains unverified.
Supply-chain declarations
Supply-chain declarations have an additional deterministic check:
& 'C:\Program Files\Git\bin\bash.exe' tests/supply-chain/run.sh
It rejects third-party Actions that are not full commit SHAs, mutable API base or
edge image references, a missing explicit API user, deployment-time
ssh-keyscan, inherited NuGet source mappings, or missing Dependabot ecosystems.
The npm declaration must cover /tests/deploy-smoke in the same update entry;
the check does not depend on a particular Playwright version.
It also checks that the API-image group patterns match Dependabot's normalized
dependency names (without their registry). CI loads the locally built API image and runs
scripts/verify-api-image.sh; that Docker-backed check verifies the runtime user,
application-directory permissions, startup, and /api/alive.
The stable required PR check is build-test. Workflow success alone does not
make it a merge gate: GitHub must require that check and enforce human review.
See GitHub setup for the inspected settings, administrator
configuration, and main/patch PR acceptance procedure.