spazio-solazzo/test/spazio_solazzo/booking_system/booking_test.exs

738 lines
20 KiB
Elixir

defmodule SpazioSolazzo.BookingSystem.BookingTest do
use ExUnit.Case, async: true
use SpazioSolazzo.DataCase
alias SpazioSolazzo.BookingSystem
setup do
{:ok, space} =
BookingSystem.create_space(
"Test Space",
"test-space",
"Test description",
2
)
user = register_user("testuser@example.com", "Test User")
date = Date.add(Date.utc_today(), 7)
%{space: space, user: user, date: date}
end
describe "create_booking" do
test "creates a booking request successfully", %{space: space, date: date} do
assert {:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
"+39 1234567890",
"Test booking"
)
assert booking.space_id == space.id
assert booking.user_id == nil
assert booking.date == date
assert booking.start_time == ~T[09:00:00]
assert booking.end_time == ~T[10:00:00]
assert booking.customer_name == "John Doe"
assert booking.customer_email == "john@example.com"
assert booking.customer_phone == "+39 1234567890"
assert booking.customer_comment == "Test booking"
assert booking.state == :requested
end
test "creates booking with authenticated user", %{space: space, user: user, date: date} do
assert {:ok, booking} =
BookingSystem.create_booking(
space.id,
user.id,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
user.email,
"+39 1234567890",
""
)
assert booking.user_id == user.id
assert to_string(booking.customer_email) == to_string(user.email)
end
test "rejects booking with end time before start time", %{space: space, date: date} do
assert {:error, error} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[10:00:00],
~T[09:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
error_messages = Ash.Error.error_descriptions(error)
assert String.contains?(error_messages, "must be after start_time")
end
test "rejects booking in the past", %{space: space} do
past_date = Date.add(Date.utc_today(), -1)
assert {:error, error} =
BookingSystem.create_booking(
space.id,
nil,
past_date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
error_messages = Ash.Error.error_descriptions(error)
assert String.contains?(error_messages, "cannot be in the past")
end
test "allows booking for today", %{space: space} do
today = Date.utc_today()
assert {:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
today,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
assert booking.date == today
end
test "requires customer name and email", %{space: space, date: date} do
assert {:error, _error} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"",
"",
"",
""
)
end
test "phone number is optional", %{space: space, date: date} do
assert {:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
assert booking.customer_phone == nil || booking.customer_phone == ""
end
end
describe "approve_booking/1" do
test "approves a pending booking", %{space: space, date: date} do
{:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
assert booking.state == :requested
{:ok, approved_booking} = BookingSystem.approve_booking(booking.id)
assert approved_booking.state == :accepted
assert approved_booking.id == booking.id
end
test "cannot approve already approved booking", %{space: space, date: date} do
{:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
{:ok, _} = BookingSystem.approve_booking(booking.id)
assert {:error, error} = BookingSystem.approve_booking(booking.id)
error_messages = Ash.Error.error_descriptions(error)
assert String.contains?(error_messages, "no matching transition")
end
test "cannot approve cancelled booking", %{space: space, date: date} do
{:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
{:ok, _} = BookingSystem.cancel_booking(booking.id, "Test cancellation")
assert {:error, error} = BookingSystem.approve_booking(booking.id)
error_messages = Ash.Error.error_descriptions(error)
assert String.contains?(error_messages, "no matching transition")
end
end
describe "cancel_booking/1" do
test "cancels a pending booking", %{space: space, date: date} do
{:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
{:ok, cancelled_booking} = BookingSystem.cancel_booking(booking.id, "Test cancellation")
assert cancelled_booking.state == :cancelled
assert cancelled_booking.id == booking.id
end
test "cancels an approved booking", %{space: space, date: date} do
{:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
{:ok, _} = BookingSystem.approve_booking(booking.id)
{:ok, cancelled_booking} = BookingSystem.cancel_booking(booking.id, "Test cancellation")
assert cancelled_booking.state == :cancelled
end
test "cannot cancel already cancelled booking", %{space: space, date: date} do
{:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"John Doe",
"john@example.com",
nil,
nil
)
{:ok, _} = BookingSystem.cancel_booking(booking.id, "Test cancellation")
assert {:error, error} = BookingSystem.cancel_booking(booking.id, "Test cancellation")
error_messages = Ash.Error.error_descriptions(error)
assert String.contains?(error_messages, "no matching transition")
end
end
describe "search_bookings/5 for accepted bookings" do
test "returns only approved bookings for specific date", %{space: space, date: date} do
{:ok, approved1} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"User 1",
"user1@example.com",
"",
""
)
{:ok, _} = BookingSystem.approve_booking(approved1.id)
{:ok, approved2} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[10:00:00],
~T[11:00:00],
"User 2",
"user2@example.com",
"",
""
)
{:ok, _} = BookingSystem.approve_booking(approved2.id)
{:ok, _pending} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[11:00:00],
~T[12:00:00],
"User 3",
"user3@example.com",
"",
""
)
start_datetime = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
end_datetime = DateTime.new!(Date.add(date, 1), ~T[00:00:00], "Etc/UTC")
{:ok, bookings} =
BookingSystem.search_bookings(
space.id,
start_datetime,
end_datetime,
[:accepted],
nil
)
assert length(bookings) == 2
assert Enum.all?(bookings, &(&1.state == :accepted))
end
test "does not return cancelled bookings", %{space: space, date: date} do
{:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"User 1",
"user1@example.com",
"",
""
)
{:ok, _} = BookingSystem.approve_booking(booking.id)
{:ok, _} = BookingSystem.cancel_booking(booking.id, "Test cancellation")
start_datetime = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
end_datetime = DateTime.new!(Date.add(date, 1), ~T[00:00:00], "Etc/UTC")
{:ok, bookings} =
BookingSystem.search_bookings(
space.id,
start_datetime,
end_datetime,
[:accepted],
nil
)
assert bookings == []
end
test "only returns bookings for specified date", %{space: space, date: date} do
other_date = Date.add(date, 1)
{:ok, booking1} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"User 1",
"user1@example.com",
"",
""
)
{:ok, _} = BookingSystem.approve_booking(booking1.id)
{:ok, booking2} =
BookingSystem.create_booking(
space.id,
nil,
other_date,
~T[09:00:00],
~T[10:00:00],
"User 2",
"user2@example.com",
"",
""
)
{:ok, _} = BookingSystem.approve_booking(booking2.id)
start_datetime = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
end_datetime = DateTime.new!(Date.add(date, 1), ~T[00:00:00], "Etc/UTC")
{:ok, bookings} =
BookingSystem.search_bookings(
space.id,
start_datetime,
end_datetime,
[:accepted],
nil
)
assert length(bookings) == 1
assert hd(bookings).date == date
end
test "only returns bookings for specified space", %{space: space, date: date} do
{:ok, other_space} =
BookingSystem.create_space(
"Other Space",
"other-space",
"Other description",
5
)
{:ok, booking} =
BookingSystem.create_booking(
other_space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"User 1",
"user1@example.com",
"",
""
)
{:ok, _} = BookingSystem.approve_booking(booking.id)
start_datetime = DateTime.new!(date, ~T[00:00:00], "Etc/UTC")
end_datetime = DateTime.new!(Date.add(date, 1), ~T[00:00:00], "Etc/UTC")
{:ok, bookings} =
BookingSystem.search_bookings(
space.id,
start_datetime,
end_datetime,
[:accepted],
nil
)
assert bookings == []
end
end
describe "count pending requests" do
test "returns only pending bookings", %{space: space, date: date} do
{:ok, _pending1} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"User 1",
"user1@example.com",
"",
""
)
{:ok, approved} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[10:00:00],
~T[11:00:00],
"User 2",
"user2@example.com",
"",
""
)
{:ok, _} = BookingSystem.approve_booking(approved.id)
{:ok, cancelled} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[11:00:00],
~T[12:00:00],
"User 3",
"user3@example.com",
"",
""
)
{:ok, _} = BookingSystem.cancel_booking(cancelled.id, "Test cancellation")
{:ok, count} =
Ash.count(SpazioSolazzo.BookingSystem.Booking,
query: [filter: [state: :requested]]
)
assert count == 1
end
test "returns zero when no pending requests", %{space: space, date: date} do
{:ok, booking} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"User 1",
"user1@example.com",
"",
""
)
{:ok, _} = BookingSystem.approve_booking(booking.id)
{:ok, count} =
Ash.count(SpazioSolazzo.BookingSystem.Booking,
query: [filter: [state: :requested]]
)
assert count == 0
end
test "counts pending requests across multiple spaces", %{space: space, date: date} do
{:ok, other_space} =
BookingSystem.create_space(
"Other Space",
"other-space-pending",
"Other description",
5
)
{:ok, _pending1} =
BookingSystem.create_booking(
space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"User 1",
"user1@example.com",
"",
""
)
{:ok, _pending2} =
BookingSystem.create_booking(
other_space.id,
nil,
date,
~T[09:00:00],
~T[10:00:00],
"User 2",
"user2@example.com",
"",
""
)
{:ok, count} =
Ash.count(SpazioSolazzo.BookingSystem.Booking,
query: [filter: [state: :requested]]
)
assert count == 2
end
end
describe "create_walk_in/7" do
test "creates a walk-in booking with accepted state", %{space: space} do
start_datetime = DateTime.utc_now() |> DateTime.add(1, :hour)
end_datetime = DateTime.add(start_datetime, 2, :hour)
assert {:ok, booking} =
BookingSystem.create_walk_in(
space.id,
start_datetime,
end_datetime,
"Walk-in Customer",
"walkin@example.com",
"+39 1234567890"
)
assert booking.space_id == space.id
assert booking.customer_name == "Walk-in Customer"
assert booking.customer_email == "walkin@example.com"
assert booking.customer_phone == "+39 1234567890"
assert booking.state == :accepted
assert booking.date == DateTime.to_date(start_datetime)
# Compare times ignoring microseconds
expected_start = DateTime.to_time(start_datetime)
expected_end = DateTime.to_time(end_datetime)
assert booking.start_time.hour == expected_start.hour
assert booking.start_time.minute == expected_start.minute
assert booking.start_time.second == expected_start.second
assert booking.end_time.hour == expected_end.hour
assert booking.end_time.minute == expected_end.minute
assert booking.end_time.second == expected_end.second
end
test "creates walk-in without optional fields", %{space: space} do
start_datetime = DateTime.utc_now() |> DateTime.add(1, :hour)
end_datetime = DateTime.add(start_datetime, 2, :hour)
assert {:ok, booking} =
BookingSystem.create_walk_in(
space.id,
start_datetime,
end_datetime,
"Walk-in Customer",
"walkin@example.com",
nil
)
assert booking.customer_phone == nil
assert booking.customer_comment == nil
assert booking.state == :accepted
end
test "rejects walk-in with end datetime before start datetime", %{space: space} do
start_datetime = DateTime.utc_now() |> DateTime.add(2, :hour)
end_datetime = DateTime.add(start_datetime, -1, :hour)
assert {:error, error} =
BookingSystem.create_walk_in(
space.id,
start_datetime,
end_datetime,
"Walk-in Customer",
"walkin@example.com",
nil
)
error_messages = Ash.Error.error_descriptions(error)
assert String.contains?(error_messages, "must be after start_datetime")
end
test "rejects walk-in with end time in the past", %{space: space} do
start_datetime = DateTime.utc_now() |> DateTime.add(-2, :hour)
end_datetime = DateTime.utc_now() |> DateTime.add(-1, :hour)
assert {:error, error} =
BookingSystem.create_walk_in(
space.id,
start_datetime,
end_datetime,
"Walk-in Customer",
"walkin@example.com",
nil
)
error_messages = Ash.Error.error_descriptions(error)
assert String.contains?(error_messages, "cannot be in the past")
end
test "allows walk-in with start time in the past but end time in the future", %{space: space} do
start_datetime = DateTime.utc_now() |> DateTime.add(-1, :hour)
end_datetime = DateTime.utc_now() |> DateTime.add(2, :hour)
assert {:ok, booking} =
BookingSystem.create_walk_in(
space.id,
start_datetime,
end_datetime,
"Walk-in Customer",
"walkin@example.com",
nil
)
assert booking.state == :accepted
assert booking.customer_name == "Walk-in Customer"
end
test "rejects walk-in with invalid email", %{space: space} do
start_datetime = DateTime.utc_now() |> DateTime.add(1, :hour)
end_datetime = DateTime.add(start_datetime, 2, :hour)
assert {:error, error} =
BookingSystem.create_walk_in(
space.id,
start_datetime,
end_datetime,
"Walk-in Customer",
"invalid-email",
nil
)
error_messages = Ash.Error.error_descriptions(error)
assert String.contains?(error_messages, "must be a valid email")
end
test "converts datetime to date and time correctly", %{space: space} do
date = Date.add(Date.utc_today(), 1)
start_time = ~T[14:30:00]
end_time = ~T[16:45:00]
start_datetime = DateTime.new!(date, start_time, "Etc/UTC")
end_datetime = DateTime.new!(date, end_time, "Etc/UTC")
assert {:ok, booking} =
BookingSystem.create_walk_in(
space.id,
start_datetime,
end_datetime,
"Walk-in Customer",
"walkin@example.com",
nil
)
assert booking.date == date
assert booking.start_time == start_time
assert booking.end_time == end_time
end
end
end