Files
DodoSSH/.github/workflows/ci.yml
T
jaap-jan 34304b989b Make the end-to-end suite self-contained with Testcontainers
It needed a hand-started stack and an opt-in flag, so it ran on one machine
and never in CI. It now brings up PostgreSQL, Keycloak and an OpenSSH server
itself, applies the committed migrations and starts the API as a child
process, which makes it part of the ordinary test run at ~25s.

The API runs as a process rather than through WebApplicationFactory. The
client builds its own HttpClient for a URL the user typed, so there is no
seam to hand a test handler through without inventing one that exists only
for tests — and a test host would replace the entry point, Kestrel and the
content root, so it would never prove that Program.cs composes or that the
committed appsettings is found and layered in the documented order. Running
out of the API's own output directory is what makes its configuration real.

The suite still consumes what ships: the realm file from deploy/keycloak,
the EF migrations, the API's own appsettings. Only Oidc:Authority is
overridden, because the container's port is assigned at start. Falsified by
reintroducing the wildcard-port redirect URI the realm once had — Keycloak
rejects the authorization request and the suite fails at sign-in, which is
what proves the committed file is the one imported. Skipping the migration
step likewise fails, and the failure names the pending migration.

A fresh Keycloak per run also sidesteps the --import-realm trap: editing the
realm file and rerunning now always tests the edit.

DodoDbContextFactory gains a Create(connectionString) so the fixture and
dotnet ef place the migrations history table in exactly one place. If they
disagreed the API would report every migration pending, which is how the
readiness gate catches it.
2026-07-29 12:09:15 +02:00

76 lines
2.5 KiB
YAML

name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
# Actions are pinned to commit SHAs, not tags: a tag can be moved to point at new code,
# which would let a compromised action run with this workflow's permissions.
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
CI: true
jobs:
build:
name: build and test (ubuntu)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
cache-dependency-path: '**/packages.lock.json'
# Locked mode fails if packages.lock.json does not match the project files, so a
# dependency cannot change without the lock file change being reviewed.
- name: restore
run: dotnet restore DodoSSH.slnx --locked-mode
- name: verify formatting
run: dotnet format DodoSSH.slnx --verify-no-changes --no-restore
- name: build
run: dotnet build DodoSSH.slnx --no-restore --configuration Release
- name: test
run: dotnet test DodoSSH.slnx --no-build --configuration Release
# This includes the end-to-end suite, which starts PostgreSQL, Keycloak and an OpenSSH
# server through Testcontainers and runs the API as a child process — so it needs a
# Docker daemon and gets one here. That is why the tests run on ubuntu rather than
# macOS, whose runners have no daemon at all. Expect the Keycloak image pull to
# dominate a cold run.
build-windows:
name: build (windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: true
cache-dependency-path: '**/packages.lock.json'
- name: restore
run: dotnet restore DodoSSH.slnx --locked-mode
# Build only. Day-to-day development happens in Rider on Windows, so a
# Windows-specific compile break must fail CI even though the tests run on Linux.
- name: build
run: dotnet build DodoSSH.slnx --no-restore --configuration Release