feat: add config write API (POST /api/0/config, POST /api/0/config/rollback)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -47,3 +47,67 @@ def test_mask_config_for_api_no_password_in_users_leaves_no_key():
|
||||
}
|
||||
result = http._mask_config_for_api(config)
|
||||
assert "password" not in result["users"]["bob"]
|
||||
|
||||
|
||||
# ---- configio integration for write path ----
|
||||
|
||||
def test_write_path_applies_server_section(tmp_path):
|
||||
cfg = tmp_path / ".hb.yaml"
|
||||
cfg.write_text("hbd_port: 50004\ninterval: 20\nusers: {}\n")
|
||||
from hbd.server import configio
|
||||
data = configio.read_roundtrip(str(cfg))
|
||||
configio.apply_structured_section(data, "server", {"interval": 60})
|
||||
configio.write_config(str(cfg), data)
|
||||
data2 = configio.read_roundtrip(str(cfg))
|
||||
assert data2["interval"] == 60
|
||||
assert data2["hbd_port"] == 50004 # unchanged
|
||||
|
||||
|
||||
def test_write_path_applies_yaml_section(tmp_path):
|
||||
cfg = tmp_path / ".hb.yaml"
|
||||
cfg.write_text(
|
||||
"hbd_port: 50004\nnotification_channels:\n old_ch:\n type: email\n"
|
||||
)
|
||||
from hbd.server import configio
|
||||
data = configio.read_roundtrip(str(cfg))
|
||||
configio.apply_yaml_section(data, "notification_channels", "new_ch:\n type: pushover\n")
|
||||
configio.write_config(str(cfg), data)
|
||||
data2 = configio.read_roundtrip(str(cfg))
|
||||
assert "new_ch" in data2["notification_channels"]
|
||||
assert "old_ch" not in data2["notification_channels"]
|
||||
|
||||
|
||||
def test_write_path_hashes_plaintext_password(tmp_path):
|
||||
cfg = tmp_path / ".hb.yaml"
|
||||
cfg.write_text("hbd_port: 50004\nusers:\n alice:\n full_name: Alice\n admin: true\n password: pbkdf2:sha256:old\n")
|
||||
from hbd.server import configio
|
||||
from hbd.server import users as users_mod
|
||||
data = configio.read_roundtrip(str(cfg))
|
||||
# Simulate what the POST handler does: hash plaintext password
|
||||
new_users = {"alice": {"full_name": "Alice", "admin": True, "password": "newplaintext"}}
|
||||
for username, attrs in new_users.items():
|
||||
pw = attrs.get("password", "")
|
||||
if pw and not pw.startswith("pbkdf2:"):
|
||||
attrs["password"] = users_mod.hash_password(pw)
|
||||
configio.apply_structured_section(data, "users", new_users)
|
||||
configio.write_config(str(cfg), data)
|
||||
data2 = configio.read_roundtrip(str(cfg))
|
||||
assert data2["users"]["alice"]["password"].startswith("pbkdf2:")
|
||||
assert data2["users"]["alice"]["password"] != "newplaintext"
|
||||
|
||||
|
||||
def test_rollback_restores_backup(tmp_path):
|
||||
cfg = tmp_path / ".hb.yaml"
|
||||
cfg.write_text("hbd_port: 50004\ninterval: 20\n")
|
||||
from hbd.server import configio
|
||||
# Make a change to create a backup
|
||||
data = configio.read_roundtrip(str(cfg))
|
||||
data["interval"] = 99
|
||||
configio.write_config(str(cfg), data)
|
||||
backups = configio.list_backups(str(cfg))
|
||||
assert len(backups) == 1
|
||||
# Read the backup and write it back (simulating rollback)
|
||||
backup_data = configio.read_roundtrip(backups[0])
|
||||
configio.write_config(str(cfg), backup_data)
|
||||
restored = configio.read_roundtrip(str(cfg))
|
||||
assert restored["interval"] == 20
|
||||
|
||||
Reference in New Issue
Block a user