test_pl_plan_product_certs.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. """证书保存状态保护:仅使用数据库边界替身,不连接业务数据库。"""
  2. from types import SimpleNamespace
  3. from unittest.mock import MagicMock
  4. import pytest
  5. from fastapi import HTTPException
  6. from app.services import pl_plan_service as svc
  7. @pytest.fixture
  8. def cert_case(monkeypatch):
  9. plan = SimpleNamespace(
  10. archive_status="pending", follow_status="pending_arrange",
  11. update_by="original", hazard_cert_ready=0,
  12. )
  13. product = SimpleNamespace(
  14. id="product-1", product_id="crm-1", product_name="测试危化品",
  15. purchase_cert_url="old.pdf", transport_cert_url=None,
  16. legal_use_cert_url=None, update_by="original",
  17. )
  18. db = MagicMock()
  19. plan_query, product_query = MagicMock(), MagicMock()
  20. plan_query.filter.return_value.first.return_value = plan
  21. product_query.filter.return_value.first.return_value = product
  22. product_query.filter.return_value.all.return_value = [product]
  23. db.query.side_effect = lambda model: plan_query if model is svc.PlPlan else product_query
  24. monkeypatch.setattr(svc, "fetch_hazard_flags_map", lambda *args: {})
  25. monkeypatch.setattr(svc, "resolve_hazard_flags_from_crm", lambda *args, **kwargs: (True, False))
  26. auto_ready = MagicMock()
  27. monkeypatch.setattr(svc, "maybe_auto_set_hazard_cert_ready", auto_ready)
  28. item = SimpleNamespace(
  29. id=product.id, purchase_cert_url=" new.pdf,other.pdf ",
  30. transport_cert_url=" ", legal_use_cert_url=None,
  31. )
  32. def save():
  33. return svc.update_plan_product_certs(
  34. db, tenant_no="test-tenant", plan_id="plan-1", items=[item], user_id="editor"
  35. )
  36. return SimpleNamespace(
  37. plan=plan, product=product, db=db, save=save,
  38. auto_ready=auto_ready, plan_query=plan_query,
  39. )
  40. @pytest.mark.parametrize("state", ["cancelled", "archived", "missing"])
  41. def test_invalid_plan_rejected_before_any_certificate_write(cert_case, state):
  42. c = cert_case
  43. if state == "cancelled":
  44. c.plan.follow_status = "cancelled"
  45. elif state == "archived":
  46. c.plan.archive_status = "archived"
  47. else:
  48. c.plan_query.filter.return_value.first.return_value = None
  49. with pytest.raises(HTTPException) as error:
  50. c.save()
  51. assert error.value.status_code == (404 if state == "missing" else 400)
  52. if state == "cancelled":
  53. assert "已作废" in error.value.detail
  54. assert c.product.purchase_cert_url == "old.pdf"
  55. assert c.product.update_by == c.plan.update_by == "original"
  56. assert c.plan.hazard_cert_ready == 0
  57. c.db.query.assert_called_once_with(svc.PlPlan)
  58. c.db.commit.assert_not_called()
  59. c.auto_ready.assert_not_called()
  60. def test_active_plan_can_save_and_clear_certificate_urls(cert_case):
  61. c = cert_case
  62. assert c.save() == [c.product]
  63. assert c.product.purchase_cert_url == "new.pdf,other.pdf"
  64. assert c.product.transport_cert_url is None
  65. assert c.product.legal_use_cert_url is None
  66. assert c.product.update_by == c.plan.update_by == "editor"
  67. c.db.commit.assert_called_once()
  68. c.db.refresh.assert_called_once_with(c.product)
  69. c.auto_ready.assert_called_once()