diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py index ad554de0..08c94f58 100644 --- a/backend/app/api/deps.py +++ b/backend/app/api/deps.py @@ -35,8 +35,8 @@ def get_current_user( session: SessionDep, token: TokenDep, api_key: Annotated[str, Depends(api_key_header)], -) -> UserOrganization: - """Authenticate user via API Key first, fallback to JWT token.""" +) -> User: + """Authenticate user via API Key first, fallback to JWT token. Returns only User.""" if api_key: api_key_record = get_api_key_by_value(session, api_key) @@ -47,10 +47,7 @@ def get_current_user( if not user: raise HTTPException(status_code=404, detail="User linked to API Key not found") - validate_organization(session, api_key_record.organization_id) - - # Return UserOrganization model with organization ID - return UserOrganization(**user.model_dump(), organization_id=api_key_record.organization_id) + return user # Return only User object if token: try: @@ -63,18 +60,37 @@ def get_current_user( status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials", ) + user = session.get(User, token_data.sub) if not user: raise HTTPException(status_code=404, detail="User not found") if not user.is_active: raise HTTPException(status_code=400, detail="Inactive user") - return UserOrganization(**user.model_dump(), organization_id=None) + return user # Return only User object raise HTTPException(status_code=401, detail="Invalid Authorization format") -CurrentUser = Annotated[UserOrganization, Depends(get_current_user)] +CurrentUser = Annotated[User, Depends(get_current_user)] + +def get_current_user_org( + current_user: CurrentUser, + session: SessionDep, + request: Request +) -> UserOrganization: + """Extend `User` with organization_id if available, otherwise return UserOrganization without it.""" + + organization_id = None + api_key = request.headers.get("X-API-KEY") + if api_key: + api_key_record = get_api_key_by_value(session, api_key) + if api_key_record: + validate_organization(session, api_key_record.organization_id) + organization_id = api_key_record.organization_id + + return UserOrganization(**current_user.model_dump(), organization_id=organization_id) +CurrentUserOrg = Annotated[UserOrganization, Depends(get_current_user_org)] def get_current_active_superuser(current_user: CurrentUser) -> User: if not current_user.is_superuser: @@ -83,6 +99,12 @@ def get_current_active_superuser(current_user: CurrentUser) -> User: ) return current_user +def get_current_active_superuser_org(current_user: CurrentUserOrg) -> User: + if not current_user.is_superuser: + raise HTTPException( + status_code=403, detail="The user doesn't have enough privileges" + ) + return current_user async def http_exception_handler(request: Request, exc: HTTPException): """ @@ -95,7 +117,7 @@ async def http_exception_handler(request: Request, exc: HTTPException): def verify_user_project_organization( db: SessionDep, - current_user: CurrentUser, + current_user: CurrentUserOrg, project_id: int, organization_id: int, ) -> UserProjectOrg: @@ -104,7 +126,7 @@ def verify_user_project_organization( and that the project belongs to the organization. """ if current_user.organization_id and current_user.organization_id != organization_id: - raise HTTPException(status_code=403, detail="User does not belong to the specified organization") + raise HTTPException(status_code=403, detail="User is not part of organization") project_organization = db.exec( select(Project, Organization) @@ -130,12 +152,10 @@ def verify_user_project_organization( raise HTTPException(status_code=400, detail="Project is not active") # Use 400 for inactive resources raise HTTPException(status_code=403, detail="Project does not belong to the organization") - - - current_user.organization_id = organization_id - # Superuser bypasses all checks - if current_user.is_superuser: + # Superuser bypasses all checks and If Api key request we give access to all the project in organization + if current_user.is_superuser or current_user.organization_id: + current_user.organization_id = organization_id return UserProjectOrg(**current_user.model_dump(), project_id=project_id) # Check if the user is part of the project @@ -150,4 +170,5 @@ def verify_user_project_organization( if not user_in_project: raise HTTPException(status_code=403, detail="User is not part of the project") + current_user.organization_id = organization_id return UserProjectOrg(**current_user.model_dump(), project_id=project_id) diff --git a/backend/app/api/routes/api_keys.py b/backend/app/api/routes/api_keys.py index 91bcff29..aa246c46 100644 --- a/backend/app/api/routes/api_keys.py +++ b/backend/app/api/routes/api_keys.py @@ -3,8 +3,7 @@ from sqlmodel import Session from app.api.deps import get_db, get_current_active_superuser from app.crud.api_key import create_api_key, get_api_key, get_api_keys_by_organization, delete_api_key, get_api_key_by_user_org -from app.crud.organization import get_organization_by_id, validate_organization -from app.crud.project_user import is_user_part_of_organization +from app.crud.organization import validate_organization from app.models import APIKeyPublic, User from app.utils import APIResponse @@ -26,10 +25,6 @@ def create_key( # Validate organization validate_organization(session, organization_id) - # Check if user belongs to organization - if not is_user_part_of_organization(session, user_id, organization_id): - raise HTTPException(status_code=403, detail="User is not part of any project in the organization") - existing_api_key = get_api_key_by_user_org(session, organization_id, user_id) if existing_api_key: raise HTTPException(status_code=400, detail="API Key already exists for this user and organization") diff --git a/backend/app/api/routes/project_user.py b/backend/app/api/routes/project_user.py index e8fad8b0..e1be2500 100644 --- a/backend/app/api/routes/project_user.py +++ b/backend/app/api/routes/project_user.py @@ -1,5 +1,5 @@ import uuid -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, HTTPException, Query, Request from sqlmodel import Session from typing import Annotated from app.api.deps import get_db, verify_user_project_organization @@ -14,6 +14,7 @@ # Add a user to a project @router.post("/{user_id}", response_model=APIResponse[ProjectUserPublic]) def add_user( + request: Request, user_id: uuid.UUID, is_admin: bool = False, session: Session = Depends(get_db), @@ -28,9 +29,17 @@ def add_user( if not user: raise HTTPException(status_code=404, detail="User not found") - # Only allow superusers or project admins to add users - if not current_user.is_superuser and not is_project_admin(session, current_user.id, project_id): - raise HTTPException(status_code=403, detail="Only project admins or superusers can add users.") + # Only allow superusers, project admins, or API key-authenticated requests to add users + if ( + not current_user.is_superuser + and not request.headers.get("X-API-KEY") + and not is_project_admin(session, current_user.id, project_id) + ): + raise HTTPException( + status_code=403, + detail="Only project admins or superusers can add users." + ) + try: added_user = add_user_to_project(session, project_id, user_id, is_admin) return APIResponse.success_response(added_user) @@ -62,6 +71,7 @@ def list_project_users( # Remove a user from a project @router.delete("/{user_id}", response_model=APIResponse[Message]) def remove_user( + request: Request, user_id: uuid.UUID, session: Session = Depends(get_db), current_user: UserProjectOrg = Depends(verify_user_project_organization) @@ -76,8 +86,17 @@ def remove_user( if not user: raise HTTPException(status_code=404, detail="User not found") - if not current_user.is_superuser and not is_project_admin(session, current_user.id, project_id): - raise HTTPException(status_code=403, detail="Only project admins or superusers can remove users.") + # Only allow superusers, project admins, or API key-authenticated requests to remove users + if ( + not current_user.is_superuser + and not request.headers.get("X-API-KEY") + and not is_project_admin(session, current_user.id, project_id) + ): + raise HTTPException( + status_code=403, + detail="Only project admins or superusers can remove users." + ) + try: remove_user_from_project(session, project_id, user_id) return APIResponse.success_response({"message": "User removed from project successfully."}) diff --git a/backend/app/crud/api_key.py b/backend/app/crud/api_key.py index d232bde6..efbfbb1b 100644 --- a/backend/app/crud/api_key.py +++ b/backend/app/crud/api_key.py @@ -56,7 +56,7 @@ def delete_api_key(session: Session, api_key_id: int) -> None: api_key = session.get(APIKey, api_key_id) if not api_key or api_key.is_deleted: - raise ValueError("API key not found or already deleted.") + raise ValueError("API key not found or already deleted") api_key.is_deleted = True api_key.deleted_at = datetime.utcnow() @@ -76,6 +76,7 @@ def get_api_key_by_user_org(session: Session, organization_id: int, user_id: str """ statement = select(APIKey).where( APIKey.organization_id == organization_id, - APIKey.user_id == user_id + APIKey.user_id == user_id, + APIKey.is_deleted == False ) return session.exec(statement).first() \ No newline at end of file diff --git a/backend/app/models/user.py b/backend/app/models/user.py index e6e49cdc..714d9616 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -11,13 +11,6 @@ class UserBase(SQLModel): is_superuser: bool = False full_name: str | None = Field(default=None, max_length=255) -class UserOrganization(UserBase): - id: uuid.UUID - organization_id: int | None - -class UserProjectOrg(UserOrganization): - project_id: int - # Properties to receive via API on creation class UserCreate(UserBase): @@ -60,6 +53,15 @@ class User(UserBase, table=True): api_keys: list["APIKey"] = Relationship(back_populates="user") +class UserOrganization(UserBase): + id : uuid.UUID + organization_id: int | None + + +class UserProjectOrg(UserOrganization): + project_id: int + + # Properties to return via API, id is always required class UserPublic(UserBase): id: uuid.UUID diff --git a/backend/app/tests/api/routes/test_api_key.py b/backend/app/tests/api/routes/test_api_key.py new file mode 100644 index 00000000..18f9cb8e --- /dev/null +++ b/backend/app/tests/api/routes/test_api_key.py @@ -0,0 +1,149 @@ +import uuid +import pytest +from fastapi.testclient import TestClient +from sqlmodel import Session +from app.main import app +from app.models import APIKey, User, Organization +from app.core.config import settings +from app.crud import api_key as api_key_crud +from app.tests.utils.utils import random_email +from app.core.security import get_password_hash + +client = TestClient(app) + +def create_test_user(db: Session) -> User: + user = User( + email=random_email(), + hashed_password=get_password_hash("password123"), + is_superuser=True + ) + db.add(user) + db.commit() + db.refresh(user) + return user + +def create_test_organization(db: Session) -> Organization: + org = Organization( + name=f"Test Organization {uuid.uuid4()}", + description="Test Organization" + ) + db.add(org) + db.commit() + db.refresh(org) + return org + + +def test_create_api_key(db: Session, superuser_token_headers: dict[str, str]): + user = create_test_user(db) + org = create_test_organization(db) + + response = client.post( + f"{settings.API_V1_STR}/apikeys", + params={"organization_id": org.id, "user_id": user.id}, + headers=superuser_token_headers, + ) + assert response.status_code == 200 + data = response.json() + assert data["success"] is True + assert "id" in data["data"] + assert "key" in data["data"] + assert data["data"]["organization_id"] == org.id + assert data["data"]["user_id"] == str(user.id) + + +def test_create_duplicate_api_key(db: Session, superuser_token_headers: dict[str, str]): + user = create_test_user(db) + org = create_test_organization(db) + + client.post( + f"{settings.API_V1_STR}/apikeys", + params={"organization_id": org.id, "user_id": user.id}, + headers=superuser_token_headers, + ) + response = client.post( + f"{settings.API_V1_STR}/apikeys", + params={"organization_id": org.id, "user_id": user.id}, + headers=superuser_token_headers, + ) + assert response.status_code == 400 + assert "API Key already exists" in response.json()["detail"] + + +def test_list_api_keys(db: Session, superuser_token_headers: dict[str, str]): + user = create_test_user(db) + org = create_test_organization(db) + api_key = api_key_crud.create_api_key(db, organization_id=org.id, user_id=user.id) + + response = client.get( + f"{settings.API_V1_STR}/apikeys", + params={"organization_id": org.id, "user_id": user.id}, + headers=superuser_token_headers, + ) + assert response.status_code == 200 + data = response.json() + assert data["success"] is True + assert isinstance(data["data"], list) + assert len(data["data"]) > 0 + assert data["data"][0]["organization_id"] == org.id + assert data["data"][0]["user_id"] == str(user.id) + + +def test_get_api_key(db: Session, superuser_token_headers: dict[str, str]): + user = create_test_user(db) + org = create_test_organization(db) + api_key = api_key_crud.create_api_key(db, organization_id=org.id, user_id=user.id) + + response = client.get( + f"{settings.API_V1_STR}/apikeys/{api_key.id}", + params={"organization_id": api_key.organization_id, "user_id": user.id}, + headers=superuser_token_headers, + ) + assert response.status_code == 200 + data = response.json() + assert data["success"] is True + assert data["data"]["id"] == api_key.id + assert data["data"]["organization_id"] == api_key.organization_id + assert data["data"]["user_id"] == str(user.id) + + +def test_get_nonexistent_api_key(db: Session, superuser_token_headers: dict[str, str]): + user = create_test_user(db) + org = create_test_organization(db) + + response = client.get( + f"{settings.API_V1_STR}/apikeys/999999", + params={"organization_id": org.id, "user_id": user.id}, + headers=superuser_token_headers, + ) + assert response.status_code == 404 + assert "API Key does not exist" in response.json()["detail"] + + +def test_revoke_api_key(db: Session, superuser_token_headers: dict[str, str]): + user = create_test_user(db) + org = create_test_organization(db) + api_key = api_key_crud.create_api_key(db, organization_id=org.id, user_id=user.id) + + response = client.delete( + f"{settings.API_V1_STR}/apikeys/{api_key.id}", + params={"organization_id": api_key.organization_id, "user_id": user.id}, + headers=superuser_token_headers, + ) + assert response.status_code == 200 + data = response.json() + assert data["success"] is True + assert "API key revoked successfully" in data["data"]["message"] + + +def test_revoke_nonexistent_api_key(db: Session, superuser_token_headers: dict[str, str]): + user = create_test_user(db) + org = create_test_organization(db) + + response = client.delete( + f"{settings.API_V1_STR}/apikeys/999999", + params={"organization_id": org.id, "user_id": user.id}, + headers=superuser_token_headers, + ) + assert response.status_code == 400 + assert "API key not found or already deleted" in response.json()["detail"] + \ No newline at end of file diff --git a/backend/app/tests/api/routes/test_project_user.py b/backend/app/tests/api/routes/test_project_user.py index a97ce6c7..bd271db5 100644 --- a/backend/app/tests/api/routes/test_project_user.py +++ b/backend/app/tests/api/routes/test_project_user.py @@ -25,15 +25,10 @@ def create_user(db: Session) -> User: def create_organization_and_project(db: Session) -> tuple[Organization, Project]: """Helper function to create an organization and a project.""" - # Check if an organization already exists to avoid duplicate key errors - existing_org = db.exec(select(Organization).where(Organization.name == "Test Organization")).first() - if existing_org: - organization = existing_org - else: - organization = Organization(name="Test Organization", is_active=True) - db.add(organization) - db.commit() - db.refresh(organization) + organization = Organization(name=f"Test Organization {uuid.uuid4()}", is_active=True) + db.add(organization) + db.commit() + db.refresh(organization) # Ensure project with unique name project_name = f"Test Project {uuid.uuid4()}" # Ensuring unique project name diff --git a/backend/app/tests/api/test_deps.py b/backend/app/tests/api/test_deps.py index a0227c81..14090686 100644 --- a/backend/app/tests/api/test_deps.py +++ b/backend/app/tests/api/test_deps.py @@ -3,7 +3,7 @@ from sqlmodel import Session, select from fastapi import HTTPException from app.api.deps import verify_user_project_organization -from app.models import User, Organization, Project, ProjectUser, UserProjectOrg +from app.models import User, Organization, Project, ProjectUser, UserProjectOrg, UserOrganization from app.tests.utils.utils import random_email from app.core.security import get_password_hash @@ -34,7 +34,8 @@ def create_user(db: Session, is_superuser=False) -> User: db.add(user) db.commit() db.refresh(user) - return user + user_org = UserOrganization(**user.model_dump(), organization_id=None) + return user_org def test_verify_success(db: Session): diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index 90ab39a3..1293ca85 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -7,7 +7,14 @@ from app.core.config import settings from app.core.db import engine, init_db from app.main import app -from app.models import Item, User +from app.models import ( + APIKey, + Item, + Organization, + Project, + ProjectUser, + User, +) from app.tests.utils.user import authentication_token_from_email from app.tests.utils.utils import get_superuser_token_headers @@ -17,13 +24,17 @@ def db() -> Generator[Session, None, None]: with Session(engine) as session: init_db(session) yield session - statement = delete(Item) - session.execute(statement) - statement = delete(User) - session.execute(statement) + # Delete data in reverse dependency order + session.execute(delete(ProjectUser)) # Many-to-many relationship + session.execute(delete(Project)) + session.execute(delete(Organization)) + session.execute(delete(Item)) + session.execute(delete(APIKey)) + session.execute(delete(User)) session.commit() + @pytest.fixture(scope="module") def client() -> Generator[TestClient, None, None]: with TestClient(app) as c: diff --git a/backend/app/tests/crud/test_api_key.py b/backend/app/tests/crud/test_api_key.py new file mode 100644 index 00000000..48cc571e --- /dev/null +++ b/backend/app/tests/crud/test_api_key.py @@ -0,0 +1,118 @@ +import uuid +import pytest +from sqlmodel import Session, select +from app.crud import api_key as api_key_crud +from app.models import APIKey, User, Organization +from app.tests.utils.utils import random_email +from app.core.security import get_password_hash + +# Helper function to create a user +def create_test_user(db: Session) -> User: + user = User(email=random_email(), hashed_password=get_password_hash("password123")) + db.add(user) + db.commit() + db.refresh(user) + return user + + +# Helper function to create an organization with a random name +def create_test_organization(db: Session) -> Organization: + org = Organization(name=f"Test Organization {uuid.uuid4()}", description="Test Organization") + db.add(org) + db.commit() + db.refresh(org) + return org + +def test_create_api_key(db: Session) -> None: + user = create_test_user(db) + org = create_test_organization(db) + + api_key = api_key_crud.create_api_key(db, org.id, user.id) + + assert api_key.key.startswith("ApiKey ") + assert len(api_key.key) > 32 + assert api_key.organization_id == org.id + assert api_key.user_id == user.id + +def test_get_api_key(db: Session) -> None: + user = create_test_user(db) + org = create_test_organization(db) + + created_key = api_key_crud.create_api_key(db, org.id, user.id) + retrieved_key = api_key_crud.get_api_key(db, created_key.id) + + assert retrieved_key is not None + assert retrieved_key.id == created_key.id + assert retrieved_key.key == created_key.key + +def test_get_api_key_not_found(db: Session) -> None: + result = api_key_crud.get_api_key(db, 9999) # Non-existent ID + assert result is None + +def test_get_api_keys_by_organization(db: Session) -> None: + user1 = create_test_user(db) + user2 = create_test_user(db) + org = create_test_organization(db) + + api_key1 = api_key_crud.create_api_key(db, org.id, user1.id) + api_key2 = api_key_crud.create_api_key(db, org.id, user2.id) + + api_keys = api_key_crud.get_api_keys_by_organization(db, org.id) + + assert len(api_keys) == 2 + assert any(key.id == api_key1.id for key in api_keys) + assert any(key.id == api_key2.id for key in api_keys) + +def test_delete_api_key(db: Session) -> None: + user = create_test_user(db) + org = create_test_organization(db) + + api_key = api_key_crud.create_api_key(db, org.id, user.id) + api_key_crud.delete_api_key(db, api_key.id) + + deleted_key = db.exec( + select(APIKey).where(APIKey.id == api_key.id) + ).first() + + assert deleted_key is not None + assert deleted_key.is_deleted is True + assert deleted_key.deleted_at is not None + +def test_delete_api_key_already_deleted(db: Session) -> None: + user = create_test_user(db) + org = create_test_organization(db) + + api_key = api_key_crud.create_api_key(db, org.id, user.id) + api_key_crud.delete_api_key(db, api_key.id) + + with pytest.raises(ValueError, match="API key not found or already deleted"): + api_key_crud.delete_api_key(db, api_key.id) + +def test_get_api_key_by_value(db: Session) -> None: + user = create_test_user(db) + org = create_test_organization(db) + + api_key = api_key_crud.create_api_key(db, org.id, user.id) + retrieved_key = api_key_crud.get_api_key_by_value(db, api_key.key) + + assert retrieved_key is not None + assert retrieved_key.id == api_key.id + assert retrieved_key.key == api_key.key + +def test_get_api_key_by_user_org(db: Session) -> None: + user = create_test_user(db) + org = create_test_organization(db) + + api_key = api_key_crud.create_api_key(db, org.id, user.id) + retrieved_key = api_key_crud.get_api_key_by_user_org(db, org.id, user.id) + + assert retrieved_key is not None + assert retrieved_key.id == api_key.id + assert retrieved_key.organization_id == org.id + assert retrieved_key.user_id == user.id + +def test_get_api_key_by_user_org_not_found(db: Session) -> None: + org = create_test_organization(db) + user_id = uuid.uuid4() + result = api_key_crud.get_api_key_by_user_org(db, org.id, user_id) + assert result is None diff --git a/backend/app/tests/crud/test_project_user.py b/backend/app/tests/crud/test_project_user.py index 149bbf43..a59e3a15 100644 --- a/backend/app/tests/crud/test_project_user.py +++ b/backend/app/tests/crud/test_project_user.py @@ -4,23 +4,36 @@ import pytest from app.crud import project_user as project_user_crud -from app.models import ProjectUser, ProjectUserPublic, User, Project +from app.models import ProjectUser, ProjectUserPublic, User, Project, Organization from app.tests.utils.utils import random_email from app.core.security import get_password_hash -def test_is_project_admin(db: Session) -> None: - user = User(email=random_email(), hashed_password=get_password_hash("password123")) - db.add(user) +def create_organization_and_project(db: Session) -> tuple[Organization, Project]: + """Helper function to create an organization and a project.""" + + organization = Organization(name=f"Test Organization {uuid.uuid4()}", is_active=True) + db.add(organization) db.commit() - db.refresh(user) + db.refresh(organization) - # Ensure the project exists - project = Project(name="Test Project", description="A test project", organization_id=1) + # Ensure project with unique name + project_name = f"Test Project {uuid.uuid4()}" # Ensuring unique project name + project = Project(name=project_name, description="A test project", organization_id=organization.id, is_active=True) db.add(project) db.commit() db.refresh(project) + return organization, project + +def test_is_project_admin(db: Session) -> None: + organization, project = create_organization_and_project(db) + + user = User(email=random_email(), hashed_password=get_password_hash("password123")) + db.add(user) + db.commit() + db.refresh(user) + project_user = ProjectUser(project_id=project.id, user_id=user.id, is_admin=True) db.add(project_user) db.commit() @@ -30,17 +43,13 @@ def test_is_project_admin(db: Session) -> None: def test_add_user_to_project(db: Session) -> None: + organization, project = create_organization_and_project(db) + user = User(email=random_email(), hashed_password=get_password_hash("password123")) db.add(user) db.commit() db.refresh(user) - # Ensure the project exists - project = Project(name="Test Project", description="A test project", organization_id=1) - db.add(project) - db.commit() - db.refresh(project) - project_user = project_user_crud.add_user_to_project(db, project.id, user.id, is_admin=True) assert project_user.user_id == user.id @@ -49,17 +58,13 @@ def test_add_user_to_project(db: Session) -> None: def test_add_user_to_project_duplicate(db: Session) -> None: + organization, project = create_organization_and_project(db) + user = User(email=random_email(), hashed_password=get_password_hash("password123")) db.add(user) db.commit() db.refresh(user) - # Ensure the project exists - project = Project(name="Test Project", description="A test project", organization_id=1) - db.add(project) - db.commit() - db.refresh(project) - project_user_crud.add_user_to_project(db, project.id, user.id) with pytest.raises(ValueError, match="User is already a member of this project"): @@ -67,17 +72,13 @@ def test_add_user_to_project_duplicate(db: Session) -> None: def test_remove_user_from_project(db: Session) -> None: + organization, project = create_organization_and_project(db) + user = User(email=random_email(), hashed_password=get_password_hash("password123")) db.add(user) db.commit() db.refresh(user) - # Ensure the project exists - project = Project(name="Test Project", description="A test project", organization_id=1) - db.add(project) - db.commit() - db.refresh(project) - # Add user to project project_user_crud.add_user_to_project(db, project.id, user.id) @@ -98,11 +99,7 @@ def test_remove_user_from_project(db: Session) -> None: def test_remove_user_from_project_not_member(db: Session) -> None: - # Ensure the project exists - project = Project(name="Test Project", description="A test project", organization_id=1) - db.add(project) - db.commit() - db.refresh(project) + organization, project = create_organization_and_project(db) project_id = project.id user_id = uuid.uuid4() @@ -112,11 +109,7 @@ def test_remove_user_from_project_not_member(db: Session) -> None: def test_get_users_by_project(db: Session) -> None: - # Ensure the project exists - project = Project(name="Test Project", description="A test project", organization_id=1) - db.add(project) - db.commit() - db.refresh(project) + organization, project = create_organization_and_project(db) user1 = User(email=random_email(), hashed_password=get_password_hash("password123")) user2 = User(email=random_email(), hashed_password=get_password_hash("password123")) @@ -132,4 +125,4 @@ def test_get_users_by_project(db: Session) -> None: users, total_count = project_user_crud.get_users_by_project(db, project.id, skip=0, limit=10) assert total_count == 2 - assert len(users) == 2 + assert len(users) == 2 \ No newline at end of file