Block conflicting extension write selectors

This commit is contained in:
2026-07-26 17:54:52 +03:00
parent d99d57eaf9
commit ad4bd3ec72
7 changed files with 394 additions and 15 deletions
+192
View File
@@ -20992,6 +20992,14 @@ def test_metadata_write_active_form_target_auto_prepares_when_apply_allowed(monk
def test_metadata_write_missing_form_target_auto_prepares_and_retries(monkeypatch: pytest.MonkeyPatch) -> None:
calls: dict[str, int] = {"apply": 0}
seen: dict[str, Any] = {}
monkeypatch.setattr(
adapter_server,
"extension_filter_to_guid",
lambda base_id, extension, method: (
"fb26cf42-7609-11f1-828f-005056b0d483",
None,
),
)
def fake_apply(payload: dict[str, Any]) -> dict[str, Any]:
calls["apply"] += 1
@@ -21856,6 +21864,104 @@ def test_write_gate_context_does_not_fall_back_to_base_for_unknown_extension(
assert adapter_server.repository_control.development_layer_id(result) == "extension:unresolved"
def test_write_gate_context_rejects_conflicting_extension_name_and_guid(
monkeypatch: pytest.MonkeyPatch,
) -> None:
name_guid = "fb26cf42-7609-11f1-828f-005056b0d483"
supplied_guid = "11111111-1111-1111-1111-111111111111"
monkeypatch.setattr(
adapter_server,
"extension_filter_to_guid",
lambda base_id, extension, method: (name_guid, None),
)
result = adapter_server.resolve_write_gate_context(
{
"base_id": "upo_test",
"extension_guid": supplied_guid,
"target": {
"kind": "form",
"extension": "test2",
"ref": "Catalog.test2",
"form": "t_Форма",
},
}
)
resolution = result["development_layer_resolution"]
assert "extension_guid" not in result
assert "extension_guid" not in result["target"]
assert resolution["status"] == "conflict"
assert resolution["layer_id"] == "extension:unresolved"
assert resolution["extension"] == {
"name": "test2",
"supplied_guid": supplied_guid,
}
assert resolution["diagnostics"]["error"] == "extension_selector_conflict"
assert resolution["diagnostics"]["resolved_extension_guid"] == name_guid
def test_repository_apply_gate_blocks_unresolved_extension_before_policy_gates(
monkeypatch: pytest.MonkeyPatch,
) -> None:
def must_not_run(_payload: dict[str, Any]) -> dict[str, Any]:
raise AssertionError("repository/support policy gates must not authorize an unresolved extension layer")
monkeypatch.setattr(adapter_server.repository_control, "write_gate", must_not_run)
monkeypatch.setattr(adapter_server, "live_sql_support_gate", must_not_run)
result = adapter_server.repository_apply_gate(
{
"base_id": "upo_test",
"development_layer_resolution": {
"status": "conflict",
"layer_id": "extension:unresolved",
"diagnostics": {"error": "extension_selector_conflict"},
},
},
"metadata.write",
"apply",
)
assert result["status"] == "blocked"
assert result["error"] == "extension_selector_conflict"
assert result["layer_id"] == "extension:unresolved"
def test_metadata_write_apply_blocks_conflicting_extension_name_and_guid(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
adapter_server,
"extension_filter_to_guid",
lambda base_id, extension, method: (
"fb26cf42-7609-11f1-828f-005056b0d483",
None,
),
)
result = adapter_server.metadata_write(
{
"base_id": "upo_test",
"extension_guid": "11111111-1111-1111-1111-111111111111",
"target": {
"kind": "form",
"extension": "test2",
"ref": "Catalog.test2",
"form": "t_Форма",
"command": "ЗаменаДомена",
},
"edits": [{"property": "Заголовок", "value": "Новое имя"}],
"mode": "apply",
}
)
assert result["schema"] == "onec_repository_write_gate.v1"
assert result["status"] == "blocked"
assert result["error"] == "extension_selector_conflict"
assert result["development_layer_resolution"]["status"] == "conflict"
def test_metadata_write_preflight_routes_public_extension_form_gates_to_extension_layer(
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -21928,6 +22034,84 @@ def test_metadata_write_preflight_routes_public_extension_form_gates_to_extensio
assert captured_layers == [expected_layer, expected_layer]
def test_metadata_write_preflight_blocks_conflicting_extension_name_and_guid(
monkeypatch: pytest.MonkeyPatch,
) -> None:
name_guid = "fb26cf42-7609-11f1-828f-005056b0d483"
supplied_guid = "11111111-1111-1111-1111-111111111111"
monkeypatch.setattr(
adapter_server,
"extension_filter_to_guid",
lambda base_id, extension, method: (name_guid, None),
)
monkeypatch.setattr(
adapter_server,
"metadata_form_write_target_resolve",
lambda payload: {
"status": "ok",
"source": {
"table": "ConfigCASSave",
"file_name": f"{name_guid}__form-guid.0",
},
"counts": {"matches": 1},
},
)
monkeypatch.setattr(
adapter_server,
"metadata_saved_state_diff",
lambda payload: {
"status": "changed",
"target": {"table": payload["table"], "file_name": payload["file_name"]},
"freshness": {
"source": "live_sql",
"status": "live_sql_verified",
"verified_against_sql": True,
},
},
)
monkeypatch.setattr(
adapter_server.repository_control,
"write_gate",
lambda payload: {
"status": "not_configured",
"allowed": True,
"layer_id": adapter_server.repository_control.development_layer_id(payload),
},
)
monkeypatch.setattr(
adapter_server,
"live_sql_support_gate",
lambda payload: {
"status": "support_not_configured_legacy",
"allowed": True,
"layer_id": adapter_server.repository_control.development_layer_id(payload),
},
)
result = adapter_server.metadata_write_preflight(
{
"base_id": "upo_test",
"extension_guid": supplied_guid,
"target": {
"kind": "form",
"extension": "test2",
"ref": "Catalog.test2",
"form": "t_Форма",
"command": "ЗаменаДомена",
},
"edits": [{"property": "Заголовок", "value": "Новое имя"}],
"resolve_origin": False,
}
)
assert result["status"] == "blocked"
assert result["allowed"] is False
assert result["repository"]["allowed"] is True
assert result["support"]["allowed"] is True
assert result["development_layer_resolution"]["status"] == "conflict"
assert result["development_layer_resolution"]["diagnostics"]["error"] == "extension_selector_conflict"
def test_metadata_write_plan_help_describes_name_first_form_and_module_targets() -> None:
methods = {
row["name"]: row
@@ -26459,6 +26643,14 @@ def test_metadata_write_missing_module_target_exposes_prepare_plan(monkeypatch:
def test_metadata_write_missing_module_target_auto_prepares_and_retries(monkeypatch: pytest.MonkeyPatch) -> None:
calls: dict[str, int] = {"search": 0}
seen: dict[str, Any] = {}
monkeypatch.setattr(
adapter_server,
"extension_filter_to_guid",
lambda base_id, extension, method: (
"fb26cf42-7609-11f1-828f-005056b0d483",
None,
),
)
def fake_search(payload: dict[str, Any]) -> dict[str, Any]:
calls["search"] += 1
+16
View File
@@ -112,6 +112,22 @@ def test_public_extension_form_candidate_is_name_first_and_prefers_title() -> No
}
def test_different_extension_guid_changes_only_last_hex_digit() -> None:
assert (
smoke.different_extension_guid(
"extension:fb26cf42-7609-11f1-828f-005056b0d483"
)
== "fb26cf42-7609-11f1-828f-005056b0d480"
)
assert (
smoke.different_extension_guid(
"extension:fb26cf42-7609-11f1-828f-005056b0d480"
)
== "fb26cf42-7609-11f1-828f-005056b0d481"
)
assert smoke.different_extension_guid("base") is None
def test_name_first_extension_form_preflight_rejects_base_gate_layer(monkeypatch) -> None:
def fake_rpc_call(
_endpoint_url: str,