237 lines
11 KiB
Python
237 lines
11 KiB
Python
"""Library display units and query/identity contracts after the approved redesign."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import socket
|
|
from copy import deepcopy
|
|
from datetime import datetime
|
|
from types import SimpleNamespace
|
|
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
import pytest
|
|
from PySide6.QtCore import Qt, QTimer
|
|
from PySide6.QtTest import QTest
|
|
from PySide6.QtWidgets import QApplication, QPushButton
|
|
|
|
from doctor_workstation.services import DemoDoctorRepository
|
|
from doctor_workstation.ui.pages import prescription_library as library
|
|
from doctor_workstation.ui.theme import apply_theme
|
|
|
|
|
|
class QueryRepository:
|
|
def __init__(self):
|
|
self.calls = []
|
|
self.rows = [
|
|
{"id": 41, "prescription_name": "乙方", "formula_type": "aux", "is_public": False,
|
|
"creator_id": 7, "creator_name": "本页医生", "efficacy": "益气养阴",
|
|
"herbs": [{"name": "白芍", "dosage": "15g"}], "create_time": "2026-07-15 10:30"},
|
|
{"id": 42, "prescription_name": "甲方", "formula_type": "main", "is_public": True,
|
|
"creator_id": 8, "creator_name": "其他医生", "efficacy": "清热祛湿",
|
|
"herbs": [{"name": "茯苓", "dosage": 10}], "create_time": "2026-07-18 16:20"},
|
|
]
|
|
|
|
def list_prescription_templates(self, **query):
|
|
self.calls.append(deepcopy(query))
|
|
# Server total deliberately differs from the current-page/effect-filter count.
|
|
return {"lists": deepcopy(self.rows), "count": 44}
|
|
|
|
|
|
def settle(app):
|
|
for _ in range(4):
|
|
app.processEvents()
|
|
|
|
|
|
def record_id(record):
|
|
return record["id"] if isinstance(record, dict) else record.id
|
|
|
|
|
|
def row_record(table, index):
|
|
return table.item(index, 0).data(Qt.ItemDataRole.UserRole)
|
|
|
|
|
|
def row_buttons(table, index):
|
|
return {button.accessibleName(): button for button in table.cellWidget(index, 9).findChildren(QPushButton)}
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def application():
|
|
app = QApplication.instance() or QApplication([])
|
|
apply_theme(app)
|
|
return app
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def local_execution(monkeypatch):
|
|
def reject(*_args, **_kwargs):
|
|
pytest.fail("Library regression tests must not contact external services")
|
|
|
|
def immediate(function, *, on_success=None, on_error=None, on_finished=None):
|
|
try:
|
|
result = function()
|
|
except Exception as error:
|
|
if on_error:
|
|
on_error(error)
|
|
raise
|
|
else:
|
|
if on_success:
|
|
on_success(result)
|
|
finally:
|
|
if on_finished:
|
|
on_finished()
|
|
|
|
monkeypatch.setattr(socket.socket, "connect", reject)
|
|
monkeypatch.setattr(socket.socket, "connect_ex", reject)
|
|
monkeypatch.setattr(socket, "create_connection", reject)
|
|
monkeypatch.setattr(library, "run_async", immediate)
|
|
|
|
|
|
@pytest.fixture
|
|
def page_factory(application):
|
|
opened = []
|
|
|
|
def create(repository=None, *, permissions=None, user=None):
|
|
repository = repository or QueryRepository()
|
|
page = library.PrescriptionLibraryPage(
|
|
repository, {"*"} if permissions is None else permissions,
|
|
user or SimpleNamespace(id=7, root=0, role_ids=[]),
|
|
)
|
|
opened.append(page)
|
|
page.resize(1328, 884)
|
|
page.show()
|
|
settle(application)
|
|
assert page.table.rowCount() == 2
|
|
return page, repository
|
|
|
|
yield create
|
|
for page in opened:
|
|
for timer in page.findChildren(QTimer):
|
|
timer.stop()
|
|
page.close()
|
|
page.deleteLater()
|
|
settle(application)
|
|
|
|
|
|
@pytest.mark.parametrize("dose, expected", [(10, "10g"), ("10", "10g"), ("10g", "10g"), ("15G", "15G"), (0, "0g")])
|
|
def test_display_unit_is_added_once_without_mutating_dose(dose, expected):
|
|
row = {"herbs": [{"medicine_id": 11, "name": "白芍", "dosage": dose}]}
|
|
before = deepcopy(row)
|
|
assert library._herbs_detail(None, row) == f"白芍 {expected}"
|
|
assert row == before
|
|
assert library._herbs_detail(None, {"herbs": [{"medicine_name": "茯苓", "amount": dose}]}) == f"茯苓 {expected}"
|
|
|
|
|
|
def test_demo_raw_doses_and_default_unselected_state_are_preserved(page_factory, monkeypatch):
|
|
monkeypatch.setattr(library, "datetime", SimpleNamespace(now=lambda: datetime(2026, 9, 5)))
|
|
repository = DemoDoctorRepository()
|
|
session = repository.login(repository.DEMO_ACCOUNT, repository.DEMO_PASSWORD)
|
|
originals = {identifier: repository.get_prescription_template(identifier) for identifier in (701, 702)}
|
|
snapshots = {identifier: deepcopy((row.herbs, row.raw)) for identifier, row in originals.items()}
|
|
assert library._herbs_detail(None, originals[701]) == "柴胡 10g、白芍 15g、茯苓 15g"
|
|
assert library._herbs_detail(None, originals[702]) == "酸枣仁 20g、夜交藤 30g"
|
|
assert {identifier: (row.herbs, row.raw) for identifier, row in originals.items()} == snapshots
|
|
page, _ = page_factory(repository, permissions=session.permissions, user=session.user)
|
|
assert page.table.columnCount() == 10
|
|
assert [page.table.horizontalHeader().logicalIndex(index) for index in range(10)] == list(range(10))
|
|
assert page.table.currentRow() == -1
|
|
assert all(not button.isEnabled() for button in (page.view_button, page.ai_button, page.edit_button, page.delete_button))
|
|
assert [page.metric_cards[key].value_label.text() for key in ("total", "private", "public", "month")] == ["2", "1", "1", "0"]
|
|
for index in range(2):
|
|
assert all(button.isEnabled() for button in row_buttons(page.table, index).values())
|
|
assert len(row_buttons(page.table, index)) == 4
|
|
assert page.table.item(index, 5).text() == "—"
|
|
identifier = record_id(row_record(page.table, index))
|
|
assert page.table.item(index, 4).text() == library._herbs_detail(None, originals[identifier])
|
|
|
|
|
|
def test_query_return_reset_favorite_tab_and_page_send_original_dto(page_factory, application):
|
|
page, repository = page_factory()
|
|
repository.calls.clear()
|
|
page.name_filter.setText(" 测试名称 ")
|
|
page.filter_disclosure.set_expanded(True)
|
|
settle(application)
|
|
page.formula_filter.setCurrentIndex(page.formula_filter.findData("主方"))
|
|
page.visibility_filter.setCurrentIndex(page.visibility_filter.findData(1))
|
|
page.effect_filter.setCurrentIndex(page.effect_filter.findData("清热祛湿"))
|
|
assert repository.calls == [] # Combo selection still requires query or Return.
|
|
QTest.keyClick(page.name_filter, Qt.Key.Key_Return)
|
|
settle(application)
|
|
filtered = {"prescription_name": "测试名称", "formula_type": "主方", "is_public": 1, "page_no": 1, "page_size": 15}
|
|
assert repository.calls == [filtered]
|
|
assert page.table.rowCount() == 1
|
|
assert record_id(row_record(page.table, 0)) == 42
|
|
assert page.metric_cards["total"].value_label.text() == "44"
|
|
assert page.metric_cards["private"].value_label.text() == "0"
|
|
assert page.metric_cards["public"].value_label.text() == "1"
|
|
page.pager.load_more()
|
|
assert repository.calls[-1] == dict(filtered, page_no=2)
|
|
assert page.pager.page == 2
|
|
page.favorite_tab.click()
|
|
assert repository.calls[-2:] == [filtered, dict(filtered, page_no=2)] # Same query refreshes its loaded prefix.
|
|
assert page.favorite_tab.isChecked() and not page.all_tab.isChecked()
|
|
page.reset_button.click()
|
|
assert repository.calls[-1] == {"prescription_name": "", "formula_type": "", "is_public": "", "page_no": 1, "page_size": 15}
|
|
assert page.effect_filter.currentData() == ""
|
|
assert page.favorite_tab.isChecked()
|
|
assert page.table.rowCount() == 2
|
|
|
|
|
|
@pytest.mark.parametrize("column, order", [(0, Qt.SortOrder.DescendingOrder), (2, Qt.SortOrder.DescendingOrder)])
|
|
def test_sort_then_refresh_keeps_selection_tags_and_each_action_bound_to_visible_id(
|
|
page_factory, application, monkeypatch, column, order
|
|
):
|
|
page, repository = page_factory()
|
|
original_rows = deepcopy(repository.rows)
|
|
table = page.table
|
|
visited = []
|
|
monkeypatch.setattr(page, "_view_selected", lambda: visited.append(record_id(table.current_data())))
|
|
table.sortItems(column, order)
|
|
settle(application)
|
|
assert [record_id(row_record(table, row)) for row in range(2)] == [42, 41]
|
|
|
|
def verify_visible_actions():
|
|
for index in range(2):
|
|
record = row_record(table, index)
|
|
assert table.item(index, 2).text() == ("主方" if record["formula_type"] == "main" else "辅方")
|
|
assert table.item(index, 6).text() == ("所有人可见" if record["is_public"] else "仅自己可见")
|
|
buttons = row_buttons(table, index)
|
|
assert buttons["编辑处方模板"].isEnabled() == (record["creator_id"] == 7)
|
|
table.selectRow(1 - index)
|
|
button = buttons["查看处方模板"]
|
|
QTest.mouseClick(button, Qt.MouseButton.LeftButton, pos=button.rect().center())
|
|
assert visited[-1] == record["id"]
|
|
assert record_id(table.current_data()) == record["id"]
|
|
|
|
verify_visible_actions()
|
|
table.selectRow(next(index for index in range(2) if record_id(row_record(table, index)) == 41))
|
|
page.refresh()
|
|
settle(application)
|
|
assert record_id(table.current_data()) == 41
|
|
assert [record_id(row_record(table, row)) for row in range(2)] == [42, 41]
|
|
verify_visible_actions()
|
|
assert visited == [42, 41, 42, 41]
|
|
assert repository.rows == original_rows
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"permissions, user, expected_actions, can_edit_foreign",
|
|
[({"wcf.prescription/read"}, SimpleNamespace(id=7, root=0, role_ids=[]), {"查看处方模板", "AI 解释"}, False),
|
|
({"wcf.prescription/*"}, SimpleNamespace(id=7, root=0, role_ids=[]), {"查看处方模板", "AI 解释", "编辑处方模板", "删除处方模板"}, False),
|
|
({"wcf.prescription/*"}, SimpleNamespace(id=7, root=0, role_ids=[3]), {"查看处方模板", "AI 解释", "编辑处方模板", "删除处方模板"}, True)],
|
|
)
|
|
def test_row_action_permissions_and_foreign_public_management_are_preserved(
|
|
page_factory, permissions, user, expected_actions, can_edit_foreign
|
|
):
|
|
page, _ = page_factory(permissions=permissions, user=user)
|
|
index = next(index for index in range(2) if record_id(row_record(page.table, index)) == 42)
|
|
buttons = row_buttons(page.table, index)
|
|
assert set(buttons) == expected_actions
|
|
assert buttons["查看处方模板"].isEnabled()
|
|
assert buttons["AI 解释"].isEnabled()
|
|
if "编辑处方模板" in buttons:
|
|
assert buttons["编辑处方模板"].isEnabled() == can_edit_foreign
|
|
assert buttons["删除处方模板"].isEnabled() == can_edit_foreign
|
|
else:
|
|
assert page.edit_button.isHidden() and page.delete_button.isHidden()
|