IAM Generators and Test Migration
Table of Contents
Overview
The IAM component has 8 domain types that lack proper generators. Their
repository tests use hand-rolled make_* helpers defined in anonymous
namespaces, bypassing the generation_context infrastructure that was
specifically designed (see 2026-02-14-generation-context-design.org) to solve
this problem. This plan creates the missing generators and migrates existing
tests.
Problem
Hand-rolled helpers in IAM test files:
| Helper | Test File | Generator Exists? |
|---|---|---|
make_tenant() |
repository_tenant_repository_tests.cpp |
No |
make_tenant_status() |
repository_tenant_status_repository_tests.cpp |
No |
make_tenant_type() |
repository_tenant_type_repository_tests.cpp |
No |
make_session() |
repository_session_repository_tests.cpp |
No |
make_login_info() |
repository_login_info_repository_tests.cpp |
No |
make_permission() |
repository_permission_repository_tests.cpp |
No |
make_role() |
repository_role_repository_tests.cpp |
No |
make_account_role() |
repository_account_role_repository_tests.cpp |
No |
make_permission() |
repository_role_permission_repository_tests.cpp |
No (duplicate) |
make_role() |
repository_role_permission_repository_tests.cpp |
No (duplicate) |
make_account_party() |
repository_account_party_repository_tests.cpp |
Yes (unused) |
These helpers have several issues:
- Not reproducible - use
boost::uuids::random_generator()()instead ofctx.generate_uuid(), so test runs are not reproducible via seed. - Duplicate context wiring - each helper manually calls
h.db_user()andh.tenant_id()for audit fields, duplicating whatgeneration_contextandmake_generation_context()already provide. - Not reusable - defined in anonymous namespaces, so each test file that needs the same type must duplicate the helper.
- Inconsistent with rest of codebase - ores.dq, ores.refdata, ores.assets,
and ores.connections all use generators with
generation_context.
Goals
- Create generators for all 8 missing IAM domain types.
- Migrate all IAM repository test files to use generators +
make_generation_context(). - Follow the established generator pattern from
generation_contextdesign doc. - Remove all hand-rolled
make_*helpers.
Non-Goals
- Migrating generators in other components (ores.dq, ores.refdata are already done).
- Changing domain type definitions.
- Adding new test cases (test coverage was addressed in the preceding PR).
Phase 1: Create Missing Generators
Create 8 generator pairs (header + source) under
projects/ores.iam/include/ores.iam/generators/ and
projects/ores.iam/src/generators/.
Each generator follows the established pattern:
domain::TYPE generate_synthetic_TYPE(generation_context& ctx) { const auto modified_by = ctx.env().get_or( std::string(generation_keys::modified_by), "system"); // ... use ctx.generate_uuid(), ctx.random_int(), faker::*, etc. return r; } std::vector<domain::TYPE> generate_synthetic_TYPEs( std::size_t n, generation_context& ctx);
1.1 tenant_generator
Domain fields: version, id (uuid), code, name, type, description,
hostname, status, modified_by, change_reason_code,
change_commentary, performed_by, recorded_at.
Context keys used: modified_by, tenant_id (for performed_by).
Uniqueness: static atomic counter appended to code and hostname.
Files:
projects/ores.iam/include/ores.iam/generators/tenant_generator.hppprojects/ores.iam/src/generators/tenant_generator.cpp
1.2 tenant_status_generator
Domain fields: version, status (key), name, description,
display_order, modified_by, change_reason_code, change_commentary,
performed_by, recorded_at.
Context keys used: modified_by.
Uniqueness: static atomic counter appended to status code.
Files:
projects/ores.iam/include/ores.iam/generators/tenant_status_generator.hppprojects/ores.iam/src/generators/tenant_status_generator.cpp
1.3 tenant_type_generator
Domain fields: version, type (key), name, description,
display_order, modified_by, change_reason_code, change_commentary,
performed_by, recorded_at.
Context keys used: modified_by.
Uniqueness: static atomic counter appended to type code.
Files:
projects/ores.iam/include/ores.iam/generators/tenant_type_generator.hppprojects/ores.iam/src/generators/tenant_type_generator.cpp
1.4 role_generator
Domain fields: version, id (uuid), code, name, description,
display_order, modified_by, change_reason_code, change_commentary,
performed_by, recorded_at.
Context keys used: modified_by, tenant_id.
Uniqueness: static atomic counter appended to code.
Files:
projects/ores.iam/include/ores.iam/generators/role_generator.hppprojects/ores.iam/src/generators/role_generator.cpp
1.5 permission_generator
Domain fields: version, id (uuid), code, name, description,
display_order, modified_by, change_reason_code, change_commentary,
performed_by, recorded_at.
Context keys used: modified_by, tenant_id.
Uniqueness: static atomic counter appended to code.
Files:
projects/ores.iam/include/ores.iam/generators/permission_generator.hppprojects/ores.iam/src/generators/permission_generator.cpp
1.6 session_generator
Domain fields: version, tenant_id, id (uuid), account_id (uuid),
ip_address, user_agent, started_at, ended_at, modified_by,
change_reason_code, change_commentary, performed_by, recorded_at.
Context keys used: modified_by, tenant_id, account_id.
Files:
projects/ores.iam/include/ores.iam/generators/session_generator.hppprojects/ores.iam/src/generators/session_generator.cpp
1.7 login_info_generator
Domain fields: version, tenant_id, account_id (uuid), ip_address,
user_agent, login_at, success, failure_reason, modified_by,
change_commentary, performed_by, recorded_at.
Context keys used: modified_by, tenant_id, account_id.
Files:
projects/ores.iam/include/ores.iam/generators/login_info_generator.hppprojects/ores.iam/src/generators/login_info_generator.cpp
1.8 account_role_generator
Domain fields: version, tenant_id, account_id (uuid), role_id (uuid),
modified_by, change_reason_code, change_commentary, performed_by,
recorded_at.
Context keys used: modified_by, tenant_id, account_id.
Files:
projects/ores.iam/include/ores.iam/generators/account_role_generator.hppprojects/ores.iam/src/generators/account_role_generator.cpp
Phase 2: Add Generator Tests
Add test cases for each new generator to the existing
projects/ores.iam/tests/generators_tests.cpp (or create one if it does not
exist). Follow the pattern from projects/ores.dq/tests/generators_tests.cpp:
TEST_CASE("generate_synthetic_tenant_status_produces_valid_instance", tags) { generation_context ctx; auto sut = generate_synthetic_tenant_status(ctx); CHECK(!sut.status.empty()); CHECK(!sut.name.empty()); CHECK(sut.version == 1); CHECK(sut.change_commentary == "Synthetic test data"); } TEST_CASE("generate_synthetic_tenant_statuses_produces_n_instances", tags) { generation_context ctx; auto items = generate_synthetic_tenant_statuses(5, ctx); CHECK(items.size() == 5); }
Phase 3: Migrate Repository Tests
Migrate each repository test file to replace the hand-rolled make_* helper
with the new generator and make_generation_context().
Before (current pattern)
#include <faker-cxx/faker.h> namespace { tenant_status make_tenant_status(ores::testing::database_helper& h) { static std::atomic<int> counter{0}; const auto idx = ++counter; tenant_status ts; ts.version = 1; ts.status = std::string(faker::string::alphanumeric(8)) + "_" + std::to_string(idx); ts.modified_by = h.db_user(); ts.performed_by = h.db_user(); // ... return ts; } } TEST_CASE("write_single_tenant_status", tags) { database_helper h; auto sys_ctx = h.context().with_tenant(tenant_id::system()); auto ts = make_tenant_status(h); // ... }
After (migrated pattern)
#include "ores.iam/generators/tenant_status_generator.hpp" #include "ores.testing/make_generation_context.hpp" // No anonymous namespace helper needed. TEST_CASE("write_single_tenant_status", tags) { database_helper h; auto sys_ctx = h.context().with_tenant(tenant_id::system()); auto gen_ctx = ores::testing::make_generation_context(h); auto ts = generate_synthetic_tenant_status(gen_ctx); // ... }
Files to migrate
| Test File | Remove Helper | Use Generator |
|---|---|---|
repository_tenant_repository_tests.cpp |
make_tenant() |
generate_synthetic_tenant() |
repository_tenant_status_repository_tests.cpp |
make_tenant_status() |
generate_synthetic_tenant_status() |
repository_tenant_type_repository_tests.cpp |
make_tenant_type() |
generate_synthetic_tenant_type() |
repository_session_repository_tests.cpp |
make_session() |
generate_synthetic_session() |
repository_login_info_repository_tests.cpp |
make_login_info() |
generate_synthetic_login_info() |
repository_permission_repository_tests.cpp |
make_permission() |
generate_synthetic_permission() |
repository_role_repository_tests.cpp |
make_role() |
generate_synthetic_role() |
repository_account_role_repository_tests.cpp |
make_account_role() |
generate_synthetic_account_role() |
repository_role_permission_repository_tests.cpp |
make_role(), make_permission() |
Use both generators |
repository_account_party_repository_tests.cpp |
make_account_party() |
generate_synthetic_account_party() (already exists) |
Phase 4: Verify
- Build:
cmake --build --preset linux-clang-debug - Run IAM tests:
build/output/linux-clang-debug/publish/bin/ores.iam.tests - Run all tests:
cmake --build --preset linux-clang-debug --target rat
Scope Summary
| Phase | Description | New Files | Modified Files |
|---|---|---|---|
| 1 | Create 8 generators | 16 | 0 |
| 2 | Generator tests | 1 | 0 |
| 3 | Migrate repository tests | 0 | 10 |
| 4 | Build and verify | 0 | 0 |
| Total | 17 | 10 |
Dependencies
- Requires
generation_contextinfrastructure (already merged infeature/generation_contextPR #448). - Requires
make_generation_context()inores.testing(already exists). - Requires
generation_keys::modified_byandgeneration_keys::tenant_id(already exist).
Open Questions
- Should we also check whether domain types need a
tenant_idfield? Thetenant_statusandtenant_typedomain structs do NOT have atenant_idfield (they are system-level reference data), whilesession,login_info, andaccount_roledo. Generators for the latter group will readtenant_idfrom context. - Some test files (e.g.
repository_role_permission_repository_tests.cpp) define twomake_*helpers for different types. These will need two different generator includes. - The
account_party_repository_tests.cppalready has a generator available (account_party_generator) but doesn't use it. This file needs the same migration but the generator already exists.