Test support Testcontainers Platform
Overview
ASOL.Core.QA.TestSupport.Testcontainers.Platform is the application-level orchestration package for integration-test topologies. It starts shared infrastructure, resolves service images, creates a private container network, starts application services, and exposes host-side runtime information.
Use the package when an integration test needs several AVA services or infrastructure containers to run as one topology. The lower-level ASOL.Core.QA.TestSupport.Testcontainers.* packages build individual containers and use a different lifecycle model. They are not direct replacements for Platform services.
Package selection
ASOL.Core.QA.TestSupport.Testcontainers.Platform- shared MongoDB, RabbitMQ, Redis, image resolution, generic application services, runtime metadata, andTestContainersWebApplicationFactory<TStartup>.ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp- optional Platform-native local Identity Provider service with generated client credentials, HTTPS for service containers, and mapped HTTP loopback access for the test host.ASOL.Core.QA.TestSupport.ApiService- host-sideLocalIdpClientTokenProviderandUseLocalIdpClientTokenProvider()for service-token acquisition from the local IDP.ASOL.Core.QA.TestSupport.Testcontainers.Idp- lower-level standalone IDP Testcontainers builder. Do not combine it with Platform as a replacement forPlatform.Idp.
The published package names use Testcontainers, matching the .NET library name. The configuration section remains TestContainers for backward compatibility.
Minimal configuration
Keep non-sensitive topology defaults in the integration-test appsettings.json:
{
"TestContainers": {
"ImagesPath": "./images",
"DumpsPath": "./dumps",
"MongoImage": "mongo:7.0.24",
"MongoSeederImage": "mongo:7.0.24",
"RabbitMqImage": "rabbitmq:3.13-management",
"RedisImage": "redis:7",
"RabbitMqUsername": "guest",
"RabbitMqPassword": "guest",
"MongoRepositoryUri": "913524905811.dkr.ecr.eu-central-1.amazonaws.com/ava/asol-testingdataprovider",
"IdentityProviderRepositoryUri": "913524905811.dkr.ecr.eu-central-1.amazonaws.com/ava/asol-identityprovider-be:latest-develop",
"SubjectManagerRepositoryUri": "913524905811.dkr.ecr.eu-central-1.amazonaws.com/ava/asol.subjectmanager.api:latest-release",
"PlatformStoreStoreRepositoryUri": "913524905811.dkr.ecr.eu-central-1.amazonaws.com/ava/asol.platformstore.store.api",
"AwsEcr": {
"Region": "eu-central-1"
}
}
}
Use user secrets or environment variables for machine-specific ECR credentials:
{
"TestContainers": {
"AwsEcr": {
"AccessKeyId": "",
"SecretAccessKey": "",
"SessionToken": ""
}
}
}
The corresponding environment variables are TestContainers__AwsEcr__AccessKeyId, TestContainers__AwsEcr__SecretAccessKey, and the optional TestContainers__AwsEcr__SessionToken. Do not keep application runtime credentials, remote alpha/beta authentication values, or copied local Mongo/Rabbit connection strings in user secrets. Platform generates or overrides those values for the current test run.
Platform setup
Derive the application test factory from TestContainersWebApplicationFactory<TStartup> and build the topology in CreateBuilderAsync:
using ASOL.Core.QA.TestSupport.Testcontainers.Platform;
using ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp;
using Microsoft.Extensions.Configuration;
protected override TestPlatformBuilder CreateBuilderAsync(CancellationToken ct = default)
{
static string Required(IConfiguration configuration, string key)
=> configuration[key]
?? throw new InvalidOperationException($"Required configuration '{key}' is missing.");
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile("appsettings.test.json", optional: true)
.AddUserSecrets<ConsumerTestAssemblyMarker>(optional: true)
.AddEnvironmentVariables()
.Build();
var builder = new TestPlatformBuilder(configuration)
.UseRestoredMongoImage();
var localIdpOptions = new LocalIdpOptions("ASOLEU-YourApp-AP-")
{
Audience = "apiim",
MongoSeederImage = Required(configuration, "TestContainers:MongoSeederImage")
};
var localIdp = builder
.AddLocalIdp(localIdpOptions)
.WithAwsRepository(Required(configuration, "TestContainers:IdentityProviderRepositoryUri"));
var credentials = localIdp.Credentials;
return localIdp.Done()
.AddService(
archiveFileName: "subject-manager",
configSection: "SubjectManagerClientOptions",
needsMongo: true,
internalPort: 5010,
health: "/health",
configure: service => service.WithCustomConfiguration(new Dictionary<string, string>
{
["SsoAuth__Authority"] = credentials.InternalAuthority,
["SsoAuth__RequireHttpsMetadata"] = "true",
["SsoAuth__Audience"] = "apiim"
}))
.WithAwsRepository(Required(configuration, "TestContainers:SubjectManagerRepositoryUri"))
.UseSeededClient(
"SubjectManagerClientOptions",
new ClientCredentialsSeedOptions { ClientId = "ASOLEU-SubjectManager-AP-" },
localIdpOptions)
.AddService(
archiveFileName: "store",
configSection: "PlatformStoreStoreClientOptions",
needsMongo: true,
internalPort: 5007,
health: "/health",
configure: service => service.WithCustomConfiguration(new Dictionary<string, string>
{
["SsoAuth__Authority"] = credentials.InternalAuthority,
["SsoAuth__RequireHttpsMetadata"] = "true",
["SsoAuth__Audience"] = "apiim"
}))
.WithAwsRepository(Required(configuration, "TestContainers:PlatformStoreStoreRepositoryUri"))
.UseSeededClient(
"PlatformStoreStoreClientOptions",
new ClientCredentialsSeedOptions { ClientId = "ASOLEU-Store-AP-" },
localIdpOptions);
}
The configure callback supplies authentication values that are specific to the current local IDP test run. InternalAuthority is the IDP address reachable from other containers on the Platform network, HTTPS metadata validation stays enabled and Platform supplies the public certificate through SSL_CERT_FILE, and the audience must match LocalIdpOptions.Audience. The service never declares its own credentials: SsoAuth__ClientId and SsoAuth__ClientSecret are seeded and injected by SeededClientStarter.
Per-service client identifiers
The Platform no longer shares one powerful client across the whole topology. Every participant authenticates with its own OIDC client and receives its own per-run secret; secrets are never derived from the client identifier and never checked in.
SeededClientStarter is the wrapper that gives a service container its credentials. It writes the secret of the client named by its ClientCredentialsSeedOptions into the OIDC application collection of the already started platform MongoDB, sets SsoAuth__ClientId and SsoAuth__ClientSecret for the service, and then delegates the container start to the starter it wraps. It never builds a container itself.
UseSeededClient() is the short form for a service that needs no special container setup:
builder.UseSeededClient(
"DataServiceClientOptions",
new ClientCredentialsSeedOptions { ClientId = "ASOLEU-DataService-AP-" },
localIdpOptions);
ClientCredentialsSeedOptions names one client: ClientId, plus an optional ClientSecret to pin a known value instead of generating a random per-run one. Pin it only when a consumer cannot read the secret back; every seeded secret is published as service runtime metadata and readable with GetClientSecret().
Where the seeds live is separate and belongs to the topology, not to each client: pass it as the store argument, an IOidcSeedStoreOptions carrying MongoDatabase, OidcApplicationsCollection, MongoSeederImage and SecretSizeBytes. LocalIdpOptions implements the interface, so pass the options you already gave to AddLocalIdp(). One seeder container is started per SeededClientStarter and reused for every client it seeds.
Seed each OIDC client from one SeededClientStarter only. Seeding the same client from several starters is not supported and leaves it in an undefined state: which secret is stored, and which services can still authenticate with it, is not guaranteed.
AddLocalIdp() wraps the Identity Provider's own starter the same way, and seeds two clients:
LocalIdpOptions.IdpClientId(defaultASOLEU-IDP-AP-) is the client the Identity Provider authenticates with. Its generated secret reaches that container and the service runtime metadata.- The
clientIdpassed to theLocalIdpOptionsconstructor is the caller client of the in-process test host, which has no container and therefore no starter of its own. Its identifier and generated secret are published onlocalIdp.Credentialsat registration time and onLocalIdpRuntimeInfoafter startup, so the host can configure its ownSsoAuthOptions.
The two identifiers must differ; AddLocalIdp() rejects equal values, because the seeds would otherwise overwrite each other.
UseSeededClient() wraps DefaultServiceContainerStarter, which builds the container exactly like the platform default, except that a failed start throws instead of being logged and skipped. Derive from it to change the readiness strategy or the way the container is built, for example when a service exposes its health endpoint behind a reverse proxy base path:
private sealed class ProxiedServiceStarter(string section) : DefaultServiceContainerStarter(section)
{
protected override IWaitForContainerOS BuildWaitStrategy(ServiceRegistration registration)
=> Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => request
.ForPath("/")
.ForPort((ushort)registration.InternalPort)
.ForStatusCodeMatching(_ => true));
}
A service with its own starter skips UseSeededClient() and registers SeededClientStarter around that starter instead:
builder.WithServiceContainerStarter(new SeededClientStarter(
new ProxiedServiceStarter("DataServiceClientOptions"),
new ClientCredentialsSeedOptions { ClientId = "ASOLEU-DataService-AP-" },
localIdpOptions));
A fourth argument seeds further clients that belong to no container, such as a platform service the topology does not run but a test authenticates as. They are seeded alongside the service client and never reach its environment:
builder.WithServiceContainerStarter(new SeededClientStarter(
new DefaultServiceContainerStarter("DataServiceClientOptions"),
new ClientCredentialsSeedOptions { ClientId = "ASOLEU-DataService-AP-" },
localIdpOptions,
new ClientCredentialsSeedOptions { ClientId = "ASOLEU-IDM-AP-" }));
Every secret a SeededClientStarter seeds is published as service runtime metadata, generated ones included. Read one with GetClientSecret():
var idmSecret = environment.GetClientSecret("ASOLEU-IDM-AP-");
It searches the metadata of every service and throws KeyNotFoundException when no starter seeded that client. Use it instead of updating an OIDC seed from the test: a seed written after startup replaces the secret the running client already received.
AddServiceConfiguration() still sets SsoAuth__ClientId and SsoAuth__ClientSecret from TestContainers:GeneralClientId and TestContainers:GeneralClientSecret, the single client the whole topology used to share. When a service also has a SeededClientStarter, the seeded per-service client wins and the startup log says which client was replaced:
[SeededClientStarter] Replacing the configured OIDC client 'ASOLEU-General-AP-' of DataServiceClientOptions with the seeded client 'ASOLEU-DataService-AP-'.
Prefer per-service seeded clients; the shared General* pair is the old model and is expected to go away.
The dictionary in the example contains only the local IDP authentication overrides. A consuming application normally combines these values with its existing service-specific environment configuration before passing the result to WithCustomConfiguration(). Applications can keep repeated AddService() registrations concise by performing that merge in their existing shared configuration helper. Apply these overrides to every containerized service that authenticates against the local IDP.
The example registers Subject Manager and Store in one fluent chain. Each WithAwsRepository() call configures the service added by the immediately preceding AddService() call. Continue the same pattern for the remaining services in the test topology.
ConsumerTestAssemblyMarker is any type declared in the consuming integration-test assembly. AddLocalIdp automatically requires Platform MongoDB and RabbitMQ. It wraps its starter in a SeededClientStarter, which generates a random per-run secret, verifies that exactly one matching OIDC seed exists and updates it before IDP startup, and then starts the IDP with HTTPS on its network alias and HTTP on the mapped host port. It generates a temporary certificate whose SAN contains that alias, maps its private PFX only into the IDP, and copies its public PEM into service containers. Startup waits for the OIDC discovery endpoint as well as the configured health endpoint.
Every configured client must exist exactly once in the restored MongoDB seed; startup fails and names the client when its seed is missing or duplicated. MongoSeederImage is pinned because it is a real temporary container used by the production starter, not only a test helper.
Local IDP runtime information
HTTPS compatibility and custom starters
LocalIdpOptions.InternalPort remains the HTTP port used by the test host (default 80). The new InternalHttpsPort defaults to 443. InternalAuthority now uses HTTPS and omits the default port, for example https://identityproviderclientoptions; a non-default HTTPS port remains explicit. The discovery document and token issuer use that same canonical authority. Ordinary service base URLs remain HTTP.
Consumers should use localIdp.Credentials for service authentication, keep SsoAuth__RequireHttpsMetadata=true, and remove custom TLS certificate generation or proxy/starter workarounds. Update the Platform and Platform.Idp packages together. Host-side HostAuthority, UseLocalIdpClientTokenProvider(), and UseLocalIdp() retain their mapped HTTP behavior.
The internal authority and issuer changes require migration for consumers that hard-code HTTP aliases: use the generated InternalAuthority and ValidIssuers instead. Custom starters that consume the platform environment must also apply the builder contributors as described below.
AddLocalIdp() registers environment and container builder contributors. The default service starter applies them automatically. A custom IServiceContainerStarter that calls context.BuildEnvironment() must also call context.ConfigureBuilder(builder) before Build(). When builder contributors are registered, Platform reports an omitted call with the starter type, service name and migration instruction, preserving the original exception if startup failed. If the starter returned a container, Platform disposes it before rejecting the result. Independent infrastructure starters that do not use the platform environment are unaffected. Other extensions can register IServiceContainerBuilderContributor through WithContainerBuilderContributor(); contributors execute in registration order and pass their returned builder to the next contributor.
One shared contributor copies the public certificates from all local IDP registrations into a single PEM bundle at /certs/core-local-idp.crt, selected using SSL_CERT_FILE. An explicit different SSL_CERT_FILE is rejected rather than silently replaced. Each generated private key is mapped only to its own IDP, is never written into repository files, and is not needed by consumers. This trust mechanism targets Linux service images whose TLS implementation honors SSL_CERT_FILE.
SSL_CERT_FILE replaces the default CA bundle file selection; it does not append the local certificates to the image's default bundle. Certificate directories remain available, and .NET on Linux reads PEM bundles in those directories as well as individual certificates. If a service also calls external HTTPS endpoints, ensure that the required public CAs remain accessible through the image's certificate directories. A minimal image alone does not imply missing trust; its actual certificate layout matters.
After Platform initialization, use GetLocalIdpRuntime() to configure the in-process test application:
var localIdp = Platform.GetLocalIdpRuntime();
configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["SsoAuth:Authority"] = localIdp.HostAuthority,
["SsoAuth:RequireHttpsMetadata"] = "false",
["SsoAuth:ClientId"] = localIdp.ClientId,
["SsoAuth:ClientSecret"] = localIdp.ClientSecret,
["IdentityProviderClientOptions:BaseUrl"] = localIdp.HostAuthority
});
ClientId and ClientSecret on the runtime information are the caller client of the test host, seeded by AddLocalIdp(). Secrets of service containers are not exposed there.
The runtime information contains:
HostAuthority- mapped loopback URL used by the test host.InternalAuthority- network alias URL used by service containers.Audience- service-token scope and audience configured for the local IDP.ValidIssuers- normalized internal and host issuer values for JWT validation.
Use InternalAuthority in service container environment values. Use HostAuthority from code running on the developer machine or build agent.
Host-side JWT bearer validation
The local IDP discovery document can advertise absolute endpoints that use its internal or HTTPS authority. Configure the test application's JWT bearer handler with UseLocalIdp() so host-side discovery and JWKS requests are routed through the mapped HostAuthority:
using ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp;
using Microsoft.AspNetCore.Authentication.JwtBearer;
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => options.UseLocalIdp(localIdp));
For an existing or named registration, apply the same options extension to that scheme:
services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme,
options => options.UseLocalIdp(localIdp));
Apply UseLocalIdp() during the regular options configuration phase. Do not call it from PostConfigure: the JWT bearer framework may already have created its ConfigurationManager with the previous backchannel, so later backchannel changes would not affect discovery and JWKS retrieval.
The extension disables HTTPS metadata only for the host-side HTTP loopback connection, applies the runtime Audience, preserves the existing TokenValidationParameters, adds the runtime audience and all runtime ValidIssuers, and keeps raw JWT claim names such as tid. Its non-static backchannel changes only the authority of absolute metadata endpoints; their path and query remain unchanged. This does not change token production: issued tokens continue to use InternalAuthority as their issuer.
Host-side token acquisition
The regular ClientTokenProvider loads OIDC discovery metadata. Local container topologies may publish an internal issuer while the test host reaches the IDP through a mapped port. LocalIdpClientTokenProvider avoids that mismatch by posting directly to {Authority}/connect/token.
Register it in the test startup after the standard API token providers:
services.AddStandardApiTokenProviders();
services.UseLocalIdpClientTokenProvider();
Configure SsoAuthOptions.Authority with HostAuthority, then configure JWT bearer authentication through JwtBearerOptions.UseLocalIdp(). The provider preserves Audience as the requested scope and adds tid when a valid tenant context is present. UseLocalIdpClientTokenProvider() creates a dedicated named host-side HttpClient; token acquisition never reuses the native client of an application connector or WebApplicationFactory.
Generic services and specialized starters
Use AddService for regular application services. A specialized package can register an IServiceContainerStarter through WithServiceContainerStarter when it needs custom startup while retaining Platform image resolution, environment contributors, network, and lifecycle.
Exactly one specialized starter may match a service registration. Platform throws InvalidOperationException when multiple starters match, instead of choosing by registration order.
Specialized starters can publish string metadata through ServiceContainerStartResult. The metadata is available from the corresponding ServiceRuntimeInfo.Metadata entry.
Image resolution behavior
WithAwsRepository accepts a repository URI or a full image reference:
- Explicit tag:
repository/image:latest-developpulls that exact tag. - Explicit digest:
repository/image@sha256:<digest>pulls that exact digest. - Tagless repository: Platform calls ECR
DescribeImages, orders byImagePushedAt, and selects the newest digest for backward compatibility.
A tagless repository URI is not a stable version pin. latest-develop and latest-release are moving branch tags, while build tags and digests are immutable references. The local IDP verification intentionally uses a tagless ava/asol-testingdataprovider repository so every run selects the newest database image by ImagePushedAt. The IDP itself uses latest-develop to select an image that supports the host-side HTTP listener as well as HTTPS.
If the exact tagged image already exists locally, Platform may use it without contacting ECR. Credential validity can therefore only be demonstrated when a remote pull is required. Always inspect ServiceRuntimeInfo.ResolvedImage, which contains the actual container image, when a test requires an exact tag.
MongoDB setup
Choose one MongoDB source:
UseRestoredMongoImage()resolves the configuredTestContainers:MongoRepositoryUriand uses a pre-restored database image.WithMongoDumpArchives()restores supported entries fromTestContainers:DumpsPathinto the regularMongoImagecontainer.
UseRestoredMongoImage() takes precedence and clears configured dump paths. The local IDP seed update runs against the resulting MongoDB instance before the IDP container starts.
Configuration precedence
The parameterless TestPlatformBuilder() loads appsettings.json, appsettings.Development.json, and appsettings.test.json, followed by environment variables and then AddUserSecrets(Assembly.GetExecutingAssembly()). The executing assembly is the Core Platform assembly, not automatically the consuming test project. This legacy order is retained for compatibility.
Applications should build and pass their own IConfiguration, as shown above. In the recommended example, JSON is followed by AddUserSecrets<ConsumerTestAssemblyMarker>() and then environment variables, so environment variables have the highest provider priority. The builder uses a supplied configuration as-is.
In-memory values later added by TestContainersWebApplicationFactory configure the in-process test application and are separate from the builder provider order. The factory adds host-side MongoDB, RabbitMQ, Redis, and service base URLs. It does not remove unrelated remote values. Remove stale alpha.avaplace.com and beta.avaplace.com settings or override them explicitly.
Explicit local IDP verification
The real MongoDB, RabbitMQ, and IDP topology is an explicit xUnit v3 test. Run it for net8.0 only:
The test project has its own UserSecretsId, ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests. Configure ECR credentials against that project without adding them to source-controlled JSON:
dotnet user-secrets set 'TestContainers:AwsEcr:AccessKeyId' '<access-key-id>' `
--project 'TestSupport/backend/ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests/ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests.csproj'
dotnet user-secrets set 'TestContainers:AwsEcr:SecretAccessKey' '<secret-access-key>' `
--project 'TestSupport/backend/ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests/ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests.csproj'
dotnet user-secrets set 'TestContainers:AwsEcr:SessionToken' '<session-token>' `
--project 'TestSupport/backend/ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests/ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests.csproj'
Set SessionToken only for temporary session credentials. Environment variables with the same configuration paths remain an alternative and have higher priority in this test's explicitly built configuration.
dotnet test 'TestSupport/backend/ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests/ASOL.Core.QA.TestSupport.Testcontainers.Platform.Idp.Tests.csproj' `
--configuration Release `
--framework net8.0 `
-- xUnit.Explicit=only
The explicit test starts a .NET service container through the default Platform starter. It exercises production Core discovery and client-credentials acquisition with standard TLS validation, verifies rejection of a hostname outside the certificate SAN, and validates both container-issued and host-acquired tokens through the host JWT bearer handler.
Normal test runs keep explicit tests disabled. The explicit test requires valid ECR access because resolving the tagless testing-data repository requires a remote metadata query. Missing clients, tenants, images, discovery metadata, signing keys, or token endpoints are test failures, not skips. The test independently verifies that the MongoDB container uses the newest digest returned by ECR.
When using TestPlatformBuilder directly, keep the builder outside the build scope so partially started infrastructure is also removed when BuildAsync fails:
var builder = new TestPlatformBuilder(configuration);
try
{
await using var environment = await builder.BuildAsync(CancellationToken.None);
// Verify the topology.
}
finally
{
await builder.DisposeAsync();
}
DisposeAsync() is idempotent, so the final cleanup is safe after successful environment disposal.
Secret logging
Platform logs environment keys for startup diagnostics, but redacts values whose keys contain secret-shaped fragments such as Secret, Password, Token, AccessKey, ApiKey, ConnectionString, PrivateKey, or EncryptionKey.
Do not rely on redaction as a storage mechanism. Keep sensitive values out of source-controlled configuration.
Troubleshooting
Tests still call alpha or beta
- Inspect user secrets and environment variables.
- Confirm in-memory overrides are added after user secrets.
- Check both service base URLs and authentication authorities.
ECR pull fails
- Verify region, access key, secret key, and session token.
- Confirm the repository URI contains the intended tag or digest.
- Verify that the credentials can access the repository.
A different image starts
- A tagless ECR repository intentionally selects the newest
ImagePushedAtdigest. - Use
latest-releaseorlatest-developfor an intentional moving branch selection, and a build tag or digest for an immutable selection. - Check
ServiceRuntimeInfo.ResolvedImagefor the actualImage.FullNameused by MongoDB, RabbitMQ, Redis, and application services.
Local IDP startup fails
- Verify the restored MongoDB image contains every OIDC client used by the topology -
LocalIdpOptions.ClientId,LocalIdpOptions.IdpClientId, and eachClientCredentialsSeedOptions.ClientId- inIDP.OIDCApplications. - Verify the client exists exactly once and that
MongoSeederImageis available. - Verify the IDP image supports both configured ports and exposes the discovery and health endpoints.
- Check the internal MongoDB and RabbitMQ configuration.
- Do not add a static PFX file.
Platform.Idpgenerates a certificate for each registration and distributes public trust automatically. Keep service-containerSsoAuth__RequireHttpsMetadata=true.
Token acquisition or JWT validation fails
- Use
HostAuthorityfor the host-side token provider. - Use
InternalAuthorityinside service containers. - Configure JWT bearer options through
UseLocalIdp()so discovery, JWKS, and valid issuers use the local runtime information. - Confirm audience/scope and tenant
tidmatch the seeded client permissions. - Register
UseLocalIdpClientTokenProvider()so token acquisition uses its dedicated host-side client instead of an application connector client.
MongoDB restore fails
- Confirm only one restore strategy is selected.
- Verify
DumpsPathresolves from the test process working directory. - Verify the restored image or archive contains the OIDC seed required by the local IDP.
Migration checklist
- Reference
Testcontainers.Platformand only the optional specialized packages the topology needs. - Add non-sensitive
TestContainersdefaults to integration-test appsettings. - Keep machine-specific ECR credentials in user secrets or environment variables.
- Derive the test factory from
TestContainersWebApplicationFactory<TStartup>. - Register regular services through
AddService. - Register the local IDP through
AddLocalIdpinstead of a customIInfrastructureManager. - Give every service container its own client with
UseSeededClient(), or with aSeededClientStarterwhen the service has its own starter. - Use
LocalIdpRuntimeInfofor host configuration, token acquisition, and valid issuers. - Remove direct usage of the low-level
Testcontainers.Idpworkaround. - Run the targeted integration-test project and verify that no runtime call reaches alpha or beta.
Known boundaries
- Platform does not invent application-specific OIDC clients. The configured client must exist in the selected MongoDB seed.
- Application-specific service lists and environment values remain in the consuming integration-test project.
- Private ECR images and application database dumps require the consumer's normal access credentials.