Files
ServerKit/backend/tests/test_backup_generic_api.py
Juan DenisandClaude Opus 4.8 45e83e9a09 refactor(unify): §8 Phase B5 — generic backup-policy CRUD for every target
Completes the §8 API consolidation: one admin surface drives a BackupPolicy for
ANY target — application, wordpress_site, database, files, server — by
(target_type, target_id):

  GET/PUT  /api/v1/backups/policies/<type>/<id>          (PUT accepts
           target_subtype + target_meta to describe database/files targets)
  POST     /api/v1/backups/policies/<type>/<id>/run
  GET      /api/v1/backups/policies/<type>/<id>/runs
  POST     .../runs/<run_id>/restore  |  /verify
  DELETE   .../runs/<run_id>

This is the only way to drive the database/files/server targets (which have no
per-resource page). The grant-aware per-resource mounts
(/apps/:id/backup-policy, /wordpress/sites/:id/backup-policy) are unchanged.

Deferred to §7 (WordPress-as-app): surfacing WP DatabaseSnapshot under
/apps/:id/db-snapshots, and the frontend Backups.jsx rewrite, both of which
depend on the app↔site unification.

Verified: 5 new tests; full backup/snapshot suite green (63 passed, 2 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:27:08 -04:00

52 lines
2.0 KiB
Python

"""§8 Phase B5 — generic backup-policy CRUD by (target_type, target_id).
One admin surface drives policies for every target type, including the
database/files/server targets that have no per-resource page. The existing
/apps/:id/backup-policy and /wordpress/sites/:id/backup-policy mounts are
unchanged.
"""
def test_put_then_get_database_policy(app, client, auth_headers):
body = {
'enabled': True,
'schedule_cron': '0 3 * * *',
'target_subtype': 'mysql',
'target_meta': {'db_name': 'shop', 'host': 'localhost'},
}
put = client.put('/api/v1/backups/policies/database/5', json=body, headers=auth_headers)
assert put.status_code == 200, put.get_json()
view = put.get_json()
assert view['policy']['target_type'] == 'database'
assert view['policy']['target_subtype'] == 'mysql'
assert view['policy']['target_meta']['db_name'] == 'shop'
assert view['policy']['enabled'] is True
get = client.get('/api/v1/backups/policies/database/5', headers=auth_headers)
assert get.status_code == 200
assert get.get_json()['policy']['schedule_cron'] == '0 3 * * *'
def test_get_creates_default_files_policy(app, client, auth_headers):
get = client.get('/api/v1/backups/policies/files/9', headers=auth_headers)
assert get.status_code == 200
assert get.get_json()['policy']['target_type'] == 'files'
assert get.get_json()['policy']['enabled'] is False
def test_invalid_target_type_rejected(app, client, auth_headers):
res = client.get('/api/v1/backups/policies/bogus/1', headers=auth_headers)
assert res.status_code == 400
def test_list_runs_empty_for_new_policy(app, client, auth_headers):
res = client.get('/api/v1/backups/policies/files/77/runs', headers=auth_headers)
assert res.status_code == 200
assert res.get_json()['runs'] == []
def test_generic_surface_requires_admin(app, client):
# No auth → rejected (matches the rest of /backups).
res = client.get('/api/v1/backups/policies/database/1')
assert res.status_code in (401, 422)