feat: Phase 6 — require_active_service decorator + wizard install wiring

Email/calendar/files routes now return 404 when the service is not
installed, using a require_active_service decorator that checks
ServiceRegistry. Status endpoints are exempt so health checks always work.

SetupManager.complete_setup() now accepts a service_store_manager and
installs any wizard-selected services in a background daemon thread after
setup completes. Failures are logged but do not fail the wizard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 16:58:57 -04:00
parent a69ca1e402
commit 44d7e96f29
12 changed files with 556 additions and 5 deletions
+27
View File
@@ -21,12 +21,21 @@ sys.path.insert(0, str(api_dir))
from app import app
# Sentinel value that make service_registry.get(...) return non-None (service installed)
_INSTALLED = {'id': 'email', 'installed': True}
class TestGetEmailUsers(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
self.client = app.test_client()
self._sr_patcher = patch('app.service_registry')
mock_sr = self._sr_patcher.start()
mock_sr.get.return_value = _INSTALLED
def tearDown(self):
self._sr_patcher.stop()
@patch('app.email_manager')
def test_get_users_returns_200_with_list(self, mock_em):
@@ -60,6 +69,12 @@ class TestCreateEmailUser(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
self.client = app.test_client()
self._sr_patcher = patch('app.service_registry')
mock_sr = self._sr_patcher.start()
mock_sr.get.return_value = _INSTALLED
def tearDown(self):
self._sr_patcher.stop()
@patch('app.email_manager')
def test_create_user_returns_200_on_success(self, mock_em):
@@ -131,6 +146,12 @@ class TestDeleteEmailUser(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
self.client = app.test_client()
self._sr_patcher = patch('app.service_registry')
mock_sr = self._sr_patcher.start()
mock_sr.get.return_value = _INSTALLED
def tearDown(self):
self._sr_patcher.stop()
@patch('app.email_manager')
def test_delete_user_returns_200_on_success(self, mock_em):
@@ -187,6 +208,12 @@ class TestEmailConnectivity(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
self.client = app.test_client()
self._sr_patcher = patch('app.service_registry')
mock_sr = self._sr_patcher.start()
mock_sr.get.return_value = _INSTALLED
def tearDown(self):
self._sr_patcher.stop()
@patch('app.email_manager')
def test_connectivity_returns_200_with_result(self, mock_em):