"""Tests for maintenance schedule CRUD.""" import pytest from httpx import AsyncClient async def _create_maintenance_tree(client, headers): resp = await client.post( "/api/v1/trees", json={ "name": "Scheduled Patch", "tree_type": "maintenance", "tree_structure": { "steps": [ {"id": "s1", "type": "procedure_step", "title": "Step", "description": "Do it", "content_type": "action"}, {"id": "end", "type": "procedure_end", "title": "Done"}, ] }, }, headers=headers, ) assert resp.status_code == 201, resp.text return resp.json()["id"] @pytest.mark.asyncio async def test_create_schedule(client: AsyncClient, auth_headers: dict): tree_id = await _create_maintenance_tree(client, auth_headers) resp = await client.post( "/api/v1/maintenance-schedules", json={ "tree_id": tree_id, "cron_expression": "0 9 15 * *", "timezone": "America/New_York", }, headers=auth_headers, ) assert resp.status_code == 201, resp.text data = resp.json() assert data["cron_expression"] == "0 9 15 * *" assert data["timezone"] == "America/New_York" assert data["is_active"] is True assert data["next_run_at"] is not None @pytest.mark.asyncio async def test_duplicate_schedule_rejected(client: AsyncClient, auth_headers: dict): """Cannot create two schedules for the same tree.""" tree_id = await _create_maintenance_tree(client, auth_headers) await client.post( "/api/v1/maintenance-schedules", json={"tree_id": tree_id, "cron_expression": "0 0 1 * *", "timezone": "UTC"}, headers=auth_headers, ) resp = await client.post( "/api/v1/maintenance-schedules", json={"tree_id": tree_id, "cron_expression": "0 6 1 * *", "timezone": "UTC"}, headers=auth_headers, ) assert resp.status_code == 409 @pytest.mark.asyncio async def test_get_schedule_for_tree(client: AsyncClient, auth_headers: dict): tree_id = await _create_maintenance_tree(client, auth_headers) await client.post( "/api/v1/maintenance-schedules", json={"tree_id": tree_id, "cron_expression": "0 0 1 * *", "timezone": "UTC"}, headers=auth_headers, ) resp = await client.get(f"/api/v1/maintenance-schedules/tree/{tree_id}", headers=auth_headers) assert resp.status_code == 200 assert resp.json()["cron_expression"] == "0 0 1 * *" @pytest.mark.asyncio async def test_disable_schedule(client: AsyncClient, auth_headers: dict): tree_id = await _create_maintenance_tree(client, auth_headers) create = await client.post( "/api/v1/maintenance-schedules", json={"tree_id": tree_id, "cron_expression": "0 6 * * 1", "timezone": "UTC"}, headers=auth_headers, ) sched_id = create.json()["id"] resp = await client.patch( f"/api/v1/maintenance-schedules/{sched_id}", json={"is_active": False}, headers=auth_headers, ) assert resp.status_code == 200 assert resp.json()["is_active"] is False @pytest.mark.asyncio async def test_get_schedule_not_found(client: AsyncClient, auth_headers: dict): tree_id = await _create_maintenance_tree(client, auth_headers) resp = await client.get(f"/api/v1/maintenance-schedules/tree/{tree_id}", headers=auth_headers) assert resp.status_code == 404 @pytest.mark.asyncio async def test_cannot_create_schedule_for_non_maintenance_tree( client: AsyncClient, auth_headers: dict, test_tree: dict ): """Schedules are restricted to maintenance flows.""" resp = await client.post( "/api/v1/maintenance-schedules", json={ "tree_id": test_tree["id"], "cron_expression": "0 0 1 * *", "timezone": "UTC", }, headers=auth_headers, ) assert resp.status_code == 400 @pytest.mark.asyncio async def test_cannot_schedule_other_teams_tree(client: AsyncClient, auth_headers: dict, test_db): """User cannot create a schedule for a tree belonging to another team.""" import uuid as _uuid from app.models.team import Team from app.models.tree import Tree # Create a tree belonging to a DIFFERENT team directly in DB other_team = Team(name=f"Other Team {_uuid.uuid4()}") test_db.add(other_team) await test_db.flush() other_tree = Tree( name="Other Team Tree", tree_type="maintenance", team_id=other_team.id, tree_structure={ "steps": [ {"id": "s1", "type": "procedure_step", "title": "Step", "description": "Do it", "content_type": "action"}, {"id": "end", "type": "procedure_end", "title": "Done"}, ] }, status="published", visibility="team", ) test_db.add(other_tree) await test_db.flush() # Current user (from auth_headers) tries to schedule it resp = await client.post( "/api/v1/maintenance-schedules", json={ "tree_id": str(other_tree.id), "cron_expression": "0 9 1 * *", "timezone": "UTC", }, headers=auth_headers, ) assert resp.status_code in (403, 404) # either is acceptable