mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-10 03:49:24 +00:00
test: ReservationForm
This commit is contained in:
@ -23,7 +23,7 @@ class ReservableRoomController(ControllerBase):
|
||||
@route.get(
|
||||
"",
|
||||
response=list[RoomSchema],
|
||||
permissions=[HasPerm("reservation.viem_room")],
|
||||
permissions=[HasPerm("reservation.view_room")],
|
||||
url_name="fetch_reservable_rooms",
|
||||
)
|
||||
def fetch_rooms(self, filters: Query[RoomFilterSchema]):
|
||||
@ -36,6 +36,7 @@ class ReservationSlotController(ControllerBase):
|
||||
"",
|
||||
response=PaginatedResponseSchema[SlotSchema],
|
||||
permissions=[HasPerm("reservation.view_reservationslot")],
|
||||
url_name="fetch_reservation_slots",
|
||||
)
|
||||
@paginate(PageNumberPaginationExtra)
|
||||
def fetch_slots(self, filters: Query[SlotFilterSchema]):
|
||||
|
@ -1,4 +1,6 @@
|
||||
from django import forms
|
||||
from django.core.exceptions import NON_FIELD_ERRORS
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from club.widgets.ajax_select import AutoCompleteSelectClub
|
||||
from core.models import User
|
||||
@ -43,6 +45,11 @@ class ReservationForm(forms.ModelForm):
|
||||
fields = ["room", "start_at", "end_at", "comment"]
|
||||
field_classes = {"start_at": FutureDateTimeField, "end_at": FutureDateTimeField}
|
||||
widgets = {"start_at": SelectDateTime(), "end_at": SelectDateTime()}
|
||||
error_messages = {
|
||||
NON_FIELD_ERRORS: {
|
||||
"start_after_end": _("The start must be set before the end")
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, *args, author: User, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -109,6 +109,7 @@ class Migration(migrations.Migration):
|
||||
models.CheckConstraint(
|
||||
condition=models.Q(("end_at__gt", models.F("start_at"))),
|
||||
name="reservation_slot_end_after_start",
|
||||
violation_error_code="start_after_end",
|
||||
)
|
||||
],
|
||||
},
|
||||
|
@ -51,10 +51,10 @@ class Room(models.Model):
|
||||
|
||||
|
||||
class ReservationSlotQuerySet(models.QuerySet):
|
||||
def overlapping_with(self, other: ReservationSlot) -> Self:
|
||||
def overlapping_with(self, slot: ReservationSlot) -> Self:
|
||||
return self.filter(
|
||||
Q(start_at__lt=other.start_at, start_at__gt=other.start_at)
|
||||
| Q(end_at__gt=other.start_at, end_at__lt=other.end_at)
|
||||
Q(start_at__lt=slot.start_at, end_at__gt=slot.start_at)
|
||||
| Q(start_at__lt=slot.end_at, end_at__gt=slot.end_at)
|
||||
)
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ class ReservationSlot(models.Model):
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("author"))
|
||||
comment = models.TextField(_("comment"), blank=True, default="")
|
||||
start_at = models.DateTimeField(_("slot start"), db_index=True)
|
||||
end_at = models.DateTimeField(verbose_name=_("slot end"))
|
||||
end_at = models.DateTimeField(_("slot end"))
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = ReservationSlotQuerySet.as_manager()
|
||||
@ -78,8 +78,9 @@ class ReservationSlot(models.Model):
|
||||
verbose_name_plural = _("reservation slots")
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
check=Q(end_at__gt=F("start_at")),
|
||||
condition=Q(end_at__gt=F("start_at")),
|
||||
name="reservation_slot_end_after_start",
|
||||
violation_error_code="start_after_end",
|
||||
)
|
||||
]
|
||||
|
||||
|
@ -3,7 +3,7 @@ from datetime import datetime
|
||||
from ninja import FilterSchema, ModelSchema
|
||||
from pydantic import Field
|
||||
|
||||
from club.schemas import ClubSchema
|
||||
from club.schemas import SimpleClubSchema
|
||||
from core.schemas import SimpleUserSchema
|
||||
from reservation.models import ReservationSlot, Room
|
||||
|
||||
@ -17,7 +17,7 @@ class RoomSchema(ModelSchema):
|
||||
model = Room
|
||||
fields = ["id", "name", "description", "location"]
|
||||
|
||||
club: ClubSchema
|
||||
club: SimpleClubSchema
|
||||
|
||||
@staticmethod
|
||||
def resolve_location(obj: Room):
|
||||
|
@ -17,9 +17,9 @@ import {
|
||||
} from "#openapi";
|
||||
|
||||
import { paginated } from "#core:utils/api";
|
||||
import type { SlotSelectedEventArg } from "#reservation:reservation/types";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
|
||||
import type { SlotSelectedEventArg } from "#reservation:reservation/types";
|
||||
|
||||
@registerComponent("room-scheduler")
|
||||
export class RoomScheduler extends inheritHtmlElement("div") {
|
||||
|
@ -1,28 +1,42 @@
|
||||
import pytest
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
from model_bakery import baker
|
||||
from ninja_extra.testing import TestClient
|
||||
from pytest_django.asserts import assertNumQueries
|
||||
|
||||
from reservation.api import ReservableRoomController
|
||||
from core.models import User
|
||||
from reservation.models import Room
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestFetchRoom:
|
||||
def test_fetch_simple(self):
|
||||
@pytest.fixture
|
||||
def user(self):
|
||||
return baker.make(
|
||||
User,
|
||||
user_permissions=[Permission.objects.get(codename="view_room")],
|
||||
)
|
||||
|
||||
def test_fetch_simple(self, client: Client, user: User):
|
||||
rooms = baker.make(Room, _quantity=3, _bulk_create=True)
|
||||
response = TestClient(ReservableRoomController).get("")
|
||||
client.force_login(user)
|
||||
response = client.get(reverse("api:fetch_reservable_rooms"))
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [
|
||||
{
|
||||
"id": room.id,
|
||||
"name": room.name,
|
||||
"description": room.description,
|
||||
"address": room.address,
|
||||
"location": room.location,
|
||||
"club": {"id": room.club.id, "name": room.club.name},
|
||||
}
|
||||
for room in rooms
|
||||
]
|
||||
|
||||
def test_nb_queries(self):
|
||||
with assertNumQueries(1):
|
||||
TestClient(ReservableRoomController).get("")
|
||||
def test_nb_queries(self, client: Client, user: User):
|
||||
client.force_login(user)
|
||||
with assertNumQueries(5):
|
||||
# 4 for authentication
|
||||
# 1 to fetch the actual data
|
||||
client.get(reverse("api:fetch_reservable_rooms"))
|
||||
|
111
reservation/tests/test_slot.py
Normal file
111
reservation/tests/test_slot.py
Normal file
@ -0,0 +1,111 @@
|
||||
from datetime import timedelta
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from model_bakery import baker
|
||||
from pytest_django.asserts import assertNumQueries
|
||||
|
||||
from core.models import User
|
||||
from reservation.forms import ReservationForm
|
||||
from reservation.models import ReservationSlot, Room
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestFetchReservationSlotsApi:
|
||||
@pytest.fixture
|
||||
def user(self):
|
||||
return baker.make(
|
||||
User,
|
||||
user_permissions=[Permission.objects.get(codename="view_reservationslot")],
|
||||
)
|
||||
|
||||
def test_fetch_simple(self, client: Client, user: User):
|
||||
slots = baker.make(ReservationSlot, _quantity=5, _bulk_create=True)
|
||||
client.force_login(user)
|
||||
response = client.get(reverse("api:fetch_reservation_slots"))
|
||||
assert response.json()["results"] == [
|
||||
{
|
||||
"id": slot.id,
|
||||
"room": slot.room_id,
|
||||
"comment": slot.comment,
|
||||
"start": slot.start_at.isoformat(timespec="milliseconds").replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"end": slot.end_at.isoformat(timespec="milliseconds").replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"author": {
|
||||
"id": slot.author.id,
|
||||
"first_name": slot.author.first_name,
|
||||
"last_name": slot.author.last_name,
|
||||
"nick_name": slot.author.nick_name,
|
||||
},
|
||||
}
|
||||
for slot in slots
|
||||
]
|
||||
|
||||
def test_nb_queries(self, client: Client, user: User):
|
||||
client.force_login(user)
|
||||
with assertNumQueries(5):
|
||||
# 4 for authentication
|
||||
# 1 to fetch the actual data
|
||||
client.get(reverse("api:fetch_reservation_slots"))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestReservationForm:
|
||||
def test_ok(self):
|
||||
start = now() + timedelta(hours=2)
|
||||
end = start + timedelta(hours=1)
|
||||
form = ReservationForm(
|
||||
author=baker.make(User),
|
||||
data={"room": baker.make(Room), "start_at": start, "end_at": end},
|
||||
)
|
||||
assert form.is_valid()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("start_date", "end_date", "errors"),
|
||||
[
|
||||
(
|
||||
now() - timedelta(hours=2),
|
||||
now() + timedelta(hours=2),
|
||||
{"start_at": ["Assurez-vous que cet horodatage est dans le futur"]},
|
||||
),
|
||||
(
|
||||
now() + timedelta(hours=3),
|
||||
now() + timedelta(hours=2),
|
||||
{"__all__": ["Le début doit être placé avant la fin"]},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_invalid_timedates(self, start_date, end_date, errors):
|
||||
form = ReservationForm(
|
||||
author=baker.make(User),
|
||||
data={"room": baker.make(Room), "start_at": start_date, "end_at": end_date},
|
||||
)
|
||||
assert not form.is_valid()
|
||||
assert form.errors == errors
|
||||
|
||||
def test_unavailable_room(self):
|
||||
room = baker.make(Room)
|
||||
baker.make(
|
||||
ReservationSlot,
|
||||
room=room,
|
||||
start_at=now() + timedelta(hours=2),
|
||||
end_at=now() + timedelta(hours=4),
|
||||
)
|
||||
form = ReservationForm(
|
||||
author=baker.make(User),
|
||||
data={
|
||||
"room": room,
|
||||
"start_at": now() + timedelta(hours=1),
|
||||
"end_at": now() + timedelta(hours=3),
|
||||
},
|
||||
)
|
||||
assert not form.is_valid()
|
||||
assert form.errors == {
|
||||
"__all__": ["Il y a déjà une réservation sur ce créneau."]
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import pytest
|
||||
from model_bakery import baker
|
||||
from ninja_extra.testing import TestClient
|
||||
from pytest_django.asserts import assertNumQueries
|
||||
|
||||
from reservation.api import ReservableRoomController, ReservationSlotController
|
||||
from reservation.models import ReservationSlot
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestFetchRoom:
|
||||
def test_fetch_simple(self):
|
||||
slots = baker.make(ReservationSlot, _quantity=5, _bulk_create=True)
|
||||
response = TestClient(ReservationSlotController).get("")
|
||||
assert response.json() == [
|
||||
{
|
||||
"id": slot.id,
|
||||
"room": slot.room_id,
|
||||
"comment": slot.comment,
|
||||
"nb_people": slot.nb_people,
|
||||
"start": slot.start_at.isoformat(timespec="milliseconds").replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"end": slot.end_at.isoformat(timespec="milliseconds").replace(
|
||||
"+00:00", "Z"
|
||||
),
|
||||
"author": {
|
||||
"id": slot.author.id,
|
||||
"first_name": slot.author.first_name,
|
||||
"last_name": slot.author.last_name,
|
||||
"nick_name": slot.author.nick_name,
|
||||
},
|
||||
}
|
||||
for slot in slots
|
||||
]
|
||||
|
||||
def test_nb_queries(self):
|
||||
with assertNumQueries(1):
|
||||
TestClient(ReservableRoomController).get("")
|
Reference in New Issue
Block a user