oatpp-authkit/test/test_role_template_schema.cpp
Uwe Schuster 3ccc25f231 #14 PR 1: relocate role_templates module + Atlas migration docs
Lifts role_templates / role_template_fields / user_role_assignments from
fewo-webapp into oatpp-authkit, exposed via the declarative SchemaContract
introduced in PR 0.

New files (all in oatpp-authkit):
- dto/RoleTemplateDto.hpp — RoleTemplateDto, RoleTemplateFieldDto,
  UserRoleAssignmentDto. UserWithPermissionsDto stays in fewo (fewo-
  specific /api/auth/me response shape).
- db/RoleTemplateDb.hpp — DbClient with all queries (CRUD + cascade
  soft-delete + getEffectiveFieldPermissions). RoleTemplateSchema struct
  declares the three tables' columns/indexes/sidecar tables in the new
  declarative form. TemporalRepository overlays valid_until + the
  composite UNIQUE(entity_id, valid_until) index.
- repo/ConcreteRoleTemplateRepository.hpp — Repository<RoleTemplateDto>
  inner adapter; makeRoleTemplateRepository helper composes the stack.
- docs/MIGRATIONS.md — Atlas workflow for consumers (atlasgo.io as the
  diff-driven migration tool; SchemaBuilder produces desired state, Atlas
  generates versioned SQL, SchemaContract::verify asserts at runtime).
- test/test_role_template_schema.cpp — verifies SchemaBuilder<
  RoleTemplateSchema, TemporalRepository<RoleTemplateDto>> emits the
  expected 5 DDL statements (2 sidecars + entity table + 2 indexes) with
  composite-FK + ON UPDATE CASCADE on both sidecars.

11 of 11 tests pass. RoleTemplateDto is registered as temporal via
OATPP_AUTHKIT_REGISTER_TEMPORAL so TemporalRepository compiles cleanly.

Atlas binary integration in CI is documented but not yet wired — owner
deferred to a follow-up after the first concrete consumer migration. The
shipped role_templates stack itself is fully consumable today; fewo-
webapp's switch from local copies to oatpp-authkit-shipped headers is
the natural next PR.

Bumped 0.9.0 → 0.10.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:36:18 +02:00

114 lines
4.6 KiB
C++

// Tests for authkit#14 PR 1 — role_templates schema contribution composes
// correctly with the TemporalRepository decorator.
#include "oatpp-authkit/db/RoleTemplateDb.hpp"
#include "oatpp-authkit/dto/RoleTemplateDto.hpp"
#include "oatpp-authkit/repo/ConcreteRoleTemplateRepository.hpp"
#include "oatpp-authkit/repo/SchemaContract.hpp"
#include "oatpp-authkit/repo/TemporalRepository.hpp"
#include <cassert>
#include <cstdio>
#include <string>
#include <vector>
#define REQUIRE(cond) do { \
if (!(cond)) { std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", \
#cond, __FILE__, __LINE__); std::abort(); } } while (0)
namespace {
bool contains(const std::string& haystack, const std::string& needle) {
return haystack.find(needle) != std::string::npos;
}
// SchemaBuilder<RoleTemplateSchema, TemporalRepository<RoleTemplateDto>>
// emits the three tables + their indexes. Verify the composition.
void test_role_templates_full_create() {
using namespace oatpp_authkit::repo;
using namespace oatpp_authkit::db;
using namespace oatpp_authkit::dto;
std::vector<std::string> sqls;
SqlExec exec = [&](const std::string& sql) { sqls.push_back(sql); };
SchemaBuilder<
RoleTemplateSchema,
TemporalRepository<RoleTemplateDto>>::create("role_templates", exec);
// Two sidecars (role_template_fields + user_role_assignments) +
// one entity table + one entity_id index + one composite UNIQUE index
// = 5
REQUIRE(sqls.size() == 5);
// Sidecar 1: role_template_fields with composite FK
REQUIRE(contains(sqls[0], "CREATE TABLE IF NOT EXISTS role_template_fields"));
REQUIRE(contains(sqls[0], "template_id TEXT NOT NULL"));
REQUIRE(contains(sqls[0], "template_valid_until TEXT NOT NULL DEFAULT '9999-12-31T23:59:59Z'"));
REQUIRE(contains(sqls[0],
"FOREIGN KEY (template_id, template_valid_until) REFERENCES "
"role_templates(entity_id, valid_until) ON UPDATE CASCADE"));
// Sidecar 2: user_role_assignments with composite FK
REQUIRE(contains(sqls[1], "CREATE TABLE IF NOT EXISTS user_role_assignments"));
REQUIRE(contains(sqls[1], "user_id TEXT NOT NULL"));
REQUIRE(contains(sqls[1],
"FOREIGN KEY (template_id, template_valid_until) REFERENCES "
"role_templates(entity_id, valid_until) ON UPDATE CASCADE"));
// Entity table: role_templates with all RoleTemplateSchema columns +
// valid_until from TemporalRepository.
REQUIRE(contains(sqls[2], "CREATE TABLE IF NOT EXISTS role_templates"));
REQUIRE(contains(sqls[2], "id TEXT PRIMARY KEY"));
REQUIRE(contains(sqls[2], "entity_id TEXT NOT NULL"));
REQUIRE(contains(sqls[2], "name TEXT NOT NULL"));
REQUIRE(contains(sqls[2], "is_system INTEGER NOT NULL DEFAULT 0"));
REQUIRE(contains(sqls[2], "valid_from TEXT NOT NULL DEFAULT (datetime('now'))"));
REQUIRE(contains(sqls[2], "valid_until TEXT NOT NULL DEFAULT '9999-12-31T23:59:59Z'"));
// Indexes: ix_role_templates_entity_id (RoleTemplateSchema)
// ux_role_templates_entity_valid_until (TemporalRepository)
REQUIRE(contains(sqls[3], "CREATE INDEX IF NOT EXISTS ix_role_templates_entity_id"));
REQUIRE(contains(sqls[3], "ON role_templates (entity_id)"));
REQUIRE(contains(sqls[4], "CREATE UNIQUE INDEX IF NOT EXISTS ux_role_templates_entity_valid_until"));
REQUIRE(contains(sqls[4], "ON role_templates (entity_id, valid_until)"));
}
// Verify that ConcreteRoleTemplateRepository contributes nothing to the
// schema — RoleTemplateSchema owns the table declarations, the concrete
// repo only adapts queries. Stacking the concrete repo into the builder
// must not duplicate columns.
void test_concrete_repo_contributes_no_schema() {
using namespace oatpp_authkit::repo;
using namespace oatpp_authkit::db;
using namespace oatpp_authkit::dto;
std::vector<std::string> sqls_with;
std::vector<std::string> sqls_without;
SchemaBuilder<
RoleTemplateSchema,
ConcreteRoleTemplateRepository,
TemporalRepository<RoleTemplateDto>>::create(
"role_templates",
[&](const std::string& s){ sqls_with.push_back(s); });
SchemaBuilder<
RoleTemplateSchema,
TemporalRepository<RoleTemplateDto>>::create(
"role_templates",
[&](const std::string& s){ sqls_without.push_back(s); });
// Including ConcreteRoleTemplateRepository in the pack changes nothing
// — empty kSchema contributes no DDL.
REQUIRE(sqls_with == sqls_without);
}
} // namespace
int main() {
test_role_templates_full_create();
test_concrete_repo_contributes_no_schema();
std::printf("test_role_template_schema: OK\n");
return 0;
}