| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- import uuid
- import pytest
- from httpx import AsyncClient
- from sqlalchemy import select
- from sqlalchemy.orm import Session
- from app.main import app
- from app.models import GlobalRole, Organization, OrgRole, Project, Slot, Volunteer
- from app.tests.conftest import default_organization_id, default_project_id, default_project_name
- pytestmark = pytest.mark.asyncio
- ALL_ORG_ROLES = [OrgRole.ORG_ADMIN, OrgRole.RESPO_BENEVOLE, OrgRole.RESPO_COMMISSION]
- class TestListProject:
- async def test_requires_auth(self, client: AsyncClient):
- response = await client.get(app.url_path_for("list_project"))
- assert response.status_code == 401
- @pytest.mark.parametrize("role", ALL_ORG_ROLES)
- async def test_org_member_sees_their_project(
- self, client: AsyncClient, default_project: Project, make_org_user, role
- ):
- _, headers = make_org_user(role=role)
- response = await client.get(app.url_path_for("list_project"), headers=headers)
- assert response.status_code == 200
- data = response.json()
- assert len(data) == 1
- assert data[0]["id"] == default_project_id
- async def test_super_admin_sees_all_orgs(
- self, client: AsyncClient, default_project: Project, make_org_user
- ):
- _, headers = make_org_user(role=None, global_role=GlobalRole.SUPER_ADMIN)
- response = await client.get(app.url_path_for("list_project"), headers=headers)
- assert response.status_code == 200
- assert len(response.json()) == 1
- async def test_excludes_other_orgs_project(
- self,
- client: AsyncClient,
- default_project: Project,
- make_org_user,
- session: Session,
- other_org_user,
- ):
- _, headers = other_org_user
- response = await client.get(app.url_path_for("list_project"), headers=headers)
- assert response.status_code == 200
- assert response.json() == []
- class TestGetProject:
- async def test_requires_auth(self, client: AsyncClient, default_project: Project):
- response = await client.get(app.url_path_for("get_project", project_id=default_project_id))
- print(response.text)
- assert response.status_code == 401
- @pytest.mark.parametrize(
- "role, expected_status",
- [
- (OrgRole.ORG_ADMIN, 200),
- (OrgRole.RESPO_BENEVOLE, 200),
- (OrgRole.RESPO_COMMISSION, 200),
- (None, 403),
- ],
- )
- async def test_role_access(
- self, client: AsyncClient, default_project: Project, make_org_user, role, expected_status
- ):
- _, headers = make_org_user(role=role)
- response = await client.get(
- app.url_path_for("get_project", project_id=default_project_id), headers=headers
- )
- assert response.status_code == expected_status
- if expected_status == 200:
- assert response.json()["id"] == default_project_id
- @pytest.mark.parametrize("project_id,status", ((default_project_id, 200), (uuid.uuid4(), 404)))
- async def test_super_admin_bypasses_membership(
- self, client: AsyncClient, default_project: Project, make_org_user, project_id, status
- ):
- _, headers = make_org_user(role=None, global_role=GlobalRole.SUPER_ADMIN)
- response = await client.get(
- app.url_path_for("get_project", project_id=project_id), headers=headers
- )
- assert response.status_code == status
- async def test_forbidden_other_org(
- self, client: AsyncClient, default_project: Project, other_org_user
- ):
- _, headers = other_org_user
- response = await client.delete(
- app.url_path_for("get_project", project_id=default_project.id),
- headers=headers,
- )
- assert response.status_code == 403
- class TestGetPublicProject:
- async def test_missing_or_private_returns_404(
- self, client: AsyncClient, default_project: Project
- ):
- response = await client.get(
- app.url_path_for("get_public_project", project_id=default_project_id)
- )
- assert response.status_code == 404
- async def test_public_project_no_auth_needed(
- self, client: AsyncClient, default_public_project: Project
- ):
- response = await client.get(
- app.url_path_for("get_public_project", project_id=default_project_id)
- )
- assert response.status_code == 200
- assert response.json()["name"] == default_project_name
- class TestListPublicProject:
- async def test_excludes_private(self, client: AsyncClient, default_project: Project):
- response = await client.get(app.url_path_for("list_public_project"))
- assert response.status_code == 200
- assert response.json() == []
- async def test_includes_public(self, client: AsyncClient, default_public_project: Project):
- response = await client.get(app.url_path_for("list_public_project"))
- assert response.status_code == 200
- assert len(response.json()) == 1
- class TestCreateProject:
- async def test_requires_auth(
- self, client: AsyncClient, default_organization: Organization, session: Session
- ):
- response = await client.post(
- app.url_path_for("create_project"),
- json={"name": "Coucou", "organization_id": default_organization_id},
- )
- assert response.status_code == 401
- assert session.execute(select(Project)).scalars().first() is None
- @pytest.mark.parametrize(
- "role, expected_status",
- [
- (OrgRole.ORG_ADMIN, 200),
- (OrgRole.RESPO_BENEVOLE, 403),
- (OrgRole.RESPO_COMMISSION, 403),
- (None, 403),
- ],
- )
- async def test_role_access(self, client: AsyncClient, make_org_user, role, expected_status):
- _, headers = make_org_user(role=role)
- response = await client.post(
- app.url_path_for("create_project"),
- headers=headers,
- json={"name": f"Project-{role}", "organization_id": default_organization_id},
- )
- assert response.status_code == expected_status
- async def test_super_admin_can_create_anywhere(self, client: AsyncClient, make_org_user):
- _, headers = make_org_user(role=None, global_role=GlobalRole.SUPER_ADMIN)
- response = await client.post(
- app.url_path_for("create_project"),
- headers=headers,
- json={"name": "Super Created", "organization_id": default_organization_id},
- )
- assert response.status_code == 200
- async def test_public_flag(self, client: AsyncClient, make_org_user, session: Session):
- _, headers = make_org_user(role=OrgRole.ORG_ADMIN)
- response = await client.post(
- app.url_path_for("create_project"),
- headers=headers,
- json={"name": "Public", "is_public": True, "organization_id": default_organization_id},
- )
- print(response.json())
- assert response.status_code == 200
- project = session.execute(select(Project).where(Project.name == "Public")).scalars().first()
- assert project.is_public
- async def test_validation_error(self, client: AsyncClient, make_org_user, session: Session):
- _, headers = make_org_user(role=OrgRole.ORG_ADMIN)
- response = await client.post(
- app.url_path_for("create_project"), headers=headers, json={"coucou": "Coucou"}
- )
- assert response.status_code == 422
- assert session.execute(select(Project)).scalars().first() is None
- async def test_existing_name(
- self, client: AsyncClient, make_org_user, default_project: Project, session: Session
- ):
- _, headers = make_org_user(role=OrgRole.ORG_ADMIN)
- response = await client.post(
- app.url_path_for("create_project"),
- headers=headers,
- json={"name": default_project_name, "organization_id": default_organization_id},
- )
- assert response.status_code == 400
- async def test_forbidden_other_org(
- self, client: AsyncClient, default_project: Project, other_org_user
- ):
- _, headers = other_org_user
- response = await client.post(
- app.url_path_for("create_project"),
- json={"name": "Coucou", "organization_id": default_organization_id},
- headers=headers,
- )
- assert response.status_code == 403
- class TestUpdateProject:
- @pytest.mark.parametrize(
- "role, expected_status",
- [
- (OrgRole.ORG_ADMIN, 200),
- (OrgRole.RESPO_BENEVOLE, 403),
- (OrgRole.RESPO_COMMISSION, 403),
- (None, 403),
- ],
- )
- async def test_role_access(
- self,
- client: AsyncClient,
- default_public_project: Project,
- make_org_user,
- session: Session,
- role,
- expected_status,
- ):
- _, headers = make_org_user(role=role)
- response = await client.post(
- app.url_path_for("update_project", project_id=default_project_id),
- headers=headers,
- json={"name": "Coucou"},
- )
- print(response.json())
- assert response.status_code == expected_status
- if expected_status == 200:
- project = session.get(Project, default_project_id)
- assert project.name == "Coucou"
- async def test_requires_auth(
- self, client: AsyncClient, default_project: Project, session: Session
- ):
- response = await client.post(
- app.url_path_for("update_project", project_id=default_project_id),
- json={"name": "Coucou 2"},
- )
- assert response.status_code == 401
- assert session.get(Project, default_project_id).name == default_project_name
- async def test_forbidden_other_org(
- self, client: AsyncClient, default_project: Project, other_org_user
- ):
- _, headers = other_org_user
- response = await client.post(
- app.url_path_for("update_project", project_id=default_project_id),
- json={"name": "Coucou 2"},
- headers=headers,
- )
- assert response.status_code == 403
- async def test_validation_error(
- self, client: AsyncClient, default_public_project: Project, make_org_user
- ):
- _, headers = make_org_user(role=OrgRole.ORG_ADMIN)
- response = await client.post(
- app.url_path_for("update_project", project_id=default_project_id),
- headers=headers,
- json={"is_public": False},
- )
- assert response.status_code == 422
- async def test_updates_timestamps(
- self, client: AsyncClient, default_public_project: Project, make_org_user, session: Session
- ):
- _, headers = make_org_user(role=OrgRole.ORG_ADMIN)
- project = session.get(Project, default_project_id)
- created_at = project.created_at
- response = await client.post(
- app.url_path_for("update_project", project_id=default_project_id),
- headers=headers,
- json={"name": "Coucou 2", "is_public": False},
- )
- assert response.status_code == 200
- session.refresh(project)
- assert project.name == "Coucou 2"
- assert project.created_at == created_at
- assert project.created_at < project.updated_at
- assert not project.is_public
- class TestDeleteProject:
- @pytest.mark.parametrize(
- "role, expected_status",
- [
- (OrgRole.ORG_ADMIN, 200),
- (OrgRole.RESPO_BENEVOLE, 403),
- (OrgRole.RESPO_COMMISSION, 403),
- (None, 403),
- ],
- )
- async def test_role_access(
- self,
- client: AsyncClient,
- default_public_project: Project,
- make_org_user,
- session: Session,
- role,
- expected_status,
- ):
- _, headers = make_org_user(role=role)
- response = await client.delete(
- app.url_path_for("delete_project", project_id=default_project_id), headers=headers
- )
- assert response.status_code == expected_status
- project = session.get(Project, default_project_id)
- if expected_status == 200:
- assert project is None
- else:
- assert project is not None
- async def test_requires_auth(self, client: AsyncClient, default_public_project: Project):
- response = await client.delete(
- app.url_path_for("delete_project", project_id=default_project_id)
- )
- assert response.status_code == 401
- async def test_forbidden_other_org(
- self, client: AsyncClient, default_project: Project, other_org_user
- ):
- _, headers = other_org_user
- response = await client.post(
- app.url_path_for("delete_project", project_id=default_project_id),
- headers=headers,
- )
- assert response.status_code == 403
- async def test_cascades_to_volunteers_and_slots(
- self, client: AsyncClient, default_public_project: Project, make_org_user, session: Session
- ):
- _, headers = make_org_user(role=OrgRole.ORG_ADMIN)
- response = await client.delete(
- app.url_path_for("delete_project", project_id=default_project_id), headers=headers
- )
- assert response.status_code == 200
- assert (
- session.execute(select(Volunteer).where(Volunteer.project_id == default_project_id))
- .scalars()
- .first()
- is None
- )
- assert (
- session.execute(select(Slot).where(Slot.project_id == default_project_id))
- .scalars()
- .first()
- is None
- )
- async def test_nonexistent_project_returns_404(self, client: AsyncClient, make_org_user):
- """require_org_role looks the project up before the handler runs, so a
- missing project now 404s -- this differs from the pre-refactor behavior
- where DELETE on a nonexistent id silently returned 200."""
- _, headers = make_org_user(role=OrgRole.ORG_ADMIN)
- response = await client.delete(
- app.url_path_for("delete_project", project_id=uuid.uuid4()), headers=headers
- )
- assert response.status_code == 404
|