114 lines
4.3 KiB
Python
114 lines
4.3 KiB
Python
import copy
|
|
import importlib.util
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
SKILL_DIR = Path(__file__).resolve().parents[1]
|
|
SCRIPT = SKILL_DIR / "scripts" / "validate_evidence.py"
|
|
SPEC = importlib.util.spec_from_file_location("validate_evidence", SCRIPT)
|
|
validate_evidence = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(validate_evidence)
|
|
|
|
|
|
class ProductEvidenceTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
template = SKILL_DIR / "templates" / "product-evidence.example.json"
|
|
cls.valid_data = json.loads(template.read_text(encoding="utf-8"))
|
|
|
|
def test_example_manifest_is_valid(self):
|
|
errors, warnings = validate_evidence.validate_manifest(self.valid_data)
|
|
self.assertEqual(errors, [])
|
|
self.assertEqual(warnings, [])
|
|
|
|
def test_rejects_duplicate_ids_and_unknown_references(self):
|
|
data = copy.deepcopy(self.valid_data)
|
|
data["parameters"][0]["id"] = "FEAT-001"
|
|
data["claims"][0]["evidence_ids"] = ["EVD-MISSING"]
|
|
errors, _ = validate_evidence.validate_manifest(data)
|
|
messages = "\n".join(item["message"] for item in errors)
|
|
self.assertIn("重复", messages)
|
|
self.assertIn("引用不存在", messages)
|
|
|
|
def test_rejects_secret_in_evidence_source(self):
|
|
data = copy.deepcopy(self.valid_data)
|
|
data["evidence"][0]["source"] = "https://example.test/doc?token=secret"
|
|
errors, _ = validate_evidence.validate_manifest(data)
|
|
self.assertTrue(
|
|
any("密钥" in item["message"] for item in errors)
|
|
)
|
|
|
|
def test_placeholder_is_warning(self):
|
|
data = copy.deepcopy(self.valid_data)
|
|
data["product"]["summary"] = "TODO"
|
|
errors, warnings = validate_evidence.validate_manifest(data)
|
|
self.assertEqual(errors, [])
|
|
self.assertTrue(warnings)
|
|
|
|
def test_rejects_record_more_public_than_evidence(self):
|
|
data = copy.deepcopy(self.valid_data)
|
|
for record in data["evidence"]:
|
|
if record["id"] == "EVD-001":
|
|
record["disclosure"] = "restricted"
|
|
errors, _ = validate_evidence.validate_manifest(data)
|
|
self.assertTrue(
|
|
any("仅为 restricted" in item["message"] for item in errors)
|
|
)
|
|
|
|
def test_restricted_parameter_is_not_a_public_channel_warning(self):
|
|
data = copy.deepcopy(self.valid_data)
|
|
data["parameters"][0]["disclosure"] = "restricted"
|
|
for record in data["evidence"]:
|
|
if record["id"] in data["parameters"][0]["evidence_ids"]:
|
|
record["disclosure"] = "restricted"
|
|
data["claims"][0]["evidence_ids"] = []
|
|
data["claims"][0]["type"] = "goal"
|
|
errors, warnings = validate_evidence.validate_manifest(
|
|
data, channel="public"
|
|
)
|
|
self.assertEqual(errors, [])
|
|
self.assertEqual(warnings, [])
|
|
|
|
def test_public_channel_rejects_internal_evidence(self):
|
|
data = copy.deepcopy(self.valid_data)
|
|
claim = data["claims"][0]
|
|
claim["allowed_outputs"] = ["one-pager"]
|
|
evidence_id = claim["evidence_ids"][0]
|
|
for record in data["evidence"]:
|
|
if record["id"] == evidence_id:
|
|
record["disclosure"] = "internal"
|
|
for parameter in data["parameters"]:
|
|
if evidence_id in parameter.get("evidence_ids", []):
|
|
parameter["disclosure"] = "internal"
|
|
errors, _ = validate_evidence.validate_manifest(data, channel="public")
|
|
self.assertTrue(
|
|
any("不足以支撑 public" in item["message"] for item in errors)
|
|
)
|
|
errors, _ = validate_evidence.validate_manifest(data, channel="internal")
|
|
self.assertEqual(errors, [])
|
|
|
|
def test_cli_json_report(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
manifest = Path(tmp) / "product-evidence.json"
|
|
manifest.write_text(
|
|
json.dumps(self.valid_data, ensure_ascii=False),
|
|
encoding="utf-8",
|
|
)
|
|
result = subprocess.run(
|
|
[sys.executable, str(SCRIPT), str(manifest), "--json"],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
report = json.loads(result.stdout)
|
|
self.assertTrue(report["valid"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|