Complete name-first 1C adapter saved-state support
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
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"
|
||||
Reference in New Issue
Block a user