114 lines
3.8 KiB
Python
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.
|