372 lines
12 KiB
Python
372 lines
12 KiB
Python
import logging
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from nextcloud_mcp_server.client.cookbook import CookbookClient
|
|
from tests.client.conftest import (
|
|
create_mock_error_response,
|
|
create_mock_recipe_list_response,
|
|
create_mock_recipe_response,
|
|
create_mock_response,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Mark all tests in this module as unit tests
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
async def test_cookbook_version(mocker):
|
|
"""Test that get_version correctly parses the API response."""
|
|
mock_response = create_mock_response(
|
|
status_code=200,
|
|
json_data={
|
|
"cookbook_version": "1.0.0",
|
|
"api_version": "1.0.0",
|
|
},
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
version_data = await client.get_version()
|
|
|
|
assert "cookbook_version" in version_data
|
|
assert "api_version" in version_data
|
|
assert version_data["cookbook_version"] == "1.0.0"
|
|
|
|
mock_make_request.assert_called_once_with("GET", "/apps/cookbook/api/version")
|
|
|
|
|
|
async def test_cookbook_config(mocker):
|
|
"""Test that get_config correctly parses the API response."""
|
|
mock_response = create_mock_response(
|
|
status_code=200,
|
|
json_data={
|
|
"folder": "/recipes",
|
|
"update_interval": 60,
|
|
"print_image": True,
|
|
},
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
config_data = await client.get_config()
|
|
|
|
assert isinstance(config_data, dict)
|
|
assert config_data["folder"] == "/recipes"
|
|
|
|
mock_make_request.assert_called_once_with("GET", "/apps/cookbook/api/v1/config")
|
|
|
|
|
|
async def test_cookbook_list_recipes(mocker):
|
|
"""Test that list_recipes correctly parses the API response."""
|
|
mock_response = create_mock_recipe_list_response(
|
|
recipes=[
|
|
{"id": 1, "name": "Recipe 1", "recipeCategory": "Test"},
|
|
{"id": 2, "name": "Recipe 2", "recipeCategory": "Test"},
|
|
]
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
recipes = await client.list_recipes()
|
|
|
|
assert isinstance(recipes, list)
|
|
assert len(recipes) == 2
|
|
assert recipes[0]["name"] == "Recipe 1"
|
|
|
|
mock_make_request.assert_called_once_with("GET", "/apps/cookbook/api/v1/recipes")
|
|
|
|
|
|
async def test_cookbook_create_recipe(mocker):
|
|
"""Test that create_recipe correctly parses the API response."""
|
|
# Create_recipe returns just the recipe ID
|
|
mock_response = create_mock_response(status_code=200, json_data=123)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
recipe_data = {
|
|
"name": "Test Recipe",
|
|
"description": "Test description",
|
|
"recipeIngredient": ["100g flour"],
|
|
"recipeInstructions": ["Mix ingredients"],
|
|
}
|
|
recipe_id = await client.create_recipe(recipe_data)
|
|
|
|
assert recipe_id == 123
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"POST", "/apps/cookbook/api/v1/recipes", json=recipe_data
|
|
)
|
|
|
|
|
|
async def test_cookbook_get_recipe(mocker):
|
|
"""Test that get_recipe correctly parses the API response."""
|
|
mock_response = create_mock_recipe_response(
|
|
recipe_id=123,
|
|
name="Test Recipe",
|
|
description="Test description",
|
|
recipe_category="Test",
|
|
keywords="test,integration",
|
|
recipe_yield=4,
|
|
recipeIngredient=["100g flour", "2 eggs"],
|
|
recipeInstructions=["Mix ingredients", "Cook"],
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
recipe = await client.get_recipe(recipe_id=123)
|
|
|
|
assert recipe["id"] == 123
|
|
assert recipe["name"] == "Test Recipe"
|
|
assert recipe["description"] == "Test description"
|
|
assert len(recipe["recipeIngredient"]) == 2
|
|
assert len(recipe["recipeInstructions"]) == 2
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/apps/cookbook/api/v1/recipes/123"
|
|
)
|
|
|
|
|
|
async def test_cookbook_update_recipe(mocker):
|
|
"""Test that update_recipe correctly parses the API response."""
|
|
# Update_recipe returns the recipe ID
|
|
mock_response = create_mock_response(status_code=200, json_data=123)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
updated_data = {
|
|
"name": "Updated Recipe",
|
|
"description": "Updated description",
|
|
"recipeIngredient": ["100g flour", "2 eggs", "200ml milk"],
|
|
"recipeInstructions": ["Mix ingredients", "Cook", "Serve"],
|
|
}
|
|
updated_id = await client.update_recipe(recipe_id=123, recipe_data=updated_data)
|
|
|
|
assert updated_id == 123
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"PUT", "/apps/cookbook/api/v1/recipes/123", json=updated_data
|
|
)
|
|
|
|
|
|
async def test_cookbook_delete_recipe(mocker):
|
|
"""Test that delete_recipe correctly parses the API response."""
|
|
mock_response = create_mock_response(
|
|
status_code=200, json_data="Recipe deleted successfully"
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
result = await client.delete_recipe(recipe_id=123)
|
|
|
|
assert isinstance(result, str)
|
|
assert "deleted" in result.lower()
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"DELETE", "/apps/cookbook/api/v1/recipes/123"
|
|
)
|
|
|
|
|
|
async def test_cookbook_delete_nonexistent_recipe(mocker):
|
|
"""Test that deleting a non-existent recipe raises HTTPStatusError."""
|
|
error_response = create_mock_error_response(404, "Recipe not found")
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(CookbookClient, "_make_request")
|
|
mock_make_request.side_effect = httpx.HTTPStatusError(
|
|
"404 Not Found",
|
|
request=httpx.Request("DELETE", "http://test.local"),
|
|
response=error_response,
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
|
|
with pytest.raises(httpx.HTTPStatusError) as excinfo:
|
|
await client.delete_recipe(recipe_id=999999999)
|
|
|
|
assert excinfo.value.response.status_code == 404
|
|
|
|
|
|
async def test_cookbook_search_recipes(mocker):
|
|
"""Test that search_recipes correctly parses the API response."""
|
|
mock_response = create_mock_recipe_list_response(
|
|
recipes=[
|
|
{"id": 1, "name": "Test Recipe 1", "keywords": "test,search"},
|
|
{"id": 2, "name": "Test Recipe 2", "keywords": "test,search"},
|
|
]
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
search_results = await client.search_recipes("test")
|
|
|
|
assert isinstance(search_results, list)
|
|
assert len(search_results) == 2
|
|
|
|
# Verify URL encoding happened
|
|
mock_make_request.assert_called_once()
|
|
call_args = mock_make_request.call_args[0]
|
|
assert "/apps/cookbook/api/v1/search/" in call_args[1]
|
|
|
|
|
|
async def test_cookbook_list_categories(mocker):
|
|
"""Test that list_categories correctly parses the API response."""
|
|
mock_response = create_mock_response(
|
|
status_code=200,
|
|
json_data=[
|
|
{"name": "Desserts", "recipe_count": 5},
|
|
{"name": "Main Course", "recipe_count": 10},
|
|
],
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
categories = await client.list_categories()
|
|
|
|
assert isinstance(categories, list)
|
|
assert len(categories) == 2
|
|
assert categories[0]["name"] == "Desserts"
|
|
assert categories[0]["recipe_count"] == 5
|
|
|
|
mock_make_request.assert_called_once_with("GET", "/apps/cookbook/api/v1/categories")
|
|
|
|
|
|
async def test_cookbook_get_recipes_in_category(mocker):
|
|
"""Test that get_recipes_in_category correctly parses the API response."""
|
|
mock_response = create_mock_recipe_list_response(
|
|
recipes=[
|
|
{"id": 1, "name": "Recipe 1", "recipeCategory": "Desserts"},
|
|
{"id": 2, "name": "Recipe 2", "recipeCategory": "Desserts"},
|
|
]
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
recipes_in_category = await client.get_recipes_in_category("Desserts")
|
|
|
|
assert isinstance(recipes_in_category, list)
|
|
assert len(recipes_in_category) == 2
|
|
assert recipes_in_category[0]["recipeCategory"] == "Desserts"
|
|
|
|
# Verify URL encoding happened
|
|
mock_make_request.assert_called_once()
|
|
call_args = mock_make_request.call_args[0]
|
|
assert "/apps/cookbook/api/v1/category/" in call_args[1]
|
|
|
|
|
|
async def test_cookbook_list_keywords(mocker):
|
|
"""Test that list_keywords correctly parses the API response."""
|
|
mock_response = create_mock_response(
|
|
status_code=200,
|
|
json_data=[
|
|
{"name": "vegetarian", "recipe_count": 15},
|
|
{"name": "quick", "recipe_count": 8},
|
|
],
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
keywords = await client.list_keywords()
|
|
|
|
assert isinstance(keywords, list)
|
|
assert len(keywords) == 2
|
|
assert keywords[0]["name"] == "vegetarian"
|
|
assert keywords[0]["recipe_count"] == 15
|
|
|
|
mock_make_request.assert_called_once_with("GET", "/apps/cookbook/api/v1/keywords")
|
|
|
|
|
|
async def test_cookbook_get_recipes_with_keywords(mocker):
|
|
"""Test that get_recipes_with_keywords correctly parses the API response."""
|
|
mock_response = create_mock_recipe_list_response(
|
|
recipes=[
|
|
{"id": 1, "name": "Recipe 1", "keywords": "vegetarian,quick"},
|
|
{"id": 2, "name": "Recipe 2", "keywords": "vegetarian,healthy"},
|
|
]
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
recipes_with_keywords = await client.get_recipes_with_keywords(
|
|
["vegetarian", "quick"]
|
|
)
|
|
|
|
assert isinstance(recipes_with_keywords, list)
|
|
assert len(recipes_with_keywords) == 2
|
|
|
|
# Verify URL encoding and keyword joining happened
|
|
mock_make_request.assert_called_once()
|
|
call_args = mock_make_request.call_args[0]
|
|
assert "/apps/cookbook/api/v1/tags/" in call_args[1]
|
|
|
|
|
|
async def test_cookbook_reindex(mocker):
|
|
"""Test that reindex correctly parses the API response."""
|
|
mock_response = create_mock_response(
|
|
status_code=200,
|
|
json_data="Reindex completed successfully",
|
|
)
|
|
|
|
mock_client = mocker.AsyncMock(spec=httpx.AsyncClient)
|
|
mock_make_request = mocker.patch.object(
|
|
CookbookClient, "_make_request", return_value=mock_response
|
|
)
|
|
|
|
client = CookbookClient(mock_client, "testuser")
|
|
result = await client.reindex()
|
|
|
|
assert isinstance(result, str)
|
|
assert "reindex" in result.lower() or "completed" in result.lower()
|
|
|
|
mock_make_request.assert_called_once_with("POST", "/apps/cookbook/api/v1/reindex")
|