diff --git a/backend/app/main.py b/backend/app/main.py index 2db80ae..2f0753c 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -3,6 +3,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager from app.database import engine, Base +from app import models # noqa: F401 - registers models with Base.metadata from app.routers import sources, jobs, executions, backups, settings, dashboard @asynccontextmanager diff --git a/backend/tests/__pycache__/test_jobs.cpython-314-pytest-9.0.3.pyc b/backend/tests/__pycache__/test_jobs.cpython-314-pytest-9.0.3.pyc index d87740a..65978cf 100644 Binary files a/backend/tests/__pycache__/test_jobs.cpython-314-pytest-9.0.3.pyc and b/backend/tests/__pycache__/test_jobs.cpython-314-pytest-9.0.3.pyc differ diff --git a/backend/tests/__pycache__/test_sources.cpython-314-pytest-9.0.3.pyc b/backend/tests/__pycache__/test_sources.cpython-314-pytest-9.0.3.pyc index c7bd3aa..40af9a6 100644 Binary files a/backend/tests/__pycache__/test_sources.cpython-314-pytest-9.0.3.pyc and b/backend/tests/__pycache__/test_sources.cpython-314-pytest-9.0.3.pyc differ diff --git a/backend/tests/test_jobs.py b/backend/tests/test_jobs.py index 69ac483..aee96e1 100644 --- a/backend/tests/test_jobs.py +++ b/backend/tests/test_jobs.py @@ -1,27 +1,24 @@ import pytest -from httpx import AsyncClient, ASGITransport -from app.main import app @pytest.mark.asyncio -async def test_create_job(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - # Create a source first (job requires source_id) - source_resp = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - assert source_resp.status_code == 200 - source_id = source_resp.json()["id"] - - response = await ac.post("/api/jobs/", json={ - "name": "Test Job", - "source_id": source_id, - "strategy": "full", - "destination_path": "/tmp/backups", - "exclude_patterns": [], - "enabled": True - }) +async def test_create_job(client): + # Create a source first (job requires source_id) + source_resp = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + assert source_resp.status_code == 200 + source_id = source_resp.json()["id"] + + response = await client.post("/api/jobs/", json={ + "name": "Test Job", + "source_id": source_id, + "strategy": "full", + "destination_path": "/tmp/backups", + "exclude_patterns": [], + "enabled": True + }) assert response.status_code == 200 data = response.json() assert data["name"] == "Test Job" @@ -30,170 +27,162 @@ async def test_create_job(): assert "id" in data @pytest.mark.asyncio -async def test_list_jobs(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - # Create source and job - source_resp = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = source_resp.json()["id"] - - await ac.post("/api/jobs/", json={ - "name": "Test Job", - "source_id": source_id, - "strategy": "full", - "destination_path": "/tmp/backups", - "exclude_patterns": [], - "enabled": True - }) - - response = await ac.get("/api/jobs/") +async def test_list_jobs(client): + # Create source and job + source_resp = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = source_resp.json()["id"] + + await client.post("/api/jobs/", json={ + "name": "Test Job", + "source_id": source_id, + "strategy": "full", + "destination_path": "/tmp/backups", + "exclude_patterns": [], + "enabled": True + }) + + response = await client.get("/api/jobs/") assert response.status_code == 200 data = response.json() assert len(data) >= 1 @pytest.mark.asyncio -async def test_get_job(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - source_resp = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = source_resp.json()["id"] - - job_resp = await ac.post("/api/jobs/", json={ - "name": "Test Job", - "source_id": source_id, - "strategy": "full", - "destination_path": "/tmp/backups", - "exclude_patterns": [], - "enabled": True - }) - job_id = job_resp.json()["id"] - - response = await ac.get(f"/api/jobs/{job_id}") +async def test_get_job(client): + source_resp = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = source_resp.json()["id"] + + job_resp = await client.post("/api/jobs/", json={ + "name": "Test Job", + "source_id": source_id, + "strategy": "full", + "destination_path": "/tmp/backups", + "exclude_patterns": [], + "enabled": True + }) + job_id = job_resp.json()["id"] + + response = await client.get(f"/api/jobs/{job_id}") assert response.status_code == 200 assert response.json()["id"] == job_id @pytest.mark.asyncio -async def test_delete_job(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - source_resp = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = source_resp.json()["id"] - - job_resp = await ac.post("/api/jobs/", json={ - "name": "Delete Me", - "source_id": source_id, - "strategy": "full", - "destination_path": "/tmp/backups", - "exclude_patterns": [], - "enabled": True - }) - job_id = job_resp.json()["id"] - - response = await ac.delete(f"/api/jobs/{job_id}") +async def test_delete_job(client): + source_resp = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = source_resp.json()["id"] + + job_resp = await client.post("/api/jobs/", json={ + "name": "Delete Me", + "source_id": source_id, + "strategy": "full", + "destination_path": "/tmp/backups", + "exclude_patterns": [], + "enabled": True + }) + job_id = job_resp.json()["id"] + + response = await client.delete(f"/api/jobs/{job_id}") assert response.status_code == 200 # Verify deletion - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - get_resp = await ac.get(f"/api/jobs/{job_id}") + get_resp = await client.get(f"/api/jobs/{job_id}") assert get_resp.status_code == 404 @pytest.mark.asyncio -async def test_run_job(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - # Create source - source_resp = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = source_resp.json()["id"] - - # Create job - job_resp = await ac.post("/api/jobs/", json={ - "name": "Test Job", - "source_id": source_id, - "strategy": "full", - "destination_path": "/tmp/backups", - "exclude_patterns": [], - "enabled": True - }) - job_id = job_resp.json()["id"] - - response = await ac.post(f"/api/jobs/{job_id}/run") +async def test_run_job(client): + # Create source + source_resp = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = source_resp.json()["id"] + + # Create job + job_resp = await client.post("/api/jobs/", json={ + "name": "Test Job", + "source_id": source_id, + "strategy": "full", + "destination_path": "/tmp/backups", + "exclude_patterns": [], + "enabled": True + }) + job_id = job_resp.json()["id"] + + response = await client.post(f"/api/jobs/{job_id}/run") assert response.status_code == 200 assert response.json()["message"] == "Job execution started" @pytest.mark.asyncio -async def test_run_job_not_found(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - response = await ac.post("/api/jobs/999/run") +async def test_run_job_not_found(client): + response = await client.post("/api/jobs/999/run") assert response.status_code == 404 @pytest.mark.asyncio -async def test_update_job(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - # Create source - source_resp = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = source_resp.json()["id"] - - # Create job - job_resp = await ac.post("/api/jobs/", json={ - "name": "Original Name", - "source_id": source_id, - "strategy": "full", - "destination_path": "/tmp/backups", - "exclude_patterns": [], - "enabled": True - }) - job_id = job_resp.json()["id"] - - response = await ac.put(f"/api/jobs/{job_id}", json={ - "name": "Updated Name" - }) +async def test_update_job(client): + # Create source + source_resp = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = source_resp.json()["id"] + + # Create job + job_resp = await client.post("/api/jobs/", json={ + "name": "Original Name", + "source_id": source_id, + "strategy": "full", + "destination_path": "/tmp/backups", + "exclude_patterns": [], + "enabled": True + }) + job_id = job_resp.json()["id"] + + response = await client.put(f"/api/jobs/{job_id}", json={ + "name": "Updated Name" + }) assert response.status_code == 200 assert response.json()["name"] == "Updated Name" assert response.json()["strategy"] == "full" # Unchanged @pytest.mark.asyncio -async def test_create_schedule(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - # Create source - source_resp = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = source_resp.json()["id"] - - # Create job - job_resp = await ac.post("/api/jobs/", json={ - "name": "Test Job", - "source_id": source_id, - "strategy": "full", - "destination_path": "/tmp/backups", - "exclude_patterns": [], - "enabled": True - }) - job_id = job_resp.json()["id"] - - response = await ac.post(f"/api/jobs/{job_id}/schedule", json={ - "job_id": job_id, - "cron_expression": "0 0 * * *", - "enabled": True - }) +async def test_create_schedule(client): + # Create source + source_resp = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = source_resp.json()["id"] + + # Create job + job_resp = await client.post("/api/jobs/", json={ + "name": "Test Job", + "source_id": source_id, + "strategy": "full", + "destination_path": "/tmp/backups", + "exclude_patterns": [], + "enabled": True + }) + job_id = job_resp.json()["id"] + + response = await client.post(f"/api/jobs/{job_id}/schedule", json={ + "job_id": job_id, + "cron_expression": "0 0 * * *", + "enabled": True + }) assert response.status_code == 200 data = response.json() assert data["job_id"] == job_id @@ -201,19 +190,16 @@ async def test_create_schedule(): assert "id" in data @pytest.mark.asyncio -async def test_get_job_not_found(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - response = await ac.get("/api/jobs/99999") +async def test_get_job_not_found(client): + response = await client.get("/api/jobs/99999") assert response.status_code == 404 @pytest.mark.asyncio -async def test_update_job_not_found(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - response = await ac.put("/api/jobs/99999", json={"name": "Test"}) +async def test_update_job_not_found(client): + response = await client.put("/api/jobs/99999", json={"name": "Test"}) assert response.status_code == 404 @pytest.mark.asyncio -async def test_delete_job_not_found(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - response = await ac.delete("/api/jobs/99999") +async def test_delete_job_not_found(client): + response = await client.delete("/api/jobs/99999") assert response.status_code == 404 diff --git a/backend/tests/test_sources.py b/backend/tests/test_sources.py index d8f08ee..14ff2ae 100644 --- a/backend/tests/test_sources.py +++ b/backend/tests/test_sources.py @@ -1,15 +1,12 @@ import pytest -from httpx import AsyncClient, ASGITransport -from app.main import app @pytest.mark.asyncio -async def test_create_source(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - response = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) +async def test_create_source(client): + response = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) assert response.status_code == 200 data = response.json() assert data["name"] == "Test Source" @@ -17,83 +14,75 @@ async def test_create_source(): assert "id" in data @pytest.mark.asyncio -async def test_list_sources(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - # Create source first - await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - - response = await ac.get("/api/sources/") +async def test_list_sources(client): + # Create source first + await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + + response = await client.get("/api/sources/") assert response.status_code == 200 data = response.json() assert len(data) >= 1 @pytest.mark.asyncio -async def test_get_source(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - create_resp = await ac.post("/api/sources/", json={ - "name": "Test Source", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = create_resp.json()["id"] - - response = await ac.get(f"/api/sources/{source_id}") +async def test_get_source(client): + create_resp = await client.post("/api/sources/", json={ + "name": "Test Source", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = create_resp.json()["id"] + + response = await client.get(f"/api/sources/{source_id}") assert response.status_code == 200 assert response.json()["id"] == source_id @pytest.mark.asyncio -async def test_delete_source(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - create_resp = await ac.post("/api/sources/", json={ - "name": "Delete Me", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = create_resp.json()["id"] - - response = await ac.delete(f"/api/sources/{source_id}") +async def test_delete_source(client): + create_resp = await client.post("/api/sources/", json={ + "name": "Delete Me", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = create_resp.json()["id"] + + response = await client.delete(f"/api/sources/{source_id}") assert response.status_code == 200 # Verify deletion - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - get_resp = await ac.get(f"/api/sources/{source_id}") + get_resp = await client.get(f"/api/sources/{source_id}") assert get_resp.status_code == 404 @pytest.mark.asyncio -async def test_update_source(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - create_resp = await ac.post("/api/sources/", json={ - "name": "Original Name", - "type": "local", - "config": {"path": "/tmp/test"} - }) - source_id = create_resp.json()["id"] - - response = await ac.put(f"/api/sources/{source_id}", json={ - "name": "Updated Name" - }) +async def test_update_source(client): + create_resp = await client.post("/api/sources/", json={ + "name": "Original Name", + "type": "local", + "config": {"path": "/tmp/test"} + }) + source_id = create_resp.json()["id"] + + response = await client.put(f"/api/sources/{source_id}", json={ + "name": "Updated Name" + }) assert response.status_code == 200 assert response.json()["name"] == "Updated Name" assert response.json()["type"] == "local" # Unchanged @pytest.mark.asyncio -async def test_get_source_not_found(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - response = await ac.get("/api/sources/99999") +async def test_get_source_not_found(client): + response = await client.get("/api/sources/99999") assert response.status_code == 404 @pytest.mark.asyncio -async def test_update_source_not_found(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - response = await ac.put("/api/sources/99999", json={"name": "Test"}) +async def test_update_source_not_found(client): + response = await client.put("/api/sources/99999", json={"name": "Test"}) assert response.status_code == 404 @pytest.mark.asyncio -async def test_delete_source_not_found(): - async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: - response = await ac.delete("/api/sources/99999") +async def test_delete_source_not_found(client): + response = await client.delete("/api/sources/99999") assert response.status_code == 404