Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
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
|
||||
from parser.payload import compress_payload # noqa: E402
|
||||
|
||||
|
||||
def require(condition: bool, message: str, failures: list[str]) -> None:
|
||||
if not condition:
|
||||
failures.append(message)
|
||||
|
||||
|
||||
def run_override(action_evidence: dict[str, Any] | None) -> dict[str, Any]:
|
||||
adapter_server.metadata_object_modules = lambda payload: {
|
||||
"status": "ok",
|
||||
"object": {"kind": "Catalog", "name": "Номенклатура"},
|
||||
"modules": [{"module_id": "ConfigCAS:ext-guid__module-guid.0", "name": "object"}],
|
||||
}
|
||||
selection = {
|
||||
"routine_name": "ПередЗаписью",
|
||||
"line_start": 1,
|
||||
"line_end": 3,
|
||||
"match_by": "routine_exact",
|
||||
}
|
||||
if action_evidence:
|
||||
selection.update(action_evidence)
|
||||
adapter_server.read_module = lambda payload: {"status": "ok", "selection": selection}
|
||||
return adapter_server.call_method(
|
||||
"metadata.resolve_overrides",
|
||||
{
|
||||
"base_id": "upo_test",
|
||||
"object_type": "Catalog",
|
||||
"object_name": "Номенклатура",
|
||||
"method_name": "ПередЗаписью",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def run_base_override() -> dict[str, Any]:
|
||||
adapter_server.metadata_object_modules = lambda payload: {
|
||||
"status": "ok",
|
||||
"object": {"kind": "Catalog", "name": "Номенклатура"},
|
||||
"modules": [{"module_id": "Config:object-module.0", "name": "object"}],
|
||||
}
|
||||
adapter_server.read_module = lambda payload: {
|
||||
"status": "ok",
|
||||
"selection": {
|
||||
"routine_name": "ПередЗаписью",
|
||||
"line_start": 1,
|
||||
"line_end": 3,
|
||||
"match_by": "routine_exact",
|
||||
},
|
||||
}
|
||||
return adapter_server.call_method(
|
||||
"metadata.resolve_overrides",
|
||||
{
|
||||
"base_id": "upo_test",
|
||||
"object_type": "Catalog",
|
||||
"object_name": "Номенклатура",
|
||||
"method_name": "ПередЗаписью",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def run_saved_state_search_with_object_name(params: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
module_text = "Процедура ПередЗаписью(Отказ)\nКонецПроцедуры".encode("utf-8")
|
||||
header = f"\r\n{len(module_text):08x} {len(module_text):08x} 7fffffff \r\n".encode("ascii")
|
||||
stored = compress_payload(b"prefix" + header + module_text, "raw_deflate")
|
||||
adapter_server.metadata_cache_lookup_row = lambda base_id, kind, name: {
|
||||
"guid": "owner-guid",
|
||||
"kind": kind,
|
||||
"kind_ru": "Справочник",
|
||||
"public_kind": "catalog",
|
||||
"name": name,
|
||||
"source": "base",
|
||||
}
|
||||
adapter_server.storage_files_list = lambda payload: {
|
||||
"status": "ok",
|
||||
"files": [{"FileName": "owner-guid__module-guid.0", "PartCount": 1, "Bytes": len(stored)}],
|
||||
}
|
||||
adapter_server.read_storage_file_bytes = lambda base_id, table, file_name, timeout_seconds=30: (stored, {"database": base_id}, None)
|
||||
request_payload = {
|
||||
"base_id": "upo_test",
|
||||
"tables": ["ConfigCASSave"],
|
||||
"object_type": "Catalog",
|
||||
"object_name": "Номенклатура",
|
||||
"query": "ПередЗаписью",
|
||||
"limit": 10,
|
||||
}
|
||||
request_payload.update(params or {})
|
||||
return adapter_server.call_method(
|
||||
adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD,
|
||||
request_payload,
|
||||
)
|
||||
|
||||
|
||||
def first_action(result: dict[str, Any]) -> dict[str, Any]:
|
||||
chain = result.get("chain") if isinstance(result.get("chain"), list) else []
|
||||
first = chain[0] if chain and isinstance(chain[0], dict) else {}
|
||||
action = first.get("extension_action") if isinstance(first.get("extension_action"), dict) else {}
|
||||
return action
|
||||
|
||||
|
||||
def run_checks() -> dict[str, Any]:
|
||||
failures: list[str] = []
|
||||
|
||||
unknown = run_override(None)
|
||||
unknown_action = first_action(unknown)
|
||||
unknown_evidence = unknown.get("write_plan_evidence") if isinstance(unknown.get("write_plan_evidence"), dict) else {}
|
||||
unknown_next = unknown_evidence.get("next_resolution") if isinstance(unknown_evidence.get("next_resolution"), dict) else {}
|
||||
unknown_next_params = unknown_next.get("params") if isinstance(unknown_next.get("params"), dict) else {}
|
||||
require(unknown.get("status") == "ok", "override chain with extension routine must resolve", failures)
|
||||
require(unknown_action.get("status") == "unknown", "extension routine without action evidence must be unknown", failures)
|
||||
require(unknown_action.get("operation_class") == "unknown_extension_action", "unknown action must not become replace", failures)
|
||||
require((unknown_evidence.get("target") or {}).get("extension_action") == unknown_action, "unknown action must be carried into write_plan_evidence target", failures)
|
||||
require("intent" not in unknown_evidence, "unknown action must not infer write intent", failures)
|
||||
require(unknown_next.get("method") == adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD, "write_plan_evidence must expose saved-state module resolver", failures)
|
||||
require(unknown_next_params.get("tables") == ["ConfigCASSave"], "extension write_plan_evidence must search ConfigCASSave", failures)
|
||||
|
||||
controlled = run_override({"operation_class": "replace_with_control"})
|
||||
controlled_action = first_action(controlled)
|
||||
controlled_evidence = controlled.get("write_plan_evidence") if isinstance(controlled.get("write_plan_evidence"), dict) else {}
|
||||
controlled_next = controlled_evidence.get("next_resolution") if isinstance(controlled_evidence.get("next_resolution"), dict) else {}
|
||||
controlled_next_params = controlled_next.get("params") if isinstance(controlled_next.get("params"), dict) else {}
|
||||
require(controlled_action.get("status") == "ok", "known extension action must be ok", failures)
|
||||
require(controlled_action.get("operation_class") == "replace_with_control", "replace_with_control must be preserved", failures)
|
||||
require(controlled_action.get("requires_control_fragment") is True, "replace_with_control must require control fragment", failures)
|
||||
require((controlled_evidence.get("target") or {}).get("extension_action") == controlled_action, "known action must be carried into write_plan_evidence target", failures)
|
||||
require((controlled_evidence.get("intent") or {}).get("operation") == "replace_with_control", "known action must infer write_plan_evidence intent", failures)
|
||||
require(controlled_next.get("method") == adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD, "known action evidence must expose saved-state module resolver", failures)
|
||||
require(controlled_next_params.get("query") == "ПередЗаписью", "known action resolver params must carry routine query", failures)
|
||||
|
||||
before = run_override({"action": "вставить до"})
|
||||
before_action = first_action(before)
|
||||
require(before_action.get("operation_class") == "insert_before", "Russian insert-before action must normalize", failures)
|
||||
|
||||
base = run_base_override()
|
||||
base_action = first_action(base)
|
||||
base_evidence = base.get("write_plan_evidence") if isinstance(base.get("write_plan_evidence"), dict) else {}
|
||||
base_next = base_evidence.get("next_resolution") if isinstance(base_evidence.get("next_resolution"), dict) else {}
|
||||
base_next_params = base_next.get("params") if isinstance(base_next.get("params"), dict) else {}
|
||||
require(base_action.get("operation_class") == "base_definition", "base routine must be marked as base_definition", failures)
|
||||
require(base_action.get("requires_control_fragment") is False, "base routine must not require control fragment", failures)
|
||||
require(base_next_params.get("tables") == ["ConfigSave"], "base write_plan_evidence must search ConfigSave", failures)
|
||||
|
||||
saved_state = run_saved_state_search_with_object_name(controlled_next_params)
|
||||
saved_state_owner = saved_state.get("owner_resolution") if isinstance(saved_state.get("owner_resolution"), dict) else {}
|
||||
saved_state_modules = saved_state.get("modules") if isinstance(saved_state.get("modules"), list) else []
|
||||
saved_state_streams = saved_state_modules[0].get("streams") if saved_state_modules and isinstance(saved_state_modules[0], dict) and isinstance(saved_state_modules[0].get("streams"), list) else []
|
||||
saved_state_target = saved_state_streams[0].get("write_plan_target") if saved_state_streams and isinstance(saved_state_streams[0], dict) and isinstance(saved_state_streams[0].get("write_plan_target"), dict) else {}
|
||||
require(saved_state.get("status") == "ok", "saved-state module search with object name must be accepted", failures)
|
||||
require(saved_state_owner.get("owner_guid") == "owner-guid", "saved-state module search must resolve object name to owner_guid", failures)
|
||||
require(bool(saved_state_modules), "saved-state module search with resolved owner must find module", failures)
|
||||
require(saved_state_target.get("module_ref") == "ConfigCASSave:owner-guid__module-guid.0#stream:0", "saved-state stream must expose write_plan_target.module_ref", failures)
|
||||
require(saved_state_target.get("expected_sha1"), "saved-state stream must expose write_plan_target.expected_sha1", failures)
|
||||
|
||||
concrete_plan_target = {
|
||||
**(controlled_evidence.get("target") if isinstance(controlled_evidence.get("target"), dict) else {}),
|
||||
**saved_state_target,
|
||||
}
|
||||
concrete_plan_intent = {
|
||||
**(controlled_evidence.get("intent") if isinstance(controlled_evidence.get("intent"), dict) else {}),
|
||||
"control_fragment": "Процедура ПередЗаписью",
|
||||
"new": "Процедура ПередЗаписью(Отказ)\n\t// smoke\nКонецПроцедуры",
|
||||
}
|
||||
concrete_plan = adapter_server.call_method(
|
||||
adapter_server.METADATA_WRITE_PLAN_METHOD,
|
||||
{
|
||||
"base_id": "upo_test",
|
||||
"target": concrete_plan_target,
|
||||
"intent": concrete_plan_intent,
|
||||
"resolve_origin": False,
|
||||
},
|
||||
)
|
||||
concrete_route = concrete_plan.get("route") if isinstance(concrete_plan.get("route"), dict) else {}
|
||||
concrete_hint = concrete_route.get("apply_payload_hint") if isinstance(concrete_route.get("apply_payload_hint"), dict) else {}
|
||||
concrete_hint_payload = concrete_hint.get("payload") if isinstance(concrete_hint.get("payload"), dict) else {}
|
||||
require(concrete_plan.get("allowed") is True, "write_plan_evidence plus saved-state write_plan_target must produce an allowed concrete plan", failures)
|
||||
require(concrete_route.get("apply_method") == adapter_server.MODULE_WRITE_APPLY_METHOD, "concrete override write plan must route to module write apply", failures)
|
||||
require(concrete_route.get("operation_class") == "replace_with_control", "concrete override write plan must preserve extension operation class", failures)
|
||||
require(concrete_hint.get("ready_for_apply_method") is True, "concrete override write plan hint must be ready for apply method", failures)
|
||||
require(concrete_hint_payload.get("module_ref") == saved_state_target.get("module_ref"), "concrete override write plan hint must carry module_ref", failures)
|
||||
require(concrete_hint_payload.get("expected_sha1") == saved_state_target.get("expected_sha1"), "concrete override write plan hint must carry expected_sha1", failures)
|
||||
|
||||
return {
|
||||
"schema": "onec_extension_action_contract_check.v1",
|
||||
"status": "ok" if not failures else "failed",
|
||||
"failures": failures,
|
||||
"checks": {
|
||||
"unknown_extension_action": "extension routine without action evidence stays unknown",
|
||||
"replace_with_control": "controlled replacement action requires control fragment",
|
||||
"russian_insert_before": "Russian action names normalize to stable classes",
|
||||
"base_definition": "base routines are not treated as extension actions",
|
||||
"write_plan_evidence": "override results include a ready metadata.write.plan evidence fragment",
|
||||
"write_plan_next_resolution": "write_plan_evidence points to the saved-state module resolver",
|
||||
"saved_state_module_name_selector": "saved-state module search resolves object_type/object_name to owner_guid",
|
||||
"saved_state_stream_write_plan_target": "saved-state module streams expose concrete metadata.write.plan target",
|
||||
"override_to_concrete_write_plan": "write_plan_evidence and write_plan_target compose into an allowed concrete module plan",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Check extension routine action 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 extension action contract status: ok")
|
||||
return 0 if report["status"] == "ok" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user