108 lines
5.0 KiB
Python
108 lines
5.0 KiB
Python
"""通用 Mapping 证据审计:两种不同 Provider 结构及 fail-closed 规则。"""
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
path = ROOT / "tools/provider-pipeline/mapping_execution_evidence_audit.py"
|
|
spec = importlib.util.spec_from_file_location("audit_mapping_module", path)
|
|
audit = importlib.util.module_from_spec(spec)
|
|
assert spec and spec.loader
|
|
spec.loader.exec_module(audit)
|
|
|
|
|
|
def item(identity, standard, status, layer_indexes, style, keys):
|
|
return {
|
|
"visualObjectId": identity, "status": status, "standardLayer": standard,
|
|
"renderEvidence": {
|
|
"sourceLayer": style["layers"][layer_indexes[0]]["source-layer"],
|
|
"geometryRole": "Point", "occurrenceCount": 1,
|
|
"components": [
|
|
{"styleIndex": index, "styleLayerId": style["layers"][index]["id"],
|
|
"role": "icon" if style["layers"][index]["layout"].get("icon-image") else "text",
|
|
"resourceKey": key}
|
|
for index, key in zip(layer_indexes, keys)
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
class EvidenceAuditTests(unittest.TestCase):
|
|
def test_provider_a_direct_and_unknown_mixed_layer_preserved(self):
|
|
style = {"layers": [{"id": "icons", "source": "alpha", "source-layer": "features",
|
|
"layout": {"icon-image": ["get", "symbol"]}}]}
|
|
mapping = {"assignments": [
|
|
item("a", "navigation_marks", "LAYER_ONLY", [0], style, ["BOY"]),
|
|
item("b", None, "UNKNOWN", [0], style, ["ALIEN"]),
|
|
]}
|
|
result = audit.audit_mapping(style, mapping)
|
|
self.assertEqual(result["mappingObjectCount"], 2)
|
|
self.assertEqual(result["executionAuthorizedCount"], 0)
|
|
self.assertEqual(result["mixedStyleLayerIds"], ["icons"])
|
|
a = result["records"][0]
|
|
self.assertEqual(a["renderComponents"][0]["candidateSelector"], ["==", ["get", "symbol"], "BOY"])
|
|
self.assertIn("STANDARD_TO_DISPLAY_GROUP_CONTRACT_UNBOUND", a["blockingReasons"])
|
|
self.assertIn("UNRESOLVED_CLASSIFICATION", result["records"][1]["blockingReasons"])
|
|
|
|
def test_provider_b_other_field_multiple_components_and_inheritance(self):
|
|
style = {"layers": [
|
|
{"id": "mark-draw", "source": "beta", "source-layer": "objects",
|
|
"layout": {"icon-image": ["get", "pictogram"]}},
|
|
{"id": "mark-label", "source": "beta", "source-layer": "objects",
|
|
"layout": {"text-field": ["get", "label"]}},
|
|
]}
|
|
mapping = {"assignments": [
|
|
item("a", "facility_boundary_point", "VISUAL_OBJECT_MATCH", [0, 1],
|
|
style, ["P", None]),
|
|
]}
|
|
result = audit.audit_mapping(style, mapping)
|
|
entry = result["records"][0]
|
|
self.assertEqual(entry["renderComponents"][0]["candidateSelector"],
|
|
["==", ["get", "pictogram"], "P"])
|
|
self.assertEqual(entry["renderComponents"][1]["selectorEvidence"], "UNPROVED")
|
|
self.assertIn("MULTI_COMPONENT_IDENTITY_UNVERIFIED", entry["blockingReasons"])
|
|
self.assertIn("INHERITANCE_REQUIRES_SAME_FEATURE_PROOF", entry["blockingReasons"])
|
|
self.assertFalse(entry["executionAuthorized"])
|
|
|
|
def test_cross_layer_contradiction_and_invalid_style_index_fail_closed(self):
|
|
style = {"layers": [
|
|
{"id": "one", "source": "beta", "source-layer": "objects",
|
|
"layout": {"icon-image": ["get", "icon"]}},
|
|
{"id": "two", "source": "beta", "source-layer": "objects",
|
|
"layout": {"icon-image": ["get", "icon"]}},
|
|
]}
|
|
mapping = {"assignments": [
|
|
item("a", "mark", "LAYER_ONLY", [0, 1], style, ["A", "B"]),
|
|
]}
|
|
result = audit.audit_mapping(style, mapping)
|
|
self.assertIn("CONTRADICTORY_RESOURCE_FIELDS",
|
|
result["records"][0]["blockingReasons"])
|
|
mapping["assignments"][0]["renderEvidence"]["components"][0]["styleIndex"] = 99
|
|
with self.assertRaisesRegex(ValueError, "索引无效"):
|
|
audit.audit_mapping(style, mapping)
|
|
|
|
def test_style_sha256_binding_fails_closed(self):
|
|
import hashlib
|
|
sample = b'{"layers":[]}'
|
|
digest = hashlib.sha256(sample).hexdigest()
|
|
mapping = {"renderEvidenceSource": {"styleSha256": digest}}
|
|
audit.verify_mapping_style_fingerprint(mapping, sample)
|
|
with self.assertRaisesRegex(ValueError, "不匹配"):
|
|
audit.verify_mapping_style_fingerprint(mapping, b'{"layers":[1]}')
|
|
with self.assertRaisesRegex(ValueError, "缺失"):
|
|
audit.verify_mapping_style_fingerprint({"renderEvidenceSource": {}}, sample)
|
|
|
|
def test_duplicate_visual_id_fails_closed(self):
|
|
style = {"layers": []}
|
|
mapping = {"assignments": [
|
|
{"visualObjectId": "same", "status": "UNKNOWN"},
|
|
{"visualObjectId": "same", "status": "UNKNOWN"},
|
|
]}
|
|
with self.assertRaisesRegex(ValueError, "重复"):
|
|
audit.audit_mapping(style, mapping)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|