Prepare GovOPlaN self-hosted release workflow
Some checks failed
Dependency Audit / dependency-audit (push) Failing after 50s
Module Matrix / module-matrix (push) Failing after 49s

This commit is contained in:
2026-07-10 21:57:22 +02:00
parent 8dd5123aab
commit 94236a7d7e
51 changed files with 3576 additions and 803 deletions

View File

@@ -11,21 +11,26 @@ from govoplan_core.core.identity import (
IdentityDirectory,
IdentityRef,
)
from govoplan_core.core.idm import (
CAPABILITY_IDM_DIRECTORY,
IdmDirectory,
OrganizationFunctionAssignmentRef,
)
from govoplan_core.core.organizations import (
CAPABILITY_ORGANIZATION_DIRECTORY,
OrganizationDirectory,
OrganizationFunctionAssignmentRef,
OrganizationFunctionRef,
OrganizationUnitRef,
)
from govoplan_core.core.modules import ModuleContext, ModuleManifest
from govoplan_core.core.registry import PlatformRegistry
from govoplan_core.db.base import Base
from govoplan_access.backend.db.models import User # noqa: F401 - registers legacy tenancy relationship target
from govoplan_access.backend.db.models import ExternalFunctionRoleAssignment, Role, User
from govoplan_access.backend.semantic import collect_external_function_roles
from govoplan_identity.backend.db.models import Identity, IdentityAccountLink
from govoplan_idm.backend.db.models import IdmOrganizationFunctionAssignment
from govoplan_organizations.backend.db.models import (
OrganizationFunction,
OrganizationFunctionAssignment,
OrganizationFunctionType,
OrganizationRelation,
OrganizationRelationType,
@@ -73,15 +78,6 @@ class _FakeOrganizationDirectory:
slug="registry-clerk",
name="Registry Clerk",
)
assignment = OrganizationFunctionAssignmentRef(
id="assignment-1",
tenant_id="tenant-1",
identity_id="identity-1",
account_id="account-1",
function_id=function.id,
organization_unit_id=unit.id,
applies_to_subunits=True,
)
def get_organization_unit(self, organization_unit_id: str) -> OrganizationUnitRef | None:
return self.unit if organization_unit_id == self.unit.id else None
@@ -96,17 +92,29 @@ class _FakeOrganizationDirectory:
del include_subunits
return (self.function,) if organization_unit_id == self.unit.id else ()
def get_function_assignment(self, assignment_id: str) -> OrganizationFunctionAssignmentRef | None:
class _FakeIdmDirectory:
assignment = OrganizationFunctionAssignmentRef(
id="assignment-1",
tenant_id="tenant-1",
identity_id="identity-1",
account_id="account-1",
function_id=_FakeOrganizationDirectory.function.id,
organization_unit_id=_FakeOrganizationDirectory.unit.id,
applies_to_subunits=True,
)
def get_organization_function_assignment(self, assignment_id: str) -> OrganizationFunctionAssignmentRef | None:
return self.assignment if assignment_id == self.assignment.id else None
def function_assignments_for_account(self, account_id: str, *, tenant_id: str | None = None):
def organization_function_assignments_for_account(self, account_id: str, *, tenant_id: str | None = None):
if account_id != self.assignment.account_id:
return ()
if tenant_id is not None and tenant_id != self.assignment.tenant_id:
return ()
return (self.assignment,)
def function_assignments_for_identity(self, identity_id: str, *, tenant_id: str | None = None):
def organization_function_assignments_for_identity(self, identity_id: str, *, tenant_id: str | None = None):
if identity_id != self.assignment.identity_id:
return ()
if tenant_id is not None and tenant_id != self.assignment.tenant_id:
@@ -118,10 +126,12 @@ class IdentityOrganizationContractTests(unittest.TestCase):
def test_protocol_shapes_match_reference_implementations(self) -> None:
self.assertIsInstance(_FakeIdentityDirectory(), IdentityDirectory)
self.assertIsInstance(_FakeOrganizationDirectory(), OrganizationDirectory)
self.assertIsInstance(_FakeIdmDirectory(), IdmDirectory)
def test_capabilities_register_and_resolve_through_platform_registry(self) -> None:
identity_directory = _FakeIdentityDirectory()
organization_directory = _FakeOrganizationDirectory()
idm_directory = _FakeIdmDirectory()
manifest = ModuleManifest(
id="directory-contract-test",
name="Directory Contract Test",
@@ -129,6 +139,7 @@ class IdentityOrganizationContractTests(unittest.TestCase):
capability_factories={
CAPABILITY_IDENTITY_DIRECTORY: lambda context: identity_directory,
CAPABILITY_ORGANIZATION_DIRECTORY: lambda context: organization_directory,
CAPABILITY_IDM_DIRECTORY: lambda context: idm_directory,
},
)
registry = PlatformRegistry()
@@ -137,6 +148,64 @@ class IdentityOrganizationContractTests(unittest.TestCase):
self.assertIs(identity_directory, registry.require_capability(CAPABILITY_IDENTITY_DIRECTORY))
self.assertIs(organization_directory, registry.require_capability(CAPABILITY_ORGANIZATION_DIRECTORY))
self.assertIs(idm_directory, registry.require_capability(CAPABILITY_IDM_DIRECTORY))
def test_access_directory_can_optionally_consume_idm_assignments(self) -> None:
from govoplan_access.backend.directory import SqlAccessDirectory
root = Path(tempfile.mkdtemp(prefix="govoplan-access-idm-directory-"))
try:
with temporary_database(f"sqlite:///{root / 'access-idm.db'}") as database:
Base.metadata.create_all(bind=database.engine)
directory = SqlAccessDirectory(idm_directory=_FakeIdmDirectory())
assignments = directory.function_assignments_for_account("account-1", tenant_id="tenant-1")
self.assertEqual(("assignment-1",), tuple(item.id for item in assignments))
self.assertEqual("identity-1", assignments[0].identity_id)
self.assertEqual("function-1", assignments[0].function_id)
finally:
shutil.rmtree(root, ignore_errors=True)
def test_access_maps_idm_function_facts_to_roles_explicitly(self) -> None:
root = Path(tempfile.mkdtemp(prefix="govoplan-access-idm-role-map-"))
try:
with temporary_database(f"sqlite:///{root / 'access-idm-role-map.db'}") as database:
Base.metadata.create_all(bind=database.engine)
with database.session() as session:
user = User(
id="user-idm-role-map",
tenant_id="tenant-1",
account_id="account-1",
email="role-map@example.test",
display_name="Role Map",
settings={},
mail_profile_policy={},
)
role = Role(
id="role-idm-function",
tenant_id="tenant-1",
slug="idm-function-reader",
name="IDM Function Reader",
permissions=["files:file:read"],
is_assignable=True,
)
mapping = ExternalFunctionRoleAssignment(
tenant_id="tenant-1",
source_module="organizations",
function_id="function-1",
role_id=role.id,
settings={},
)
session.add_all([user, role, mapping])
session.commit()
roles = collect_external_function_roles(
session,
user,
_FakeIdmDirectory().organization_function_assignments_for_account("account-1", tenant_id="tenant-1"),
)
self.assertEqual(["role-idm-function"], [item.id for item in roles])
finally:
shutil.rmtree(root, ignore_errors=True)
def test_sql_identity_and_organization_directories_return_dtos(self) -> None:
root = Path(tempfile.mkdtemp(prefix="govoplan-directory-contracts-"))
@@ -210,7 +279,7 @@ class IdentityOrganizationContractTests(unittest.TestCase):
slug="registry-clerk",
name="Registry Clerk",
)
assignment = OrganizationFunctionAssignment(
assignment = IdmOrganizationFunctionAssignment(
id="assignment-directory",
tenant_id=tenant.id,
identity_id=identity.id,
@@ -235,10 +304,12 @@ class IdentityOrganizationContractTests(unittest.TestCase):
session.commit()
from govoplan_identity.backend.directory import SqlIdentityDirectory
from govoplan_idm.backend.directory import SqlIdmDirectory
from govoplan_organizations.backend.directory import SqlOrganizationDirectory
identity_directory = SqlIdentityDirectory()
organization_directory = SqlOrganizationDirectory(identity_directory=identity_directory)
idm_directory = SqlIdmDirectory()
organization_directory = SqlOrganizationDirectory()
self.assertEqual("Directory Person", identity_directory.get_identity("identity-directory").display_name) # type: ignore[union-attr]
self.assertEqual("identity-directory", identity_directory.identity_for_account("account-directory").id) # type: ignore[union-attr]
@@ -249,11 +320,11 @@ class IdentityOrganizationContractTests(unittest.TestCase):
self.assertEqual("function-type-directory", organization_directory.get_function("function-directory").function_type_id) # type: ignore[union-attr]
self.assertEqual(
["assignment-directory"],
[item.id for item in organization_directory.function_assignments_for_identity("identity-directory", tenant_id="tenant-directory")],
[item.id for item in idm_directory.organization_function_assignments_for_identity("identity-directory", tenant_id="tenant-directory")],
)
self.assertEqual(
["assignment-directory"],
[item.id for item in organization_directory.function_assignments_for_account("account-directory", tenant_id="tenant-directory")],
[item.id for item in idm_directory.organization_function_assignments_for_account("account-directory", tenant_id="tenant-directory")],
)
finally:
shutil.rmtree(root, ignore_errors=True)