223 lines
8.7 KiB
Python
223 lines
8.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import scripts.audit_1c_adapter_coverage as coverage # noqa: E402
|
|
|
|
|
|
def test_record_ref_from_public_reference_value() -> None:
|
|
ref = "0123456789ABCDEF0123456789ABCDEF"
|
|
assert coverage.record_ref_from_row({"ref": ref}) == ref
|
|
assert coverage.record_ref_from_row({"ref": {"type": "reference", "hex": ref}}) == ref
|
|
assert coverage.record_ref_from_row({"period": "2026-01-01"}) is None
|
|
|
|
|
|
def test_audit_data_kind_runs_complete_read_chain() -> None:
|
|
calls: list[tuple[str, dict[str, Any]]] = []
|
|
ref = "0123456789ABCDEF0123456789ABCDEF"
|
|
|
|
def fake_rpc(_url: str, _token: str, method: str, payload: dict[str, Any], _timeout: float) -> dict[str, Any]:
|
|
calls.append((method, payload))
|
|
responses = {
|
|
"metadata.objects.list": {"status": "ok", "objects": [{"kind": "Catalog", "name": "Товары", "ref": "Catalog.Товары"}]},
|
|
"data.schema": {"status": "ok", "table": {"name": "_Reference1"}, "fields": [{"name": "ref"}]},
|
|
"data.list": {"status": "ok", "rows": [{"ref": {"type": "reference", "hex": ref}}]},
|
|
"data.count": {"status": "ok", "count": 7},
|
|
"data.get": {"status": "ok", "rows": [{"ref": ref}]},
|
|
}
|
|
return responses[method]
|
|
|
|
result = coverage.audit_data_kind(
|
|
"http://adapter",
|
|
"upo_test",
|
|
"secret",
|
|
"Catalog",
|
|
10,
|
|
True,
|
|
fake_rpc,
|
|
)
|
|
|
|
assert result["status"] == "ok"
|
|
assert [method for method, _ in calls] == [
|
|
"metadata.objects.list",
|
|
"data.schema",
|
|
"data.list",
|
|
"data.count",
|
|
"data.get",
|
|
]
|
|
assert calls[-1][1]["record_ref"] == ref
|
|
assert result["operations"]["data.count"]["count"] == 7
|
|
|
|
|
|
def test_audit_data_kind_marks_get_not_applicable_for_register_row() -> None:
|
|
def fake_rpc(_url: str, _token: str, method: str, _payload: dict[str, Any], _timeout: float) -> dict[str, Any]:
|
|
responses = {
|
|
"metadata.objects.list": {"status": "ok", "objects": [{"kind": "InformationRegister", "name": "Курсы"}]},
|
|
"data.schema": {"status": "ok", "table": {"name": "_InfoRg1"}, "fields": [{"name": "period"}]},
|
|
"data.list": {"status": "ok", "rows": [{"period": "2026-01-01"}]},
|
|
"data.count": {"status": "ok", "count": 1},
|
|
}
|
|
return responses[method]
|
|
|
|
result = coverage.audit_data_kind(
|
|
"http://adapter", "upo_test", "secret", "InformationRegister", 10, True, fake_rpc
|
|
)
|
|
|
|
assert result["status"] == "ok"
|
|
assert result["operations"]["data.get"] == {
|
|
"status": "not_applicable",
|
|
"reason": "object_has_no_reference_key",
|
|
"duration_ms": 0,
|
|
}
|
|
|
|
|
|
def test_audit_data_kind_does_not_mark_get_not_applicable_when_list_failed() -> None:
|
|
def fake_rpc(_url: str, _token: str, method: str, _payload: dict[str, Any], _timeout: float) -> dict[str, Any]:
|
|
responses = {
|
|
"metadata.objects.list": {"status": "ok", "objects": [{"kind": "Document", "name": "Продажа"}]},
|
|
"data.schema": {"status": "ok", "table": {"name": "_Document1"}, "fields": [{"name": "ref"}]},
|
|
"data.list": {"status": "transport_error", "diagnostics": {"message": "timed out"}},
|
|
"data.count": {"status": "ok", "count": 3},
|
|
}
|
|
return responses[method]
|
|
|
|
result = coverage.audit_data_kind(
|
|
"http://adapter", "upo_test", "secret", "Document", 10, True, fake_rpc
|
|
)
|
|
|
|
assert result["status"] == "degraded"
|
|
assert result["operations"]["data.get"]["status"] == "blocked"
|
|
assert result["operations"]["data.get"]["reason"] == "data_list_failed"
|
|
|
|
|
|
def test_run_data_checks_saves_and_resumes_checkpoint(tmp_path: Path) -> None:
|
|
checkpoint = tmp_path / "coverage.checkpoint.json"
|
|
calls: list[str] = []
|
|
|
|
def fake_rpc(_url: str, _token: str, method: str, payload: dict[str, Any], _timeout: float) -> dict[str, Any]:
|
|
calls.append(f"{method}:{payload.get('kind', '')}")
|
|
if method == "metadata.objects.list":
|
|
return {"status": "ok", "objects": [{"kind": payload["kind"], "name": payload["kind"]}]}
|
|
if method == "data.schema":
|
|
return {"status": "ok", "table": {"name": "_Data1"}, "fields": []}
|
|
raise AssertionError(method)
|
|
|
|
first, resumed = coverage.run_data_checks(
|
|
"http://adapter", "upo_test", "secret", ["Catalog", "Document"], 10, False, 2, checkpoint, False, False, fake_rpc
|
|
)
|
|
assert resumed == 0
|
|
assert set(first) == {"Catalog", "Document"}
|
|
saved = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
assert saved["include_reads"] is False
|
|
assert set(saved["checks"]) == {"Catalog", "Document"}
|
|
|
|
calls.clear()
|
|
second, resumed = coverage.run_data_checks(
|
|
"http://adapter", "upo_test", "secret", ["Catalog", "Document"], 10, False, 2, checkpoint, True, False, fake_rpc
|
|
)
|
|
assert resumed == 2
|
|
assert second == first
|
|
assert calls == []
|
|
|
|
|
|
def test_run_data_checks_can_retry_only_degraded_entries(tmp_path: Path) -> None:
|
|
checkpoint = tmp_path / "coverage.checkpoint.json"
|
|
checkpoint.write_text(
|
|
json.dumps(
|
|
{
|
|
"schema": "onec_adapter_data_audit_checkpoint.v1",
|
|
"base_url": "http://adapter",
|
|
"base_id": "upo_test",
|
|
"include_reads": False,
|
|
"checks": {
|
|
"Catalog": {"kind": "Catalog", "status": "ok"},
|
|
"Document": {"kind": "Document", "status": "degraded"},
|
|
},
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
calls: list[str] = []
|
|
|
|
def fake_rpc(_url: str, _token: str, method: str, payload: dict[str, Any], _timeout: float) -> dict[str, Any]:
|
|
calls.append(method)
|
|
if method == "metadata.objects.list":
|
|
return {"status": "ok", "objects": [{"kind": payload["kind"], "name": "Продажа"}]}
|
|
if method == "data.schema":
|
|
return {"status": "ok", "table": {"name": "_Document1"}, "fields": []}
|
|
raise AssertionError(method)
|
|
|
|
checks, resumed = coverage.run_data_checks(
|
|
"http://adapter", "upo_test", "secret", ["Catalog", "Document"], 10, False, 1, checkpoint, True, True, fake_rpc
|
|
)
|
|
|
|
assert resumed == 1
|
|
assert checks["Catalog"]["status"] == "ok"
|
|
assert checks["Document"]["status"] == "ok"
|
|
assert calls == ["metadata.objects.list", "data.schema"]
|
|
|
|
|
|
def test_checkpoint_rejects_schema_only_resume_for_full_reads(tmp_path: Path) -> None:
|
|
checkpoint = tmp_path / "coverage.checkpoint.json"
|
|
checkpoint.write_text(
|
|
json.dumps(
|
|
{
|
|
"schema": "onec_adapter_data_audit_checkpoint.v1",
|
|
"base_url": "http://adapter",
|
|
"base_id": "upo_test",
|
|
"include_reads": False,
|
|
"checks": {},
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
with pytest.raises(ValueError, match="another data audit mode"):
|
|
coverage.load_checkpoint(checkpoint, "http://adapter", "upo_test", True, True)
|
|
|
|
|
|
def test_build_report_does_not_treat_unchecked_non_data_kind_as_list_failure() -> None:
|
|
def fake_rpc(_url: str, _token: str, method: str, payload: dict[str, Any], _timeout: float) -> dict[str, Any]:
|
|
if method == "metadata.adapter.audit":
|
|
return {
|
|
"status": "ok",
|
|
"optional_deep_reads": [
|
|
{"kind": "DocumentJournal", "property": "column_types", "flag": "include_column_types=true"}
|
|
],
|
|
"metadata_kinds": [
|
|
{"kind": "Catalog", "kind_ru": "Справочник", "count": 1, "capabilities": ["list"]},
|
|
{"kind": "CommonModule", "kind_ru": "ОбщийМодуль", "count": 1, "capabilities": ["list"]},
|
|
],
|
|
}
|
|
if method == "metadata.objects.list":
|
|
return {"status": "ok", "objects": [{"kind": payload["kind"], "name": "Товары"}]}
|
|
if method == "data.schema":
|
|
return {"status": "ok", "table": {"name": "_Reference1"}, "fields": []}
|
|
raise AssertionError(method)
|
|
|
|
report = coverage.build_report(
|
|
"http://adapter",
|
|
"upo_test",
|
|
"secret",
|
|
10,
|
|
True,
|
|
True,
|
|
workers=1,
|
|
rpc_call=fake_rpc,
|
|
)
|
|
|
|
assert report["status"] == "ok"
|
|
assert report["list_failures"] == []
|
|
assert report["counts"]["data_kinds_checked"] == 1
|
|
assert report["optional_deep_reads"][0]["property"] == "column_types"
|
|
common_module = next(row for row in report["matrix"] if row["kind"] == "CommonModule")
|
|
assert "list_status" not in common_module
|