57a338f7e6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
"""FC-5: /api/migrate API tests."""
|
|
import io
|
|
import json
|
|
|
|
import pytest
|
|
from werkzeug.datastructures import FileStorage
|
|
|
|
import backend.app.tasks.migration # noqa: F401 — register celery task
|
|
from backend.app import create_app
|
|
from backend.app.celery_app import celery
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
def _file_storage(payload: dict, filename: str = "export.json") -> FileStorage:
|
|
return FileStorage(
|
|
stream=io.BytesIO(json.dumps(payload).encode("utf-8")),
|
|
filename=filename,
|
|
content_type="application/json",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
async def app():
|
|
return create_app()
|
|
|
|
|
|
@pytest.fixture
|
|
async def client(app):
|
|
async with app.test_client() as c:
|
|
yield c
|
|
|
|
|
|
# Retired 2026-05-24 (FC-3h): `test_post_backup_returns_202_and_id`
|
|
# asserted the /api/migrate/backup endpoint, which was retired in FC-3h.
|
|
# Backup is now a first-class feature at /api/system/backup/*;
|
|
# coverage lives in tests/test_api_system_backup.py.
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_post_rejects_unknown_kind(client):
|
|
resp = await client.post("/api/migrate/destroy", json={})
|
|
assert resp.status_code == 400
|
|
body = await resp.get_json()
|
|
assert body["error"] == "unknown_kind"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_post_ingest_rejects_missing_file(client):
|
|
resp = await client.post("/api/migrate/gs_ingest", form={})
|
|
assert resp.status_code == 400
|
|
body = await resp.get_json()
|
|
assert body["error"] == "missing_export_file"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_post_ingest_accepts_multipart_file(client, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"backend.app.api.migrate.run_migration",
|
|
type("F", (), {"delay": lambda self, *a, **k: None})(),
|
|
)
|
|
|
|
export = {
|
|
"source_app": "gallerysubscriber", "schema_version": 1,
|
|
"subscriptions": [], "credentials": [],
|
|
}
|
|
resp = await client.post(
|
|
"/api/migrate/gs_ingest",
|
|
form={"dry_run": "false"},
|
|
files={"export_file": _file_storage(export, "gs-export.json")},
|
|
)
|
|
assert resp.status_code == 202
|
|
|
|
|
|
# Retired 2026-05-24: `test_post_apply_without_backup_rejected` asserted
|
|
# the migration backup-gate rejected applies without a recent backup.
|
|
# That gate was removed in commit 5535677 (_APPLY_KINDS is now empty,
|
|
# FC-3h will own backup as a first-class feature) so the test was
|
|
# testing dead behavior. Removed rather than rewritten.
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_post_dry_run_allowed_without_backup(client, monkeypatch):
|
|
# FC-3h: the _has_recent_pre_migration_backup gate was retired; dry-run
|
|
# ingests were always allowed and now non-dry-run ingests are too.
|
|
# This test stays as regression coverage that dry_run ingest still works.
|
|
monkeypatch.setattr(
|
|
"backend.app.api.migrate.run_migration",
|
|
type("F", (), {"delay": lambda self, *a, **k: None})(),
|
|
)
|
|
export = {
|
|
"source_app": "gallerysubscriber", "schema_version": 1,
|
|
"subscriptions": [], "credentials": [],
|
|
}
|
|
resp = await client.post(
|
|
"/api/migrate/gs_ingest",
|
|
form={"dry_run": "true"},
|
|
files={"export_file": _file_storage(export, "gs-export.json")},
|
|
)
|
|
assert resp.status_code == 202
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_run_404_unknown(client):
|
|
resp = await client.get("/api/migrate/runs/9999999")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_run_migration_task_registered():
|
|
assert "backend.app.tasks.migration.run_migration" in celery.tasks
|