diff --git a/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py b/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py new file mode 100644 index 000000000..0035b705b --- /dev/null +++ b/backend/app/alembic/versions/66abc97f3782_user_id_from_uuid_to_int.py @@ -0,0 +1,156 @@ +"""user_id from uuid to int + +Revision ID: 66abc97f3782 +Revises: 60b6c511a485 +Create Date: 2025-06-16 11:05:36.196795 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = "66abc97f3782" +down_revision = "60b6c511a485" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + + # Drop foreign key constraints + conn.execute( + sa.text("ALTER TABLE document DROP CONSTRAINT document_owner_id_fkey;") + ) + conn.execute( + sa.text("ALTER TABLE collection DROP CONSTRAINT collection_owner_id_fkey;") + ) + conn.execute( + sa.text("ALTER TABLE projectuser DROP CONSTRAINT projectuser_user_id_fkey;") + ) + conn.execute(sa.text("ALTER TABLE apikey DROP CONSTRAINT apikey_user_id_fkey;")) + + # Drop primary key constraint on "user" table + conn.execute(sa.text('ALTER TABLE "user" DROP CONSTRAINT user_pkey;')) + + # Create mapping table from UUID to INT + conn.execute( + sa.text( + """ + CREATE TABLE uuid_to_int_map ( + user_id_uuid UUID PRIMARY KEY, + user_id_int INT GENERATED ALWAYS AS IDENTITY + ); + """ + ) + ) + + # Populate mapping table + conn.execute( + sa.text('INSERT INTO uuid_to_int_map (user_id_uuid) SELECT id FROM "user";') + ) + + # Add new_id to user table and populate it + conn.execute(sa.text('ALTER TABLE "user" ADD COLUMN new_id INT;')) + conn.execute( + sa.text( + """ + UPDATE "user" SET new_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE "user".id = uuid_map.user_id_uuid; + """ + ) + ) + + # document + conn.execute(sa.text("ALTER TABLE document ADD COLUMN new_owner_id INT;")) + conn.execute( + sa.text( + """ + UPDATE document SET new_owner_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE document.owner_id = uuid_map.user_id_uuid; + """ + ) + ) + + # collection + conn.execute(sa.text("ALTER TABLE collection ADD COLUMN new_owner_id INT;")) + conn.execute( + sa.text( + """ + UPDATE collection SET new_owner_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE collection.owner_id = uuid_map.user_id_uuid; + """ + ) + ) + + # projectuser + conn.execute(sa.text("ALTER TABLE projectuser ADD COLUMN new_user_id INT;")) + conn.execute( + sa.text( + """ + UPDATE projectuser SET new_user_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE projectuser.user_id = uuid_map.user_id_uuid; + """ + ) + ) + + # apikey + conn.execute(sa.text("ALTER TABLE apikey ADD COLUMN new_user_id INT;")) + conn.execute( + sa.text( + """ + UPDATE apikey SET new_user_id = uuid_map.user_id_int + FROM uuid_to_int_map uuid_map + WHERE apikey.user_id = uuid_map.user_id_uuid; + """ + ) + ) + + # Drop old columns and rename new ones + conn.execute(sa.text("ALTER TABLE document DROP COLUMN owner_id;")) + conn.execute( + sa.text("ALTER TABLE document RENAME COLUMN new_owner_id TO owner_id;") + ) + + conn.execute(sa.text("ALTER TABLE collection DROP COLUMN owner_id;")) + conn.execute( + sa.text("ALTER TABLE collection RENAME COLUMN new_owner_id TO owner_id;") + ) + + conn.execute(sa.text("ALTER TABLE projectuser DROP COLUMN user_id;")) + conn.execute( + sa.text("ALTER TABLE projectuser RENAME COLUMN new_user_id TO user_id;") + ) + + conn.execute(sa.text("ALTER TABLE apikey DROP COLUMN user_id;")) + conn.execute(sa.text("ALTER TABLE apikey RENAME COLUMN new_user_id TO user_id;")) + + conn.execute(sa.text('ALTER TABLE "user" DROP COLUMN id;')) + conn.execute(sa.text('ALTER TABLE "user" RENAME COLUMN new_id TO id;')) + + # Re-add primary key + conn.execute(sa.text('ALTER TABLE "user" ADD PRIMARY KEY (id);')) + + # Create sequence for new integer IDs + conn.execute(sa.text('CREATE SEQUENCE user_id_seq START 1 OWNED BY "user".id;')) + conn.execute( + sa.text( + "ALTER TABLE \"user\" ALTER COLUMN id SET DEFAULT nextval('user_id_seq');" + ) + ) + conn.execute( + sa.text("SELECT setval('user_id_seq', (SELECT MAX(id) FROM \"user\"));") + ) + + # Drop mapping table + conn.execute(sa.text("DROP TABLE uuid_to_int_map;")) + + +def downgrade(): + pass diff --git a/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py b/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py new file mode 100644 index 000000000..09fe3ef99 --- /dev/null +++ b/backend/app/alembic/versions/8e7dc5eab0b0_add_fk_constraint_to_user_id_columns.py @@ -0,0 +1,55 @@ +"""add fk constraint to user id columns + +Revision ID: 8e7dc5eab0b0 +Revises: 66abc97f3782 +Create Date: 2025-06-16 11:08:46.893642 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = "8e7dc5eab0b0" +down_revision = "66abc97f3782" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("apikey", "user_id", existing_type=sa.INTEGER(), nullable=False) + op.create_foreign_key( + None, "apikey", "user", ["user_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column( + "collection", "owner_id", existing_type=sa.INTEGER(), nullable=False + ) + op.create_foreign_key( + None, "collection", "user", ["owner_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=False) + op.create_foreign_key( + None, "document", "user", ["owner_id"], ["id"], ondelete="CASCADE" + ) + op.alter_column( + "projectuser", "user_id", existing_type=sa.INTEGER(), nullable=False + ) + op.create_foreign_key( + None, "projectuser", "user", ["user_id"], ["id"], ondelete="CASCADE" + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, "projectuser", type_="foreignkey") + op.alter_column("projectuser", "user_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "document", type_="foreignkey") + op.alter_column("document", "owner_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "collection", type_="foreignkey") + op.alter_column("collection", "owner_id", existing_type=sa.INTEGER(), nullable=True) + op.drop_constraint(None, "apikey", type_="foreignkey") + op.alter_column("apikey", "user_id", existing_type=sa.INTEGER(), nullable=True) + # ### end Alembic commands ### diff --git a/backend/app/api/routes/api_keys.py b/backend/app/api/routes/api_keys.py index 8d6905908..977dd02a5 100644 --- a/backend/app/api/routes/api_keys.py +++ b/backend/app/api/routes/api_keys.py @@ -20,7 +20,7 @@ @router.post("/", response_model=APIResponse[APIKeyPublic]) def create_key( project_id: int, - user_id: uuid.UUID, + user_id: int, session: Session = Depends(get_db), current_user: User = Depends(get_current_active_superuser), ): diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index fa8559d55..f267e37f6 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -40,7 +40,7 @@ class OnboardingRequest(BaseModel): class OnboardingResponse(BaseModel): organization_id: int project_id: int - user_id: uuid.UUID + user_id: int api_key: str diff --git a/backend/app/api/routes/project_user.py b/backend/app/api/routes/project_user.py index e5723891a..17bd5b8aa 100644 --- a/backend/app/api/routes/project_user.py +++ b/backend/app/api/routes/project_user.py @@ -22,7 +22,7 @@ ) def add_user( request: Request, - user_id: uuid.UUID, + user_id: int, is_admin: bool = False, session: Session = Depends(get_db), current_user: UserProjectOrg = Depends(verify_user_project_organization), @@ -81,7 +81,7 @@ def list_project_users( ) def remove_user( request: Request, - user_id: uuid.UUID, + user_id: int, session: Session = Depends(get_db), current_user: UserProjectOrg = Depends(verify_user_project_organization), ): diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index 60a043bc6..956461010 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -160,7 +160,7 @@ def register_user(session: SessionDep, user_in: UserRegister) -> Any: @router.get("/{user_id}", response_model=UserPublic, include_in_schema=False) def read_user_by_id( - user_id: uuid.UUID, session: SessionDep, current_user: CurrentUser + user_id: int, session: SessionDep, current_user: CurrentUser ) -> Any: """ Get a specific user by id. @@ -185,7 +185,7 @@ def read_user_by_id( def update_user_endpoint( *, session: SessionDep, - user_id: uuid.UUID, + user_id: int, user_in: UserUpdate, ) -> Any: """ @@ -215,7 +215,7 @@ def update_user_endpoint( include_in_schema=False, ) def delete_user( - session: SessionDep, current_user: CurrentUser, user_id: uuid.UUID + session: SessionDep, current_user: CurrentUser, user_id: int ) -> Message: """ Delete a user. diff --git a/backend/app/crud/api_key.py b/backend/app/crud/api_key.py index 5a4f3a047..905d0a1b1 100644 --- a/backend/app/crud/api_key.py +++ b/backend/app/crud/api_key.py @@ -19,7 +19,7 @@ def generate_api_key() -> tuple[str, str]: def create_api_key( - session: Session, organization_id: int, user_id: uuid.UUID, project_id: int + session: Session, organization_id: int, user_id: int, project_id: int ) -> APIKeyPublic: """ Generates a new API key for an organization and associates it with a user. diff --git a/backend/app/crud/collection.py b/backend/app/crud/collection.py index 830466833..7b91f4fbd 100644 --- a/backend/app/crud/collection.py +++ b/backend/app/crud/collection.py @@ -11,7 +11,7 @@ class CollectionCrud: - def __init__(self, session: Session, owner_id: UUID): + def __init__(self, session: Session, owner_id: int): self.session = session self.owner_id = owner_id diff --git a/backend/app/crud/document.py b/backend/app/crud/document.py index db1dce4d0..32b59f484 100644 --- a/backend/app/crud/document.py +++ b/backend/app/crud/document.py @@ -8,7 +8,7 @@ class DocumentCrud: - def __init__(self, session: Session, owner_id: UUID): + def __init__(self, session: Session, owner_id: int): self.session = session self.owner_id = owner_id diff --git a/backend/app/crud/project_user.py b/backend/app/crud/project_user.py index 8a3d88350..3b5dedd1d 100644 --- a/backend/app/crud/project_user.py +++ b/backend/app/crud/project_user.py @@ -6,7 +6,7 @@ from app.core.util import now -def is_project_admin(session: Session, user_id: str, project_id: int) -> bool: +def is_project_admin(session: Session, user_id: int, project_id: int) -> bool: """ Checks if a user is an admin of the given project. """ @@ -23,7 +23,7 @@ def is_project_admin(session: Session, user_id: str, project_id: int) -> bool: # Add a user to a project def add_user_to_project( - session: Session, project_id: uuid.UUID, user_id: uuid.UUID, is_admin: bool = False + session: Session, project_id: int, user_id: int, is_admin: bool = False ) -> ProjectUserPublic: """ Adds a user to a project. @@ -47,9 +47,7 @@ def add_user_to_project( return ProjectUserPublic.model_validate(project_user) -def remove_user_from_project( - session: Session, project_id: uuid.UUID, user_id: uuid.UUID -) -> None: +def remove_user_from_project(session: Session, project_id: int, user_id: int) -> None: """ Removes a user from a project. """ @@ -70,7 +68,7 @@ def remove_user_from_project( def get_users_by_project( - session: Session, project_id: uuid.UUID, skip: int = 0, limit: int = 100 + session: Session, project_id: int, skip: int = 0, limit: int = 100 ) -> tuple[list[ProjectUserPublic], int]: """ Returns paginated users in a given project along with the total count. @@ -94,9 +92,7 @@ def get_users_by_project( # Check if a user belongs to an at least one project in organization -def is_user_part_of_organization( - session: Session, user_id: uuid.UUID, org_id: int -) -> bool: +def is_user_part_of_organization(session: Session, user_id: int, org_id: int) -> bool: """ Checks if a user is part of at least one project within the organization. """ diff --git a/backend/app/models/api_key.py b/backend/app/models/api_key.py index 188f529b3..22387e1bf 100644 --- a/backend/app/models/api_key.py +++ b/backend/app/models/api_key.py @@ -14,9 +14,7 @@ class APIKeyBase(SQLModel): project_id: int = Field( foreign_key="project.id", nullable=False, ondelete="CASCADE" ) - user_id: uuid.UUID = Field( - foreign_key="user.id", nullable=False, ondelete="CASCADE" - ) + user_id: int = Field(foreign_key="user.id", nullable=False, ondelete="CASCADE") key: str = Field( default_factory=lambda: secrets.token_urlsafe(32), unique=True, index=True ) diff --git a/backend/app/models/collection.py b/backend/app/models/collection.py index 1e74d29c7..2d7ea2a5e 100644 --- a/backend/app/models/collection.py +++ b/backend/app/models/collection.py @@ -12,7 +12,7 @@ class Collection(SQLModel, table=True): default_factory=uuid4, primary_key=True, ) - owner_id: UUID = Field( + owner_id: int = Field( foreign_key="user.id", nullable=False, ondelete="CASCADE", diff --git a/backend/app/models/document.py b/backend/app/models/document.py index c0c02ee75..71da74c1b 100644 --- a/backend/app/models/document.py +++ b/backend/app/models/document.py @@ -12,7 +12,7 @@ class Document(SQLModel, table=True): default_factory=uuid4, primary_key=True, ) - owner_id: UUID = Field( + owner_id: int = Field( foreign_key="user.id", nullable=False, ondelete="CASCADE", diff --git a/backend/app/models/project_user.py b/backend/app/models/project_user.py index cbc18efd5..57028436f 100644 --- a/backend/app/models/project_user.py +++ b/backend/app/models/project_user.py @@ -11,9 +11,7 @@ class ProjectUserBase(SQLModel): project_id: int = Field( foreign_key="project.id", nullable=False, ondelete="CASCADE" ) - user_id: uuid.UUID = Field( - foreign_key="user.id", nullable=False, ondelete="CASCADE" - ) + user_id: int = Field(foreign_key="user.id", nullable=False, ondelete="CASCADE") is_admin: bool = Field( default=False, nullable=False ) # Determines if user is an admin of the project diff --git a/backend/app/models/user.py b/backend/app/models/user.py index f39c6161c..66a7e932c 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -46,7 +46,7 @@ class UpdatePassword(SQLModel): # Database model, database table inferred from class name class User(UserBase, table=True): - id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + id: int = Field(default=None, primary_key=True) hashed_password: str documents: list["Document"] = Relationship( back_populates="owner", cascade_delete=True @@ -61,7 +61,7 @@ class User(UserBase, table=True): class UserOrganization(UserBase): - id: uuid.UUID + id: int organization_id: int | None @@ -71,7 +71,7 @@ class UserProjectOrg(UserOrganization): # Properties to return via API, id is always required class UserPublic(UserBase): - id: uuid.UUID + id: int class UsersPublic(SQLModel): diff --git a/backend/app/tests/api/routes/test_api_key.py b/backend/app/tests/api/routes/test_api_key.py index 3c1f49c25..0191a594b 100644 --- a/backend/app/tests/api/routes/test_api_key.py +++ b/backend/app/tests/api/routes/test_api_key.py @@ -58,7 +58,7 @@ def test_create_api_key(db: Session, superuser_token_headers: dict[str, str]): 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) + assert data["data"]["user_id"] == user.id def test_create_duplicate_api_key(db: Session, superuser_token_headers: dict[str, str]): @@ -101,7 +101,7 @@ def test_list_api_keys(db: Session, superuser_token_headers: dict[str, str]): first_key = data["data"][0] assert first_key["organization_id"] == org.id - assert first_key["user_id"] == str(user.id) + assert first_key["user_id"] == user.id def test_get_api_key(db: Session, superuser_token_headers: dict[str, str]): @@ -121,7 +121,7 @@ def test_get_api_key(db: Session, superuser_token_headers: dict[str, str]): 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) + assert data["data"]["user_id"] == user.id def test_get_nonexistent_api_key(db: Session, superuser_token_headers: dict[str, str]): diff --git a/backend/app/tests/api/routes/test_project_user.py b/backend/app/tests/api/routes/test_project_user.py index fab5f9937..85b5241ab 100644 --- a/backend/app/tests/api/routes/test_project_user.py +++ b/backend/app/tests/api/routes/test_project_user.py @@ -5,7 +5,7 @@ from app.core.config import settings from app.models import User, Project, ProjectUser, Organization from app.crud.project_user import add_user_to_project -from app.tests.utils.utils import random_email +from app.tests.utils.utils import random_email, get_non_existent_id from app.tests.utils.user import authentication_token_from_email from app.core.security import get_password_hash from app.main import app @@ -63,7 +63,7 @@ def test_add_user_to_project( assert response.status_code == 200, response.text added_user = response.json()["data"] - assert added_user["user_id"] == str(user.id) + assert added_user["user_id"] == user.id assert added_user["project_id"] == project.id assert added_user["is_admin"] is True @@ -75,9 +75,10 @@ def test_add_user_not_found( Test adding a non-existing user to a project (should return 404). """ organization, project = create_organization_and_project(db) + user_id = get_non_existent_id(db, User) response = client.post( - f"{settings.API_V1_STR}/project/users/{uuid.uuid4()}?is_admin=false&project_id={project.id}&organization_id={organization.id}", + f"{settings.API_V1_STR}/project/users/{user_id}?is_admin=false&project_id={project.id}&organization_id={organization.id}", headers=superuser_token_headers, ) diff --git a/backend/app/tests/api/routes/test_users.py b/backend/app/tests/api/routes/test_users.py index 68cb3adab..ac518075e 100644 --- a/backend/app/tests/api/routes/test_users.py +++ b/backend/app/tests/api/routes/test_users.py @@ -8,7 +8,7 @@ from app.core.config import settings from app.core.security import verify_password from app.models import User, UserCreate -from app.tests.utils.utils import random_email, random_lower_string +from app.tests.utils.utils import random_email, random_lower_string, get_non_existent_id def test_get_users_superuser_me( @@ -103,10 +103,12 @@ def test_get_existing_user_current_user(client: TestClient, db: Session) -> None def test_get_existing_user_permissions_error( - client: TestClient, normal_user_token_headers: dict[str, str] + db: Session, client: TestClient, normal_user_token_headers: dict[str, str] ) -> None: + non_existent_user_id = get_non_existent_id(db, User) + r = client.get( - f"{settings.API_V1_STR}/users/{uuid.uuid4()}", + f"{settings.API_V1_STR}/users/{non_existent_user_id}", headers=normal_user_token_headers, ) assert r.status_code == 403 @@ -347,11 +349,13 @@ def test_update_user( def test_update_user_not_exists( - client: TestClient, superuser_token_headers: dict[str, str] + db: Session, client: TestClient, superuser_token_headers: dict[str, str] ) -> None: + non_existent_user_id = get_non_existent_id(db, User) + data = {"full_name": "Updated_full_name"} r = client.patch( - f"{settings.API_V1_STR}/users/{uuid.uuid4()}", + f"{settings.API_V1_STR}/users/{non_existent_user_id}", headers=superuser_token_headers, json=data, ) @@ -445,10 +449,12 @@ def test_delete_user_super_user( def test_delete_user_not_found( - client: TestClient, superuser_token_headers: dict[str, str] + db: Session, client: TestClient, superuser_token_headers: dict[str, str] ) -> None: + non_existent_user_id = get_non_existent_id(db, User) + r = client.delete( - f"{settings.API_V1_STR}/users/{uuid.uuid4()}", + f"{settings.API_V1_STR}/users/{non_existent_user_id}", headers=superuser_token_headers, ) assert r.status_code == 404 diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py index fa36ddf0d..8ea01daca 100644 --- a/backend/app/tests/conftest.py +++ b/backend/app/tests/conftest.py @@ -27,8 +27,8 @@ def db() -> Generator[Session, None, None]: yield session # Delete data in reverse dependency order session.execute(delete(ProjectUser)) # Many-to-many relationship - session.execute(delete(Project)) session.execute(delete(Credential)) + session.execute(delete(Project)) session.execute(delete(Organization)) session.execute(delete(APIKey)) session.execute(delete(User)) diff --git a/backend/app/tests/crud/collections/test_crud_collection_read_one.py b/backend/app/tests/crud/collections/test_crud_collection_read_one.py index 6c28f31b4..22edba532 100644 --- a/backend/app/tests/crud/collections/test_crud_collection_read_one.py +++ b/backend/app/tests/crud/collections/test_crud_collection_read_one.py @@ -36,7 +36,7 @@ def test_can_select_valid_id(self, db: Session): def test_cannot_select_others_collections(self, db: Session): collection = mk_collection(db) - other = uuid_increment(collection.owner_id) + other = collection.owner_id + 1 crud = CollectionCrud(db, other) with pytest.raises(NoResultFound): crud.read_one(collection.id) diff --git a/backend/app/tests/crud/documents/test_crud_document_delete.py b/backend/app/tests/crud/documents/test_crud_document_delete.py index 304dd5647..8b42451a5 100644 --- a/backend/app/tests/crud/documents/test_crud_document_delete.py +++ b/backend/app/tests/crud/documents/test_crud_document_delete.py @@ -33,7 +33,7 @@ def test_delete_follows_insert(self, document: Document): def test_cannot_delete_others_documents(self, db: Session): store = DocumentStore(db) document = store.put() - other_owner_id = store.documents.index.peek() + other_owner_id = store.documents.owner_id + 1 crud = DocumentCrud(db, other_owner_id) with pytest.raises(NoResultFound): diff --git a/backend/app/tests/crud/test_org.py b/backend/app/tests/crud/test_org.py index 77ef5b46c..7efba0ec3 100644 --- a/backend/app/tests/crud/test_org.py +++ b/backend/app/tests/crud/test_org.py @@ -2,7 +2,7 @@ from app.crud.organization import create_organization, get_organization_by_id from app.models import Organization, OrganizationCreate -from app.tests.utils.utils import random_lower_string +from app.tests.utils.utils import random_lower_string, get_non_existent_id def test_create_organization(db: Session) -> None: @@ -30,7 +30,8 @@ def test_get_organization_by_id(db: Session) -> None: def test_get_non_existent_organization(db: Session) -> None: """Test retrieving a non-existent organization should return None.""" + org_id = get_non_existent_id(db, Organization) fetched_org = get_organization_by_id( - session=db, org_id=999 + session=db, org_id=org_id ) # Assuming ID 999 does not exist assert fetched_org is None diff --git a/backend/app/tests/crud/test_project_user.py b/backend/app/tests/crud/test_project_user.py index e5eea790c..72025ae66 100644 --- a/backend/app/tests/crud/test_project_user.py +++ b/backend/app/tests/crud/test_project_user.py @@ -5,7 +5,7 @@ from app.crud import project_user as project_user_crud from app.models import ProjectUser, ProjectUserPublic, User, Project, Organization -from app.tests.utils.utils import random_email +from app.tests.utils.utils import random_email, get_non_existent_id from app.core.security import get_password_hash @@ -111,7 +111,7 @@ def test_remove_user_from_project_not_member(db: Session) -> None: organization, project = create_organization_and_project(db) project_id = project.id - user_id = uuid.uuid4() + user_id = get_non_existent_id(db, User) with pytest.raises( ValueError, match="User is not a member of this project or already removed" diff --git a/backend/app/tests/utils/utils.py b/backend/app/tests/utils/utils.py index 271b338ef..2945e8b35 100644 --- a/backend/app/tests/utils/utils.py +++ b/backend/app/tests/utils/utils.py @@ -4,12 +4,16 @@ import pytest from fastapi.testclient import TestClient -from sqlmodel import Session +from sqlmodel import Session, select +from typing import Type, TypeVar from app.core.config import settings from app.crud.user import get_user_by_email +T = TypeVar("T") + + @pytest.fixture(scope="class") def openai_credentials(): settings.OPENAI_API_KEY = "sk-fake123" @@ -40,6 +44,15 @@ def get_user_id_by_email(db: Session): return user.id +def get_non_existent_id(session: Session, model: Type[T]) -> int: + """ + Returns an ID that does not exist for the given model. + It fetches the current max ID and adds 1. + """ + result = session.exec(select(model.id).order_by(model.id.desc())).first() + return (result or 0) + 1 + + class SequentialUuidGenerator: def __init__(self, start=0): self.start = start