协议样本、生产产出和盘点明细按行更新并保留原 id;补齐存储证明质检预填、离职人员显示名,以及样本采集通知模板字段。 Co-authored-by: Cursor <cursoragent@cursor.com>
406 lines
14 KiB
Python
406 lines
14 KiB
Python
"""协议新增样本服务号通知单测。"""
|
|
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"}
|