"""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()