|
22 | 22 | from rest_framework.test import APIClient |
23 | 23 |
|
24 | 24 | from calendar_integration.factories import create_booking_policy |
25 | | -from calendar_integration.models import BookingPolicy, Calendar, CalendarGroup |
| 25 | +from calendar_integration.models import ( |
| 26 | + BookingPolicy, |
| 27 | + Calendar, |
| 28 | + CalendarGroup, |
| 29 | + CalendarOwnership, |
| 30 | +) |
26 | 31 | from organizations.models import Organization, OrganizationMembership, OrganizationRole |
27 | 32 | from users.factories import UserFactory |
28 | 33 |
|
@@ -606,3 +611,149 @@ def test_unauthenticated_delete_returns_401(self): |
606 | 611 | client = APIClient() |
607 | 612 | response = client.delete(_detail_url(1)) |
608 | 613 | assert response.status_code == status.HTTP_401_UNAUTHORIZED |
| 614 | + |
| 615 | + |
| 616 | +def _own(org: Organization, calendar: Calendar, membership: OrganizationMembership) -> None: |
| 617 | + """Link ``membership`` to ``calendar`` as an owner.""" |
| 618 | + CalendarOwnership.objects.create( |
| 619 | + organization=org, |
| 620 | + calendar=calendar, |
| 621 | + membership_user_id=membership.user_id, |
| 622 | + is_default=True, |
| 623 | + ) |
| 624 | + |
| 625 | + |
| 626 | +@pytest.mark.django_db |
| 627 | +class TestBookingPolicySelfService: |
| 628 | + """Non-admin members manage their OWN personal/calendar policies; group and |
| 629 | + organization-default policies stay admin-only.""" |
| 630 | + |
| 631 | + # -- create ------------------------------------------------------------ |
| 632 | + |
| 633 | + def test_member_can_create_policy_for_owned_calendar(self): |
| 634 | + org, membership = _make_org_with_member(is_admin=False) |
| 635 | + cal = _make_calendar(org) |
| 636 | + _own(org, cal, membership) |
| 637 | + client = _auth_client(membership) |
| 638 | + |
| 639 | + response = client.post(_list_url(), {"calendar": cal.pk, "lead_time_seconds": 60}) |
| 640 | + |
| 641 | + assert response.status_code == status.HTTP_201_CREATED, response.json() |
| 642 | + assert response.json()["lead_time_seconds"] == 60 |
| 643 | + |
| 644 | + def test_member_cannot_create_policy_for_unowned_calendar(self): |
| 645 | + org, membership = _make_org_with_member(is_admin=False) |
| 646 | + cal = _make_calendar(org) # not owned by this member |
| 647 | + client = _auth_client(membership) |
| 648 | + |
| 649 | + response = client.post(_list_url(), {"calendar": cal.pk, "lead_time_seconds": 60}) |
| 650 | + |
| 651 | + assert response.status_code == status.HTTP_403_FORBIDDEN |
| 652 | + |
| 653 | + def test_member_can_create_policy_for_own_membership(self): |
| 654 | + _, membership = _make_org_with_member(is_admin=False) |
| 655 | + client = _auth_client(membership) |
| 656 | + |
| 657 | + response = client.post( |
| 658 | + _list_url(), |
| 659 | + {"membership_user_id": membership.user_id, "lead_time_seconds": 120}, |
| 660 | + ) |
| 661 | + |
| 662 | + assert response.status_code == status.HTTP_201_CREATED, response.json() |
| 663 | + |
| 664 | + def test_member_cannot_create_policy_for_another_membership(self): |
| 665 | + org, membership = _make_org_with_member(is_admin=False) |
| 666 | + other_user = UserFactory().create_user() |
| 667 | + other_membership = OrganizationMembership.objects.create( |
| 668 | + user=other_user, organization=org, role=OrganizationRole.MEMBER, is_active=True |
| 669 | + ) |
| 670 | + client = _auth_client(membership) |
| 671 | + |
| 672 | + response = client.post( |
| 673 | + _list_url(), |
| 674 | + {"membership_user_id": other_membership.user_id, "lead_time_seconds": 120}, |
| 675 | + ) |
| 676 | + |
| 677 | + assert response.status_code == status.HTTP_403_FORBIDDEN |
| 678 | + |
| 679 | + def test_member_cannot_create_calendar_group_policy(self): |
| 680 | + org, membership = _make_org_with_member(is_admin=False) |
| 681 | + group = _make_group(org) |
| 682 | + client = _auth_client(membership) |
| 683 | + |
| 684 | + response = client.post(_list_url(), {"calendar_group": group.pk, "lead_time_seconds": 60}) |
| 685 | + |
| 686 | + assert response.status_code == status.HTTP_403_FORBIDDEN |
| 687 | + |
| 688 | + def test_member_cannot_create_org_default_policy(self): |
| 689 | + _, membership = _make_org_with_member(is_admin=False) |
| 690 | + client = _auth_client(membership) |
| 691 | + |
| 692 | + response = client.post( |
| 693 | + _list_url(), {"is_organization_default": True, "lead_time_seconds": 60} |
| 694 | + ) |
| 695 | + |
| 696 | + assert response.status_code == status.HTTP_403_FORBIDDEN |
| 697 | + |
| 698 | + def test_admin_can_create_group_and_org_default(self): |
| 699 | + org, membership = _make_org_with_member(is_admin=True) |
| 700 | + group = _make_group(org) |
| 701 | + client = _auth_client(membership) |
| 702 | + |
| 703 | + group_resp = client.post(_list_url(), {"calendar_group": group.pk, "lead_time_seconds": 60}) |
| 704 | + org_resp = client.post( |
| 705 | + _list_url(), {"is_organization_default": True, "lead_time_seconds": 60} |
| 706 | + ) |
| 707 | + |
| 708 | + assert group_resp.status_code == status.HTTP_201_CREATED, group_resp.json() |
| 709 | + assert org_resp.status_code == status.HTTP_201_CREATED, org_resp.json() |
| 710 | + |
| 711 | + # -- update ------------------------------------------------------------ |
| 712 | + |
| 713 | + def test_member_can_update_own_calendar_policy(self): |
| 714 | + org, membership = _make_org_with_member(is_admin=False) |
| 715 | + cal = _make_calendar(org) |
| 716 | + _own(org, cal, membership) |
| 717 | + policy = create_booking_policy(calendar=cal, lead_time_seconds=60) |
| 718 | + client = _auth_client(membership) |
| 719 | + |
| 720 | + response = client.patch(_detail_url(policy.pk), {"lead_time_seconds": 999}) |
| 721 | + |
| 722 | + assert response.status_code == status.HTTP_200_OK, response.json() |
| 723 | + assert response.json()["lead_time_seconds"] == 999 |
| 724 | + |
| 725 | + def test_member_cannot_update_group_policy(self): |
| 726 | + org, membership = _make_org_with_member(is_admin=False) |
| 727 | + group = _make_group(org) |
| 728 | + policy = create_booking_policy(calendar_group=group, lead_time_seconds=60) |
| 729 | + client = _auth_client(membership) |
| 730 | + |
| 731 | + response = client.patch(_detail_url(policy.pk), {"lead_time_seconds": 999}) |
| 732 | + |
| 733 | + assert response.status_code == status.HTTP_403_FORBIDDEN |
| 734 | + |
| 735 | + # -- delete ------------------------------------------------------------ |
| 736 | + |
| 737 | + def test_member_can_delete_own_calendar_policy(self): |
| 738 | + org, membership = _make_org_with_member(is_admin=False) |
| 739 | + cal = _make_calendar(org) |
| 740 | + _own(org, cal, membership) |
| 741 | + policy = create_booking_policy(calendar=cal) |
| 742 | + client = _auth_client(membership) |
| 743 | + |
| 744 | + response = client.delete(_detail_url(policy.pk)) |
| 745 | + |
| 746 | + assert response.status_code == status.HTTP_204_NO_CONTENT |
| 747 | + assert ( |
| 748 | + not BookingPolicy.objects.filter_by_organization(org.id).filter(pk=policy.pk).exists() |
| 749 | + ) |
| 750 | + |
| 751 | + def test_member_cannot_delete_org_default_policy(self): |
| 752 | + org, membership = _make_org_with_member(is_admin=False) |
| 753 | + policy = create_booking_policy(is_organization_default=True, organization=org) |
| 754 | + client = _auth_client(membership) |
| 755 | + |
| 756 | + response = client.delete(_detail_url(policy.pk)) |
| 757 | + |
| 758 | + assert response.status_code == status.HTTP_403_FORBIDDEN |
| 759 | + assert BookingPolicy.objects.filter_by_organization(org.id).filter(pk=policy.pk).exists() |
0 commit comments