From fadb20115d2f8757b485a0cedf22139516512df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dtalo=20Johnny?= Date: Mon, 28 Oct 2024 17:06:05 -0300 Subject: [PATCH] test: add unit tests for routes (#4249) --- src/backend/base/langflow/api/v1/api_key.py | 22 +-- src/backend/base/langflow/api/v1/endpoints.py | 6 +- src/backend/base/langflow/api/v1/flows.py | 14 +- src/backend/base/langflow/api/v1/folders.py | 8 +- .../base/langflow/api/v1/starter_projects.py | 2 +- src/backend/base/langflow/api/v1/store.py | 2 +- src/backend/base/langflow/api/v1/users.py | 12 +- src/backend/base/langflow/api/v1/validate.py | 4 +- src/backend/base/langflow/api/v1/variable.py | 8 +- src/backend/tests/conftest.py | 40 +++- src/backend/tests/integration/test_misc.py | 8 +- src/backend/tests/unit/api/v1/test_api_key.py | 64 +++++++ .../tests/unit/api/v1/test_endpoints.py | 36 ++++ src/backend/tests/unit/api/v1/test_flows.py | 172 ++++++++++++++++++ src/backend/tests/unit/api/v1/test_folders.py | 64 +++++++ .../unit/api/v1/test_starter_projects.py | 10 + src/backend/tests/unit/api/v1/test_store.py | 12 ++ src/backend/tests/unit/api/v1/test_users.py | 98 ++++++++++ .../tests/unit/api/v1/test_validate.py | 56 ++++++ .../starter_projects/test_memory_chatbot.py | 1 + src/backend/tests/unit/test_database.py | 91 ++++----- src/backend/tests/unit/test_endpoints.py | 151 --------------- src/backend/tests/unit/test_user.py | 14 -- 23 files changed, 623 insertions(+), 272 deletions(-) create mode 100644 src/backend/tests/unit/api/v1/test_api_key.py create mode 100644 src/backend/tests/unit/api/v1/test_endpoints.py create mode 100644 src/backend/tests/unit/api/v1/test_flows.py create mode 100644 src/backend/tests/unit/api/v1/test_folders.py create mode 100644 src/backend/tests/unit/api/v1/test_starter_projects.py create mode 100644 src/backend/tests/unit/api/v1/test_store.py create mode 100644 src/backend/tests/unit/api/v1/test_users.py create mode 100644 src/backend/tests/unit/api/v1/test_validate.py diff --git a/src/backend/base/langflow/api/v1/api_key.py b/src/backend/base/langflow/api/v1/api_key.py index 42ff58546..c5d78c78a 100644 --- a/src/backend/base/langflow/api/v1/api_key.py +++ b/src/backend/base/langflow/api/v1/api_key.py @@ -19,7 +19,7 @@ router = APIRouter(tags=["APIKey"], prefix="/api_key") @router.get("/") -def get_api_keys_route( +async def get_api_keys_route( db: DbSession, current_user: CurrentActiveUser, ) -> ApiKeysResponse: @@ -33,7 +33,7 @@ def get_api_keys_route( @router.post("/") -def create_api_key_route( +async def create_api_key_route( req: ApiKeyCreate, current_user: CurrentActiveUser, db: DbSession, @@ -46,7 +46,7 @@ def create_api_key_route( @router.delete("/{api_key_id}", dependencies=[Depends(auth_utils.get_current_active_user)]) -def delete_api_key_route( +async def delete_api_key_route( api_key_id: UUID, db: DbSession, ): @@ -58,7 +58,7 @@ def delete_api_key_route( @router.post("/store") -def save_store_api_key( +async def save_store_api_key( api_key_request: ApiKeyCreateRequest, response: Response, current_user: CurrentActiveUser, @@ -90,17 +90,3 @@ def save_store_api_key( raise HTTPException(status_code=400, detail=str(e)) from e return {"detail": "API Key saved"} - - -@router.delete("/store") -def delete_store_api_key( - current_user: CurrentActiveUser, - db: DbSession, -): - try: - current_user.store_api_key = None - db.commit() - except Exception as e: - raise HTTPException(status_code=400, detail=str(e)) from e - - return {"detail": "API Key deleted"} diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index 0c4a213e0..38dc6c297 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -549,7 +549,7 @@ async def create_upload_file( # get endpoint to return version of langflow @router.get("/version") -def get_version(): +async def get_version(): return get_version_info() @@ -625,7 +625,7 @@ async def custom_component_update( @router.get("/config", response_model=ConfigResponse) -def get_config(): +async def get_config(): try: from langflow.services.deps import get_settings_service @@ -637,5 +637,5 @@ def get_config(): @router.get("/sidebar_categories") -def get_sidebar_categories() -> SidebarCategoriesResponse: +async def get_sidebar_categories() -> SidebarCategoriesResponse: return SidebarCategoriesResponse(categories=SIDEBAR_CATEGORIES) diff --git a/src/backend/base/langflow/api/v1/flows.py b/src/backend/base/langflow/api/v1/flows.py index db2e6a3b9..0e3b7be63 100644 --- a/src/backend/base/langflow/api/v1/flows.py +++ b/src/backend/base/langflow/api/v1/flows.py @@ -35,7 +35,7 @@ router = APIRouter(prefix="/flows", tags=["Flows"]) @router.post("/", response_model=FlowRead, status_code=201) -def create_flow( +async def create_flow( *, session: DbSession, flow: FlowCreate, @@ -124,7 +124,7 @@ def create_flow( @router.get("/", response_model=list[FlowRead] | Page[FlowRead] | list[FlowHeader], status_code=200) -def read_flows( +async def read_flows( *, current_user: CurrentActiveUser, session: DbSession, @@ -226,7 +226,7 @@ def _read_flow( @router.get("/{flow_id}", response_model=FlowRead, status_code=200) -def read_flow( +async def read_flow( *, session: DbSession, flow_id: UUID, @@ -239,7 +239,7 @@ def read_flow( @router.patch("/{flow_id}", response_model=FlowRead, status_code=200) -def update_flow( +async def update_flow( *, session: DbSession, flow_id: UUID, @@ -320,7 +320,7 @@ async def delete_flow( @router.post("/batch/", response_model=list[FlowRead], status_code=201) -def create_flows( +async def create_flows( *, session: DbSession, flow_list: FlowListCreate, @@ -357,7 +357,7 @@ async def upload_file( flow.user_id = current_user.id if folder_id: flow.folder_id = folder_id - response = create_flow(session=session, flow=flow, current_user=current_user) + response = await create_flow(session=session, flow=flow, current_user=current_user) response_list.append(response) return response_list @@ -442,7 +442,7 @@ async def download_multiple_file( @router.get("/basic_examples/", response_model=list[FlowRead], status_code=200) -def read_basic_examples( +async def read_basic_examples( *, session: DbSession, ): diff --git a/src/backend/base/langflow/api/v1/folders.py b/src/backend/base/langflow/api/v1/folders.py index 15b4d2abf..1e14f2836 100644 --- a/src/backend/base/langflow/api/v1/folders.py +++ b/src/backend/base/langflow/api/v1/folders.py @@ -28,7 +28,7 @@ router = APIRouter(prefix="/folders", tags=["Folders"]) @router.post("/", response_model=FolderRead, status_code=201) -def create_folder( +async def create_folder( *, session: DbSession, folder: FolderCreate, @@ -82,7 +82,7 @@ def create_folder( @router.get("/", response_model=list[FolderRead], status_code=200) -def read_folders( +async def read_folders( *, session: DbSession, current_user: CurrentActiveUser, @@ -100,7 +100,7 @@ def read_folders( @router.get("/{folder_id}", response_model=FolderWithPaginatedFlows | FolderReadWithFlows, status_code=200) -def read_folder( +async def read_folder( *, session: DbSession, folder_id: str, @@ -145,7 +145,7 @@ def read_folder( @router.patch("/{folder_id}", response_model=FolderRead, status_code=200) -def update_folder( +async def update_folder( *, session: DbSession, folder_id: str, diff --git a/src/backend/base/langflow/api/v1/starter_projects.py b/src/backend/base/langflow/api/v1/starter_projects.py index 1f76ab4e9..8e8b99a84 100644 --- a/src/backend/base/langflow/api/v1/starter_projects.py +++ b/src/backend/base/langflow/api/v1/starter_projects.py @@ -7,7 +7,7 @@ router = APIRouter(prefix="/starter-projects", tags=["Flows"]) @router.get("/", dependencies=[Depends(get_current_active_user)], status_code=200) -def get_starter_projects() -> list[GraphDump]: +async def get_starter_projects() -> list[GraphDump]: """Get a list of starter projects.""" from langflow.initial_setup.load import get_starter_projects_dump diff --git a/src/backend/base/langflow/api/v1/store.py b/src/backend/base/langflow/api/v1/store.py index 33b11b7fd..418424667 100644 --- a/src/backend/base/langflow/api/v1/store.py +++ b/src/backend/base/langflow/api/v1/store.py @@ -40,7 +40,7 @@ def get_optional_user_store_api_key(user: CurrentActiveUser): @router.get("/check/") -def check_if_store_is_enabled(): +async def check_if_store_is_enabled(): return { "enabled": get_settings_service().settings.store, } diff --git a/src/backend/base/langflow/api/v1/users.py b/src/backend/base/langflow/api/v1/users.py index 5322332fd..a2fcbc42c 100644 --- a/src/backend/base/langflow/api/v1/users.py +++ b/src/backend/base/langflow/api/v1/users.py @@ -23,7 +23,7 @@ router = APIRouter(tags=["Users"], prefix="/users") @router.post("/", response_model=UserRead, status_code=201) -def add_user( +async def add_user( user: UserCreate, session: DbSession, ) -> User: @@ -46,7 +46,7 @@ def add_user( @router.get("/whoami", response_model=UserRead) -def read_current_user( +async def read_current_user( current_user: CurrentActiveUser, ) -> User: """Retrieve the current user's data.""" @@ -54,7 +54,7 @@ def read_current_user( @router.get("/", dependencies=[Depends(get_current_active_superuser)]) -def read_all_users( +async def read_all_users( *, skip: int = 0, limit: int = 10, @@ -74,7 +74,7 @@ def read_all_users( @router.patch("/{user_id}", response_model=UserRead) -def patch_user( +async def patch_user( user_id: UUID, user_update: UserUpdate, user: CurrentActiveUser, @@ -101,7 +101,7 @@ def patch_user( @router.patch("/{user_id}/reset-password", response_model=UserRead) -def reset_password( +async def reset_password( user_id: UUID, user_update: UserUpdate, user: CurrentActiveUser, @@ -124,7 +124,7 @@ def reset_password( @router.delete("/{user_id}") -def delete_user( +async def delete_user( user_id: UUID, current_user: Annotated[User, Depends(get_current_active_superuser)], session: DbSession, diff --git a/src/backend/base/langflow/api/v1/validate.py b/src/backend/base/langflow/api/v1/validate.py index 9f205d767..06617152d 100644 --- a/src/backend/base/langflow/api/v1/validate.py +++ b/src/backend/base/langflow/api/v1/validate.py @@ -10,7 +10,7 @@ router = APIRouter(prefix="/validate", tags=["Validate"]) @router.post("/code", status_code=200) -def post_validate_code(code: Code) -> CodeValidationResponse: +async def post_validate_code(code: Code) -> CodeValidationResponse: try: errors = validate_code(code.code) return CodeValidationResponse( @@ -23,7 +23,7 @@ def post_validate_code(code: Code) -> CodeValidationResponse: @router.post("/prompt", status_code=200) -def post_validate_prompt(prompt_request: ValidatePromptRequest) -> PromptValidationResponse: +async def post_validate_prompt(prompt_request: ValidatePromptRequest) -> PromptValidationResponse: try: if not prompt_request.frontend_node: return PromptValidationResponse( diff --git a/src/backend/base/langflow/api/v1/variable.py b/src/backend/base/langflow/api/v1/variable.py index 5fae52009..5b3e3e6e8 100644 --- a/src/backend/base/langflow/api/v1/variable.py +++ b/src/backend/base/langflow/api/v1/variable.py @@ -13,7 +13,7 @@ router = APIRouter(prefix="/variables", tags=["Variables"]) @router.post("/", response_model=VariableRead, status_code=201) -def create_variable( +async def create_variable( *, session: DbSession, variable: VariableCreate, @@ -48,7 +48,7 @@ def create_variable( @router.get("/", response_model=list[VariableRead], status_code=200) -def read_variables( +async def read_variables( *, session: DbSession, current_user: CurrentActiveUser, @@ -65,7 +65,7 @@ def read_variables( @router.patch("/{variable_id}", response_model=VariableRead, status_code=200) -def update_variable( +async def update_variable( *, session: DbSession, variable_id: UUID, @@ -92,7 +92,7 @@ def update_variable( @router.delete("/{variable_id}", status_code=204) -def delete_variable( +async def delete_variable( *, session: DbSession, variable_id: UUID, diff --git a/src/backend/tests/conftest.py b/src/backend/tests/conftest.py index 3405630bf..0bc016036 100644 --- a/src/backend/tests/conftest.py +++ b/src/backend/tests/conftest.py @@ -384,6 +384,44 @@ async def logged_in_headers(client, active_user): return {"Authorization": f"Bearer {a_token}"} +@pytest.fixture +def active_super_user(client): # noqa: ARG001 + db_manager = get_db_service() + with db_manager.with_session() as session: + user = User( + username="activeuser", + password=get_password_hash("testpassword"), + is_active=True, + is_superuser=True, + ) + if active_user := session.exec(select(User).where(User.username == user.username)).first(): + user = active_user + else: + session.add(user) + session.commit() + session.refresh(user) + user = UserRead.model_validate(user, from_attributes=True) + yield user + # Clean up + # Now cleanup transactions, vertex_build + with db_manager.with_session() as session: + user = session.get(User, user.id) + _delete_transactions_and_vertex_builds(session, user) + session.delete(user) + + session.commit() + + +@pytest.fixture +async def logged_in_headers_super_user(client, active_super_user): + login_data = {"username": active_super_user.username, "password": "testpassword"} + response = await client.post("api/v1/login", data=login_data) + assert response.status_code == 200 + tokens = response.json() + a_token = tokens["access_token"] + return {"Authorization": f"Bearer {a_token}"} + + @pytest.fixture def flow( client, # noqa: ARG001 @@ -484,7 +522,7 @@ async def added_webhook_test(client, json_webhook_test, logged_in_headers): @pytest.fixture -async def flow_component(client: TestClient, logged_in_headers): +async def flow_component(client: AsyncClient, logged_in_headers): chat_input = ChatInput() graph = Graph(start=chat_input, end=chat_input) graph_dict = graph.dump(name="Chat Input Component") diff --git a/src/backend/tests/integration/test_misc.py b/src/backend/tests/integration/test_misc.py index 5d10fe1fd..28afbd19b 100644 --- a/src/backend/tests/integration/test_misc.py +++ b/src/backend/tests/integration/test_misc.py @@ -2,14 +2,14 @@ from uuid import uuid4 import pytest from fastapi import status -from fastapi.testclient import TestClient +from httpx import AsyncClient from langflow.graph.schema import RunOutputs from langflow.initial_setup.setup import load_starter_projects from langflow.load import run_flow_from_json @pytest.mark.api_key_required -async def test_run_flow_with_caching_success(client: TestClient, starter_project, created_api_key): +async def test_run_flow_with_caching_success(client: AsyncClient, starter_project, created_api_key): flow_id = starter_project["id"] headers = {"x-api-key": created_api_key.api_key} payload = { @@ -27,7 +27,7 @@ async def test_run_flow_with_caching_success(client: TestClient, starter_project @pytest.mark.api_key_required -async def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key): +async def test_run_flow_with_caching_invalid_flow_id(client: AsyncClient, created_api_key): invalid_flow_id = uuid4() headers = {"x-api-key": created_api_key.api_key} payload = {"input_value": "", "input_type": "text", "output_type": "text", "tweaks": {}, "stream": False} @@ -39,7 +39,7 @@ async def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created @pytest.mark.api_key_required -async def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key): +async def test_run_flow_with_caching_invalid_input_format(client: AsyncClient, starter_project, created_api_key): flow_id = starter_project["id"] headers = {"x-api-key": created_api_key.api_key} payload = {"input_value": {"key": "value"}, "input_type": "text", "output_type": "text", "tweaks": {}} diff --git a/src/backend/tests/unit/api/v1/test_api_key.py b/src/backend/tests/unit/api/v1/test_api_key.py new file mode 100644 index 000000000..e93467439 --- /dev/null +++ b/src/backend/tests/unit/api/v1/test_api_key.py @@ -0,0 +1,64 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_create_folder(client: AsyncClient, logged_in_headers): + response = await client.get("api/v1/api_key/", headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "api_keys" in result, "The dictionary must contain a key called 'api_keys'" + assert "user_id" in result, "The dictionary must contain a key called 'user_id'" + assert "total_count" in result, "The dictionary must contain a key called 'total_count'" + + +async def test_create_api_key_route(client: AsyncClient, logged_in_headers, active_user): + basic_case = { + "name": "string", + "total_uses": 0, + "is_active": True, + "api_key": "string", + "user_id": str(active_user.id), + } + response = await client.post("api/v1/api_key/", json=basic_case, headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "api_key" in result, "The dictionary must contain a key called 'api_key'" + assert "id" in result, "The dictionary must contain a key called 'id'" + assert "is_active" in result, "The dictionary must contain a key called 'is_active'" + assert "last_used_at" in result, "The dictionary must contain a key called 'last_used_at'" + assert "name" in result, "The dictionary must contain a key called 'name'" + assert "total_uses" in result, "The dictionary must contain a key called 'total_uses'" + assert "user_id" in result, "The dictionary must contain a key called 'user_id'" + + +async def test_delete_api_key_route(client: AsyncClient, logged_in_headers, active_user): + basic_case = { + "name": "string", + "total_uses": 0, + "is_active": True, + "api_key": "string", + "user_id": str(active_user.id), + } + _response = await client.post("api/v1/api_key/", json=basic_case, headers=logged_in_headers) + _id = _response.json()["id"] + + response = await client.delete(f"api/v1/api_key/{_id}", headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "detail" in result, "The dictionary must contain a key called 'detail'" + + +async def test_save_store_api_key(client: AsyncClient, logged_in_headers): + basic_case = {"api_key": "string"} + response = await client.post("api/v1/api_key/store", json=basic_case, headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "detail" in result, "The dictionary must contain a key called 'detail'" diff --git a/src/backend/tests/unit/api/v1/test_endpoints.py b/src/backend/tests/unit/api/v1/test_endpoints.py new file mode 100644 index 000000000..15d96e9aa --- /dev/null +++ b/src/backend/tests/unit/api/v1/test_endpoints.py @@ -0,0 +1,36 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_get_version(client: AsyncClient): + response = await client.get("api/v1/version") + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "version" in result, "The dictionary must contain a key called 'version'" + assert "main_version" in result, "The dictionary must contain a key called 'main_version'" + assert "package" in result, "The dictionary must contain a key called 'package'" + + +async def test_get_config(client: AsyncClient): + response = await client.get("api/v1/config") + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "frontend_timeout" in result, "The dictionary must contain a key called 'frontend_timeout'" + assert "auto_saving" in result, "The dictionary must contain a key called 'auto_saving'" + assert "health_check_max_retries" in result, "The dictionary must contain a 'health_check_max_retries' key" + assert "max_file_size_upload" in result, "The dictionary must contain a key called 'max_file_size_upload'" + + +async def test_get_sidebar_components(client: AsyncClient): + response = await client.get("api/v1/sidebar_categories") + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "categories" in result, "The dictionary must contain a key called 'categories'" + assert len(result["categories"]) > 0, "The categories list must not be empty" + assert isinstance(result["categories"], list), "The categories must be a list" diff --git a/src/backend/tests/unit/api/v1/test_flows.py b/src/backend/tests/unit/api/v1/test_flows.py new file mode 100644 index 000000000..27a9ab5d8 --- /dev/null +++ b/src/backend/tests/unit/api/v1/test_flows.py @@ -0,0 +1,172 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_create_flow(client: AsyncClient, logged_in_headers): + basic_case = { + "name": "string", + "description": "string", + "icon": "string", + "icon_bg_color": "#ff00ff", + "gradient": "string", + "data": {}, + "is_component": False, + "webhook": False, + "endpoint_name": "string", + "tags": ["string"], + "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + } + response = await client.post("api/v1/flows/", json=basic_case, headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_201_CREATED + assert isinstance(result, dict), "The result must be a dictionary" + assert "data" in result, "The result must have a 'data' key" + assert "description" in result, "The result must have a 'description' key" + assert "endpoint_name" in result, "The result must have a 'endpoint_name' key" + assert "folder_id" in result, "The result must have a 'folder_id' key" + assert "gradient" in result, "The result must have a 'gradient' key" + assert "icon" in result, "The result must have a 'icon' key" + assert "icon_bg_color" in result, "The result must have a 'icon_bg_color' key" + assert "id" in result, "The result must have a 'id' key" + assert "is_component" in result, "The result must have a 'is_component' key" + assert "name" in result, "The result must have a 'name' key" + assert "tags" in result, "The result must have a 'tags' key" + assert "updated_at" in result, "The result must have a 'updated_at' key" + assert "user_id" in result, "The result must have a 'user_id' key" + assert "webhook" in result, "The result must have a 'webhook' key" + + +async def test_read_flows(client: AsyncClient, logged_in_headers): + params = { + "remove_example_flows": False, + "components_only": False, + "get_all": True, + "header_flows": False, + "page": 1, + "size": 50, + } + response = await client.get("api/v1/flows/", params=params, headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, list), "The result must be a list" + + +async def test_read_flow(client: AsyncClient, logged_in_headers): + basic_case = { + "name": "string", + "description": "string", + "icon": "string", + "icon_bg_color": "#ff00ff", + "gradient": "string", + "data": {}, + "is_component": False, + "webhook": False, + "endpoint_name": "string", + "tags": ["string"], + "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + } + _response = await client.post("api/v1/flows/", json=basic_case, headers=logged_in_headers) + _id = _response.json()["id"] + response = await client.get(f"api/v1/flows/{_id}", headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "data" in result, "The result must have a 'data' key" + assert "description" in result, "The result must have a 'description' key" + assert "endpoint_name" in result, "The result must have a 'endpoint_name' key" + assert "folder_id" in result, "The result must have a 'folder_id' key" + assert "gradient" in result, "The result must have a 'gradient' key" + assert "icon" in result, "The result must have a 'icon' key" + assert "icon_bg_color" in result, "The result must have a 'icon_bg_color' key" + assert "id" in result, "The result must have a 'id' key" + assert "is_component" in result, "The result must have a 'is_component' key" + assert "name" in result, "The result must have a 'name' key" + assert "tags" in result, "The result must have a 'tags' key" + assert "updated_at" in result, "The result must have a 'updated_at' key" + assert "user_id" in result, "The result must have a 'user_id' key" + assert "webhook" in result, "The result must have a 'webhook' key" + + +async def test_update_flow(client: AsyncClient, logged_in_headers): + name = "first_name" + updated_name = "second_name" + basic_case = { + "description": "string", + "icon": "string", + "icon_bg_color": "#ff00ff", + "gradient": "string", + "data": {}, + "is_component": False, + "webhook": False, + "endpoint_name": "string", + "tags": ["string"], + "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + } + basic_case["name"] = name + _response = await client.post("api/v1/flows/", json=basic_case, headers=logged_in_headers) + _id = _response.json()["id"] + + basic_case["name"] = updated_name + response = await client.patch(f"api/v1/flows/{_id}", json=basic_case, headers=logged_in_headers) + result = response.json() + + assert isinstance(result, dict), "The result must be a dictionary" + assert "data" in result, "The result must have a 'data' key" + assert "description" in result, "The result must have a 'description' key" + assert "endpoint_name" in result, "The result must have a 'endpoint_name' key" + assert "folder_id" in result, "The result must have a 'folder_id' key" + assert "gradient" in result, "The result must have a 'gradient' key" + assert "icon" in result, "The result must have a 'icon' key" + assert "icon_bg_color" in result, "The result must have a 'icon_bg_color' key" + assert "id" in result, "The result must have a 'id' key" + assert "is_component" in result, "The result must have a 'is_component' key" + assert "name" in result, "The result must have a 'name' key" + assert "tags" in result, "The result must have a 'tags' key" + assert "updated_at" in result, "The result must have a 'updated_at' key" + assert "user_id" in result, "The result must have a 'user_id' key" + assert "webhook" in result, "The result must have a 'webhook' key" + assert result["name"] == updated_name, "The name must be updated" + + +async def test_create_flows(client: AsyncClient, logged_in_headers): + amount_flows = 10 + basic_case = { + "description": "string", + "icon": "string", + "icon_bg_color": "#ff00ff", + "gradient": "string", + "data": {}, + "is_component": False, + "webhook": False, + "tags": ["string"], + "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + } + cases = [] + for i in range(amount_flows): + case = basic_case.copy() + case["name"] = f"string_{i}" + case["endpoint_name"] = f"string_{i}" + cases.append(case) + + response = await client.post("api/v1/flows/batch/", json={"flows": cases}, headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_201_CREATED + assert isinstance(result, list), "The result must be a list" + assert len(result) == amount_flows, "The result must have the same amount of flows" + + +async def test_read_basic_examples(client: AsyncClient, logged_in_headers): + response = await client.get("api/v1/flows/basic_examples/", headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, list), "The result must be a list" + assert len(result) > 0, "The result must have at least one flow" diff --git a/src/backend/tests/unit/api/v1/test_folders.py b/src/backend/tests/unit/api/v1/test_folders.py new file mode 100644 index 000000000..e19451623 --- /dev/null +++ b/src/backend/tests/unit/api/v1/test_folders.py @@ -0,0 +1,64 @@ +import pytest +from fastapi import status +from httpx import AsyncClient + + +@pytest.fixture +def basic_case(): + return { + "name": "New Folder", + "description": "", + "flows_list": [], + "components_list": [], + } + + +async def test_create_folder(client: AsyncClient, logged_in_headers, basic_case): + response = await client.post("api/v1/folders/", json=basic_case, headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_201_CREATED + assert isinstance(result, dict), "The result must be a dictionary" + assert "name" in result, "The dictionary must contain a key called 'name'" + assert "description" in result, "The dictionary must contain a key called 'description'" + assert "id" in result, "The dictionary must contain a key called 'id'" + assert "parent_id" in result, "The dictionary must contain a key called 'parent_id'" + + +async def test_read_folders(client: AsyncClient, logged_in_headers): + response = await client.get("api/v1/folders/", headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, list), "The result must be a list" + assert len(result) > 0, "The list must not be empty" + + +async def test_read_folder(client: AsyncClient, logged_in_headers, basic_case): + _response = await client.post("api/v1/folders/", json=basic_case, headers=logged_in_headers) + _id = _response.json()["id"] + response = await client.get(f"api/v1/folders/{_id}", headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "name" in result, "The dictionary must contain a key called 'name'" + assert "description" in result, "The dictionary must contain a key called 'description'" + assert "id" in result, "The dictionary must contain a key called 'id'" + assert "parent_id" in result, "The dictionary must contain a key called 'parent_id'" + + +async def test_update_folder(client: AsyncClient, logged_in_headers, basic_case): + update_case = basic_case.copy() + update_case["name"] = "Updated Folder" + _response = await client.post("api/v1/folders/", json=basic_case, headers=logged_in_headers) + _id = _response.json()["id"] + response = await client.patch(f"api/v1/folders/{_id}", json=update_case, headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "name" in result, "The dictionary must contain a key called 'name'" + assert "description" in result, "The dictionary must contain a key called 'description'" + assert "id" in result, "The dictionary must contain a key called 'id'" + assert "parent_id" in result, "The dictionary must contain a key called 'parent_id'" diff --git a/src/backend/tests/unit/api/v1/test_starter_projects.py b/src/backend/tests/unit/api/v1/test_starter_projects.py new file mode 100644 index 000000000..9d319830f --- /dev/null +++ b/src/backend/tests/unit/api/v1/test_starter_projects.py @@ -0,0 +1,10 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_get_starter_projects(client: AsyncClient, logged_in_headers): + response = await client.get("api/v1/starter-projects/", headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, list), "The result must be a list" diff --git a/src/backend/tests/unit/api/v1/test_store.py b/src/backend/tests/unit/api/v1/test_store.py new file mode 100644 index 000000000..ca17dc761 --- /dev/null +++ b/src/backend/tests/unit/api/v1/test_store.py @@ -0,0 +1,12 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_check_if_store_is_enabled(client: AsyncClient): + response = await client.get("api/v1/store/check/") + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The variable must be a dictionary" + assert "enabled" in result, "The dictionary must contain a key called 'enabled'" + assert isinstance(result["enabled"], bool), "There must be a boolean value for the key 'enabled' in the dictionary" diff --git a/src/backend/tests/unit/api/v1/test_users.py b/src/backend/tests/unit/api/v1/test_users.py new file mode 100644 index 000000000..87814507e --- /dev/null +++ b/src/backend/tests/unit/api/v1/test_users.py @@ -0,0 +1,98 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_add_user(client: AsyncClient): + basic_case = {"username": "string", "password": "string"} + response = await client.post("api/v1/users/", json=basic_case) + result = response.json() + + assert response.status_code == status.HTTP_201_CREATED + assert isinstance(result, dict), "The result must be a dictionary" + assert "id" in result, "The result must have an 'id' key" + assert "is_active" in result, "The result must have an 'is_active' key" + assert "is_superuser" in result, "The result must have an 'is_superuser' key" + assert "last_login_at" in result, "The result must have an 'last_login_at' key" + assert "profile_image" in result, "The result must have an 'profile_image' key" + assert "store_api_key" in result, "The result must have an 'store_api_key' key" + assert "updated_at" in result, "The result must have an 'updated_at' key" + assert "username" in result, "The result must have an 'username' key" + + +async def test_read_current_user(client: AsyncClient, logged_in_headers): + response = await client.get("api/v1/users/whoami", headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "id" in result, "The result must have an 'id' key" + assert "is_active" in result, "The result must have an 'is_active' key" + assert "is_superuser" in result, "The result must have an 'is_superuser' key" + assert "last_login_at" in result, "The result must have an 'last_login_at' key" + assert "profile_image" in result, "The result must have an 'profile_image' key" + assert "store_api_key" in result, "The result must have an 'store_api_key' key" + assert "updated_at" in result, "The result must have an 'updated_at' key" + assert "username" in result, "The result must have an 'username' key" + + +async def test_read_all_users(client: AsyncClient, logged_in_headers_super_user): + response = await client.get("api/v1/users/", headers=logged_in_headers_super_user) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "total_count" in result, "The result must have an 'total_count' key" + assert "users" in result, "The result must have an 'users' key" + + +async def test_patch_user(client: AsyncClient, logged_in_headers_super_user): + name = "string" + updated_name = "string2" + basic_case = {"username": name, "password": "string"} + _response = await client.post("api/v1/users/", json=basic_case) + _id = _response.json()["id"] + basic_case["username"] = updated_name + response = await client.patch(f"api/v1/users/{_id}", json=basic_case, headers=logged_in_headers_super_user) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "id" in result, "The result must have an 'id' key" + assert "is_active" in result, "The result must have an 'is_active' key" + assert "is_superuser" in result, "The result must have an 'is_superuser' key" + assert "last_login_at" in result, "The result must have an 'last_login_at' key" + assert "profile_image" in result, "The result must have an 'profile_image' key" + assert "store_api_key" in result, "The result must have an 'store_api_key' key" + assert "updated_at" in result, "The result must have an 'updated_at' key" + assert "username" in result, "The result must have an 'username' key" + assert result["username"] == updated_name, "The username must be updated" + + +async def test_reset_password(client: AsyncClient, logged_in_headers, active_user): + _id = str(active_user.id) + basic_case = {"username": "string", "password": "new_password"} + response = await client.patch(f"api/v1/users/{_id}/reset-password", json=basic_case, headers=logged_in_headers) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "id" in result, "The result must have an 'id' key" + assert "is_active" in result, "The result must have an 'is_active' key" + assert "is_superuser" in result, "The result must have an 'is_superuser' key" + assert "last_login_at" in result, "The result must have an 'last_login_at' key" + assert "profile_image" in result, "The result must have an 'profile_image' key" + assert "store_api_key" in result, "The result must have an 'store_api_key' key" + assert "updated_at" in result, "The result must have an 'updated_at' key" + assert "username" in result, "The result must have an 'username' key" + + +async def test_delete_user(client: AsyncClient, logged_in_headers_super_user): + basic_case = {"username": "string", "password": "string"} + _response = await client.post("api/v1/users/", json=basic_case) + _id = _response.json()["id"] + response = await client.delete(f"api/v1/users/{_id}", headers=logged_in_headers_super_user) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "detail" in result, "The result must have an 'detail' key" diff --git a/src/backend/tests/unit/api/v1/test_validate.py b/src/backend/tests/unit/api/v1/test_validate.py new file mode 100644 index 000000000..56cee8a67 --- /dev/null +++ b/src/backend/tests/unit/api/v1/test_validate.py @@ -0,0 +1,56 @@ +from fastapi import status +from httpx import AsyncClient + + +async def test_post_validate_code(client: AsyncClient): + good_code = """ +from pprint import pprint +var = {"a": 1, "b": 2} +pprint(var) + """ + response = await client.post("api/v1/validate/code", json={"code": good_code}) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "imports" in result, "The result must have an 'imports' key" + assert "function" in result, "The result must have a 'function' key" + + +async def test_post_validate_prompt(client: AsyncClient): + basic_case = { + "name": "string", + "template": "string", + "custom_fields": {}, + "frontend_node": { + "template": {}, + "description": "string", + "icon": "string", + "is_input": True, + "is_output": True, + "is_composition": True, + "base_classes": ["string"], + "name": "", + "display_name": "", + "documentation": "", + "custom_fields": {}, + "output_types": [], + "full_path": "string", + "pinned": False, + "conditional_paths": [], + "frozen": False, + "outputs": [], + "field_order": [], + "beta": False, + "error": "string", + "edited": False, + "metadata": {}, + }, + } + response = await client.post("api/v1/validate/prompt", json=basic_case) + result = response.json() + + assert response.status_code == status.HTTP_200_OK + assert isinstance(result, dict), "The result must be a dictionary" + assert "frontend_node" in result, "The result must have a 'frontend_node' key" + assert "input_variables" in result, "The result must have an 'input_variables' key" diff --git a/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py b/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py index 83203209b..024433335 100644 --- a/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py +++ b/src/backend/tests/unit/initial_setup/starter_projects/test_memory_chatbot.py @@ -43,6 +43,7 @@ AI: """ return graph +@pytest.mark.usefixtures("client") def test_memory_chatbot(memory_chatbot_graph): # Now we run step by step expected_order = deque(["chat_input", "chat_memory", "prompt", "openai", "chat_output"]) diff --git a/src/backend/tests/unit/test_database.py b/src/backend/tests/unit/test_database.py index bc4983fe2..8691012f8 100644 --- a/src/backend/tests/unit/test_database.py +++ b/src/backend/tests/unit/test_database.py @@ -4,10 +4,10 @@ from uuid import UUID, uuid4 import orjson import pytest -from fastapi.testclient import TestClient +from httpx import AsyncClient from langflow.api.v1.schemas import FlowListCreate, ResultDataResponse from langflow.graph.utils import log_transaction, log_vertex_build -from langflow.initial_setup.setup import load_flows_from_directory, load_starter_projects +from langflow.initial_setup.setup import load_starter_projects from langflow.services.database.models.base import orjson_dumps from langflow.services.database.models.flow import Flow, FlowCreate, FlowUpdate from langflow.services.database.models.folder.model import FolderCreate @@ -30,7 +30,7 @@ def json_style(): @pytest.mark.usefixtures("active_user") -async def test_create_flow(client: TestClient, json_flow: str, logged_in_headers): +async def test_create_flow(client: AsyncClient, json_flow: str, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name=str(uuid4()), description="description", data=data) @@ -47,7 +47,7 @@ async def test_create_flow(client: TestClient, json_flow: str, logged_in_headers @pytest.mark.usefixtures("active_user") -async def test_read_flows(client: TestClient, json_flow: str, logged_in_headers): +async def test_read_flows(client: AsyncClient, json_flow: str, logged_in_headers): flow_data = orjson.loads(json_flow) data = flow_data["data"] flow = FlowCreate(name=str(uuid4()), description="description", data=data) @@ -67,14 +67,7 @@ async def test_read_flows(client: TestClient, json_flow: str, logged_in_headers) assert len(response.json()) > 0 -async def test_read_flows_pagination_without_params(client: TestClient, logged_in_headers): - response = await client.get("api/v1/flows/", headers=logged_in_headers) - response_json = response.json() - assert response.status_code == 200 - assert len(response_json) == 0 - - -async def test_read_flows_pagination_with_params(client: TestClient, logged_in_headers): +async def test_read_flows_pagination_with_params(client: AsyncClient, logged_in_headers): response = await client.get( "api/v1/flows/", headers=logged_in_headers, params={"page": 3, "size": 10, "get_all": False} ) @@ -86,7 +79,7 @@ async def test_read_flows_pagination_with_params(client: TestClient, logged_in_h assert len(response.json()["items"]) == 0 -async def test_read_flows_pagination_with_flows(client: TestClient, logged_in_headers): +async def test_read_flows_pagination_with_flows(client: AsyncClient, logged_in_headers): number_of_flows = 30 flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] flow_ids = [] @@ -116,7 +109,7 @@ async def test_read_flows_pagination_with_flows(client: TestClient, logged_in_he assert len(response.json()["items"]) == 0 -async def test_read_flows_custom_page_size(client: TestClient, logged_in_headers): +async def test_read_flows_custom_page_size(client: AsyncClient, logged_in_headers): number_of_flows = 30 flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] for flow in flows: @@ -134,7 +127,7 @@ async def test_read_flows_custom_page_size(client: TestClient, logged_in_headers assert len(response.json()["items"]) == 15 -async def test_read_flows_invalid_page(client: TestClient, logged_in_headers): +async def test_read_flows_invalid_page(client: AsyncClient, logged_in_headers): number_of_flows = 30 flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] flow_ids = [] @@ -149,7 +142,7 @@ async def test_read_flows_invalid_page(client: TestClient, logged_in_headers): assert response.status_code == 422 # Assuming 422 is the status code for invalid input -async def test_read_flows_invalid_size(client: TestClient, logged_in_headers): +async def test_read_flows_invalid_size(client: AsyncClient, logged_in_headers): number_of_flows = 30 flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] flow_ids = [] @@ -164,7 +157,7 @@ async def test_read_flows_invalid_size(client: TestClient, logged_in_headers): assert response.status_code == 422 # Assuming 422 is the status code for invalid input -async def test_read_flows_no_pagination_params(client: TestClient, logged_in_headers): +async def test_read_flows_no_pagination_params(client: AsyncClient, logged_in_headers): number_of_flows = 30 flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] for flow in flows: @@ -181,7 +174,7 @@ async def test_read_flows_no_pagination_params(client: TestClient, logged_in_hea assert len(response.json()["items"]) == number_of_flows -async def test_read_flows_components_only_paginated(client: TestClient, logged_in_headers): +async def test_read_flows_components_only_paginated(client: AsyncClient, logged_in_headers): number_of_flows = 10 flows = [ FlowCreate(name=f"Flow {i}", description="description", data={}, is_component=True) @@ -202,7 +195,7 @@ async def test_read_flows_components_only_paginated(client: TestClient, logged_i assert all(flow["is_component"] is True for flow in response_json["items"]) -async def test_read_flows_components_only(client: TestClient, logged_in_headers): +async def test_read_flows_components_only(client: AsyncClient, logged_in_headers): number_of_flows = 10 flows = [ FlowCreate(name=f"Flow {i}", description="description", data={}, is_component=True) @@ -217,7 +210,7 @@ async def test_read_flows_components_only(client: TestClient, logged_in_headers) assert all(flow["is_component"] is True for flow in response_json) -async def test_read_flow(client: TestClient, json_flow: str, logged_in_headers): +async def test_read_flow(client: AsyncClient, json_flow: str, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] unique_name = str(uuid4()) @@ -234,7 +227,7 @@ async def test_read_flow(client: TestClient, json_flow: str, logged_in_headers): @pytest.mark.usefixtures("active_user") -async def test_update_flow(client: TestClient, json_flow: str, logged_in_headers): +async def test_update_flow(client: AsyncClient, json_flow: str, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] @@ -256,7 +249,7 @@ async def test_update_flow(client: TestClient, json_flow: str, logged_in_headers @pytest.mark.usefixtures("active_user") -async def test_delete_flow(client: TestClient, json_flow: str, logged_in_headers): +async def test_delete_flow(client: AsyncClient, json_flow: str, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) @@ -268,7 +261,7 @@ async def test_delete_flow(client: TestClient, json_flow: str, logged_in_headers @pytest.mark.usefixtures("active_user") -async def test_delete_flows(client: TestClient, logged_in_headers): +async def test_delete_flows(client: AsyncClient, logged_in_headers): # Create ten flows number_of_flows = 10 flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] @@ -285,7 +278,7 @@ async def test_delete_flows(client: TestClient, logged_in_headers): @pytest.mark.asyncio @pytest.mark.usefixtures("active_user") -async def test_delete_flows_with_transaction_and_build(client: TestClient, logged_in_headers): +async def test_delete_flows_with_transaction_and_build(client: AsyncClient, logged_in_headers): # Create ten flows number_of_flows = 10 flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] @@ -344,7 +337,7 @@ async def test_delete_flows_with_transaction_and_build(client: TestClient, logge @pytest.mark.asyncio @pytest.mark.usefixtures("active_user") -async def test_delete_folder_with_flows_with_transaction_and_build(client: TestClient, logged_in_headers): +async def test_delete_folder_with_flows_with_transaction_and_build(client: AsyncClient, logged_in_headers): # Create a new folder folder_name = f"Test Folder {uuid4()}" folder = FolderCreate(name=folder_name, description="Test folder description", components_list=[], flows_list=[]) @@ -411,7 +404,7 @@ async def test_delete_folder_with_flows_with_transaction_and_build(client: TestC assert response.json() == {"vertex_builds": {}} -async def test_get_flows_from_folder_pagination(client: TestClient, logged_in_headers): +async def test_get_flows_from_folder_pagination(client: AsyncClient, logged_in_headers): # Create a new folder folder_name = f"Test Folder {uuid4()}" folder = FolderCreate(name=folder_name, description="Test folder description", components_list=[], flows_list=[]) @@ -435,7 +428,7 @@ async def test_get_flows_from_folder_pagination(client: TestClient, logged_in_he assert len(response.json()["flows"]["items"]) == 0 -async def test_get_flows_from_folder_pagination_with_params(client: TestClient, logged_in_headers): +async def test_get_flows_from_folder_pagination_with_params(client: AsyncClient, logged_in_headers): # Create a new folder folder_name = f"Test Folder {uuid4()}" folder = FolderCreate(name=folder_name, description="Test folder description", components_list=[], flows_list=[]) @@ -460,7 +453,7 @@ async def test_get_flows_from_folder_pagination_with_params(client: TestClient, @pytest.mark.usefixtures("session") -async def test_create_flows(client: TestClient, json_flow: str, logged_in_headers): +async def test_create_flows(client: AsyncClient, json_flow: str, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] # Create test data @@ -488,7 +481,7 @@ async def test_create_flows(client: TestClient, json_flow: str, logged_in_header @pytest.mark.usefixtures("session") -async def test_upload_file(client: TestClient, json_flow: str, logged_in_headers): +async def test_upload_file(client: AsyncClient, json_flow: str, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] # Create test data @@ -521,7 +514,7 @@ async def test_upload_file(client: TestClient, json_flow: str, logged_in_headers @pytest.mark.usefixtures("session") async def test_download_file( - client: TestClient, + client: AsyncClient, json_flow, active_user, logged_in_headers, @@ -563,21 +556,21 @@ async def test_download_file( @pytest.mark.usefixtures("active_user") -async def test_create_flow_with_invalid_data(client: TestClient, logged_in_headers): +async def test_create_flow_with_invalid_data(client: AsyncClient, logged_in_headers): flow = {"name": "a" * 256, "data": "Invalid flow data"} response = await client.post("api/v1/flows/", json=flow, headers=logged_in_headers) assert response.status_code == 422 @pytest.mark.usefixtures("active_user") -async def test_get_nonexistent_flow(client: TestClient, logged_in_headers): +async def test_get_nonexistent_flow(client: AsyncClient, logged_in_headers): uuid = uuid4() response = await client.get(f"api/v1/flows/{uuid}", headers=logged_in_headers) assert response.status_code == 404 @pytest.mark.usefixtures("active_user") -async def test_update_flow_idempotency(client: TestClient, json_flow: str, logged_in_headers): +async def test_update_flow_idempotency(client: AsyncClient, json_flow: str, logged_in_headers): flow_data = orjson.loads(json_flow) data = flow_data["data"] flow_data = FlowCreate(name="Test Flow", description="description", data=data) @@ -590,7 +583,7 @@ async def test_update_flow_idempotency(client: TestClient, json_flow: str, logge @pytest.mark.usefixtures("active_user") -async def test_update_nonexistent_flow(client: TestClient, json_flow: str, logged_in_headers): +async def test_update_nonexistent_flow(client: AsyncClient, json_flow: str, logged_in_headers): flow_data = orjson.loads(json_flow) data = flow_data["data"] uuid = uuid4() @@ -604,34 +597,20 @@ async def test_update_nonexistent_flow(client: TestClient, json_flow: str, logge @pytest.mark.usefixtures("active_user") -async def test_delete_nonexistent_flow(client: TestClient, logged_in_headers): +async def test_delete_nonexistent_flow(client: AsyncClient, logged_in_headers): uuid = uuid4() response = await client.delete(f"api/v1/flows/{uuid}", headers=logged_in_headers) assert response.status_code == 404 @pytest.mark.usefixtures("active_user") -async def test_read_only_starter_projects(client: TestClient, logged_in_headers): +async def test_read_only_starter_projects(client: AsyncClient, logged_in_headers): response = await client.get("api/v1/flows/basic_examples/", headers=logged_in_headers) starter_projects = load_starter_projects() assert response.status_code == 200 assert len(response.json()) == len(starter_projects) -@pytest.mark.load_flows -async def test_load_flows(client: TestClient): - response = await client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b") - assert response.status_code == 200 - assert response.json()["name"] == "BasicExample" - assert response.json()["folder_id"] is not None - # re-run to ensure updates work well - load_flows_from_directory() - response = await client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b") - assert response.status_code == 200 - assert response.json()["name"] == "BasicExample" - assert response.json()["folder_id"] is not None - - def test_sqlite_pragmas(): db_service = get_db_service() @@ -643,7 +622,7 @@ def test_sqlite_pragmas(): @pytest.mark.usefixtures("active_user") -async def test_read_folder(client: TestClient, logged_in_headers): +async def test_read_folder(client: AsyncClient, logged_in_headers): # Create a new folder folder_name = f"Test Folder {uuid4()}" folder = FolderCreate(name=folder_name, description="Test folder description") @@ -663,7 +642,7 @@ async def test_read_folder(client: TestClient, logged_in_headers): @pytest.mark.usefixtures("active_user") -async def test_read_folder_with_pagination(client: TestClient, logged_in_headers): +async def test_read_folder_with_pagination(client: AsyncClient, logged_in_headers): # Create a new folder folder_name = f"Test Folder {uuid4()}" folder = FolderCreate(name=folder_name, description="Test folder description") @@ -689,7 +668,7 @@ async def test_read_folder_with_pagination(client: TestClient, logged_in_headers @pytest.mark.usefixtures("active_user") -async def test_read_folder_with_flows(client: TestClient, json_flow: str, logged_in_headers): +async def test_read_folder_with_flows(client: AsyncClient, json_flow: str, logged_in_headers): # Create a new folder folder_name = f"Test Folder {uuid4()}" flow_name = f"Test Flow {uuid4()}" @@ -718,7 +697,7 @@ async def test_read_folder_with_flows(client: TestClient, json_flow: str, logged @pytest.mark.usefixtures("active_user") -async def test_read_nonexistent_folder(client: TestClient, logged_in_headers): +async def test_read_nonexistent_folder(client: AsyncClient, logged_in_headers): nonexistent_id = str(uuid4()) response = await client.get(f"api/v1/folders/{nonexistent_id}", headers=logged_in_headers) assert response.status_code == 404 @@ -726,7 +705,7 @@ async def test_read_nonexistent_folder(client: TestClient, logged_in_headers): @pytest.mark.usefixtures("active_user") -async def test_read_folder_with_search(client: TestClient, json_flow: str, logged_in_headers): +async def test_read_folder_with_search(client: AsyncClient, json_flow: str, logged_in_headers): # Create a new folder folder_name = f"Test Folder {uuid4()}" folder = FolderCreate(name=folder_name, description="Test folder description") @@ -762,7 +741,7 @@ async def test_read_folder_with_search(client: TestClient, json_flow: str, logge @pytest.mark.usefixtures("active_user") -async def test_read_folder_with_component_filter(client: TestClient, json_flow: str, logged_in_headers): +async def test_read_folder_with_component_filter(client: AsyncClient, json_flow: str, logged_in_headers): # Create a new folder folder_name = f"Test Folder {uuid4()}" folder = FolderCreate(name=folder_name, description="Test folder description") diff --git a/src/backend/tests/unit/test_endpoints.py b/src/backend/tests/unit/test_endpoints.py index bd207212e..352766f10 100644 --- a/src/backend/tests/unit/test_endpoints.py +++ b/src/backend/tests/unit/test_endpoints.py @@ -108,157 +108,6 @@ PROMPT_REQUEST = { } -# def test_process_flow_invalid_api_key(client, flow, monkeypatch): -# # Mock de process_graph_cached -# from langflow.api.v1 import endpoints -# from langflow.services.database.models.api_key import crud - -# settings_service = get_settings_service() -# settings_service.auth_settings.AUTO_LOGIN = False - -# async def mock_process_graph_cached(*args, **kwargs): -# return Result(result={}, session_id="session_id_mock") - -# def mock_update_total_uses(*args, **kwargs): -# return created_api_key - -# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached) -# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses) - -# headers = {"x-api-key": "invalid_api_key"} - -# post_data = { -# "inputs": {"key": "value"}, -# "tweaks": None, -# "clear_cache": False, -# "session_id": None, -# } - -# response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data) - -# assert response.status_code == 403 -# assert response.json() == {"detail": "Invalid or missing API key"} - - -# def test_process_flow_invalid_id(client, monkeypatch, created_api_key): -# async def mock_process_graph_cached(*args, **kwargs): -# return Result(result={}, session_id="session_id_mock") - -# from langflow.api.v1 import endpoints - -# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached) - -# api_key = created_api_key.api_key -# headers = {"x-api-key": api_key} - -# post_data = { -# "inputs": {"key": "value"}, -# "tweaks": None, -# "clear_cache": False, -# "session_id": None, -# } - -# invalid_id = uuid.uuid4() -# response = await client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data) - -# assert response.status_code == 404 -# assert f"Flow {invalid_id} not found" in response.json()["detail"] - - -# def test_process_flow_without_autologin(client, flow, monkeypatch, created_api_key): -# # Mock de process_graph_cached -# from langflow.api.v1 import endpoints -# from langflow.services.database.models.api_key import crud - -# settings_service = get_settings_service() -# settings_service.auth_settings.AUTO_LOGIN = False - -# async def mock_process_graph_cached(*args, **kwargs): -# return Result(result={}, session_id="session_id_mock") - -# def mock_process_graph_cached_task(*args, **kwargs): -# return Result(result={}, session_id="session_id_mock") - -# # The task function is ran like this: -# # if not self.use_celery: -# # return None, await task_func(*args, **kwargs) -# # if not hasattr(task_func, "apply"): -# # raise ValueError(f"Task function {task_func} does not have an apply method") -# # task = task_func.apply(args=args, kwargs=kwargs) -# # result = task.get() -# # return task.id, result -# # So we need to mock the task function to return a task object -# # and then mock the task object to return a result -# # maybe a named tuple would be better here -# task = namedtuple("task", ["id", "get"]) -# mock_process_graph_cached_task.apply = lambda *args, **kwargs: task( -# id="task_id_mock", get=lambda: Result(result={}, session_id="session_id_mock") -# ) - -# def mock_update_total_uses(*args, **kwargs): -# return created_api_key - -# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached) -# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses) -# monkeypatch.setattr(endpoints, "process_graph_cached_task", mock_process_graph_cached_task) - -# api_key = created_api_key.api_key -# headers = {"x-api-key": api_key} - -# # Dummy POST data -# post_data = { -# "inputs": {"input": "value"}, -# "tweaks": None, -# "clear_cache": False, -# "session_id": None, -# } - -# # Make the request to the FastAPI TestClient - -# response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data) - -# # Check the response -# assert response.status_code == 200, response.json() -# assert response.json()["result"] == {}, response.json() -# assert response.json()["session_id"] == "session_id_mock", response.json() - - -# def test_process_flow_fails_autologin_off(client, flow, monkeypatch): -# # Mock de process_graph_cached -# from langflow.api.v1 import endpoints -# from langflow.services.database.models.api_key import crud - -# settings_service = get_settings_service() -# settings_service.auth_settings.AUTO_LOGIN = False - -# async def mock_process_graph_cached(*args, **kwargs): -# return Result(result={}, session_id="session_id_mock") - -# async def mock_update_total_uses(*args, **kwargs): -# return created_api_key - -# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached) -# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses) - -# headers = {"x-api-key": "api_key"} - -# # Dummy POST data -# post_data = { -# "inputs": {"key": "value"}, -# "tweaks": None, -# "clear_cache": False, -# "session_id": None, -# } - -# # Make the request to the FastAPI TestClient - -# response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data) - -# # Check the response -# assert response.status_code == 403, response.json() -# assert response.json() == {"detail": "Invalid or missing API key"} - - async def test_get_all(client: AsyncClient, logged_in_headers): response = await client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 diff --git a/src/backend/tests/unit/test_user.py b/src/backend/tests/unit/test_user.py index 41fdaeabe..cb4f57e4b 100644 --- a/src/backend/tests/unit/test_user.py +++ b/src/backend/tests/unit/test_user.py @@ -159,20 +159,6 @@ async def test_add_user(test_user): assert test_user["username"] == "testuser" -# This is not used in the Frontend at the moment -# def test_read_current_user(client: TestClient, active_user): -# # First we need to login to get the access token -# login_data = {"username": "testuser", "password": "testpassword"} -# response = await client.post("api/v1/login", data=login_data) -# assert response.status_code == 200 - -# headers = {"Authorization": f"Bearer {response.json()['access_token']}"} - -# response = await client.get("api/v1/user", headers=headers) -# assert response.status_code == 200, response.json() -# assert response.json()["username"] == "testuser" - - @pytest.mark.api_key_required async def test_read_all_users(client: AsyncClient, super_user_headers): response = await client.get("api/v1/users/", headers=super_user_headers)