test_report_metadata.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """可信报告元数据测试。"""
  2. from datetime import date
  3. from pathlib import Path
  4. from tempfile import TemporaryDirectory
  5. import unittest
  6. from vendor_guard.report_metadata import (
  7. stamp_report_date,
  8. validate_report_risk_semantics,
  9. )
  10. class ReportMetadataTests(unittest.TestCase):
  11. def setUp(self) -> None:
  12. self.temp_dir = TemporaryDirectory()
  13. self.addCleanup(self.temp_dir.cleanup)
  14. self.report_path = Path(self.temp_dir.name) / "report.md"
  15. def test_replaces_model_generated_report_date(self) -> None:
  16. self.report_path.write_text(
  17. "# ACME 报告\n\n**报告日期:2026-05-12**\n",
  18. encoding="utf-8",
  19. )
  20. changed = stamp_report_date(self.report_path, date(2026, 7, 23))
  21. self.assertTrue(changed)
  22. report = self.report_path.read_text(encoding="utf-8")
  23. self.assertIn("**报告日期:** 2026-07-23", report)
  24. self.assertNotIn("2026-05-12", report)
  25. def test_inserts_date_when_model_omits_it(self) -> None:
  26. self.report_path.write_text("# ACME 报告\n\n正文\n", encoding="utf-8")
  27. stamp_report_date(self.report_path, date(2026, 7, 23))
  28. report = self.report_path.read_text(encoding="utf-8")
  29. self.assertTrue(
  30. report.startswith("# ACME 报告\n\n**报告日期:** 2026-07-23")
  31. )
  32. def test_canonical_date_is_idempotent(self) -> None:
  33. self.report_path.write_text(
  34. "# ACME 报告\n\n**报告日期:** 2026-07-23\n",
  35. encoding="utf-8",
  36. )
  37. self.assertFalse(
  38. stamp_report_date(self.report_path, date(2026, 7, 23))
  39. )
  40. def test_requires_separate_evidence_status_column(self) -> None:
  41. self.report_path.write_text(
  42. "# 报告\n\n### 安全域\n\n| 风险项 | 等级 | 证据位置 |\n",
  43. encoding="utf-8",
  44. )
  45. with self.assertRaisesRegex(ValueError, "证据状态"):
  46. validate_report_risk_semantics(self.report_path)
  47. def test_rejects_low_domain_with_medium_detail(self) -> None:
  48. self.report_path.write_text(
  49. "# 报告\n\n"
  50. "| 专业域 | 风险等级 |\n"
  51. "|---|---|\n"
  52. "| **安全 (Security)** | **LOW** |\n\n"
  53. "### 安全域\n\n"
  54. "| 风险项 | 风险等级 | 证据状态 |\n"
  55. "|---|---|---|\n"
  56. "| PT-02 | Medium | 已验证 |\n",
  57. encoding="utf-8",
  58. )
  59. with self.assertRaisesRegex(ValueError, "总评为 low"):
  60. validate_report_risk_semantics(self.report_path)
  61. def test_accepts_medium_domain_with_separated_evidence_status(self) -> None:
  62. self.report_path.write_text(
  63. "# 报告\n\n"
  64. "| 专业域 | 风险等级 |\n"
  65. "|---|---|\n"
  66. "| **安全 (Security)** | **MEDIUM** |\n\n"
  67. "### 安全域\n\n"
  68. "| 风险项 | 风险等级 | 证据状态 |\n"
  69. "|---|---|---|\n"
  70. "| PT-02 | Medium | 已验证 |\n"
  71. "| MFA | Low | 自述待验证 |\n",
  72. encoding="utf-8",
  73. )
  74. validate_report_risk_semantics(self.report_path)
  75. def test_accepts_numbered_security_domain_heading(self) -> None:
  76. self.report_path.write_text(
  77. "# 报告\n\n"
  78. "| 专业域 | 风险等级 |\n"
  79. "|---|---|\n"
  80. "| **安全 (Security)** | **MEDIUM** |\n\n"
  81. "### 2.1 安全域 — 风险等级:medium\n\n"
  82. "| 风险项 | 风险等级 | 证据状态 |\n"
  83. "|---|---|---|\n"
  84. "| PT-02 | Medium | 已验证 |\n",
  85. encoding="utf-8",
  86. )
  87. validate_report_risk_semantics(self.report_path)
  88. def test_accepts_numbered_security_review_heading(self) -> None:
  89. self.report_path.write_text(
  90. "# UNKNOWN 报告\n\n"
  91. "| 专业域 | 风险等级 |\n"
  92. "|---|---|\n"
  93. "| 安全审查 | high |\n\n"
  94. "### 3.1 安全审查(Security Review)\n\n"
  95. "| 风险领域 | 风险等级 | 证据状态 |\n"
  96. "|---|---|---|\n"
  97. "| 身份与访问控制 | unknown | 缺失 |\n",
  98. encoding="utf-8",
  99. )
  100. validate_report_risk_semantics(self.report_path)
  101. if __name__ == "__main__":
  102. unittest.main()