_bootstrap_dns runs at container start before the wizard, writing the
default cell name ('mycell') into cell.zone. When the wizard completed
it fired IDENTITY_CHANGED for Caddy but never updated the DNS zone, so
DNS records kept showing 'mycell.cell' even after naming the cell.
After successful wizard completion, call network_manager.apply_cell_name
to rename the hostname record in the primary zone file, then reload
CoreDNS. The empty old_name triggers auto-detection so it works even
when the zone was written with the env-var default.
Adds test_setup_route.py covering: apply_cell_name called on success,
not called on failure, 410 on repeat completion, and IDENTITY_CHANGED
publication.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -90,15 +90,19 @@ def complete_setup():
|
||||
result = sm.complete_setup(payload)
|
||||
if result.get('success'):
|
||||
try:
|
||||
from app import config_manager, service_bus, EventType
|
||||
from app import config_manager, service_bus, EventType, network_manager
|
||||
identity = config_manager.configs.get('_identity', {})
|
||||
cell_name = identity.get('cell_name', '')
|
||||
service_bus.publish_event(EventType.IDENTITY_CHANGED, 'setup', {
|
||||
'cell_name': identity.get('cell_name'),
|
||||
'cell_name': cell_name,
|
||||
'domain': identity.get('domain'),
|
||||
'domain_name': identity.get('domain_name'),
|
||||
'domain_mode': identity.get('domain_mode'),
|
||||
'effective_domain': config_manager.get_effective_domain(),
|
||||
})
|
||||
# Bootstrap wrote the zone with 'mycell'; rename to the real cell name.
|
||||
if cell_name:
|
||||
network_manager.apply_cell_name('', cell_name)
|
||||
except Exception as exc:
|
||||
logger.warning(f'Failed to publish IDENTITY_CHANGED after setup: {exc}')
|
||||
status_code = 200 if result.get('success') else 400
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for the /api/setup/complete route in routes/setup.py.
|
||||
|
||||
Verifies that completing the wizard fires IDENTITY_CHANGED AND updates the
|
||||
primary DNS zone with the real cell name (fixing the 'mycell' placeholder
|
||||
written by _bootstrap_dns before the wizard runs).
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / 'api'))
|
||||
|
||||
from app import app
|
||||
|
||||
|
||||
# ── helpers ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _valid_payload(**overrides):
|
||||
base = {
|
||||
'cell_name': 'mycel',
|
||||
'password': 'SecurePass1!',
|
||||
'domain_mode': 'lan',
|
||||
'timezone': 'UTC',
|
||||
'ddns_provider': 'none',
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client():
|
||||
app.config['TESTING'] = True
|
||||
app.config['SECRET_KEY'] = 'test-secret'
|
||||
with app.test_client() as c:
|
||||
yield c
|
||||
|
||||
|
||||
def _post_complete(client, payload):
|
||||
return client.post(
|
||||
'/api/setup/complete',
|
||||
data=json.dumps(payload),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
|
||||
# ── tests ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_complete_setup_calls_apply_cell_name_on_success(client, tmp_path):
|
||||
"""After successful wizard, DNS zone is updated with the real cell name."""
|
||||
mock_sm = MagicMock()
|
||||
mock_sm.is_setup_complete.return_value = False
|
||||
mock_sm.complete_setup.return_value = {'success': True, 'redirect': '/login'}
|
||||
|
||||
mock_cm = MagicMock()
|
||||
mock_cm.configs = {'_identity': {'cell_name': 'mycel'}}
|
||||
mock_cm.get_effective_domain.return_value = 'mycel.pic.ngo'
|
||||
|
||||
mock_nm = MagicMock()
|
||||
mock_sbus = MagicMock()
|
||||
|
||||
with (
|
||||
patch('app.setup_manager', mock_sm),
|
||||
patch('app.config_manager', mock_cm),
|
||||
patch('app.network_manager', mock_nm),
|
||||
patch('app.service_bus', mock_sbus),
|
||||
patch.dict('sys.modules', {'app': __import__('app')}),
|
||||
):
|
||||
resp = _post_complete(client, _valid_payload())
|
||||
|
||||
assert resp.status_code == 200
|
||||
mock_nm.apply_cell_name.assert_called_once_with('', 'mycel')
|
||||
|
||||
|
||||
def test_complete_setup_does_not_call_apply_cell_name_on_failure(client, tmp_path):
|
||||
"""DNS zone is NOT touched when setup fails."""
|
||||
mock_sm = MagicMock()
|
||||
mock_sm.is_setup_complete.return_value = False
|
||||
mock_sm.complete_setup.return_value = {'success': False, 'errors': ['bad']}
|
||||
|
||||
mock_nm = MagicMock()
|
||||
|
||||
with (
|
||||
patch('app.setup_manager', mock_sm),
|
||||
patch('app.network_manager', mock_nm),
|
||||
):
|
||||
resp = _post_complete(client, _valid_payload())
|
||||
|
||||
assert resp.status_code == 400
|
||||
mock_nm.apply_cell_name.assert_not_called()
|
||||
|
||||
|
||||
def test_complete_setup_returns_410_when_already_complete(client):
|
||||
"""Route returns 410 if setup was already done."""
|
||||
mock_sm = MagicMock()
|
||||
mock_sm.is_setup_complete.return_value = True
|
||||
|
||||
with patch('app.setup_manager', mock_sm):
|
||||
resp = _post_complete(client, _valid_payload())
|
||||
|
||||
assert resp.status_code == 410
|
||||
|
||||
|
||||
def test_complete_setup_fires_identity_changed_on_success(client):
|
||||
"""IDENTITY_CHANGED is published after successful wizard completion."""
|
||||
mock_sm = MagicMock()
|
||||
mock_sm.is_setup_complete.return_value = False
|
||||
mock_sm.complete_setup.return_value = {'success': True, 'redirect': '/login'}
|
||||
|
||||
mock_cm = MagicMock()
|
||||
mock_cm.configs = {'_identity': {'cell_name': 'mycel', 'domain': 'cell'}}
|
||||
mock_cm.get_effective_domain.return_value = 'cell'
|
||||
|
||||
mock_sbus = MagicMock()
|
||||
mock_nm = MagicMock()
|
||||
|
||||
with (
|
||||
patch('app.setup_manager', mock_sm),
|
||||
patch('app.config_manager', mock_cm),
|
||||
patch('app.network_manager', mock_nm),
|
||||
patch('app.service_bus', mock_sbus),
|
||||
):
|
||||
resp = _post_complete(client, _valid_payload())
|
||||
|
||||
assert resp.status_code == 200
|
||||
mock_sbus.publish_event.assert_called_once()
|
||||
event_args = mock_sbus.publish_event.call_args
|
||||
assert event_args[0][1] == 'setup'
|
||||
Reference in New Issue
Block a user