Public Access
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.
This commit is contained in:
@@ -7,34 +7,60 @@
|
||||
Nothing here is stubbed. Every other suite in the repository substitutes something — a stubbed identity
|
||||
provider, an in-memory vault server, an in-memory cache — and each of those substitutions is a place a
|
||||
misunderstanding of the protocol can hide on both sides at once. This suite exists to catch exactly
|
||||
that, and it already has: the realm file registered a loopback redirect URI that Keycloak rejects,
|
||||
which no stub would ever have noticed.
|
||||
that, and it already has: the realm file registered a loopback redirect URI that Keycloak rejects, and
|
||||
the API never applied the contract's JSON settings, which put the whole sync surface out of reach of
|
||||
the real client. No stub would have noticed either.
|
||||
|
||||
It needs the development stack up and is skipped otherwise. The commands are in the README under
|
||||
"End-to-end verification"; a skipped run prints them too. (They cannot be repeated here: an XML
|
||||
comment may not contain a double hyphen, and every one of them has a flag.)
|
||||
It needs a Docker daemon and nothing else. See DevStack for what it starts and why.
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
Exit code 8 is "no tests ran". Microsoft.Testing.Platform reports that as a failure, so an assembly
|
||||
whose only test skips would fail the solution-wide `dotnet test` on any machine without the stack up.
|
||||
The minimum-expected-tests option cannot express this: it demands a non-zero integer.
|
||||
|
||||
Narrow enough to be safe here: this project contains exactly one test, so if it runs at all it either
|
||||
passes or fails, and 8 can only mean it skipped for want of the stack.
|
||||
-->
|
||||
<TestingPlatformCommandLineArguments>--ignore-exit-code 8</TestingPlatformCommandLineArguments>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../src/DodoSSH.Client.Session/DodoSSH.Client.Session.csproj" />
|
||||
<ProjectReference Include="../../src/DodoSSH.Client.Ssh/DodoSSH.Client.Ssh.csproj" />
|
||||
<ProjectReference Include="../../src/DodoSSH.Client.Terminal/DodoSSH.Client.Terminal.csproj" />
|
||||
|
||||
<!-- Applies the committed migrations to the throwaway database, as `dotnet ef database update` would. -->
|
||||
<ProjectReference Include="../../src/DodoSSH.Infrastructure/DodoSSH.Infrastructure.csproj" />
|
||||
|
||||
<!--
|
||||
Built but not linked. The API runs as a child process out of its own output directory so that it
|
||||
reads its own committed appsettings and executes its real entry point; referencing its assembly here
|
||||
would copy a DLL with no runtime configuration beside it and prove nothing. ReferenceOutputAssembly
|
||||
keeps the build dependency without the reference.
|
||||
-->
|
||||
<ProjectReference Include="../../src/DodoSSH.Api/DodoSSH.Api.csproj" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Testcontainers" />
|
||||
<PackageReference Include="Testcontainers.PostgreSql" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!--
|
||||
The realm the deployment ships, copied so the Keycloak container can be handed the real file rather
|
||||
than a fixture written to match it. A copy rather than a bind mount: Testcontainers hands the bytes
|
||||
to the daemon, which works the same whether the daemon is local, in a VM, or remote.
|
||||
-->
|
||||
<Content Include="../../deploy/keycloak/realm-dodossh.json"
|
||||
Link="realm-dodossh.json"
|
||||
CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Where the API's own build put it, asked of the API project rather than assembled from the bin layout.
|
||||
A changed output path then moves this with it instead of failing at run time with a path nobody wrote.
|
||||
-->
|
||||
<Target Name="DodoCaptureApiPath" BeforeTargets="GetAssemblyAttributes">
|
||||
<MSBuild Projects="../../src/DodoSSH.Api/DodoSSH.Api.csproj"
|
||||
Targets="GetTargetPath"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=$(TargetFramework)">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="DodoApiTarget" />
|
||||
</MSBuild>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyMetadata Include="DodoSSH.ApiAssemblyPath" Value="@(DodoApiTarget)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user