feat(backend): CRUD jardins + tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,17 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
from app.routers import gardens, varieties, plantings, tasks, settings, media
|
||||
|
||||
app.include_router(gardens.router, prefix="/api")
|
||||
app.include_router(varieties.router, prefix="/api")
|
||||
app.include_router(plantings.router, prefix="/api")
|
||||
app.include_router(tasks.router, prefix="/api")
|
||||
app.include_router(settings.router, prefix="/api")
|
||||
app.include_router(media.router, prefix="/api")
|
||||
|
||||
# Note: le mount StaticFiles sera ajouté ici dans Task 6
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
def health():
|
||||
|
||||
82
backend/app/routers/gardens.py
Normal file
82
backend/app/routers/gardens.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.database import get_session
|
||||
from app.models.garden import Garden, GardenCell, GardenImage, Measurement
|
||||
|
||||
router = APIRouter(tags=["jardins"])
|
||||
|
||||
|
||||
@router.get("/gardens", response_model=List[Garden])
|
||||
def list_gardens(session: Session = Depends(get_session)):
|
||||
return session.exec(select(Garden)).all()
|
||||
|
||||
|
||||
@router.post("/gardens", response_model=Garden, status_code=status.HTTP_201_CREATED)
|
||||
def create_garden(garden: Garden, session: Session = Depends(get_session)):
|
||||
session.add(garden)
|
||||
session.commit()
|
||||
session.refresh(garden)
|
||||
return garden
|
||||
|
||||
|
||||
@router.get("/gardens/{id}", response_model=Garden)
|
||||
def get_garden(id: int, session: Session = Depends(get_session)):
|
||||
g = session.get(Garden, id)
|
||||
if not g:
|
||||
raise HTTPException(status_code=404, detail="Jardin introuvable")
|
||||
return g
|
||||
|
||||
|
||||
@router.put("/gardens/{id}", response_model=Garden)
|
||||
def update_garden(id: int, data: Garden, session: Session = Depends(get_session)):
|
||||
g = session.get(Garden, id)
|
||||
if not g:
|
||||
raise HTTPException(status_code=404, detail="Jardin introuvable")
|
||||
for k, v in data.model_dump(exclude_unset=True, exclude={"id", "created_at"}).items():
|
||||
setattr(g, k, v)
|
||||
g.updated_at = datetime.utcnow()
|
||||
session.add(g)
|
||||
session.commit()
|
||||
session.refresh(g)
|
||||
return g
|
||||
|
||||
|
||||
@router.delete("/gardens/{id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_garden(id: int, session: Session = Depends(get_session)):
|
||||
g = session.get(Garden, id)
|
||||
if not g:
|
||||
raise HTTPException(status_code=404, detail="Jardin introuvable")
|
||||
session.delete(g)
|
||||
session.commit()
|
||||
|
||||
|
||||
@router.get("/gardens/{id}/cells", response_model=List[GardenCell])
|
||||
def list_cells(id: int, session: Session = Depends(get_session)):
|
||||
return session.exec(select(GardenCell).where(GardenCell.garden_id == id)).all()
|
||||
|
||||
|
||||
@router.post("/gardens/{id}/cells", response_model=GardenCell, status_code=status.HTTP_201_CREATED)
|
||||
def create_cell(id: int, cell: GardenCell, session: Session = Depends(get_session)):
|
||||
cell.garden_id = id
|
||||
session.add(cell)
|
||||
session.commit()
|
||||
session.refresh(cell)
|
||||
return cell
|
||||
|
||||
|
||||
@router.get("/gardens/{id}/measurements", response_model=List[Measurement])
|
||||
def list_measurements(id: int, session: Session = Depends(get_session)):
|
||||
return session.exec(select(Measurement).where(Measurement.garden_id == id)).all()
|
||||
|
||||
|
||||
@router.post("/gardens/{id}/measurements", response_model=Measurement, status_code=status.HTTP_201_CREATED)
|
||||
def create_measurement(id: int, m: Measurement, session: Session = Depends(get_session)):
|
||||
m.garden_id = id
|
||||
session.add(m)
|
||||
session.commit()
|
||||
session.refresh(m)
|
||||
return m
|
||||
2
backend/app/routers/media.py
Normal file
2
backend/app/routers/media.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from fastapi import APIRouter
|
||||
router = APIRouter()
|
||||
2
backend/app/routers/plantings.py
Normal file
2
backend/app/routers/plantings.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from fastapi import APIRouter
|
||||
router = APIRouter()
|
||||
2
backend/app/routers/settings.py
Normal file
2
backend/app/routers/settings.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from fastapi import APIRouter
|
||||
router = APIRouter()
|
||||
2
backend/app/routers/tasks.py
Normal file
2
backend/app/routers/tasks.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from fastapi import APIRouter
|
||||
router = APIRouter()
|
||||
2
backend/app/routers/varieties.py
Normal file
2
backend/app/routers/varieties.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from fastapi import APIRouter
|
||||
router = APIRouter()
|
||||
31
backend/tests/conftest.py
Normal file
31
backend/tests/conftest.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlmodel import SQLModel, create_engine, Session
|
||||
from sqlmodel.pool import StaticPool
|
||||
|
||||
import app.models # noqa — force l'enregistrement des modèles
|
||||
from app.main import app
|
||||
from app.database import get_session
|
||||
|
||||
|
||||
@pytest.fixture(name="session")
|
||||
def session_fixture():
|
||||
engine = create_engine(
|
||||
"sqlite://",
|
||||
connect_args={"check_same_thread": False},
|
||||
poolclass=StaticPool,
|
||||
)
|
||||
SQLModel.metadata.create_all(engine)
|
||||
with Session(engine) as session:
|
||||
yield session
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def client_fixture(session: Session):
|
||||
def get_session_override():
|
||||
yield session
|
||||
|
||||
app.dependency_overrides[get_session] = get_session_override
|
||||
client = TestClient(app)
|
||||
yield client
|
||||
app.dependency_overrides.clear()
|
||||
54
backend/tests/test_gardens.py
Normal file
54
backend/tests/test_gardens.py
Normal file
@@ -0,0 +1,54 @@
|
||||
def test_health(client):
|
||||
r = client.get("/api/health")
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
def test_create_garden(client):
|
||||
r = client.post("/api/gardens", json={"nom": "Mon potager", "type": "plein_air"})
|
||||
assert r.status_code == 201
|
||||
data = r.json()
|
||||
assert data["nom"] == "Mon potager"
|
||||
assert data["id"] is not None
|
||||
|
||||
|
||||
def test_list_gardens(client):
|
||||
client.post("/api/gardens", json={"nom": "Jardin 1", "type": "plein_air"})
|
||||
client.post("/api/gardens", json={"nom": "Jardin 2", "type": "serre"})
|
||||
r = client.get("/api/gardens")
|
||||
assert r.status_code == 200
|
||||
assert len(r.json()) == 2
|
||||
|
||||
|
||||
def test_get_garden(client):
|
||||
r = client.post("/api/gardens", json={"nom": "Potager", "type": "plein_air"})
|
||||
id = r.json()["id"]
|
||||
r2 = client.get(f"/api/gardens/{id}")
|
||||
assert r2.status_code == 200
|
||||
assert r2.json()["nom"] == "Potager"
|
||||
|
||||
|
||||
def test_update_garden(client):
|
||||
r = client.post("/api/gardens", json={"nom": "Vieux nom", "type": "plein_air"})
|
||||
id = r.json()["id"]
|
||||
r2 = client.put(f"/api/gardens/{id}", json={"nom": "Nouveau nom", "type": "serre"})
|
||||
assert r2.status_code == 200
|
||||
assert r2.json()["nom"] == "Nouveau nom"
|
||||
|
||||
|
||||
def test_delete_garden(client):
|
||||
r = client.post("/api/gardens", json={"nom": "À supprimer", "type": "plein_air"})
|
||||
id = r.json()["id"]
|
||||
r2 = client.delete(f"/api/gardens/{id}")
|
||||
assert r2.status_code == 204
|
||||
r3 = client.get(f"/api/gardens/{id}")
|
||||
assert r3.status_code == 404
|
||||
|
||||
|
||||
def test_create_measurement(client):
|
||||
r = client.post("/api/gardens", json={"nom": "Potager", "type": "plein_air"})
|
||||
id = r.json()["id"]
|
||||
r2 = client.post(
|
||||
f"/api/gardens/{id}/measurements",
|
||||
json={"temp_air": 22.5, "humidite_air": 60.0},
|
||||
)
|
||||
assert r2.status_code == 201
|
||||
Reference in New Issue
Block a user