Improve gallery tests and fix behaviour if a provider errors

This commit is contained in:
Julian Stirling 2026-06-21 20:03:48 +01:00
parent cc6caa39d7
commit 03d74bb044
2 changed files with 124 additions and 1 deletions

View file

@ -111,4 +111,7 @@ class GalleryThing(lt.Thing):
def delete_all_data(self) -> None: def delete_all_data(self) -> None:
"""Delete all the gallery data on this microscope.""" """Delete all the gallery data on this microscope."""
for thing in self.gallery_providing_things.values(): for thing in self.gallery_providing_things.values():
thing.delete_all_gallery_items() try:
thing.delete_all_gallery_items()
except Exception as e:
self.logger.exception(e)

View file

@ -1,9 +1,13 @@
"""Tests that captures have the expected metadata.""" """Tests that captures have the expected metadata."""
import logging import logging
from dataclasses import dataclass
from typing import Callable, Optional
import pytest
from pydantic import BaseModel from pydantic import BaseModel
from labthings_fastapi.exceptions import InvocationCancelledError
from labthings_fastapi.testing import create_thing_without_server from labthings_fastapi.testing import create_thing_without_server
from openflexure_microscope_server.things import OFMThing from openflexure_microscope_server.things import OFMThing
@ -15,6 +19,16 @@ from openflexure_microscope_server.things.gallery import (
from ..shared_utils.lt_test_utils import LabThingsTestEnv from ..shared_utils.lt_test_utils import LabThingsTestEnv
def test_error_if_accessing_gallery_things_before_started():
"""Check that gallery_providing_things property errors before init."""
thing = create_thing_without_server(GalleryThing)
with pytest.raises(
RuntimeError,
match="Cannot access gallery_providing_things before server has started.",
):
thing.gallery_providing_things
class MockGalleryData(BaseModel): class MockGalleryData(BaseModel):
"""Mock gallery data.""" """Mock gallery data."""
@ -126,3 +140,109 @@ def test_gallery_logs_for_bad_providers(caplog):
record = caplog.records[0] record = caplog.records[0]
assert "Data from bad_thing cannot be shown in gallery" in record.message assert "Data from bad_thing cannot be shown in gallery" in record.message
assert record.levelname == "ERROR" assert record.levelname == "ERROR"
@pytest.fixture
def minimal_gallery_env():
"""Return a minimal working environment for testing the gallery."""
things = {
"minimal_thing1": MinimalGallerySource,
"minimal_thing2": MinimalGallerySource,
"gallery": GalleryThing,
}
with LabThingsTestEnv(
things=things, application_config={"data_folder": "./openflexure/data/"}
) as env:
return env
def test_gallery_lists_data(minimal_gallery_env, mocker, caplog):
"""Test that the gallery calls all gallery things to list data."""
# Create a config with the minimal Thing and the bad Thing
gallery = minimal_gallery_env.get_thing_by_name("gallery")
thing_1 = minimal_gallery_env.get_thing_by_name("minimal_thing1")
thing_2 = minimal_gallery_env.get_thing_by_name("minimal_thing2")
thing_1_data = [
MockGalleryData(mock="thing_1", foobar=1),
MockGalleryData(mock="thing_1", foobar=2),
]
thing_2_data = [
MockGalleryData(mock="thing_2", foobar=1),
MockGalleryData(mock="thing_2", foobar=2),
]
mocker.patch.object(thing_1, "get_data_for_gallery", return_value=thing_1_data)
mocker.patch.object(thing_2, "get_data_for_gallery", return_value=thing_2_data)
thing_1_return = [
{"mock": "thing_1", "foobar": 1},
{"mock": "thing_1", "foobar": 2},
]
thing_2_return = [
{"mock": "thing_2", "foobar": 1},
{"mock": "thing_2", "foobar": 2},
]
assert gallery.list_data == thing_1_return + thing_2_return
thing_1.get_data_for_gallery.side_effect = RuntimeError
with caplog.at_level(logging.INFO):
assert gallery.list_data == thing_2_return
assert len(caplog.records) == 1
assert caplog.records[0].levelname == "ERROR"
@dataclass
class GalleryDeleteTestCase:
"""Inputs and expected outputs for testing ``save_from_memory``.
The default save kwargs assume a jpeg.
"""
thing_1_side_effect: Optional[Callable]
thing_2_call_count: int
# Expect directly calling the action will raise the thing 1 side_effect error
action_errors: bool
GALLERY_DELETE_TEST_CASES = [
# No errors from thing 1, thing 2 gets called
GalleryDeleteTestCase(
thing_1_side_effect=None, thing_2_call_count=1, action_errors=False
),
# Thing 1 errors, thing 2 is still called
GalleryDeleteTestCase(
thing_1_side_effect=RuntimeError, thing_2_call_count=1, action_errors=False
),
# Thing 1 raises invocation cancelled error. Thing 2 is not called. The InvocationError is raised
GalleryDeleteTestCase(
thing_1_side_effect=InvocationCancelledError,
thing_2_call_count=0,
action_errors=True,
),
]
@pytest.mark.parametrize("test_case", GALLERY_DELETE_TEST_CASES)
def test_gallery_calls_delete(test_case, minimal_gallery_env, mocker):
"""Test that the gallery delete on all gallery providing things.."""
# Create a config with the minimal Thing and the bad Thing
gallery = minimal_gallery_env.get_thing_by_name("gallery")
thing_1 = minimal_gallery_env.get_thing_by_name("minimal_thing1")
thing_2 = minimal_gallery_env.get_thing_by_name("minimal_thing2")
mocker.patch.object(
thing_1, "delete_all_gallery_items", side_effect=test_case.thing_1_side_effect
)
mocker.patch.object(thing_2, "delete_all_gallery_items")
if test_case.action_errors:
with pytest.raises(test_case.thing_1_side_effect):
gallery.delete_all_data()
else:
gallery.delete_all_data()
assert thing_1.delete_all_gallery_items.call_count == 1
assert thing_2.delete_all_gallery_items.call_count == test_case.thing_2_call_count