from __future__ import annotations import argparse import json import sys from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "plugins" / "1c")) sys.path.insert(0, str(ROOT / "plugins" / "1c" / "connector")) import adapter_1c_server as adapter_server # noqa: E402 def require(condition: bool, message: str, failures: list[str]) -> None: if not condition: failures.append(message) def patch_adapter_for_symbol_checks() -> None: def fake_definition_find(payload: dict[str, Any]) -> dict[str, Any]: query = str(payload.get("query") or "") if query == "Артикул": return { "schema": "onec_definition_find.v1", "status": "ok", "matches": [ { "canonical_path": "Справочник.Номенклатура.Артикул", "kind": "Catalog", "name": "Номенклатура", } ], } if query == "Номенклатура": return { "schema": "onec_definition_find.v1", "status": "ok", "matches": [ { "canonical_path": "Справочник.Номенклатура", "kind": "Catalog", "name": "Номенклатура", } ], } return {"schema": "onec_definition_find.v1", "status": "ok", "matches": []} def fake_read_module(payload: dict[str, Any]) -> dict[str, Any]: return { "schema": "onec_module_read.v1", "status": "ok", "text": "Процедура ПередЗаписью(Отказ) Экспорт\n Отказ = Истина;\nКонецПроцедуры", "owner": {"kind": "Catalog", "name": "Номенклатура"}, "module": {"name": "Модуль объекта"}, } def fake_attributes(payload: dict[str, Any]) -> dict[str, Any]: return {"schema": "onec_metadata_object_attributes.v1", "status": "ok", "attributes": []} adapter_server.metadata_definition_find = fake_definition_find adapter_server.read_module = fake_read_module adapter_server.metadata_object_attributes = fake_attributes def check_full_path(failures: list[str]) -> None: result = adapter_server.call_method( "code.symbol.resolve", { "base_id": "upo_test", "expression": "Справочник.Номенклатура.Артикул", "module_ref": "ConfigSave:file:0", }, ) require(result.get("schema") == "onec_bsl_symbol_resolution.v1", "full path must return BSL symbol schema", failures) require(result.get("status") == "resolved", "full path must resolve", failures) require(result.get("resolution_kind") == "metadata_path", "full path must be classified as metadata_path", failures) require(result.get("canonical_path") == "Справочник.Номенклатура.Артикул", "full path must expose canonical_path publicly", failures) require(result.get("safe_as_metadata_path") is True, "full path must expose safe_as_metadata_path=true", failures) def check_parameter(failures: list[str]) -> None: result = adapter_server.call_method( "code.symbol.resolve", { "base_id": "upo_test", "expression": "Отказ.Код", "routine_name": "ПередЗаписью", "module_ref": "ConfigSave:file:0", }, ) require(result.get("status") == "resolved", "routine parameter must resolve", failures) require(result.get("resolution_kind") == "parameter", "routine parameter must not be metadata", failures) require(result.get("context_path") == "Отказ.Код", "routine parameter must expose context_path publicly", failures) require(result.get("safe_as_metadata_path") is False, "routine parameter must expose safe_as_metadata_path=false", failures) def check_short_name(failures: list[str]) -> None: result = adapter_server.call_method( "code.symbol.resolve", { "base_id": "upo_test", "expression": "Номенклатура.ЕдИзмерение.Код", "routine_name": "ПередЗаписью", "module_ref": "ConfigSave:file:0", }, ) candidates = result.get("candidates") if isinstance(result.get("candidates"), list) else [] require(result.get("status") == "unresolved", "short object name must stay unresolved", failures) require(result.get("safe_as_metadata_path") is False, "short object name must expose safe_as_metadata_path=false", failures) require(bool(candidates), "short object name must return ambiguity candidates", failures) if candidates: require(candidates[0].get("canonical_path") == "Справочник.Номенклатура", "candidate canonical_path must stay public", failures) require(candidates[0].get("reason") == "short_object_name_requires_kind", "candidate must explain short name risk", failures) def run_checks() -> dict[str, Any]: patch_adapter_for_symbol_checks() failures: list[str] = [] check_full_path(failures) check_parameter(failures) check_short_name(failures) return { "schema": "onec_code_symbol_contract_check.v1", "status": "ok" if not failures else "failed", "failures": failures, "checks": { "full_path_metadata": "full path must resolve as metadata_path", "parameter_not_metadata": "routine parameter must not be metadata", "short_name_unsafe": "short object name must remain unsafe", }, } def main() -> int: parser = argparse.ArgumentParser(description="Check adapter-level code.symbol.resolve contract invariants.") parser.add_argument("--print", action="store_true") args = parser.parse_args() report = run_checks() if args.print or report["status"] != "ok": print(json.dumps(report, ensure_ascii=False, indent=2)) else: print("1C code symbol contract status: ok") return 0 if report["status"] == "ok" else 1 if __name__ == "__main__": raise SystemExit(main())