Files
govoplan-workflow-engine/tests/test_migrations.py
T

270 lines
10 KiB
Python

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
import shutil
from alembic.runtime.migration import MigrationContext
from sqlalchemy import create_engine, inspect, text
from govoplan_core.db.migrations import migrate_database
from dataclasses import replace
from govoplan_workflow_engine.backend.manifest import get_manifest
class WorkflowMigrationTests(unittest.TestCase):
def test_migration_creates_definition_tables_and_head(self) -> None:
with tempfile.TemporaryDirectory(
prefix="govoplan-workflow-engine-migration-"
) as directory:
url = f"sqlite:///{Path(directory) / 'workflow.db'}"
migrate_database(
database_url=url,
enabled_modules=("workflow_engine",),
manifest_factories=(get_manifest,),
)
engine = create_engine(url)
try:
with engine.connect() as connection:
self.assertIn(
"8d5a2f7c1b4e",
set(MigrationContext.configure(connection).get_current_heads()),
)
self.assertEqual(
{
"workflow_definition_revisions",
"workflow_definitions",
"workflow_instance_events",
"workflow_instance_steps",
"workflow_instances",
"workflow_triggers",
"workflow_trigger_deliveries",
"workflow_wait_states",
},
{
name
for name in inspect(connection).get_table_names()
if name.startswith("workflow_")
},
)
revision_columns = {
item["name"]
for item in inspect(connection).get_columns(
"workflow_definition_revisions"
)
}
self.assertTrue(
{
"bpmn_xml",
"bpmn_hash",
"bpmn_adapter_id",
"bpmn_adapter_version",
"bpmn_runtime_kind",
"bpmn_executable",
"execution_mode",
"view_id",
"view_revision_id",
}.issubset(revision_columns)
)
instance_columns = {
item["name"]
for item in inspect(connection).get_columns(
"workflow_instances"
)
}
self.assertIn("start_origin", instance_columns)
definition_columns = {
item["name"]
for item in inspect(connection).get_columns(
"workflow_definitions"
)
}
self.assertIn(
"standard_origin_module_id",
definition_columns,
)
finally:
engine.dispose()
def test_existing_workflow_migration_head_transfers_without_replay(self) -> None:
with tempfile.TemporaryDirectory(
prefix="govoplan-workflow-engine-upgrade-"
) as directory:
url = f"sqlite:///{Path(directory) / 'workflow.db'}"
engine_manifest = get_manifest()
assert engine_manifest.migration_spec is not None
legacy_revisions = Path(directory) / "legacy-revisions"
legacy_revisions.mkdir()
current_revisions = Path(
engine_manifest.migration_spec.script_location or ""
)
for path in current_revisions.glob("*.py"):
if path.name.startswith(
(
"0b4e7c9a2d6f_",
"b2e4f6a8c0d1_",
"e4a1f8c2d7b6_",
"8d5a2f7c1b4e_",
)
):
continue
shutil.copy2(path, legacy_revisions / path.name)
legacy_manifest = replace(
engine_manifest,
id="workflow",
name="Workflow",
permission_namespace=None,
migration_spec=replace(
engine_manifest.migration_spec,
module_id="workflow",
script_location=str(legacy_revisions),
),
)
migrate_database(
database_url=url,
enabled_modules=("workflow",),
manifest_factories=(lambda: legacy_manifest,),
)
engine = create_engine(url)
try:
first_tables = set(inspect(engine).get_table_names())
with engine.connect() as connection:
self.assertIn(
"f1b7d3e5a9c2",
set(MigrationContext.configure(connection).get_current_heads()),
)
self.assertNotIn(
"standard_origin_module_id",
{
item["name"]
for item in inspect(engine).get_columns("workflow_definitions")
},
)
finally:
engine.dispose()
result = migrate_database(
database_url=url,
enabled_modules=("workflow_engine",),
manifest_factories=(get_manifest,),
)
self.assertIn("8d5a2f7c1b4e", result.current_revision or "")
engine = create_engine(url)
try:
upgraded_tables = set(inspect(engine).get_table_names())
self.assertTrue(first_tables.issubset(upgraded_tables))
self.assertTrue(
{
"workflow_triggers",
"workflow_trigger_deliveries",
"workflow_wait_states",
}.issubset(upgraded_tables)
)
self.assertIn(
"standard_origin_module_id",
{
item["name"]
for item in inspect(engine).get_columns("workflow_definitions")
},
)
finally:
engine.dispose()
def test_repair_revision_adds_columns_missing_from_applied_parent(self) -> None:
with tempfile.TemporaryDirectory(
prefix="govoplan-workflow-engine-repair-"
) as directory:
url = f"sqlite:///{Path(directory) / 'workflow.db'}"
migrate_database(
database_url=url,
enabled_modules=("workflow_engine",),
manifest_factories=(get_manifest,),
)
engine = create_engine(url)
try:
with engine.begin() as connection:
for index_name in (
"ix_workflow_instance_steps_work_assignment",
"ix_workflow_instance_steps_work_due_at",
"ix_workflow_instance_steps_work_assignment_id",
"ix_workflow_instance_steps_work_assignment_kind",
):
connection.execute(text(f"DROP INDEX {index_name}"))
for column_name in (
"work_due_at",
"work_assignment_label",
"work_assignment_id",
"work_assignment_kind",
):
connection.execute(
text(
"ALTER TABLE workflow_instance_steps "
f"DROP COLUMN {column_name}"
)
)
connection.execute(
text(
"ALTER TABLE workflow_definition_revisions "
"DROP COLUMN bpmn_runtime_kind"
)
)
connection.execute(
text(
"ALTER TABLE workflow_definition_revisions "
"DROP COLUMN bpmn_executable"
)
)
connection.execute(
text(
"UPDATE alembic_version SET version_num = "
"'b2e4f6a8c0d1' WHERE version_num = '8d5a2f7c1b4e'"
)
)
finally:
engine.dispose()
migrate_database(
database_url=url,
enabled_modules=("workflow_engine",),
manifest_factories=(get_manifest,),
)
engine = create_engine(url)
try:
columns = {
item["name"]
for item in inspect(engine).get_columns(
"workflow_definition_revisions"
)
}
self.assertIn("bpmn_runtime_kind", columns)
self.assertIn("bpmn_executable", columns)
step_columns = {
item["name"]
for item in inspect(engine).get_columns(
"workflow_instance_steps"
)
}
self.assertTrue(
{
"work_assignment_kind",
"work_assignment_id",
"work_assignment_label",
"work_due_at",
}.issubset(step_columns)
)
with engine.connect() as connection:
self.assertIn(
"8d5a2f7c1b4e",
set(MigrationContext.configure(connection).get_current_heads()),
)
finally:
engine.dispose()
if __name__ == "__main__":
unittest.main()