| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- """NovaPay 续约复审(**整改后**)数据房间的纯业务层查询测试。
- > 整改前("故意埋入 5 条风险线索")快照位于
- > ``data-room/NOVAPAY.original-2026-08-19/_fixtures-snapshot/``。
- > 当前 ``fixtures/`` 是整改后版本,对应 `policies/vendor-renewal.md` 的
- > ``renewal`` 档 —— 数据应当让 Agent 顺利得出可提交审批的结论。
- 覆盖以下场景(无需调用模型):
- 1. 供应商主数据:NovaPay 已从 ``renewal-review`` 回到 ``active``,
- 延迟交付率回落至历史基线,``notes`` 字段记录 PT-01 修复时间线。
- 2. 安全事故:2026-02-15 撞库事件状态为 ``closed``,且
- ``investigation_report`` 字段已由 ``null`` 变为 Atrium Labs 出具的
- 独立调查报告引用 —— 关键信息缺失被补齐。
- 3. 负面新闻:NovaPay 共 6 条记录,包含 ``recovered``、``denied``、
- ``confirmed`` 三种状态,覆盖 ``labor`` / ``financial`` / ``operational``
- 三个分类;声誉 Agent 应能识别"历史事件已结清"的语义。
- 4. 制裁名单:NovaPay 仍无命中,必须返回空数组而非抛错。
- 5. 跨供应商隔离:新增 NovaPay 记录不能影响 ACME 的查询结果数量。
- """
- from __future__ import annotations
- from pathlib import Path
- import unittest
- from vendor_guard.repository import JsonRepository
- PROJECT_ROOT = Path(__file__).resolve().parents[1]
- class NovapayFixtureTests(unittest.TestCase):
- """针对 ``fixtures/`` 中**整改后** NovaPay 记录的业务层查询行为。"""
- def setUp(self) -> None:
- self.repository = JsonRepository(PROJECT_ROOT / "fixtures")
- # -------- vendor_registry.json --------
- def test_vendor_registry_finds_novapay(self) -> None:
- vendor = self.repository.get_vendor("NOVAPAY")
- self.assertTrue(vendor["found"])
- self.assertEqual(vendor["vendor"], "NOVAPAY")
- self.assertEqual(vendor["status"], "active")
- self.assertTrue(vendor["beneficial_owner_verified"])
- def test_vendor_registry_lookup_is_case_insensitive_for_novapay(self) -> None:
- for variant in ("novapay", "NovaPay", "NOVAPAY", "NovaPAY"):
- with self.subTest(variant=variant):
- vendor = self.repository.get_vendor(variant)
- self.assertTrue(vendor["found"], msg=f"未命中:{variant}")
- self.assertEqual(vendor["vendor"], "NOVAPAY")
- def test_vendor_registry_shows_healthy_operating_signals(self) -> None:
- """整改后 ``late_delivery_rate`` 应当低于基线警戒值(0.10),
- ``disputes_24m`` 不应超过 2,且 ``notes`` 反映 PT-01 修复完成。"""
- vendor = self.repository.get_vendor("NOVAPAY")
- self.assertLess(vendor["late_delivery_rate"], 0.10)
- self.assertLessEqual(vendor["disputes_24m"], 2)
- self.assertIn("PT-01", vendor["notes"])
- self.assertIn("复测通过", vendor["notes"])
- def test_vendor_registry_addition_does_not_change_acme(self) -> None:
- acme = self.repository.get_vendor("ACME")
- self.assertTrue(acme["found"])
- self.assertEqual(acme["late_delivery_rate"], 0.08)
- self.assertEqual(acme["disputes_24m"], 2)
- self.assertNotIn("notes", acme)
- # -------- security_incidents.json --------
- def test_security_incident_returns_single_novapay_record(self) -> None:
- incidents = self.repository.get_incidents("NOVAPAY")
- self.assertEqual(len(incidents), 1)
- incident = incidents[0]
- self.assertEqual(incident["severity"], "high")
- self.assertEqual(incident["status"], "closed")
- self.assertEqual(incident["date"], "2026-02-15")
- def test_security_incident_now_carries_investigation_report(self) -> None:
- """整改前 ``investigation_report`` 为 ``null``(关键信息缺失)。
- 整改后必须引用 Atrium Labs 2026-04-10 独立调查报告,
- 让 Agent 能看到客观证据而不是空白。"""
- incidents = self.repository.get_incidents("NOVAPAY")
- self.assertEqual(len(incidents), 1)
- self.assertIn("investigation_report", incidents[0])
- report = incidents[0]["investigation_report"]
- self.assertIsNotNone(report)
- self.assertIn("Atrium Labs", report)
- self.assertIn("2026-04-10", report)
- def test_security_incident_lookup_is_case_insensitive_for_novapay(self) -> None:
- for variant in ("novapay", "NovaPay"):
- with self.subTest(variant=variant):
- incidents = self.repository.get_incidents(variant)
- self.assertEqual(len(incidents), 1)
- self.assertEqual(incidents[0]["vendor"].upper(), "NOVAPAY")
- def test_security_incident_isolation_acme_count_unchanged(self) -> None:
- incidents = self.repository.get_incidents("ACME")
- self.assertEqual(len(incidents), 1)
- self.assertEqual(incidents[0]["severity"], "medium")
- # -------- adverse_news.json --------
- def test_adverse_news_returns_all_six_novapay_records(self) -> None:
- news = self.repository.get_adverse_news("NOVAPAY")
- self.assertEqual(len(news), 6)
- dates = {item["date"] for item in news}
- self.assertEqual(
- dates,
- {
- "2026-04-22",
- "2026-06-10",
- "2026-07-03",
- "2026-07-19",
- "2026-08-05",
- "2026-08-12",
- },
- )
- def test_adverse_news_distinguishes_recovered_denied_confirmed(self) -> None:
- """整改后三类状态必须被透传,便于声誉 Agent 区分
- "历史事件已结清"(``recovered``)、"传言被否认"(``denied``)、
- "新近已验证事件"(``confirmed``)。"""
- news = self.repository.get_adverse_news("NOVAPAY")
- statuses = {item["status"] for item in news}
- self.assertEqual(statuses, {"recovered", "denied", "confirmed"})
- def test_adverse_news_covers_labor_financial_and_operational(self) -> None:
- news = self.repository.get_adverse_news("NOVAPAY")
- categories = {item["category"] for item in news}
- self.assertEqual(categories, {"labor", "financial", "operational"})
- def test_adverse_news_keeps_denied_record_distinct(self) -> None:
- """``denied`` 记录(TechRabbit 收购传闻 + NovaPay 官方否认)必须
- 与 ``confirmed`` / ``recovered`` 记录分开,便于声誉 Agent 按
- ``skills/reputation-review/SKILL.md`` 的来源优先级排序。"""
- news = self.repository.get_adverse_news("NOVAPAY")
- denied = [item for item in news if item["status"] == "denied"]
- confirmed = [item for item in news if item["status"] == "confirmed"]
- recovered = [item for item in news if item["status"] == "recovered"]
- self.assertEqual(len(denied), 1)
- self.assertEqual(len(confirmed), 2)
- self.assertEqual(len(recovered), 3)
- self.assertIn("收购", denied[0]["summary"])
- def test_adverse_news_recovered_records_carry_resolution_evidence(self) -> None:
- """``recovered`` 记录必须包含客观的恢复/结清证据,否则 Agent
- 无法判断历史事件的影响是否仍存在。允许使用 ``恢复`` /
- ``已完成`` / ``已结清`` 等多种表达。"""
- news = self.repository.get_adverse_news("NOVAPAY")
- recovered = [item for item in news if item["status"] == "recovered"]
- resolution_markers = ("恢复", "已完成", "已结清", "recovered")
- for item in recovered:
- with self.subTest(date=item["date"]):
- haystack = item["summary"] + item.get("source", "")
- self.assertTrue(
- any(marker in haystack for marker in resolution_markers),
- msg=f"recovered 记录缺少恢复证据:{item['date']}",
- )
- def test_adverse_news_lookup_is_case_insensitive_for_novapay(self) -> None:
- for variant in ("novapay", "NovaPay", "NOVAPAY"):
- with self.subTest(variant=variant):
- news = self.repository.get_adverse_news(variant)
- self.assertEqual(len(news), 6)
- def test_adverse_news_acme_count_unchanged(self) -> None:
- news = self.repository.get_adverse_news("ACME")
- self.assertEqual(len(news), 1)
- self.assertEqual(news[0]["status"], "settled")
- # -------- sanctions.json --------
- def test_sanctions_return_empty_for_novapay(self) -> None:
- result = self.repository.get_sanctions("NOVAPAY")
- self.assertEqual(result, [])
- def test_sanctions_lookup_is_case_insensitive_for_novapay(self) -> None:
- for variant in ("novapay", "NovaPay", "NOVAPAY"):
- with self.subTest(variant=variant):
- self.assertEqual(self.repository.get_sanctions(variant), [])
- def test_sanctions_still_empty_for_acme(self) -> None:
- self.assertEqual(self.repository.get_sanctions("ACME"), [])
- # -------- 跨供应商隔离(防止记录泄漏) --------
- def test_novapay_records_do_not_leak_into_acme_queries(self) -> None:
- self.assertEqual(len(self.repository.get_incidents("ACME")), 1)
- self.assertEqual(len(self.repository.get_adverse_news("ACME")), 1)
- # 反向:ACME 的事件也不应出现在 NovaPay 的查询中。
- novapay_incidents = self.repository.get_incidents("NOVAPAY")
- for incident in novapay_incidents:
- self.assertEqual(incident["vendor"], "NOVAPAY")
- novapay_news = self.repository.get_adverse_news("NOVAPAY")
- for item in novapay_news:
- self.assertEqual(item["vendor"], "NOVAPAY")
- if __name__ == "__main__":
- unittest.main()
|