refactor(tests): use client fixture for test isolation

Replace all AsyncClient instantiations in test_sources.py and test_jobs.py
with the shared  fixture from conftest.py. This ensures proper test
database isolation via dependency overrides.

- Remove imports of httpx AsyncClient, ASGITransport, and app
- Add client fixture parameter to all test functions
- Remove async with blocks around HTTP requests
- test_engine.py already uses db fixture correctly, no changes needed
This commit is contained in:
2026-05-11 21:37:55 +02:00
parent 58c4bc7f30
commit 19987d68a0
5 changed files with 205 additions and 229 deletions
+1
View File
@@ -3,6 +3,7 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from app.database import engine, Base 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 from app.routers import sources, jobs, executions, backups, settings, dashboard
@asynccontextmanager @asynccontextmanager
+140 -154
View File
@@ -1,27 +1,24 @@
import pytest import pytest
from httpx import AsyncClient, ASGITransport
from app.main import app
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_job(): async def test_create_job(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: # Create a source first (job requires source_id)
# Create a source first (job requires source_id) source_resp = await client.post("/api/sources/", json={
source_resp = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) assert source_resp.status_code == 200
assert source_resp.status_code == 200 source_id = source_resp.json()["id"]
source_id = source_resp.json()["id"]
response = await ac.post("/api/jobs/", json={ response = await client.post("/api/jobs/", json={
"name": "Test Job", "name": "Test Job",
"source_id": source_id, "source_id": source_id,
"strategy": "full", "strategy": "full",
"destination_path": "/tmp/backups", "destination_path": "/tmp/backups",
"exclude_patterns": [], "exclude_patterns": [],
"enabled": True "enabled": True
}) })
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert data["name"] == "Test Job" assert data["name"] == "Test Job"
@@ -30,170 +27,162 @@ async def test_create_job():
assert "id" in data assert "id" in data
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_list_jobs(): async def test_list_jobs(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: # Create source and job
# Create source and job source_resp = await client.post("/api/sources/", json={
source_resp = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = source_resp.json()["id"]
source_id = source_resp.json()["id"]
await ac.post("/api/jobs/", json={ await client.post("/api/jobs/", json={
"name": "Test Job", "name": "Test Job",
"source_id": source_id, "source_id": source_id,
"strategy": "full", "strategy": "full",
"destination_path": "/tmp/backups", "destination_path": "/tmp/backups",
"exclude_patterns": [], "exclude_patterns": [],
"enabled": True "enabled": True
}) })
response = await ac.get("/api/jobs/") response = await client.get("/api/jobs/")
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert len(data) >= 1 assert len(data) >= 1
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_job(): async def test_get_job(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: source_resp = await client.post("/api/sources/", json={
source_resp = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = source_resp.json()["id"]
source_id = source_resp.json()["id"]
job_resp = await ac.post("/api/jobs/", json={ job_resp = await client.post("/api/jobs/", json={
"name": "Test Job", "name": "Test Job",
"source_id": source_id, "source_id": source_id,
"strategy": "full", "strategy": "full",
"destination_path": "/tmp/backups", "destination_path": "/tmp/backups",
"exclude_patterns": [], "exclude_patterns": [],
"enabled": True "enabled": True
}) })
job_id = job_resp.json()["id"] job_id = job_resp.json()["id"]
response = await ac.get(f"/api/jobs/{job_id}") response = await client.get(f"/api/jobs/{job_id}")
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["id"] == job_id assert response.json()["id"] == job_id
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_delete_job(): async def test_delete_job(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: source_resp = await client.post("/api/sources/", json={
source_resp = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = source_resp.json()["id"]
source_id = source_resp.json()["id"]
job_resp = await ac.post("/api/jobs/", json={ job_resp = await client.post("/api/jobs/", json={
"name": "Delete Me", "name": "Delete Me",
"source_id": source_id, "source_id": source_id,
"strategy": "full", "strategy": "full",
"destination_path": "/tmp/backups", "destination_path": "/tmp/backups",
"exclude_patterns": [], "exclude_patterns": [],
"enabled": True "enabled": True
}) })
job_id = job_resp.json()["id"] job_id = job_resp.json()["id"]
response = await ac.delete(f"/api/jobs/{job_id}") response = await client.delete(f"/api/jobs/{job_id}")
assert response.status_code == 200 assert response.status_code == 200
# Verify deletion # Verify deletion
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: get_resp = await client.get(f"/api/jobs/{job_id}")
get_resp = await ac.get(f"/api/jobs/{job_id}")
assert get_resp.status_code == 404 assert get_resp.status_code == 404
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_run_job(): async def test_run_job(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: # Create source
# Create source source_resp = await client.post("/api/sources/", json={
source_resp = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = source_resp.json()["id"]
source_id = source_resp.json()["id"]
# Create job # Create job
job_resp = await ac.post("/api/jobs/", json={ job_resp = await client.post("/api/jobs/", json={
"name": "Test Job", "name": "Test Job",
"source_id": source_id, "source_id": source_id,
"strategy": "full", "strategy": "full",
"destination_path": "/tmp/backups", "destination_path": "/tmp/backups",
"exclude_patterns": [], "exclude_patterns": [],
"enabled": True "enabled": True
}) })
job_id = job_resp.json()["id"] job_id = job_resp.json()["id"]
response = await ac.post(f"/api/jobs/{job_id}/run") response = await client.post(f"/api/jobs/{job_id}/run")
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["message"] == "Job execution started" assert response.json()["message"] == "Job execution started"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_run_job_not_found(): async def test_run_job_not_found(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await client.post("/api/jobs/999/run")
response = await ac.post("/api/jobs/999/run")
assert response.status_code == 404 assert response.status_code == 404
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_update_job(): async def test_update_job(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: # Create source
# Create source source_resp = await client.post("/api/sources/", json={
source_resp = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = source_resp.json()["id"]
source_id = source_resp.json()["id"]
# Create job # Create job
job_resp = await ac.post("/api/jobs/", json={ job_resp = await client.post("/api/jobs/", json={
"name": "Original Name", "name": "Original Name",
"source_id": source_id, "source_id": source_id,
"strategy": "full", "strategy": "full",
"destination_path": "/tmp/backups", "destination_path": "/tmp/backups",
"exclude_patterns": [], "exclude_patterns": [],
"enabled": True "enabled": True
}) })
job_id = job_resp.json()["id"] job_id = job_resp.json()["id"]
response = await ac.put(f"/api/jobs/{job_id}", json={ response = await client.put(f"/api/jobs/{job_id}", json={
"name": "Updated Name" "name": "Updated Name"
}) })
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["name"] == "Updated Name" assert response.json()["name"] == "Updated Name"
assert response.json()["strategy"] == "full" # Unchanged assert response.json()["strategy"] == "full" # Unchanged
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_schedule(): async def test_create_schedule(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: # Create source
# Create source source_resp = await client.post("/api/sources/", json={
source_resp = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = source_resp.json()["id"]
source_id = source_resp.json()["id"]
# Create job # Create job
job_resp = await ac.post("/api/jobs/", json={ job_resp = await client.post("/api/jobs/", json={
"name": "Test Job", "name": "Test Job",
"source_id": source_id, "source_id": source_id,
"strategy": "full", "strategy": "full",
"destination_path": "/tmp/backups", "destination_path": "/tmp/backups",
"exclude_patterns": [], "exclude_patterns": [],
"enabled": True "enabled": True
}) })
job_id = job_resp.json()["id"] job_id = job_resp.json()["id"]
response = await ac.post(f"/api/jobs/{job_id}/schedule", json={ response = await client.post(f"/api/jobs/{job_id}/schedule", json={
"job_id": job_id, "job_id": job_id,
"cron_expression": "0 0 * * *", "cron_expression": "0 0 * * *",
"enabled": True "enabled": True
}) })
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert data["job_id"] == job_id assert data["job_id"] == job_id
@@ -201,19 +190,16 @@ async def test_create_schedule():
assert "id" in data assert "id" in data
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_job_not_found(): async def test_get_job_not_found(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await client.get("/api/jobs/99999")
response = await ac.get("/api/jobs/99999")
assert response.status_code == 404 assert response.status_code == 404
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_update_job_not_found(): async def test_update_job_not_found(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await client.put("/api/jobs/99999", json={"name": "Test"})
response = await ac.put("/api/jobs/99999", json={"name": "Test"})
assert response.status_code == 404 assert response.status_code == 404
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_delete_job_not_found(): async def test_delete_job_not_found(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await client.delete("/api/jobs/99999")
response = await ac.delete("/api/jobs/99999")
assert response.status_code == 404 assert response.status_code == 404
+47 -58
View File
@@ -1,15 +1,12 @@
import pytest import pytest
from httpx import AsyncClient, ASGITransport
from app.main import app
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_source(): async def test_create_source(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await client.post("/api/sources/", json={
response = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
})
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert data["name"] == "Test Source" assert data["name"] == "Test Source"
@@ -17,83 +14,75 @@ async def test_create_source():
assert "id" in data assert "id" in data
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_list_sources(): async def test_list_sources(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: # Create source first
# Create source first await client.post("/api/sources/", json={
await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
})
response = await ac.get("/api/sources/") response = await client.get("/api/sources/")
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert len(data) >= 1 assert len(data) >= 1
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_source(): async def test_get_source(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: create_resp = await client.post("/api/sources/", json={
create_resp = await ac.post("/api/sources/", json={ "name": "Test Source",
"name": "Test Source", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = create_resp.json()["id"]
source_id = create_resp.json()["id"]
response = await ac.get(f"/api/sources/{source_id}") response = await client.get(f"/api/sources/{source_id}")
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["id"] == source_id assert response.json()["id"] == source_id
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_delete_source(): async def test_delete_source(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: create_resp = await client.post("/api/sources/", json={
create_resp = await ac.post("/api/sources/", json={ "name": "Delete Me",
"name": "Delete Me", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = create_resp.json()["id"]
source_id = create_resp.json()["id"]
response = await ac.delete(f"/api/sources/{source_id}") response = await client.delete(f"/api/sources/{source_id}")
assert response.status_code == 200 assert response.status_code == 200
# Verify deletion # Verify deletion
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: get_resp = await client.get(f"/api/sources/{source_id}")
get_resp = await ac.get(f"/api/sources/{source_id}")
assert get_resp.status_code == 404 assert get_resp.status_code == 404
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_update_source(): async def test_update_source(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: create_resp = await client.post("/api/sources/", json={
create_resp = await ac.post("/api/sources/", json={ "name": "Original Name",
"name": "Original Name", "type": "local",
"type": "local", "config": {"path": "/tmp/test"}
"config": {"path": "/tmp/test"} })
}) source_id = create_resp.json()["id"]
source_id = create_resp.json()["id"]
response = await ac.put(f"/api/sources/{source_id}", json={ response = await client.put(f"/api/sources/{source_id}", json={
"name": "Updated Name" "name": "Updated Name"
}) })
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["name"] == "Updated Name" assert response.json()["name"] == "Updated Name"
assert response.json()["type"] == "local" # Unchanged assert response.json()["type"] == "local" # Unchanged
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_source_not_found(): async def test_get_source_not_found(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await client.get("/api/sources/99999")
response = await ac.get("/api/sources/99999")
assert response.status_code == 404 assert response.status_code == 404
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_update_source_not_found(): async def test_update_source_not_found(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await client.put("/api/sources/99999", json={"name": "Test"})
response = await ac.put("/api/sources/99999", json={"name": "Test"})
assert response.status_code == 404 assert response.status_code == 404
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_delete_source_not_found(): async def test_delete_source_not_found(client):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: response = await client.delete("/api/sources/99999")
response = await ac.delete("/api/sources/99999")
assert response.status_code == 404 assert response.status_code == 404