|
9 | 9 | import re |
10 | 10 | import time |
11 | 11 | from pathlib import Path |
| 12 | +from types import SimpleNamespace |
12 | 13 | from typing import TYPE_CHECKING |
13 | 14 |
|
14 | 15 | import bokeh.models as bkmodels |
|
25 | 26 | from scipy.ndimage import label |
26 | 27 |
|
27 | 28 | from tiatoolbox.data import _fetch_remote_sample |
28 | | -from tiatoolbox.visualization.bokeh_app import main |
| 29 | +from tiatoolbox.visualization.bokeh_app import app_hooks, main |
29 | 30 | from tiatoolbox.visualization.tileserver import TileServer |
30 | 31 | from tiatoolbox.visualization.ui_utils import get_level_by_extent |
31 | 32 |
|
|
41 | 42 | GRIDLINES = 2 |
42 | 43 |
|
43 | 44 |
|
| 45 | +class _DummySessionContext: |
| 46 | + """Simple shim matching the subset of Bokeh's SessionContext we use.""" |
| 47 | + |
| 48 | + def __init__(self: _DummySessionContext, user: str) -> None: |
| 49 | + self.request = SimpleNamespace(arguments={"user": user}) |
| 50 | + |
| 51 | + |
44 | 52 | # helper functions and fixtures |
45 | 53 | def get_tile(layer: str, x: float, y: float, z: float, *, show: bool) -> np.ndarray: |
46 | 54 | """Get a tile from the server. |
@@ -831,3 +839,47 @@ def test_clearing_doc(doc: Document) -> None: |
831 | 839 | """Test that the doc can be cleared.""" |
832 | 840 | doc.clear() |
833 | 841 | assert len(doc.roots) == 0 |
| 842 | + |
| 843 | + |
| 844 | +def test_app_hooks_session_destroyed(monkeypatch: pytest.MonkeyPatch) -> None: |
| 845 | + """Hook should call reset endpoint and exit.""" |
| 846 | + recorded: dict[str, object] = {} |
| 847 | + |
| 848 | + def fake_get(url: str, *, timeout: int) -> None: |
| 849 | + recorded["url"] = url |
| 850 | + recorded["timeout"] = timeout |
| 851 | + |
| 852 | + monkeypatch.setattr(app_hooks, "PORT", "6150") |
| 853 | + monkeypatch.setattr(app_hooks.requests, "get", fake_get) |
| 854 | + exited = False |
| 855 | + |
| 856 | + def fake_exit() -> None: |
| 857 | + nonlocal exited |
| 858 | + exited = True |
| 859 | + |
| 860 | + monkeypatch.setattr(app_hooks, "sys", SimpleNamespace(exit=fake_exit)) |
| 861 | + app_hooks.on_session_destroyed(_DummySessionContext("user-1")) |
| 862 | + assert recorded["url"] == "http://127.0.0.1:6150/tileserver/reset/user-1" |
| 863 | + assert recorded["timeout"] == 5 |
| 864 | + assert exited |
| 865 | + |
| 866 | + |
| 867 | +def test_app_hooks_session_destroyed_suppresses_timeout( |
| 868 | + monkeypatch: pytest.MonkeyPatch, |
| 869 | +) -> None: |
| 870 | + """ReadTimeout should be suppressed and exit still called.""" |
| 871 | + |
| 872 | + def fake_get(*_: object, **__: object) -> None: |
| 873 | + raise app_hooks.requests.exceptions.ReadTimeout # type: ignore[attr-defined] |
| 874 | + |
| 875 | + monkeypatch.setattr(app_hooks, "PORT", "6160") |
| 876 | + monkeypatch.setattr(app_hooks.requests, "get", fake_get) |
| 877 | + exited = False |
| 878 | + |
| 879 | + def fake_exit() -> None: |
| 880 | + nonlocal exited |
| 881 | + exited = True |
| 882 | + |
| 883 | + monkeypatch.setattr(app_hooks, "sys", SimpleNamespace(exit=fake_exit)) |
| 884 | + app_hooks.on_session_destroyed(_DummySessionContext("user-2")) |
| 885 | + assert exited |
0 commit comments