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 suite | Cases | What it promises, for example |
|---|---|---|
IdentityProvisioningContract | 9 | new accounts are disabled and unverified, an address exists only once, lookup ignores upper and lower case, every state change may come twice |
OrganizationManagementContract | 7 | an alias exists only once, granting and revoking memberships, the account stays |
RoleManagementContract | 10 | roles in an organization never show up among the global roles, “no roles” means nothing is allowed |
ClientRoleManagementContract | 15 | the same role on two clients is two roles, a client role is not a realm role |
GroupManagementContract | 26 | groups without the marker cias-managed are invisible and unchangeable, roles are set rather than added, default groups |
UserProfileManagementContract | 6 | attributes are added and corrected, never removed, foreign entries stay |
ClaimMappingContract | 3 | a 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
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
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
- 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
- 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.