mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-10 11:59:23 +00:00
test: ReservationForm
This commit is contained in:
@ -23,7 +23,7 @@ class ReservableRoomController(ControllerBase):
|
|||||||
@route.get(
|
@route.get(
|
||||||
"",
|
"",
|
||||||
response=list[RoomSchema],
|
response=list[RoomSchema],
|
||||||
permissions=[HasPerm("reservation.viem_room")],
|
permissions=[HasPerm("reservation.view_room")],
|
||||||
url_name="fetch_reservable_rooms",
|
url_name="fetch_reservable_rooms",
|
||||||
)
|
)
|
||||||
def fetch_rooms(self, filters: Query[RoomFilterSchema]):
|
def fetch_rooms(self, filters: Query[RoomFilterSchema]):
|
||||||
@ -36,6 +36,7 @@ class ReservationSlotController(ControllerBase):
|
|||||||
"",
|
"",
|
||||||
response=PaginatedResponseSchema[SlotSchema],
|
response=PaginatedResponseSchema[SlotSchema],
|
||||||
permissions=[HasPerm("reservation.view_reservationslot")],
|
permissions=[HasPerm("reservation.view_reservationslot")],
|
||||||
|
url_name="fetch_reservation_slots",
|
||||||
)
|
)
|
||||||
@paginate(PageNumberPaginationExtra)
|
@paginate(PageNumberPaginationExtra)
|
||||||
def fetch_slots(self, filters: Query[SlotFilterSchema]):
|
def fetch_slots(self, filters: Query[SlotFilterSchema]):
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
from django import forms
|
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 club.widgets.ajax_select import AutoCompleteSelectClub
|
||||||
from core.models import User
|
from core.models import User
|
||||||
@ -43,6 +45,11 @@ class ReservationForm(forms.ModelForm):
|
|||||||
fields = ["room", "start_at", "end_at", "comment"]
|
fields = ["room", "start_at", "end_at", "comment"]
|
||||||
field_classes = {"start_at": FutureDateTimeField, "end_at": FutureDateTimeField}
|
field_classes = {"start_at": FutureDateTimeField, "end_at": FutureDateTimeField}
|
||||||
widgets = {"start_at": SelectDateTime(), "end_at": SelectDateTime()}
|
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):
|
def __init__(self, *args, author: User, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -109,6 +109,7 @@ class Migration(migrations.Migration):
|
|||||||
models.CheckConstraint(
|
models.CheckConstraint(
|
||||||
condition=models.Q(("end_at__gt", models.F("start_at"))),
|
condition=models.Q(("end_at__gt", models.F("start_at"))),
|
||||||
name="reservation_slot_end_after_start",
|
name="reservation_slot_end_after_start",
|
||||||
|
violation_error_code="start_after_end",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -51,10 +51,10 @@ class Room(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ReservationSlotQuerySet(models.QuerySet):
|
class ReservationSlotQuerySet(models.QuerySet):
|
||||||
def overlapping_with(self, other: ReservationSlot) -> Self:
|
def overlapping_with(self, slot: ReservationSlot) -> Self:
|
||||||
return self.filter(
|
return self.filter(
|
||||||
Q(start_at__lt=other.start_at, start_at__gt=other.start_at)
|
Q(start_at__lt=slot.start_at, end_at__gt=slot.start_at)
|
||||||
| Q(end_at__gt=other.start_at, end_at__lt=other.end_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"))
|
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("author"))
|
||||||
comment = models.TextField(_("comment"), blank=True, default="")
|
comment = models.TextField(_("comment"), blank=True, default="")
|
||||||
start_at = models.DateTimeField(_("slot start"), db_index=True)
|
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)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
objects = ReservationSlotQuerySet.as_manager()
|
objects = ReservationSlotQuerySet.as_manager()
|
||||||
@ -78,8 +78,9 @@ class ReservationSlot(models.Model):
|
|||||||
verbose_name_plural = _("reservation slots")
|
verbose_name_plural = _("reservation slots")
|
||||||
constraints = [
|
constraints = [
|
||||||
models.CheckConstraint(
|
models.CheckConstraint(
|
||||||
check=Q(end_at__gt=F("start_at")),
|
condition=Q(end_at__gt=F("start_at")),
|
||||||
name="reservation_slot_end_after_start",
|
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 ninja import FilterSchema, ModelSchema
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from club.schemas import ClubSchema
|
from club.schemas import SimpleClubSchema
|
||||||
from core.schemas import SimpleUserSchema
|
from core.schemas import SimpleUserSchema
|
||||||
from reservation.models import ReservationSlot, Room
|
from reservation.models import ReservationSlot, Room
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ class RoomSchema(ModelSchema):
|
|||||||
model = Room
|
model = Room
|
||||||
fields = ["id", "name", "description", "location"]
|
fields = ["id", "name", "description", "location"]
|
||||||
|
|
||||||
club: ClubSchema
|
club: SimpleClubSchema
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_location(obj: Room):
|
def resolve_location(obj: Room):
|
||||||
|
@ -17,9 +17,9 @@ import {
|
|||||||
} from "#openapi";
|
} from "#openapi";
|
||||||
|
|
||||||
import { paginated } from "#core:utils/api";
|
import { paginated } from "#core:utils/api";
|
||||||
|
import type { SlotSelectedEventArg } from "#reservation:reservation/types";
|
||||||
import interactionPlugin from "@fullcalendar/interaction";
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
|
import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
|
||||||
import type { SlotSelectedEventArg } from "#reservation:reservation/types";
|
|
||||||
|
|
||||||
@registerComponent("room-scheduler")
|
@registerComponent("room-scheduler")
|
||||||
export class RoomScheduler extends inheritHtmlElement("div") {
|
export class RoomScheduler extends inheritHtmlElement("div") {
|
||||||
|
@ -1,28 +1,42 @@
|
|||||||
import pytest
|
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 model_bakery import baker
|
||||||
from ninja_extra.testing import TestClient
|
|
||||||
from pytest_django.asserts import assertNumQueries
|
from pytest_django.asserts import assertNumQueries
|
||||||
|
|
||||||
from reservation.api import ReservableRoomController
|
from core.models import User
|
||||||
from reservation.models import Room
|
from reservation.models import Room
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestFetchRoom:
|
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)
|
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() == [
|
assert response.json() == [
|
||||||
{
|
{
|
||||||
"id": room.id,
|
"id": room.id,
|
||||||
"name": room.name,
|
"name": room.name,
|
||||||
"description": room.description,
|
"description": room.description,
|
||||||
"address": room.address,
|
"location": room.location,
|
||||||
"club": {"id": room.club.id, "name": room.club.name},
|
"club": {"id": room.club.id, "name": room.club.name},
|
||||||
}
|
}
|
||||||
for room in rooms
|
for room in rooms
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_nb_queries(self):
|
def test_nb_queries(self, client: Client, user: User):
|
||||||
with assertNumQueries(1):
|
client.force_login(user)
|
||||||
TestClient(ReservableRoomController).get("")
|
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