mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-09 13:35:12 +09:00
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>
88 lines
3.9 KiB
Python
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
|