feat: Phase 2 — remove builtins layer, ServiceRegistry is installed-only
Unit Tests / test (push) Successful in 11m31s
Unit Tests / test (push) Successful in 11m31s
Builtins (email/calendar/files) are no longer baked into the API image. ServiceRegistry now only knows about installed store services. When nothing is installed, Caddy and DNS get no service routes — no hardcoded fallback. Changes: - service_registry.py: remove _BUILTINS_DIR, _builtin_ids, _builtin_manifest, _load_manifest; get() and list_all() now delegate entirely to installed services - caddy_manager.py: remove _build_core_service_routes(); remove hardcoded fallback pairs from _http01_service_pairs(); empty registry → api block only - network_manager.py: _get_service_subdomains() returns [] when no registry - api/services/builtins/: deleted (email, calendar, files manifests) - Tests updated throughout: removed builtin-dependent assertions, added installed-service fixtures, updated fallback expectations to api-only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ def _mgr_with_registry(registry=None):
|
||||
|
||||
|
||||
def _mock_registry():
|
||||
"""Return a mock ServiceRegistry that reproduces the 3 builtin service routes."""
|
||||
"""Return a mock ServiceRegistry that reproduces 3 store service routes."""
|
||||
reg = MagicMock()
|
||||
reg.get_caddy_routes.return_value = [
|
||||
{
|
||||
@@ -76,33 +76,39 @@ def _nm(registry=None):
|
||||
|
||||
class TestBuildRegistryServiceRoutes(unittest.TestCase):
|
||||
|
||||
def test_returns_hardcoded_when_no_registry(self):
|
||||
"""service_registry=None produces the same output as _build_core_service_routes."""
|
||||
def test_returns_api_only_when_no_registry(self):
|
||||
"""service_registry=None produces only the @api block."""
|
||||
mgr = _mgr_with_registry(registry=None)
|
||||
domain = 'alpha.pic.ngo'
|
||||
result = mgr._build_registry_service_routes(domain)
|
||||
expected = CaddyManager._build_core_service_routes(domain)
|
||||
self.assertEqual(result, expected)
|
||||
self.assertIn('@api host api.alpha.pic.ngo', result)
|
||||
self.assertIn('reverse_proxy cell-api:3000', result)
|
||||
self.assertNotIn('@calendar', result)
|
||||
self.assertNotIn('@mail', result)
|
||||
|
||||
def test_returns_hardcoded_when_registry_empty(self):
|
||||
"""An empty route list from the registry falls back to hardcoded."""
|
||||
def test_returns_api_only_when_registry_empty(self):
|
||||
"""An empty route list from the registry produces only the @api block."""
|
||||
reg = MagicMock()
|
||||
reg.get_caddy_routes.return_value = []
|
||||
mgr = _mgr_with_registry(registry=reg)
|
||||
domain = 'alpha.pic.ngo'
|
||||
result = mgr._build_registry_service_routes(domain)
|
||||
expected = CaddyManager._build_core_service_routes(domain)
|
||||
self.assertEqual(result, expected)
|
||||
self.assertIn('@api host api.alpha.pic.ngo', result)
|
||||
self.assertIn('reverse_proxy cell-api:3000', result)
|
||||
self.assertNotIn('@calendar', result)
|
||||
self.assertNotIn('@mail', result)
|
||||
|
||||
def test_registry_error_falls_back(self):
|
||||
"""When get_caddy_routes raises, output equals _build_core_service_routes."""
|
||||
def test_returns_api_only_on_registry_error(self):
|
||||
"""When get_caddy_routes raises, only the @api block is produced."""
|
||||
reg = MagicMock()
|
||||
reg.get_caddy_routes.side_effect = Exception('registry unavailable')
|
||||
mgr = _mgr_with_registry(registry=reg)
|
||||
domain = 'alpha.pic.ngo'
|
||||
result = mgr._build_registry_service_routes(domain)
|
||||
expected = CaddyManager._build_core_service_routes(domain)
|
||||
self.assertEqual(result, expected)
|
||||
self.assertIn('@api host api.alpha.pic.ngo', result)
|
||||
self.assertIn('reverse_proxy cell-api:3000', result)
|
||||
self.assertNotIn('@calendar', result)
|
||||
self.assertNotIn('@mail', result)
|
||||
|
||||
def test_single_service_no_extras(self):
|
||||
"""One service with no extra_subdomains produces one matcher + handle + api block."""
|
||||
@@ -234,27 +240,25 @@ class TestHttp01ServicePairs(unittest.TestCase):
|
||||
self.assertEqual(webdav_entry, 'cell-webdav:80')
|
||||
self.assertNotEqual(webdav_entry, 'cell-filegator:8080')
|
||||
|
||||
def test_fallback_when_no_registry(self):
|
||||
"""Without a registry the hardcoded pairs are returned, including api."""
|
||||
def test_only_api_when_no_registry(self):
|
||||
"""Without a registry only the api pair is returned."""
|
||||
mgr = _mgr_with_registry(registry=None)
|
||||
pairs = mgr._http01_service_pairs()
|
||||
subdomains = [s for s, _ in pairs]
|
||||
self.assertIn('calendar', subdomains)
|
||||
self.assertIn('mail', subdomains)
|
||||
self.assertIn('webmail', subdomains)
|
||||
self.assertIn('files', subdomains)
|
||||
self.assertIn('webdav', subdomains)
|
||||
self.assertIn('api', subdomains)
|
||||
self.assertNotIn('calendar', subdomains)
|
||||
self.assertNotIn('mail', subdomains)
|
||||
self.assertNotIn('files', subdomains)
|
||||
|
||||
def test_fallback_when_registry_error(self):
|
||||
"""When get_caddy_routes raises, falls back to hardcoded pairs."""
|
||||
def test_only_api_on_registry_error(self):
|
||||
"""When get_caddy_routes raises, only the api pair is present."""
|
||||
reg = MagicMock()
|
||||
reg.get_caddy_routes.side_effect = RuntimeError('boom')
|
||||
mgr = _mgr_with_registry(registry=reg)
|
||||
pairs = mgr._http01_service_pairs()
|
||||
subdomains = [s for s, _ in pairs]
|
||||
self.assertIn('calendar', subdomains)
|
||||
self.assertIn('api', subdomains)
|
||||
self.assertNotIn('calendar', subdomains)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -326,14 +330,14 @@ class TestCaddyfileWithRegistry(unittest.TestCase):
|
||||
self.assertIn('reverse_proxy cell-filegator:8080', out)
|
||||
self.assertIn('reverse_proxy cell-webdav:80', out)
|
||||
|
||||
def test_pic_ngo_fallback_when_registry_empty(self):
|
||||
"""pic_ngo falls back to hardcoded routes when registry returns empty list."""
|
||||
def test_pic_ngo_api_only_when_registry_empty(self):
|
||||
"""pic_ngo emits only the api block when registry returns empty list."""
|
||||
reg = MagicMock()
|
||||
reg.get_caddy_routes.return_value = []
|
||||
out = self._generate('pic_ngo', cell_name='alpha', registry=reg)
|
||||
# Hardcoded routes should appear
|
||||
self.assertIn('@calendar host calendar.alpha.pic.ngo', out)
|
||||
self.assertIn('@mail host mail.alpha.pic.ngo webmail.alpha.pic.ngo', out)
|
||||
self.assertIn('@api host api.alpha.pic.ngo', out)
|
||||
self.assertNotIn('@calendar', out)
|
||||
self.assertNotIn('@mail', out)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -354,11 +358,11 @@ class TestNetworkManagerGetServiceSubdomains(unittest.TestCase):
|
||||
self.managers.append(nm)
|
||||
return nm
|
||||
|
||||
def test_no_registry_returns_hardcoded(self):
|
||||
"""Without a registry the hardcoded service subdomain list is returned."""
|
||||
def test_no_registry_returns_empty(self):
|
||||
"""Without a registry an empty list is returned."""
|
||||
nm = self._make(registry=None)
|
||||
subs = nm._get_service_subdomains()
|
||||
self.assertCountEqual(subs, ['calendar', 'files', 'mail', 'webmail', 'webdav'])
|
||||
self.assertEqual(subs, [])
|
||||
|
||||
def test_registry_returns_all_subdomains(self):
|
||||
"""Primary + extra_subdomains from all routes are returned."""
|
||||
@@ -369,13 +373,13 @@ class TestNetworkManagerGetServiceSubdomains(unittest.TestCase):
|
||||
for expected in ('calendar', 'mail', 'webmail', 'files', 'webdav'):
|
||||
self.assertIn(expected, subs)
|
||||
|
||||
def test_registry_error_falls_back(self):
|
||||
"""When get_caddy_routes raises, hardcoded list is returned."""
|
||||
def test_registry_error_returns_empty(self):
|
||||
"""When get_caddy_routes raises, an empty list is returned."""
|
||||
reg = MagicMock()
|
||||
reg.get_caddy_routes.side_effect = Exception('broken registry')
|
||||
nm = self._make(registry=reg)
|
||||
subs = nm._get_service_subdomains()
|
||||
self.assertCountEqual(subs, ['calendar', 'files', 'mail', 'webmail', 'webdav'])
|
||||
self.assertEqual(subs, [])
|
||||
|
||||
def test_registry_extra_subdomains_included(self):
|
||||
"""extra_subdomains from each route are included in the returned list."""
|
||||
|
||||
Reference in New Issue
Block a user