更新bug
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from copy import deepcopy
|
||||
from datetime import date
|
||||
from typing import Any
|
||||
@@ -40,8 +41,11 @@ def immediate_async(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
on_success: Any = None,
|
||||
on_error: Any = None,
|
||||
on_finished: Any = None,
|
||||
pool: Any = None,
|
||||
priority: int = 0,
|
||||
**kwargs: Any,
|
||||
) -> object:
|
||||
del pool, priority
|
||||
try:
|
||||
result = function(*args, **kwargs)
|
||||
except Exception as error:
|
||||
@@ -305,6 +309,64 @@ def test_openai_failure_keeps_new_qwen_snapshot(
|
||||
page.close()
|
||||
|
||||
|
||||
def test_finished_only_cached_regeneration_unlocks_retry_and_keeps_snapshot(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
detail = _detail(109, 301, 509)
|
||||
saved = _snapshot("qwen", 1, "2026-08-14 10:45:00")
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
|
||||
class Repository:
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
return {"patient_id": patient_id, "reports": [saved]}
|
||||
|
||||
def generate_patient_ai_report(
|
||||
self,
|
||||
patient_id: int,
|
||||
*,
|
||||
model: str,
|
||||
) -> dict[str, Any]:
|
||||
raise AssertionError(f"queued worker must not run inline: {patient_id}/{model}")
|
||||
|
||||
page = ReceptionPage(
|
||||
Repository(),
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(detail["appointment"])
|
||||
jobs[0]["on_success"]({"detail": detail, "warnings": []})
|
||||
jobs[1]["on_success"]({"patient_id": 301, "reports": [saved]})
|
||||
jobs[1]["on_finished"]()
|
||||
assert page._ai_analysis_model_states["qwen"] == "success"
|
||||
assert page.ai_analysis_regenerate_button.isEnabled()
|
||||
|
||||
page.ai_analysis_regenerate_button.click()
|
||||
assert len(jobs) == 3
|
||||
assert page._ai_analysis_regenerating
|
||||
assert not page.ai_analysis_regenerate_button.isEnabled()
|
||||
|
||||
jobs[2]["on_finished"]()
|
||||
|
||||
assert not page._ai_analysis_regenerating
|
||||
assert page._ai_analysis_regeneration_model is None
|
||||
assert page._ai_analysis_model_states["qwen"] == "success"
|
||||
assert page.ai_summary_label.text() == "千问第 1 版诊断建议"
|
||||
assert page.ai_analysis_regenerate_button.isEnabled()
|
||||
assert "未返回有效结果" in page.ai_analysis_secondary_status.text()
|
||||
page.close()
|
||||
|
||||
|
||||
def test_late_patient_history_response_is_discarded_after_switch(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
@@ -353,6 +415,7 @@ def test_late_patient_history_response_is_discarded_after_switch(
|
||||
first_history_job = jobs[1]
|
||||
|
||||
page._select_record(second["appointment"])
|
||||
assert first_history_job["function"]() is reception_module._ASYNC_REQUEST_CANCELLED
|
||||
finish(jobs[2])
|
||||
second_history_job = jobs[3]
|
||||
finish(second_history_job)
|
||||
@@ -364,6 +427,563 @@ def test_late_patient_history_response_is_discarded_after_switch(
|
||||
page.close()
|
||||
|
||||
|
||||
def test_aba_switch_attaches_to_inflight_patient_generation_without_duplicate_post(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
first = _detail(110, 301, 510)
|
||||
second = _detail(111, 302, 511)
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
|
||||
class Repository:
|
||||
def __init__(self) -> None:
|
||||
self.generate_calls: list[tuple[int, str]] = []
|
||||
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
return {"patient_id": patient_id, "reports": []}
|
||||
|
||||
def generate_patient_ai_report(
|
||||
self,
|
||||
patient_id: int,
|
||||
*,
|
||||
model: str,
|
||||
) -> dict[str, Any]:
|
||||
self.generate_calls.append((patient_id, model))
|
||||
generated = _snapshot(model, 1, "2026-08-14 12:00:00")
|
||||
generated["patient_id"] = patient_id
|
||||
return {
|
||||
"patient_id": patient_id,
|
||||
"generated_report": generated,
|
||||
"report": generated,
|
||||
}
|
||||
|
||||
repository = Repository()
|
||||
page = ReceptionPage(
|
||||
repository,
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(first["appointment"])
|
||||
jobs[0]["on_success"]({"detail": first, "warnings": []})
|
||||
jobs[1]["on_success"]({"patient_id": 301, "reports": []})
|
||||
first_qwen_job = jobs[2]
|
||||
qwen_result = first_qwen_job["function"]()
|
||||
|
||||
page._select_record(second["appointment"])
|
||||
page._select_record(first["appointment"])
|
||||
jobs[4]["on_success"]({"detail": first, "warnings": []})
|
||||
|
||||
assert len(jobs) == 5
|
||||
first_qwen_job["on_success"](qwen_result)
|
||||
first_qwen_job["on_finished"]()
|
||||
|
||||
assert repository.generate_calls == [(301, "qwen")]
|
||||
assert len(jobs) == 6
|
||||
assert page._ai_analysis_model_states["qwen"] == "success"
|
||||
assert page.ai_summary_label.text() == "千问第 1 版诊断建议"
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_patient_history_get_is_singleflight_across_aba_switch(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
first = _detail(112, 301, 512)
|
||||
second = _detail(113, 302, 513)
|
||||
saved = _snapshot("qwen", 1, "2026-08-14 12:10:00")
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
|
||||
class Repository:
|
||||
def __init__(self) -> None:
|
||||
self.list_calls: list[int] = []
|
||||
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
self.list_calls.append(patient_id)
|
||||
return {"patient_id": patient_id, "reports": [saved]}
|
||||
|
||||
def generate_patient_ai_report(self, patient_id: int, *, model: str) -> Any:
|
||||
raise AssertionError(f"history exists: {patient_id}/{model}")
|
||||
|
||||
repository = Repository()
|
||||
page = ReceptionPage(
|
||||
repository,
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(first["appointment"])
|
||||
jobs[0]["on_success"]({"detail": first, "warnings": []})
|
||||
first_list_job = jobs[1]
|
||||
list_result = first_list_job["function"]()
|
||||
|
||||
page._select_record(second["appointment"])
|
||||
page._select_record(first["appointment"])
|
||||
jobs[3]["on_success"]({"detail": first, "warnings": []})
|
||||
|
||||
assert len(jobs) == 4
|
||||
assert repository.list_calls == [301]
|
||||
|
||||
first_list_job["on_success"](list_result)
|
||||
first_list_job["on_finished"]()
|
||||
|
||||
assert page._ai_analysis_model_states["qwen"] == "success"
|
||||
assert page.ai_summary_label.text() == "千问第 1 版诊断建议"
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_patient_ai_finished_only_tracks_qwen_workers_not_unrelated_openai(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
detail = _detail(114, 301, 514)
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
|
||||
class Repository:
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
return {"patient_id": patient_id, "reports": []}
|
||||
|
||||
def generate_patient_ai_report(self, patient_id: int, *, model: str) -> Any:
|
||||
raise AssertionError(f"worker must remain queued: {patient_id}/{model}")
|
||||
|
||||
page = ReceptionPage(
|
||||
Repository(),
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(detail["appointment"])
|
||||
jobs[0]["on_success"]({"detail": detail, "warnings": []})
|
||||
current_list_job = jobs[1]
|
||||
stale_qwen = (page._ai_analysis_generation - 2, 114, 301, "qwen")
|
||||
stale_openai = (page._ai_analysis_generation - 1, 114, 301, "openai")
|
||||
|
||||
page._patient_ai_generation_requests.add(stale_qwen)
|
||||
page._patient_ai_generation_finished(*stale_qwen)
|
||||
assert page._ai_analysis_model_states["qwen"] == "loading"
|
||||
|
||||
page._patient_ai_generation_requests.add(stale_openai)
|
||||
current_list_job["on_finished"]()
|
||||
page._patient_ai_generation_finished(*stale_openai)
|
||||
|
||||
assert not page._patient_ai_list_requests
|
||||
assert not page._patient_ai_generation_requests
|
||||
assert page._ai_analysis_model_states["qwen"] == "error"
|
||||
assert not page._ai_analysis_loading
|
||||
assert "未返回有效结果" in page.ai_analysis_state_label.text()
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_aba_cancelled_generation_is_replaced_instead_of_becoming_false_error(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
first = _detail(115, 301, 515)
|
||||
second = _detail(116, 302, 516)
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
monkeypatch.setattr(
|
||||
reception_module,
|
||||
"_AI_AUTOMATIC_GENERATION_SETTLE_SECONDS",
|
||||
5.0,
|
||||
)
|
||||
|
||||
class Repository:
|
||||
def __init__(self) -> None:
|
||||
self.generate_calls: list[tuple[int, str]] = []
|
||||
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
return {"patient_id": patient_id, "reports": []}
|
||||
|
||||
def generate_patient_ai_report(
|
||||
self,
|
||||
patient_id: int,
|
||||
*,
|
||||
model: str,
|
||||
) -> dict[str, Any]:
|
||||
self.generate_calls.append((patient_id, model))
|
||||
generated = _snapshot(model, 1, "2026-08-14 12:20:00")
|
||||
generated["patient_id"] = patient_id
|
||||
return {
|
||||
"patient_id": patient_id,
|
||||
"generated_report": generated,
|
||||
"report": generated,
|
||||
}
|
||||
|
||||
repository = Repository()
|
||||
page = ReceptionPage(
|
||||
repository,
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(first["appointment"])
|
||||
jobs[0]["on_success"]({"detail": first, "warnings": []})
|
||||
jobs[1]["on_success"]({"patient_id": 301, "reports": []})
|
||||
old_qwen_job = jobs[2]
|
||||
|
||||
with ThreadPoolExecutor(max_workers=1) as executor:
|
||||
cancelled_future = executor.submit(old_qwen_job["function"])
|
||||
page._select_record(second["appointment"])
|
||||
cancelled = cancelled_future.result(timeout=1.0)
|
||||
monkeypatch.setattr(
|
||||
reception_module,
|
||||
"_AI_AUTOMATIC_GENERATION_SETTLE_SECONDS",
|
||||
0.0,
|
||||
)
|
||||
assert cancelled is reception_module._ASYNC_REQUEST_CANCELLED
|
||||
assert repository.generate_calls == []
|
||||
|
||||
page._select_record(first["appointment"])
|
||||
jobs[4]["on_success"]({"detail": first, "warnings": []})
|
||||
assert len(jobs) == 6
|
||||
current_list_job = jobs[5]
|
||||
current_list_job["on_success"]({"patient_id": 301, "reports": []})
|
||||
assert len(jobs) == 7
|
||||
replacement_qwen_job = jobs[6]
|
||||
|
||||
current_list_job["on_finished"]()
|
||||
assert len(jobs) == 7
|
||||
qwen_result = replacement_qwen_job["function"]()
|
||||
replacement_qwen_job["on_success"](qwen_result)
|
||||
replacement_qwen_job["on_finished"]()
|
||||
jobs_after_replacement = len(jobs)
|
||||
|
||||
old_qwen_job["on_success"](cancelled)
|
||||
old_qwen_job["on_finished"]()
|
||||
|
||||
assert repository.generate_calls == [(301, "qwen")]
|
||||
assert len(jobs) == jobs_after_replacement
|
||||
assert page._ai_analysis_model_states["qwen"] == "success"
|
||||
assert page.ai_summary_label.text() == "千问第 1 版诊断建议"
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("late_completion", ["cancelled", "error"])
|
||||
def test_late_history_completion_cannot_clear_newer_generated_snapshot(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
late_completion: str,
|
||||
) -> None:
|
||||
detail = _detail(117, 301, 517)
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
|
||||
class Repository:
|
||||
def __init__(self) -> None:
|
||||
self.generate_calls: list[tuple[int, str]] = []
|
||||
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
raise AssertionError(f"history worker remains pending: {patient_id}")
|
||||
|
||||
def generate_patient_ai_report(
|
||||
self,
|
||||
patient_id: int,
|
||||
*,
|
||||
model: str,
|
||||
) -> dict[str, Any]:
|
||||
self.generate_calls.append((patient_id, model))
|
||||
generated = _snapshot(model, 9, "2026-08-14 12:30:00")
|
||||
generated["id"] = 901 if model == "qwen" else 902
|
||||
generated["patient_id"] = patient_id
|
||||
return {
|
||||
"patient_id": patient_id,
|
||||
"generated_report": generated,
|
||||
"report": generated,
|
||||
}
|
||||
|
||||
repository = Repository()
|
||||
page = ReceptionPage(
|
||||
repository,
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(detail["appointment"])
|
||||
jobs[0]["on_success"]({"detail": detail, "warnings": []})
|
||||
history_job = jobs[1]
|
||||
current_context = page._current_patient_ai_context(301)
|
||||
assert current_context is not None
|
||||
generation, appointment_id, _patient_id = current_context
|
||||
page._request_patient_ai_generation("qwen", generation, appointment_id, 301)
|
||||
qwen_job = jobs[2]
|
||||
qwen_result = qwen_job["function"]()
|
||||
qwen_job["on_success"](qwen_result)
|
||||
qwen_job["on_finished"]()
|
||||
jobs_after_generation = len(jobs)
|
||||
|
||||
if late_completion == "cancelled":
|
||||
history_job["on_success"](reception_module._ASYNC_REQUEST_CANCELLED)
|
||||
else:
|
||||
history_job["on_error"](RuntimeError("late history failure"))
|
||||
history_job["on_finished"]()
|
||||
|
||||
assert repository.generate_calls == [(301, "qwen")]
|
||||
assert len(jobs) == jobs_after_generation
|
||||
assert page._ai_analysis_model_states["qwen"] == "success"
|
||||
assert page._ai_analysis_payloads["qwen"]["id"] == 901
|
||||
assert page.ai_summary_label.text() == "千问第 9 版诊断建议"
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_cancelled_queued_generation_does_not_invalidate_valid_history_get(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
first = _detail(118, 301, 518)
|
||||
second = _detail(119, 302, 519)
|
||||
saved = _snapshot("qwen", 10, "2026-08-14 12:40:00")
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
|
||||
class Repository:
|
||||
def __init__(self) -> None:
|
||||
self.list_calls: list[int] = []
|
||||
self.generate_calls: list[tuple[int, str]] = []
|
||||
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
self.list_calls.append(patient_id)
|
||||
return {"patient_id": patient_id, "reports": [saved]}
|
||||
|
||||
def generate_patient_ai_report(self, patient_id: int, *, model: str) -> Any:
|
||||
self.generate_calls.append((patient_id, model))
|
||||
raise AssertionError("cancelled queued POST must not enter repository")
|
||||
|
||||
repository = Repository()
|
||||
page = ReceptionPage(
|
||||
repository,
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(first["appointment"])
|
||||
jobs[0]["on_success"]({"detail": first, "warnings": []})
|
||||
history_job = jobs[1]
|
||||
history_result = history_job["function"]()
|
||||
current_context = page._current_patient_ai_context(301)
|
||||
assert current_context is not None
|
||||
generation, appointment_id, _patient_id = current_context
|
||||
page._request_patient_ai_generation("qwen", generation, appointment_id, 301)
|
||||
queued_qwen_job = jobs[2]
|
||||
|
||||
page._select_record(second["appointment"])
|
||||
cancelled = queued_qwen_job["function"]()
|
||||
page._select_record(first["appointment"])
|
||||
jobs[4]["on_success"]({"detail": first, "warnings": []})
|
||||
queued_qwen_job["on_success"](cancelled)
|
||||
queued_qwen_job["on_finished"]()
|
||||
history_job["on_success"](history_result)
|
||||
history_job["on_finished"]()
|
||||
|
||||
assert repository.list_calls == [301]
|
||||
assert repository.generate_calls == []
|
||||
assert len(jobs) == 5
|
||||
assert page._ai_analysis_model_states["qwen"] == "success"
|
||||
assert page.ai_summary_label.text() == "千问第 10 版诊断建议"
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_saturated_automatic_ai_slots_end_in_retryable_state_without_post(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
detail = _detail(120, 301, 520)
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
class BusyAutomaticSlots:
|
||||
@staticmethod
|
||||
def acquire(*, blocking: bool) -> bool:
|
||||
assert not blocking
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def release() -> None:
|
||||
raise AssertionError("an unacquired slot must not be released")
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
monkeypatch.setattr(
|
||||
reception_module,
|
||||
"_AI_AUTOMATIC_GENERATION_SETTLE_SECONDS",
|
||||
0.0,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
reception_module,
|
||||
"_AI_AUTOMATIC_REQUEST_SLOTS",
|
||||
BusyAutomaticSlots(),
|
||||
)
|
||||
|
||||
class Repository:
|
||||
def __init__(self) -> None:
|
||||
self.generate_calls: list[tuple[int, str]] = []
|
||||
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
return {"patient_id": patient_id, "reports": []}
|
||||
|
||||
def generate_patient_ai_report(self, patient_id: int, *, model: str) -> Any:
|
||||
self.generate_calls.append((patient_id, model))
|
||||
raise AssertionError("busy automatic work must not submit a POST")
|
||||
|
||||
repository = Repository()
|
||||
page = ReceptionPage(
|
||||
repository,
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(detail["appointment"])
|
||||
jobs[0]["on_success"]({"detail": detail, "warnings": []})
|
||||
jobs[1]["on_success"]({"patient_id": 301, "reports": []})
|
||||
automatic_qwen_job = jobs[2]
|
||||
deferred = automatic_qwen_job["function"]()
|
||||
automatic_qwen_job["on_success"](deferred)
|
||||
automatic_qwen_job["on_finished"]()
|
||||
|
||||
assert deferred is reception_module._ASYNC_REQUEST_DEFERRED
|
||||
assert repository.generate_calls == []
|
||||
assert page._ai_analysis_model_states["qwen"] == "error"
|
||||
assert not page._ai_analysis_loading
|
||||
assert not page._ai_analysis_regenerating
|
||||
assert page.ai_analysis_regenerate_button.isEnabled()
|
||||
assert "后台分析任务较多" in page.ai_analysis_state_label.text()
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_saturated_history_slots_end_in_retryable_state_without_get(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
detail = _detail(121, 301, 521)
|
||||
jobs: list[dict[str, Any]] = []
|
||||
|
||||
def queue(function: Any, *args: Any, **options: Any) -> object:
|
||||
jobs.append({"function": function, "args": args, **options})
|
||||
return object()
|
||||
|
||||
class BusyAutomaticSlots:
|
||||
@staticmethod
|
||||
def acquire(*, blocking: bool) -> bool:
|
||||
assert not blocking
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def release() -> None:
|
||||
raise AssertionError("an unacquired slot must not be released")
|
||||
|
||||
monkeypatch.setattr(reception_module, "run_async", queue)
|
||||
monkeypatch.setattr(
|
||||
reception_module,
|
||||
"_AI_AUTOMATIC_GENERATION_SETTLE_SECONDS",
|
||||
0.0,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
reception_module,
|
||||
"_AI_AUTOMATIC_REQUEST_SLOTS",
|
||||
BusyAutomaticSlots(),
|
||||
)
|
||||
|
||||
class Repository:
|
||||
def __init__(self) -> None:
|
||||
self.list_calls: list[int] = []
|
||||
|
||||
def list_patient_ai_reports(self, patient_id: int) -> dict[str, Any]:
|
||||
self.list_calls.append(patient_id)
|
||||
raise AssertionError("busy automatic read must not enter repository")
|
||||
|
||||
def generate_patient_ai_report(self, patient_id: int, *, model: str) -> Any:
|
||||
raise AssertionError(f"history did not complete: {patient_id}/{model}")
|
||||
|
||||
repository = Repository()
|
||||
page = ReceptionPage(
|
||||
repository,
|
||||
PermissionSet(
|
||||
[
|
||||
"tcm.diagnosis/patientAiReports",
|
||||
"tcm.diagnosis/generatePatientAiReport",
|
||||
]
|
||||
),
|
||||
)
|
||||
page._select_record(detail["appointment"])
|
||||
jobs[0]["on_success"]({"detail": detail, "warnings": []})
|
||||
history_job = jobs[1]
|
||||
deferred = history_job["function"]()
|
||||
history_job["on_success"](deferred)
|
||||
history_job["on_finished"]()
|
||||
|
||||
assert deferred is reception_module._ASYNC_REQUEST_DEFERRED
|
||||
assert repository.list_calls == []
|
||||
assert page._ai_analysis_model_states["qwen"] == "error"
|
||||
assert not page._ai_analysis_loading
|
||||
assert page.ai_analysis_retry_button.isEnabled()
|
||||
assert "查询任务较多" in page.ai_analysis_state_label.text()
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_get_history_requires_exact_top_level_and_row_patient_ids() -> None:
|
||||
row = _snapshot("qwen", 1, "2026-08-14 11:00:00")
|
||||
valid = {"patient_id": 301, "reports": [row]}
|
||||
|
||||
Reference in New Issue
Block a user