Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
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
|
||||
Reference in New Issue
Block a user