168 lines
6.2 KiB
Python
168 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import scripts.check_1c_metadata_kind_fixtures as fixtures # noqa: E402
|
|
|
|
|
|
MANIFEST = ROOT / "config" / "1c_metadata_kind_fixtures.json"
|
|
|
|
|
|
def test_fixture_manifest_is_valid_and_forbids_direct_sql() -> None:
|
|
manifest = fixtures.load_json(MANIFEST)
|
|
|
|
assert fixtures.validate_manifest(manifest) == []
|
|
assert manifest["policy"]["direct_sql_write_allowed"] is False
|
|
assert manifest["designer"] == {
|
|
"required_version": "8.5.1.1236",
|
|
"access_mode": "operating_system_integrated_only",
|
|
"credential_arguments_allowed": False,
|
|
"export_script": "scripts/export_1c_extension_sources.ps1",
|
|
}
|
|
assert manifest["external_reference"]["usage"] == "external_reference_only_not_vendored"
|
|
assert len(manifest["external_reference"]["commit"]) == 40
|
|
assert {item["kind"] for item in manifest["fixtures"]} == {
|
|
"CalculationRegister",
|
|
"Sequence",
|
|
"Interface",
|
|
}
|
|
|
|
|
|
def test_offline_check_does_not_claim_live_readiness() -> None:
|
|
result = fixtures.check_manifest(MANIFEST)
|
|
|
|
assert result["passed"] is True
|
|
assert result["status"] == "manifest_valid"
|
|
assert result["counts"] == {"findings": 0, "fixtures": 3, "ready": 0, "gaps": 0}
|
|
assert {item["status"] for item in result["fixtures"]} == {"not_checked"}
|
|
|
|
|
|
def test_live_check_distinguishes_ready_missing_and_unconfigured() -> None:
|
|
calls: list[tuple[str, dict[str, Any]]] = []
|
|
|
|
def fake_rpc(_url: str, method: str, payload: dict[str, Any], _timeout: float, _token: str) -> dict[str, Any]:
|
|
calls.append((method, payload))
|
|
if method == "extension.objects.find":
|
|
return {
|
|
"status": "ok",
|
|
"objects": [
|
|
{
|
|
"kind": "CalculationRegister",
|
|
"name": "tt_РегистрРасчета1",
|
|
"guid": "a4dfe8f1-2ce8-43c9-8b41-3deb7238c5d5",
|
|
"activation_state": "saved_only",
|
|
"extension_guid": "fb26cf42-7609-11f1-828f-005056b0d483",
|
|
},
|
|
{
|
|
"kind": "Catalog",
|
|
"name": "tt_Последовательность1",
|
|
"guid": "11111111-1111-1111-1111-111111111111",
|
|
"activation_state": "saved_only",
|
|
},
|
|
],
|
|
}
|
|
return {"status": "ok"}
|
|
|
|
result = fixtures.check_manifest(MANIFEST, live=True, rpc_call=fake_rpc)
|
|
|
|
by_kind = {item["kind"]: item for item in result["fixtures"]}
|
|
assert by_kind["CalculationRegister"]["status"] == "fixture_ready"
|
|
assert by_kind["Sequence"]["status"] == "fixture_missing"
|
|
assert by_kind["Interface"]["status"] == "target_not_configured"
|
|
assert [item["method"] for item in by_kind["CalculationRegister"]["smoke"]] == [
|
|
"metadata.object.get",
|
|
"metadata.object.properties",
|
|
"metadata.object.attributes",
|
|
"metadata.object.related",
|
|
]
|
|
extension_find_calls = [payload for method, payload in calls if method == "extension.objects.find"]
|
|
assert len(extension_find_calls) == 1
|
|
calc_find_payload = extension_find_calls[0]
|
|
assert "kind" not in calc_find_payload
|
|
assert calc_find_payload["use_cache"] is True
|
|
assert calc_find_payload["full_scan"] is False
|
|
assert calc_find_payload["refresh_cache"] is False
|
|
assert result["passed"] is True
|
|
assert result["status"] == "gaps_found"
|
|
|
|
|
|
def test_require_ready_fails_when_a_fixture_is_missing() -> None:
|
|
def fake_rpc(_url: str, method: str, _payload: dict[str, Any], _timeout: float, _token: str) -> dict[str, Any]:
|
|
if method == "metadata.objects.list":
|
|
return {"status": "not_found", "objects": []}
|
|
return {"status": "not_found", "objects": []}
|
|
|
|
result = fixtures.check_manifest(
|
|
MANIFEST,
|
|
live=True,
|
|
require_ready=True,
|
|
base_overrides={"interface_legacy_base": "legacy_test"},
|
|
rpc_call=fake_rpc,
|
|
)
|
|
|
|
assert result["passed"] is False
|
|
assert result["counts"]["ready"] == 0
|
|
assert result["counts"]["gaps"] == 3
|
|
|
|
|
|
def test_live_check_reuses_extension_scan_transport_failure() -> None:
|
|
calls = 0
|
|
|
|
def fake_rpc(_url: str, _method: str, _payload: dict[str, Any], _timeout: float, _token: str) -> dict[str, Any]:
|
|
nonlocal calls
|
|
calls += 1
|
|
raise TimeoutError("scan timed out")
|
|
|
|
result = fixtures.check_manifest(MANIFEST, live=True, rpc_call=fake_rpc)
|
|
|
|
by_kind = {item["kind"]: item for item in result["fixtures"]}
|
|
assert calls == 1
|
|
assert by_kind["CalculationRegister"]["error"] == "TimeoutError"
|
|
assert "probe_error_reused" not in by_kind["CalculationRegister"]
|
|
assert by_kind["Sequence"]["error"] == "TimeoutError"
|
|
assert by_kind["Sequence"]["probe_error_reused"] is True
|
|
|
|
|
|
def test_manifest_rejects_credential_like_keys(tmp_path: Path) -> None:
|
|
value = fixtures.load_json(MANIFEST)
|
|
value["fixtures"][0]["password"] = "must-not-be-here"
|
|
path = tmp_path / "manifest.json"
|
|
path.write_text(json.dumps(value, ensure_ascii=False), encoding="utf-8")
|
|
|
|
result = fixtures.check_manifest(path)
|
|
|
|
assert result["passed"] is False
|
|
assert any(item["code"] == "credential_key_in_manifest" for item in result["findings"])
|
|
|
|
|
|
def test_manifest_requires_safe_designer_access_and_pinned_reference() -> None:
|
|
value = fixtures.load_json(MANIFEST)
|
|
value["designer"]["access_mode"] = "interactive_credentials"
|
|
value["external_reference"]["commit"] = "main"
|
|
|
|
findings = fixtures.validate_manifest(value)
|
|
|
|
assert {item["code"] for item in findings} >= {
|
|
"unsafe_designer_access_mode",
|
|
"invalid_external_reference_commit",
|
|
}
|
|
|
|
|
|
def test_designer_export_script_has_no_user_or_credential_parameters() -> None:
|
|
script = (ROOT / "scripts" / "export_1c_extension_sources.ps1").read_text(encoding="utf-8")
|
|
|
|
assert "/WA+" in script
|
|
assert "/N" not in script
|
|
assert "/P" not in script
|
|
assert "UserName" not in script
|
|
assert "Credential" not in script
|
|
assert "/DumpConfigToFiles" in script
|
|
assert "-Extension" in script
|