diff --git a/tests/routers/test_account.py b/tests/routers/test_account.py index 1459648..9004c90 100644 --- a/tests/routers/test_account.py +++ b/tests/routers/test_account.py @@ -285,7 +285,7 @@ def test_request_email_update_success(auth_client: TestClient, test_account: Acc ) assert response.status_code == 303 - assert "profile?email_update_requested=true" in response.headers["location"] + assert f"{app.url_path_for('read_profile')}?email_update_requested=true" in response.headers["location"] # Verify email was "sent" mock_resend_send.assert_called_once() @@ -350,7 +350,7 @@ def test_confirm_email_update_success(unauth_client: TestClient, session: Sessio ) assert response.status_code == 303 - assert "profile?email_updated=true" in response.headers["location"] + assert f"{app.url_path_for('read_profile')}?email_updated=true" in response.headers["location"] # Verify email was updated session.refresh(test_account) diff --git a/tests/routers/test_organization.py b/tests/routers/test_organization.py index ba74aaf..842758e 100644 --- a/tests/routers/test_organization.py +++ b/tests/routers/test_organization.py @@ -9,7 +9,7 @@ def test_create_organization_success(auth_client, session, test_user): """Test successful organization creation""" response = auth_client.post( - "/organizations/create", + app.url_path_for("create_organization"), data={"name": "New Test Organization"}, follow_redirects=False ) @@ -66,7 +66,7 @@ def test_create_organization_success(auth_client, session, test_user): def test_create_organization_empty_name(auth_client): """Test organization creation with empty name""" response = auth_client.post( - "/organizations/create", + app.url_path_for("create_organization"), data={"name": " "}, follow_redirects=True ) @@ -82,7 +82,7 @@ def test_create_organization_duplicate_name(auth_client, session, test_organizat org_count_before = len(session.exec(select(Organization)).all()) response = auth_client.post( - "/organizations/create", + app.url_path_for("create_organization"), data={"name": test_organization.name}, follow_redirects=False ) @@ -105,7 +105,7 @@ def test_create_organization_duplicate_name(auth_client, session, test_organizat def test_create_organization_unauthenticated(unauth_client): """Test organization creation without authentication""" response = unauth_client.post( - "/organizations/create", + app.url_path_for("create_organization"), data={"name": "Unauthorized Org"}, follow_redirects=False ) @@ -130,7 +130,7 @@ def test_update_organization_success( new_name = "Updated Organization Name" response = auth_client.post( - f"/organizations/update/{test_organization.id}", + app.url_path_for("update_organization", org_id=test_organization.id), data={"id": str(test_organization.id), "name": new_name}, follow_redirects=False ) @@ -156,7 +156,7 @@ def test_update_organization_unauthorized(auth_client, session, test_organizatio session.commit() response = auth_client.post( - f"/organizations/update/{test_organization.id}", + app.url_path_for("update_organization", org_id=test_organization.id), data={ "id": test_organization.id, "name": "Unauthorized Update" @@ -183,7 +183,7 @@ def test_update_organization_duplicate_name(auth_client, session, test_organizat session.commit() response = auth_client.post( - f"/organizations/update/{test_organization.id}", + app.url_path_for("update_organization", org_id=test_organization.id), data={ "id": test_organization.id, "name": "Existing Org" @@ -206,7 +206,7 @@ def test_update_organization_empty_name(auth_client, session, test_organization, session.commit() response = auth_client.post( - f"/organizations/update/{test_organization.id}", + app.url_path_for("update_organization", org_id=test_organization.id), data={ "id": test_organization.id, "name": " " @@ -221,7 +221,7 @@ def test_update_organization_empty_name(auth_client, session, test_organization, def test_update_organization_unauthenticated(unauth_client, test_organization): """Test organization update without authentication""" response = unauth_client.post( - f"/organizations/update/{test_organization.id}", + app.url_path_for("update_organization", org_id=test_organization.id), data={ "id": test_organization.id, "name": "Unauthorized Update" @@ -246,12 +246,12 @@ def test_delete_organization_success(auth_client, session, test_organization, te session.commit() response = auth_client.post( - f"/organizations/delete/{org_id}", + app.url_path_for("delete_organization", org_id=org_id), follow_redirects=False ) assert response.status_code == 303 # Redirect status code - assert "/profile" in response.headers["location"] + assert app.url_path_for("read_profile") in response.headers["location"] # Expire all objects in the session to force a refresh from the database session.expire_all() @@ -266,7 +266,7 @@ def test_delete_organization_unauthorized(auth_client_member, session, test_orga """Test organization deletion without proper permissions""" # Use auth_client_member, who belongs to the org but has no delete permission response = auth_client_member.post( - f"/organizations/delete/{test_organization.id}", + app.url_path_for("delete_organization", org_id=test_organization.id), follow_redirects=False ) @@ -280,7 +280,7 @@ def test_delete_organization_unauthorized(auth_client_member, session, test_orga def test_delete_organization_not_member(auth_client_non_member, session, test_organization): """Test organization deletion by non-member""" response = auth_client_non_member.post( - f"/organizations/delete/{test_organization.id}", + app.url_path_for("delete_organization", org_id=test_organization.id), follow_redirects=False ) @@ -294,7 +294,7 @@ def test_delete_organization_not_member(auth_client_non_member, session, test_or def test_delete_organization_unauthenticated(unauth_client, test_organization): """Test organization deletion without authentication""" response = unauth_client.post( - f"/organizations/delete/{test_organization.id}", + app.url_path_for("delete_organization", org_id=test_organization.id), follow_redirects=False ) @@ -320,7 +320,7 @@ def test_delete_organization_cascade(auth_client, session, test_organization, te session.commit() response = auth_client.post( - f"/organizations/delete/{org_id}", + app.url_path_for("delete_organization", org_id=org_id), follow_redirects=False ) @@ -341,7 +341,7 @@ def test_delete_organization_cascade(auth_client, session, test_organization, te def test_read_organization_as_owner(auth_client_owner, test_organization): """Test accessing organization page as an owner""" response = auth_client_owner.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) @@ -360,7 +360,7 @@ def test_read_organization_as_owner(auth_client_owner, test_organization): def test_read_organization_as_admin(auth_client_admin, test_organization): """Test accessing organization page as an admin""" response = auth_client_admin.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) @@ -379,7 +379,7 @@ def test_read_organization_as_admin(auth_client_admin, test_organization): def test_read_organization_as_member(auth_client_member, test_organization): """Test accessing organization page as a regular member""" response = auth_client_member.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) @@ -398,7 +398,7 @@ def test_read_organization_as_member(auth_client_member, test_organization): def test_read_organization_as_non_member(auth_client_non_member, test_organization): """Test accessing organization page as a non-member""" response = auth_client_non_member.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) @@ -410,7 +410,7 @@ def test_read_organization_as_non_member(auth_client_non_member, test_organizati def test_organization_page_displays_members_correctly(auth_client_owner, org_admin_user, org_member_user, test_organization): """Test that members and their roles are displayed correctly""" response = auth_client_owner.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) @@ -463,7 +463,7 @@ def test_empty_organization_displays_no_members_message(auth_client_owner, sessi session.commit() response = auth_client_owner.get( - f"/organizations/{empty_org.id}", + app.url_path_for("read_organization", org_id=empty_org.id), follow_redirects=False ) @@ -481,14 +481,14 @@ def test_invite_user_success(auth_client_owner, session, test_organization, non_ # Send invite response = auth_client_owner.post( - f"/organizations/invite/{test_organization.id}", + app.url_path_for("invite_member", org_id=test_organization.id), data={"email": non_member_user.account.email}, follow_redirects=False ) # Should redirect back to organization page assert response.status_code == 303 - assert f"/organizations/{test_organization.id}" in response.headers["location"] + assert app.url_path_for("read_organization", org_id=test_organization.id) in response.headers["location"] # Verify database state - user should now have the Member role session.refresh(non_member_user) @@ -508,7 +508,7 @@ def test_invite_user_success(auth_client_owner, session, test_organization, non_ def test_invite_nonexistent_user(auth_client_owner, test_organization): """Test inviting a user that doesn't exist in the system""" response = auth_client_owner.post( - f"/organizations/invite/{test_organization.id}", + app.url_path_for("invite_member", org_id=test_organization.id), data={"email": "nonexistent@example.com"}, follow_redirects=True ) @@ -521,7 +521,7 @@ def test_invite_nonexistent_user(auth_client_owner, test_organization): def test_invite_existing_member(auth_client_owner, test_organization, org_member_user): """Test inviting a user who is already a member""" response = auth_client_owner.post( - f"/organizations/invite/{test_organization.id}", + app.url_path_for("invite_member", org_id=test_organization.id), data={"email": org_member_user.account.email}, follow_redirects=True ) @@ -534,7 +534,7 @@ def test_invite_existing_member(auth_client_owner, test_organization, org_member def test_invite_without_permission(auth_client_member, test_organization, non_member_user): """Test inviting a user without having the INVITE_USER permission""" response = auth_client_member.post( - f"/organizations/invite/{test_organization.id}", + app.url_path_for("invite_member", org_id=test_organization.id), data={"email": non_member_user.account.email}, follow_redirects=True ) diff --git a/tests/routers/test_role.py b/tests/routers/test_role.py index 4ada493..0c3d779 100644 --- a/tests/routers/test_role.py +++ b/tests/routers/test_role.py @@ -5,6 +5,7 @@ from utils.models import Role, Permission, ValidPermissions, User from sqlmodel import Session, select import re +from main import app @pytest.fixture @@ -34,7 +35,7 @@ def admin_user(session: Session, test_user: User, test_organization): def test_create_role_success(auth_client, admin_user, test_organization, session: Session): """Test successful role creation""" response = auth_client.post( - "/roles/create", + app.url_path_for("create_role"), data={ "name": "Test Role", "organization_id": test_organization.id, @@ -44,6 +45,7 @@ def test_create_role_success(auth_client, admin_user, test_organization, session ) assert response.status_code == 303 + assert app.url_path_for("read_organization", org_id=test_organization.id) in response.headers["location"] # Verify role was created in database created_role = session.exec( @@ -62,7 +64,7 @@ def test_create_role_success(auth_client, admin_user, test_organization, session def test_create_role_unauthorized(auth_client, test_user, test_organization): """Test role creation without proper permissions""" response = auth_client.post( - "/roles/create", + app.url_path_for("create_role"), data={ "name": "Test Role", "organization_id": test_organization.id, @@ -86,7 +88,7 @@ def test_create_duplicate_role(auth_client, admin_user, test_organization, sessi # Attempt to create role with same name response = auth_client.post( - "/roles/create", + app.url_path_for("create_role"), data={ "name": "Existing Role", "organization_id": test_organization.id, @@ -101,7 +103,7 @@ def test_create_duplicate_role(auth_client, admin_user, test_organization, sessi def test_create_role_unauthenticated(unauth_client, test_organization): """Test role creation without authentication""" response = unauth_client.post( - "/roles/create", + app.url_path_for("create_role"), data={ "name": "Test Role", "organization_id": test_organization.id, @@ -168,7 +170,7 @@ def test_update_role_success(auth_client, editor_user, test_organization, sessio # Update the role using the /roles/update endpoint response = auth_client.post( - "/roles/update", + app.url_path_for("update_role"), data={ "id": existing_role.id, "name": "New Role Name", @@ -179,6 +181,7 @@ def test_update_role_success(auth_client, editor_user, test_organization, sessio ) assert response.status_code == 303 + assert app.url_path_for("read_organization", org_id=test_organization.id) in response.headers["location"] # Expire all objects in the session to force a refresh from the database session.expire_all() @@ -209,7 +212,7 @@ def test_update_role_unauthorized(auth_client, test_user, test_organization, ses session.refresh(some_role) response = auth_client.post( - "/roles/update", + app.url_path_for("update_role"), data={ "id": some_role.id, "name": "Attempted Update", @@ -228,7 +231,7 @@ def test_update_role_nonexistent(auth_client, editor_user, test_organization): A 404 (RoleNotFoundError) is expected. """ response = auth_client.post( - "/roles/update", + app.url_path_for("update_role"), data={ "id": 9999999, # A role ID that doesn't exist "name": "Nonexistent Role", @@ -254,7 +257,7 @@ def test_update_role_duplicate_name(auth_client, editor_user, test_organization, # Try to update 'role1' to have the same name as 'role2' response = auth_client.post( - "/roles/update", + app.url_path_for("update_role"), data={ "id": role1.id, "name": "Conflict Role", @@ -282,7 +285,7 @@ def test_update_role_invalid_permission(auth_client, editor_user, test_organizat # Provide an invalid permission string response = auth_client.post( - "/roles/update", + app.url_path_for("update_role"), data={ "id": role_to_update.id, "name": "Invalid Permission Test", @@ -311,7 +314,7 @@ def test_update_role_unauthenticated(unauth_client, test_organization, session: session.refresh(some_role) response = unauth_client.post( - "/roles/update", + app.url_path_for("update_role"), data={ "id": some_role.id, "name": "Should Not Succeed", @@ -362,7 +365,7 @@ def test_delete_role_success(auth_client, delete_role_user, test_organization, s role_id = role_to_delete.id response = auth_client.post( - "/roles/delete", + app.url_path_for("delete_role"), data={ "id": role_id, "organization_id": test_organization.id @@ -393,7 +396,7 @@ def test_delete_role_unauthorized(auth_client, test_user, test_organization, ses session.commit() response = auth_client.post( - "/roles/delete", + app.url_path_for("delete_role"), data={ "id": role.id, "organization_id": test_organization.id @@ -407,7 +410,7 @@ def test_delete_role_unauthorized(auth_client, test_user, test_organization, ses def test_delete_nonexistent_role(auth_client, delete_role_user, test_organization): """Test attempting to delete a role that doesn't exist""" response = auth_client.post( - "/roles/delete", + app.url_path_for("delete_role"), data={ "id": 99999, # Non-existent role ID "organization_id": test_organization.id @@ -433,7 +436,7 @@ def test_delete_role_with_users(auth_client, delete_role_user, test_organization session.commit() response = auth_client.post( - "/roles/delete", + app.url_path_for("delete_role"), data={ "id": role_with_users.id, "organization_id": test_organization.id @@ -455,7 +458,7 @@ def test_delete_role_unauthenticated(unauth_client, test_organization, session: session.commit() response = unauth_client.post( - "/roles/delete", + app.url_path_for("delete_role"), data={ "id": role.id, "organization_id": test_organization.id @@ -472,7 +475,7 @@ def test_organization_page_role_creation_access(auth_client_owner, auth_client_a """Test that role creation UI elements are only shown to users with CREATE_ROLE permission""" # Owner should see role creation owner_response = auth_client_owner.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert owner_response.status_code == 200 @@ -480,7 +483,7 @@ def test_organization_page_role_creation_access(auth_client_owner, auth_client_a # Admin should see role creation admin_response = auth_client_admin.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert admin_response.status_code == 200 @@ -488,7 +491,7 @@ def test_organization_page_role_creation_access(auth_client_owner, auth_client_a # Member should not see role creation member_response = auth_client_member.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert member_response.status_code == 200 @@ -499,7 +502,7 @@ def test_organization_page_role_edit_access(auth_client_owner, auth_client_admin """Test that role editing UI elements are only shown to users with EDIT_ROLE permission""" # Owner should see role editing controls owner_response = auth_client_owner.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert owner_response.status_code == 200 @@ -507,7 +510,7 @@ def test_organization_page_role_edit_access(auth_client_owner, auth_client_admin # Admin should see role editing controls admin_response = auth_client_admin.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert admin_response.status_code == 200 @@ -515,7 +518,7 @@ def test_organization_page_role_edit_access(auth_client_owner, auth_client_admin # Member should not see role editing controls member_response = auth_client_member.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert member_response.status_code == 200 @@ -535,44 +538,44 @@ def test_organization_page_role_delete_access(auth_client_owner, auth_client_adm # Owner should see the delete role form action because a custom role exists and they have permission owner_response = auth_client_owner.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert owner_response.status_code == 200 - expected_custom_delete_form = f'
\\s*\\s*\\s*\\s*
' + expected_custom_delete_form = f'
\\s*\\s*\\s*\\s*
' assert re.search(expected_custom_delete_form, owner_response.text) is not None # Admin should see the delete role form action admin_response = auth_client_admin.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert admin_response.status_code == 200 assert f'' in admin_response.text - assert 'action="http://testserver/roles/delete"' in admin_response.text + assert f'action="http://testserver{app.url_path_for('delete_role')}"' in admin_response.text # Member should *not* see the delete role form action member_response = auth_client_member.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert member_response.status_code == 200 assert f'' not in member_response.text - assert 'action="http://testserver/roles/delete"' not in member_response.text + assert f'action="http://testserver{app.url_path_for('delete_role')}"' not in member_response.text # Built-in roles should not have delete forms for anyone # Check that the delete form is NOT present for the built-in "Owner" role (hardcoded ID 1 in fixtures) - expected_owner_delete_form = f'
\\s*' # Check only for the form targeting owner role ID + expected_owner_delete_form = f'\\s*' # Check only for the form targeting owner role ID assert expected_owner_delete_form not in owner_response.text assert expected_owner_delete_form not in admin_response.text assert expected_owner_delete_form not in member_response.text # Check that the delete form is NOT present for built-in Administrator role - expected_admin_delete_form = f'\\s*' # Check only for the form targeting admin role ID + expected_admin_delete_form = f'\\s*' # Check only for the form targeting admin role ID assert expected_admin_delete_form not in owner_response.text assert expected_admin_delete_form not in admin_response.text assert expected_admin_delete_form not in member_response.text # Check that the delete form is NOT present for built-in Member role - expected_member_delete_form = f'\\s*' # Check only for the form targeting member role ID + expected_member_delete_form = f'\\s*' # Check only for the form targeting member role ID assert expected_member_delete_form not in owner_response.text assert expected_member_delete_form not in admin_response.text assert expected_member_delete_form not in member_response.text @@ -581,7 +584,7 @@ def test_organization_page_role_delete_access(auth_client_owner, auth_client_adm def test_create_role_form_modal(auth_client_owner, test_organization): """Test that the create role modal form contains all required elements""" response = auth_client_owner.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) @@ -589,7 +592,7 @@ def test_create_role_form_modal(auth_client_owner, test_organization): # Check for modal elements assert 'id="createRoleModal"' in response.text - assert 'action="http://testserver/roles/create"' in response.text + assert f'action="http://testserver{app.url_path_for('create_role')}"' in response.text assert 'method="POST"' in response.text or 'method="post"' in response.text assert 'name="name"' in response.text assert 'name="organization_id"' in response.text @@ -628,14 +631,14 @@ def test_edit_role_form_modal(auth_client_owner, session, test_organization): assert ValidPermissions.DELETE_ROLE not in role_permission_names, "DELETE_ROLE should not be in permissions before test" response = auth_client_owner.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert response.status_code == 200 # Check for modal elements assert f'id="editRoleModal{test_role.id}"' in response.text - assert 'action="http://testserver/roles/update"' in response.text + assert f'action="http://testserver{app.url_path_for('update_role')}"' in response.text assert 'method="POST"' in response.text or 'method="post"' in response.text assert 'name="name"' in response.text assert f'value="{test_role.name}"' in response.text @@ -673,14 +676,14 @@ def test_delete_role_form(auth_client_owner, session, test_organization): session.refresh(test_role) response = auth_client_owner.get( - f"/organizations/{test_organization.id}", + app.url_path_for("read_organization", org_id=test_organization.id), follow_redirects=False ) assert response.status_code == 200 # Check for delete form elements - assert 'action="http://testserver/roles/delete"' in response.text + assert f'action="http://testserver{app.url_path_for('delete_role')}"' in response.text assert 'method="POST"' in response.text or 'method="post"' in response.text assert 'name="id"' in response.text assert f'value="{test_role.id}"' in response.text diff --git a/tests/routers/test_user.py b/tests/routers/test_user.py index c9ba243..377046b 100644 --- a/tests/routers/test_user.py +++ b/tests/routers/test_user.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient from httpx import Response -from sqlmodel import Session, select +from sqlmodel import Session from unittest.mock import patch, MagicMock from tests.conftest import SetupError from main import app @@ -241,7 +241,7 @@ def test_profile_displays_multiple_organizations( session.commit() # Visit profile page - response = auth_client.get("/user/profile") + response = auth_client.get(app.url_path_for("read_profile")) assert response.status_code == 200 # Check that both organizations are displayed @@ -254,7 +254,7 @@ def test_profile_displays_organization_list( ): """Test that the profile page shows organizations in a macro-rendered list""" - response = auth_client_owner.get("/user/profile") + response = auth_client_owner.get(app.url_path_for("read_profile")) assert response.status_code == 200 # Find the entire Organizations card section using regex @@ -274,7 +274,7 @@ def test_profile_displays_organization_list( # Check that the organization name and link are rendered within this specific section assert test_organization.name in org_section_html, f"Organization name '{test_organization.name}' not found within the organizations card section" - assert f"/organizations/{test_organization.id}" in org_section_html, f"Organization link '/organizations/{test_organization.id}' not found within the organizations card section" + assert app.url_path_for("read_organization", org_id=test_organization.id) in org_section_html, f"Organization link '{app.url_path_for('read_organization', org_id=test_organization.id)}' not found within the organizations card section" def test_profile_no_organizations( @@ -286,7 +286,7 @@ def test_profile_no_organizations( session.commit() # Visit profile page - response = auth_client.get("/user/profile") + response = auth_client.get(app.url_path_for("read_profile")) assert response.status_code == 200 # Should show "no organizations" message