127 lines
5.0 KiB
Python
127 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
CONNECTOR = ROOT / "plugins" / "1c" / "connector"
|
|
|
|
|
|
def load_adapter():
|
|
path = CONNECTOR / "adapter_1c_server.py"
|
|
spec = importlib.util.spec_from_file_location("adapter_1c_policy_test", path)
|
|
assert spec and spec.loader
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def test_sql_base_access_policy_contract() -> None:
|
|
policy = yaml.safe_load((CONNECTOR / "policies" / "sql-base-access-policy.yaml").read_text(encoding="utf-8"))
|
|
assert policy["status"] == "active"
|
|
assert policy["base_settings"]["selector"] == "base_id"
|
|
assert set(policy["base_settings"]["required_fields"]) == {"server", "database", "user"}
|
|
assert policy["read_scope"]["application_data"] == "read_only"
|
|
assert policy["read_scope"]["metadata_structure"] == "read_only"
|
|
assert set(policy["write_scope"]["allowed"].values()) == {"ConfigSave", "ConfigCASSave"}
|
|
assert policy["sql_identity_management"]["mode"] == "forbidden"
|
|
|
|
|
|
def test_xml_is_offline_decoder_evidence_and_runtime_is_sql_only() -> None:
|
|
policy = yaml.safe_load((CONNECTOR / "policies" / "xml-decoding-reference-policy.yaml").read_text(encoding="utf-8"))
|
|
service = yaml.safe_load((CONNECTOR / "service.yaml").read_text(encoding="utf-8"))
|
|
|
|
assert policy["status"] == "active"
|
|
assert policy["offline_analysis"]["allowed"] is True
|
|
assert policy["runtime"]["source"] == "sql_only"
|
|
assert policy["runtime"]["xml_mount_required"] is False
|
|
assert "policies/xml-decoding-reference-policy.yaml" in service["contracts"]["policies"]
|
|
|
|
|
|
def test_designer_sql_decoding_policy_keeps_adapter_read_only() -> None:
|
|
policy = yaml.safe_load((CONNECTOR / "policies" / "designer-sql-decoding-policy.yaml").read_text(encoding="utf-8"))
|
|
service = yaml.safe_load((CONNECTOR / "service.yaml").read_text(encoding="utf-8"))
|
|
|
|
assert policy["scope"]["default_base_id"] == "upo_test"
|
|
assert policy["scope"]["adapter_role"] == "sql_observer_and_decoder"
|
|
assert policy["credentials"]["persistence"] == "forbidden_in_repository"
|
|
assert policy["sql_observation"]["adapter_access"] == "read_only"
|
|
assert "direct_application_data_write" in policy["sql_observation"]["forbidden"]
|
|
assert policy["xml"]["role"] == "offline_schema_reference_only"
|
|
assert "policies/designer-sql-decoding-policy.yaml" in service["contracts"]["policies"]
|
|
|
|
|
|
def test_runtime_rejects_xml_sources_at_any_payload_depth() -> None:
|
|
adapter = load_adapter()
|
|
|
|
direct = adapter.call_method_impl(
|
|
"metadata.object.get",
|
|
{"base_id": "upo_test", "ref": "Catalog.Test", "xml_path": "Configuration.xml"},
|
|
)
|
|
nested = adapter.call_method_impl(
|
|
"metadata.write.plan",
|
|
{"base_id": "upo_test", "target": {"form_xml_path": "Forms/Test/Ext/Form.xml"}},
|
|
)
|
|
|
|
assert direct["status"] == "invalid_argument"
|
|
assert direct["argument"] == "payload.xml_path"
|
|
assert nested["status"] == "invalid_argument"
|
|
assert nested["argument"] == "payload.target.form_xml_path"
|
|
assert "SQL-only" in direct["diagnostics"]["message"]
|
|
|
|
|
|
def test_adapter_has_no_xml_runtime_environment_configuration() -> None:
|
|
source = (CONNECTOR / "adapter_1c_server.py").read_text(encoding="utf-8")
|
|
assert "ONEC_XML" not in source
|
|
|
|
|
|
def test_sql_connection_requires_explicit_base_entry(monkeypatch) -> None:
|
|
adapter = load_adapter()
|
|
for name in (
|
|
"ONEC_SQL_BASES_JSON",
|
|
"ONEC_SQL_CONNECTIONS_JSON",
|
|
"ONEC_SQL_BASES_JSON_FILE",
|
|
"ONEC_SQL_CONNECTIONS_JSON_FILE",
|
|
):
|
|
monkeypatch.delenv(name, raising=False)
|
|
monkeypatch.setenv("ONEC_SQL_SERVER", "must-not-be-used")
|
|
monkeypatch.setenv("ONEC_SQL_USER", "must-not-be-used")
|
|
monkeypatch.setenv("ONEC_SQL_PASSWORD", "must-not-be-used")
|
|
|
|
config, error = adapter.sql_config_for_base("upo_test")
|
|
|
|
assert config is None
|
|
assert error and error["status"] == "not_configured"
|
|
|
|
|
|
def test_sql_connection_does_not_infer_database_name(monkeypatch) -> None:
|
|
adapter = load_adapter()
|
|
monkeypatch.setenv(
|
|
"ONEC_SQL_BASES_JSON",
|
|
'{"upo_test":{"server":"sql-host","user":"login","password":"secret"}}',
|
|
)
|
|
|
|
config, error = adapter.sql_config_for_base("upo_test")
|
|
|
|
assert config is None
|
|
assert error and "database" in error["message"]
|
|
|
|
|
|
def test_sql_connection_ignores_legacy_connection_map(monkeypatch) -> None:
|
|
adapter = load_adapter()
|
|
monkeypatch.delenv("ONEC_SQL_BASES_JSON", raising=False)
|
|
monkeypatch.delenv("ONEC_SQL_BASES_JSON_FILE", raising=False)
|
|
monkeypatch.setenv(
|
|
"ONEC_SQL_CONNECTIONS_JSON",
|
|
'{"upo_test":{"server":"must-not-be-used","database":"must-not-be-used","user":"must-not-be-used","password":"must-not-be-used"}}',
|
|
)
|
|
|
|
config, error = adapter.sql_config_for_base("upo_test")
|
|
|
|
assert config is None
|
|
assert error and error["status"] == "not_configured"
|
|
assert adapter.sql_configured_base_ids() == []
|