- auth_manager._ensure_file(): stop creating the empty auth_users.json on init — the constructor now only creates the parent directory. The 503 guard in enforce_auth relies on the file existing-but-empty; by not creating it on init, a fresh install correctly bypasses auth (file missing → FileNotFoundError → bypass), while the explicit misconfiguration case (file created with [] but no users added) still returns 503. - test_enforce_auth_configured.py: update empty_auth_manager fixture to explicitly write '[]' to the file (reproduces the misconfig scenario now that the constructor no longer creates it). - ddns_manager: read ddns config from configs['ddns'] directly instead of identity.domain.ddns — _identity.domain is a plain string, not a dict, so the nested lookup silently returned nothing on every call. - setup_cell.py: write top-level 'ddns' block into cell_config.json with provider, api_base_url, and totp_secret; default TOTP secret to the production value so installs work without a manual env var. - test_ddns_manager.py: update _make_config_manager to populate cm.configs instead of mocking get_identity() to match the new ddns config location. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ import app as app_module
|
||||
|
||||
class TestAppMisc(unittest.TestCase):
|
||||
def setUp(self):
|
||||
app_module.app.config['TESTING'] = True
|
||||
# Patch managers to avoid side effects
|
||||
self.patches = [
|
||||
patch.object(app_module, 'network_manager', MagicMock()),
|
||||
|
||||
@@ -37,15 +37,12 @@ def _make_response(status_code=200, json_data=None, text=''):
|
||||
|
||||
|
||||
def _make_config_manager(ddns_cfg=None, domain_cfg=None):
|
||||
"""Return a mock config_manager whose get_identity() returns a useful dict."""
|
||||
"""Return a mock config_manager with a real configs dict."""
|
||||
cm = MagicMock()
|
||||
configs = {}
|
||||
if ddns_cfg is not None:
|
||||
identity = {'domain': {'ddns': ddns_cfg}}
|
||||
elif domain_cfg is not None:
|
||||
identity = {'domain': domain_cfg}
|
||||
else:
|
||||
identity = {}
|
||||
cm.get_identity.return_value = identity
|
||||
configs['ddns'] = ddns_cfg
|
||||
cm.configs = configs
|
||||
return cm
|
||||
|
||||
|
||||
@@ -83,6 +80,27 @@ class TestPicNgoDDNSRegister(unittest.TestCase):
|
||||
_, kwargs = mock_post.call_args
|
||||
self.assertNotIn('Authorization', kwargs.get('headers', {}))
|
||||
|
||||
def test_register_sends_otp_header_when_secret_configured(self):
|
||||
"""register() sends X-Register-OTP when totp_secret is set."""
|
||||
provider = PicNgoDDNS(totp_secret='JBSWY3DPEHPK3PXP')
|
||||
mock_resp = _make_response(200, json_data={'token': 'tok', 'subdomain': 'x.pic.ngo'})
|
||||
with patch('requests.post', return_value=mock_resp) as mock_post:
|
||||
provider.register('x', '1.2.3.4')
|
||||
_, kwargs = mock_post.call_args
|
||||
self.assertIn('X-Register-OTP', kwargs.get('headers', {}))
|
||||
otp = kwargs['headers']['X-Register-OTP']
|
||||
self.assertEqual(len(otp), 6)
|
||||
self.assertTrue(otp.isdigit())
|
||||
|
||||
def test_register_no_otp_header_without_secret(self):
|
||||
"""register() omits X-Register-OTP when no TOTP secret is configured."""
|
||||
provider = PicNgoDDNS()
|
||||
mock_resp = _make_response(200, json_data={'token': 't', 'subdomain': 'x'})
|
||||
with patch('requests.post', return_value=mock_resp) as mock_post:
|
||||
provider.register('x', '1.2.3.4')
|
||||
_, kwargs = mock_post.call_args
|
||||
self.assertNotIn('X-Register-OTP', kwargs.get('headers', {}))
|
||||
|
||||
|
||||
class TestPicNgoDDNSUpdate(unittest.TestCase):
|
||||
"""PicNgoDDNS.update() calls the correct URL with Authorization header."""
|
||||
|
||||
@@ -53,8 +53,11 @@ def empty_auth_manager(tmp_path):
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
mgr = AuthManager(data_dir=data_dir, config_dir=config_dir)
|
||||
# The constructor creates the file with '[]' (empty list). We do NOT add
|
||||
# any user, so list_users() returns [] but the file is readable.
|
||||
# Explicitly create the file with an empty list to simulate the
|
||||
# "auth configured but no users" misconfiguration scenario.
|
||||
users_file = os.path.join(data_dir, 'auth_users.json')
|
||||
with open(users_file, 'w') as f:
|
||||
f.write('[]')
|
||||
assert mgr.list_users() == [], 'Expected empty user list'
|
||||
return mgr
|
||||
|
||||
|
||||
Reference in New Issue
Block a user