Files
syndarix/mcp-servers/knowledge-base/tests/test_exceptions.py
Felipe Cardoso d0fc7f37ff feat(knowledge-base): implement Knowledge Base MCP Server (#57)
Implements RAG capabilities with pgvector for semantic search:

- Intelligent chunking strategies (code-aware, markdown-aware, text)
- Semantic search with vector similarity (HNSW index)
- Keyword search with PostgreSQL full-text search
- Hybrid search using Reciprocal Rank Fusion (RRF)
- Redis caching for embeddings
- Collection management (ingest, search, delete, stats)
- FastMCP tools: search_knowledge, ingest_content, delete_content,
  list_collections, get_collection_stats, update_document

Testing:
- 128 comprehensive tests covering all components
- 58% code coverage (database integration tests use mocks)
- Passes ruff linting and mypy type checking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 21:33:26 +01:00

308 lines
9.8 KiB
Python

"""Tests for exception classes."""
class TestErrorCode:
"""Tests for ErrorCode enum."""
def test_error_code_values(self):
"""Test error code values."""
from exceptions import ErrorCode
assert ErrorCode.UNKNOWN_ERROR.value == "KB_UNKNOWN_ERROR"
assert ErrorCode.DATABASE_CONNECTION_ERROR.value == "KB_DATABASE_CONNECTION_ERROR"
assert ErrorCode.EMBEDDING_GENERATION_ERROR.value == "KB_EMBEDDING_GENERATION_ERROR"
assert ErrorCode.CHUNKING_ERROR.value == "KB_CHUNKING_ERROR"
assert ErrorCode.SEARCH_ERROR.value == "KB_SEARCH_ERROR"
assert ErrorCode.COLLECTION_NOT_FOUND.value == "KB_COLLECTION_NOT_FOUND"
assert ErrorCode.DOCUMENT_NOT_FOUND.value == "KB_DOCUMENT_NOT_FOUND"
class TestKnowledgeBaseError:
"""Tests for base exception class."""
def test_basic_error(self):
"""Test basic error creation."""
from exceptions import ErrorCode, KnowledgeBaseError
error = KnowledgeBaseError(
message="Something went wrong",
code=ErrorCode.UNKNOWN_ERROR,
)
assert error.message == "Something went wrong"
assert error.code == ErrorCode.UNKNOWN_ERROR
assert error.details == {}
assert error.cause is None
def test_error_with_details(self):
"""Test error with details."""
from exceptions import ErrorCode, KnowledgeBaseError
error = KnowledgeBaseError(
message="Query failed",
code=ErrorCode.DATABASE_QUERY_ERROR,
details={"query": "SELECT * FROM table", "error_code": 42},
)
assert error.details["query"] == "SELECT * FROM table"
assert error.details["error_code"] == 42
def test_error_with_cause(self):
"""Test error with underlying cause."""
from exceptions import ErrorCode, KnowledgeBaseError
original = ValueError("Original error")
error = KnowledgeBaseError(
message="Wrapped error",
code=ErrorCode.INTERNAL_ERROR,
cause=original,
)
assert error.cause is original
assert isinstance(error.cause, ValueError)
def test_to_dict(self):
"""Test to_dict method."""
from exceptions import ErrorCode, KnowledgeBaseError
error = KnowledgeBaseError(
message="Test error",
code=ErrorCode.INVALID_REQUEST,
details={"field": "value"},
)
result = error.to_dict()
assert result["error"] == "KB_INVALID_REQUEST"
assert result["message"] == "Test error"
assert result["details"]["field"] == "value"
def test_str_representation(self):
"""Test string representation."""
from exceptions import ErrorCode, KnowledgeBaseError
error = KnowledgeBaseError(
message="Test error",
code=ErrorCode.INVALID_REQUEST,
)
assert str(error) == "[KB_INVALID_REQUEST] Test error"
def test_repr_representation(self):
"""Test repr representation."""
from exceptions import ErrorCode, KnowledgeBaseError
error = KnowledgeBaseError(
message="Test error",
code=ErrorCode.INVALID_REQUEST,
details={"key": "value"},
)
repr_str = repr(error)
assert "KnowledgeBaseError" in repr_str
assert "Test error" in repr_str
assert "KB_INVALID_REQUEST" in repr_str
class TestDatabaseErrors:
"""Tests for database-related exceptions."""
def test_database_connection_error(self):
"""Test database connection error."""
from exceptions import DatabaseConnectionError, ErrorCode
error = DatabaseConnectionError(
message="Cannot connect to database",
details={"host": "localhost", "port": 5432},
)
assert error.code == ErrorCode.DATABASE_CONNECTION_ERROR
assert error.details["host"] == "localhost"
def test_database_connection_error_default_message(self):
"""Test database connection error with default message."""
from exceptions import DatabaseConnectionError
error = DatabaseConnectionError()
assert error.message == "Failed to connect to database"
def test_database_query_error(self):
"""Test database query error."""
from exceptions import DatabaseQueryError, ErrorCode
error = DatabaseQueryError(
message="Query failed",
query="SELECT * FROM missing_table",
)
assert error.code == ErrorCode.DATABASE_QUERY_ERROR
assert error.details["query"] == "SELECT * FROM missing_table"
class TestEmbeddingErrors:
"""Tests for embedding-related exceptions."""
def test_embedding_generation_error(self):
"""Test embedding generation error."""
from exceptions import EmbeddingGenerationError, ErrorCode
error = EmbeddingGenerationError(
message="Failed to generate",
texts_count=10,
)
assert error.code == ErrorCode.EMBEDDING_GENERATION_ERROR
assert error.details["texts_count"] == 10
def test_embedding_dimension_mismatch(self):
"""Test embedding dimension mismatch error."""
from exceptions import EmbeddingDimensionMismatchError, ErrorCode
error = EmbeddingDimensionMismatchError(
expected=1536,
actual=768,
)
assert error.code == ErrorCode.EMBEDDING_DIMENSION_MISMATCH
assert "expected 1536" in error.message
assert "got 768" in error.message
assert error.details["expected_dimension"] == 1536
assert error.details["actual_dimension"] == 768
class TestChunkingErrors:
"""Tests for chunking-related exceptions."""
def test_unsupported_file_type_error(self):
"""Test unsupported file type error."""
from exceptions import ErrorCode, UnsupportedFileTypeError
error = UnsupportedFileTypeError(
file_type=".xyz",
supported_types=[".py", ".js", ".md"],
)
assert error.code == ErrorCode.UNSUPPORTED_FILE_TYPE
assert error.details["file_type"] == ".xyz"
assert len(error.details["supported_types"]) == 3
def test_file_too_large_error(self):
"""Test file too large error."""
from exceptions import ErrorCode, FileTooLargeError
error = FileTooLargeError(
file_size=10_000_000,
max_size=1_000_000,
)
assert error.code == ErrorCode.FILE_TOO_LARGE
assert error.details["file_size"] == 10_000_000
assert error.details["max_size"] == 1_000_000
def test_encoding_error(self):
"""Test encoding error."""
from exceptions import EncodingError, ErrorCode
error = EncodingError(
message="Cannot decode file",
encoding="utf-8",
)
assert error.code == ErrorCode.ENCODING_ERROR
assert error.details["encoding"] == "utf-8"
class TestSearchErrors:
"""Tests for search-related exceptions."""
def test_invalid_search_type_error(self):
"""Test invalid search type error."""
from exceptions import ErrorCode, InvalidSearchTypeError
error = InvalidSearchTypeError(
search_type="invalid",
valid_types=["semantic", "keyword", "hybrid"],
)
assert error.code == ErrorCode.INVALID_SEARCH_TYPE
assert error.details["search_type"] == "invalid"
assert len(error.details["valid_types"]) == 3
def test_search_timeout_error(self):
"""Test search timeout error."""
from exceptions import ErrorCode, SearchTimeoutError
error = SearchTimeoutError(timeout=30.0)
assert error.code == ErrorCode.SEARCH_TIMEOUT
assert error.details["timeout"] == 30.0
assert "30" in error.message
class TestCollectionErrors:
"""Tests for collection-related exceptions."""
def test_collection_not_found_error(self):
"""Test collection not found error."""
from exceptions import CollectionNotFoundError, ErrorCode
error = CollectionNotFoundError(
collection="missing-collection",
project_id="proj-123",
)
assert error.code == ErrorCode.COLLECTION_NOT_FOUND
assert error.details["collection"] == "missing-collection"
assert error.details["project_id"] == "proj-123"
class TestDocumentErrors:
"""Tests for document-related exceptions."""
def test_document_not_found_error(self):
"""Test document not found error."""
from exceptions import DocumentNotFoundError, ErrorCode
error = DocumentNotFoundError(
source_path="/path/to/file.py",
project_id="proj-123",
)
assert error.code == ErrorCode.DOCUMENT_NOT_FOUND
assert error.details["source_path"] == "/path/to/file.py"
def test_invalid_document_error(self):
"""Test invalid document error."""
from exceptions import ErrorCode, InvalidDocumentError
error = InvalidDocumentError(
message="Empty content",
details={"reason": "no content"},
)
assert error.code == ErrorCode.INVALID_DOCUMENT
class TestProjectErrors:
"""Tests for project-related exceptions."""
def test_project_not_found_error(self):
"""Test project not found error."""
from exceptions import ErrorCode, ProjectNotFoundError
error = ProjectNotFoundError(project_id="missing-proj")
assert error.code == ErrorCode.PROJECT_NOT_FOUND
assert error.details["project_id"] == "missing-proj"
def test_project_access_denied_error(self):
"""Test project access denied error."""
from exceptions import ErrorCode, ProjectAccessDeniedError
error = ProjectAccessDeniedError(project_id="restricted-proj")
assert error.code == ErrorCode.PROJECT_ACCESS_DENIED
assert "restricted-proj" in error.message