147 lines
7.3 KiB
Python
147 lines
7.3 KiB
Python
"""B2.6:显示组和 PRIMARY 证据必须由独立人审,且审核通过不等于执行授权。"""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SCRIPT = ROOT / "tools/provider-pipeline/standard_display_review_gate.py"
|
|
spec = importlib.util.spec_from_file_location("review_gate_module", SCRIPT)
|
|
gate = importlib.util.module_from_spec(spec)
|
|
assert spec and spec.loader
|
|
spec.loader.exec_module(gate)
|
|
|
|
HASHARGS = dict(queue_sha256="a" * 64, groups_sha256="b" * 64, style_sha256="c" * 64)
|
|
|
|
|
|
def fixtures():
|
|
queue = {
|
|
"schemaVersion": "navsea-feature-identity-scope-audit/v1",
|
|
"providerId": "unknown-provider", "datasetId": "test-v1",
|
|
"reviewQueue": [
|
|
{
|
|
"visualObjectTypeId": "VO-1", "assignmentStatus": "LAYER_ONLY",
|
|
"standardLayer": "reviewed_category", "primaryReviewStatus": "PENDING_REVIEW",
|
|
"componentCandidates": [
|
|
{"componentIndex": 0, "drawingRole": "line", "styleLayerId": "layer-A"},
|
|
{"componentIndex": 1, "drawingRole": "text", "styleLayerId": "layer-B"},
|
|
],
|
|
},
|
|
{
|
|
"visualObjectTypeId": "VO-2", "assignmentStatus": "UNKNOWN",
|
|
"standardLayer": None, "primaryReviewStatus": "PENDING_REVIEW",
|
|
"componentCandidates": [{"componentIndex": 0, "drawingRole": "icon", "styleLayerId": "layer-C"}],
|
|
},
|
|
],
|
|
}
|
|
groups = {
|
|
"schemaVersion": gate.GROUP_SCHEMA, "approvalStatus": "OBSERVED_NOT_APPROVED",
|
|
"groupIds": ["display-A", "display-B"],
|
|
}
|
|
draft = gate.generate_review_draft(queue, groups, **HASHARGS)
|
|
return queue, groups, draft
|
|
|
|
|
|
def review_both(draft):
|
|
std = draft["standardLayerDecisions"][0]
|
|
std.update(status="REVIEWED", displayGroupId="display-A", reviewer="real-reviewer",
|
|
evidenceRefs=["audit/signed-standard-review"])
|
|
obj = draft["visualObjectDecisions"][0]
|
|
obj.update(status="REVIEWED", reviewedStandardLayer="reviewed_category",
|
|
primaryComponentIndex=0, componentParentLinks=[
|
|
{"componentIndex": 1, "parentComponentIndex": 0}
|
|
], reviewer="real-reviewer", evidenceRefs=["audit/signed-primary-review"])
|
|
|
|
|
|
class StandardDisplayReviewGateTests(unittest.TestCase):
|
|
def test_pending_draft_is_not_executable_and_keeps_unknown(self):
|
|
queue, groups, draft = fixtures()
|
|
self.assertEqual(len(draft["standardLayerDecisions"]), 1)
|
|
self.assertEqual(len(draft["visualObjectDecisions"]), 2)
|
|
result = gate.validate_review_draft(draft, queue, groups, **HASHARGS)
|
|
self.assertEqual(result["status"], "PASS_PENDING_REVIEW")
|
|
self.assertEqual(result["runtimeExecutionAuthorizedCount"], 0)
|
|
self.assertEqual(result["visualObjectStatusCounts"], {"PENDING": 2})
|
|
|
|
def test_unapproved_host_groups_rejects_review(self):
|
|
queue, groups, draft = fixtures()
|
|
review_both(draft)
|
|
out = gate.validate_review_draft(draft, queue, groups, **HASHARGS)
|
|
self.assertEqual(out["status"], "FAIL_CLOSED")
|
|
self.assertTrue(any("未正式批准" in error for error in out["validationErrors"]))
|
|
|
|
def test_explicit_reviewed_records_structurally_valid_but_never_runtime_authorized(self):
|
|
queue, groups, draft = fixtures()
|
|
groups["approvalStatus"] = "APPROVED"
|
|
review_both(draft)
|
|
result = gate.validate_review_draft(draft, queue, groups, **HASHARGS)
|
|
self.assertEqual(result["status"], "PASS_PENDING_REVIEW")
|
|
self.assertEqual(result["reviewedObjectCount"], 1)
|
|
self.assertEqual(result["runtimeExecutionAuthorizedCount"], 0)
|
|
self.assertEqual(result["globalIdentityAuthorizedCount"], 0)
|
|
|
|
def test_incomplete_or_double_parent_component_links_fail_closed(self):
|
|
queue, groups, draft = fixtures()
|
|
groups["approvalStatus"] = "APPROVED"
|
|
review_both(draft)
|
|
first = draft["visualObjectDecisions"][0]
|
|
first["componentParentLinks"] = []
|
|
self.assertEqual(gate.validate_review_draft(draft, queue, groups, **HASHARGS)["status"], "FAIL_CLOSED")
|
|
first["componentParentLinks"] = [
|
|
{"componentIndex": 1, "parentComponentIndex": 0},
|
|
{"componentIndex": 1, "parentComponentIndex": 0},
|
|
]
|
|
self.assertEqual(gate.validate_review_draft(draft, queue, groups, **HASHARGS)["status"], "FAIL_CLOSED")
|
|
|
|
def test_unknown_classification_cannot_be_promoted_through_review(self):
|
|
queue, groups, draft = fixtures()
|
|
groups["approvalStatus"] = "APPROVED"
|
|
review_both(draft)
|
|
second = draft["visualObjectDecisions"][1]
|
|
second.update(status="REVIEWED", reviewedStandardLayer="reviewed_category",
|
|
primaryComponentIndex=0, componentParentLinks=[],
|
|
reviewer="not-authorized", evidenceRefs=["ticket/123"])
|
|
out = gate.validate_review_draft(draft, queue, groups, **HASHARGS)
|
|
self.assertEqual(out["status"], "FAIL_CLOSED")
|
|
self.assertTrue(any("UNKNOWN/AMBIGUOUS" in err for err in out["validationErrors"]))
|
|
|
|
def test_binding_digest_and_extra_or_missing_review_ids_fail_closed(self):
|
|
queue, groups, draft = fixtures()
|
|
with self.assertRaisesRegex(ValueError, "指纹"):
|
|
gate.validate_review_draft(draft, queue, groups, **{**HASHARGS, "style_sha256": "wrong"})
|
|
draft["visualObjectDecisions"].pop()
|
|
with self.assertRaisesRegex(ValueError, "缺少"):
|
|
gate.validate_review_draft(draft, queue, groups, **HASHARGS)
|
|
draft = gate.generate_review_draft(queue, groups, **HASHARGS)
|
|
draft["standardLayerDecisions"].append(copy.deepcopy(draft["standardLayerDecisions"][0]))
|
|
with self.assertRaisesRegex(ValueError, "重复"):
|
|
gate.validate_review_draft(draft, queue, groups, **HASHARGS)
|
|
|
|
def test_pending_row_cannot_smuggle_parent_or_group(self):
|
|
queue, groups, draft = fixtures()
|
|
draft["visualObjectDecisions"][0]["primaryComponentIndex"] = 0
|
|
result = gate.validate_review_draft(draft, queue, groups, **HASHARGS)
|
|
self.assertEqual(result["status"], "FAIL_CLOSED")
|
|
draft = gate.generate_review_draft(queue, groups, **HASHARGS)
|
|
draft["standardLayerDecisions"][0]["displayGroupId"] = "display-A"
|
|
result = gate.validate_review_draft(draft, queue, groups, **HASHARGS)
|
|
self.assertEqual(result["status"], "FAIL_CLOSED")
|
|
|
|
def test_cross_provider_visual_types_have_no_implied_same_semantics(self):
|
|
queue_a, groups, draft_a = fixtures()
|
|
queue_b = copy.deepcopy(queue_a)
|
|
queue_b["providerId"] = "independent-provider"
|
|
draft_b = gate.generate_review_draft(queue_b, groups, **HASHARGS)
|
|
self.assertEqual(draft_a["visualObjectDecisions"], draft_b["visualObjectDecisions"])
|
|
self.assertNotEqual(draft_a["binding"]["providerId"], draft_b["binding"]["providerId"])
|
|
with self.assertRaisesRegex(ValueError, "来源身份"):
|
|
gate.validate_review_draft(draft_a, queue_b, groups, **HASHARGS)
|
|
result = gate.validate_review_draft(draft_b, queue_b, groups, **HASHARGS)
|
|
self.assertEqual(result["runtimeExecutionAuthorizedCount"], 0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|