"""Editing backup destinations and schedules from Settings. The UI only ever sees stored secrets masked, so it sends them back empty; the server must keep the stored value then, and apply every other change. A schedule whose timing changes gets a new next run. """ from __future__ import annotations import uuid from datetime import datetime import pytest @pytest.fixture def destination(as_admin): r = as_admin.post( "/api/backups/destinations", json={ "name": f"nas-{uuid.uuid4().hex[:6]}", "type": "sftp", "config": {"host": "nas", "username": "backup", "password": "s3cret", "path": "/b"}, }, ) assert r.status_code == 201 return r.json() def _stored_config(dest_id): from sqlmodel import Session from database import engine from models.backup_destination import BackupDestination from services import backup_destination_service as dest_service with Session(engine) as session: return dest_service.parse_config(session.get(BackupDestination, dest_id)) def test_edit_keeps_a_secret_sent_back_empty(as_admin, destination): assert destination["config"]["password"] == "••••••" r = as_admin.put( f"/api/backups/destinations/{destination['id']}", json={ "name": "renamed", "config": {"host": "nas2", "username": "backup", "password": "", "path": "/new"}, }, ) assert r.status_code == 200 assert r.json()["name"] == "renamed" stored = _stored_config(destination["id"]) assert stored == {"host": "nas2", "username": "backup", "password": "s3cret", "path": "/new"} def test_edit_replaces_a_secret_that_was_typed(as_admin, destination): as_admin.put( f"/api/backups/destinations/{destination['id']}", json={"config": {"password": "n3w"}}, ) assert _stored_config(destination["id"])["password"] == "n3w" def test_edit_schedule_moves_its_next_run(as_admin, destination): from sqlmodel import Session from database import engine from models.stack import Stack stack_id = f"s{uuid.uuid4().hex[:8]}" with Session(engine) as session: session.add(Stack(id=stack_id, name=stack_id)) session.commit() r = as_admin.post( "/api/backups/schedules", json={"stack_id": stack_id, "destination_id": destination["id"], "frequency": "daily", "hour": 3, "minute": 0}, ) assert r.status_code == 201 created = r.json() r = as_admin.put( f"/api/backups/schedules/{created['id']}", json={"frequency": "weekly", "weekday": 6, "hour": 4, "minute": 30, "keep": 3, "include_volumes": False, "stop_first": False, "enabled": True, "destination_id": destination["id"]}, ) assert r.status_code == 200 updated = r.json() assert updated["stack_id"] == stack_id assert (updated["frequency"], updated["weekday"], updated["hour"], updated["minute"]) == ( "weekly", 6, 4, 30, ) assert (updated["keep"], updated["include_volumes"], updated["stop_first"]) == (3, False, False) next_run = datetime.fromisoformat(updated["next_run"]) assert (next_run.weekday(), next_run.hour, next_run.minute) == (6, 4, 30) assert updated["next_run"] != created["next_run"]