from __future__ import annotations import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "plugins" / "1c")) sys.path.insert(0, str(ROOT / "plugins" / "1c" / "connector")) import adapter_1c_server # noqa: E402 from parser.support_rules import parse_parent_configurations_bytes GUID = "8cec71dd-5046-4b16-ab13-cca39c826ca2" CONFIG_GUID = "8cbe2d2d-295e-4b77-813b-63588e84f87a" OTHER_GUID = "1fbe6b79-1ea3-43f9-bf72-f6cd4da9743b" def payload(general_mode: int = 0, rule: int = 1) -> bytes: text = ( f'{{6,0,1,{CONFIG_GUID},{general_mode},{OTHER_GUID},"1.2.3",' f'"Поставщик","Конфигурация",1,{rule},0,{GUID},{GUID},0,0}}' ) return text.encode("utf-8-sig") def test_decodes_editable_support_rule() -> None: result = parse_parent_configurations_bytes(payload()) supplier = result["suppliers"][0] assert result["supplier_count"] == 1 assert supplier.general_mode == "editable" assert supplier.name == "Конфигурация" assert supplier.rules[0].object_guid == GUID assert supplier.rules[0].rule == "editable_support_preserved" def test_locked_general_mode_overrides_object_rule() -> None: result = parse_parent_configurations_bytes(payload(general_mode=1, rule=2)) supplier = result["suppliers"][0] assert supplier.general_mode == "locked" assert supplier.rules[0].rule_code == 0 assert supplier.rules[0].rule == "not_editable" @pytest.mark.parametrize( ("data", "message"), [ (b'{5,0,0}', "expected format marker 6"), (b'{6,0,1,not-a-guid,0,x,v,p,n,0,0,0}', "configuration GUID is invalid"), (payload(rule=9), "unsupported object rule code 9"), ], ) def test_rejects_invalid_support_payload(data: bytes, message: str) -> None: with pytest.raises(ValueError, match=message): parse_parent_configurations_bytes(data) def test_sql_method_decodes_only_explicit_source_and_filters_object(monkeypatch) -> None: monkeypatch.setattr( adapter_1c_server, "read_storage_file_bytes", lambda base_id, table, file_name, timeout_seconds=30: (payload(), {"database": "db"}, None), ) result = adapter_1c_server.metadata_support_decode( {"base_id": "base", "table": "Params", "file_name": "support.si", "object_guid": GUID} ) assert result["status"] == "ok" assert result["source"] == {"kind": "live_sql", "database": "db", "table": "Params", "file_name": "support.si"} assert result["matches"][0]["rule"] == "editable_support_preserved" assert result["object_support"]["status"] == "editable_support_preserved" assert result["object_support"]["edit_allowed_by_support"] is True def test_sql_method_resolves_public_1c_name_before_filtering_support(monkeypatch) -> None: captured: dict[str, str] = {} def get_object(kind, name, **kwargs): captured["kind"] = kind captured["name"] = name return { "status": "ok", "object": { "kind": "CommonModule", "name": "ОбменДанными", "guid": GUID, "ref": "CommonModule.ОбменДанными", }, } monkeypatch.setattr(adapter_1c_server, "get_object", get_object) monkeypatch.setattr( adapter_1c_server, "read_storage_file_bytes", lambda base_id, table, file_name, timeout_seconds=30: (payload(), {"database": "db"}, None), ) result = adapter_1c_server.call_method( "metadata.support.decode", { "base_id": "base", "ref": "ОбщиеМодули.ОбменДанными", "table": "Params", "file_name": "support.si", }, ) assert result["status"] == "ok" assert captured == {"kind": "CommonModule", "name": "ОбменДанными"} assert result["query"]["object_guid"] == GUID assert result["query"]["object"]["ref"] == "CommonModule.ОбменДанными" assert result["object_support"]["status"] == "editable_support_preserved" def test_support_decode_help_publishes_optional_name_selector() -> None: methods = { row["name"]: row for row in adapter_1c_server.call_method("help.methods", {})["methods"] } method = methods["metadata.support.decode"] assert method["selector_capabilities"]["selector_purpose"] == "optional_object_support_filter" assert method["selector_capabilities"]["accepts_ordinal"] is False assert method["input_schema"]["properties"]["object_name"]["type"] == "string" def test_sql_method_treats_absent_guid_as_not_supported_after_complete_decode(monkeypatch) -> None: monkeypatch.setattr( adapter_1c_server, "read_storage_file_bytes", lambda base_id, table, file_name, timeout_seconds=30: (payload(), {"database": "db"}, None), ) result = adapter_1c_server.metadata_support_decode( {"base_id": "base", "table": "Config", "file_name": "support.bin", "object_guid": OTHER_GUID} ) assert result["status"] == "ok" assert result["matches"] == [] assert result["object_support"] == { "object_guid": OTHER_GUID, "status": "not_supported", "edit_allowed_by_support": True, "evidence": "absent_from_complete_parent_configurations", } assert result["diagnostics"]["repository_lock_evaluated"] is False def test_live_sql_support_gate_resolves_public_object_name(monkeypatch) -> None: monkeypatch.setattr( adapter_1c_server.repository_control, "support_gate", lambda request: {"required": True, "allowed": False, "status": "blocked_support_unknown"}, ) monkeypatch.setattr( adapter_1c_server, "get_object", lambda kind, name, **kwargs: {"status": "ok", "object": {"guid": GUID}}, ) monkeypatch.setattr( adapter_1c_server, "metadata_support_decode", lambda request: { "status": "ok", "object_support": { "status": "not_supported", "edit_allowed_by_support": True, "evidence": "absent_from_complete_parent_configurations", }, "diagnostics": {"source_discovery": "base_root_descriptor"}, }, ) gate = adapter_1c_server.live_sql_support_gate( {"base_id": "base", "layer_id": "base", "ref": "ОбщийМодуль.Модуль"} ) assert gate["allowed"] is True assert gate["required"] is False assert gate["mode"] == "live_sql" assert gate["object_guid"] == GUID def test_sql_method_discovers_base_source_and_rejects_invalid_payload(monkeypatch) -> None: monkeypatch.setattr( adapter_1c_server, "discover_base_support_source", lambda base_id, timeout_seconds=30: ( { "kind": "live_sql", "database": "db", "table": "Config", "file_name": f"{CONFIG_GUID}.4", "root_file": OTHER_GUID, "configuration_id": CONFIG_GUID, "discovery": "base_root_descriptor", }, None, ), ) monkeypatch.setattr( adapter_1c_server, "read_storage_file_bytes", lambda base_id, table, file_name, timeout_seconds=30: (payload(), {"database": "db"}, None), ) discovered = adapter_1c_server.metadata_support_decode({"base_id": "base", "object_guid": GUID}) assert discovered["status"] == "ok" assert discovered["source"]["file_name"] == f"{CONFIG_GUID}.4" assert discovered["diagnostics"]["source_discovery"] == "base_root_descriptor" monkeypatch.setattr( adapter_1c_server, "discover_extension_support_source", lambda base_id, extension_guid, timeout_seconds=30: ( { "kind": "live_sql", "table": "ConfigCAS", "configuration_id": OTHER_GUID, "support_payload_present": False, "discovery": "extension_cas_manifest", }, None, ), ) extension = adapter_1c_server.metadata_support_decode( {"base_id": "base", "layer_id": f"extension:{CONFIG_GUID}", "object_guid": GUID} ) assert extension["status"] == "ok" assert extension["object_support"]["status"] == "not_supported" assert extension["object_support"]["edit_allowed_by_support"] is True assert extension["diagnostics"]["repository_lock_evaluated"] is False monkeypatch.setattr( adapter_1c_server, "read_storage_file_bytes", lambda base_id, table, file_name, timeout_seconds=30: (b"{0}", {"database": "db"}, None), ) invalid = adapter_1c_server.metadata_support_decode( {"base_id": "base", "table": "Params", "file_name": "not-support.si"} ) assert invalid["status"] == "invalid_format" assert invalid["diagnostics"]["permission_inference"] == "forbidden"