|
3 | 3 | import pytest
|
4 | 4 | from tests.conftest import SetupError
|
5 | 5 | from utils.models import Role, Permission, ValidPermissions, User
|
6 |
| -from sqlmodel import Session, select |
| 6 | +from sqlmodel import Session, select, col |
7 | 7 | import re
|
8 | 8 | from main import app
|
9 | 9 |
|
@@ -479,50 +479,63 @@ def test_organization_page_role_creation_access(auth_client_owner, auth_client_a
|
479 | 479 | follow_redirects=False
|
480 | 480 | )
|
481 | 481 | assert owner_response.status_code == 200
|
482 |
| - assert "Create Role" in owner_response.text |
| 482 | + # Check for the button's modal trigger specifically |
| 483 | + assert 'data-bs-target="#createRoleModal"' in owner_response.text |
483 | 484 |
|
484 | 485 | # Admin should see role creation
|
485 | 486 | admin_response = auth_client_admin.get(
|
486 | 487 | app.url_path_for("read_organization", org_id=test_organization.id),
|
487 | 488 | follow_redirects=False
|
488 | 489 | )
|
489 | 490 | assert admin_response.status_code == 200
|
490 |
| - assert "Create Role" in admin_response.text |
| 491 | + # Check for the button's modal trigger specifically |
| 492 | + assert 'data-bs-target="#createRoleModal"' in admin_response.text |
491 | 493 |
|
492 | 494 | # Member should not see role creation
|
493 | 495 | member_response = auth_client_member.get(
|
494 | 496 | app.url_path_for("read_organization", org_id=test_organization.id),
|
495 | 497 | follow_redirects=False
|
496 | 498 | )
|
497 | 499 | assert member_response.status_code == 200
|
498 |
| - assert "Create Role" not in member_response.text |
| 500 | + # Check that the button's modal trigger is NOT present |
| 501 | + assert 'data-bs-target="#createRoleModal"' not in member_response.text |
499 | 502 |
|
500 | 503 |
|
501 |
| -def test_organization_page_role_edit_access(auth_client_owner, auth_client_admin, auth_client_member, test_organization): |
502 |
| - """Test that role editing UI elements are only shown to users with EDIT_ROLE permission""" |
503 |
| - # Owner should see role editing controls |
| 504 | +def test_organization_page_role_edit_access(auth_client_owner, auth_client_admin, auth_client_member, test_organization, session: Session): |
| 505 | + """Test that the 'Edit Role' button appears for custom roles only for users with permission.""" |
| 506 | + # Create a custom, editable role for the test |
| 507 | + custom_role = Role(name="Custom Role To Edit", organization_id=test_organization.id) |
| 508 | + session.add(custom_role) |
| 509 | + session.commit() |
| 510 | + session.refresh(custom_role) |
| 511 | + |
| 512 | + # Define the regex pattern for the specific custom role's edit button |
| 513 | + edit_button_pattern = fr'<button[^>]*data-bs-target="#editRoleModal{custom_role.id}"[^>]*>\s*Edit Role\s*</button>' |
| 514 | + |
| 515 | + # Owner should see the edit button for the custom role |
504 | 516 | owner_response = auth_client_owner.get(
|
505 | 517 | app.url_path_for("read_organization", org_id=test_organization.id),
|
506 | 518 | follow_redirects=False
|
507 | 519 | )
|
508 | 520 | assert owner_response.status_code == 200
|
509 |
| - assert "Edit Role" in owner_response.text |
| 521 | + assert re.search(edit_button_pattern, owner_response.text) is not None, "Owner should see edit button for custom role" |
510 | 522 |
|
511 |
| - # Admin should see role editing controls |
| 523 | + # Admin should see the edit button for the custom role |
512 | 524 | admin_response = auth_client_admin.get(
|
513 | 525 | app.url_path_for("read_organization", org_id=test_organization.id),
|
514 | 526 | follow_redirects=False
|
515 | 527 | )
|
516 | 528 | assert admin_response.status_code == 200
|
517 |
| - assert "Edit Role" in admin_response.text |
518 |
| - |
519 |
| - # Member should not see role editing controls |
| 529 | + assert re.search(edit_button_pattern, admin_response.text) is not None, "Admin should see edit button for custom role" |
| 530 | + |
| 531 | + # Member should not see *any* edit role button |
520 | 532 | member_response = auth_client_member.get(
|
521 | 533 | app.url_path_for("read_organization", org_id=test_organization.id),
|
522 | 534 | follow_redirects=False
|
523 | 535 | )
|
524 | 536 | assert member_response.status_code == 200
|
525 |
| - assert "Edit Role" not in member_response.text |
| 537 | + # Use a general pattern to ensure no edit buttons are present for the member |
| 538 | + assert re.search(r'<button[^>]*>\\s*Edit Role\\s*</button>', member_response.text) is None, "Member should not see any edit buttons" |
526 | 539 |
|
527 | 540 |
|
528 | 541 | def test_organization_page_role_delete_access(auth_client_owner, auth_client_admin, auth_client_member, test_organization, session: Session):
|
@@ -581,6 +594,153 @@ def test_organization_page_role_delete_access(auth_client_owner, auth_client_adm
|
581 | 594 | assert expected_member_delete_form not in member_response.text
|
582 | 595 |
|
583 | 596 |
|
| 597 | +def test_organization_page_always_shows_default_roles(auth_client_member, test_organization, session: Session): |
| 598 | + """ |
| 599 | + Test that Owner, Administrator, and Member roles are always visible |
| 600 | + on the organization page, even if no custom roles exist. |
| 601 | + The default Member client is used as it has the least privileges but should still see the roles. |
| 602 | + """ |
| 603 | + # Ensure no custom roles exist for this test |
| 604 | + custom_roles = session.exec( |
| 605 | + select(Role).where( |
| 606 | + Role.organization_id == test_organization.id, |
| 607 | + col(Role.name).not_in(["Owner", "Administrator", "Member"]) |
| 608 | + ) |
| 609 | + ).all() |
| 610 | + assert len(custom_roles) == 0, "Test setup failed: Custom roles exist unexpectedly." |
| 611 | + |
| 612 | + response = auth_client_member.get( |
| 613 | + app.url_path_for("read_organization", org_id=test_organization.id), |
| 614 | + follow_redirects=False |
| 615 | + ) |
| 616 | + assert response.status_code == 200 |
| 617 | + |
| 618 | + # Check if the default role names are present in the rendered table body |
| 619 | + # This implicitly checks if the table is rendered even without custom roles. |
| 620 | + assert "<td>Owner</td>" in response.text |
| 621 | + assert "<td>Administrator</td>" in response.text |
| 622 | + assert "<td>Member</td>" in response.text |
| 623 | + |
| 624 | + |
| 625 | +def test_organization_page_no_edit_for_default_roles(auth_client_owner, test_organization): |
| 626 | + """ |
| 627 | + Test that the 'Edit Role' button/modal trigger does not appear for default roles |
| 628 | + (Owner, Administrator, Member), even for the owner. |
| 629 | + """ |
| 630 | + response = auth_client_owner.get( |
| 631 | + app.url_path_for("read_organization", org_id=test_organization.id), |
| 632 | + follow_redirects=False |
| 633 | + ) |
| 634 | + assert response.status_code == 200 |
| 635 | + |
| 636 | + # Owner role (ID 1) should not have an edit button |
| 637 | + owner_edit_button_pattern = r'<button[^>]*data-bs-target="#editRoleModal1"[^>]*>\\s*Edit Role\\s*</button>' |
| 638 | + assert re.search(owner_edit_button_pattern, response.text) is None, "Edit button found for Owner role" |
| 639 | + |
| 640 | + # Administrator role (ID 2) should not have an edit button |
| 641 | + admin_edit_button_pattern = r'<button[^>]*data-bs-target="#editRoleModal2"[^>]*>\\s*Edit Role\\s*</button>' |
| 642 | + assert re.search(admin_edit_button_pattern, response.text) is None, "Edit button found for Administrator role" |
| 643 | + |
| 644 | + # Member role (ID 3) should not have an edit button |
| 645 | + member_edit_button_pattern = r'<button[^>]*data-bs-target="#editRoleModal3"[^>]*>\\s*Edit Role\\s*</button>' |
| 646 | + assert re.search(member_edit_button_pattern, response.text) is None, "Edit button found for Member role" |
| 647 | + |
| 648 | + # Check that the edit modals themselves are not generated for default roles |
| 649 | + assert 'id="editRoleModal1"' not in response.text, "Edit modal found for Owner role" |
| 650 | + assert 'id="editRoleModal2"' not in response.text, "Edit modal found for Administrator role" |
| 651 | + assert 'id="editRoleModal3"' not in response.text, "Edit modal found for Member role" |
| 652 | + |
| 653 | + |
| 654 | +def test_organization_page_no_delete_for_default_roles(auth_client_owner, test_organization): |
| 655 | + """ |
| 656 | + Test that the 'Delete Role' button/form does not appear for default roles |
| 657 | + (Owner, Administrator, Member), even for the owner. |
| 658 | + This re-verifies and centralizes checks from test_organization_page_role_delete_access. |
| 659 | + """ |
| 660 | + response = auth_client_owner.get( |
| 661 | + app.url_path_for("read_organization", org_id=test_organization.id), |
| 662 | + follow_redirects=False |
| 663 | + ) |
| 664 | + assert response.status_code == 200 |
| 665 | + |
| 666 | + # Check that delete forms are NOT present for default roles (IDs 1, 2, 3) |
| 667 | + owner_delete_form_pattern = r'<form[^>]*action="[^"]*?/roles/delete"[^>]*>.*?<input[^>]*name="id"[^>]*value="1"[^>]*>.*?</form>' |
| 668 | + assert re.search(owner_delete_form_pattern, response.text, re.DOTALL) is None, "Delete form found for Owner role" |
| 669 | + |
| 670 | + admin_delete_form_pattern = r'<form[^>]*action="[^"]*?/roles/delete"[^>]*>.*?<input[^>]*name="id"[^>]*value="2"[^>]*>.*?</form>' |
| 671 | + assert re.search(admin_delete_form_pattern, response.text, re.DOTALL) is None, "Delete form found for Administrator role" |
| 672 | + |
| 673 | + member_delete_form_pattern = r'<form[^>]*action="[^"]*?/roles/delete"[^>]*>.*?<input[^>]*name="id"[^>]*value="3"[^>]*>.*?</form>' |
| 674 | + assert re.search(member_delete_form_pattern, response.text, re.DOTALL) is None, "Delete form found for Member role" |
| 675 | + |
| 676 | + |
| 677 | +# --- API Endpoint Tests for Default Roles --- |
| 678 | + |
| 679 | +@pytest.mark.parametrize("default_role_name", ["Owner", "Administrator", "Member"]) |
| 680 | +def test_update_default_role_api_forbidden(auth_client_owner, test_organization, session: Session, default_role_name): |
| 681 | + """ |
| 682 | + Test that attempting to update default roles via the API is forbidden (403). |
| 683 | + Uses the owner client which has EDIT_ROLE permission. |
| 684 | + Finds the role ID dynamically based on the name for the specific organization. |
| 685 | + """ |
| 686 | + # Find the actual role ID for the current test organization instance |
| 687 | + default_role = session.exec( |
| 688 | + select(Role) |
| 689 | + .where(Role.organization_id == test_organization.id) |
| 690 | + .where(Role.name == default_role_name) |
| 691 | + ).first() |
| 692 | + |
| 693 | + if not default_role: |
| 694 | + pytest.fail(f"Default role '{default_role_name}' not found for organization {test_organization.id}") |
| 695 | + |
| 696 | + default_role_id = default_role.id |
| 697 | + |
| 698 | + response = auth_client_owner.post( |
| 699 | + app.url_path_for("update_role"), |
| 700 | + data={ |
| 701 | + "id": default_role_id, # Use dynamically fetched ID |
| 702 | + "name": f"Attempt to Change {default_role_name} Role", |
| 703 | + "organization_id": test_organization.id, |
| 704 | + "permissions": [ValidPermissions.EDIT_ROLE.value] # Arbitrary permission |
| 705 | + }, |
| 706 | + follow_redirects=False # We expect a direct 403, not a redirect |
| 707 | + ) |
| 708 | + |
| 709 | + assert response.status_code == 403 # Expecting Forbidden |
| 710 | + |
| 711 | + |
| 712 | +@pytest.mark.parametrize("default_role_name", ["Owner", "Administrator", "Member"]) |
| 713 | +def test_delete_default_role_api_forbidden(auth_client_owner, test_organization, session: Session, default_role_name): |
| 714 | + """ |
| 715 | + Test that attempting to delete default roles via the API is forbidden (403). |
| 716 | + Uses the owner client which has DELETE_ROLE permission. |
| 717 | + Finds the role ID dynamically based on the name for the specific organization. |
| 718 | + """ |
| 719 | + # Find the actual role ID for the current test organization instance |
| 720 | + default_role = session.exec( |
| 721 | + select(Role) |
| 722 | + .where(Role.organization_id == test_organization.id) |
| 723 | + .where(Role.name == default_role_name) |
| 724 | + ).first() |
| 725 | + |
| 726 | + if not default_role: |
| 727 | + pytest.fail(f"Default role '{default_role_name}' not found for organization {test_organization.id}") |
| 728 | + |
| 729 | + default_role_id = default_role.id |
| 730 | + print(default_role_id) |
| 731 | + |
| 732 | + response = auth_client_owner.post( |
| 733 | + app.url_path_for("delete_role"), |
| 734 | + data={ |
| 735 | + "id": default_role_id, # Use dynamically fetched ID |
| 736 | + "organization_id": test_organization.id |
| 737 | + }, |
| 738 | + follow_redirects=False # We expect a direct 403, not a redirect |
| 739 | + ) |
| 740 | + |
| 741 | + assert response.status_code == 403 # Expecting Forbidden |
| 742 | + |
| 743 | + |
584 | 744 | def test_create_role_form_modal(auth_client_owner, test_organization):
|
585 | 745 | """Test that the create role modal form contains all required elements"""
|
586 | 746 | response = auth_client_owner.get(
|
|
0 commit comments