test_failure_detection_provider_exclusivity.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. """Provider selection stays exclusive even when a backup bypasses the settings route."""
  2. import asyncio
  3. from contextlib import asynccontextmanager
  4. from types import SimpleNamespace
  5. from unittest.mock import AsyncMock, patch
  6. import pytest
  7. from backend.app.models.settings import Settings
  8. from backend.app.services.github_restore import GitHubRestoreService, _CategoryTally
  9. from backend.app.services.obico_detection import ObicoDetectionService
  10. from backend.app.services.octoeverywhere_detection import OctoEverywhereDetectionService
  11. @pytest.fixture
  12. def detectors(db_session):
  13. @asynccontextmanager
  14. async def session():
  15. yield db_session
  16. obico = ObicoDetectionService()
  17. octoeverywhere = OctoEverywhereDetectionService()
  18. with (
  19. patch("backend.app.services.obico_detection.async_session", session),
  20. patch("backend.app.services.octoeverywhere_detection.async_session", session),
  21. ):
  22. yield obico, octoeverywhere
  23. obico.stop()
  24. octoeverywhere.stop()
  25. @pytest.mark.parametrize(
  26. ("obico_flag", "octoeverywhere_flag", "expected"),
  27. [
  28. ("false", "false", (False, False)),
  29. ("true", "false", (True, False)),
  30. ("false", "true", (False, True)),
  31. ("true", "true", (False, False)),
  32. ("true", None, (True, False)),
  33. (None, "true", (False, True)),
  34. (None, None, (False, False)),
  35. ],
  36. )
  37. async def test_provider_selection_from_saved_flags(db_session, detectors, obico_flag, octoeverywhere_flag, expected):
  38. for key, value in (("obico_enabled", obico_flag), ("octoeverywhere_enabled", octoeverywhere_flag)):
  39. if value is not None:
  40. db_session.add(Settings(key=key, value=value))
  41. await db_session.commit()
  42. loaded = [await detector._load_settings() for detector in detectors]
  43. assert tuple(settings["enabled"] for settings in loaded) == expected
  44. async def test_legacy_restore_never_starts_both_providers_even_with_cached_settings(db_session, detectors):
  45. obico, octoeverywhere = detectors
  46. db_session.add_all(
  47. Settings(key=key, value=value)
  48. for key, value in {
  49. "obico_enabled": "false",
  50. "obico_ml_url": "http://obico.local:3333",
  51. "octoeverywhere_enabled": "true",
  52. "octoeverywhere_api_key": "test-key",
  53. }.items()
  54. )
  55. await db_session.commit()
  56. octoeverywhere._cache_settings(await octoeverywhere._load_settings())
  57. assert octoeverywhere._settings["enabled"] is True
  58. tally, written = _CategoryTally(), set()
  59. await GitHubRestoreService()._restore_settings(
  60. db_session, {"settings": {"obico_enabled": "true"}}, overwrite=True, tally=tally, keys_written=written
  61. )
  62. await db_session.commit()
  63. assert written == {"obico_enabled"}
  64. # Stop after one scheduler tick, retaining normal event-loop yields used by
  65. # the database driver and the independently scheduled printer check.
  66. original_sleep = asyncio.sleep
  67. async def one_tick(delay):
  68. if delay > 0:
  69. raise asyncio.CancelledError
  70. await original_sleep(0)
  71. obico._check_printer = AsyncMock()
  72. octoeverywhere._check_printer = AsyncMock()
  73. with (
  74. patch("backend.app.services.printer_manager.printer_manager") as manager,
  75. patch("asyncio.sleep", one_tick),
  76. ):
  77. manager.get_all_statuses.return_value = {1: SimpleNamespace(state="RUNNING", task_name="test-print")}
  78. manager.is_connected.return_value = True
  79. await obico._loop()
  80. await octoeverywhere._loop()
  81. await asyncio.gather(*octoeverywhere._checks.values())
  82. obico._check_printer.assert_not_awaited()
  83. octoeverywhere._check_printer.assert_awaited_once()
  84. # At its next normal settings refresh, OctoEverywhere also sees the
  85. # conflicting selection and stops inspecting, without a process restart.
  86. octoeverywhere._settings_refresh_at = 0
  87. await octoeverywhere._loop()
  88. await obico._loop()
  89. assert octoeverywhere._settings["enabled"] is False
  90. obico._check_printer.assert_not_awaited()
  91. octoeverywhere._check_printer.assert_awaited_once()