134 lines
5.9 KiB
Python
134 lines
5.9 KiB
Python
"""Самопроверка compare_responses (без стенда)."""
|
|
import gzip
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "analysis"))
|
|
|
|
import compare_responses as cr # noqa: E402
|
|
|
|
|
|
def test_strip_nulls_pg_semantics():
|
|
v = {"a": None, "b": {"c": None}, "d": [None, {"e": None, "f": 1}], "g": {}}
|
|
assert cr.strip_nulls(v) == {"b": {}, "d": [None, {"f": 1}], "g": {}}
|
|
|
|
|
|
def test_strip_response_keeps_wrapper_and_sort_order():
|
|
body = {"success": True, "message": None, "count": 1,
|
|
"result": [{"row_type": "INPUT", "depth": 3, "sort_order": None, "data": {"x": None, "y": 1}}]}
|
|
out = cr.strip_response(body)
|
|
assert out["message"] is None
|
|
assert out["result"][0]["sort_order"] is None
|
|
assert out["result"][0]["data"] == {"y": 1}
|
|
assert body["result"][0]["data"] == {"x": None, "y": 1} # исходное не изменено
|
|
|
|
|
|
def test_first_diff_path():
|
|
a = {"result": [{"data": {"q1": {"m1": 1}}}]}
|
|
b = {"result": [{"data": {"q1": {"m1": 2}}}]}
|
|
assert cr.first_diff(a, b).startswith("$.result[0].data.q1.m1")
|
|
assert cr.first_diff(a, a) is None
|
|
assert cr.first_diff({"a": [1]}, {"a": [1, 2]}).startswith("$.a")
|
|
assert cr.first_diff({"a": 1}, {"a": 1, "b": 2}).startswith("$")
|
|
|
|
|
|
def _write(d: Path, name: str, body: dict, status: int = 200):
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
(d / f"{name}.json.gz").write_bytes(gzip.compress(json.dumps(body).encode()))
|
|
idx = d / "index.json"
|
|
cur = json.loads(idx.read_text()) if idx.exists() else {}
|
|
cur[name] = status
|
|
idx.write_text(json.dumps(cur))
|
|
|
|
|
|
def _body(data):
|
|
return {"success": True, "message": None, "count": 1,
|
|
"result": [{"row_type": "INPUT", "depth": 1, "sort_order": 1, "data": data}]}
|
|
|
|
|
|
def test_compare_exact_and_strip_modes(tmp_path):
|
|
b, a = tmp_path / "before", tmp_path / "after"
|
|
for name in ("sheet-FORM_2-AHR-none-1", "report-5-2026-LIMIT"):
|
|
_write(b, name, _body({"x": None, "y": 1}))
|
|
_write(a, name, _body({"y": 1}))
|
|
n, bad = cr.compare(b, a, "exact")
|
|
assert n == 0 and len(bad) == 2
|
|
n, bad = cr.compare(b, a, "strip-nulls")
|
|
assert n == 1 and len(bad) == 1 and "report-5-2026-LIMIT" in bad[0]
|
|
|
|
|
|
def test_compare_status_mismatch(tmp_path):
|
|
b, a = tmp_path / "before", tmp_path / "after"
|
|
_write(b, "svod1-MAIN", _body({"y": 1}), 200)
|
|
_write(a, "svod1-MAIN", _body({"y": 1}), 500)
|
|
n, bad = cr.compare(b, a, "exact")
|
|
assert n == 0 and len(bad) == 1
|
|
|
|
|
|
def test_compare_missing_after(tmp_path):
|
|
b, a = tmp_path / "before", tmp_path / "after"
|
|
_write(b, "svod1-MAIN", _body({}))
|
|
a.mkdir()
|
|
n, bad = cr.compare(b, a, "exact")
|
|
assert n == 0 and len(bad) == 1
|
|
|
|
|
|
def test_build_requests_minimal_manifest():
|
|
man = {"sheets": [
|
|
{"form_id": 1, "form_type": "FORM_2", "sheet": "AHR", "direction": None, "year": 2026, "url": "", "rows": 1, "owner": "rf"},
|
|
{"form_id": 2, "form_type": "FORM_1", "sheet": "AHR", "direction": "Support", "year": 2026, "url": "", "rows": 1, "owner": "ssp"},
|
|
], "project_reports": [{"project_id": 5, "year": 2026, "report_type": "LIMIT", "url": "", "rows": 1}]}
|
|
reqs = cr.build_requests(man)
|
|
by = {n: (r, p) for r, n, p in reqs}
|
|
assert by["sheet-FORM_2-AHR-none-1"] == ("rf", "/api/v1/form/1/sheet/AHR")
|
|
assert by["sheet-FORM_2-AHR-none-1-sections"] == ("rf", "/api/v1/form/1/sheet/AHR?sections=plan,q1")
|
|
assert by["sheet-FORM_1-AHR-Support-2"] == ("dfip", "/api/v1/form/2/sheet/AHR?direction=Support")
|
|
assert sum(n.startswith("svod1-") for n in by) == 5
|
|
assert sum(n.startswith("svod2-") for n in by) == 2
|
|
assert by["svod1-MAIN"] == ("dfip", "/api/v1/svod/1/MAIN/2026")
|
|
assert by["report-5-2026-LIMIT"][0] == "rf"
|
|
assert by["project-summary-5"] == ("rf", "/api/v1/projects/5/summary")
|
|
assert {"projects-plan-summary", "projects-fact-summary", "projects-detail-summary"} <= set(by)
|
|
assert not any("rollup" in n for n in by)
|
|
assert cr.is_stripped("sheet-x") and cr.is_stripped("svod2-GO") and not cr.is_stripped("report-1")
|
|
|
|
|
|
def test_strip_response_without_result():
|
|
assert cr.strip_response({"success": False}) == {"success": False}
|
|
assert cr.strip_response({"result": None}) == {"result": None}
|
|
|
|
|
|
def test_compare_non_json_bodies(tmp_path):
|
|
b, a = tmp_path / "before", tmp_path / "after"
|
|
for name, bb, ab in (("svod1-A", b"<html>502", b"<html>502"), ("svod1-B", b"<html>502", b"<html>503"),
|
|
("svod1-C", b"<html>", b'{"result": []}')):
|
|
for d, raw in ((b, bb), (a, ab)):
|
|
d.mkdir(exist_ok=True)
|
|
(d / f"{name}.json.gz").write_bytes(gzip.compress(raw))
|
|
for d in (b, a):
|
|
(d / "index.json").write_text(json.dumps({"svod1-A": 502, "svod1-B": 502, "svod1-C": 502}))
|
|
n, bad = cr.compare(b, a, "strip-nulls")
|
|
assert n == 1 and len(bad) == 2
|
|
|
|
|
|
def test_dump_survives_network_error(tmp_path):
|
|
import httpx
|
|
|
|
def handler(req: httpx.Request) -> httpx.Response:
|
|
if req.url.path == "/api/v1/auth/login":
|
|
return httpx.Response(200, json={"access_token": "t"})
|
|
if req.url.path.startswith("/api/v1/svod/1/MAIN"):
|
|
raise httpx.ConnectError("boom", request=req)
|
|
return httpx.Response(200, json={"result": []})
|
|
|
|
man = {"users": {"rf": {"username": "u", "password": "p"}, "dfip": {"username": "u", "password": "p"}},
|
|
"sheets": [{"form_id": 1, "form_type": "FORM_2", "sheet": "AHR", "direction": None, "year": 2026,
|
|
"url": "", "rows": 1, "owner": "rf"}], "project_reports": []}
|
|
with httpx.Client(base_url="http://x", transport=httpx.MockTransport(handler)) as c:
|
|
idx = cr.dump(man, c, tmp_path / "o")
|
|
assert idx["svod1-MAIN"] == "error: ConnectError"
|
|
assert idx["sheet-FORM_2-AHR-none-1"] == 200
|
|
assert json.loads((tmp_path / "o" / "index.json").read_text()) == idx
|
|
assert not (tmp_path / "o" / "svod1-MAIN.json.gz").exists()
|