Files
backup-tool/tests/integration/test_scheduler_live_sync.py
T

114 lines
3.8 KiB
Python

from __future__ import annotations
from datetime import UTC, datetime, timedelta
from typing import Any, cast
import httpx
import pytest
from backup_tool.config import Settings
from backup_tool.db.models import NotificationDelivery, NotificationEvent, Schedule
from backup_tool.scheduler import SchedulerService
from sqlalchemy import select
PASSWORD = "correct-horse-battery-staple"
async def setup_job(client: httpx.AsyncClient, settings: Settings) -> tuple[dict[str, str], str]:
source_root = settings.local_source_roots[0] / "source"
source_root.mkdir()
headers = await login(client)
repository = await client.post(
"/api/v2/repositories",
json={
"name": "repo",
"relative_path": "repo",
"compression": "none",
"encryption": "none",
},
headers=headers,
)
source = await client.post(
"/api/v2/sources",
json={
"name": "source",
"kind": "local",
"public_config": {"root": str(source_root)},
},
headers=headers,
)
job = await client.post(
"/api/v2/jobs",
json={
"name": "job",
"source_id": source.json()["id"],
"repository_id": repository.json()["id"],
"requested_mode": "full",
"exclusions": [],
"retention": {},
"enabled": True,
"allow_empty": True,
},
headers=headers,
)
assert job.status_code == 201
return headers, job.json()["id"]
async def login(client: httpx.AsyncClient) -> dict[str, str]:
response = await client.post("/api/v2/setup", json={"username": "admin", "password": PASSWORD})
assert response.status_code == 201
return {"X-CSRF-Token": client.cookies["backup_tool_csrf"]}
@pytest.mark.asyncio
async def test_schedule_role_delivery_and_live_crud_sync(
app_client: tuple[httpx.AsyncClient, Settings],
) -> None:
client, settings = app_client
headers, job_id = await setup_job(client, settings)
subscription = await client.post(
"/api/v2/notifications/subscriptions",
json={
"channel": "email",
"event_filters": ["schedule.occurrence_enqueued"],
"destination": {"recipients": ["operator@example.test"]},
},
headers=headers,
)
assert subscription.status_code == 201
created = await client.post(
f"/api/v2/jobs/{job_id}/schedule",
json={"cron": "* * * * *", "timezone": "UTC"},
headers=headers,
)
assert created.status_code == 201
app = cast(Any, client._transport).app
async with app.state.sessions() as db:
schedule = await db.scalar(select(Schedule).where(Schedule.job_id == job_id))
assert schedule is not None
schedule.next_nominal_at = datetime.now(UTC) - timedelta(seconds=1)
await db.commit()
service = SchedulerService(settings)
try:
assert await service.run_once() == 1
finally:
await service.engine.dispose()
async with app.state.sessions() as db:
delivery = await db.scalar(
select(NotificationDelivery.id)
.join(NotificationEvent, NotificationDelivery.event_id == NotificationEvent.id)
.where(NotificationEvent.type == "schedule.occurrence_enqueued")
.limit(1)
)
assert delivery is not None
updated = await client.patch(
f"/api/v2/jobs/{job_id}/schedule",
json={"cron": "0 10 * * *", "timezone": "UTC", "enabled": False},
headers=headers,
)
assert updated.status_code == 200
assert updated.json()["next_nominal_at"] is None
# Deletion with historical executions is deliberately restricted; schedule
# delete behavior is covered before occurrence enqueue in the catalog test.