CodamAIDocs
Topicdone

The in-memory adapter for tests and development

An identity provider in memory, checked against the same contract tests, for tests and local development without Keycloak.

Variants
contract testtest of a whole applicationlocal development (profile local)role not createdpassword link from memoryforeign group for a testrestart

What this is about

The in-memory adapter (cias-iam-memory) is an identity provider that only lives in memory. It exists for two purposes:

  • Testing without Keycloak. A test that plays through a registration does not need a running Keycloak.
  • Developing without Keycloak. Whoever builds on registration or role granting can try out the whole flow on their own machine.

A contract test is a test that does not check one adapter but the promise of a port. It is written once and runs against every adapter.

One store for all ports

flowchart LR
    subgraph Ports
      P1["Accounts"]
      P2["Organizations and memberships"]
      P3["Realm and client roles"]
      P4["Groups"]
      P5["Profile and claims"]
    end
    P1 --> S
    P2 --> S
    P3 --> S
    P4 --> S
    P5 --> S
    S[("InMemoryIdentityProvider<br/>one class, one store")]

Why one class and not one per port? A fake spread over five separate stores allows states that cannot exist in reality: a membership for an account nobody created, or a role in a deleted organization. A test that turns green against something like that proves nothing.

The same tests for both

flowchart TB
    V["cias-test-support<br/>contract tests as abstract classes"]
    V --> MT["cias-iam-memory<br/>InMemory…Test"]
    V --> KT["cias-iam-keycloak<br/>Keycloak…Test"]
    MT --> M[("Memory")]
    KT --> K["real Keycloak<br/>in a container"]

The contract tests live in the module cias-test-support, as abstract JUnit classes. Each adapter extends them and only supplies its ports and a few helpers to create test data. The checks themselves are the same.

Contract suiteCasesWhat it promises, for example
IdentityProvisioningContract9new accounts are disabled and unverified, an address exists only once, lookup ignores upper and lower case, every state change may come twice
OrganizationManagementContract7an alias exists only once, granting and revoking memberships, the account stays
RoleManagementContract10roles in an organization never show up among the global roles, “no roles” means nothing is allowed
ClientRoleManagementContract15the same role on two clients is two roles, a client role is not a realm role
GroupManagementContract26groups without the marker cias-managed are invisible and unchangeable, roles are set rather than added, default groups
UserProfileManagementContract6attributes are added and corrected, never removed, foreign entries stay
ClaimMappingContract3a mapper is created once, a changed shape is corrected

The direction matters: if a case has to be relaxed so that the in-memory adapter passes, the in-memory adapter is wrong, not the contract.

The Keycloak adapter also runs against a real Keycloak in a container (Testcontainers, Keycloak 26.7) or against a running server named by -Dcias.test.keycloak.url. Every test class creates its own realm there. On top of that come cases a fake cannot model at all: an unknown client, the permissions on profile attributes, what Keycloak itself brings in its profile, and the password link with and without the extension.

Where you meet it

Three places of use

When: You build or change an adapter or a port.

The test classes in cias-iam-memory extend the contract suites. Every test gets a fresh store. With defineRole and defineClientRole you create roles, with defineForeignGroup a group that does not belong to CIAS.

Result: the same yardstick as for Keycloak

When: A test starts the standalone CIAS with all its wiring, for example to play through a registration from start to end.

The test gets the InMemoryIdentityProvider as a bean, creates the roles it needs, and afterwards checks what is in the store: which account, which organization, which roles.

Result: whole flow without Keycloak

When: You start the standalone CIAS on your machine without further settings.

The local profile sets codamai.cias.iam-provider: memory, plus an in-memory H2 database and emails only to the log. You can try registrations, roles and groups through the API.

Result: no Keycloak needed, but also no login with these accounts

The in-memory adapter is switched on like any adapter: the jar is on the classpath, and codamai.cias.iam-provider is memory. It never steps in as a fallback on its own. See Ports and adapters.

How it behaves

Particularities in memory

When: A test grants a role that nobody created before.

As in Keycloak, a role has to exist first. The in-memory adapter reports IamNotFoundException. Without this rule, "does the role exist?" would always answer yes, and the check whether all configured roles exist would be worthless.

Result: error, as in Keycloak

When: The flow asks for a password link.

The in-memory adapter always returns a link, so that emails and pages that show it can be tested. The link starts with memory://password-setup/…, so that nobody opens it by accident and you recognize it in an email at once. If no duration is given, it is valid for one day. A validity above 72 hours it refuses, just like the Keycloak extension.

Result: link that leads nowhere

When: A test needs a group that an operator would have created by hand.

defineForeignGroup creates a group without the marker cias-managed, as a default group if wanted, and fillForeignGroup gives it roles and members. That makes it possible to check that CIAS does not touch foreign groups and that the import takes them over.

Result: boundary to foreign groups testable

When: The process ends.

Everything is gone: accounts, organizations, roles, groups, profile. Nothing is saved.

Result: empty store at the next start

In-memory adapter and Keycloak adapter compared

In-memory adapter
cias-iam-memory
  • all ports in one class, one store
  • no tokens, no login, no passwords
  • no permissions on profile attributes
  • password link always there, but memory://
  • empty after a restart
Keycloak adapter
cias-iam-keycloak
  • ports spread over several classes, one HTTP access
  • accounts people can log in with
  • permissions on every profile attribute
  • password link only with the extension
  • data stays in Keycloak

What is the same are the promises the contract tests check: unique addresses and aliases, separate levels for roles, groups only with the marker, repeatable calls.

Pitfalls

Next

Sources in the code and the knowledge base
  • CIAS/cias-iam-memory – InMemoryIdentityProvider (defineRole, defineClientRole, reset, createPasswordSetupLink, findByEmail, list, ensureAttributes, ensureClaim, defineForeignGroup, fillForeignGroup), CLAUDE.md
  • CIAS/cias-iam-memory – InMemory*Test (ClaimMapping, ClientRoleManagement, GroupManagement, IdentityProvisioning, OrganizationManagement, RoleManagement, UserProfile)
  • CIAS/cias-test-support – ClaimMappingContract, ClientRoleManagementContract, GroupManagementContract, IdentityProvisioningContract, OrganizationManagementContract, RoleManagementContract, UserProfileManagementContract
  • CIAS/cias-iam-keycloak – Keycloak*Test, KeycloakTestEnvironment, KeycloakAdapters
  • CIAS/cias-spring-boot-starter – CiasIamAutoConfiguration.MemoryConfiguration
  • CIAS/cias-runtime – application-local.yml, RegistrationEndToEndTest, AuthorizationEndToEndTest
  • CIAS/cias-iam-api/docs/adr – ADR-004
Search