Add ruff linter/formatter for Python

- Add ruff as dev dependency
- Configure ruff in pyproject.toml with strict 88-char line limit
- Ignore B008 (FastAPI Depends pattern is standard)
- Allow longer lines in tests for readability
- Fix all lint issues in source files
- Add Makefile targets: lint-backend, format-backend, fix-backend
This commit is contained in:
counterweight 2025-12-21 21:54:26 +01:00
parent 69bc8413e0
commit 6c218130e9
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
31 changed files with 1234 additions and 876 deletions

View file

@ -3,7 +3,9 @@ Availability API Tests
Tests for the admin availability management endpoints.
"""
from datetime import date, time, timedelta
from datetime import date, timedelta
import pytest
@ -19,6 +21,7 @@ def in_days(n: int) -> date:
# Permission Tests
# =============================================================================
class TestAvailabilityPermissions:
"""Test that only admins can access availability endpoints."""
@ -44,7 +47,9 @@ class TestAvailabilityPermissions:
assert response.status_code == 200
@pytest.mark.asyncio
async def test_regular_user_cannot_get_availability(self, client_factory, regular_user):
async def test_regular_user_cannot_get_availability(
self, client_factory, regular_user
):
async with client_factory.create(cookies=regular_user["cookies"]) as client:
response = await client.get(
"/api/admin/availability",
@ -53,7 +58,9 @@ class TestAvailabilityPermissions:
assert response.status_code == 403
@pytest.mark.asyncio
async def test_regular_user_cannot_set_availability(self, client_factory, regular_user):
async def test_regular_user_cannot_set_availability(
self, client_factory, regular_user
):
async with client_factory.create(cookies=regular_user["cookies"]) as client:
response = await client.put(
"/api/admin/availability",
@ -88,6 +95,7 @@ class TestAvailabilityPermissions:
# Set Availability Tests
# =============================================================================
class TestSetAvailability:
"""Test setting availability for a date."""
@ -101,7 +109,7 @@ class TestSetAvailability:
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
},
)
assert response.status_code == 200
data = response.json()
assert data["date"] == str(tomorrow())
@ -122,13 +130,15 @@ class TestSetAvailability:
],
},
)
assert response.status_code == 200
data = response.json()
assert len(data["slots"]) == 2
@pytest.mark.asyncio
async def test_set_empty_slots_clears_availability(self, client_factory, admin_user):
async def test_set_empty_slots_clears_availability(
self, client_factory, admin_user
):
async with client_factory.create(cookies=admin_user["cookies"]) as client:
# First set some availability
await client.put(
@ -138,13 +148,13 @@ class TestSetAvailability:
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
},
)
# Then clear it
response = await client.put(
"/api/admin/availability",
json={"date": str(tomorrow()), "slots": []},
)
assert response.status_code == 200
data = response.json()
assert len(data["slots"]) == 0
@ -160,22 +170,22 @@ class TestSetAvailability:
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
},
)
# Replace with different slots
response = await client.put(
await client.put(
"/api/admin/availability",
json={
"date": str(tomorrow()),
"slots": [{"start_time": "14:00:00", "end_time": "16:00:00"}],
},
)
# Verify the replacement
get_response = await client.get(
"/api/admin/availability",
params={"from": str(tomorrow()), "to": str(tomorrow())},
)
data = get_response.json()
assert len(data["days"]) == 1
assert len(data["days"][0]["slots"]) == 1
@ -186,6 +196,7 @@ class TestSetAvailability:
# Validation Tests
# =============================================================================
class TestAvailabilityValidation:
"""Test validation rules for availability."""
@ -200,7 +211,7 @@ class TestAvailabilityValidation:
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
},
)
assert response.status_code == 400
assert "past" in response.json()["detail"].lower()
@ -214,7 +225,7 @@ class TestAvailabilityValidation:
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
},
)
assert response.status_code == 400
assert "past" in response.json()["detail"].lower()
@ -229,7 +240,7 @@ class TestAvailabilityValidation:
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
},
)
assert response.status_code == 400
assert "30" in response.json()["detail"]
@ -243,7 +254,7 @@ class TestAvailabilityValidation:
"slots": [{"start_time": "09:05:00", "end_time": "12:00:00"}],
},
)
assert response.status_code == 422 # Pydantic validation error
assert "15-minute" in response.json()["detail"][0]["msg"]
@ -257,7 +268,7 @@ class TestAvailabilityValidation:
"slots": [{"start_time": "12:00:00", "end_time": "09:00:00"}],
},
)
assert response.status_code == 400
assert "after" in response.json()["detail"].lower()
@ -274,7 +285,7 @@ class TestAvailabilityValidation:
],
},
)
assert response.status_code == 400
assert "overlap" in response.json()["detail"].lower()
@ -283,6 +294,7 @@ class TestAvailabilityValidation:
# Get Availability Tests
# =============================================================================
class TestGetAvailability:
"""Test retrieving availability."""
@ -293,7 +305,7 @@ class TestGetAvailability:
"/api/admin/availability",
params={"from": str(tomorrow()), "to": str(in_days(7))},
)
assert response.status_code == 200
data = response.json()
assert data["days"] == []
@ -310,13 +322,13 @@ class TestGetAvailability:
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
},
)
# Get range that includes all
response = await client.get(
"/api/admin/availability",
params={"from": str(in_days(1)), "to": str(in_days(3))},
)
assert response.status_code == 200
data = response.json()
assert len(data["days"]) == 3
@ -333,13 +345,13 @@ class TestGetAvailability:
"slots": [{"start_time": "09:00:00", "end_time": "12:00:00"}],
},
)
# Get only a subset
response = await client.get(
"/api/admin/availability",
params={"from": str(in_days(2)), "to": str(in_days(4))},
)
assert response.status_code == 200
data = response.json()
assert len(data["days"]) == 3
@ -351,7 +363,7 @@ class TestGetAvailability:
"/api/admin/availability",
params={"from": str(in_days(7)), "to": str(in_days(1))},
)
assert response.status_code == 400
assert "before" in response.json()["detail"].lower()
@ -360,6 +372,7 @@ class TestGetAvailability:
# Copy Availability Tests
# =============================================================================
class TestCopyAvailability:
"""Test copying availability from one day to others."""
@ -377,7 +390,7 @@ class TestCopyAvailability:
],
},
)
# Copy to another day
response = await client.post(
"/api/admin/availability/copy",
@ -386,7 +399,7 @@ class TestCopyAvailability:
"target_dates": [str(in_days(2))],
},
)
assert response.status_code == 200
data = response.json()
assert len(data["days"]) == 1
@ -404,7 +417,7 @@ class TestCopyAvailability:
"slots": [{"start_time": "10:00:00", "end_time": "11:00:00"}],
},
)
# Copy to multiple days
response = await client.post(
"/api/admin/availability/copy",
@ -413,7 +426,7 @@ class TestCopyAvailability:
"target_dates": [str(in_days(2)), str(in_days(3)), str(in_days(4))],
},
)
assert response.status_code == 200
data = response.json()
assert len(data["days"]) == 3
@ -429,7 +442,7 @@ class TestCopyAvailability:
"slots": [{"start_time": "08:00:00", "end_time": "09:00:00"}],
},
)
# Set source availability
await client.put(
"/api/admin/availability",
@ -438,7 +451,7 @@ class TestCopyAvailability:
"slots": [{"start_time": "14:00:00", "end_time": "15:00:00"}],
},
)
# Copy (should replace)
await client.post(
"/api/admin/availability/copy",
@ -447,13 +460,13 @@ class TestCopyAvailability:
"target_dates": [str(in_days(2))],
},
)
# Verify target was replaced
response = await client.get(
"/api/admin/availability",
params={"from": str(in_days(2)), "to": str(in_days(2))},
)
data = response.json()
assert len(data["days"]) == 1
assert len(data["days"][0]["slots"]) == 1
@ -469,7 +482,7 @@ class TestCopyAvailability:
"target_dates": [str(in_days(2))],
},
)
assert response.status_code == 400
assert "no availability" in response.json()["detail"].lower()
@ -484,7 +497,7 @@ class TestCopyAvailability:
"slots": [{"start_time": "09:00:00", "end_time": "10:00:00"}],
},
)
# Copy including self in targets
response = await client.post(
"/api/admin/availability/copy",
@ -493,7 +506,7 @@ class TestCopyAvailability:
"target_dates": [str(in_days(1)), str(in_days(2))],
},
)
assert response.status_code == 200
data = response.json()
# Should only have copied to day 2, not day 1 (self)
@ -511,7 +524,7 @@ class TestCopyAvailability:
"slots": [{"start_time": "09:00:00", "end_time": "10:00:00"}],
},
)
# Try to copy to a date beyond 30 days
response = await client.post(
"/api/admin/availability/copy",
@ -520,7 +533,7 @@ class TestCopyAvailability:
"target_dates": [str(in_days(31))],
},
)
assert response.status_code == 400
assert "30" in response.json()["detail"]
@ -535,6 +548,6 @@ class TestCopyAvailability:
"target_dates": [str(in_days(1))],
},
)
assert response.status_code == 400
assert "past" in response.json()["detail"].lower()