from __future__ import annotations import unittest from govoplan_core.auth import ApiPrincipal from govoplan_core.core.access import PrincipalRef from govoplan_core.core.dataflows import DataflowDatasetRequest, DataflowRunConflictError from govoplan_core.db.base import Base from govoplan_core.db.session import configure_database, reset_database from govoplan_dataflow.backend.dataset_output import SqlDataflowDatasetOutputProvider from govoplan_dataflow.backend.db.models import DataflowPipeline, DataflowPipelineRevision from govoplan_dataflow.backend.schemas import PipelineGraph from govoplan_dataflow.backend.service import definition_hash def principal(tenant_id: str = "tenant-1") -> ApiPrincipal: return ApiPrincipal( principal=PrincipalRef( account_id="account-1", membership_id="membership-1", tenant_id=tenant_id, scopes=frozenset({"dataflow:pipeline:read", "dataflow:pipeline:run"}), ), account=object(), user=object(), ) class DataflowDatasetOutputTests(unittest.TestCase): def setUp(self) -> None: self.database = configure_database("sqlite:///:memory:") Base.metadata.create_all( self.database.engine, tables=[DataflowPipeline.__table__, DataflowPipelineRevision.__table__], ) def tearDown(self) -> None: reset_database(dispose=True) def test_pinned_revision_executes_through_typed_registry(self) -> None: graph = PipelineGraph.model_validate( { "nodes": [ { "id": "source", "type": "source.inline", "label": "Source", "position": {"x": 0, "y": 0}, "config": { "source_name": "audience", "rows": [ {"recipient_key": "one", "email": "one@example.test"}, {"recipient_key": "two", "email": "two@example.test"}, ] }, }, { "id": "output", "type": "output", "label": "Output", "position": {"x": 200, "y": 0}, "config": {}, }, ], "edges": [{"id": "edge-1", "source": "source", "target": "output"}], } ) content_hash = definition_hash(graph, None) with self.database.session() as session: pipeline = DataflowPipeline( id="pipeline-1", tenant_id="tenant-1", scope_type="tenant", scope_id="tenant-1", definition_kind="flow", name="Audience", status="active", current_revision=1, allow_run=True, metadata_={}, ) revision = DataflowPipelineRevision( id="revision-1", tenant_id="tenant-1", pipeline_id=pipeline.id, revision=1, graph=graph.model_dump(mode="json"), sql_text=None, editor_mode="graph", content_hash=content_hash, ) session.add_all((pipeline, revision)) session.flush() provider = SqlDataflowDatasetOutputProvider() descriptors = provider.list_outputs(session, principal()) result = provider.read_output( session, principal(), request=DataflowDatasetRequest( pipeline_ref=pipeline.id, revision=1, expected_definition_hash=content_hash, ), ) self.assertEqual(1, len(descriptors)) self.assertEqual(2, result.total_rows) self.assertEqual("one", result.rows[0]["recipient_key"]) self.assertEqual(64, len(result.output_hash)) replay = provider.read_output( session, principal(), request=DataflowDatasetRequest( pipeline_ref=pipeline.id, revision=1, expected_definition_hash=content_hash, expected_source_fingerprints=result.source_fingerprints, ), ) self.assertEqual(result.output_hash, replay.output_hash) with self.assertRaises(DataflowRunConflictError): provider.read_output( session, principal(), request=DataflowDatasetRequest( pipeline_ref=pipeline.id, revision=1, expected_definition_hash="wrong", ), ) with self.assertRaises(DataflowRunConflictError): provider.read_output( session, principal(), request=DataflowDatasetRequest( pipeline_ref=pipeline.id, revision=1, expected_definition_hash=content_hash, expected_source_fingerprints=( {"node_id": "source", "fingerprint": "changed"}, ), ), ) if __name__ == "__main__": unittest.main()