Silicon IAMDocs

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

  1. Assert the environment begins empty.
  2. Use control-plane tooling to complete both-contact signup and IAM login with 000000.
  3. Create or import the Application and persist every one-time test secret.
  4. Mint the SLT with explicit scope_version, approved_scopes, and org_ids, give the Application client only that SLT, complete OAuth::login, and introspect it with the matching organization in the same plane. Use short_lived_token_for_organizations with explicit selections; test selected and unselected organizations separately.
  5. Prove production credentials fail inside the environment and test credentials fail without it.
  6. 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.
  7. Call clean_current with 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.