blob: e8d9e0164420362d53ab7227c2e4620ac3048e2c [file]
"""Tests that plugin enable() failures are caught gracefully in AppService.load_plugins()."""
import asyncio
import os
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
@pytest.mark.asyncio
async def test_failing_plugin_enable_is_caught(tmp_path):
"""Verify that a plugin enable() raising an exception is caught and logged,
not propagated, and does not prevent load_plugins from completing."""
# Create a fake plugin directory with hook.py so load_plugins won't exit
plugin_dir = tmp_path / 'plugins' / 'bad_plugin'
plugin_dir.mkdir(parents=True)
(plugin_dir / 'hook.py').write_text('')
mock_plugin = MagicMock()
mock_plugin.name = 'bad_plugin'
mock_plugin.load_plugin = MagicMock(return_value=True)
mock_plugin.enable = AsyncMock(side_effect=RuntimeError('init failed'))
with patch('app.service.app_svc.Plugin', return_value=mock_plugin), \
patch('app.service.app_svc.os.path.isdir', return_value=True), \
patch('app.service.app_svc.os.path.isfile', return_value=True):
from app.service.app_svc import AppService
app_svc = MagicMock(spec=AppService)
app_svc.log = MagicMock()
app_svc.get_config = MagicMock(return_value=['bad_plugin'])
app_svc.get_services = MagicMock(return_value={})
app_svc.get_service = MagicMock(return_value=MagicMock(store=AsyncMock()))
app_svc._loaded_plugins = []
# Call the real load_plugins method bound to our mock instance
await AppService.load_plugins(app_svc, ['bad_plugin'])
# Allow the created task to run
await asyncio.sleep(0.1)
app_svc.log.error.assert_called()
error_msg = app_svc.log.error.call_args[0][0]
assert 'Failed to enable plugin' in error_msg
assert 'bad_plugin' in error_msg
@pytest.mark.asyncio
async def test_successful_plugin_enable_logs_info(tmp_path):
"""Verify a successful enable() logs an info message from the production code."""
plugin_dir = tmp_path / 'plugins' / 'good_plugin'
plugin_dir.mkdir(parents=True)
(plugin_dir / 'hook.py').write_text('')
mock_plugin = MagicMock()
mock_plugin.name = 'good_plugin'
mock_plugin.load_plugin = MagicMock(return_value=True)
mock_plugin.enable = AsyncMock(return_value=None)
with patch('app.service.app_svc.Plugin', return_value=mock_plugin), \
patch('app.service.app_svc.os.path.isdir', return_value=True), \
patch('app.service.app_svc.os.path.isfile', return_value=True):
from app.service.app_svc import AppService
app_svc = MagicMock(spec=AppService)
app_svc.log = MagicMock()
app_svc.get_config = MagicMock(return_value=['good_plugin'])
app_svc.get_services = MagicMock(return_value={})
app_svc.get_service = MagicMock(return_value=MagicMock(store=AsyncMock()))
app_svc._loaded_plugins = []
await AppService.load_plugins(app_svc, ['good_plugin'])
await asyncio.sleep(0.1)
app_svc.log.info.assert_called()
info_msg = app_svc.log.info.call_args[0][0]
assert 'Enabled plugin' in info_msg
assert 'good_plugin' in info_msg