arm-ka-docs/scripts/kfo_contract/test_ex_payload.py

109 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Проверка схемы среза ЕХ ex-payload-v1 (docs #208): примеры проходят, типовые ошибки — нет.
Запуск: .venv/bin/python -I scripts/kfo_contract/test_ex_payload.py
"""
import copy
import json
import sys
from pathlib import Path
from jsonschema import Draft202012Validator
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "scripts"))
from validate_contracts import make_format_checker # noqa: E402 — та же проверка форматов, что в make check
SCHEMA = ROOT / "contracts" / "ex-payload-v1.schema.json"
EXAMPLES = sorted((ROOT / "contracts" / "examples").glob("ex-payload-*.json"))
REQUIRED_TYPES = {"summary_osv", "vgo_pairs", "chess", "kfo_balance", "kfo_income", "goodwill", "corrections",
"version_card"}
def main():
schema = json.loads(SCHEMA.read_text(encoding="utf-8"))
Draft202012Validator.check_schema(schema)
v = Draft202012Validator(schema, format_checker=make_format_checker())
by_type = {}
for p in EXAMPLES:
doc = json.loads(p.read_text(encoding="utf-8"))
errs = list(v.iter_errors(doc))
assert not errs, f"{p.name}: {errs[0].message[:200]}"
by_type[doc["item_type"]] = doc
missing = REQUIRED_TYPES - set(by_type)
assert not missing, f"нет примеров: {sorted(missing)}"
def rejected(doc, why):
assert list(v.iter_errors(doc)), f"схема пропустила: {why}"
# 1. item_type конверта не совпадает с формой data
bad = copy.deepcopy(by_type["kfo_balance"])
bad["data"] = copy.deepcopy(by_type["goodwill"]["data"])
rejected(bad, "kfo_balance с данными гудвилла")
# 2. kfo_balance без сводки корректировок в реквизитах
bad = copy.deepcopy(by_type["kfo_balance"])
del bad["data"]["requisites"]["corrections_balance"]
rejected(bad, "нет requisites.corrections_balance")
# 4. деньги числом
bad = copy.deepcopy(by_type["kfo_balance"])
bad["data"]["balance"]["lines"][0]["bank_amount"] = 2500.0
rejected(bad, "деньги числом")
# конверт: лишнее поле и неизвестная единица
bad = copy.deepcopy(by_type["version_card"])
bad["extra"] = 1
rejected(bad, "лишнее поле конверта")
bad = copy.deepcopy(by_type["summary_osv"])
bad["units"] = "mln_rub"
rejected(bad, "неизвестная единица")
# единица не соответствует типу
bad = copy.deepcopy(by_type["summary_osv"])
bad["units"] = "thousand_rub"
rejected(bad, "summary_osv в тысячах")
# ревью #208: легитимные ответы API проходят
ok = copy.deepcopy(by_type["summary_osv"])
ok["data"]["summary"]["gaps"][0]["missing_quarters"] = ["Q1", "Q2"]
assert not list(v.iter_errors(ok)), "C-1: пробелы свода как в API (Q1…Q4)"
ok = copy.deepcopy(by_type["chess"])
ok["data"]["matrix_all"]["cells"][0]["variance_percent"] = "10199959.95"
assert not list(v.iter_errors(ok)), "C-2: большой процент шахматки"
ok = copy.deepcopy(by_type["vgo_pairs"])
ok["data"]["balance"]["aggregates"][0]["deviation_percent"] = "10199959.95"
assert not list(v.iter_errors(ok)), "C-2: большой процент агрегата"
# ревью #208: то, что нужно выгрузкам, обязательно
for path, why in [(("chess", "matrix_all", "total_debit"), "I-1 итоги шахматки"),
(("vgo_pairs", "balance", "rules"), "I-1 правила таблицы корректировок"),
(("chess", "presets", 0, "rule"), "I-1 правило пресета")]:
bad = copy.deepcopy(by_type[path[0]])
node = bad["data"]
for k in path[1:-1]:
node = node[k]
del node[path[-1]]
rejected(bad, why)
bad = copy.deepcopy(by_type["corrections"])
del bad["data"]["items"][0]["correction"]["balance"]
rejected(bad, "I-1 balance корректировки")
bad = copy.deepcopy(by_type["vgo_pairs"])
bad["data"]["balance"]["aggregates"][0]["side_1"]["entries"][0]["entry_ids"] = []
rejected(bad, "I-1 строка агрегата без entry_ids")
bad = copy.deepcopy(by_type["corrections"])
bad["data"]["items"][0]["correction"]["updated_by"] = {"user_id": "x", "full_name": "y"}
rejected(bad, "I-2 updated_by не Ref")
# ревью #208: пустой и незамороженный payload
bad = copy.deepcopy(by_type["version_card"])
bad["data"]["members"] = []
rejected(bad, "M-1 карточка без состава")
bad = copy.deepcopy(by_type["kfo_balance"])
bad["data"]["is_frozen"] = False
rejected(bad, "M-1 КФО не заморожена")
bad = copy.deepcopy(by_type["version_card"])
bad["data"]["quarter"] = 3
rejected(bad, "M-3 квартал числом")
# форматы проверяются
bad = copy.deepcopy(by_type["version_card"])
bad["data"]["verified_at"] = "вчера"
rejected(bad, "M-6 date-time")
print(f"OK: примеров {len(EXAMPLES)}, отрицательных случаев 17, положительных граничных 3")
return 0
if __name__ == "__main__":
sys.exit(main())