Tests updated & minor environment notes for agents

This commit is contained in:
2026-02-28 12:36:47 +03:00
parent 411180e312
commit 7718f8ccfe
3 changed files with 53 additions and 21 deletions
+2 -1
View File
@@ -53,6 +53,7 @@ Build a reliable, maintainable salon booking platform with Django (backend) and
- Python is invoked as `python3`.
- A virtualenv is in use.
- DB: PostgreSQL in production, SQLite allowed for local dev.
- Backend tests must run with the venv active and `pytest-django` installed; run from `backend/` so `backend/pytest.ini` is picked up and `DJANGO_SETTINGS_MODULE` resolves.
## Collaboration Rules for Agents
- Dont delete or rewrite unrelated work.
@@ -62,4 +63,4 @@ Build a reliable, maintainable salon booking platform with Django (backend) and
# ExecPlans
When writing complex features or significant refactors, use an ExecPlan (as described in PLANS.md) from design to implementation. The active ExecPlan is `docs/execplans/arabic-localization.md`.
When writing complex features or significant refactors, use an ExecPlan (as described in PLANS.md) from design to implementation. The active ExecPlan is `docs/execplans/booking-integrity.md`.
+7 -3
View File
@@ -1,5 +1,6 @@
from datetime import timedelta
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
@@ -21,16 +22,19 @@ def validate_booking_request(service, staff, start_time, end_time):
if expected_end != end_time:
raise serializers.ValidationError({"end_time": _("End time must match service duration")})
start_local = timezone.localtime(start_time)
end_local = timezone.localtime(end_time)
availability_qs = StaffAvailability.objects.filter(
staff=staff,
day_of_week=start_time.weekday(),
day_of_week=start_local.weekday(),
is_active=True,
)
if availability_qs.exists():
within_window = availability_qs.filter(
start_time__lte=start_time.time(),
end_time__gte=end_time.time(),
start_time__lte=start_local.time(),
end_time__gte=end_local.time(),
).exists()
if not within_window:
raise serializers.ValidationError({"start_time": _("Booking is outside staff availability")})
@@ -3,6 +3,7 @@ from datetime import timedelta
import pytest
from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APIClient
from apps.accounts.models import User, UserRole
from apps.bookings.models import Booking, BookingStatus
@@ -46,15 +47,34 @@ def booking_payload(service, staff, start_time, end_time, notes=""):
def next_day_at(hour, minute=0):
now = timezone.now()
target = now + timedelta(days=1)
now_local = timezone.localtime(timezone.now())
target = now_local + timedelta(days=1)
return target.replace(hour=hour, minute=minute, second=0, microsecond=0)
@pytest.mark.django_db
def test_requires_staff_assignment(client, base_entities):
def test_requires_authentication(base_entities):
_, staff, service = base_entities
client = APIClient()
start_time = next_day_at(9)
end_time = start_time + timedelta(minutes=service.duration_minutes)
payload = booking_payload(service, staff, start_time, end_time)
response = client.post(
reverse("booking-list"),
payload,
content_type="application/json",
)
assert response.status_code == 401
@pytest.mark.django_db
def test_requires_staff_assignment(base_entities):
customer, staff, service = base_entities
client.force_login(customer)
client = APIClient()
client.force_authenticate(user=customer)
start_time = next_day_at(9)
end_time = start_time + timedelta(minutes=service.duration_minutes)
@@ -71,9 +91,10 @@ def test_requires_staff_assignment(client, base_entities):
@pytest.mark.django_db
def test_rejects_end_before_start(client, base_entities):
def test_rejects_end_before_start(base_entities):
customer, staff, service = base_entities
client.force_login(customer)
client = APIClient()
client.force_authenticate(user=customer)
start_time = next_day_at(9)
end_time = start_time - timedelta(minutes=service.duration_minutes)
@@ -90,9 +111,10 @@ def test_rejects_end_before_start(client, base_entities):
@pytest.mark.django_db
def test_rejects_duration_mismatch(client, base_entities):
def test_rejects_duration_mismatch(base_entities):
customer, staff, service = base_entities
client.force_login(customer)
client = APIClient()
client.force_authenticate(user=customer)
start_time = next_day_at(9)
end_time = start_time + timedelta(minutes=30)
@@ -109,16 +131,18 @@ def test_rejects_duration_mismatch(client, base_entities):
@pytest.mark.django_db
def test_rejects_outside_availability_when_defined(client, base_entities):
def test_rejects_outside_availability_when_defined(base_entities):
customer, staff, service = base_entities
client.force_login(customer)
client = APIClient()
client.force_authenticate(user=customer)
start_time = next_day_at(8)
end_time = start_time + timedelta(minutes=service.duration_minutes)
start_local = timezone.localtime(start_time)
StaffAvailability.objects.create(
staff=staff,
day_of_week=start_time.weekday(),
day_of_week=start_local.weekday(),
start_time=timezone.datetime(2000, 1, 1, 9, 0).time(),
end_time=timezone.datetime(2000, 1, 1, 17, 0).time(),
)
@@ -135,9 +159,10 @@ def test_rejects_outside_availability_when_defined(client, base_entities):
@pytest.mark.django_db
def test_allows_booking_without_availability_records(client, base_entities):
def test_allows_booking_without_availability_records(base_entities):
customer, staff, service = base_entities
client.force_login(customer)
client = APIClient()
client.force_authenticate(user=customer)
start_time = next_day_at(10)
end_time = start_time + timedelta(minutes=service.duration_minutes)
@@ -153,9 +178,10 @@ def test_allows_booking_without_availability_records(client, base_entities):
@pytest.mark.django_db
def test_rejects_overlapping_pending_or_confirmed_bookings(client, base_entities):
def test_rejects_overlapping_pending_or_confirmed_bookings(base_entities):
customer, staff, service = base_entities
client.force_login(customer)
client = APIClient()
client.force_authenticate(user=customer)
start_time = next_day_at(9)
end_time = start_time + timedelta(minutes=service.duration_minutes)
@@ -188,9 +214,10 @@ def test_rejects_overlapping_pending_or_confirmed_bookings(client, base_entities
@pytest.mark.django_db
def test_allows_overlap_with_cancelled_or_completed(client, base_entities):
def test_allows_overlap_with_cancelled_or_completed(base_entities):
customer, staff, service = base_entities
client.force_login(customer)
client = APIClient()
client.force_authenticate(user=customer)
start_time = next_day_at(9)
end_time = start_time + timedelta(minutes=service.duration_minutes)