Files
ServerKit/backend/tests/test_app_wordpress_surface.py
T
Juan DenisandClaude Opus 4.8 92c435d436 refactor(unify): §7 — WordPress reachable through the generic /apps surface
Additive API consolidation (plan §7 Phases 3–4) that exposes a WordPress site
through its Application id, treating WordPressSite as the 1:1 extension table it
already is:
- GET  /apps/:id/wordpress            → {is_wordpress, site_id, env_type, ...}
- GET  /apps/:id/db-snapshots         → WP DB snapshots (was §8-deferred)
- POST /apps/:id/db-snapshots         → create
- DELETE /apps/:id/db-snapshots/:id   → delete
All grant-aware via the apps.py access seam (ResourceGrantService).

Deliberately NOT done (plan §7 Phase 1/2, with rationale): blanket-setting
app_type='wordpress' on WordPressSite-linked apps. Recon showed many WP sites are
app_type='docker', and 14+ docker-compose/nginx/scale/sleep code paths key on
app_type=='docker' — changing it would silently break start/stop/restart/logs/
status/domain config for containerized WP. The codebase already treats the
WordPressSite record (not app_type) as the source of truth (wordpress.py
_is_wp_app), which is the correct architecture; flipping app_type would regress
it. Frontend WordPress→Services merge likewise deferred (the /wordpress/:id route
is site-id-keyed).

Verified: 6 new surface tests + WP/resource-grant regression suites pass (23).

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

88 lines
3.9 KiB
Python

"""§7 unification — WordPress reachable through the generic /apps surface.
A WordPress site is a 1:1 extension of an Application. These additive routes
expose the linkage (/apps/:id/wordpress) and the WP DB snapshots
(/apps/:id/db-snapshots) under the app id WITHOUT changing app_type, so the
docker-compose paths that key on app_type=='docker' are unaffected.
"""
import pytest
def _seed_app(app, *, wordpress=True):
from app import db
from app.models import User
from app.models.application import Application
from app.models.wordpress_site import WordPressSite
with app.app_context():
admin = User.query.filter_by(username='testadmin').first()
a = Application(name='wp-app', app_type='docker', status='running',
root_path='/srv/wp', user_id=admin.id if admin else 1)
db.session.add(a)
db.session.commit()
site_id = None
if wordpress:
site = WordPressSite(application_id=a.id, db_name='wpdb', db_user='wpuser',
db_host='localhost', is_production=True,
environment_type='production', wp_version='6.5')
db.session.add(site)
db.session.commit()
site_id = site.id
return a.id, site_id
def test_app_wordpress_info_true(app, client, auth_headers):
app_id, site_id = _seed_app(app, wordpress=True)
res = client.get(f'/api/v1/apps/{app_id}/wordpress', headers=auth_headers)
assert res.status_code == 200
body = res.get_json()
assert body['is_wordpress'] is True
assert body['site_id'] == site_id
assert body['is_production'] is True
def test_app_wordpress_info_false_for_plain_app(app, client, auth_headers):
app_id, _ = _seed_app(app, wordpress=False)
res = client.get(f'/api/v1/apps/{app_id}/wordpress', headers=auth_headers)
assert res.status_code == 200
assert res.get_json()['is_wordpress'] is False
def test_db_snapshots_rejects_non_wordpress(app, client, auth_headers):
app_id, _ = _seed_app(app, wordpress=False)
res = client.get(f'/api/v1/apps/{app_id}/db-snapshots', headers=auth_headers)
assert res.status_code == 400
def test_db_snapshots_list_empty(app, client, auth_headers):
app_id, _ = _seed_app(app, wordpress=True)
res = client.get(f'/api/v1/apps/{app_id}/db-snapshots', headers=auth_headers)
assert res.status_code == 200
assert res.get_json()['snapshots'] == []
def test_db_snapshot_create_and_delete(app, client, auth_headers, monkeypatch):
from app.services.db_sync_service import DatabaseSyncService
from app.services.wordpress_env_service import WordPressEnvService
monkeypatch.setattr(WordPressEnvService, '_get_db_password', staticmethod(lambda site: 'pw'))
monkeypatch.setattr(DatabaseSyncService, 'create_snapshot', staticmethod(
lambda **kw: {'success': True, 'snapshot': {
'name': kw.get('name'), 'file_path': '/var/backups/snap.sql.gz',
'size_bytes': 1234, 'compressed': True, 'tables': ['wp_posts'], 'row_count': 10,
}}))
monkeypatch.setattr(DatabaseSyncService, 'upload_snapshot_offsite', staticmethod(lambda p: None))
monkeypatch.setattr(DatabaseSyncService, 'delete_snapshot', staticmethod(lambda p: True))
app_id, _ = _seed_app(app, wordpress=True)
created = client.post(f'/api/v1/apps/{app_id}/db-snapshots',
json={'name': 'nightly', 'tag': 'manual'}, headers=auth_headers)
assert created.status_code == 201, created.get_json()
snap_id = created.get_json()['snapshot']['id']
listed = client.get(f'/api/v1/apps/{app_id}/db-snapshots', headers=auth_headers)
assert listed.get_json()['total'] == 1
deleted = client.delete(f'/api/v1/apps/{app_id}/db-snapshots/{snap_id}', headers=auth_headers)
assert deleted.status_code == 200
assert client.get(f'/api/v1/apps/{app_id}/db-snapshots', headers=auth_headers).get_json()['total'] == 0