Testing environments
The Rust client uses one switch for an entire isolated IAM world:
Client::with_environment. Every ordinary API group then keeps the same methods and
paths while the client adds the environment root key to each request.
Create on production, execute in the test plane
use silicon_iam_client::{Client, EnvironmentKey, Mutation, models};
// `production` carries the creator's production bearer.
let created = production.environments().create(
"acme",
&models::TestingEnvironmentCreate {
name: "checkout-e2e".to_owned(),
description: Some("CI application proof".to_owned()),
},
&Mutation::new(),
).await?;
// Store created.id as normal metadata. Put created.key in a secret store.
let sandbox = Client::new("https://backend.iam.teamofsilicons.com")?
.with_environment(EnvironmentKey::new(created.key)?);
EnvironmentKey accepts only the exact 32-character alphanumeric wire form and
redacts itself from Debug. The SDK stores no IAM credentials, so retaining the public
UUID-to-key mapping is your program's responsibility. Do not use the UUID as a credential and do not expose
the key as a selector.
Bootstrap the empty environment
Use the CLI or raw control-plane API to run the normal signup and IAM-session login sequence.
Email and SMS are not sent; pass 000000 to each verification call. Keep the returned
control-plane tokens under the environment UUID, never in your production token slot. When
testing the Application itself, complete the IAM-hosted Application login and pass only its SLT
to OAuth::login.
From there, attach the access token and create organizations, tags, Silicons, invitations, and governance state with the same client methods used in production. Expiry, failed-attempt cooldowns, idempotency, ETags, step-up, and authorization are still real; only delivery and the fixed verification code differ.
Create a test-only Application
use silicon_iam_client::{Credential, Mutation, models};
let carbon = sandbox.with_credential(Credential::bearer(test_access_token));
let created_app = carbon.applications().create(
&models::ApplicationCreate {
app_id: "checkout".to_owned(),
org_id: "acme".to_owned(),
app_name: Some("Checkout".to_owned()),
app_logo: None,
webhook_url: "https://hooks.example.test/iam".to_owned(),
webhook_secret: "test-webhook-secret-with-32-characters".to_owned(),
base_url: "http://127.0.0.1:4100".to_owned(),
obo_endpoints: None,
},
&Mutation::new(),
).await?;
assert_eq!(created_app.application.app_id, "acme>checkout");
// Store created_app.app_secret now; the webhook secret was caller-supplied.
The create input uses a local handle; every returned and later Application ID is canonical
{org_id}>{handle}. A test-only creation cannot claim a canonical ID that already
exists in production.
Import a production Application
let imported = carbon.applications()
.import_from_production("google>drive", &Mutation::new())
.await?;
assert!(imported.webhook_secret_inherited);
store_test_secret(imported.app_secret);
// No production signing secret exists anywhere in this response.
The import method fails locally when the client has no environment key. On success IAM copies
the production canonical ID, base URL, webhook URL, and OBO registry. It creates the test
organization with the requesting test Carbon as owner when necessary and returns a fresh
test-only client secret. Its no-store response can be recovered for ten minutes only by
repeating the exact request with the same Mutation.
The production webhook secret is inherited but not revealed. A testing
replace_webhook call installs a supplied test-only webhook_secret,
or generates one when omitted. Store webhook_signing_secret from the response.
Use rotate_webhook_secret with an explicit successor for a later rotation.
Discover a base URL
let app = sandbox.with_credential(Credential::application(
"acme>checkout",
test_app_secret,
));
let drive = app.applications()
.discover_base_url("google>drive")
.await?;
assert_eq!(drive.app_id, "google>drive");
Any Application may discover any verified target, even across organizations. With an environment key on the client, requester and target both resolve only there. IAM will not use a production credential or fall through to a production target. OBO discovery remains a separate, scope-authorized operation across application-owning organizations.
Receive a test webhook
{
"test": {
"testing_key": "…",
"metadata": {
"spec_version": "1.0",
"event_id": "…",
"event_type": "organization.membership.created.v1",
"occurred_at": "…",
"organization_id": "…",
"aggregate": { "type": "membership", "id": "…", "version": 1 }
},
"data": {}
}
}
Verify the signature over the exact raw outer bytes first. Then detect test, compare
testing_key to the expected secret without timing leakage, route to that isolated
run, deduplicate on metadata.event_id, and order on
metadata.aggregate.version. Redact the key before logging and do not persist it in
the event table.
let verified = webhook_verifier.verify(&headers, &body)?;
verified.verify_testing_environment(&environment_key)?;
// The SDK removes the root key and normalizes metadata/data after verification.
let event_id = verified.event_id();
let event = verified.event();
Proof checklist
- Assert the environment begins empty.
- Use control-plane tooling to complete both-contact signup and IAM login with
000000. - Create or import the Application and persist every one-time test secret.
- Mint the SLT with explicit
scope_version,approved_scopes, andorg_ids, give the Application client only that SLT, completeOAuth::login, and introspect it with the matching organization in the same plane. Useshort_lived_token_for_organizationswith explicit selections; test selected and unselected organizations separately. - Prove production credentials fail inside the environment and test credentials fail without it.
- Verify and deduplicate a wrapped webhook; run OBO exchange/verification with the organization-bound access token, and prove a token without the calling app's organization in its selection is refused.
- Call
clean_currentwith the key when the run finishes, or retire it from production.
Never construct a second set of test endpoint paths. If a test can pass only through a mock-only route, it is not proving the production integration. The manual CLI walkthrough exercises this sequence and lists the negative cases to verify before production.
Provision testing from your application
Applications support test mode by default. Call
applications().create_testing_environment(&ApplicationTestingEnvironmentCreate, &mutation)
using the production application's credential. Supply a name and optional description.
An optional valid iam_test_key attaches to that environment; an invalid provided
key fails instead of creating another environment. Without a key, IAM creates a new environment.
IAM imports the caller plus every transitive external dependency into the same test layer.
The result includes the environment ID, IAM key, caller's new test app secret, and dependency
IDs. Only IAM handles dependency credentials. Use applications().testing_environments
to list active environments for the application and its organization.
Use the ordinary SDK methods with with_environment(EnvironmentKey::new(key)?)
and the test application credential. Exercise login consent, token exchange, cross-app OBO,
webhooks, invalid keys, and production/test credential rejection. An inactive application test
environment expires after 30 days by default; configure testing_idle_days on the app.