120 lines
5.7 KiB
Python
120 lines
5.7 KiB
Python
"""按原始 Feature 重放,覆盖两种 Provider 字段结构、身份歧义及跨层继承。"""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
from mapbox_vector_tile.Mapbox import vector_tile_pb2
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SCRIPT = ROOT / "tools/provider-pipeline/feature_lineage_replay_audit.py"
|
|
spec = importlib.util.spec_from_file_location("lineage_audit_module", SCRIPT)
|
|
audit = importlib.util.module_from_spec(spec)
|
|
assert spec and spec.loader
|
|
spec.loader.exec_module(audit)
|
|
engine = audit.load_instantiator()
|
|
|
|
|
|
def make_provider(root: Path, field: str, value: str, multi: bool, *, duplicates: bool = False):
|
|
tile = vector_tile_pb2.tile()
|
|
layer = tile.layers.add()
|
|
layer.name = "objects"
|
|
layer.version = 2
|
|
layer.extent = 4096
|
|
layer.keys.extend([field, "text"])
|
|
string1 = layer.values.add()
|
|
string1.string_value = value
|
|
string2 = layer.values.add()
|
|
string2.string_value = "A"
|
|
for _ in range(2 if duplicates else 1):
|
|
feature = layer.features.add()
|
|
feature.id = 101
|
|
feature.type = 1
|
|
feature.tags.extend([0, 0, 1, 1])
|
|
feature.geometry.extend([9, 0, 0])
|
|
tile_path = root / "1/0/0.pbf"
|
|
tile_path.parent.mkdir(parents=True, exist_ok=True)
|
|
tile_path.write_bytes(tile.SerializeToString())
|
|
|
|
layers = [{"id": "picture", "type": "symbol", "source": "provider-a",
|
|
"source-layer": "objects", "layout": {"icon-image": ["get", field]}}]
|
|
if multi:
|
|
layers.append({"id": "label", "type": "symbol", "source": "provider-a",
|
|
"source-layer": "objects", "layout": {"text-field": ["get", "text"]}})
|
|
style = {"layers": layers}
|
|
sprites = {value: {"x": 0, "y": 0, "width": 1, "height": 1}}
|
|
hashes = {value: "visual-hash-for-test"}
|
|
props = {field: value, "text": "A"}
|
|
components = []
|
|
for index, part in enumerate(layers):
|
|
components.extend(engine.component_from_layer(part, index, props, 1, sprites, hashes))
|
|
visual_id = engine.stable_id("VO", {
|
|
"geometry_role": "Point",
|
|
"components": engine.signature_components(components),
|
|
})
|
|
obj = {
|
|
"visual_object_id": visual_id,
|
|
"provenance": {"source_layer": "objects"},
|
|
"example": {"tile": "1/0/0", "feature_id": 101},
|
|
"components": components,
|
|
}
|
|
return {"visual_objects": [obj]}, style, sprites, hashes
|
|
|
|
|
|
class FeatureLineageTests(unittest.TestCase):
|
|
def test_two_provider_field_shapes_and_same_feature_components(self):
|
|
for field, value, multi in (("symbol", "A", False), ("providerIconCode", "ALT", True)):
|
|
with self.subTest(field=field), tempfile.TemporaryDirectory() as folder:
|
|
cat, style, sprites, hashes = make_provider(Path(folder), field, value, multi)
|
|
out = audit.replay_catalog(cat, style, sprites, hashes, Path(folder), instantiator=engine)
|
|
self.assertEqual(out["statusCounts"], {"SAME_RAW_FEATURE_REPLAY_CONFIRMED": 1})
|
|
self.assertEqual(out["results"][0]["replayedComponentCount"], 2 if multi else 1)
|
|
self.assertTrue(out["results"][0]["sameRawFeature"])
|
|
self.assertEqual(out["primaryComponentAuthorizedCount"], 0)
|
|
self.assertEqual(out["displaySelectorAuthorizedCount"], 0)
|
|
|
|
def test_feature_id_not_found_and_duplicate_identity_fail_closed(self):
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
root = Path(folder)
|
|
cat, style, sprites, hashes = make_provider(root, "mark", "ICON", True)
|
|
cat["visual_objects"][0]["example"]["feature_id"] = 998
|
|
out = audit.replay_catalog(cat, style, sprites, hashes, root, instantiator=engine)
|
|
self.assertEqual(out["results"][0]["status"], "FEATURE_ID_NOT_FOUND")
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
root = Path(folder)
|
|
cat, style, sprites, hashes = make_provider(root, "mark", "ICON", True, duplicates=True)
|
|
out = audit.replay_catalog(cat, style, sprites, hashes, root, instantiator=engine)
|
|
self.assertEqual(out["results"][0]["status"], "FEATURE_ID_NOT_UNIQUE")
|
|
|
|
def test_modified_composite_hash_is_not_accepted(self):
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
root = Path(folder)
|
|
cat, style, sprites, hashes = make_provider(root, "symbol", "REAL", True)
|
|
cat["visual_objects"][0]["components"][0]["resource_visual_hash"] = "bad"
|
|
out = audit.replay_catalog(cat, style, sprites, hashes, root, instantiator=engine)
|
|
self.assertEqual(out["results"][0]["status"], "CATALOG_COMPONENT_MISMATCH")
|
|
|
|
def test_source_layer_collision_fails_closed(self):
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
root = Path(folder)
|
|
cat, style, sprites, hashes = make_provider(root, "symbol", "REAL", True)
|
|
style["layers"].append({**style["layers"][0], "id": "second-provider",
|
|
"source": "provider-b"})
|
|
out = audit.replay_catalog(cat, style, sprites, hashes, root, instantiator=engine)
|
|
self.assertEqual(out["results"][0]["status"], "AMBIGUOUS_STYLE_SOURCE")
|
|
|
|
def test_missing_tile_fails_closed(self):
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
root = Path(folder)
|
|
cat, style, sprites, hashes = make_provider(root, "symbol", "REAL", True)
|
|
(root / "1/0/0.pbf").unlink()
|
|
out = audit.replay_catalog(cat, style, sprites, hashes, root, instantiator=engine)
|
|
self.assertEqual(out["results"][0]["status"], "PBF_TILE_OR_SOURCE_LAYER_UNAVAILABLE")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|