test_wechat_mp_message.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. from unittest.mock import MagicMock, patch
  2. from app.core.config import settings
  3. def test_send_approval_requires_mp_openid():
  4. from app.services.wechat_mp_message import send_oa_approval_notify
  5. r = send_oa_approval_notify(
  6. mp_openid="",
  7. pagepath="pages/oa/webview/index?instanceId=i1&taskId=t1&mode=approve",
  8. title="请假",
  9. applicant="张三",
  10. node_name="主管审批",
  11. time_text="2026-08-13 12:00",
  12. )
  13. assert r.get("success") is False
  14. assert r.get("error") == "missing_mp_openid"
  15. def test_send_approval_requires_template_id():
  16. from app.services.wechat_mp_message import send_oa_approval_notify
  17. with patch.object(settings, "WX_MP_TEMPLATE_APPROVAL", ""):
  18. r = send_oa_approval_notify(
  19. mp_openid="mp_openid_1",
  20. pagepath="pages/oa/webview/index?instanceId=i1&taskId=t1&mode=approve",
  21. title="请假",
  22. applicant="张三",
  23. node_name="主管审批",
  24. time_text="2026-08-13 12:00",
  25. )
  26. assert r.get("success") is False
  27. assert r.get("error") == "missing_template_id"
  28. @patch("app.services.wechat_mp_message.get_mp_access_token", return_value="tok_mp")
  29. @patch("app.services.wechat_mp_message.httpx.Client")
  30. def test_send_approval_success_payload_shape(mock_client_cls, _mock_token):
  31. from app.services.wechat_mp_message import send_oa_approval_notify
  32. mock_resp = MagicMock()
  33. mock_resp.raise_for_status = MagicMock()
  34. mock_resp.json.return_value = {"errcode": 0, "msgid": 1001}
  35. mock_client = MagicMock()
  36. mock_client.__enter__.return_value = mock_client
  37. mock_client.__exit__.return_value = False
  38. mock_client.post.return_value = mock_resp
  39. mock_client_cls.return_value = mock_client
  40. long_title = "这是一个非常非常长的审批标题需要截断处理一下才行"
  41. with patch.object(settings, "WX_MP_TEMPLATE_APPROVAL", "tpl_approval"), patch.object(
  42. settings, "WXPAY_MINI_APPID", "wx_mini_appid"
  43. ), patch.object(settings, "WX_MP_TPL_FIELD_TITLE", "thing12"), patch.object(
  44. settings, "WX_MP_TPL_FIELD_APPLICANT", "thing21"
  45. ), patch.object(settings, "WX_MP_TPL_FIELD_NODE", "thing7"), patch.object(
  46. settings, "WX_MP_TPL_FIELD_TIME", "time3"
  47. ), patch.object(settings, "WX_MP_TPL_FIELD_PROCESS", "thing48"):
  48. r = send_oa_approval_notify(
  49. mp_openid="mp_openid_1",
  50. pagepath="/pages/oa/webview/index?instanceId=i1&taskId=t1&mode=approve",
  51. title=long_title,
  52. applicant="张三",
  53. node_name="主管审批",
  54. time_text="2026-08-13 12:00",
  55. process_name="请假申请",
  56. )
  57. assert r == {"success": True, "msgid": 1001}
  58. assert mock_client.post.call_count == 1
  59. args, kwargs = mock_client.post.call_args
  60. assert args[0].startswith(
  61. "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="
  62. )
  63. payload = kwargs["json"]
  64. assert payload["touser"] == "mp_openid_1"
  65. assert payload["template_id"] == "tpl_approval"
  66. assert payload["miniprogram"] == {
  67. "appid": "wx_mini_appid",
  68. "pagepath": "pages/oa/webview/index?instanceId=i1&taskId=t1&mode=approve",
  69. }
  70. assert payload["data"]["thing12"]["value"] == long_title[:20]
  71. assert payload["data"]["thing21"]["value"] == "张三"
  72. assert payload["data"]["thing7"]["value"] == "主管审批"
  73. assert payload["data"]["time3"]["value"] == "2026-08-13 12:00"
  74. assert payload["data"]["thing48"]["value"] == "请假申请"
  75. @patch("app.services.wechat_mp_message.get_mp_access_token", return_value="tok_mp")
  76. @patch("app.services.wechat_mp_message.httpx.Client")
  77. def test_send_approval_skips_blank_field_keys(mock_client_cls, _mock_token):
  78. """临时模板仅申请人+时间时,TITLE/NODE 留空不进 data。"""
  79. from app.services.wechat_mp_message import send_oa_approval_notify
  80. mock_resp = MagicMock()
  81. mock_resp.raise_for_status = MagicMock()
  82. mock_resp.json.return_value = {"errcode": 0, "msgid": 3003}
  83. mock_client = MagicMock()
  84. mock_client.__enter__.return_value = mock_client
  85. mock_client.__exit__.return_value = False
  86. mock_client.post.return_value = mock_resp
  87. mock_client_cls.return_value = mock_client
  88. with patch.object(settings, "WX_MP_TEMPLATE_APPROVAL", "tpl_tmp"), patch.object(
  89. settings, "WXPAY_MINI_APPID", "wx_mini_appid"
  90. ), patch.object(settings, "WX_MP_TPL_FIELD_TITLE", ""), patch.object(
  91. settings, "WX_MP_TPL_FIELD_APPLICANT", "thing1"
  92. ), patch.object(settings, "WX_MP_TPL_FIELD_NODE", ""), patch.object(
  93. settings, "WX_MP_TPL_FIELD_TIME", "time2"
  94. ), patch.object(settings, "WX_MP_TPL_FIELD_PROCESS", ""):
  95. r = send_oa_approval_notify(
  96. mp_openid="mp_openid_1",
  97. pagepath="pages/oa/webview/index?instanceId=i1&taskId=t1&mode=approve",
  98. title="请假",
  99. applicant="张三",
  100. node_name="主管审批",
  101. time_text="2026-08-13 12:00",
  102. )
  103. assert r.get("success") is True
  104. data = mock_client.post.call_args.kwargs["json"]["data"]
  105. assert data == {
  106. "thing1": {"value": "张三"},
  107. "time2": {"value": "2026-08-13 12:00"},
  108. }
  109. @patch("app.services.wechat_mp_message.get_mp_access_token", return_value="tok_mp")
  110. @patch("app.services.wechat_mp_message.httpx.Client")
  111. def test_send_cc_success_payload_shape(mock_client_cls, _mock_token):
  112. from app.services.wechat_mp_message import send_oa_cc_notify
  113. mock_resp = MagicMock()
  114. mock_resp.raise_for_status = MagicMock()
  115. mock_resp.json.return_value = {"errcode": 0, "msgid": 2002}
  116. mock_client = MagicMock()
  117. mock_client.__enter__.return_value = mock_client
  118. mock_client.__exit__.return_value = False
  119. mock_client.post.return_value = mock_resp
  120. mock_client_cls.return_value = mock_client
  121. with patch.object(settings, "WX_MP_TEMPLATE_CC", "tpl_cc"), patch.object(
  122. settings, "WXPAY_MINI_APPID", "wx_mini_appid"
  123. ), patch.object(settings, "WX_MP_TPL_CC_FIELD_TITLE", "thing5"), patch.object(
  124. settings, "WX_MP_TPL_CC_FIELD_APPLICANT", "thing2"
  125. ), patch.object(settings, "WX_MP_TPL_CC_FIELD_TIME", "time3"), patch.object(
  126. settings, "WX_MP_TPL_CC_FIELD_PROCESS", "thing1"
  127. ):
  128. r = send_oa_cc_notify(
  129. mp_openid="mp_openid_2",
  130. pagepath="pages/oa/webview/index?instanceId=i2&taskId=t2&mode=view",
  131. title="李四 - 报销申请",
  132. applicant="李四",
  133. node_name="抄送知会",
  134. time_text="2026-08-13 15:30",
  135. process_name="报销申请",
  136. )
  137. assert r == {"success": True, "msgid": 2002}
  138. payload = mock_client.post.call_args.kwargs["json"]
  139. assert payload["touser"] == "mp_openid_2"
  140. assert payload["template_id"] == "tpl_cc"
  141. assert payload["miniprogram"]["appid"] == "wx_mini_appid"
  142. assert payload["miniprogram"]["pagepath"].endswith("mode=view")
  143. assert payload["data"] == {
  144. "thing5": {"value": "李四 - 报销申请"},
  145. "thing2": {"value": "李四"},
  146. "time3": {"value": "2026-08-13 15:30"},
  147. "thing1": {"value": "报销申请"},
  148. }
  149. @patch("app.services.wechat_mp_message.get_mp_access_token", return_value="tok_mp")
  150. @patch("app.services.wechat_mp_message.httpx.Client")
  151. def test_send_applicant_progress_payload_shape(mock_client_cls, _mock_token):
  152. from app.services.wechat_mp_message import send_oa_applicant_progress_notify
  153. mock_resp = MagicMock()
  154. mock_resp.raise_for_status = MagicMock()
  155. mock_resp.json.return_value = {"errcode": 0, "msgid": 5005}
  156. mock_client = MagicMock()
  157. mock_client.__enter__.return_value = mock_client
  158. mock_client.__exit__.return_value = False
  159. mock_client.post.return_value = mock_resp
  160. mock_client_cls.return_value = mock_client
  161. with patch.object(
  162. settings, "WX_MP_TEMPLATE_APPLICANT_PROGRESS", "tpl_progress"
  163. ), patch.object(settings, "WXPAY_MINI_APPID", "wx_mini_appid"), patch.object(
  164. settings, "WX_MP_TPL_PROGRESS_FIELD_BUSINESS", "thing5"
  165. ), patch.object(
  166. settings, "WX_MP_TPL_PROGRESS_FIELD_PROCESS", "thing1"
  167. ), patch.object(
  168. settings, "WX_MP_TPL_PROGRESS_FIELD_APPROVER", "thing6"
  169. ), patch.object(
  170. settings, "WX_MP_TPL_PROGRESS_FIELD_RESULT", "const4"
  171. ), patch.object(settings, "WX_MP_TPL_PROGRESS_FIELD_TIME", "time11"):
  172. r = send_oa_applicant_progress_notify(
  173. mp_openid="mp_ini",
  174. pagepath="pages/oa/webview/index?instanceId=i1&taskId=t1&mode=view",
  175. business_name="张三 - 请假申请",
  176. process_name="请假申请",
  177. approver_name="李四",
  178. result="通过",
  179. time_text="2026-08-14 14:30",
  180. )
  181. assert r == {"success": True, "msgid": 5005}
  182. payload = mock_client.post.call_args.kwargs["json"]
  183. assert payload["template_id"] == "tpl_progress"
  184. assert payload["data"] == {
  185. "thing5": {"value": "张三 - 请假申请"},
  186. "thing1": {"value": "请假申请"},
  187. "thing6": {"value": "李四"},
  188. "const4": {"value": "通过"},
  189. "time11": {"value": "2026-08-14 14:30"},
  190. }
  191. @patch("app.services.wechat_mp_message.get_mp_access_token", return_value="tok_mp")
  192. @patch("app.services.wechat_mp_message.httpx.Client")
  193. def test_send_approval_retries_without_miniprogram_on_40165(
  194. mock_client_cls, _mock_token
  195. ):
  196. from app.services.wechat_mp_message import send_oa_approval_notify
  197. mock_resp_fail = MagicMock()
  198. mock_resp_fail.raise_for_status = MagicMock()
  199. mock_resp_fail.json.return_value = {
  200. "errcode": 40165,
  201. "errmsg": "invalid weapp pagepath",
  202. }
  203. mock_resp_ok = MagicMock()
  204. mock_resp_ok.raise_for_status = MagicMock()
  205. mock_resp_ok.json.return_value = {"errcode": 0, "msgid": 4004}
  206. mock_client = MagicMock()
  207. mock_client.__enter__.return_value = mock_client
  208. mock_client.__exit__.return_value = False
  209. mock_client.post.side_effect = [mock_resp_fail, mock_resp_ok]
  210. mock_client_cls.return_value = mock_client
  211. with patch.object(settings, "WX_MP_TEMPLATE_APPROVAL", "tpl_approval"), patch.object(
  212. settings, "WXPAY_MINI_APPID", "wx_mini_appid"
  213. ), patch.object(settings, "WX_MP_TPL_FIELD_TITLE", ""), patch.object(
  214. settings, "WX_MP_TPL_FIELD_APPLICANT", "thing1"
  215. ), patch.object(settings, "WX_MP_TPL_FIELD_NODE", ""), patch.object(
  216. settings, "WX_MP_TPL_FIELD_TIME", "time2"
  217. ), patch.object(settings, "WX_MP_TPL_FIELD_PROCESS", ""):
  218. r = send_oa_approval_notify(
  219. mp_openid="mp_openid_1",
  220. pagepath="pages/oa/webview/index?instanceId=i1&taskId=t1&mode=approve",
  221. title="请假",
  222. applicant="张三",
  223. node_name="主管审批",
  224. time_text="2026-08-13 12:00",
  225. )
  226. assert r == {"success": True, "msgid": 4004}
  227. assert mock_client.post.call_count == 2
  228. first = mock_client.post.call_args_list[0].kwargs["json"]
  229. second = mock_client.post.call_args_list[1].kwargs["json"]
  230. assert "miniprogram" in first
  231. assert "miniprogram" not in second