spazio-solazzo/test/spazio_solazzo/accounts/user_test.exs
Víctor Martínez 2cbce8ec39
refactor: make phone number optional (#9)
This pull request makes phone numbers optional for both user registrations and bookings, and updates validation, database schema, and UI to reflect this change. The main focus is to ensure that users are no longer required to provide a phone number, and that the application gracefully handles cases where a phone number is absent.

**Database & Resource Model Updates**

* Made the `phone_number` attribute in the `users` table and the `customer_phone` attribute in the `bookings` table nullable, including migration and resource snapshot updates. [[1]](diffhunk://#diff-baa6aed3674c4d6cbbebeafb076662df02dc4c25231dbd9dc9c8f0534ed1a1bfR1-R29) [[2]](diffhunk://#diff-a401f66b2ae5bfb798eb1bc2221bfeeac943e258950c90d59570b0bae05d3664R1-R244) [[3]](diffhunk://#diff-0c1180d6f6abc19b5987c8703bdee9ef67905535202f950e8327c32bd5b89d8aR1-R82)
* Updated Ash resource definitions in `user.ex` and `booking.ex` to allow `phone_number` and `customer_phone` to be `nil`. [[1]](diffhunk://#diff-9194b9d80dce091f6dcb56f784217272ae160e35454c4b4ccc8850ad5ee06e38L152-R152) [[2]](diffhunk://#diff-4b1ddd6d86899f2144c69d142883b8719c755e32c03dbda5da2188208a5ad503L55-R55) [[3]](diffhunk://#diff-4b1ddd6d86899f2144c69d142883b8719c755e32c03dbda5da2188208a5ad503L170-R170)

**Validation & Parsing Logic**

* Renamed and refactored user registration field validation to `ParseRegistrationFields`, allowing phone numbers to be omitted and trimming input values. Empty phone numbers are now treated as absent rather than as errors. [[1]](diffhunk://#diff-8ffdd76e260e3cda6f0816c8e585ae76b993a90d2519c38185a5fe22b4b49e47L1-R1) [[2]](diffhunk://#diff-8ffdd76e260e3cda6f0816c8e585ae76b993a90d2519c38185a5fe22b4b49e47R14-R60)
* Updated the authentication callback logic to trim input values and omit the phone number parameter if it is blank.

**User Interface Improvements**

* Updated registration and booking forms to indicate that phone numbers are optional, removed the required attribute, and improved placeholder text. [[1]](diffhunk://#diff-f356eb84970d8c9ee6ff1992c297b0cae07bade37ff967c1e6e0de6f8b67081cL101-R115) [[2]](diffhunk://#diff-43c0e1f7a869ee5c43a911bc10dc80cbb265a8672340ef0fa7c1d3009c047f02L92-R92)
* Updated email templates and confirmation screens to display "N/A" or "-" when phone numbers are missing. [[1]](diffhunk://#diff-48468ef2d1bb2c33b5ffb40457b77532815c7faf1830932661f665bff58b2177R6-R11) [[2]](diffhunk://#diff-3f33187b4021450b481ce53fe13166addea582c627f2cfbc99c75c7ce5c34857L10-R10) [[3]](diffhunk://#diff-43c0e1f7a869ee5c43a911bc10dc80cbb265a8672340ef0fa7c1d3009c047f02L92-R92)

**Profile Management**

* Improved profile update flow to ensure the form reflects the latest user data after saving changes.
* Made the "Full Name" field explicitly required in the profile form UI.
2026-01-15 15:57:54 +01:00

207 lines
5.9 KiB
Elixir

defmodule SpazioSolazzo.Accounts.UserTest do
use ExUnit.Case, async: true
use SpazioSolazzo.DataCase
alias SpazioSolazzo.Accounts.User
alias SpazioSolazzo.Accounts
alias SpazioSolazzo.BookingSystem
alias SpazioSolazzo.BookingSystem.Booking
describe "update_profile" do
test "allows user to update their own name and phone_number" do
user = register_user("test@example.com")
{:ok, updated_user} =
Accounts.update_profile(user, "Updated Name", "+9876543210", actor: user)
assert updated_user.name == "Updated Name"
assert updated_user.phone_number == "+9876543210"
assert to_string(updated_user.email) == "test@example.com"
end
test "prevents user from updating another user's profile" do
user1 = register_user("user1@example.com")
user2 = register_user("user2@example.com")
result =
Accounts.update_profile(user2, "Hacker", "1235837", actor: user1)
assert {:error, %Ash.Error.Forbidden{}} = result
end
test "validates that name is present" do
user = register_user("test@example.com")
result =
Accounts.update_profile(user, "", "+9876543210", actor: user)
assert {:error, changeset} = result
assert Enum.any?(changeset.errors, fn error -> error.field == :name end)
end
end
describe "terminate_account with delete_history: false (anonymization)" do
test "deletes user but preserves bookings with nullified user_id" do
user = register_user("delete@example.com")
{_space, asset, time_slot} = create_booking_fixtures()
{:ok, booking1} =
BookingSystem.create_booking(
time_slot.id,
asset.id,
user.id,
Date.utc_today(),
"John Doe",
"john@example.com",
"+393627384027",
"test booking 1"
)
{:ok, booking2} =
BookingSystem.create_booking(
time_slot.id,
asset.id,
user.id,
Date.add(Date.utc_today(), 1),
"Jane Doe",
"jane@example.com",
"+393627384028",
"test booking 2"
)
booking1_id = booking1.id
booking2_id = booking2.id
:ok = Accounts.terminate_account(user, false, actor: user)
assert {:error, _} = Ash.get(User, user.id)
{:ok, preserved_booking1} = Ash.get(Booking, booking1_id)
{:ok, preserved_booking2} = Ash.get(Booking, booking2_id)
assert preserved_booking1.user_id == nil
assert preserved_booking2.user_id == nil
assert preserved_booking1.customer_name == "John Doe"
assert preserved_booking2.customer_name == "Jane Doe"
end
test "cancels future confirmed bookings before anonymizing" do
user = register_user("cancel@example.com")
{_space, asset, time_slot} = create_booking_fixtures()
future_date = Date.add(Date.utc_today(), 7)
{:ok, future_booking} =
BookingSystem.create_booking(
time_slot.id,
asset.id,
user.id,
future_date,
"Future User",
"future@example.com",
"+393627384029",
"future booking"
)
{:ok, past_booking} =
BookingSystem.create_booking(
time_slot.id,
asset.id,
user.id,
Date.add(Date.utc_today(), -7),
"Past User",
"past@example.com",
"+393627384030",
"past booking"
)
future_booking_id = future_booking.id
past_booking_id = past_booking.id
:ok = Accounts.terminate_account(user, false, actor: user)
{:ok, cancelled_booking} = Ash.get(Booking, future_booking_id, authorize?: false)
{:ok, preserved_past_booking} = Ash.get(Booking, past_booking_id, authorize?: false)
assert cancelled_booking.state == :cancelled
assert cancelled_booking.user_id == nil
assert preserved_past_booking.user_id == nil
end
end
describe "terminate_account with delete_history: true (hard delete)" do
test "deletes user and all associated bookings permanently" do
user = register_user("harddelete@example.com")
{_space, asset, time_slot} = create_booking_fixtures()
{:ok, booking1} =
BookingSystem.create_booking(
time_slot.id,
asset.id,
user.id,
Date.utc_today(),
"Delete Me",
"deleteme@example.com",
"+393627384031",
"test booking"
)
{:ok, booking2} =
BookingSystem.create_booking(
time_slot.id,
asset.id,
user.id,
Date.add(Date.utc_today(), 1),
"Delete Me Too",
"deletemetoo@example.com",
"+393627384032",
"test booking 2"
)
booking1_id = booking1.id
booking2_id = booking2.id
:ok = Accounts.terminate_account(user, true, actor: user)
assert {:error, _} = Ash.get(User, user.id)
assert {:error, _} = Ash.get(Booking, booking1_id, authorize?: false)
assert {:error, _} = Ash.get(Booking, booking2_id, authorize?: false)
end
end
describe "terminate_account authorization" do
test "prevents user from deleting another user's account" do
user1 = register_user("user1@example.com")
user2 = register_user("user2@example.com")
result = Accounts.terminate_account(user2, false, actor: user1)
assert {:error, %Ash.Error.Forbidden{}} = result
end
end
defp create_booking_fixtures do
unique_id = :erlang.unique_integer([:positive, :monotonic])
{:ok, space} =
BookingSystem.create_space(
"Test Space #{unique_id}",
"test-space-#{unique_id}",
"Test description"
)
{:ok, asset} = BookingSystem.create_asset("Test Asset", space.id)
{:ok, time_slot} =
BookingSystem.create_time_slot_template(
~T[09:00:00],
~T[18:00:00],
:monday,
space.id
)
{space, asset, time_slot}
end
end