From 5aa409a87e3650f40ce78802daccebc0a146e272 Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Mon, 4 May 2026 23:31:56 +0530 Subject: [PATCH 1/7] fix(api): remove the api travilling slash for consistency --- backend/app/api/routes/api_keys.py | 4 +- backend/app/api/routes/assistants.py | 4 +- backend/app/api/routes/collections.py | 4 +- backend/app/api/routes/config/__init__.py | 6 +-- backend/app/api/routes/config/config.py | 4 +- backend/app/api/routes/credentials.py | 8 ++-- .../app/api/routes/doc_transformation_job.py | 2 +- backend/app/api/routes/documents.py | 4 +- backend/app/api/routes/languages.py | 2 +- backend/app/api/routes/login.py | 2 +- backend/app/api/routes/model_config.py | 2 +- backend/app/api/routes/model_evaluation.py | 2 +- backend/app/api/routes/openai_conversation.py | 2 +- backend/app/api/routes/organization.py | 4 +- backend/app/api/routes/private.py | 2 +- backend/app/api/routes/project.py | 4 +- backend/app/api/routes/user_project.py | 4 +- backend/app/api/routes/users.py | 4 +- backend/app/api/routes/utils.py | 4 +- .../collections/test_collection_list.py | 10 ++--- .../tests/api/routes/configs/test_config.py | 16 ++++---- .../tests/api/routes/configs/test_version.py | 2 +- backend/app/tests/api/routes/test_api_key.py | 6 +-- .../app/tests/api/routes/test_assistants.py | 8 ++-- backend/app/tests/api/routes/test_creds.py | 38 +++++++++---------- .../api/routes/test_doc_transformation_job.py | 18 ++++----- .../app/tests/api/routes/test_evaluation.py | 30 +++++++-------- .../app/tests/api/routes/test_languages.py | 8 ++-- backend/app/tests/api/routes/test_login.py | 4 +- .../app/tests/api/routes/test_model_config.py | 8 ++-- .../tests/api/routes/test_model_evaluation.py | 4 +- backend/app/tests/api/routes/test_org.py | 8 ++-- backend/app/tests/api/routes/test_private.py | 2 +- backend/app/tests/api/routes/test_project.py | 8 ++-- backend/app/tests/api/routes/test_users.py | 6 +-- backend/app/tests/api/test_auth_failures.py | 12 +++--- backend/app/tests/api/test_user_project.py | 18 ++++----- .../app/tests/core/test_exception_handlers.py | 4 +- 38 files changed, 139 insertions(+), 139 deletions(-) diff --git a/backend/app/api/routes/api_keys.py b/backend/app/api/routes/api_keys.py index 95e660f17..23eba1177 100644 --- a/backend/app/api/routes/api_keys.py +++ b/backend/app/api/routes/api_keys.py @@ -16,7 +16,7 @@ @router.post( - "/", + "", response_model=APIResponse[APIKeyCreateResponse], status_code=201, dependencies=[Depends(require_permission(Permission.SUPERUSER))], @@ -44,7 +44,7 @@ def create_api_key_route( @router.get( - "/", + "", response_model=APIResponse[list[APIKeyPublic]], dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], description=load_description("api_keys/list.md"), diff --git a/backend/app/api/routes/assistants.py b/backend/app/api/routes/assistants.py index 24f7a502f..6cfc7a245 100644 --- a/backend/app/api/routes/assistants.py +++ b/backend/app/api/routes/assistants.py @@ -50,7 +50,7 @@ def ingest_assistant_route( @router.post( - "/", + "", response_model=APIResponse[Assistant], status_code=201, dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], @@ -126,7 +126,7 @@ def get_assistant_route( @router.get( - "/", + "", response_model=APIResponse[list[Assistant]], summary="List all assistants in the current project", dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], diff --git a/backend/app/api/routes/collections.py b/backend/app/api/routes/collections.py index 0efbdb576..96272d38c 100644 --- a/backend/app/api/routes/collections.py +++ b/backend/app/api/routes/collections.py @@ -60,7 +60,7 @@ def collection_callback_notification(body: APIResponse[CollectionJobPublic]): @router.get( - "/", + "", description=load_description("collections/list.md"), response_model=APIResponse[List[CollectionPublic]], dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], @@ -79,7 +79,7 @@ def list_collections( @router.post( - "/", + "", description=load_description("collections/create.md"), response_model=APIResponse[CollectionJobImmediatePublic], callbacks=collection_callback_router.routes, diff --git a/backend/app/api/routes/config/__init__.py b/backend/app/api/routes/config/__init__.py index 39fbf9203..4a44d2de9 100644 --- a/backend/app/api/routes/config/__init__.py +++ b/backend/app/api/routes/config/__init__.py @@ -2,9 +2,9 @@ from app.api.routes.config import config, version -router = APIRouter(prefix="/configs", tags=["Config Management"]) +router = APIRouter(tags=["Config Management"]) -router.include_router(config.router) -router.include_router(version.router) +router.include_router(config.router, prefix="/configs") +router.include_router(version.router, prefix="/configs") __all__ = ["router"] diff --git a/backend/app/api/routes/config/config.py b/backend/app/api/routes/config/config.py index c08cdba3c..5f819d042 100644 --- a/backend/app/api/routes/config/config.py +++ b/backend/app/api/routes/config/config.py @@ -19,7 +19,7 @@ @router.post( - "/", + "", description=load_description("config/create.md"), response_model=APIResponse[ConfigWithVersion], status_code=201, @@ -44,7 +44,7 @@ def create_config( @router.get( - "/", + "", description=load_description("config/list.md"), response_model=APIResponse[list[ConfigPublic]], status_code=200, diff --git a/backend/app/api/routes/credentials.py b/backend/app/api/routes/credentials.py index be75b5b98..4943330ff 100644 --- a/backend/app/api/routes/credentials.py +++ b/backend/app/api/routes/credentials.py @@ -22,7 +22,7 @@ @router.post( - "/", + "", response_model=APIResponse[list[CredsPublic]], description=load_description("credentials/create.md"), dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], @@ -52,7 +52,7 @@ def create_new_credential( @router.get( - "/", + "", response_model=APIResponse[list[CredsPublic]], description=load_description("credentials/list.md"), dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], @@ -103,7 +103,7 @@ def read_provider_credential( @router.patch( - "/", + "", response_model=APIResponse[list[CredsPublic]], description=load_description("credentials/update.md"), dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], @@ -165,7 +165,7 @@ def delete_provider_credential( @router.delete( - "/", + "", response_model=APIResponse[dict], description=load_description("credentials/delete_all.md"), dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], diff --git a/backend/app/api/routes/doc_transformation_job.py b/backend/app/api/routes/doc_transformation_job.py index 7af491af9..0522b47a9 100644 --- a/backend/app/api/routes/doc_transformation_job.py +++ b/backend/app/api/routes/doc_transformation_job.py @@ -53,7 +53,7 @@ def get_transformation_job( @router.get( - "/", + "", description=load_description("documents/job_list.md"), response_model=APIResponse[DocTransformationJobsPublic], dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], diff --git a/backend/app/api/routes/documents.py b/backend/app/api/routes/documents.py index 3c02ee4a6..eb7d41ca3 100644 --- a/backend/app/api/routes/documents.py +++ b/backend/app/api/routes/documents.py @@ -68,7 +68,7 @@ def doctransformation_callback_notification( @router.get( - "/", + "", description=load_description("documents/list.md"), response_model=APIResponse[list[Union[DocumentPublic, TransformedDocumentPublic]]], dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], @@ -100,7 +100,7 @@ def list_docs( @router.post( - "/", + "", description=load_description("documents/upload.md"), response_model=APIResponse[DocumentUploadResponse], callbacks=doctransformation_callback_router.routes, diff --git a/backend/app/api/routes/languages.py b/backend/app/api/routes/languages.py index 9a184ea81..a1cf6dcab 100644 --- a/backend/app/api/routes/languages.py +++ b/backend/app/api/routes/languages.py @@ -12,7 +12,7 @@ @router.get( - "/", + "", response_model=APIResponse[LanguagesPublic], description=load_description("languages/list.md"), ) diff --git a/backend/app/api/routes/login.py b/backend/app/api/routes/login.py index 704a5e8d7..54c1dbeb1 100644 --- a/backend/app/api/routes/login.py +++ b/backend/app/api/routes/login.py @@ -83,7 +83,7 @@ def recover_password(email: str, session: SessionDep) -> Message: return Message(message="Password recovery email sent") -@router.post("/reset-password/", include_in_schema=False) +@router.post("/reset-password", include_in_schema=False) def reset_password(session: SessionDep, body: NewPassword) -> Message: """ Reset password diff --git a/backend/app/api/routes/model_config.py b/backend/app/api/routes/model_config.py index 565149c7e..751055839 100644 --- a/backend/app/api/routes/model_config.py +++ b/backend/app/api/routes/model_config.py @@ -17,7 +17,7 @@ @router.get( - "/", + "", response_model=APIResponse[ModelConfigListPublic], description=load_description("model_config/list_models.md"), ) diff --git a/backend/app/api/routes/model_evaluation.py b/backend/app/api/routes/model_evaluation.py index c22c78508..8c9d07057 100644 --- a/backend/app/api/routes/model_evaluation.py +++ b/backend/app/api/routes/model_evaluation.py @@ -113,7 +113,7 @@ def run_model_evaluation( @router.post( - "/evaluate_models/", + "/evaluate_models", response_model=APIResponse, description=load_description("model_evaluation/evaluate.md"), dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))], diff --git a/backend/app/api/routes/openai_conversation.py b/backend/app/api/routes/openai_conversation.py index de0be838f..d7fb98d28 100644 --- a/backend/app/api/routes/openai_conversation.py +++ b/backend/app/api/routes/openai_conversation.py @@ -100,7 +100,7 @@ def get_conversation_by_ancestor_id_route( @router.get( - "/", + "", response_model=APIResponse[list[OpenAIConversationPublic]], summary="List all conversations in the current project", description=load_description("openai_conversation/list.md"), diff --git a/backend/app/api/routes/organization.py b/backend/app/api/routes/organization.py index 079e1a508..123d644da 100644 --- a/backend/app/api/routes/organization.py +++ b/backend/app/api/routes/organization.py @@ -22,7 +22,7 @@ # Retrieve organizations @router.get( - "/", + "", dependencies=[Depends(require_permission(Permission.SUPERUSER))], response_model=APIResponse[List[OrganizationPublic]], description=load_description("organization/list.md"), @@ -44,7 +44,7 @@ def read_organizations( # Create a new organization @router.post( - "/", + "", dependencies=[Depends(require_permission(Permission.SUPERUSER))], response_model=APIResponse[OrganizationPublic], description=load_description("organization/create.md"), diff --git a/backend/app/api/routes/private.py b/backend/app/api/routes/private.py index 04c002243..6100829c2 100644 --- a/backend/app/api/routes/private.py +++ b/backend/app/api/routes/private.py @@ -20,7 +20,7 @@ class PrivateUserCreate(BaseModel): is_verified: bool = False -@router.post("/users/", response_model=UserPublic, include_in_schema=False) +@router.post("/users", response_model=UserPublic, include_in_schema=False) def create_user(user_in: PrivateUserCreate, session: SessionDep) -> Any: """ Create a new user. diff --git a/backend/app/api/routes/project.py b/backend/app/api/routes/project.py index 71fcf50ee..bd962358d 100644 --- a/backend/app/api/routes/project.py +++ b/backend/app/api/routes/project.py @@ -22,7 +22,7 @@ # Retrieve projects @router.get( - "/", + "", dependencies=[Depends(require_permission(Permission.SUPERUSER))], response_model=APIResponse[List[ProjectPublic]], description=load_description("projects/list.md"), @@ -44,7 +44,7 @@ def read_projects( # Create a new project @router.post( - "/", + "", dependencies=[Depends(require_permission(Permission.SUPERUSER))], response_model=APIResponse[ProjectPublic], description=load_description("projects/create.md"), diff --git a/backend/app/api/routes/user_project.py b/backend/app/api/routes/user_project.py index 3da8afdca..460f7bf2d 100644 --- a/backend/app/api/routes/user_project.py +++ b/backend/app/api/routes/user_project.py @@ -32,7 +32,7 @@ @router.get( - "/", + "", description=load_description("user_project/list.md"), response_model=APIResponse[list[UserProjectPublic]], ) @@ -47,7 +47,7 @@ def list_project_users( @router.post( - "/", + "", dependencies=[Depends(require_permission(Permission.SUPERUSER))], description=load_description("user_project/add.md"), response_model=APIResponse[list[UserProjectPublic]], diff --git a/backend/app/api/routes/users.py b/backend/app/api/routes/users.py index ba13a6c1c..44385fbb2 100644 --- a/backend/app/api/routes/users.py +++ b/backend/app/api/routes/users.py @@ -31,7 +31,7 @@ @router.get( - "/", + "", dependencies=[Depends(require_permission(Permission.SUPERUSER))], response_model=UsersPublic, include_in_schema=False, @@ -43,7 +43,7 @@ def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: @router.post( - "/", + "", dependencies=[Depends(require_permission(Permission.SUPERUSER))], response_model=UserPublic, include_in_schema=False, diff --git a/backend/app/api/routes/utils.py b/backend/app/api/routes/utils.py index 56247b304..0ee3dadfd 100644 --- a/backend/app/api/routes/utils.py +++ b/backend/app/api/routes/utils.py @@ -9,7 +9,7 @@ @router.post( - "/test-email/", + "/test-email", dependencies=[Depends(require_permission(Permission.SUPERUSER))], status_code=201, include_in_schema=False, @@ -27,6 +27,6 @@ def test_email(email_to: EmailStr) -> Message: return Message(message="Test email sent") -@router.get("/health/", include_in_schema=False) +@router.get("/health", include_in_schema=False) async def health_check() -> bool: return True diff --git a/backend/app/tests/api/routes/collections/test_collection_list.py b/backend/app/tests/api/routes/collections/test_collection_list.py index 368603253..e1cf3d589 100644 --- a/backend/app/tests/api/routes/collections/test_collection_list.py +++ b/backend/app/tests/api/routes/collections/test_collection_list.py @@ -22,7 +22,7 @@ def test_list_collections_returns_api_response( """ response = client.get( - f"{settings.API_V1_STR}/collections/", + f"{settings.API_V1_STR}/collections", headers=user_api_key_header, ) @@ -47,7 +47,7 @@ def test_list_collections_includes_assistant_collection( project = get_project(db, "Dalgo") response_before = client.get( - f"{settings.API_V1_STR}/collections/", + f"{settings.API_V1_STR}/collections", headers=user_api_key_header, ) assert response_before.status_code == 200 @@ -55,7 +55,7 @@ def test_list_collections_includes_assistant_collection( collection = get_assistant_collection(db, project) response_after = client.get( - f"{settings.API_V1_STR}/collections/", + f"{settings.API_V1_STR}/collections", headers=user_api_key_header, ) assert response_after.status_code == 200 @@ -86,7 +86,7 @@ def test_list_collections_includes_vector_store_collection_with_fields( collection = get_vector_store_collection(db, project) response = client.get( - f"{settings.API_V1_STR}/collections/", + f"{settings.API_V1_STR}/collections", headers=user_api_key_header, ) assert response.status_code == 200 @@ -119,7 +119,7 @@ def test_list_collections_does_not_error_with_no_collections( This assumes a clean DB or that there may be zero collections initially. """ response = client.get( - f"{settings.API_V1_STR}/collections/", + f"{settings.API_V1_STR}/collections", headers=user_api_key_header, ) diff --git a/backend/app/tests/api/routes/configs/test_config.py b/backend/app/tests/api/routes/configs/test_config.py index c5f14f2b3..e466bfc3b 100644 --- a/backend/app/tests/api/routes/configs/test_config.py +++ b/backend/app/tests/api/routes/configs/test_config.py @@ -32,7 +32,7 @@ def test_create_config_success( } response = client.post( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, json=config_data, ) @@ -73,7 +73,7 @@ def test_create_config_empty_blob_fails( } response = client.post( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, json=config_data, ) @@ -107,7 +107,7 @@ def test_create_config_duplicate_name_fails( } response = client.post( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, json=config_data, ) @@ -133,7 +133,7 @@ def test_list_configs( created_configs.append(config) response = client.get( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, ) assert response.status_code == 200 @@ -163,7 +163,7 @@ def test_list_configs_with_pagination( # Test with limit response = client.get( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, params={"skip": 0, "limit": 2}, ) @@ -174,7 +174,7 @@ def test_list_configs_with_pagination( # Test with skip response = client.get( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, params={"skip": 2, "limit": 2}, ) @@ -445,7 +445,7 @@ def test_configs_isolated_by_project( # User should only see their project's configs response = client.get( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, ) assert response.status_code == 200 @@ -467,7 +467,7 @@ def test_list_configs_with_query( create_test_config(db=db, project_id=user_api_key.project_id, name="other-config") response = client.get( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, params={"query": "search"}, ) diff --git a/backend/app/tests/api/routes/configs/test_version.py b/backend/app/tests/api/routes/configs/test_version.py index 4ec42f5bd..dd952858b 100644 --- a/backend/app/tests/api/routes/configs/test_version.py +++ b/backend/app/tests/api/routes/configs/test_version.py @@ -754,7 +754,7 @@ def test_create_config_with_kaapi_provider_success( } response = client.post( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, json=config_data, ) diff --git a/backend/app/tests/api/routes/test_api_key.py b/backend/app/tests/api/routes/test_api_key.py index dfcffa92f..0f2d1a5f8 100644 --- a/backend/app/tests/api/routes/test_api_key.py +++ b/backend/app/tests/api/routes/test_api_key.py @@ -21,7 +21,7 @@ def test_create_api_key_as_superuser( project = create_test_project(db) response = client.post( - f"{settings.API_V1_STR}/apikeys/", + f"{settings.API_V1_STR}/apikeys", headers=superuser_token_headers, params={ "project_id": project.id, @@ -48,7 +48,7 @@ def test_create_api_key_as_normal_user_forbidden( ) -> None: """Test that normal users cannot create API keys (superuser only).""" response = client.post( - f"{settings.API_V1_STR}/apikeys/", + f"{settings.API_V1_STR}/apikeys", headers=normal_user_token_headers, params={ "project_id": user_api_key.project_id, @@ -74,7 +74,7 @@ def test_list_api_keys( created_keys.append(key) response = client.get( - f"{settings.API_V1_STR}/apikeys/", + f"{settings.API_V1_STR}/apikeys", headers={"X-API-KEY": user_api_key.key}, ) assert response.status_code == 200 diff --git a/backend/app/tests/api/routes/test_assistants.py b/backend/app/tests/api/routes/test_assistants.py index d54651c2f..1be3e94ba 100644 --- a/backend/app/tests/api/routes/test_assistants.py +++ b/backend/app/tests/api/routes/test_assistants.py @@ -244,7 +244,7 @@ def test_list_assistants_success( assistant = get_assistant(db, project_id=user_api_key.project_id) response = client.get( - "/api/v1/assistant/", + "/api/v1/assistant", headers={"X-API-KEY": f"{user_api_key.key}"}, ) @@ -266,21 +266,21 @@ def test_list_assistants_invalid_pagination( """Test assistants list with invalid pagination parameters.""" # Test negative skip response = client.get( - "/api/v1/assistant/?skip=-1&limit=10", + "/api/v1/assistant?skip=-1&limit=10", headers=user_api_key_header, ) assert response.status_code == 422 # Test limit too high response = client.get( - "/api/v1/assistant/?skip=0&limit=101", + "/api/v1/assistant?skip=0&limit=101", headers=user_api_key_header, ) assert response.status_code == 422 # Test limit too low response = client.get( - "/api/v1/assistant/?skip=0&limit=0", + "/api/v1/assistant?skip=0&limit=0", headers=user_api_key_header, ) assert response.status_code == 422 diff --git a/backend/app/tests/api/routes/test_creds.py b/backend/app/tests/api/routes/test_creds.py index 39f164ecd..c5fd9c1ea 100644 --- a/backend/app/tests/api/routes/test_creds.py +++ b/backend/app/tests/api/routes/test_creds.py @@ -14,7 +14,7 @@ def test_read_credentials( user_api_key: TestAuthContext, ) -> None: response = client.get( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", headers={"X-API-KEY": user_api_key.key}, ) @@ -81,7 +81,7 @@ def test_update_credentials( } response = client.patch( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=update_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -133,7 +133,7 @@ def test_create_credential( } create_response = client.post( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=credential_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -191,7 +191,7 @@ def test_update_nonexistent_provider_upserts( } response = client.patch( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=update_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -222,7 +222,7 @@ def test_create_ignores_mismatched_ids( } response = client.post( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=credential_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -252,7 +252,7 @@ def test_duplicate_credential_creation_fails( } response = client.post( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=duplicate_credential, headers={"X-API-KEY": user_api_key.key}, ) @@ -267,7 +267,7 @@ def test_delete_all_credentials( ) -> None: """Test deleting all credentials for a project.""" response = client.delete( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", headers={"X-API-KEY": user_api_key.key}, ) @@ -276,7 +276,7 @@ def test_delete_all_credentials( assert response_data["data"]["message"] == "All credentials deleted successfully" get_response = client.get( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", headers={"X-API-KEY": user_api_key.key}, ) assert get_response.status_code == 200 @@ -289,12 +289,12 @@ def test_delete_all_when_none_exist_returns_404( ) -> None: """Test deleting when no credentials exist.""" client.delete( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", headers={"X-API-KEY": user_api_key.key}, ) response = client.delete( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", headers={"X-API-KEY": user_api_key.key}, ) @@ -396,7 +396,7 @@ def test_create_credential_missing_credential_field( } response = client.post( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=credential_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -417,7 +417,7 @@ def test_create_credential_empty_credential_dict( } response = client.post( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=credential_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -438,7 +438,7 @@ def test_update_credential_missing_provider_field( } response = client.patch( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=update_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -457,7 +457,7 @@ def test_update_credential_missing_credential_field( } response = client.patch( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=update_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -477,7 +477,7 @@ def test_update_credential_empty_credential( } response = client.patch( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=update_data, headers={"X-API-KEY": user_api_key.key}, ) @@ -493,12 +493,12 @@ def test_read_credentials_when_none_exist( """Test reading credentials when none exist returns an empty list.""" # Delete all credentials first client.delete( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", headers={"X-API-KEY": user_api_key.key}, ) response = client.get( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", headers={"X-API-KEY": user_api_key.key}, ) @@ -513,7 +513,7 @@ def test_create_multiple_providers_at_once( """Test creating credentials for multiple providers in a single request.""" # Delete all credentials first client.delete( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", headers={"X-API-KEY": user_api_key.key}, ) @@ -536,7 +536,7 @@ def test_create_multiple_providers_at_once( } response = client.post( - f"{settings.API_V1_STR}/credentials/", + f"{settings.API_V1_STR}/credentials", json=credential_data, headers={"X-API-KEY": user_api_key.key}, ) diff --git a/backend/app/tests/api/routes/test_doc_transformation_job.py b/backend/app/tests/api/routes/test_doc_transformation_job.py index 298f73ec0..294489b3e 100644 --- a/backend/app/tests/api/routes/test_doc_transformation_job.py +++ b/backend/app/tests/api/routes/test_doc_transformation_job.py @@ -154,7 +154,7 @@ def test_get_multiple_jobs_success( job_ids_params = "&".join(f"job_ids={job.id}" for job in jobs) response = client.get( - f"{settings.API_V1_STR}/documents/transformation/?{job_ids_params}", + f"{settings.API_V1_STR}/documents/transformation?{job_ids_params}", headers={"X-API-KEY": user_api_key.key}, ) @@ -186,7 +186,7 @@ def test_get_mixed_existing_nonexisting_jobs( ) response = client.get( - f"{settings.API_V1_STR}/documents/transformation/?{job_ids_params}", + f"{settings.API_V1_STR}/documents/transformation?{job_ids_params}", headers={"X-API-KEY": user_api_key.key}, ) @@ -201,7 +201,7 @@ def test_get_jobs_with_empty_string( ) -> None: """Test retrieving jobs with empty job_ids parameter.""" response = client.get( - f"{settings.API_V1_STR}/documents/transformation/?job_ids=", + f"{settings.API_V1_STR}/documents/transformation?job_ids=", headers={"X-API-KEY": user_api_key.key}, ) @@ -221,7 +221,7 @@ def test_get_jobs_with_whitespace_only( ) -> None: """Test retrieving jobs with whitespace-only job_ids.""" response = client.get( - f"{settings.API_V1_STR}/documents/transformation/?job_ids= ", + f"{settings.API_V1_STR}/documents/transformation?job_ids= ", headers={"X-API-KEY": user_api_key.key}, ) @@ -239,7 +239,7 @@ def test_get_jobs_invalid_uuid_format_422( invalid_uuid = "not-a-uuid" response = client.get( - f"{settings.API_V1_STR}/documents/transformation/?job_ids={invalid_uuid}", + f"{settings.API_V1_STR}/documents/transformation?job_ids={invalid_uuid}", headers={"X-API-KEY": user_api_key.key}, ) @@ -266,7 +266,7 @@ def test_get_jobs_mixed_valid_invalid_uuid_422( job_ids_params = f"job_ids={job.id}&job_ids=not-a-uuid" response = client.get( - f"{settings.API_V1_STR}/documents/transformation/?{job_ids_params}", + f"{settings.API_V1_STR}/documents/transformation?{job_ids_params}", headers={"X-API-KEY": user_api_key.key}, ) @@ -288,7 +288,7 @@ def test_get_jobs_missing_parameter_422( ) -> None: """Missing job_ids parameter should 422 (Query(min=1)).""" response = client.get( - f"{settings.API_V1_STR}/documents/transformation/", + f"{settings.API_V1_STR}/documents/transformation", headers={"X-API-KEY": user_api_key.key}, ) @@ -314,7 +314,7 @@ def test_get_jobs_different_project_not_found( job = crud.create(DocTransformJobCreate(source_document_id=document.id)) response = client.get( - f"{settings.API_V1_STR}/documents/transformation/?job_ids={job.id}", + f"{settings.API_V1_STR}/documents/transformation?job_ids={job.id}", headers={"X-API-KEY": superuser_api_key.key}, ) @@ -357,7 +357,7 @@ def test_get_jobs_with_various_statuses( job_ids_params = "&".join(f"job_ids={job.id}" for job in jobs) response = client.get( - f"{settings.API_V1_STR}/documents/transformation/?{job_ids_params}", + f"{settings.API_V1_STR}/documents/transformation?{job_ids_params}", headers={"X-API-KEY": user_api_key.key}, ) diff --git a/backend/app/tests/api/routes/test_evaluation.py b/backend/app/tests/api/routes/test_evaluation.py index 17d647aea..8b4e6679c 100644 --- a/backend/app/tests/api/routes/test_evaluation.py +++ b/backend/app/tests/api/routes/test_evaluation.py @@ -82,7 +82,7 @@ def test_upload_dataset_valid_csv( filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -122,7 +122,7 @@ def test_upload_dataset_missing_columns( # The CSV validation happens before any mocked functions are called # so this test checks the actual validation logic response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -164,7 +164,7 @@ def test_upload_dataset_empty_rows( filename, file_obj = create_csv_file(csv_with_empty_rows) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -212,7 +212,7 @@ def test_upload_with_default_duplication( filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -256,7 +256,7 @@ def test_upload_with_custom_duplication( filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -301,7 +301,7 @@ def test_upload_with_description( filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset_with_description", @@ -336,7 +336,7 @@ def test_upload_with_duplication_factor_below_minimum( filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -363,7 +363,7 @@ def test_upload_with_duplication_factor_above_maximum( filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -405,7 +405,7 @@ def test_upload_with_duplication_factor_boundary_minimum( filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -447,7 +447,7 @@ def test_upload_langfuse_configuration_fails( filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -476,7 +476,7 @@ def test_upload_invalid_csv_format( filename, file_obj = create_csv_file(invalid_csv) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -502,7 +502,7 @@ def test_upload_without_authentication(self, client, valid_csv_content): filename, file_obj = create_csv_file(valid_csv_content) response = client.post( - "/api/v1/evaluations/datasets/", + "/api/v1/evaluations/datasets", files={"file": (filename, file_obj, "text/csv")}, data={ "dataset_name": "test_dataset", @@ -538,7 +538,7 @@ def test_start_batch_evaluation_invalid_dataset_id( # Try to start evaluation with non-existent dataset_id response = client.post( - "/api/v1/evaluations/", + "/api/v1/evaluations", json={ "experiment_name": "test_evaluation_run", "dataset_id": 99999, # Non-existent @@ -561,7 +561,7 @@ def test_start_batch_evaluation_invalid_config_id( """Test batch evaluation fails with invalid config_id.""" # Test with a non-existent config_id (random UUID) response = client.post( - "/api/v1/evaluations/", + "/api/v1/evaluations", json={ "experiment_name": "test_no_config", "dataset_id": 1, # Dummy ID, config validation happens first @@ -585,7 +585,7 @@ def test_start_batch_evaluation_without_authentication( ): """Test batch evaluation requires authentication.""" response = client.post( - "/api/v1/evaluations/", + "/api/v1/evaluations", json={ "experiment_name": "test_evaluation_run", "dataset_id": 1, diff --git a/backend/app/tests/api/routes/test_languages.py b/backend/app/tests/api/routes/test_languages.py index 15ccdb81b..c8a87df60 100644 --- a/backend/app/tests/api/routes/test_languages.py +++ b/backend/app/tests/api/routes/test_languages.py @@ -12,7 +12,7 @@ def test_list_languages( ) -> None: """Test retrieving list of all active languages.""" response = client.get( - f"{settings.API_V1_STR}/languages/", + f"{settings.API_V1_STR}/languages", headers=superuser_token_headers, ) @@ -31,7 +31,7 @@ def test_list_languages_with_pagination( ) -> None: """Test retrieving languages with pagination parameters.""" response = client.get( - f"{settings.API_V1_STR}/languages/?skip=0&limit=5", + f"{settings.API_V1_STR}/languages?skip=0&limit=5", headers=superuser_token_headers, ) @@ -84,7 +84,7 @@ def test_list_languages_with_api_key( ) -> None: """Test retrieving languages using API key authentication.""" response = client.get( - f"{settings.API_V1_STR}/languages/", + f"{settings.API_V1_STR}/languages", headers=superuser_api_key_header, ) @@ -115,7 +115,7 @@ def test_get_language_with_api_key( def test_list_languages_unauthorized(client: TestClient) -> None: """Test that listing languages without authentication returns 401.""" - response = client.get(f"{settings.API_V1_STR}/languages/") + response = client.get(f"{settings.API_V1_STR}/languages") assert response.status_code == 401 diff --git a/backend/app/tests/api/routes/test_login.py b/backend/app/tests/api/routes/test_login.py index 34074546a..67ce7feb1 100644 --- a/backend/app/tests/api/routes/test_login.py +++ b/backend/app/tests/api/routes/test_login.py @@ -72,7 +72,7 @@ def test_reset_password(client: TestClient, db: Session) -> None: data = {"new_password": new_password, "token": token} r = client.post( - f"{settings.API_V1_STR}/reset-password/", + f"{settings.API_V1_STR}/reset-password", headers=headers, json=data, ) @@ -89,7 +89,7 @@ def test_reset_password_invalid_token( ) -> None: data = {"new_password": "changethis", "token": "invalid"} r = client.post( - f"{settings.API_V1_STR}/reset-password/", + f"{settings.API_V1_STR}/reset-password", headers=superuser_token_headers, json=data, ) diff --git a/backend/app/tests/api/routes/test_model_config.py b/backend/app/tests/api/routes/test_model_config.py index 4f111194e..e63b0bbbb 100644 --- a/backend/app/tests/api/routes/test_model_config.py +++ b/backend/app/tests/api/routes/test_model_config.py @@ -7,7 +7,7 @@ def test_list_models( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: response = client.get( - f"{settings.API_V1_STR}/models/", + f"{settings.API_V1_STR}/models", headers=superuser_token_headers, ) @@ -23,7 +23,7 @@ def test_list_models_has_more( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: response = client.get( - f"{settings.API_V1_STR}/models/?skip=0&limit=1", + f"{settings.API_V1_STR}/models?skip=0&limit=1", headers=superuser_token_headers, ) @@ -38,7 +38,7 @@ def test_list_models_filter_by_provider( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: response = client.get( - f"{settings.API_V1_STR}/models/?provider=openai&limit=5", + f"{settings.API_V1_STR}/models?provider=openai&limit=5", headers=superuser_token_headers, ) @@ -52,7 +52,7 @@ def test_list_models_invalid_limit( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: response = client.get( - f"{settings.API_V1_STR}/models/?skip=0&limit=0", + f"{settings.API_V1_STR}/models?skip=0&limit=0", headers=superuser_token_headers, ) assert response.status_code == 422 diff --git a/backend/app/tests/api/routes/test_model_evaluation.py b/backend/app/tests/api/routes/test_model_evaluation.py index 03c1177f4..ea485678d 100644 --- a/backend/app/tests/api/routes/test_model_evaluation.py +++ b/backend/app/tests/api/routes/test_model_evaluation.py @@ -21,7 +21,7 @@ def test_evaluate_model( body = {"fine_tuning_ids": [fine_tuned[0].id]} resp = client.post( - "/api/v1/model_evaluation/evaluate_models/", + "/api/v1/model_evaluation/evaluate_models", json=body, headers=user_api_key_header, ) @@ -44,7 +44,7 @@ def test_evaluate_model_finetuning_not_found( body = {"fine_tuning_ids": [invalid_fine_tune_id]} response = client.post( - "/api/v1/model_evaluation/evaluate_models/", + "/api/v1/model_evaluation/evaluate_models", json=body, headers=user_api_key_header, ) diff --git a/backend/app/tests/api/routes/test_org.py b/backend/app/tests/api/routes/test_org.py index d263dd6f3..7daae61be 100644 --- a/backend/app/tests/api/routes/test_org.py +++ b/backend/app/tests/api/routes/test_org.py @@ -23,7 +23,7 @@ def test_create_organization( org_name = "Test-Org" org_data = {"name": org_name, "is_active": True} response = client.post( - f"{settings.API_V1_STR}/organizations/", + f"{settings.API_V1_STR}/organizations", json=org_data, headers=superuser_token_headers, ) @@ -43,7 +43,7 @@ def test_read_organizations( db: Session, superuser_token_headers: dict[str, str] ) -> None: response = client.get( - f"{settings.API_V1_STR}/organizations/", headers=superuser_token_headers + f"{settings.API_V1_STR}/organizations", headers=superuser_token_headers ) assert response.status_code == 200 response_data = response.json() @@ -101,7 +101,7 @@ def test_read_organizations_has_more( create_test_organization(db) response = client.get( - f"{settings.API_V1_STR}/organizations/?skip=0&limit=1", + f"{settings.API_V1_STR}/organizations?skip=0&limit=1", headers=superuser_token_headers, ) assert response.status_code == 200 @@ -111,7 +111,7 @@ def test_read_organizations_has_more( # Request all with large limit to verify has_more=False response = client.get( - f"{settings.API_V1_STR}/organizations/?skip=0&limit=100", + f"{settings.API_V1_STR}/organizations?skip=0&limit=100", headers=superuser_token_headers, ) assert response.status_code == 200 diff --git a/backend/app/tests/api/routes/test_private.py b/backend/app/tests/api/routes/test_private.py index 1e1f98502..1b5a3794c 100644 --- a/backend/app/tests/api/routes/test_private.py +++ b/backend/app/tests/api/routes/test_private.py @@ -7,7 +7,7 @@ def test_create_user(client: TestClient, db: Session) -> None: r = client.post( - f"{settings.API_V1_STR}/private/users/", + f"{settings.API_V1_STR}/private/users", json={ "email": "pollo@listo.com", "password": "password123", diff --git a/backend/app/tests/api/routes/test_project.py b/backend/app/tests/api/routes/test_project.py index cdf464495..487bec70f 100644 --- a/backend/app/tests/api/routes/test_project.py +++ b/backend/app/tests/api/routes/test_project.py @@ -32,7 +32,7 @@ def test_create_new_project( ) response = client.post( - f"{settings.API_V1_STR}/projects/", + f"{settings.API_V1_STR}/projects", json=project_data.dict(), headers=superuser_token_headers, ) @@ -52,7 +52,7 @@ def test_create_new_project( # Test retrieving projects def test_read_projects(db: Session, superuser_token_headers: dict[str, str]) -> None: response = client.get( - f"{settings.API_V1_STR}/projects/", headers=superuser_token_headers + f"{settings.API_V1_STR}/projects", headers=superuser_token_headers ) assert response.status_code == 200 response_data = response.json() @@ -68,7 +68,7 @@ def test_read_projects_has_more( create_test_project(db) response = client.get( - f"{settings.API_V1_STR}/projects/?skip=0&limit=1", + f"{settings.API_V1_STR}/projects?skip=0&limit=1", headers=superuser_token_headers, ) assert response.status_code == 200 @@ -77,7 +77,7 @@ def test_read_projects_has_more( assert response_data["metadata"]["has_more"] is True response = client.get( - f"{settings.API_V1_STR}/projects/?skip=0&limit=100", + f"{settings.API_V1_STR}/projects?skip=0&limit=100", headers=superuser_token_headers, ) assert response.status_code == 200 diff --git a/backend/app/tests/api/routes/test_users.py b/backend/app/tests/api/routes/test_users.py index cbf1aaaba..4b1e2113f 100644 --- a/backend/app/tests/api/routes/test_users.py +++ b/backend/app/tests/api/routes/test_users.py @@ -101,7 +101,7 @@ def test_create_user_existing_username( crud.create_user(session=db, user_create=user_in) data = {"email": username, "password": password} r = client.post( - f"{settings.API_V1_STR}/users/", + f"{settings.API_V1_STR}/users", headers=superuser_token_headers, json=data, ) @@ -117,7 +117,7 @@ def test_create_user_by_normal_user( password = random_lower_string() data = {"email": username, "password": password} r = client.post( - f"{settings.API_V1_STR}/users/", + f"{settings.API_V1_STR}/users", headers=normal_user_token_headers, json=data, ) @@ -137,7 +137,7 @@ def test_retrieve_users( user_in2 = UserCreate(email=username2, password=password2) crud.create_user(session=db, user_create=user_in2) - r = client.get(f"{settings.API_V1_STR}/users/", headers=superuser_token_headers) + r = client.get(f"{settings.API_V1_STR}/users", headers=superuser_token_headers) all_users = r.json() assert len(all_users["data"]) > 1 diff --git a/backend/app/tests/api/test_auth_failures.py b/backend/app/tests/api/test_auth_failures.py index aaab64cab..1fe0b13d4 100644 --- a/backend/app/tests/api/test_auth_failures.py +++ b/backend/app/tests/api/test_auth_failures.py @@ -5,8 +5,8 @@ PROTECTED_ENDPOINTS = [ - (f"{settings.API_V1_STR}/collections/", "GET"), - (f"{settings.API_V1_STR}/collections/", "POST"), + (f"{settings.API_V1_STR}/collections", "GET"), + (f"{settings.API_V1_STR}/collections", "POST"), (f"{settings.API_V1_STR}/collections/12345678-1234-5678-1234-567812345678", "GET"), ( f"{settings.API_V1_STR}/collections/12345678-1234-5678-1234-567812345678", @@ -16,8 +16,8 @@ f"{settings.API_V1_STR}/collections/jobs/12345678-1234-5678-1234-567812345678", "GET", ), - (f"{settings.API_V1_STR}/documents/", "GET"), - (f"{settings.API_V1_STR}/documents/", "POST"), + (f"{settings.API_V1_STR}/documents", "GET"), + (f"{settings.API_V1_STR}/documents", "POST"), (f"{settings.API_V1_STR}/documents/12345678-1234-5678-1234-567812345678", "GET"), (f"{settings.API_V1_STR}/documents/12345678-1234-5678-1234-567812345678", "DELETE"), ( @@ -25,8 +25,8 @@ "GET", ), (f"{settings.API_V1_STR}/cron/evaluations", "GET"), - (f"{settings.API_V1_STR}/evaluations/datasets/", "POST"), - (f"{settings.API_V1_STR}/evaluations/datasets/", "GET"), + (f"{settings.API_V1_STR}/evaluations/datasets", "POST"), + (f"{settings.API_V1_STR}/evaluations/datasets", "GET"), ( f"{settings.API_V1_STR}/evaluations/datasets/12345678-1234-5678-1234-567812345678", "GET", diff --git a/backend/app/tests/api/test_user_project.py b/backend/app/tests/api/test_user_project.py index dd4f6cceb..40ae6d429 100644 --- a/backend/app/tests/api/test_user_project.py +++ b/backend/app/tests/api/test_user_project.py @@ -14,7 +14,7 @@ class TestListProjectUsers: - """Test suite for GET /user-projects/""" + """Test suite for GET /user-projects""" def test_list_returns_empty( self, @@ -23,7 +23,7 @@ def test_list_returns_empty( ): """Test listing users for a project with no users.""" resp = client.get( - f"{USER_PROJECTS_URL}/?project_id=99999", + f"{USER_PROJECTS_URL}?project_id=99999", headers=superuser_token_headers, ) assert resp.status_code == 200 @@ -47,7 +47,7 @@ def test_list_returns_users( db.commit() resp = client.get( - f"{USER_PROJECTS_URL}/?project_id={project.id}", + f"{USER_PROJECTS_URL}?project_id={project.id}", headers=superuser_token_headers, ) assert resp.status_code == 200 @@ -57,7 +57,7 @@ def test_list_returns_users( class TestAddProjectUsers: - """Test suite for POST /user-projects/""" + """Test suite for POST /user-projects""" def test_add_user_requires_superuser( self, @@ -68,7 +68,7 @@ def test_add_user_requires_superuser( """Test non-superuser cannot add users.""" project = create_test_project(db) resp = client.post( - f"{USER_PROJECTS_URL}/", + USER_PROJECTS_URL, json={ "organization_id": project.organization_id, "project_id": project.id, @@ -89,7 +89,7 @@ def test_add_single_user( email = random_email() resp = client.post( - f"{USER_PROJECTS_URL}/", + USER_PROJECTS_URL, json={ "organization_id": project.organization_id, "project_id": project.id, @@ -124,7 +124,7 @@ def test_add_user_sends_invite_email( mock_settings.PROJECT_NAME = "Kaapi" resp = client.post( - f"{USER_PROJECTS_URL}/", + USER_PROJECTS_URL, json={ "organization_id": project.organization_id, "project_id": project.id, @@ -156,7 +156,7 @@ def test_add_duplicate_user_same_project( # Try adding again resp = client.post( - f"{USER_PROJECTS_URL}/", + USER_PROJECTS_URL, json={ "organization_id": project.organization_id, "project_id": project.id, @@ -187,7 +187,7 @@ def test_add_user_different_project_returns_409( db.commit() resp = client.post( - f"{USER_PROJECTS_URL}/", + USER_PROJECTS_URL, json={ "organization_id": project2.organization_id, "project_id": project2.id, diff --git a/backend/app/tests/core/test_exception_handlers.py b/backend/app/tests/core/test_exception_handlers.py index da6aa7b5b..4955136be 100644 --- a/backend/app/tests/core/test_exception_handlers.py +++ b/backend/app/tests/core/test_exception_handlers.py @@ -112,7 +112,7 @@ def test_structured_error_format( self, client: TestClient, user_api_key: TestAuthContext ) -> None: response = client.post( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, json={}, ) @@ -127,7 +127,7 @@ def test_union_noise_filtered( self, client: TestClient, user_api_key: TestAuthContext ) -> None: response = client.post( - f"{settings.API_V1_STR}/configs/", + f"{settings.API_V1_STR}/configs", headers={"X-API-KEY": user_api_key.key}, json={ "name": "test-config", From 2d4bfb29bc3c4bbf0833598f0946b19db21a2562 Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Tue, 5 May 2026 12:11:57 +0530 Subject: [PATCH 2/7] fix(api): add the tralling slash backward compatibility issue --- backend/app/core/middleware.py | 26 ++++++++++++++++ backend/app/main.py | 3 +- .../tests/core/test_trailing_slash_compat.py | 31 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 backend/app/tests/core/test_trailing_slash_compat.py diff --git a/backend/app/core/middleware.py b/backend/app/core/middleware.py index 153a41947..c1f5571d1 100644 --- a/backend/app/core/middleware.py +++ b/backend/app/core/middleware.py @@ -8,6 +8,32 @@ logger = logging.getLogger("http_request_logger") +class StripTrailingSlashMiddleware: + """ + Rewrite '/foo/' to '/foo' before routing so both forms hit the same handler. + + Why: removing trailing slashes from declared routes would break clients + that don't follow 307 redirects on POST/PUT/DELETE or that drop the + Authorization header across redirects. This middleware preserves the + trailing-slash form during the deprecation window — to be removed once + integrators have migrated. + """ + + def __init__(self, app): + self.app = app + + async def __call__(self, scope, receive, send): + if scope["type"] == "http": + path = scope["path"] + if len(path) > 1 and path.endswith("/"): + scope = dict(scope) + scope["path"] = path[:-1] + raw_path = scope.get("raw_path") + if raw_path is not None and raw_path.endswith(b"/"): + scope["raw_path"] = raw_path[:-1] + await self.app(scope, receive, send) + + def _resolve_http_route(request: Request) -> str: """ Resolve the HTTP route for telemetry and logging. diff --git a/backend/app/main.py b/backend/app/main.py index cb965fe4d..73a791cee 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -17,7 +17,7 @@ from app.core.config import settings from app.core.exception_handlers import register_exception_handlers from app.core.logger import configure_logging -from app.core.middleware import http_request_logger +from app.core.middleware import StripTrailingSlashMiddleware, http_request_logger from app.core.sentry_filters import before_send_transaction_filter from app.core.telemetry import instrument_app, setup_telemetry @@ -91,6 +91,7 @@ def custom_openapi(): app.middleware("http")(http_request_logger) app.add_middleware(CorrelationIdMiddleware) +app.add_middleware(StripTrailingSlashMiddleware) app.include_router(api_router, prefix=settings.API_V1_STR) diff --git a/backend/app/tests/core/test_trailing_slash_compat.py b/backend/app/tests/core/test_trailing_slash_compat.py new file mode 100644 index 000000000..e4a14c920 --- /dev/null +++ b/backend/app/tests/core/test_trailing_slash_compat.py @@ -0,0 +1,31 @@ +from fastapi.testclient import TestClient + +from app.core.config import settings + + +def test_trailing_slash_routes_to_canonical(client: TestClient) -> None: + """Both '/health' and '/health/' must hit the handler directly (no redirect).""" + canonical = client.get("/health") + legacy = client.get("/health/", follow_redirects=False) + + assert canonical.status_code == 200 + assert legacy.status_code == 200 + assert legacy.json() == canonical.json() + + +def test_trailing_slash_preserves_query_string( + client: TestClient, superuser_token_headers: dict[str, str] +) -> None: + """Query string survives the rewrite from '/users/?...' to '/users?...'.""" + response = client.get( + f"{settings.API_V1_STR}/users/?skip=0&limit=1", + headers=superuser_token_headers, + follow_redirects=False, + ) + assert response.status_code == 200 + + +def test_root_path_unaffected(client: TestClient) -> None: + """The bare '/' must not be rewritten to ''.""" + response = client.get("/", follow_redirects=False) + assert response.status_code != 500 From c21bd331f1c01bfea9a467d3707bc2387e1041e8 Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Tue, 5 May 2026 13:10:56 +0530 Subject: [PATCH 3/7] fix(api): js comment --- backend/app/core/middleware.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/app/core/middleware.py b/backend/app/core/middleware.py index c1f5571d1..8bf9ea899 100644 --- a/backend/app/core/middleware.py +++ b/backend/app/core/middleware.py @@ -11,12 +11,6 @@ class StripTrailingSlashMiddleware: """ Rewrite '/foo/' to '/foo' before routing so both forms hit the same handler. - - Why: removing trailing slashes from declared routes would break clients - that don't follow 307 redirects on POST/PUT/DELETE or that drop the - Authorization header across redirects. This middleware preserves the - trailing-slash form during the deprecation window — to be removed once - integrators have migrated. """ def __init__(self, app): From 471141aff7c4ffe9ef85bb5efeef49971b2ffeef Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Tue, 5 May 2026 13:34:27 +0530 Subject: [PATCH 4/7] fix(api): intilize the docuemnts router after doc transformation --- backend/app/api/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/api/main.py b/backend/app/api/main.py index 292e4d1b7..b82fd45ad 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -40,8 +40,8 @@ api_router.include_router(config.router) api_router.include_router(credentials.router) api_router.include_router(cron.router) -api_router.include_router(documents.router) api_router.include_router(doc_transformation_job.router) +api_router.include_router(documents.router) api_router.include_router(auth.router) api_router.include_router(evaluations.router) api_router.include_router(languages.router) From 768016fb02269162b67103d0828cdfaffb89d798 Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Tue, 5 May 2026 14:50:41 +0530 Subject: [PATCH 5/7] fix(api): few small updates for prefix --- backend/app/api/routes/config/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/app/api/routes/config/__init__.py b/backend/app/api/routes/config/__init__.py index 4a44d2de9..39fbf9203 100644 --- a/backend/app/api/routes/config/__init__.py +++ b/backend/app/api/routes/config/__init__.py @@ -2,9 +2,9 @@ from app.api.routes.config import config, version -router = APIRouter(tags=["Config Management"]) +router = APIRouter(prefix="/configs", tags=["Config Management"]) -router.include_router(config.router, prefix="/configs") -router.include_router(version.router, prefix="/configs") +router.include_router(config.router) +router.include_router(version.router) __all__ = ["router"] From e5ce437add255fe14e6e8906698939104e725d64 Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Tue, 5 May 2026 15:07:02 +0530 Subject: [PATCH 6/7] fix(api): few small updates for prefix --- backend/app/api/routes/config/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/app/api/routes/config/__init__.py b/backend/app/api/routes/config/__init__.py index 39fbf9203..4a44d2de9 100644 --- a/backend/app/api/routes/config/__init__.py +++ b/backend/app/api/routes/config/__init__.py @@ -2,9 +2,9 @@ from app.api.routes.config import config, version -router = APIRouter(prefix="/configs", tags=["Config Management"]) +router = APIRouter(tags=["Config Management"]) -router.include_router(config.router) -router.include_router(version.router) +router.include_router(config.router, prefix="/configs") +router.include_router(version.router, prefix="/configs") __all__ = ["router"] From ba8106f9b9850a4cb04b131f04c0113d3b97af9b Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Tue, 5 May 2026 17:16:26 +0530 Subject: [PATCH 7/7] fix(api): coderabbit suggestion implemented --- backend/app/core/middleware.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/app/core/middleware.py b/backend/app/core/middleware.py index 8bf9ea899..21546db7a 100644 --- a/backend/app/core/middleware.py +++ b/backend/app/core/middleware.py @@ -4,6 +4,7 @@ import sentry_sdk from fastapi import Request, Response from opentelemetry import trace +from starlette.types import ASGIApp, Receive, Scope, Send logger = logging.getLogger("http_request_logger") @@ -13,10 +14,10 @@ class StripTrailingSlashMiddleware: Rewrite '/foo/' to '/foo' before routing so both forms hit the same handler. """ - def __init__(self, app): + def __init__(self, app: ASGIApp) -> None: self.app = app - async def __call__(self, scope, receive, send): + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] == "http": path = scope["path"] if len(path) > 1 and path.endswith("/"):