fix(dataflow): preserve edits during saves and CSV staging evidence
Module Package Release / publish-packages (push) Successful in 15s
Module Package Release / publish-packages (push) Successful in 15s
Release v0.1.25. Coordinated integrity review: GovOPlaN/govoplan-core#298.
This commit is contained in:
Executable
+121
@@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from fastapi import HTTPException
|
||||
from pydantic import ValidationError
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal
|
||||
from govoplan_core.core.access import PrincipalRef
|
||||
from govoplan_dataflow.backend.router import WRITE_SCOPE, api_create_source_snapshot
|
||||
from govoplan_dataflow.backend.schemas import TabularSnapshotCreateRequest
|
||||
|
||||
|
||||
class CsvStagingRouteTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.principal = ApiPrincipal(
|
||||
principal=PrincipalRef(
|
||||
account_id="account",
|
||||
membership_id="membership",
|
||||
tenant_id="tenant",
|
||||
scopes=frozenset({WRITE_SCOPE}),
|
||||
),
|
||||
user=object(),
|
||||
account=object(),
|
||||
)
|
||||
self.session = Mock()
|
||||
self.writer = Mock()
|
||||
self.writer.create_stage.return_value = SimpleNamespace(ref="stage:one")
|
||||
self.writer.promote_stage.return_value = (
|
||||
SimpleNamespace(
|
||||
ref="datasource:one",
|
||||
provider="dataflow.upload",
|
||||
source_name="upload",
|
||||
row_count=1,
|
||||
fingerprint="f" * 64,
|
||||
),
|
||||
SimpleNamespace(ref="materialization:one"),
|
||||
)
|
||||
for name, options in (
|
||||
("get_registry", {"return_value": object()}),
|
||||
("datasource_lifecycle", {"return_value": self.writer}),
|
||||
("audit_event", {}),
|
||||
("_source_response", {"side_effect": lambda value: value}),
|
||||
):
|
||||
active = patch(f"govoplan_dataflow.backend.router.{name}", **options)
|
||||
active.start()
|
||||
self.addCleanup(active.stop)
|
||||
|
||||
def test_csv_stage_receives_exact_original_and_selected_parser_mode(self) -> None:
|
||||
text = 'value\r\n" 001 "\r\n'
|
||||
for mode, expected in (("text", " 001 "), ("legacy_typed", "001")):
|
||||
with self.subTest(mode=mode):
|
||||
api_create_source_snapshot(
|
||||
TabularSnapshotCreateRequest(
|
||||
name="Upload",
|
||||
source_name="upload",
|
||||
format="csv",
|
||||
csv_text=text,
|
||||
csv_value_mode=mode,
|
||||
),
|
||||
session=self.session,
|
||||
principal=self.principal,
|
||||
)
|
||||
stage = self.writer.create_stage.call_args.kwargs["stage"]
|
||||
self.assertEqual(({"value": expected},), stage.rows)
|
||||
self.assertEqual(text, stage.csv_source.text)
|
||||
self.assertEqual(mode, stage.csv_source.value_mode)
|
||||
self.assertEqual("core.csv.v1", stage.csv_source.parser_profile)
|
||||
self.assertNotIn("text", stage.metadata)
|
||||
self.assertEqual(2, self.writer.promote_stage.call_count)
|
||||
self.assertEqual(2, self.session.commit.call_count)
|
||||
|
||||
def test_json_stage_does_not_invent_csv_evidence(self) -> None:
|
||||
api_create_source_snapshot(
|
||||
TabularSnapshotCreateRequest(
|
||||
name="Upload", source_name="upload", rows=[{"value": "001"}]
|
||||
),
|
||||
session=self.session,
|
||||
principal=self.principal,
|
||||
)
|
||||
stage = self.writer.create_stage.call_args.kwargs["stage"]
|
||||
self.assertIsNone(stage.csv_source)
|
||||
self.assertEqual(({"value": "001"},), stage.rows)
|
||||
|
||||
def test_malformed_text_csv_is_422_before_any_durable_write(self) -> None:
|
||||
with self.assertRaises(HTTPException) as raised:
|
||||
api_create_source_snapshot(
|
||||
TabularSnapshotCreateRequest(
|
||||
name="Upload",
|
||||
source_name="upload",
|
||||
format="csv",
|
||||
csv_text="a,b\nonly-one\n",
|
||||
csv_value_mode="text",
|
||||
),
|
||||
session=self.session,
|
||||
principal=self.principal,
|
||||
)
|
||||
self.assertEqual(422, raised.exception.status_code)
|
||||
self.writer.create_stage.assert_not_called()
|
||||
self.writer.promote_stage.assert_not_called()
|
||||
self.session.commit.assert_not_called()
|
||||
|
||||
def test_invalid_unicode_is_rejected_by_request_schema_before_any_durable_write(
|
||||
self,
|
||||
) -> None:
|
||||
with self.assertRaises(ValidationError):
|
||||
api_create_source_snapshot(
|
||||
TabularSnapshotCreateRequest(
|
||||
name="Upload",
|
||||
source_name="upload",
|
||||
format="csv",
|
||||
csv_text="value\nprivate-\ud800\n",
|
||||
),
|
||||
session=self.session,
|
||||
principal=self.principal,
|
||||
)
|
||||
self.writer.create_stage.assert_not_called()
|
||||
self.writer.promote_stage.assert_not_called()
|
||||
self.session.commit.assert_not_called()
|
||||
Reference in New Issue
Block a user