| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- """协议新增样本服务号通知单测。"""
- from datetime import datetime
- from unittest.mock import MagicMock, patch
- from app.services.sample_collect_wx_notify import (
- PROTOCOL_KIND_CELL,
- PROTOCOL_KIND_DONATION,
- TASK_TYPE_CELL,
- TASK_TYPE_DONATION,
- diff_new_sample_codes,
- format_protocol_create_time,
- format_sample_collect_title,
- protocol_status_blocks_notify,
- run_sample_collect_notify,
- sample_status_blocks_notify,
- sample_codes_from_orm_rows,
- sample_codes_from_payload,
- schedule_sample_collect_notify,
- )
- def test_sample_codes_from_payload_camel_and_snake():
- assert sample_codes_from_payload(
- [
- {"sampleCode": "A1"},
- {"sample_code": "A2"},
- {"sampleCode": ""},
- {},
- ]
- ) == {"A1", "A2"}
- def test_sample_codes_from_orm_rows():
- a = MagicMock(sample_code="X1")
- b = MagicMock(sample_code=" ")
- c = MagicMock(sample_code="X2")
- assert sample_codes_from_orm_rows([a, b, c]) == {"X1", "X2"}
- def test_diff_new_sample_codes_create_and_edit():
- assert diff_new_sample_codes(before_codes=[], submitted_codes=["b", "a"]) == [
- "a",
- "b",
- ]
- assert diff_new_sample_codes(
- before_codes=["a", "b"], submitted_codes=["a", "b", "c"]
- ) == ["c"]
- assert (
- diff_new_sample_codes(before_codes=["a"], submitted_codes=["a"]) == []
- )
- def test_format_sample_collect_title_with_and_without_eta():
- eta = datetime(2022, 11, 22, 16, 17)
- assert format_sample_collect_title(eta) == "样本采集(预计到达 11月22日)"
- assert format_sample_collect_title(None) == "样本采集(到达待定)"
- assert len(format_sample_collect_title(eta)) <= 20
- assert len(format_sample_collect_title(None)) <= 20
- def test_sample_type_name_by_code_uses_type_name_or_blank():
- from app.services.sample_collect_wx_notify import _sample_type_name_by_code
- db = MagicMock()
- type_row = MagicMock()
- type_row.id = "t1"
- type_row.type_name = " 胎盘亚全能干细胞 "
- db.query.return_value.filter.return_value.all.return_value = [type_row]
- named = MagicMock(sample_code="A", sample_type_id="t1")
- blank = MagicMock(sample_code="B", sample_type_id="")
- missing = MagicMock(sample_code="C", sample_type_id="gone")
- out = _sample_type_name_by_code(
- db, tenant_no="zkxw", samples=[named, blank, missing]
- )
- assert out == {"A": "胎盘亚全能干细胞", "B": "", "C": ""}
- def test_format_protocol_create_time():
- assert (
- format_protocol_create_time(datetime(2022, 11, 22, 16, 17))
- == "2022年11月22日 16:17"
- )
- assert format_protocol_create_time(None) == ""
- def test_send_sample_collect_notify_builds_data_and_truncates_title():
- with patch(
- "app.services.wechat_mp_message._send_template"
- ) as send_tpl, patch("app.services.wechat_mp_message.settings") as st:
- st.WX_MP_TEMPLATE_SAMPLE_COLLECT = "tpl-sc"
- st.WX_MP_TPL_SC_FIELD_TITLE = "thing2"
- st.WX_MP_TPL_SC_FIELD_APPLICANT = "thing8"
- st.WX_MP_TPL_SC_FIELD_TIME = "time3"
- st.WX_MP_TPL_SC_FIELD_TYPE = "phrase10"
- st.WX_MP_TPL_SC_FIELD_SAMPLE_TYPE = "thing17"
- send_tpl.return_value = {"success": True, "msgid": "1"}
- from app.services.wechat_mp_message import send_sample_collect_notify
- long_title = "样本采集(预计到达 11月22日)额外超长文字会被截断"
- result = send_sample_collect_notify(
- mp_openid="oid",
- title=long_title,
- applicant="彭某某",
- time_text="2022年11月22日 16:17",
- task_type=TASK_TYPE_CELL,
- sample_type="脐带间充质干细胞" + "甲" * 20,
- )
- assert result["success"] is True
- kwargs = send_tpl.call_args.kwargs
- assert kwargs["template_id"] == "tpl-sc"
- data = kwargs["data"]
- assert len(data["thing2"]["value"]) <= 20
- assert data["thing8"]["value"] == "彭某某"
- assert data["time3"]["value"] == "2022年11月22日 16:17"
- assert data["phrase10"]["value"] == "细胞存储"
- assert data["thing17"]["value"] == ("脐带间充质干细胞" + "甲" * 20)[:20]
- assert len(data["thing17"]["value"]) == 20
- send_sample_collect_notify(
- mp_openid="oid",
- title="样本采集(到达待定)",
- applicant="彭某某",
- time_text="2022年11月22日 16:17",
- task_type=TASK_TYPE_DONATION,
- sample_type=" ",
- )
- empty_data = send_tpl.call_args.kwargs["data"]
- assert empty_data["phrase10"]["value"] == "科研捐赠"
- assert "thing17" not in empty_data
- def test_schedule_skips_when_samples_not_in_payload():
- with patch(
- "app.services.sample_collect_wx_notify._notify_enabled", return_value=True
- ), patch(
- "app.services.sample_collect_wx_notify.threading.Thread"
- ) as th:
- schedule_sample_collect_notify(
- tenant_no="zkxw",
- protocol_id="p1",
- protocol_kind=PROTOCOL_KIND_CELL,
- before_codes=["a"],
- submitted_codes=None,
- )
- th.assert_not_called()
- def test_schedule_skips_when_no_new_codes():
- with patch(
- "app.services.sample_collect_wx_notify._notify_enabled", return_value=True
- ), patch(
- "app.services.sample_collect_wx_notify.threading.Thread"
- ) as th:
- schedule_sample_collect_notify(
- tenant_no="zkxw",
- protocol_id="p1",
- protocol_kind=PROTOCOL_KIND_CELL,
- before_codes=["a"],
- submitted_codes=["a"],
- )
- th.assert_not_called()
- def test_schedule_starts_thread_for_new_codes():
- with patch(
- "app.services.sample_collect_wx_notify._notify_enabled", return_value=True
- ), patch(
- "app.services.sample_collect_wx_notify.threading.Thread"
- ) as th:
- th.return_value = MagicMock()
- schedule_sample_collect_notify(
- tenant_no="zkxw",
- protocol_id="p1",
- protocol_kind=PROTOCOL_KIND_DONATION,
- before_codes=["a"],
- submitted_codes=["a", "b"],
- )
- th.assert_called_once()
- kwargs = th.call_args.kwargs
- assert kwargs["kwargs"]["new_sample_codes"] == ["b"]
- assert kwargs["kwargs"]["protocol_kind"] == PROTOCOL_KIND_DONATION
- th.return_value.start.assert_called_once()
- def test_run_notify_sends_per_code_per_user_and_idempotent():
- db = MagicMock()
- user = MagicMock()
- user.id = "u1"
- user.wx_mp_openid = "mp1"
- user.nick_name = "通知人"
- with patch(
- "app.services.sample_collect_wx_notify._notify_enabled", return_value=True
- ), patch(
- "app.services.sample_collect_wx_notify._load_cell_context",
- return_value={
- "create_time": datetime(2022, 11, 22, 16, 17),
- "applicant": "彭某某",
- "eta_by_code": {"S1": datetime(2022, 11, 22, 16, 17), "S2": None},
- "sample_type_by_code": {"S1": "围产期造血干细胞", "S2": ""},
- },
- ), patch(
- "app.services.sample_collect_wx_notify.list_notify_recipient_users",
- return_value=[user],
- ), patch(
- "app.services.sample_collect_wx_notify._already_success",
- side_effect=[False, True],
- ) as already, patch(
- "app.services.wechat_mp_message.send_sample_collect_notify",
- return_value={"success": True, "msgid": "1"},
- ) as send, patch(
- "app.services.sample_collect_wx_notify._write_success_log"
- ) as write_log:
- stats = run_sample_collect_notify(
- db,
- tenant_no="zkxw",
- protocol_id="p1",
- protocol_kind=PROTOCOL_KIND_CELL,
- new_sample_codes=["S1", "S2"],
- )
- assert stats["codes"] == 2
- assert stats["sent"] == 1
- assert stats["skipped"] == 1
- assert send.call_count == 1
- assert send.call_args.kwargs["task_type"] == TASK_TYPE_CELL
- assert send.call_args.kwargs["sample_type"] == "围产期造血干细胞"
- assert send.call_args.kwargs["title"] == "样本采集(预计到达 11月22日)"
- assert write_log.call_count == 1
- assert already.call_count == 2
- db.commit.assert_called_once()
- def test_run_notify_donation_task_type():
- db = MagicMock()
- user = MagicMock()
- user.id = "u1"
- user.wx_mp_openid = "mp1"
- with patch(
- "app.services.sample_collect_wx_notify._notify_enabled", return_value=True
- ), patch(
- "app.services.sample_collect_wx_notify._load_donation_context",
- return_value={
- "create_time": datetime(2022, 11, 22, 16, 17),
- "applicant": "李某",
- "eta_by_code": {"D1": None},
- "sample_type_by_code": {"D1": "脐带间充质干细胞"},
- },
- ), patch(
- "app.services.sample_collect_wx_notify.list_notify_recipient_users",
- return_value=[user],
- ), patch(
- "app.services.sample_collect_wx_notify._already_success",
- return_value=False,
- ), patch(
- "app.services.wechat_mp_message.send_sample_collect_notify",
- return_value={"success": True},
- ) as send, patch(
- "app.services.sample_collect_wx_notify._write_success_log"
- ):
- run_sample_collect_notify(
- db,
- tenant_no="zkxw",
- protocol_id="d1",
- protocol_kind=PROTOCOL_KIND_DONATION,
- new_sample_codes=["D1"],
- )
- assert send.call_args.kwargs["task_type"] == TASK_TYPE_DONATION
- assert send.call_args.kwargs["sample_type"] == "脐带间充质干细胞"
- assert send.call_args.kwargs["title"] == "样本采集(到达待定)"
- def test_protocol_and_sample_void_status_block_notify():
- assert protocol_status_blocks_notify("yzf") is True
- assert protocol_status_blocks_notify("sdfq") is True
- assert protocol_status_blocks_notify("sxz") is False
- assert protocol_status_blocks_notify("") is False
- assert sample_status_blocks_notify("yzf") is True
- assert sample_status_blocks_notify("ycj") is False
- assert sample_status_blocks_notify(None) is False
- def test_run_notify_skips_inactive_protocol():
- db = MagicMock()
- with patch(
- "app.services.sample_collect_wx_notify._notify_enabled", return_value=True
- ), patch(
- "app.services.sample_collect_wx_notify._load_cell_context",
- return_value={
- "create_time": datetime(2022, 11, 22, 16, 17),
- "applicant": "彭某某",
- "protocol_status": "sdfq",
- "eta_by_code": {"S1": None},
- "sample_status_by_code": {"S1": "ycj"},
- "sample_type_by_code": {"S1": "围产期造血干细胞"},
- },
- ), patch(
- "app.services.sample_collect_wx_notify.list_notify_recipient_users"
- ) as recipients, patch(
- "app.services.wechat_mp_message.send_sample_collect_notify"
- ) as send:
- stats = run_sample_collect_notify(
- db,
- tenant_no="zkxw",
- protocol_id="p1",
- protocol_kind=PROTOCOL_KIND_CELL,
- new_sample_codes=["S1"],
- )
- assert stats["sent"] == 0
- assert stats["skipped"] == 1
- recipients.assert_not_called()
- send.assert_not_called()
- db.commit.assert_not_called()
- def test_run_notify_skips_voided_sample_and_sends_the_rest():
- db = MagicMock()
- user = MagicMock()
- user.id = "u1"
- user.wx_mp_openid = "mp1"
- with patch(
- "app.services.sample_collect_wx_notify._notify_enabled", return_value=True
- ), patch(
- "app.services.sample_collect_wx_notify._load_donation_context",
- return_value={
- "create_time": datetime(2022, 11, 22, 16, 17),
- "applicant": "李某",
- "protocol_status": "sxz",
- "eta_by_code": {"D1": None, "D2": None},
- "sample_status_by_code": {"D1": "yzf", "D2": "ycj"},
- "sample_type_by_code": {"D1": "脐带间充质干细胞", "D2": "胎盘亚全能干细胞"},
- },
- ), patch(
- "app.services.sample_collect_wx_notify.list_notify_recipient_users",
- return_value=[user],
- ), patch(
- "app.services.sample_collect_wx_notify._already_success",
- return_value=False,
- ), patch(
- "app.services.wechat_mp_message.send_sample_collect_notify",
- return_value={"success": True},
- ) as send, patch(
- "app.services.sample_collect_wx_notify._write_success_log"
- ):
- stats = run_sample_collect_notify(
- db,
- tenant_no="zkxw",
- protocol_id="d1",
- protocol_kind=PROTOCOL_KIND_DONATION,
- new_sample_codes=["D1", "D2"],
- )
- assert stats["sent"] == 1
- assert stats["skipped"] == 1
- assert send.call_count == 1
- assert send.call_args.kwargs["sample_type"] == "胎盘亚全能干细胞"
- assert send.call_args.kwargs["task_type"] == TASK_TYPE_DONATION
- def test_list_notify_recipient_users_uses_effective_role_including_post():
- """接收人须走有效角色(含职位继承),不能只查 sys_user_role。"""
- db = MagicMock()
- user = MagicMock()
- user.id = "u-post"
- user.wx_mp_openid = "mp-openid"
- db.query.return_value.filter.return_value.all.return_value = [user]
- with patch(
- "app.services.sample_collect_wx_notify.list_user_ids_with_effective_role_name",
- return_value={"u-post"},
- ) as list_ids:
- from app.services.sample_collect_wx_notify import list_notify_recipient_users
- rows = list_notify_recipient_users(db, tenant_no="zkxw")
- list_ids.assert_called_once_with(
- db, tenant_no="zkxw", role_name="样本采集通知人员"
- )
- assert rows == [user]
- def test_list_notify_recipient_users_dedupes_same_openid():
- db = MagicMock()
- u1 = MagicMock()
- u1.id = "u1"
- u1.wx_mp_openid = "same-oid"
- u2 = MagicMock()
- u2.id = "u2"
- u2.wx_mp_openid = "same-oid"
- u3 = MagicMock()
- u3.id = "u3"
- u3.wx_mp_openid = "other-oid"
- db.query.return_value.filter.return_value.all.return_value = [u1, u2, u3]
- with patch(
- "app.services.sample_collect_wx_notify.list_user_ids_with_effective_role_name",
- return_value={"u1", "u2", "u3"},
- ):
- from app.services.sample_collect_wx_notify import list_notify_recipient_users
- rows = list_notify_recipient_users(db, tenant_no="zkxw")
- assert len(rows) == 2
- assert {r.wx_mp_openid for r in rows} == {"same-oid", "other-oid"}
|