286 lines
8.9 KiB
Python
286 lines
8.9 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from govoplan_core.db.base import Base
|
|
from govoplan_organizations.backend.api.v1.schemas import (
|
|
OrganizationModelTemplateDefinition,
|
|
)
|
|
from govoplan_organizations.backend.db.models import (
|
|
OrganizationFunction,
|
|
OrganizationFunctionType,
|
|
OrganizationModelInstantiation,
|
|
OrganizationModelTemplate,
|
|
OrganizationModelTemplateVersion,
|
|
OrganizationRelation,
|
|
OrganizationRelationType,
|
|
OrganizationStructure,
|
|
OrganizationUnit,
|
|
OrganizationUnitType,
|
|
)
|
|
from govoplan_organizations.backend.templates import (
|
|
OrganizationTemplateError,
|
|
canonical_template_definition,
|
|
instantiate_template_version,
|
|
)
|
|
|
|
|
|
TABLES = [
|
|
OrganizationModelTemplate.__table__,
|
|
OrganizationModelTemplateVersion.__table__,
|
|
OrganizationUnitType.__table__,
|
|
OrganizationStructure.__table__,
|
|
OrganizationRelationType.__table__,
|
|
OrganizationFunctionType.__table__,
|
|
OrganizationUnit.__table__,
|
|
OrganizationFunction.__table__,
|
|
OrganizationRelation.__table__,
|
|
OrganizationModelInstantiation.__table__,
|
|
]
|
|
|
|
|
|
class OrganizationModelTemplateTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.engine = create_engine("sqlite:///:memory:")
|
|
Base.metadata.create_all(self.engine, tables=TABLES)
|
|
self.session: Session = sessionmaker(
|
|
bind=self.engine,
|
|
expire_on_commit=False,
|
|
)()
|
|
|
|
def tearDown(self) -> None:
|
|
self.session.close()
|
|
Base.metadata.drop_all(self.engine, tables=reversed(TABLES))
|
|
self.engine.dispose()
|
|
|
|
def test_published_template_is_copied_into_tenant_owned_records(self) -> None:
|
|
definition, fingerprint = canonical_template_definition(_definition())
|
|
template = OrganizationModelTemplate(
|
|
id="template-1",
|
|
slug="municipality",
|
|
name="Municipality",
|
|
)
|
|
version = OrganizationModelTemplateVersion(
|
|
id="template-version-1",
|
|
template_id=template.id,
|
|
version="1.0.0",
|
|
status="published",
|
|
definition=definition,
|
|
definition_sha256=fingerprint,
|
|
)
|
|
self.session.add_all([template, version])
|
|
self.session.commit()
|
|
|
|
result = instantiate_template_version(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
template=template,
|
|
version=version,
|
|
actor_account_id="account-1",
|
|
)
|
|
self.session.commit()
|
|
|
|
self.assertEqual(
|
|
{
|
|
"unit_types": 1,
|
|
"structures": 1,
|
|
"relation_types": 1,
|
|
"units": 2,
|
|
"relations": 1,
|
|
"function_types": 1,
|
|
"functions": 1,
|
|
},
|
|
result.object_counts,
|
|
)
|
|
office = (
|
|
self.session.query(OrganizationUnit)
|
|
.filter(
|
|
OrganizationUnit.tenant_id == "tenant-1",
|
|
OrganizationUnit.slug == "office",
|
|
)
|
|
.one()
|
|
)
|
|
root = (
|
|
self.session.query(OrganizationUnit)
|
|
.filter(
|
|
OrganizationUnit.tenant_id == "tenant-1",
|
|
OrganizationUnit.slug == "municipality",
|
|
)
|
|
.one()
|
|
)
|
|
self.assertEqual(root.id, office.parent_id)
|
|
self.assertEqual(
|
|
"tenant_owned_no_live_inheritance",
|
|
result.provenance["copy_semantics"],
|
|
)
|
|
self.assertEqual(
|
|
"1.0.0",
|
|
office.settings["template_provenance"]["template_version"],
|
|
)
|
|
|
|
version.definition = {}
|
|
self.session.commit()
|
|
self.assertEqual("Office", office.name)
|
|
|
|
def test_instantiation_refuses_implicit_merge_into_existing_model(self) -> None:
|
|
definition, fingerprint = canonical_template_definition(_definition())
|
|
template = OrganizationModelTemplate(
|
|
id="template-2",
|
|
slug="municipality-2",
|
|
name="Municipality",
|
|
)
|
|
version = OrganizationModelTemplateVersion(
|
|
id="template-version-2",
|
|
template_id=template.id,
|
|
version="1",
|
|
status="published",
|
|
definition=definition,
|
|
definition_sha256=fingerprint,
|
|
)
|
|
self.session.add_all(
|
|
[
|
|
template,
|
|
version,
|
|
OrganizationUnitType(
|
|
tenant_id="tenant-1",
|
|
slug="existing",
|
|
name="Existing",
|
|
),
|
|
]
|
|
)
|
|
self.session.commit()
|
|
|
|
with self.assertRaisesRegex(
|
|
OrganizationTemplateError,
|
|
"only into an empty tenant model",
|
|
):
|
|
instantiate_template_version(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
template=template,
|
|
version=version,
|
|
actor_account_id="account-1",
|
|
)
|
|
|
|
def test_unknown_template_reference_is_rejected_before_storage(self) -> None:
|
|
definition = _definition().model_copy(deep=True)
|
|
definition.units[1].unit_type_slug = "unknown"
|
|
|
|
with self.assertRaisesRegex(
|
|
OrganizationTemplateError,
|
|
"unknown unit type",
|
|
):
|
|
canonical_template_definition(definition)
|
|
|
|
def test_parent_cycles_are_rejected_before_storage(self) -> None:
|
|
definition = _definition().model_copy(deep=True)
|
|
definition.units[0].parent_slug = "office"
|
|
|
|
with self.assertRaisesRegex(
|
|
OrganizationTemplateError,
|
|
"cycle in unit parent hierarchy",
|
|
):
|
|
canonical_template_definition(definition)
|
|
|
|
def test_relation_unit_type_mismatch_is_rejected(self) -> None:
|
|
definition = _definition().model_copy(deep=True)
|
|
definition.unit_types.append(
|
|
definition.unit_types[0].model_copy(
|
|
update={"slug": "other", "name": "Other"}
|
|
)
|
|
)
|
|
definition.units[1].unit_type_slug = "other"
|
|
|
|
with self.assertRaisesRegex(
|
|
OrganizationTemplateError,
|
|
"does not allow source unit",
|
|
):
|
|
canonical_template_definition(definition)
|
|
|
|
def test_function_unit_type_mismatch_is_rejected(self) -> None:
|
|
definition = _definition().model_copy(deep=True)
|
|
definition.unit_types.append(
|
|
definition.unit_types[0].model_copy(
|
|
update={"slug": "other", "name": "Other"}
|
|
)
|
|
)
|
|
definition.units[1].unit_type_slug = "other"
|
|
definition.relations = []
|
|
|
|
with self.assertRaisesRegex(
|
|
OrganizationTemplateError,
|
|
"does not apply to unit",
|
|
):
|
|
canonical_template_definition(definition)
|
|
|
|
|
|
def _definition() -> OrganizationModelTemplateDefinition:
|
|
return OrganizationModelTemplateDefinition.model_validate(
|
|
{
|
|
"unit_types": [
|
|
{
|
|
"slug": "administrative-unit",
|
|
"name": "Administrative unit",
|
|
}
|
|
],
|
|
"structures": [
|
|
{
|
|
"slug": "administrative",
|
|
"name": "Administrative hierarchy",
|
|
}
|
|
],
|
|
"relation_types": [
|
|
{
|
|
"slug": "reports-to",
|
|
"name": "Reports to",
|
|
"structure_slug": "administrative",
|
|
"source_unit_type_slug": "administrative-unit",
|
|
"target_unit_type_slug": "administrative-unit",
|
|
}
|
|
],
|
|
"units": [
|
|
{
|
|
"slug": "municipality",
|
|
"name": "Municipality",
|
|
"unit_type_slug": "administrative-unit",
|
|
},
|
|
{
|
|
"slug": "office",
|
|
"name": "Office",
|
|
"unit_type_slug": "administrative-unit",
|
|
"parent_slug": "municipality",
|
|
},
|
|
],
|
|
"relations": [
|
|
{
|
|
"structure_slug": "administrative",
|
|
"relation_type_slug": "reports-to",
|
|
"source_unit_slug": "office",
|
|
"target_unit_slug": "municipality",
|
|
}
|
|
],
|
|
"function_types": [
|
|
{
|
|
"slug": "head",
|
|
"name": "Head",
|
|
"organization_unit_type_slug": "administrative-unit",
|
|
}
|
|
],
|
|
"functions": [
|
|
{
|
|
"slug": "head",
|
|
"name": "Office head",
|
|
"function_type_slug": "head",
|
|
"organization_unit_slug": "office",
|
|
}
|
|
],
|
|
}
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|