mirror of
https://github.com/hansjone/netx.git
synced 2026-10-12 06:30:44 +08:00
Fix business compare history, search, sampling and exports
This commit is contained in:
parent
afa8e06b2d
commit
4d5101ec3a
19 changed files with 1598 additions and 274 deletions
87
tests/test_biz_state_compare_export.py
Normal file
87
tests/test_biz_state_compare_export.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
"""Export must page lazily, keep tied sequence rows, and produce valid UTF-8 CSV."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import io
|
||||
import unittest
|
||||
import zipfile
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from netx_api.biz_state.compare_service import _iter_sheet_diffs, export_run_zip
|
||||
from netx_api.db import Base
|
||||
from netx_api.models import BizCompareDiff, BizCompareRun
|
||||
|
||||
|
||||
class CompareExportTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.engine = create_engine("sqlite+pysqlite:///:memory:")
|
||||
Base.metadata.create_all(self.engine)
|
||||
self.db = Session(self.engine)
|
||||
self.sheet = {"sheet_id": "sheet", "metric_id": "test", "key_fields": ["id"],
|
||||
"compare_fields": ["note"], "display_fields": ["id", "note"],
|
||||
"summary": {"unchanged": 7}, "status": "done"}
|
||||
self.run = BizCompareRun(id="run", metric_id="test", status="success",
|
||||
summary_json={"sheets": [self.sheet]})
|
||||
self.db.add(self.run)
|
||||
for seq, ids in ((0, "cba"), (1, "ed"), (2, "gf")):
|
||||
for rid in ids:
|
||||
note = '中文, "quoted"\nsecond line'
|
||||
self.db.add(BizCompareDiff(id=rid, run_id="run", metric_id="sheet", seq=seq,
|
||||
kind="unchanged", key_json={"id": rid},
|
||||
before_json={"id": rid, "note": note},
|
||||
after_json={"id": rid, "note": note}))
|
||||
self.db.commit()
|
||||
|
||||
def tearDown(self):
|
||||
self.db.close()
|
||||
self.engine.dispose()
|
||||
|
||||
def test_cursor_handles_tied_sequence_numbers_across_chunks(self):
|
||||
statements = []
|
||||
def record(_conn, _cursor, statement, _parameters, _context, _executemany):
|
||||
if "ORDER BY biz_compare_diff.seq" in statement:
|
||||
statements.append(statement)
|
||||
event.listen(self.engine, "before_cursor_execute", record)
|
||||
with patch("netx_api.biz_state.compare_service._DIFF_CHUNK", 2):
|
||||
iterator = _iter_sheet_diffs(self.db, "run", "sheet")
|
||||
self.assertEqual(statements, [])
|
||||
self.assertEqual(next(iterator)["key"]["id"], "a")
|
||||
self.assertEqual(next(iterator)["key"]["id"], "b")
|
||||
self.assertEqual(len(statements), 1, "do not load the next chunk early")
|
||||
self.assertEqual([d["key"]["id"] for d in iterator], list("cdefg"))
|
||||
self.assertEqual(len(statements), 4)
|
||||
self.assertTrue(all("biz_compare_diff.seq >" in q and "biz_compare_diff.id >" in q
|
||||
for q in statements[1:]))
|
||||
|
||||
def test_export_csv_escapes_unicode_quotes_commas_and_newlines(self):
|
||||
with patch("netx_api.biz_state.compare_service._DIFF_CHUNK", 2):
|
||||
archive_bytes = export_run_zip(self.db, "run")
|
||||
with zipfile.ZipFile(io.BytesIO(archive_bytes)) as archive:
|
||||
payload = archive.read("tables/sheet.csv")
|
||||
self.assertTrue(payload.startswith(b"\xef\xbb\xbf"))
|
||||
rows = list(csv.DictReader(io.StringIO(payload.decode("utf-8-sig"))))
|
||||
self.assertEqual([r["id"] for r in rows], list("abcdefg"))
|
||||
self.assertTrue(all(r["note__pre"] == '中文, "quoted"\nsecond line' for r in rows))
|
||||
self.assertTrue(all(r["note__pre"] == r["note__post"] for r in rows))
|
||||
|
||||
def test_other_sheets_do_not_leak_into_export(self):
|
||||
self.db.add(BizCompareDiff(id="other", run_id="run", metric_id="another", seq=0,
|
||||
kind="added", key_json={"id": "other"}, after_json={"note": "other"}))
|
||||
self.db.commit()
|
||||
with patch("netx_api.biz_state.compare_service._DIFF_CHUNK", 2):
|
||||
self.assertEqual([d["key"]["id"] for d in _iter_sheet_diffs(self.db, "run", "sheet")], list("abcdefg"))
|
||||
|
||||
def test_legacy_inline_and_empty_exports(self):
|
||||
self.db.query(BizCompareDiff).delete()
|
||||
inline = {"kind": "added", "key": {"id": "legacy"}, "after": {"note": "value"}}
|
||||
self.run.summary_json = {"sheets": [{**self.sheet, "diffs": [inline]}]}
|
||||
self.db.commit()
|
||||
self.assertEqual(list(_iter_sheet_diffs(self.db, "run", "sheet")), [inline])
|
||||
self.run.summary_json = {"sheets": [self.sheet]}
|
||||
self.db.commit()
|
||||
with zipfile.ZipFile(io.BytesIO(export_run_zip(self.db, "run"))) as archive:
|
||||
self.assertEqual(archive.read("tables/sheet.csv").decode("utf-8-sig"), "kind,id,note__pre,note__post\n")
|
||||
Loading…
Add table
Add a link
Reference in a new issue