chore: fixed imports and tests configuration

This commit is contained in:
2025-12-27 19:39:36 +01:00
parent b132554631
commit 6195abbaa5
43 changed files with 216 additions and 243 deletions

View File

@@ -41,10 +41,6 @@ docs/
*.md
!README.md
# Tests
tests/
pytest.ini
# Data (will be mounted as volumes)
memory_data/
logs/

View File

@@ -59,12 +59,8 @@ RUN --mount=type=cache,target=/root/.cache/pip \
uv pip install --system -e .[dev]; \
fi
COPY alfred/agent/ ./agent/
COPY alfred/application/ ./application/
COPY alfred/domain/ ./domain/
COPY alfred/infrastructure/ ./infrastructure/
COPY alfred/app.py .
COPY tests/ ./tests/
COPY alfred/ ./alfred
COPY tests/ ./tests
# ===========================================
# Stage 3: Runtime
@@ -96,18 +92,14 @@ RUN mkdir -p /data/memory /data/logs \
USER appuser
# Set working directory (owned by appuser)
WORKDIR /home/appuser/app
WORKDIR /home/appuser
# Copy Python packages from builder stage
COPY --from=builder /usr/local/lib/python${PYTHON_VERSION_SHORT}/site-packages /usr/local/lib/python${PYTHON_VERSION_SHORT}/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# Copy application code (already owned by appuser)
COPY --chown=appuser:appuser alfred/agent/ ./agent/
COPY --chown=appuser:appuser alfred/application/ ./application/
COPY --chown=appuser:appuser alfred/domain/ ./domain/
COPY --chown=appuser:appuser alfred/infrastructure/ ./infrastructure/
COPY --chown=appuser:appuser alfred/app.py .
COPY --chown=appuser:appuser alfred/ ./alfred
# Create volumes for persistent data
VOLUME ["/data/memory", "/data/logs"]

View File

@@ -5,7 +5,7 @@ import logging
from collections.abc import AsyncGenerator
from typing import Any
from infrastructure.persistence import get_memory
from alfred.infrastructure.persistence import get_memory
from .config import settings
from .prompts import PromptBuilder

View File

@@ -3,7 +3,7 @@
import json
from typing import Any
from infrastructure.persistence import get_memory
from alfred.infrastructure.persistence import get_memory
from .registry import Tool

View File

@@ -3,12 +3,12 @@
import logging
from typing import Any
from application.movies import SearchMovieUseCase
from application.torrents import AddTorrentUseCase, SearchTorrentsUseCase
from infrastructure.api.knaben import knaben_client
from infrastructure.api.qbittorrent import qbittorrent_client
from infrastructure.api.tmdb import tmdb_client
from infrastructure.persistence import get_memory
from alfred.application.movies import SearchMovieUseCase
from alfred.application.torrents import AddTorrentUseCase, SearchTorrentsUseCase
from alfred.infrastructure.api.knaben import knaben_client
from alfred.infrastructure.api.qbittorrent import qbittorrent_client
from alfred.infrastructure.api.tmdb import tmdb_client
from alfred.infrastructure.persistence import get_memory
logger = logging.getLogger(__name__)

View File

@@ -2,8 +2,8 @@
from typing import Any
from application.filesystem import ListFolderUseCase, SetFolderPathUseCase
from infrastructure.filesystem import FileManager
from alfred.application.filesystem import ListFolderUseCase, SetFolderPathUseCase
from alfred.infrastructure.filesystem import FileManager
def set_path_for_folder(folder_name: str, path_value: str) -> dict[str, Any]:

View File

@@ -3,7 +3,7 @@
import logging
from typing import Any
from infrastructure.persistence import get_memory
from alfred.infrastructure.persistence import get_memory
logger = logging.getLogger(__name__)

View File

@@ -5,19 +5,18 @@ import logging
import os
import time
import uuid
from typing import Any
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field, validator
from typing import Any
from agent.agent import Agent
from agent.config import settings
from agent.llm.deepseek import DeepSeekClient
from agent.llm.exceptions import LLMAPIError, LLMConfigurationError
from agent.llm.ollama import OllamaClient
from infrastructure.persistence import get_memory, init_memory
from alfred.agent.agent import Agent
from alfred.agent.config import settings
from alfred.agent.llm.deepseek import DeepSeekClient
from alfred.agent.llm.exceptions import LLMAPIError, LLMConfigurationError
from alfred.agent.llm.ollama import OllamaClient
from alfred.infrastructure.persistence import get_memory, init_memory
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"

View File

@@ -2,7 +2,7 @@
import logging
from infrastructure.filesystem import FileManager
from alfred.infrastructure.filesystem import FileManager
from .dto import ListFolderResponse

View File

@@ -2,7 +2,7 @@
import logging
from infrastructure.filesystem import FileManager
from alfred.infrastructure.filesystem import FileManager
from .dto import SetFolderPathResponse

View File

@@ -2,7 +2,7 @@
import logging
from infrastructure.api.tmdb import (
from alfred.infrastructure.api.tmdb import (
TMDBAPIError,
TMDBClient,
TMDBConfigurationError,

View File

@@ -2,7 +2,7 @@
import logging
from infrastructure.api.qbittorrent import (
from alfred.infrastructure.api.qbittorrent import (
QBittorrentAPIError,
QBittorrentAuthError,
QBittorrentClient,

View File

@@ -2,7 +2,7 @@
import logging
from infrastructure.api.knaben import KnabenAPIError, KnabenClient, KnabenNotFoundError
from alfred.infrastructure.api.knaben import KnabenAPIError, KnabenClient, KnabenNotFoundError
from .dto import SearchTorrentsResponse

View File

@@ -1,13 +1,12 @@
"""Knaben torrent search API client."""
import logging
from typing import Any
import logging
import requests
from requests.exceptions import HTTPError, RequestException, Timeout
from typing import Any
from agent.config import Settings, settings
from alfred.agent.config import Settings, settings
from .dto import TorrentResult
from .exceptions import KnabenAPIError, KnabenNotFoundError

View File

@@ -1,13 +1,11 @@
"""qBittorrent Web API client."""
import logging
from typing import Any
import requests
from requests.exceptions import HTTPError, RequestException, Timeout
from typing import Any
from agent.config import Settings, settings
from alfred.agent.config import Settings, settings
from .dto import TorrentInfo
from .exceptions import QBittorrentAPIError, QBittorrentAuthError

View File

@@ -1,13 +1,12 @@
"""TMDB (The Movie Database) API client."""
import logging
from typing import Any
import requests
from requests.exceptions import HTTPError, RequestException, Timeout
from typing import Any
from agent.config import Settings, settings
from alfred.agent.config import Settings, settings
from .dto import MediaResult
from .exceptions import (
TMDBAPIError,

View File

@@ -7,7 +7,7 @@ from enum import Enum
from pathlib import Path
from typing import Any
from infrastructure.persistence import get_memory
from alfred.infrastructure.persistence import get_memory
from .exceptions import PathTraversalError

View File

@@ -3,9 +3,9 @@
import logging
from pathlib import Path
from domain.movies.entities import Movie
from domain.tv_shows.entities import Episode, Season, TVShow
from domain.tv_shows.value_objects import SeasonNumber
from alfred.domain.movies.entities import Movie
from alfred.domain.tv_shows.entities import Episode, Season, TVShow
from alfred.domain.tv_shows.value_objects import SeasonNumber
logger = logging.getLogger(__name__)

View File

@@ -6,7 +6,7 @@ without passing it explicitly through all function calls.
Usage:
# At application startup
from infrastructure.persistence import init_memory, get_memory
from alfred.infrastructure.persistence import init_memory, get_memory
init_memory("memory_data")

View File

@@ -4,11 +4,11 @@ import logging
from datetime import datetime
from typing import Any
from domain.movies.entities import Movie
from domain.movies.repositories import MovieRepository
from domain.movies.value_objects import MovieTitle, Quality, ReleaseYear
from domain.shared.value_objects import FilePath, FileSize, ImdbId
from infrastructure.persistence import get_memory
from alfred.domain.movies.entities import Movie
from alfred.domain.movies.repositories import MovieRepository
from alfred.domain.movies.value_objects import MovieTitle, Quality, ReleaseYear
from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId
from alfred.infrastructure.persistence import get_memory
logger = logging.getLogger(__name__)

View File

@@ -3,11 +3,11 @@
import logging
from typing import Any
from domain.shared.value_objects import FilePath, ImdbId
from domain.subtitles.entities import Subtitle
from domain.subtitles.repositories import SubtitleRepository
from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
from infrastructure.persistence import get_memory
from alfred.domain.shared.value_objects import FilePath, ImdbId
from alfred.domain.subtitles.entities import Subtitle
from alfred.domain.subtitles.repositories import SubtitleRepository
from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
from alfred.infrastructure.persistence import get_memory
logger = logging.getLogger(__name__)

View File

@@ -4,11 +4,11 @@ import logging
from datetime import datetime
from typing import Any
from domain.shared.value_objects import ImdbId
from domain.tv_shows.entities import TVShow
from domain.tv_shows.repositories import TVShowRepository
from domain.tv_shows.value_objects import ShowStatus
from infrastructure.persistence import get_memory
from alfred.domain.shared.value_objects import ImdbId
from alfred.domain.tv_shows.entities import TVShow
from alfred.domain.tv_shows.repositories import TVShowRepository
from alfred.domain.tv_shows.value_objects import ShowStatus
from alfred.infrastructure.persistence import get_memory
logger = logging.getLogger(__name__)

View File

@@ -31,6 +31,8 @@ build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
# Chemins où pytest cherche les tests
testpaths = ["tests"]
# Ajouter le répertoire racine au PYTHONPATH pour les imports
pythonpath = ["."]
# Patterns de fichiers/classes/fonctions à considérer comme tests
python_files = ["test_*.py"] # Fichiers commençant par "test_"

View File

@@ -1,20 +1,16 @@
"""Pytest configuration and shared fixtures."""
import sys
from pathlib import Path
#TODO: Moved directory, should not be necessary anymore but need to check !!
# TODO: Moved directory, should not be necessary anymore but need to check !!
# Ajouter le dossier parent (brain) au PYTHONPATH
# sys.path.insert(0, str(Path(__file__).parent.parent))
import pytest
import shutil
import sys
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, Mock
import pytest
from infrastructure.persistence import Memory, set_memory
from alfred.infrastructure.persistence import Memory, set_memory
@pytest.fixture
@@ -255,7 +251,6 @@ def mock_deepseek():
def test_something(mock_deepseek):
# Your test code here
"""
import sys
from unittest.mock import Mock
# Save the original module if it exists

View File

@@ -2,8 +2,8 @@
from unittest.mock import Mock
from agent.agent import Agent
from infrastructure.persistence import get_memory
from alfred.agent.agent import Agent
from alfred.infrastructure.persistence import get_memory
class TestAgentInit:

View File

@@ -1,11 +1,9 @@
"""Edge case tests for the Agent."""
import pytest
from unittest.mock import Mock
import pytest
from agent.agent import Agent
from infrastructure.persistence import get_memory
from alfred.agent.agent import Agent
from alfred.infrastructure.persistence import get_memory
class TestExecuteToolCallEdgeCases:
@@ -16,7 +14,7 @@ class TestExecuteToolCallEdgeCases:
agent = Agent(llm=mock_llm)
# Mock a tool that returns None
from agent.registry import Tool
from alfred.agent.registry import Tool
agent.tools["test_tool"] = Tool(
name="test_tool", description="Test", func=lambda: None, parameters={}
@@ -34,7 +32,7 @@ class TestExecuteToolCallEdgeCases:
"""Should propagate KeyboardInterrupt."""
agent = Agent(llm=mock_llm)
from agent.registry import Tool
from alfred.agent.registry import Tool
def raise_interrupt():
raise KeyboardInterrupt()

View File

@@ -10,7 +10,7 @@ class TestHealthEndpoint:
def test_health_check(self, memory):
"""Should return healthy status."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -25,7 +25,7 @@ class TestModelsEndpoint:
def test_list_models(self, memory):
"""Should return model list."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -43,7 +43,7 @@ class TestMemoryEndpoints:
def test_get_memory_state(self, memory):
"""Should return full memory state."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -57,7 +57,7 @@ class TestMemoryEndpoints:
def test_get_search_results_empty(self, memory):
"""Should return empty when no search results."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -69,7 +69,7 @@ class TestMemoryEndpoints:
def test_get_search_results_with_data(self, memory_with_search_results):
"""Should return search results when available."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -83,7 +83,7 @@ class TestMemoryEndpoints:
def test_clear_session(self, memory_with_search_results):
"""Should clear session memories."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -102,10 +102,10 @@ class TestChatCompletionsEndpoint:
def test_chat_completion_success(self, memory):
"""Should return chat completion."""
from app import app
from alfred.app import app
# Patch the agent's step method directly
with patch("app.agent.step", return_value="Hello! How can I help?"):
with patch("alfred.app.agent.step", return_value="Hello! How can I help?"):
client = TestClient(app)
response = client.post(
@@ -123,7 +123,7 @@ class TestChatCompletionsEndpoint:
def test_chat_completion_no_user_message(self, memory):
"""Should return error if no user message."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -146,7 +146,7 @@ class TestChatCompletionsEndpoint:
def test_chat_completion_empty_messages(self, memory):
"""Should return error for empty messages."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -162,7 +162,7 @@ class TestChatCompletionsEndpoint:
def test_chat_completion_invalid_json(self, memory):
"""Should return error for invalid JSON."""
from app import app
from alfred.app import app
client = TestClient(app)
@@ -176,9 +176,9 @@ class TestChatCompletionsEndpoint:
def test_chat_completion_streaming(self, memory):
"""Should support streaming mode."""
from app import app
from alfred.app import app
with patch("app.agent.step", return_value="Streaming response"):
with patch("alfred.app.agent.step", return_value="Streaming response"):
client = TestClient(app)
response = client.post(
@@ -195,9 +195,9 @@ class TestChatCompletionsEndpoint:
def test_chat_completion_extracts_last_user_message(self, memory):
"""Should use last user message."""
from app import app
from alfred.app import app
with patch("app.agent.step", return_value="Response") as mock_step:
with patch("alfred.app.agent.step", return_value="Response") as mock_step:
client = TestClient(app)
response = client.post(
@@ -218,9 +218,9 @@ class TestChatCompletionsEndpoint:
def test_chat_completion_response_format(self, memory):
"""Should return OpenAI-compatible format."""
from app import app
from alfred.app import app
with patch("app.agent.step", return_value="Test response"):
with patch("alfred.app.agent.step", return_value="Test response"):
client = TestClient(app)
response = client.post(

View File

@@ -10,7 +10,8 @@ class TestChatCompletionsEdgeCases:
def test_very_long_message(self, memory):
"""Should handle very long user message."""
from app import agent, app
from alfred.app import app
from alfred.agent import agent
# Patch the agent's LLM directly
mock_llm = Mock()
@@ -32,7 +33,8 @@ class TestChatCompletionsEdgeCases:
def test_unicode_message(self, memory):
"""Should handle unicode in message."""
from app import agent, app
from alfred.app import app
from alfred.agent import agent
mock_llm = Mock()
mock_llm.complete.return_value = {
@@ -57,7 +59,8 @@ class TestChatCompletionsEdgeCases:
def test_special_characters_in_message(self, memory):
"""Should handle special characters."""
from app import agent, app
from alfred.app import app
from alfred.agent import agent
mock_llm = Mock()
mock_llm.complete.return_value = {"role": "assistant", "content": "Response"}
@@ -78,12 +81,12 @@ class TestChatCompletionsEdgeCases:
def test_empty_content_in_message(self, memory):
"""Should handle empty content in message."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm.complete.return_value = "Response"
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -100,11 +103,11 @@ class TestChatCompletionsEdgeCases:
def test_null_content_in_message(self, memory):
"""Should handle null content in message."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -120,11 +123,11 @@ class TestChatCompletionsEdgeCases:
def test_missing_content_field(self, memory):
"""Should handle missing content field."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -141,11 +144,11 @@ class TestChatCompletionsEdgeCases:
def test_missing_role_field(self, memory):
"""Should handle missing role field."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -162,12 +165,12 @@ class TestChatCompletionsEdgeCases:
def test_invalid_role(self, memory):
"""Should handle invalid role."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm.complete.return_value = "Response"
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -184,7 +187,8 @@ class TestChatCompletionsEdgeCases:
def test_many_messages(self, memory):
"""Should handle many messages in conversation."""
from app import agent, app
from alfred.app import app
from alfred.agent import agent
mock_llm = Mock()
mock_llm.complete.return_value = {"role": "assistant", "content": "Response"}
@@ -210,11 +214,11 @@ class TestChatCompletionsEdgeCases:
def test_only_system_messages(self, memory):
"""Should reject if only system messages."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -233,11 +237,11 @@ class TestChatCompletionsEdgeCases:
def test_only_assistant_messages(self, memory):
"""Should reject if only assistant messages."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -255,11 +259,11 @@ class TestChatCompletionsEdgeCases:
def test_messages_not_array(self, memory):
"""Should reject if messages is not array."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -276,11 +280,11 @@ class TestChatCompletionsEdgeCases:
def test_message_not_object(self, memory):
"""Should handle message that is not object."""
with patch("app.DeepSeekClient") as mock_llm_class:
with patch("alfred.app.DeepSeekClient") as mock_llm_class:
mock_llm = Mock()
mock_llm_class.return_value = mock_llm
from app import app
from alfred.app import app
client = TestClient(app)
@@ -297,7 +301,8 @@ class TestChatCompletionsEdgeCases:
def test_extra_fields_in_request(self, memory):
"""Should ignore extra fields in request."""
from app import agent, app
from alfred.app import app
from alfred.agent import agent
mock_llm = Mock()
mock_llm.complete.return_value = {"role": "assistant", "content": "Response"}
@@ -320,8 +325,9 @@ class TestChatCompletionsEdgeCases:
def test_streaming_with_tool_call(self, memory, real_folder):
"""Should handle streaming with tool execution."""
from app import agent, app
from infrastructure.persistence import get_memory
from alfred.app import app
from alfred.agent import agent
from alfred.infrastructure.persistence import get_memory
mem = get_memory()
mem.ltm.set_config("download_folder", str(real_folder["downloads"]))
@@ -365,7 +371,8 @@ class TestChatCompletionsEdgeCases:
def test_concurrent_requests_simulation(self, memory):
"""Should handle rapid sequential requests."""
from app import agent, app
from alfred.app import app
from alfred.agent import agent
mock_llm = Mock()
mock_llm.complete.return_value = {"role": "assistant", "content": "Response"}
@@ -385,7 +392,8 @@ class TestChatCompletionsEdgeCases:
def test_llm_returns_json_in_response(self, memory):
"""Should handle LLM returning JSON in text response."""
from app import agent, app
from alfred.app import app
from alfred.agent import agent
mock_llm = Mock()
mock_llm.complete.return_value = {
@@ -414,9 +422,9 @@ class TestMemoryEndpointsEdgeCases:
def test_memory_state_with_large_data(self, memory):
"""Should handle large memory state."""
with patch("app.DeepSeekClient") as mock_llm:
with patch("alfred.app.DeepSeekClient") as mock_llm:
mock_llm.return_value = Mock()
from app import app
from alfred.app import app
# Add lots of data to memory
for i in range(100):
@@ -432,9 +440,9 @@ class TestMemoryEndpointsEdgeCases:
def test_memory_state_with_unicode(self, memory):
"""Should handle unicode in memory state."""
with patch("app.DeepSeekClient") as mock_llm:
with patch("alfred.app.DeepSeekClient") as mock_llm:
mock_llm.return_value = Mock()
from app import app
from alfred.app import app
memory.ltm.set_config("japanese", "日本語テスト")
memory.stm.add_message("user", "🎬 Movie request")
@@ -448,9 +456,9 @@ class TestMemoryEndpointsEdgeCases:
def test_search_results_with_special_chars(self, memory):
"""Should handle special characters in search results."""
with patch("app.DeepSeekClient") as mock_llm:
with patch("alfred.app.DeepSeekClient") as mock_llm:
mock_llm.return_value = Mock()
from app import app
from alfred.app import app
memory.episodic.store_search_results(
"Test <script>alert('xss')</script>",
@@ -467,9 +475,9 @@ class TestMemoryEndpointsEdgeCases:
def test_clear_session_idempotent(self, memory):
"""Should be idempotent - multiple clears should work."""
with patch("app.DeepSeekClient") as mock_llm:
with patch("alfred.app.DeepSeekClient") as mock_llm:
mock_llm.return_value = Mock()
from app import app
from alfred.app import app
client = TestClient(app)
@@ -480,9 +488,9 @@ class TestMemoryEndpointsEdgeCases:
def test_clear_session_preserves_ltm(self, memory):
"""Should preserve LTM after clear."""
with patch("app.DeepSeekClient") as mock_llm:
with patch("alfred.app.DeepSeekClient") as mock_llm:
mock_llm.return_value = Mock()
from app import app
from alfred.app import app
memory.ltm.set_config("important", "data")
memory.stm.add_message("user", "Hello")
@@ -502,9 +510,9 @@ class TestHealthEndpointEdgeCases:
def test_health_returns_version(self, memory):
"""Should return version in health check."""
with patch("app.DeepSeekClient") as mock_llm:
with patch("alfred.app.DeepSeekClient") as mock_llm:
mock_llm.return_value = Mock()
from app import app
from alfred.app import app
client = TestClient(app)
@@ -515,9 +523,9 @@ class TestHealthEndpointEdgeCases:
def test_health_with_query_params(self, memory):
"""Should ignore query parameters."""
with patch("app.DeepSeekClient") as mock_llm:
with patch("alfred.app.DeepSeekClient") as mock_llm:
mock_llm.return_value = Mock()
from app import app
from alfred.app import app
client = TestClient(app)
@@ -531,9 +539,9 @@ class TestModelsEndpointEdgeCases:
def test_models_response_format(self, memory):
"""Should return OpenAI-compatible format."""
with patch("app.DeepSeekClient") as mock_llm:
with patch("alfred.app.DeepSeekClient") as mock_llm:
mock_llm.return_value = Mock()
from app import app
from alfred.app import app
client = TestClient(app)

View File

@@ -1,8 +1,7 @@
"""Critical tests for configuration validation."""
import pytest
from agent.config import ConfigurationError, Settings
from alfred.agent.config import ConfigurationError, Settings
class TestConfigValidation:

View File

@@ -1,12 +1,11 @@
"""Edge case tests for configuration and parameters."""
import os
import pytest
from unittest.mock import patch
import pytest
from agent.config import ConfigurationError, Settings
from agent.parameters import (
from alfred.agent.config import ConfigurationError, Settings
from alfred.agent.parameters import (
REQUIRED_PARAMETERS,
ParameterSchema,
format_parameters_for_prompt,

View File

@@ -1,17 +1,15 @@
"""Edge case tests for domain entities and value objects."""
import pytest
from datetime import datetime
import pytest
from domain.movies.entities import Movie
from domain.movies.value_objects import MovieTitle, Quality, ReleaseYear
from domain.shared.exceptions import ValidationError
from domain.shared.value_objects import FilePath, FileSize, ImdbId
from domain.subtitles.entities import Subtitle
from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
from domain.tv_shows.entities import TVShow
from domain.tv_shows.value_objects import ShowStatus
from alfred.domain.movies.entities import Movie
from alfred.domain.movies.value_objects import MovieTitle, Quality, ReleaseYear
from alfred.domain.shared.exceptions import ValidationError
from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId
from alfred.domain.subtitles.entities import Subtitle
from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
from alfred.domain.tv_shows.entities import TVShow
from alfred.domain.tv_shows.value_objects import ShowStatus
class TestImdbIdEdgeCases:

View File

@@ -1,10 +1,8 @@
"""Tests for the Memory system."""
import pytest
from datetime import datetime
import pytest
from infrastructure.persistence import (
from alfred.infrastructure.persistence import (
EpisodicMemory,
LongTermMemory,
Memory,
@@ -13,7 +11,7 @@ from infrastructure.persistence import (
has_memory,
init_memory,
)
from infrastructure.persistence.context import _memory_ctx
from alfred.infrastructure.persistence.context import _memory_ctx
def is_iso_format(s: str) -> bool:

View File

@@ -4,8 +4,7 @@ import json
import os
import pytest
from infrastructure.persistence import (
from alfred.infrastructure.persistence import (
EpisodicMemory,
LongTermMemory,
Memory,
@@ -14,7 +13,7 @@ from infrastructure.persistence import (
init_memory,
set_memory,
)
from infrastructure.persistence.context import _memory_ctx
from alfred.infrastructure.persistence.context import _memory_ctx
class TestLongTermMemoryEdgeCases:

View File

@@ -1,7 +1,7 @@
"""Tests for PromptBuilder."""
from agent.prompts import PromptBuilder
from agent.registry import make_tools
from alfred.agent.prompts import PromptBuilder
from alfred.agent.registry import make_tools
class TestPromptBuilder:

View File

@@ -1,7 +1,7 @@
"""Critical tests for prompt builder - Tests that would have caught bugs."""
from agent.prompts import PromptBuilder
from agent.registry import make_tools
from alfred.agent.prompts import PromptBuilder
from alfred.agent.registry import make_tools
class TestPromptBuilderToolsInjection:

View File

@@ -1,7 +1,7 @@
"""Edge case tests for PromptBuilder."""
from agent.prompts import PromptBuilder
from agent.registry import make_tools
from alfred.agent.prompts import PromptBuilder
from alfred.agent.registry import make_tools
class TestPromptBuilderEdgeCases:
@@ -266,7 +266,7 @@ class TestFormatToolsDescriptionEdgeCases:
def test_format_with_complex_parameters(self, memory):
"""Should format complex parameter schemas."""
from agent.registry import Tool
from alfred.agent.registry import Tool
tools = {
"complex_tool": Tool(

View File

@@ -3,9 +3,8 @@
import inspect
import pytest
from agent.prompts import PromptBuilder
from agent.registry import Tool, _create_tool_from_function, make_tools
from alfred.agent.prompts import PromptBuilder
from alfred.agent.registry import Tool, _create_tool_from_function, make_tools
class TestToolSpecFormat:

View File

@@ -1,8 +1,7 @@
"""Edge case tests for tool registry."""
import pytest
from agent.registry import Tool, make_tools
from alfred.agent.registry import Tool, make_tools
class TestToolEdgeCases:

View File

@@ -1,13 +1,13 @@
"""Tests for JSON repositories."""
from domain.movies.entities import Movie
from domain.movies.value_objects import MovieTitle, Quality, ReleaseYear
from domain.shared.value_objects import FilePath, FileSize, ImdbId
from domain.subtitles.entities import Subtitle
from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
from domain.tv_shows.entities import TVShow
from domain.tv_shows.value_objects import ShowStatus
from infrastructure.persistence.json import (
from alfred.domain.movies.entities import Movie
from alfred.domain.movies.value_objects import MovieTitle, Quality, ReleaseYear
from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId
from alfred.domain.subtitles.entities import Subtitle
from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
from alfred.domain.tv_shows.entities import TVShow
from alfred.domain.tv_shows.value_objects import ShowStatus
from alfred.infrastructure.persistence.json import (
JsonMovieRepository,
JsonSubtitleRepository,
JsonTVShowRepository,

View File

@@ -2,14 +2,14 @@
from datetime import datetime
from domain.movies.entities import Movie
from domain.movies.value_objects import MovieTitle, Quality
from domain.shared.value_objects import FilePath, FileSize, ImdbId
from domain.subtitles.entities import Subtitle
from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
from domain.tv_shows.entities import TVShow
from domain.tv_shows.value_objects import ShowStatus
from infrastructure.persistence.json import (
from alfred.domain.movies.entities import Movie
from alfred.domain.movies.value_objects import MovieTitle, Quality
from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId
from alfred.domain.subtitles.entities import Subtitle
from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset
from alfred.domain.tv_shows.entities import TVShow
from alfred.domain.tv_shows.value_objects import ShowStatus
from alfred.infrastructure.persistence.json import (
JsonMovieRepository,
JsonSubtitleRepository,
JsonTVShowRepository,

View File

@@ -2,8 +2,8 @@
from unittest.mock import Mock, patch
from agent.tools import api as api_tools
from infrastructure.persistence import get_memory
from alfred.agent.tools import api as api_tools
from alfred.infrastructure.persistence import get_memory
def create_mock_response(status_code, json_data=None, text=None):
@@ -21,7 +21,7 @@ def create_mock_response(status_code, json_data=None, text=None):
class TestFindMediaImdbId:
"""Tests for find_media_imdb_id tool."""
@patch("infrastructure.api.tmdb.client.requests.get")
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_success(self, mock_get, memory):
"""Should return movie info on success."""
@@ -56,7 +56,7 @@ class TestFindMediaImdbId:
# Verify HTTP calls
assert mock_get.call_count == 2
@patch("infrastructure.api.tmdb.client.requests.get")
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_stores_in_stm(self, mock_get, memory):
"""Should store result in STM on success."""
@@ -88,7 +88,7 @@ class TestFindMediaImdbId:
assert entity["title"] == "Inception"
assert mem.stm.current_topic == "searching_media"
@patch("infrastructure.api.tmdb.client.requests.get")
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_not_found(self, mock_get, memory):
"""Should return error when not found."""
mock_get.return_value = create_mock_response(200, json_data={"results": []})
@@ -98,7 +98,7 @@ class TestFindMediaImdbId:
assert result["status"] == "error"
assert result["error"] == "not_found"
@patch("infrastructure.api.tmdb.client.requests.get")
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_does_not_store_on_error(self, mock_get, memory):
"""Should not store in STM on error."""
mock_get.return_value = create_mock_response(200, json_data={"results": []})
@@ -112,7 +112,7 @@ class TestFindMediaImdbId:
class TestFindTorrent:
"""Tests for find_torrent tool."""
@patch("infrastructure.api.knaben.client.requests.post")
@patch("alfred.infrastructure.api.knaben.client.requests.post")
def test_success(self, mock_post, memory):
"""Should return torrents on success."""
mock_post.return_value = create_mock_response(
@@ -146,7 +146,7 @@ class TestFindTorrent:
payload = mock_post.call_args[1]["json"]
assert payload["query"] == "Inception 1080p"
@patch("infrastructure.api.knaben.client.requests.post")
@patch("alfred.infrastructure.api.knaben.client.requests.post")
def test_stores_in_episodic(self, mock_post, memory):
"""Should store results in episodic memory."""
mock_post.return_value = create_mock_response(
@@ -171,7 +171,7 @@ class TestFindTorrent:
assert mem.episodic.last_search_results["query"] == "Inception"
assert mem.stm.current_topic == "selecting_torrent"
@patch("infrastructure.api.knaben.client.requests.post")
@patch("alfred.infrastructure.api.knaben.client.requests.post")
def test_results_have_indexes(self, mock_post, memory):
"""Should add indexes to results."""
mock_post.return_value = create_mock_response(
@@ -211,7 +211,7 @@ class TestFindTorrent:
assert results[1]["index"] == 2
assert results[2]["index"] == 3
@patch("infrastructure.api.knaben.client.requests.post")
@patch("alfred.infrastructure.api.knaben.client.requests.post")
def test_not_found(self, mock_post, memory):
"""Should return error when no torrents found."""
mock_post.return_value = create_mock_response(200, json_data={"hits": []})
@@ -286,7 +286,7 @@ class TestAddTorrentToQbittorrent:
This is acceptable mocking because we're testing the TOOL logic, not the client.
"""
@patch("agent.tools.api.qbittorrent_client")
@patch("alfred.agent.tools.api.qbittorrent_client")
def test_success(self, mock_client, memory):
"""Should add torrent successfully and update memory."""
mock_client.add_torrent.return_value = True
@@ -298,7 +298,7 @@ class TestAddTorrentToQbittorrent:
# Verify client was called correctly
mock_client.add_torrent.assert_called_once_with("magnet:?xt=urn:btih:abc123")
@patch("agent.tools.api.qbittorrent_client")
@patch("alfred.agent.tools.api.qbittorrent_client")
def test_adds_to_active_downloads(self, mock_client, memory_with_search_results):
"""Should add to active downloads on success."""
mock_client.add_torrent.return_value = True
@@ -313,7 +313,7 @@ class TestAddTorrentToQbittorrent:
== "Inception.2010.1080p.BluRay.x264"
)
@patch("agent.tools.api.qbittorrent_client")
@patch("alfred.agent.tools.api.qbittorrent_client")
def test_sets_topic_and_ends_workflow(self, mock_client, memory):
"""Should set topic and end workflow."""
mock_client.add_torrent.return_value = True
@@ -326,10 +326,10 @@ class TestAddTorrentToQbittorrent:
assert mem.stm.current_topic == "downloading"
assert mem.stm.current_workflow is None
@patch("agent.tools.api.qbittorrent_client")
@patch("alfred.agent.tools.api.qbittorrent_client")
def test_error_handling(self, mock_client, memory):
"""Should handle client errors correctly."""
from infrastructure.api.qbittorrent.exceptions import QBittorrentAPIError
from alfred.infrastructure.api.qbittorrent.exceptions import QBittorrentAPIError
mock_client.add_torrent.side_effect = QBittorrentAPIError("Connection failed")
@@ -349,7 +349,7 @@ class TestAddTorrentByIndex:
- Error handling for edge cases
"""
@patch("agent.tools.api.qbittorrent_client")
@patch("alfred.agent.tools.api.qbittorrent_client")
def test_success(self, mock_client, memory_with_search_results):
"""Should get torrent by index and add it."""
mock_client.add_torrent.return_value = True
@@ -362,7 +362,7 @@ class TestAddTorrentByIndex:
# Verify correct magnet was extracted and used
mock_client.add_torrent.assert_called_once_with("magnet:?xt=urn:btih:abc123")
@patch("agent.tools.api.qbittorrent_client")
@patch("alfred.agent.tools.api.qbittorrent_client")
def test_uses_correct_magnet(self, mock_client, memory_with_search_results):
"""Should extract correct magnet from index."""
mock_client.add_torrent.return_value = True

View File

@@ -1,18 +1,16 @@
"""Edge case tests for tools."""
import pytest
from unittest.mock import Mock, patch
import pytest
from agent.tools import api as api_tools
from agent.tools import filesystem as fs_tools
from infrastructure.persistence import get_memory
from alfred.agent.tools import api as api_tools
from alfred.agent.tools import filesystem as fs_tools
from alfred.infrastructure.persistence import get_memory
class TestFindTorrentEdgeCases:
"""Edge case tests for find_torrent."""
@patch("agent.tools.api.SearchTorrentsUseCase")
@patch("alfred.agent.tools.api.SearchTorrentsUseCase")
def test_empty_query(self, mock_use_case_class, memory):
"""Should handle empty query."""
mock_response = Mock()
@@ -28,7 +26,7 @@ class TestFindTorrentEdgeCases:
assert result["status"] == "error"
@patch("agent.tools.api.SearchTorrentsUseCase")
@patch("alfred.agent.tools.api.SearchTorrentsUseCase")
def test_very_long_query(self, mock_use_case_class, memory):
"""Should handle very long query."""
mock_response = Mock()
@@ -47,7 +45,7 @@ class TestFindTorrentEdgeCases:
# Should not crash
assert "status" in result
@patch("agent.tools.api.SearchTorrentsUseCase")
@patch("alfred.agent.tools.api.SearchTorrentsUseCase")
def test_special_characters_in_query(self, mock_use_case_class, memory):
"""Should handle special characters in query."""
mock_response = Mock()
@@ -65,7 +63,7 @@ class TestFindTorrentEdgeCases:
assert "status" in result
@patch("agent.tools.api.SearchTorrentsUseCase")
@patch("alfred.agent.tools.api.SearchTorrentsUseCase")
def test_unicode_query(self, mock_use_case_class, memory):
"""Should handle unicode in query."""
mock_response = Mock()
@@ -82,7 +80,7 @@ class TestFindTorrentEdgeCases:
assert "status" in result
@patch("agent.tools.api.SearchTorrentsUseCase")
@patch("alfred.agent.tools.api.SearchTorrentsUseCase")
def test_results_with_missing_fields(self, mock_use_case_class, memory):
"""Should handle results with missing fields."""
mock_response = Mock()
@@ -104,7 +102,7 @@ class TestFindTorrentEdgeCases:
mem = get_memory()
assert len(mem.episodic.last_search_results["results"]) == 2
@patch("agent.tools.api.SearchTorrentsUseCase")
@patch("alfred.agent.tools.api.SearchTorrentsUseCase")
def test_api_timeout(self, mock_use_case_class, memory):
"""Should handle API timeout."""
mock_use_case = Mock()
@@ -157,7 +155,7 @@ class TestGetTorrentByIndexEdgeCases:
class TestAddTorrentEdgeCases:
"""Edge case tests for add_torrent functions."""
@patch("agent.tools.api.AddTorrentUseCase")
@patch("alfred.agent.tools.api.AddTorrentUseCase")
def test_invalid_magnet_link(self, mock_use_case_class, memory):
"""Should handle invalid magnet link."""
mock_response = Mock()
@@ -173,7 +171,7 @@ class TestAddTorrentEdgeCases:
assert result["status"] == "error"
@patch("agent.tools.api.AddTorrentUseCase")
@patch("alfred.agent.tools.api.AddTorrentUseCase")
def test_empty_magnet_link(self, mock_use_case_class, memory):
"""Should handle empty magnet link."""
mock_response = Mock()
@@ -189,7 +187,7 @@ class TestAddTorrentEdgeCases:
assert result["status"] == "error"
@patch("agent.tools.api.AddTorrentUseCase")
@patch("alfred.agent.tools.api.AddTorrentUseCase")
def test_very_long_magnet_link(self, mock_use_case_class, memory):
"""Should handle very long magnet link."""
mock_response = Mock()
@@ -203,7 +201,7 @@ class TestAddTorrentEdgeCases:
assert "status" in result
@patch("agent.tools.api.AddTorrentUseCase")
@patch("alfred.agent.tools.api.AddTorrentUseCase")
def test_qbittorrent_connection_refused(self, mock_use_case_class, memory):
"""Should handle qBittorrent connection refused."""
mock_use_case = Mock()
@@ -391,7 +389,7 @@ class TestFilesystemEdgeCases:
class TestFindMediaImdbIdEdgeCases:
"""Edge case tests for find_media_imdb_id."""
@patch("agent.tools.api.SearchMovieUseCase")
@patch("alfred.agent.tools.api.SearchMovieUseCase")
def test_movie_with_same_name_different_years(self, mock_use_case_class, memory):
"""Should handle movies with same name."""
mock_response = Mock()
@@ -409,7 +407,7 @@ class TestFindMediaImdbIdEdgeCases:
assert result["status"] == "ok"
@patch("agent.tools.api.SearchMovieUseCase")
@patch("alfred.agent.tools.api.SearchMovieUseCase")
def test_movie_with_special_title(self, mock_use_case_class, memory):
"""Should handle movies with special characters in title."""
mock_response = Mock()
@@ -426,7 +424,7 @@ class TestFindMediaImdbIdEdgeCases:
assert result["status"] == "ok"
@patch("agent.tools.api.SearchMovieUseCase")
@patch("alfred.agent.tools.api.SearchMovieUseCase")
def test_tv_show_vs_movie(self, mock_use_case_class, memory):
"""Should distinguish TV shows from movies."""
mock_response = Mock()

View File

@@ -1,11 +1,9 @@
"""Tests for filesystem tools."""
import pytest
from pathlib import Path
import pytest
from agent.tools import filesystem as fs_tools
from infrastructure.persistence import get_memory
from alfred.agent.tools import filesystem as fs_tools
from alfred.infrastructure.persistence import get_memory
class TestSetPathForFolder: