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:
"""Delete all the gallery data on this microscope."""
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."""
import logging
from dataclasses import dataclass
from typing import Callable, Optional
import pytest
from pydantic import BaseModel
from labthings_fastapi.exceptions import InvocationCancelledError
from labthings_fastapi.testing import create_thing_without_server
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
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):
"""Mock gallery data."""
@ -126,3 +140,109 @@ def test_gallery_logs_for_bad_providers(caplog):
record = caplog.records[0]
assert "Data from bad_thing cannot be shown in gallery" in record.message
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