Exercise recovery in datasource composition check
Dependency Audit / dependency-audit (push) Successful in 1m40s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 10m14s

This commit is contained in:
2026-08-03 07:46:57 +02:00
parent ac80d7e4e3
commit c7d1cd0e8f
+45 -10
View File
@@ -25,6 +25,12 @@ from govoplan_core.core.datasources import (
datasource_publication, datasource_publication,
) )
from govoplan_core.core.modules import ModuleContext from govoplan_core.core.modules import ModuleContext
from govoplan_core.core.recovery import RecoveryCheckpoint, RecoveryOperation
from govoplan_core.core.runtime_coordination import (
DistributedLease,
RuntimeIdentity,
bind_process_runtime_identity,
)
from govoplan_core.core.tabular_sources import ( from govoplan_core.core.tabular_sources import (
TabularSnapshotInput, TabularSnapshotInput,
tabular_snapshot_writer, tabular_snapshot_writer,
@@ -73,6 +79,9 @@ def main() -> int:
Base.metadata.create_all( Base.metadata.create_all(
engine, engine,
tables=[ tables=[
DistributedLease.__table__,
RecoveryOperation.__table__,
RecoveryCheckpoint.__table__,
ConnectorTabularSource.__table__, ConnectorTabularSource.__table__,
DatasourceRecord.__table__, DatasourceRecord.__table__,
DatasourcePayloadRecord.__table__, DatasourcePayloadRecord.__table__,
@@ -86,6 +95,8 @@ def main() -> int:
], ],
) )
session_factory = sessionmaker(bind=engine) session_factory = sessionmaker(bind=engine)
bind_process_runtime_identity(_runtime_identity())
try:
with session_factory() as session: with session_factory() as session:
principal = _principal() principal = _principal()
writer = tabular_snapshot_writer(registry) writer = tabular_snapshot_writer(registry)
@@ -100,7 +111,9 @@ def main() -> int:
or publisher is None or publisher is None
or runner is None or runner is None
): ):
raise RuntimeError("Datasource composition capabilities are incomplete.") raise RuntimeError(
"Datasource composition capabilities are incomplete."
)
origin = writer.create_snapshot( origin = writer.create_snapshot(
session, session,
@@ -141,11 +154,15 @@ def main() -> int:
{"id": 2, "amount": 15}, {"id": 2, "amount": 15},
] ]
if result.status != "succeeded": if result.status != "succeeded":
raise RuntimeError(f"Dataflow preview failed: {result.diagnostics}") raise RuntimeError(
f"Dataflow preview failed: {result.diagnostics}"
)
if result.rows != expected_rows: if result.rows != expected_rows:
raise RuntimeError(f"Unexpected Dataflow rows: {result.rows!r}") raise RuntimeError(f"Unexpected Dataflow rows: {result.rows!r}")
if result.source_fingerprints[0]["source_ref"] != datasource.ref: if result.source_fingerprints[0]["source_ref"] != datasource.ref:
raise RuntimeError("Dataflow lineage did not retain the datasource reference.") raise RuntimeError(
"Dataflow lineage did not retain the datasource reference."
)
pipeline = create_pipeline( pipeline = create_pipeline(
session, session,
tenant_id="tenant-1", tenant_id="tenant-1",
@@ -193,9 +210,7 @@ def main() -> int:
worker_id="composition-worker", worker_id="composition-worker",
) )
if worker_result["succeeded"] != 1: if worker_result["succeeded"] != 1:
raise RuntimeError( raise RuntimeError(f"Dataflow worker failed: {worker_result!r}")
f"Dataflow worker failed: {worker_result!r}"
)
completed = runner.get_run( completed = runner.get_run(
session, session,
principal, principal,
@@ -205,14 +220,20 @@ def main() -> int:
raise RuntimeError("Dataflow run evidence disappeared.") raise RuntimeError("Dataflow run evidence disappeared.")
published = completed published = completed
if published.status != "succeeded": if published.status != "succeeded":
raise RuntimeError(f"Dataflow publication failed: {published.error}") raise RuntimeError(
f"Dataflow publication failed: {published.error}"
)
if replayed.ref != published.ref or not replayed.replayed: if replayed.ref != published.ref or not replayed.replayed:
raise RuntimeError("Dataflow run idempotency did not replay the prior run.") raise RuntimeError(
"Dataflow run idempotency did not replay the prior run."
)
if ( if (
not published.output_datasource_ref not published.output_datasource_ref
or not published.output_materialization_ref or not published.output_materialization_ref
): ):
raise RuntimeError("Dataflow publication did not retain output references.") raise RuntimeError(
"Dataflow publication did not retain output references."
)
output = catalogue.read_datasource( output = catalogue.read_datasource(
session, session,
principal, principal,
@@ -226,12 +247,15 @@ def main() -> int:
) )
if ( if (
output.materialization is None output.materialization is None
or output.materialization.ref != published.output_materialization_ref or output.materialization.ref
!= published.output_materialization_ref
or output.materialization.frozen_at is None or output.materialization.frozen_at is None
): ):
raise RuntimeError( raise RuntimeError(
"Published Datasource materialization is not pinned and frozen." "Published Datasource materialization is not pinned and frozen."
) )
finally:
bind_process_runtime_identity(None)
engine.dispose() engine.dispose()
print( print(
"Connector -> Datasources -> pinned Dataflow publication composition passed." "Connector -> Datasources -> pinned Dataflow publication composition passed."
@@ -252,6 +276,17 @@ class _AutomationProvider:
) )
def _runtime_identity() -> RuntimeIdentity:
return RuntimeIdentity(
installation_id="datasource-composition-check",
node_id="composition-worker",
incarnation="composition-worker-incarnation",
role="worker",
software_version="test",
composition_hash="c" * 64,
)
class _AutomationRegistry: class _AutomationRegistry:
def __init__(self, registry, principal: ApiPrincipal) -> None: def __init__(self, registry, principal: ApiPrincipal) -> None:
self.registry = registry self.registry = registry