Removed async

This commit is contained in:
2025-09-24 21:53:48 +02:00
parent e17c4c7e7b
commit 48f5b009ae
23 changed files with 609 additions and 770 deletions

View File

@@ -1,18 +1,16 @@
"""
Test suite for FileDocumentRepository with async/await support.
Test suite for FileDocumentRepository with async/support.
This module contains comprehensive tests for all FileDocumentRepository methods
using mongomock-motor for in-memory MongoDB testing.
"""
import pytest
from datetime import datetime
from typing import Dict, Any
import pytest_asyncio
import pytest
from bson import ObjectId
from pymongo.errors import DuplicateKeyError, PyMongoError
from mongomock_motor import AsyncMongoMockClient
from mongomock.mongo_client import MongoClient
from pymongo.errors import PyMongoError
from app.database.repositories.document_repository import (
FileDocumentRepository,
@@ -23,13 +21,13 @@ from app.database.repositories.document_repository import (
from app.models.document import FileDocument, FileType, ExtractionMethod
@pytest_asyncio.fixture
async def in_memory_repository():
@pytest.fixture
def in_memory_repository():
"""Create an in-memory FileDocumentRepository for testing."""
client = AsyncMongoMockClient()
client = MongoClient()
db = client.test_database
repo = FileDocumentRepository(db)
await repo.initialize()
repo.initialize()
return repo
@@ -107,14 +105,13 @@ def multiple_sample_files():
class TestFileDocumentRepositoryInitialization:
"""Tests for repository initialization."""
@pytest.mark.asyncio
async def test_i_can_initialize_repository(self):
def test_i_can_initialize_repository(self):
"""Test repository initialization."""
# Arrange
client = AsyncMongoMockClient()
client = MongoClient()
db = client.test_database
repo = FileDocumentRepository(db)
await repo.initialize()
repo.initialize()
# Act & Assert (should not raise any exception)
assert repo.db is not None
@@ -125,11 +122,10 @@ class TestFileDocumentRepositoryInitialization:
class TestFileDocumentRepositoryCreation:
"""Tests for file document creation functionality."""
@pytest.mark.asyncio
async def test_i_can_create_file_document(self, in_memory_repository, sample_file_document):
def test_i_can_create_file_document(self, in_memory_repository, sample_file_document):
"""Test successful file document creation."""
# Act
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
# Assert
assert created_file is not None
@@ -144,54 +140,28 @@ class TestFileDocumentRepositoryCreation:
assert created_file.id is not None
assert isinstance(created_file.id, ObjectId)
@pytest.mark.asyncio
async def test_i_can_create_file_document_without_id(self, in_memory_repository, sample_file_document):
def test_i_can_create_file_document_without_id(self, in_memory_repository, sample_file_document):
"""Test creating file document with _id set to None (should be removed)."""
# Arrange
sample_file_document.id = None
# Act
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
# Assert
assert created_file is not None
assert created_file.id is not None
assert isinstance(created_file.id, ObjectId)
@pytest.mark.asyncio
async def test_i_cannot_create_duplicate_file_document(self, in_memory_repository, sample_file_document):
"""Test that creating file document with duplicate filepath raises DuplicateKeyError."""
# Arrange
await in_memory_repository.create_document(sample_file_document)
duplicate_file = FileDocument(
filename="different_name.pdf",
filepath=sample_file_document.filepath, # Same filepath
file_type=FileType.PDF,
extraction_method=ExtractionMethod.OCR,
metadata={"different": "metadata"},
detected_at=datetime.now(),
file_hash="different_hash_123456789012345678901234567890123456789012345678",
encoding="utf-8",
file_size=2000,
mime_type="application/pdf"
)
# Act & Assert
with pytest.raises(DuplicateKeyError) as exc_info:
await in_memory_repository.create_document(duplicate_file)
assert "already exists" in str(exc_info.value)
@pytest.mark.asyncio
async def test_i_cannot_create_file_document_with_pymongo_error(self, in_memory_repository,
sample_file_document, mocker):
def test_i_cannot_create_file_document_with_pymongo_error(self, in_memory_repository,
sample_file_document, mocker):
"""Test handling of PyMongo errors during file document creation."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'insert_one', side_effect=PyMongoError("Database error"))
# Act & Assert
with pytest.raises(ValueError) as exc_info:
await in_memory_repository.create_document(sample_file_document)
in_memory_repository.create_document(sample_file_document)
assert "Failed to create file document" in str(exc_info.value)
@@ -199,14 +169,13 @@ class TestFileDocumentRepositoryCreation:
class TestFileDocumentRepositoryFinding:
"""Tests for file document finding functionality."""
@pytest.mark.asyncio
async def test_i_can_find_document_by_valid_id(self, in_memory_repository, sample_file_document):
def test_i_can_find_document_by_valid_id(self, in_memory_repository, sample_file_document):
"""Test finding file document by valid ObjectId."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
# Act
found_file = await in_memory_repository.find_document_by_id(str(created_file.id))
found_file = in_memory_repository.find_document_by_id(str(created_file.id))
# Assert
assert found_file is not None
@@ -214,81 +183,74 @@ class TestFileDocumentRepositoryFinding:
assert found_file.filename == created_file.filename
assert found_file.filepath == created_file.filepath
@pytest.mark.asyncio
async def test_i_cannot_find_document_with_invalid_id(self, in_memory_repository):
def test_i_cannot_find_document_with_invalid_id(self, in_memory_repository):
"""Test that invalid ObjectId returns None."""
# Act
found_file = await in_memory_repository.find_document_by_id("invalid_id")
found_file = in_memory_repository.find_document_by_id("invalid_id")
# Assert
assert found_file is None
@pytest.mark.asyncio
async def test_i_cannot_find_document_by_nonexistent_id(self, in_memory_repository):
def test_i_cannot_find_document_by_nonexistent_id(self, in_memory_repository):
"""Test that nonexistent but valid ObjectId returns None."""
# Arrange
nonexistent_id = str(ObjectId())
# Act
found_file = await in_memory_repository.find_document_by_id(nonexistent_id)
found_file = in_memory_repository.find_document_by_id(nonexistent_id)
# Assert
assert found_file is None
@pytest.mark.asyncio
async def test_i_can_find_document_by_file_hash(self, in_memory_repository, sample_file_document):
def test_i_can_find_document_by_file_hash(self, in_memory_repository, sample_file_document):
"""Test finding file document by file hash."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
# Act
found_file = await in_memory_repository.find_document_by_hash(sample_file_document.file_hash)
found_file = in_memory_repository.find_document_by_hash(sample_file_document.file_hash)
# Assert
assert found_file is not None
assert found_file.file_hash == created_file.file_hash
assert found_file.id == created_file.id
@pytest.mark.asyncio
async def test_i_cannot_find_document_with_nonexistent_file_hash(self, in_memory_repository):
def test_i_cannot_find_document_with_nonexistent_file_hash(self, in_memory_repository):
"""Test that nonexistent file hash returns None."""
# Act
found_file = await in_memory_repository.find_document_by_hash("nonexistent_hash")
found_file = in_memory_repository.find_document_by_hash("nonexistent_hash")
# Assert
assert found_file is None
@pytest.mark.asyncio
async def test_i_can_find_document_by_filepath(self, in_memory_repository, sample_file_document):
def test_i_can_find_document_by_filepath(self, in_memory_repository, sample_file_document):
"""Test finding file document by filepath."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
# Act
found_file = await in_memory_repository.find_document_by_filepath(sample_file_document.filepath)
found_file = in_memory_repository.find_document_by_filepath(sample_file_document.filepath)
# Assert
assert found_file is not None
assert found_file.filepath == created_file.filepath
assert found_file.id == created_file.id
@pytest.mark.asyncio
async def test_i_cannot_find_document_with_nonexistent_filepath(self, in_memory_repository):
def test_i_cannot_find_document_with_nonexistent_filepath(self, in_memory_repository):
"""Test that nonexistent filepath returns None."""
# Act
found_file = await in_memory_repository.find_document_by_filepath("/nonexistent/path/file.pdf")
found_file = in_memory_repository.find_document_by_filepath("/nonexistent/path/file.pdf")
# Assert
assert found_file is None
@pytest.mark.asyncio
async def test_i_cannot_find_document_with_pymongo_error(self, in_memory_repository, mocker):
def test_i_cannot_find_document_with_pymongo_error(self, in_memory_repository, mocker):
"""Test handling of PyMongo errors during file document finding."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'find_one', side_effect=PyMongoError("Database error"))
# Act
found_file = await in_memory_repository.find_document_by_hash("test_hash")
found_file = in_memory_repository.find_document_by_hash("test_hash")
# Assert
assert found_file is None
@@ -297,16 +259,15 @@ class TestFileDocumentRepositoryFinding:
class TestFileDocumentRepositoryNameMatching:
"""Tests for file document name matching functionality."""
@pytest.mark.asyncio
async def test_i_can_find_documents_by_name_with_fuzzy_matching(self, in_memory_repository, multiple_sample_files):
def test_i_can_find_documents_by_name_with_fuzzy_matching(self, in_memory_repository, multiple_sample_files):
"""Test finding file documents by filename using fuzzy matching."""
# Arrange
for file_doc in multiple_sample_files:
await in_memory_repository.create_document(file_doc)
in_memory_repository.create_document(file_doc)
# Act
fuzzy_method = FuzzyMatching(threshold=0.5)
found_files = await in_memory_repository.find_document_by_name("document", fuzzy_method)
found_files = in_memory_repository.find_document_by_name("document", fuzzy_method)
# Assert
assert len(found_files) >= 1
@@ -315,44 +276,41 @@ class TestFileDocumentRepositoryNameMatching:
found_filenames = [f.filename for f in found_files]
assert any("document" in fname.lower() for fname in found_filenames)
@pytest.mark.asyncio
async def test_i_can_find_documents_by_name_with_subsequence_matching(self, in_memory_repository,
multiple_sample_files):
def test_i_can_find_documents_by_name_with_subsequence_matching(self, in_memory_repository,
multiple_sample_files):
"""Test finding file documents by filename using subsequence matching."""
# Arrange
for file_doc in multiple_sample_files:
await in_memory_repository.create_document(file_doc)
in_memory_repository.create_document(file_doc)
# Act
subsequence_method = SubsequenceMatching()
found_files = await in_memory_repository.find_document_by_name("doc", subsequence_method)
found_files = in_memory_repository.find_document_by_name("doc", subsequence_method)
# Assert
assert len(found_files) >= 1
assert all(isinstance(file_doc, FileDocument) for file_doc in found_files)
@pytest.mark.asyncio
async def test_i_can_find_documents_by_name_with_default_method(self, in_memory_repository, multiple_sample_files):
def test_i_can_find_documents_by_name_with_default_method(self, in_memory_repository, multiple_sample_files):
"""Test finding file documents by filename with default matching method."""
# Arrange
for file_doc in multiple_sample_files:
await in_memory_repository.create_document(file_doc)
in_memory_repository.create_document(file_doc)
# Act
found_files = await in_memory_repository.find_document_by_name("first")
found_files = in_memory_repository.find_document_by_name("first")
# Assert
assert len(found_files) >= 0
assert all(isinstance(file_doc, FileDocument) for file_doc in found_files)
@pytest.mark.asyncio
async def test_i_cannot_find_documents_by_name_with_pymongo_error(self, in_memory_repository, mocker):
def test_i_cannot_find_documents_by_name_with_pymongo_error(self, in_memory_repository, mocker):
"""Test handling of PyMongo errors during document name matching."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'find', side_effect=PyMongoError("Database error"))
# Act
found_files = await in_memory_repository.find_document_by_name("test")
found_files = in_memory_repository.find_document_by_name("test")
# Assert
assert found_files == []
@@ -361,30 +319,28 @@ class TestFileDocumentRepositoryNameMatching:
class TestFileDocumentRepositoryListing:
"""Tests for file document listing functionality."""
@pytest.mark.asyncio
async def test_i_can_list_documents_with_default_pagination(self, in_memory_repository, multiple_sample_files):
def test_i_can_list_documents_with_default_pagination(self, in_memory_repository, multiple_sample_files):
"""Test listing file documents with default pagination."""
# Arrange
for file_doc in multiple_sample_files:
await in_memory_repository.create_document(file_doc)
in_memory_repository.create_document(file_doc)
# Act
files = await in_memory_repository.list_documents()
files = in_memory_repository.list_documents()
# Assert
assert len(files) == len(multiple_sample_files)
assert all(isinstance(file_doc, FileDocument) for file_doc in files)
@pytest.mark.asyncio
async def test_i_can_list_documents_with_custom_pagination(self, in_memory_repository, multiple_sample_files):
def test_i_can_list_documents_with_custom_pagination(self, in_memory_repository, multiple_sample_files):
"""Test listing file documents with custom pagination."""
# Arrange
for file_doc in multiple_sample_files:
await in_memory_repository.create_document(file_doc)
in_memory_repository.create_document(file_doc)
# Act
files_page1 = await in_memory_repository.list_documents(skip=0, limit=2)
files_page2 = await in_memory_repository.list_documents(skip=2, limit=2)
files_page1 = in_memory_repository.list_documents(skip=0, limit=2)
files_page2 = in_memory_repository.list_documents(skip=2, limit=2)
# Assert
assert len(files_page1) == 2
@@ -395,8 +351,7 @@ class TestFileDocumentRepositoryListing:
page2_ids = [file_doc.id for file_doc in files_page2]
assert len(set(page1_ids).intersection(set(page2_ids))) == 0
@pytest.mark.asyncio
async def test_i_can_list_documents_sorted_by_detected_at(self, in_memory_repository, sample_file_document):
def test_i_can_list_documents_sorted_by_detected_at(self, in_memory_repository, sample_file_document):
"""Test that file documents are sorted by detected_at in descending order."""
# Arrange
file1 = sample_file_document.model_copy()
@@ -411,11 +366,11 @@ class TestFileDocumentRepositoryListing:
file2.file_hash = "hash2" + "0" * 58
file2.detected_at = datetime(2024, 1, 2, 10, 0, 0) # Later date
created_file1 = await in_memory_repository.create_document(file1)
created_file2 = await in_memory_repository.create_document(file2)
created_file1 = in_memory_repository.create_document(file1)
created_file2 = in_memory_repository.create_document(file2)
# Act
files = await in_memory_repository.list_documents()
files = in_memory_repository.list_documents()
# Assert
assert len(files) == 2
@@ -423,23 +378,21 @@ class TestFileDocumentRepositoryListing:
assert files[0].id == created_file2.id
assert files[1].id == created_file1.id
@pytest.mark.asyncio
async def test_i_can_list_empty_documents(self, in_memory_repository):
def test_i_can_list_empty_documents(self, in_memory_repository):
"""Test listing file documents from empty collection."""
# Act
files = await in_memory_repository.list_documents()
files = in_memory_repository.list_documents()
# Assert
assert files == []
@pytest.mark.asyncio
async def test_i_cannot_list_documents_with_pymongo_error(self, in_memory_repository, mocker):
def test_i_cannot_list_documents_with_pymongo_error(self, in_memory_repository, mocker):
"""Test handling of PyMongo errors during file document listing."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'find', side_effect=PyMongoError("Database error"))
# Act
files = await in_memory_repository.list_documents()
files = in_memory_repository.list_documents()
# Assert
assert files == []
@@ -448,15 +401,14 @@ class TestFileDocumentRepositoryListing:
class TestFileDocumentRepositoryUpdate:
"""Tests for file document update functionality."""
@pytest.mark.asyncio
async def test_i_can_update_document_successfully(self, in_memory_repository, sample_file_document,
sample_update_data):
def test_i_can_update_document_successfully(self, in_memory_repository, sample_file_document,
sample_update_data):
"""Test successful file document update."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
# Act
updated_file = await in_memory_repository.update_document(str(created_file.id), sample_update_data)
updated_file = in_memory_repository.update_document(str(created_file.id), sample_update_data)
# Assert
assert updated_file is not None
@@ -467,15 +419,14 @@ class TestFileDocumentRepositoryUpdate:
assert updated_file.filename == created_file.filename # Unchanged fields remain
assert updated_file.filepath == created_file.filepath
@pytest.mark.asyncio
async def test_i_can_update_document_with_partial_data(self, in_memory_repository, sample_file_document):
def test_i_can_update_document_with_partial_data(self, in_memory_repository, sample_file_document):
"""Test updating file document with partial data."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
partial_update = {"file_size": 999999}
# Act
updated_file = await in_memory_repository.update_document(str(created_file.id), partial_update)
updated_file = in_memory_repository.update_document(str(created_file.id), partial_update)
# Assert
assert updated_file is not None
@@ -483,30 +434,28 @@ class TestFileDocumentRepositoryUpdate:
assert updated_file.filename == created_file.filename # Should remain unchanged
assert updated_file.metadata == created_file.metadata # Should remain unchanged
@pytest.mark.asyncio
async def test_i_can_update_document_filtering_none_values(self, in_memory_repository, sample_file_document):
def test_i_can_update_document_filtering_none_values(self, in_memory_repository, sample_file_document):
"""Test that None values are filtered out from update data."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
update_with_none = {"file_size": 777777, "metadata": None}
# Act
updated_file = await in_memory_repository.update_document(str(created_file.id), update_with_none)
updated_file = in_memory_repository.update_document(str(created_file.id), update_with_none)
# Assert
assert updated_file is not None
assert updated_file.file_size == 777777
assert updated_file.metadata == created_file.metadata # Should remain unchanged (None filtered out)
@pytest.mark.asyncio
async def test_i_can_update_document_with_empty_data(self, in_memory_repository, sample_file_document):
def test_i_can_update_document_with_empty_data(self, in_memory_repository, sample_file_document):
"""Test updating file document with empty data returns current document."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
empty_update = {}
# Act
result = await in_memory_repository.update_document(str(created_file.id), empty_update)
result = in_memory_repository.update_document(str(created_file.id), empty_update)
# Assert
assert result is not None
@@ -514,38 +463,35 @@ class TestFileDocumentRepositoryUpdate:
assert result.filepath == created_file.filepath
assert result.metadata == created_file.metadata
@pytest.mark.asyncio
async def test_i_cannot_update_document_with_invalid_id(self, in_memory_repository, sample_update_data):
def test_i_cannot_update_document_with_invalid_id(self, in_memory_repository, sample_update_data):
"""Test that updating with invalid ID returns None."""
# Act
result = await in_memory_repository.update_document("invalid_id", sample_update_data)
result = in_memory_repository.update_document("invalid_id", sample_update_data)
# Assert
assert result is None
@pytest.mark.asyncio
async def test_i_cannot_update_nonexistent_document(self, in_memory_repository, sample_update_data):
def test_i_cannot_update_nonexistent_document(self, in_memory_repository, sample_update_data):
"""Test that updating nonexistent file document returns None."""
# Arrange
nonexistent_id = str(ObjectId())
# Act
result = await in_memory_repository.update_document(nonexistent_id, sample_update_data)
result = in_memory_repository.update_document(nonexistent_id, sample_update_data)
# Assert
assert result is None
@pytest.mark.asyncio
async def test_i_cannot_update_document_with_pymongo_error(self, in_memory_repository, sample_file_document,
sample_update_data, mocker):
def test_i_cannot_update_document_with_pymongo_error(self, in_memory_repository, sample_file_document,
sample_update_data, mocker):
"""Test handling of PyMongo errors during file document update."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
mocker.patch.object(in_memory_repository.collection, 'find_one_and_update',
side_effect=PyMongoError("Database error"))
# Act
result = await in_memory_repository.update_document(str(created_file.id), sample_update_data)
result = in_memory_repository.update_document(str(created_file.id), sample_update_data)
# Assert
assert result is None
@@ -554,52 +500,48 @@ class TestFileDocumentRepositoryUpdate:
class TestFileDocumentRepositoryDeletion:
"""Tests for file document deletion functionality."""
@pytest.mark.asyncio
async def test_i_can_delete_existing_document(self, in_memory_repository, sample_file_document):
def test_i_can_delete_existing_document(self, in_memory_repository, sample_file_document):
"""Test successful file document deletion."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
# Act
deletion_result = await in_memory_repository.delete_document(str(created_file.id))
deletion_result = in_memory_repository.delete_document(str(created_file.id))
# Assert
assert deletion_result is True
# Verify document is actually deleted
found_file = await in_memory_repository.find_document_by_id(str(created_file.id))
found_file = in_memory_repository.find_document_by_id(str(created_file.id))
assert found_file is None
@pytest.mark.asyncio
async def test_i_cannot_delete_document_with_invalid_id(self, in_memory_repository):
def test_i_cannot_delete_document_with_invalid_id(self, in_memory_repository):
"""Test that deleting with invalid ID returns False."""
# Act
result = await in_memory_repository.delete_document("invalid_id")
result = in_memory_repository.delete_document("invalid_id")
# Assert
assert result is False
@pytest.mark.asyncio
async def test_i_cannot_delete_nonexistent_document(self, in_memory_repository):
def test_i_cannot_delete_nonexistent_document(self, in_memory_repository):
"""Test that deleting nonexistent file document returns False."""
# Arrange
nonexistent_id = str(ObjectId())
# Act
result = await in_memory_repository.delete_document(nonexistent_id)
result = in_memory_repository.delete_document(nonexistent_id)
# Assert
assert result is False
@pytest.mark.asyncio
async def test_i_cannot_delete_document_with_pymongo_error(self, in_memory_repository, sample_file_document, mocker):
def test_i_cannot_delete_document_with_pymongo_error(self, in_memory_repository, sample_file_document, mocker):
"""Test handling of PyMongo errors during file document deletion."""
# Arrange
created_file = await in_memory_repository.create_document(sample_file_document)
created_file = in_memory_repository.create_document(sample_file_document)
mocker.patch.object(in_memory_repository.collection, 'delete_one', side_effect=PyMongoError("Database error"))
# Act
result = await in_memory_repository.delete_document(str(created_file.id))
result = in_memory_repository.delete_document(str(created_file.id))
# Assert
assert result is False
@@ -608,36 +550,33 @@ class TestFileDocumentRepositoryDeletion:
class TestFileDocumentRepositoryUtilities:
"""Tests for utility methods."""
@pytest.mark.asyncio
async def test_i_can_count_documents(self, in_memory_repository, sample_file_document):
def test_i_can_count_documents(self, in_memory_repository, sample_file_document):
"""Test counting file documents."""
# Arrange
initial_count = await in_memory_repository.count_documents()
await in_memory_repository.create_document(sample_file_document)
initial_count = in_memory_repository.count_documents()
in_memory_repository.create_document(sample_file_document)
# Act
final_count = await in_memory_repository.count_documents()
final_count = in_memory_repository.count_documents()
# Assert
assert final_count == initial_count + 1
@pytest.mark.asyncio
async def test_i_can_count_zero_documents(self, in_memory_repository):
def test_i_can_count_zero_documents(self, in_memory_repository):
"""Test counting file documents in empty collection."""
# Act
count = await in_memory_repository.count_documents()
count = in_memory_repository.count_documents()
# Assert
assert count == 0
@pytest.mark.asyncio
async def test_i_cannot_count_documents_with_pymongo_error(self, in_memory_repository, mocker):
def test_i_cannot_count_documents_with_pymongo_error(self, in_memory_repository, mocker):
"""Test handling of PyMongo errors during file document counting."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'count_documents', side_effect=PyMongoError("Database error"))
# Act
count = await in_memory_repository.count_documents()
count = in_memory_repository.count_documents()
# Assert
assert count == 0

View File

@@ -1,5 +1,5 @@
"""
Test suite for JobRepository with async/await support.
Test suite for JobRepository with async/support.
This module contains comprehensive tests for all JobRepository methods
using mongomock-motor for in-memory MongoDB testing.
@@ -8,8 +8,8 @@ using mongomock-motor for in-memory MongoDB testing.
from datetime import datetime
import pytest
import pytest_asyncio
from bson import ObjectId
from mongomock.mongo_client import MongoClient
from mongomock_motor import AsyncMongoMockClient
from pymongo.errors import PyMongoError
@@ -19,13 +19,13 @@ from app.models.job import ProcessingJob, ProcessingStatus
from app.models.types import PyObjectId
@pytest_asyncio.fixture
async def in_memory_repository():
@pytest.fixture
def in_memory_repository():
"""Create an in-memory JobRepository for testing."""
client = AsyncMongoMockClient()
client = MongoClient()
db = client.test_database
repo = JobRepository(db)
await repo.initialize()
repo.initialize()
return repo
@@ -82,8 +82,7 @@ def multiple_sample_jobs():
class TestJobRepositoryInitialization:
"""Tests for repository initialization."""
@pytest.mark.asyncio
async def test_i_can_initialize_repository(self):
def test_i_can_initialize_repository(self):
"""Test repository initialization."""
# Arrange
client = AsyncMongoMockClient()
@@ -91,7 +90,7 @@ class TestJobRepositoryInitialization:
repo = JobRepository(db)
# Act
initialized_repo = await repo.initialize()
initialized_repo = repo.initialize()
# Assert
assert initialized_repo is repo
@@ -102,11 +101,10 @@ class TestJobRepositoryInitialization:
class TestJobRepositoryCreation:
"""Tests for job creation functionality."""
@pytest.mark.asyncio
async def test_i_can_create_job_with_task_id(self, in_memory_repository, sample_document_id, sample_task_id):
def test_i_can_create_job_with_task_id(self, in_memory_repository, sample_document_id, sample_task_id):
"""Test successful job creation with task ID."""
# Act
created_job = await in_memory_repository.create_job(sample_document_id, sample_task_id)
created_job = in_memory_repository.create_job(sample_document_id, sample_task_id)
# Assert
assert created_job is not None
@@ -120,11 +118,10 @@ class TestJobRepositoryCreation:
assert created_job.id is not None
assert isinstance(created_job.id, ObjectId)
@pytest.mark.asyncio
async def test_i_can_create_job_without_task_id(self, in_memory_repository, sample_document_id):
def test_i_can_create_job_without_task_id(self, in_memory_repository, sample_document_id):
"""Test successful job creation without task ID."""
# Act
created_job = await in_memory_repository.create_job(sample_document_id)
created_job = in_memory_repository.create_job(sample_document_id)
# Assert
assert created_job is not None
@@ -138,28 +135,26 @@ class TestJobRepositoryCreation:
assert created_job.id is not None
assert isinstance(created_job.id, ObjectId)
@pytest.mark.asyncio
async def test_i_cannot_create_duplicate_job_for_document(self, in_memory_repository, sample_document_id,
sample_task_id):
def test_i_cannot_create_duplicate_job_for_document(self, in_memory_repository, sample_document_id,
sample_task_id):
"""Test that creating job with duplicate document_id raises DuplicateKeyError."""
# Arrange
await in_memory_repository.create_job(sample_document_id, sample_task_id)
in_memory_repository.create_job(sample_document_id, sample_task_id)
# Act & Assert
with pytest.raises(JobRepositoryError) as exc_info:
await in_memory_repository.create_job(sample_document_id, "different-task-id")
in_memory_repository.create_job(sample_document_id, "different-task-id")
assert "create_job" in str(exc_info.value)
@pytest.mark.asyncio
async def test_i_cannot_create_job_with_pymongo_error(self, in_memory_repository, sample_document_id, mocker):
def test_i_cannot_create_job_with_pymongo_error(self, in_memory_repository, sample_document_id, mocker):
"""Test handling of PyMongo errors during job creation."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'insert_one', side_effect=PyMongoError("Database error"))
# Act & Assert
with pytest.raises(JobRepositoryError) as exc_info:
await in_memory_repository.create_job(sample_document_id)
in_memory_repository.create_job(sample_document_id)
assert "create_job" in str(exc_info.value)
@@ -167,14 +162,13 @@ class TestJobRepositoryCreation:
class TestJobRepositoryFinding:
"""Tests for job finding functionality."""
@pytest.mark.asyncio
async def test_i_can_find_job_by_valid_id(self, in_memory_repository, sample_document_id, sample_task_id):
def test_i_can_find_job_by_valid_id(self, in_memory_repository, sample_document_id, sample_task_id):
"""Test finding job by valid ObjectId."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id, sample_task_id)
created_job = in_memory_repository.create_job(sample_document_id, sample_task_id)
# Act
found_job = await in_memory_repository.find_job_by_id(created_job.id)
found_job = in_memory_repository.find_job_by_id(created_job.id)
# Assert
assert found_job is not None
@@ -183,97 +177,90 @@ class TestJobRepositoryFinding:
assert found_job.task_id == created_job.task_id
assert found_job.status == created_job.status
@pytest.mark.asyncio
async def test_i_cannot_find_job_by_nonexistent_id(self, in_memory_repository):
def test_i_cannot_find_job_by_nonexistent_id(self, in_memory_repository):
"""Test that nonexistent ObjectId returns None."""
# Arrange
nonexistent_id = PyObjectId()
# Act
found_job = await in_memory_repository.find_job_by_id(nonexistent_id)
found_job = in_memory_repository.find_job_by_id(nonexistent_id)
# Assert
assert found_job is None
@pytest.mark.asyncio
async def test_i_cannot_find_job_with_pymongo_error(self, in_memory_repository, mocker):
def test_i_cannot_find_job_with_pymongo_error(self, in_memory_repository, mocker):
"""Test handling of PyMongo errors during job finding."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'find_one', side_effect=PyMongoError("Database error"))
# Act & Assert
with pytest.raises(JobRepositoryError) as exc_info:
await in_memory_repository.find_job_by_id(PyObjectId())
in_memory_repository.find_job_by_id(PyObjectId())
assert "get_job_by_id" in str(exc_info.value)
@pytest.mark.asyncio
async def test_i_can_find_jobs_by_document_id(self, in_memory_repository, sample_document_id, sample_task_id):
def test_i_can_find_jobs_by_document_id(self, in_memory_repository, sample_document_id, sample_task_id):
"""Test finding jobs by document ID."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id, sample_task_id)
created_job = in_memory_repository.create_job(sample_document_id, sample_task_id)
# Act
found_jobs = await in_memory_repository.find_jobs_by_document_id(sample_document_id)
found_jobs = in_memory_repository.find_jobs_by_document_id(sample_document_id)
# Assert
assert len(found_jobs) == 1
assert found_jobs[0].id == created_job.id
assert found_jobs[0].document_id == sample_document_id
@pytest.mark.asyncio
async def test_i_can_find_empty_jobs_list_for_nonexistent_document(self, in_memory_repository):
def test_i_can_find_empty_jobs_list_for_nonexistent_document(self, in_memory_repository):
"""Test that nonexistent document ID returns empty list."""
# Arrange
nonexistent_id = ObjectId()
# Act
found_jobs = await in_memory_repository.find_jobs_by_document_id(nonexistent_id)
found_jobs = in_memory_repository.find_jobs_by_document_id(nonexistent_id)
# Assert
assert found_jobs == []
@pytest.mark.asyncio
async def test_i_cannot_find_jobs_by_document_with_pymongo_error(self, in_memory_repository, mocker):
def test_i_cannot_find_jobs_by_document_with_pymongo_error(self, in_memory_repository, mocker):
"""Test handling of PyMongo errors during finding jobs by document ID."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'find', side_effect=PyMongoError("Database error"))
# Act & Assert
with pytest.raises(JobRepositoryError) as exc_info:
await in_memory_repository.find_jobs_by_document_id(PyObjectId())
in_memory_repository.find_jobs_by_document_id(PyObjectId())
assert "get_jobs_by_file_id" in str(exc_info.value)
@pytest.mark.asyncio
@pytest.mark.parametrize("status", [
ProcessingStatus.PENDING,
ProcessingStatus.PROCESSING,
ProcessingStatus.COMPLETED
])
async def test_i_can_find_jobs_by_pending_status(self, in_memory_repository, sample_document_id, status):
def test_i_can_find_jobs_by_pending_status(self, in_memory_repository, sample_document_id, status):
"""Test finding jobs by PENDING status."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
await in_memory_repository.update_job_status(created_job.id, status)
created_job = in_memory_repository.create_job(sample_document_id)
in_memory_repository.update_job_status(created_job.id, status)
# Act
found_jobs = await in_memory_repository.get_jobs_by_status(status)
found_jobs = in_memory_repository.get_jobs_by_status(status)
# Assert
assert len(found_jobs) == 1
assert found_jobs[0].id == created_job.id
assert found_jobs[0].status == status
@pytest.mark.asyncio
async def test_i_can_find_jobs_by_failed_status(self, in_memory_repository, sample_document_id):
def test_i_can_find_jobs_by_failed_status(self, in_memory_repository, sample_document_id):
"""Test finding jobs by FAILED status."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
await in_memory_repository.update_job_status(created_job.id, ProcessingStatus.FAILED, "Test error")
created_job = in_memory_repository.create_job(sample_document_id)
in_memory_repository.update_job_status(created_job.id, ProcessingStatus.FAILED, "Test error")
# Act
found_jobs = await in_memory_repository.get_jobs_by_status(ProcessingStatus.FAILED)
found_jobs = in_memory_repository.get_jobs_by_status(ProcessingStatus.FAILED)
# Assert
assert len(found_jobs) == 1
@@ -281,24 +268,22 @@ class TestJobRepositoryFinding:
assert found_jobs[0].status == ProcessingStatus.FAILED
assert found_jobs[0].error_message == "Test error"
@pytest.mark.asyncio
async def test_i_can_find_empty_jobs_list_for_unused_status(self, in_memory_repository):
def test_i_can_find_empty_jobs_list_for_unused_status(self, in_memory_repository):
"""Test that unused status returns empty list."""
# Act
found_jobs = await in_memory_repository.get_jobs_by_status(ProcessingStatus.COMPLETED)
found_jobs = in_memory_repository.get_jobs_by_status(ProcessingStatus.COMPLETED)
# Assert
assert found_jobs == []
@pytest.mark.asyncio
async def test_i_cannot_find_jobs_by_status_with_pymongo_error(self, in_memory_repository, mocker):
def test_i_cannot_find_jobs_by_status_with_pymongo_error(self, in_memory_repository, mocker):
"""Test handling of PyMongo errors during finding jobs by status."""
# Arrange
mocker.patch.object(in_memory_repository.collection, 'find', side_effect=PyMongoError("Database error"))
# Act & Assert
with pytest.raises(JobRepositoryError) as exc_info:
await in_memory_repository.get_jobs_by_status(ProcessingStatus.PENDING)
in_memory_repository.get_jobs_by_status(ProcessingStatus.PENDING)
assert "get_jobs_by_status" in str(exc_info.value)
@@ -306,14 +291,13 @@ class TestJobRepositoryFinding:
class TestJobRepositoryStatusUpdate:
"""Tests for job status update functionality."""
@pytest.mark.asyncio
async def test_i_can_update_job_status_to_processing(self, in_memory_repository, sample_document_id):
def test_i_can_update_job_status_to_processing(self, in_memory_repository, sample_document_id):
"""Test updating job status to PROCESSING with started_at timestamp."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
created_job = in_memory_repository.create_job(sample_document_id)
# Act
updated_job = await in_memory_repository.update_job_status(created_job.id, ProcessingStatus.PROCESSING)
updated_job = in_memory_repository.update_job_status(created_job.id, ProcessingStatus.PROCESSING)
# Assert
assert updated_job is not None
@@ -323,15 +307,14 @@ class TestJobRepositoryStatusUpdate:
assert updated_job.completed_at is None
assert updated_job.error_message is None
@pytest.mark.asyncio
async def test_i_can_update_job_status_to_completed(self, in_memory_repository, sample_document_id):
def test_i_can_update_job_status_to_completed(self, in_memory_repository, sample_document_id):
"""Test updating job status to COMPLETED with completed_at timestamp."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
await in_memory_repository.update_job_status(created_job.id, ProcessingStatus.PROCESSING)
created_job = in_memory_repository.create_job(sample_document_id)
in_memory_repository.update_job_status(created_job.id, ProcessingStatus.PROCESSING)
# Act
updated_job = await in_memory_repository.update_job_status(created_job.id, ProcessingStatus.COMPLETED)
updated_job = in_memory_repository.update_job_status(created_job.id, ProcessingStatus.COMPLETED)
# Assert
assert updated_job is not None
@@ -341,15 +324,14 @@ class TestJobRepositoryStatusUpdate:
assert updated_job.completed_at is not None
assert updated_job.error_message is None
@pytest.mark.asyncio
async def test_i_can_update_job_status_to_failed_with_error(self, in_memory_repository, sample_document_id):
def test_i_can_update_job_status_to_failed_with_error(self, in_memory_repository, sample_document_id):
"""Test updating job status to FAILED with error message and completed_at timestamp."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
created_job = in_memory_repository.create_job(sample_document_id)
error_message = "Processing failed due to invalid format"
# Act
updated_job = await in_memory_repository.update_job_status(
updated_job = in_memory_repository.update_job_status(
created_job.id, ProcessingStatus.FAILED, error_message
)
@@ -360,14 +342,13 @@ class TestJobRepositoryStatusUpdate:
assert updated_job.completed_at is not None
assert updated_job.error_message == error_message
@pytest.mark.asyncio
async def test_i_can_update_job_status_to_failed_without_error(self, in_memory_repository, sample_document_id):
def test_i_can_update_job_status_to_failed_without_error(self, in_memory_repository, sample_document_id):
"""Test updating job status to FAILED without error message."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
created_job = in_memory_repository.create_job(sample_document_id)
# Act
updated_job = await in_memory_repository.update_job_status(created_job.id, ProcessingStatus.FAILED)
updated_job = in_memory_repository.update_job_status(created_job.id, ProcessingStatus.FAILED)
# Assert
assert updated_job is not None
@@ -376,29 +357,27 @@ class TestJobRepositoryStatusUpdate:
assert updated_job.completed_at is not None
assert updated_job.error_message is None
@pytest.mark.asyncio
async def test_i_cannot_update_nonexistent_job_status(self, in_memory_repository):
def test_i_cannot_update_nonexistent_job_status(self, in_memory_repository):
"""Test that updating nonexistent job returns None."""
# Arrange
nonexistent_id = ObjectId()
# Act
result = await in_memory_repository.update_job_status(nonexistent_id, ProcessingStatus.COMPLETED)
result = in_memory_repository.update_job_status(nonexistent_id, ProcessingStatus.COMPLETED)
# Assert
assert result is None
@pytest.mark.asyncio
async def test_i_cannot_update_job_status_with_pymongo_error(self, in_memory_repository, sample_document_id, mocker):
def test_i_cannot_update_job_status_with_pymongo_error(self, in_memory_repository, sample_document_id, mocker):
"""Test handling of PyMongo errors during job status update."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
created_job = in_memory_repository.create_job(sample_document_id)
mocker.patch.object(in_memory_repository.collection, 'find_one_and_update',
side_effect=PyMongoError("Database error"))
# Act & Assert
with pytest.raises(JobRepositoryError) as exc_info:
await in_memory_repository.update_job_status(created_job.id, ProcessingStatus.COMPLETED)
in_memory_repository.update_job_status(created_job.id, ProcessingStatus.COMPLETED)
assert "update_job_status" in str(exc_info.value)
@@ -406,44 +385,41 @@ class TestJobRepositoryStatusUpdate:
class TestJobRepositoryDeletion:
"""Tests for job deletion functionality."""
@pytest.mark.asyncio
async def test_i_can_delete_existing_job(self, in_memory_repository, sample_document_id):
def test_i_can_delete_existing_job(self, in_memory_repository, sample_document_id):
"""Test successful job deletion."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
created_job = in_memory_repository.create_job(sample_document_id)
# Act
deletion_result = await in_memory_repository.delete_job(created_job.id)
deletion_result = in_memory_repository.delete_job(created_job.id)
# Assert
assert deletion_result is True
# Verify job is actually deleted
found_job = await in_memory_repository.find_job_by_id(created_job.id)
found_job = in_memory_repository.find_job_by_id(created_job.id)
assert found_job is None
@pytest.mark.asyncio
async def test_i_cannot_delete_nonexistent_job(self, in_memory_repository):
def test_i_cannot_delete_nonexistent_job(self, in_memory_repository):
"""Test that deleting nonexistent job returns False."""
# Arrange
nonexistent_id = ObjectId()
# Act
result = await in_memory_repository.delete_job(nonexistent_id)
result = in_memory_repository.delete_job(nonexistent_id)
# Assert
assert result is False
@pytest.mark.asyncio
async def test_i_cannot_delete_job_with_pymongo_error(self, in_memory_repository, sample_document_id, mocker):
def test_i_cannot_delete_job_with_pymongo_error(self, in_memory_repository, sample_document_id, mocker):
"""Test handling of PyMongo errors during job deletion."""
# Arrange
created_job = await in_memory_repository.create_job(sample_document_id)
created_job = in_memory_repository.create_job(sample_document_id)
mocker.patch.object(in_memory_repository.collection, 'delete_one', side_effect=PyMongoError("Database error"))
# Act & Assert
with pytest.raises(JobRepositoryError) as exc_info:
await in_memory_repository.delete_job(created_job.id)
in_memory_repository.delete_job(created_job.id)
assert "delete_job" in str(exc_info.value)
@@ -451,38 +427,36 @@ class TestJobRepositoryDeletion:
class TestJobRepositoryComplexScenarios:
"""Tests for complex job repository scenarios."""
@pytest.mark.asyncio
async def test_i_can_handle_complete_job_lifecycle(self, in_memory_repository, sample_document_id, sample_task_id):
def test_i_can_handle_complete_job_lifecycle(self, in_memory_repository, sample_document_id, sample_task_id):
"""Test complete job lifecycle from creation to completion."""
# Create job
job = await in_memory_repository.create_job(sample_document_id, sample_task_id)
job = in_memory_repository.create_job(sample_document_id, sample_task_id)
assert job.status == ProcessingStatus.PENDING
assert job.started_at is None
assert job.completed_at is None
# Start processing
job = await in_memory_repository.update_job_status(job.id, ProcessingStatus.PROCESSING)
job = in_memory_repository.update_job_status(job.id, ProcessingStatus.PROCESSING)
assert job.status == ProcessingStatus.PROCESSING
assert job.started_at is not None
assert job.completed_at is None
# Complete job
job = await in_memory_repository.update_job_status(job.id, ProcessingStatus.COMPLETED)
job = in_memory_repository.update_job_status(job.id, ProcessingStatus.COMPLETED)
assert job.status == ProcessingStatus.COMPLETED
assert job.started_at is not None
assert job.completed_at is not None
assert job.error_message is None
@pytest.mark.asyncio
async def test_i_can_handle_job_failure_scenario(self, in_memory_repository, sample_document_id, sample_task_id):
def test_i_can_handle_job_failure_scenario(self, in_memory_repository, sample_document_id, sample_task_id):
"""Test job failure scenario with error message."""
# Create and start job
job = await in_memory_repository.create_job(sample_document_id, sample_task_id)
job = await in_memory_repository.update_job_status(job.id, ProcessingStatus.PROCESSING)
job = in_memory_repository.create_job(sample_document_id, sample_task_id)
job = in_memory_repository.update_job_status(job.id, ProcessingStatus.PROCESSING)
# Fail job with error
error_msg = "File format not supported"
job = await in_memory_repository.update_job_status(job.id, ProcessingStatus.FAILED, error_msg)
job = in_memory_repository.update_job_status(job.id, ProcessingStatus.FAILED, error_msg)
# Assert failure state
assert job.status == ProcessingStatus.FAILED
@@ -490,28 +464,27 @@ class TestJobRepositoryComplexScenarios:
assert job.completed_at is not None
assert job.error_message == error_msg
@pytest.mark.asyncio
async def test_i_can_handle_multiple_documents_with_different_statuses(self, in_memory_repository):
def test_i_can_handle_multiple_documents_with_different_statuses(self, in_memory_repository):
"""Test managing multiple jobs for different documents with various statuses."""
# Create jobs for different documents
doc1 = PyObjectId()
doc2 = PyObjectId()
doc3 = PyObjectId()
job1 = await in_memory_repository.create_job(doc1, "task-1")
job2 = await in_memory_repository.create_job(doc2, "task-2")
job3 = await in_memory_repository.create_job(doc3, "task-3")
job1 = in_memory_repository.create_job(doc1, "task-1")
job2 = in_memory_repository.create_job(doc2, "task-2")
job3 = in_memory_repository.create_job(doc3, "task-3")
# Update to different statuses
await in_memory_repository.update_job_status(job1.id, ProcessingStatus.PROCESSING)
await in_memory_repository.update_job_status(job2.id, ProcessingStatus.COMPLETED)
await in_memory_repository.update_job_status(job3.id, ProcessingStatus.FAILED, "Error occurred")
in_memory_repository.update_job_status(job1.id, ProcessingStatus.PROCESSING)
in_memory_repository.update_job_status(job2.id, ProcessingStatus.COMPLETED)
in_memory_repository.update_job_status(job3.id, ProcessingStatus.FAILED, "Error occurred")
# Verify status queries
pending_jobs = await in_memory_repository.get_jobs_by_status(ProcessingStatus.PENDING)
processing_jobs = await in_memory_repository.get_jobs_by_status(ProcessingStatus.PROCESSING)
completed_jobs = await in_memory_repository.get_jobs_by_status(ProcessingStatus.COMPLETED)
failed_jobs = await in_memory_repository.get_jobs_by_status(ProcessingStatus.FAILED)
pending_jobs = in_memory_repository.get_jobs_by_status(ProcessingStatus.PENDING)
processing_jobs = in_memory_repository.get_jobs_by_status(ProcessingStatus.PROCESSING)
completed_jobs = in_memory_repository.get_jobs_by_status(ProcessingStatus.COMPLETED)
failed_jobs = in_memory_repository.get_jobs_by_status(ProcessingStatus.FAILED)
assert len(pending_jobs) == 0
assert len(processing_jobs) == 1

View File

@@ -1,29 +1,26 @@
"""
Test suite for UserRepository with async/await support.
Test suite for UserRepository with async/support.
This module contains comprehensive tests for all UserRepository methods
using mongomock-motor for in-memory MongoDB testing.
"""
import pytest
from datetime import datetime
import pytest_asyncio
from bson import ObjectId
from mongomock.mongo_client import MongoClient
from pymongo.errors import DuplicateKeyError
from mongomock_motor import AsyncMongoMockClient
from app.database.repositories.user_repository import UserRepository
from app.models.user import UserCreate, UserUpdate, UserInDB
from app.models.user import UserCreate, UserUpdate
@pytest_asyncio.fixture
async def in_memory_repository():
@pytest.fixture
def in_memory_repository():
"""Create an in-memory UserRepository for testing."""
client = AsyncMongoMockClient()
client = MongoClient()
db = client.test_database
repo = UserRepository(db)
await repo.initialize()
repo.initialize()
return repo
@@ -51,11 +48,10 @@ def sample_user_update():
class TestUserRepositoryCreation:
"""Tests for user creation functionality."""
@pytest.mark.asyncio
async def test_i_can_create_user(self, in_memory_repository, sample_user_create):
def test_i_can_create_user(self, in_memory_repository, sample_user_create):
"""Test successful user creation."""
# Act
created_user = await in_memory_repository.create_user(sample_user_create)
created_user = in_memory_repository.create_user(sample_user_create)
# Assert
assert created_user is not None
@@ -68,15 +64,14 @@ class TestUserRepositoryCreation:
assert created_user.updated_at is not None
assert created_user.hashed_password != sample_user_create.password # Should be hashed
@pytest.mark.asyncio
async def test_i_cannot_create_user_with_duplicate_username(self, in_memory_repository, sample_user_create):
def test_i_cannot_create_user_with_duplicate_username(self, in_memory_repository, sample_user_create):
"""Test that creating user with duplicate username raises DuplicateKeyError."""
# Arrange
await in_memory_repository.create_user(sample_user_create)
in_memory_repository.create_user(sample_user_create)
# Act & Assert
with pytest.raises(DuplicateKeyError) as exc_info:
await in_memory_repository.create_user(sample_user_create)
in_memory_repository.create_user(sample_user_create)
assert "already exists" in str(exc_info.value)
@@ -84,14 +79,13 @@ class TestUserRepositoryCreation:
class TestUserRepositoryFinding:
"""Tests for user finding functionality."""
@pytest.mark.asyncio
async def test_i_can_find_user_by_id(self, in_memory_repository, sample_user_create):
def test_i_can_find_user_by_id(self, in_memory_repository, sample_user_create):
"""Test finding user by valid ID."""
# Arrange
created_user = await in_memory_repository.create_user(sample_user_create)
created_user = in_memory_repository.create_user(sample_user_create)
# Act
found_user = await in_memory_repository.find_user_by_id(str(created_user.id))
found_user = in_memory_repository.find_user_by_id(str(created_user.id))
# Assert
assert found_user is not None
@@ -99,69 +93,63 @@ class TestUserRepositoryFinding:
assert found_user.username == created_user.username
assert found_user.email == created_user.email
@pytest.mark.asyncio
async def test_i_cannot_find_user_by_invalid_id(self, in_memory_repository):
def test_i_cannot_find_user_by_invalid_id(self, in_memory_repository):
"""Test that invalid ObjectId returns None."""
# Act
found_user = await in_memory_repository.find_user_by_id("invalid_id")
found_user = in_memory_repository.find_user_by_id("invalid_id")
# Assert
assert found_user is None
@pytest.mark.asyncio
async def test_i_cannot_find_user_by_nonexistent_id(self, in_memory_repository):
def test_i_cannot_find_user_by_nonexistent_id(self, in_memory_repository):
"""Test that nonexistent but valid ObjectId returns None."""
# Arrange
nonexistent_id = str(ObjectId())
# Act
found_user = await in_memory_repository.find_user_by_id(nonexistent_id)
found_user = in_memory_repository.find_user_by_id(nonexistent_id)
# Assert
assert found_user is None
@pytest.mark.asyncio
async def test_i_can_find_user_by_username(self, in_memory_repository, sample_user_create):
def test_i_can_find_user_by_username(self, in_memory_repository, sample_user_create):
"""Test finding user by username."""
# Arrange
created_user = await in_memory_repository.create_user(sample_user_create)
created_user = in_memory_repository.create_user(sample_user_create)
# Act
found_user = await in_memory_repository.find_user_by_username(sample_user_create.username)
found_user = in_memory_repository.find_user_by_username(sample_user_create.username)
# Assert
assert found_user is not None
assert found_user.username == created_user.username
assert found_user.id == created_user.id
@pytest.mark.asyncio
async def test_i_cannot_find_user_by_nonexistent_username(self, in_memory_repository):
def test_i_cannot_find_user_by_nonexistent_username(self, in_memory_repository):
"""Test that nonexistent username returns None."""
# Act
found_user = await in_memory_repository.find_user_by_username("nonexistent")
found_user = in_memory_repository.find_user_by_username("nonexistent")
# Assert
assert found_user is None
@pytest.mark.asyncio
async def test_i_can_find_user_by_email(self, in_memory_repository, sample_user_create):
def test_i_can_find_user_by_email(self, in_memory_repository, sample_user_create):
"""Test finding user by email."""
# Arrange
created_user = await in_memory_repository.create_user(sample_user_create)
created_user = in_memory_repository.create_user(sample_user_create)
# Act
found_user = await in_memory_repository.find_user_by_email(str(sample_user_create.email))
found_user = in_memory_repository.find_user_by_email(str(sample_user_create.email))
# Assert
assert found_user is not None
assert found_user.email == created_user.email
assert found_user.id == created_user.id
@pytest.mark.asyncio
async def test_i_cannot_find_user_by_nonexistent_email(self, in_memory_repository):
def test_i_cannot_find_user_by_nonexistent_email(self, in_memory_repository):
"""Test that nonexistent email returns None."""
# Act
found_user = await in_memory_repository.find_user_by_email("nonexistent@example.com")
found_user = in_memory_repository.find_user_by_email("nonexistent@example.com")
# Assert
assert found_user is None
@@ -170,15 +158,14 @@ class TestUserRepositoryFinding:
class TestUserRepositoryUpdate:
"""Tests for user update functionality."""
@pytest.mark.asyncio
async def test_i_can_update_user(self, in_memory_repository, sample_user_create, sample_user_update):
def test_i_can_update_user(self, in_memory_repository, sample_user_create, sample_user_update):
"""Test successful user update."""
# Arrange
created_user = await in_memory_repository.create_user(sample_user_create)
created_user = in_memory_repository.create_user(sample_user_create)
original_updated_at = created_user.updated_at
# Act
updated_user = await in_memory_repository.update_user(str(created_user.id), sample_user_update)
updated_user = in_memory_repository.update_user(str(created_user.id), sample_user_update)
# Assert
assert updated_user is not None
@@ -187,24 +174,22 @@ class TestUserRepositoryUpdate:
assert updated_user.role == sample_user_update.role
assert updated_user.id == created_user.id
@pytest.mark.asyncio
async def test_i_cannot_update_user_with_invalid_id(self, in_memory_repository, sample_user_update):
def test_i_cannot_update_user_with_invalid_id(self, in_memory_repository, sample_user_update):
"""Test that updating with invalid ID returns None."""
# Act
result = await in_memory_repository.update_user("invalid_id", sample_user_update)
result = in_memory_repository.update_user("invalid_id", sample_user_update)
# Assert
assert result is None
@pytest.mark.asyncio
async def test_i_can_update_user_with_partial_data(self, in_memory_repository, sample_user_create):
def test_i_can_update_user_with_partial_data(self, in_memory_repository, sample_user_create):
"""Test updating user with partial data."""
# Arrange
created_user = await in_memory_repository.create_user(sample_user_create)
created_user = in_memory_repository.create_user(sample_user_create)
partial_update = UserUpdate(username="newusername")
# Act
updated_user = await in_memory_repository.update_user(str(created_user.id), partial_update)
updated_user = in_memory_repository.update_user(str(created_user.id), partial_update)
# Assert
assert updated_user is not None
@@ -212,15 +197,14 @@ class TestUserRepositoryUpdate:
assert updated_user.email == created_user.email # Should remain unchanged
assert updated_user.role == created_user.role # Should remain unchanged
@pytest.mark.asyncio
async def test_i_can_update_user_with_empty_data(self, in_memory_repository, sample_user_create):
def test_i_can_update_user_with_empty_data(self, in_memory_repository, sample_user_create):
"""Test updating user with empty data returns current user."""
# Arrange
created_user = await in_memory_repository.create_user(sample_user_create)
created_user = in_memory_repository.create_user(sample_user_create)
empty_update = UserUpdate()
# Act
result = await in_memory_repository.update_user(str(created_user.id), empty_update)
result = in_memory_repository.update_user(str(created_user.id), empty_update)
# Assert
assert result is not None
@@ -231,39 +215,36 @@ class TestUserRepositoryUpdate:
class TestUserRepositoryDeletion:
"""Tests for user deletion functionality."""
@pytest.mark.asyncio
async def test_i_can_delete_user(self, in_memory_repository, sample_user_create):
def test_i_can_delete_user(self, in_memory_repository, sample_user_create):
"""Test successful user deletion."""
# Arrange
created_user = await in_memory_repository.create_user(sample_user_create)
created_user = in_memory_repository.create_user(sample_user_create)
# Act
deletion_result = await in_memory_repository.delete_user(str(created_user.id))
deletion_result = in_memory_repository.delete_user(str(created_user.id))
# Assert
assert deletion_result is True
# Verify user is actually deleted
found_user = await in_memory_repository.find_user_by_id(str(created_user.id))
found_user = in_memory_repository.find_user_by_id(str(created_user.id))
assert found_user is None
@pytest.mark.asyncio
async def test_i_cannot_delete_user_with_invalid_id(self, in_memory_repository):
def test_i_cannot_delete_user_with_invalid_id(self, in_memory_repository):
"""Test that deleting with invalid ID returns False."""
# Act
result = await in_memory_repository.delete_user("invalid_id")
result = in_memory_repository.delete_user("invalid_id")
# Assert
assert result is False
@pytest.mark.asyncio
async def test_i_cannot_delete_nonexistent_user(self, in_memory_repository):
def test_i_cannot_delete_nonexistent_user(self, in_memory_repository):
"""Test that deleting nonexistent user returns False."""
# Arrange
nonexistent_id = str(ObjectId())
# Act
result = await in_memory_repository.delete_user(nonexistent_id)
result = in_memory_repository.delete_user(nonexistent_id)
# Assert
assert result is False
@@ -272,30 +253,27 @@ class TestUserRepositoryDeletion:
class TestUserRepositoryUtilities:
"""Tests for utility methods."""
@pytest.mark.asyncio
async def test_i_can_count_users(self, in_memory_repository, sample_user_create):
def test_i_can_count_users(self, in_memory_repository, sample_user_create):
"""Test counting users."""
# Arrange
initial_count = await in_memory_repository.count_users()
await in_memory_repository.create_user(sample_user_create)
initial_count = in_memory_repository.count_users()
in_memory_repository.create_user(sample_user_create)
# Act
final_count = await in_memory_repository.count_users()
final_count = in_memory_repository.count_users()
# Assert
assert final_count == initial_count + 1
@pytest.mark.asyncio
async def test_i_can_check_user_exists(self, in_memory_repository, sample_user_create):
def test_i_can_check_user_exists(self, in_memory_repository, sample_user_create):
"""Test checking if user exists."""
# Arrange
await in_memory_repository.create_user(sample_user_create)
in_memory_repository.create_user(sample_user_create)
# Act
exists = await in_memory_repository.user_exists(sample_user_create.username)
not_exists = await in_memory_repository.user_exists("nonexistent")
exists = in_memory_repository.user_exists(sample_user_create.username)
not_exists = in_memory_repository.user_exists("nonexistent")
# Assert
assert exists is True
assert not_exists is False

View File

@@ -11,7 +11,7 @@ from unittest.mock import patch
import pytest
import pytest_asyncio
from bson import ObjectId
from mongomock_motor import AsyncMongoMockClient
from mongomock.mongo_client import MongoClient
from app.models.document import FileType
from app.services.document_service import DocumentService
@@ -24,15 +24,15 @@ def cleanup_test_folder():
shutil.rmtree("test_folder", ignore_errors=True)
@pytest_asyncio.fixture
async def in_memory_database():
@pytest.fixture
def in_memory_database():
"""Create an in-memory database for testing."""
client = AsyncMongoMockClient()
client = MongoClient()
return client.test_database
@pytest_asyncio.fixture
async def document_service(in_memory_database):
def document_service(in_memory_database):
"""Create DocumentService with in-memory repositories."""
service = DocumentService(in_memory_database, objects_folder="test_folder")
return service
@@ -72,8 +72,7 @@ class TestCreateDocument:
@patch('app.services.document_service.magic.from_buffer')
@patch('app.services.document_service.datetime')
@pytest.mark.asyncio
async def test_i_can_create_document_with_new_content(
def test_i_can_create_document_with_new_content(
self,
mock_datetime,
mock_magic,
@@ -87,7 +86,7 @@ class TestCreateDocument:
mock_magic.return_value = "application/pdf"
# Execute
result = await document_service.create_document(
result = document_service.create_document(
"/test/test.pdf",
sample_file_bytes,
"utf-8"
@@ -102,7 +101,7 @@ class TestCreateDocument:
assert result.file_hash == document_service._calculate_file_hash(sample_file_bytes)
# Verify document created in database
doc_in_db = await document_service.document_repository.find_document_by_id(result.id)
doc_in_db = document_service.document_repository.find_document_by_id(result.id)
assert doc_in_db is not None
assert doc_in_db.id == result.id
assert doc_in_db.filename == result.filename
@@ -116,8 +115,7 @@ class TestCreateDocument:
@patch('app.services.document_service.magic.from_buffer')
@patch('app.services.document_service.datetime')
@pytest.mark.asyncio
async def test_i_can_create_document_with_existing_content(
def test_i_can_create_document_with_existing_content(
self,
mock_datetime,
mock_magic,
@@ -131,14 +129,14 @@ class TestCreateDocument:
mock_magic.return_value = "application/pdf"
# Create first document
first_doc = await document_service.create_document(
first_doc = document_service.create_document(
"/test/first.pdf",
sample_file_bytes,
"utf-8"
)
# Create second document with same content
second_doc = await document_service.create_document(
second_doc = document_service.create_document(
"/test/second.pdf",
sample_file_bytes,
"utf-8"
@@ -149,37 +147,34 @@ class TestCreateDocument:
assert first_doc.filename != second_doc.filename
assert first_doc.filepath != second_doc.filepath
@pytest.mark.asyncio
async def test_i_cannot_create_document_with_unsupported_file_type(
def test_i_cannot_create_document_with_unsupported_file_type(
self,
document_service,
sample_file_bytes
):
"""Test that unsupported file types raise ValueError."""
with pytest.raises(ValueError, match="Unsupported file type"):
await document_service.create_document(
document_service.create_document(
"/test/test.xyz", # Unsupported extension
sample_file_bytes,
"utf-8"
)
@pytest.mark.asyncio
async def test_i_cannot_create_document_with_empty_file_path(
def test_i_cannot_create_document_with_empty_file_path(
self,
document_service,
sample_file_bytes
):
"""Test that empty file path raises ValueError."""
with pytest.raises(ValueError):
await document_service.create_document(
document_service.create_document(
"", # Empty path
sample_file_bytes,
"utf-8"
)
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_create_document_with_empty_bytes(
def test_i_can_create_document_with_empty_bytes(
self,
mock_magic,
document_service
@@ -189,7 +184,7 @@ class TestCreateDocument:
mock_magic.return_value = "text/plain"
# Execute with empty bytes
result = await document_service.create_document(
result = document_service.create_document(
"/test/empty.txt",
b"", # Empty bytes
"utf-8"
@@ -203,8 +198,7 @@ class TestGetMethods:
"""Tests for document retrieval methods."""
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_get_document_by_id(
def test_i_can_get_document_by_id(
self,
mock_magic,
document_service,
@@ -215,14 +209,14 @@ class TestGetMethods:
mock_magic.return_value = "application/pdf"
# Create a document first
created_doc = await document_service.create_document(
created_doc = document_service.create_document(
"/test/test.pdf",
sample_file_bytes,
"utf-8"
)
# Execute
result = await document_service.get_document_by_id(created_doc.id)
result = document_service.get_document_by_id(created_doc.id)
# Verify
assert result is not None
@@ -230,8 +224,7 @@ class TestGetMethods:
assert result.filename == created_doc.filename
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_get_document_by_hash(
def test_i_can_get_document_by_hash(
self,
mock_magic,
document_service,
@@ -242,14 +235,14 @@ class TestGetMethods:
mock_magic.return_value = "application/pdf"
# Create a document first
created_doc = await document_service.create_document(
created_doc = document_service.create_document(
"/test/test.pdf",
sample_file_bytes,
"utf-8"
)
# Execute
result = await document_service.get_document_by_hash(created_doc.file_hash)
result = document_service.get_document_by_hash(created_doc.file_hash)
# Verify
assert result is not None
@@ -257,8 +250,7 @@ class TestGetMethods:
assert result.filename == created_doc.filename
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_get_document_by_filepath(
def test_i_can_get_document_by_filepath(
self,
mock_magic,
document_service,
@@ -270,14 +262,14 @@ class TestGetMethods:
test_path = "/test/unique_test.pdf"
# Create a document first
created_doc = await document_service.create_document(
created_doc = document_service.create_document(
test_path,
sample_file_bytes,
"utf-8"
)
# Execute
result = await document_service.get_document_by_filepath(test_path)
result = document_service.get_document_by_filepath(test_path)
# Verify
assert result is not None
@@ -285,8 +277,7 @@ class TestGetMethods:
assert result.id == created_doc.id
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_get_document_content(
def test_i_can_get_document_content(
self,
mock_magic,
document_service,
@@ -297,38 +288,36 @@ class TestGetMethods:
mock_magic.return_value = "application/pdf"
# Create a document first
created_doc = await document_service.create_document(
created_doc = document_service.create_document(
"/test/test.pdf",
sample_file_bytes,
"utf-8"
)
# Execute
result = await document_service.get_document_content_by_hash(created_doc.file_hash)
result = document_service.get_document_content_by_hash(created_doc.file_hash)
# Verify
assert result == sample_file_bytes
@pytest.mark.asyncio
async def test_i_cannot_get_nonexistent_document_by_id(
def test_i_cannot_get_nonexistent_document_by_id(
self,
document_service
):
"""Test that nonexistent document returns None."""
# Execute with random ObjectId
result = await document_service.get_document_by_id(ObjectId())
result = document_service.get_document_by_id(ObjectId())
# Verify
assert result is None
@pytest.mark.asyncio
async def test_i_cannot_get_nonexistent_document_by_hash(
def test_i_cannot_get_nonexistent_document_by_hash(
self,
document_service
):
"""Test that nonexistent document hash returns None."""
# Execute
result = await document_service.get_document_by_hash("nonexistent_hash")
result = document_service.get_document_by_hash("nonexistent_hash")
# Verify
assert result is None
@@ -338,8 +327,7 @@ class TestPaginationAndCounting:
"""Tests for document listing and counting."""
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_list_documents_with_pagination(
def test_i_can_list_documents_with_pagination(
self,
mock_magic,
document_service,
@@ -351,25 +339,24 @@ class TestPaginationAndCounting:
# Create multiple documents
for i in range(5):
await document_service.create_document(
document_service.create_document(
f"/test/test{i}.pdf",
sample_file_bytes + bytes(str(i), 'utf-8'), # Make each file unique
"utf-8"
)
# Execute with pagination
result = await document_service.list_documents(skip=1, limit=2)
result = document_service.list_documents(skip=1, limit=2)
# Verify
assert len(result) == 2
# Test counting
total_count = await document_service.count_documents()
total_count = document_service.count_documents()
assert total_count == 5
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_count_documents(
def test_i_can_count_documents(
self,
mock_magic,
document_service,
@@ -380,19 +367,19 @@ class TestPaginationAndCounting:
mock_magic.return_value = "text/plain"
# Initially should be 0
initial_count = await document_service.count_documents()
initial_count = document_service.count_documents()
assert initial_count == 0
# Create some documents
for i in range(3):
await document_service.create_document(
document_service.create_document(
f"/test/test{i}.txt",
sample_file_bytes + bytes(str(i), 'utf-8'),
"utf-8"
)
# Execute
final_count = await document_service.count_documents()
final_count = document_service.count_documents()
# Verify
assert final_count == 3
@@ -402,8 +389,7 @@ class TestUpdateAndDelete:
"""Tests for document update and deletion operations."""
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_update_document_metadata(
def test_i_can_update_document_metadata(
self,
mock_magic,
document_service,
@@ -414,7 +400,7 @@ class TestUpdateAndDelete:
mock_magic.return_value = "application/pdf"
# Create a document first
created_doc = await document_service.create_document(
created_doc = document_service.create_document(
"/test/test.pdf",
sample_file_bytes,
"utf-8"
@@ -422,7 +408,7 @@ class TestUpdateAndDelete:
# Execute update
update_data = {"metadata": {"page_count": 5}}
result = await document_service.update_document(created_doc.id, update_data)
result = document_service.update_document(created_doc.id, update_data)
# Verify
assert result is not None
@@ -433,14 +419,13 @@ class TestUpdateAndDelete:
assert result.file_type == created_doc.file_type
assert result.metadata == update_data['metadata']
@pytest.mark.asyncio
async def test_i_can_update_document_content(
def test_i_can_update_document_content(
self,
document_service,
sample_file_bytes
):
# Create a document first
created_doc = await document_service.create_document(
created_doc = document_service.create_document(
"/test/test.pdf",
sample_file_bytes,
"utf-8"
@@ -448,7 +433,7 @@ class TestUpdateAndDelete:
# Execute update
update_data = {"file_bytes": b"this is an updated file content"}
result = await document_service.update_document(created_doc.id, update_data)
result = document_service.update_document(created_doc.id, update_data)
assert result.filename == created_doc.filename
assert result.filepath == created_doc.filepath
@@ -460,8 +445,7 @@ class TestUpdateAndDelete:
validate_file_saved(document_service, result.file_hash, b"this is an updated file content")
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_delete_document_and_orphaned_content(
def test_i_can_delete_document_and_orphaned_content(
self,
mock_magic,
document_service,
@@ -472,7 +456,7 @@ class TestUpdateAndDelete:
mock_magic.return_value = "application/pdf"
# Create a document
created_doc = await document_service.create_document(
created_doc = document_service.create_document(
"/test/test.pdf",
sample_file_bytes,
"utf-8"
@@ -482,12 +466,12 @@ class TestUpdateAndDelete:
validate_file_saved(document_service, created_doc.file_hash, sample_file_bytes)
# Execute deletion
result = await document_service.delete_document(created_doc.id)
result = document_service.delete_document(created_doc.id)
# Verify document and content are deleted
assert result is True
deleted_doc = await document_service.get_document_by_id(created_doc.id)
deleted_doc = document_service.get_document_by_id(created_doc.id)
assert deleted_doc is None
# validate content is deleted
@@ -496,8 +480,7 @@ class TestUpdateAndDelete:
assert not os.path.exists(target_file_path)
@patch('app.services.document_service.magic.from_buffer')
@pytest.mark.asyncio
async def test_i_can_delete_document_without_affecting_shared_content(
def test_i_can_delete_document_without_affecting_shared_content(
self,
mock_magic,
document_service,
@@ -508,13 +491,13 @@ class TestUpdateAndDelete:
mock_magic.return_value = "application/pdf"
# Create two documents with same content
doc1 = await document_service.create_document(
doc1 = document_service.create_document(
"/test/test1.pdf",
sample_file_bytes,
"utf-8"
)
doc2 = await document_service.create_document(
doc2 = document_service.create_document(
"/test/test2.pdf",
sample_file_bytes,
"utf-8"
@@ -524,14 +507,14 @@ class TestUpdateAndDelete:
assert doc1.file_hash == doc2.file_hash
# Delete first document
result = await document_service.delete_document(doc1.id)
result = document_service.delete_document(doc1.id)
assert result is True
# Verify first document is deleted but content still exists
deleted_doc = await document_service.get_document_by_id(doc1.id)
deleted_doc = document_service.get_document_by_id(doc1.id)
assert deleted_doc is None
remaining_doc = await document_service.get_document_by_id(doc2.id)
remaining_doc = document_service.get_document_by_id(doc2.id)
assert remaining_doc is not None
validate_file_saved(document_service, doc2.file_hash, sample_file_bytes)

View File

@@ -6,9 +6,8 @@ using mongomock for better integration testing.
"""
import pytest
import pytest_asyncio
from bson import ObjectId
from mongomock_motor import AsyncMongoMockClient
from mongomock.mongo_client import MongoClient
from app.exceptions.job_exceptions import InvalidStatusTransitionError
from app.models.job import ProcessingStatus
@@ -16,17 +15,17 @@ from app.models.types import PyObjectId
from app.services.job_service import JobService
@pytest_asyncio.fixture
async def in_memory_database():
@pytest.fixture
def in_memory_database():
"""Create an in-memory database for testing."""
client = AsyncMongoMockClient()
client = MongoClient()
return client.test_database
@pytest_asyncio.fixture
async def job_service(in_memory_database):
@pytest.fixture
def job_service(in_memory_database):
"""Create JobService with in-memory repositories."""
service = await JobService(in_memory_database).initialize()
service = JobService(in_memory_database).initialize()
return service
@@ -45,8 +44,7 @@ def sample_task_id():
class TestCreateJob:
"""Tests for create_job method."""
@pytest.mark.asyncio
async def test_i_can_create_job_with_task_id(
def test_i_can_create_job_with_task_id(
self,
job_service,
sample_document_id,
@@ -54,7 +52,7 @@ class TestCreateJob:
):
"""Test creating job with task ID."""
# Execute
result = await job_service.create_job(sample_document_id, sample_task_id)
result = job_service.create_job(sample_document_id, sample_task_id)
# Verify job creation
assert result is not None
@@ -66,22 +64,21 @@ class TestCreateJob:
assert result.error_message is None
# Verify job exists in database
job_in_db = await job_service.get_job_by_id(result.id)
job_in_db = job_service.get_job_by_id(result.id)
assert job_in_db is not None
assert job_in_db.id == result.id
assert job_in_db.document_id == sample_document_id
assert job_in_db.task_id == sample_task_id
assert job_in_db.status == ProcessingStatus.PENDING
@pytest.mark.asyncio
async def test_i_can_create_job_without_task_id(
def test_i_can_create_job_without_task_id(
self,
job_service,
sample_document_id
):
"""Test creating job without task ID."""
# Execute
result = await job_service.create_job(sample_document_id)
result = job_service.create_job(sample_document_id)
# Verify job creation
assert result is not None
@@ -96,8 +93,7 @@ class TestCreateJob:
class TestGetJobMethods:
"""Tests for job retrieval methods."""
@pytest.mark.asyncio
async def test_i_can_get_job_by_id(
def test_i_can_get_job_by_id(
self,
job_service,
sample_document_id,
@@ -105,10 +101,10 @@ class TestGetJobMethods:
):
"""Test retrieving job by ID."""
# Create a job first
created_job = await job_service.create_job(sample_document_id, sample_task_id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
# Execute
result = await job_service.get_job_by_id(created_job.id)
result = job_service.get_job_by_id(created_job.id)
# Verify
assert result is not None
@@ -117,25 +113,24 @@ class TestGetJobMethods:
assert result.task_id == created_job.task_id
assert result.status == created_job.status
@pytest.mark.asyncio
async def test_i_can_get_jobs_by_status(
def test_i_can_get_jobs_by_status(
self,
job_service,
sample_document_id
):
"""Test retrieving jobs by status."""
# Create jobs with different statuses
pending_job = await job_service.create_job(sample_document_id, "pending-task")
pending_job = job_service.create_job(sample_document_id, "pending-task")
processing_job = await job_service.create_job(ObjectId(), "processing-task")
await job_service.mark_job_as_started(processing_job.id)
processing_job = job_service.create_job(ObjectId(), "processing-task")
job_service.mark_job_as_started(processing_job.id)
completed_job = await job_service.create_job(ObjectId(), "completed-task")
await job_service.mark_job_as_started(completed_job.id)
await job_service.mark_job_as_completed(completed_job.id)
completed_job = job_service.create_job(ObjectId(), "completed-task")
job_service.mark_job_as_started(completed_job.id)
job_service.mark_job_as_completed(completed_job.id)
# Execute - get pending jobs
pending_results = await job_service.get_jobs_by_status(ProcessingStatus.PENDING)
pending_results = job_service.get_jobs_by_status(ProcessingStatus.PENDING)
# Verify
assert len(pending_results) == 1
@@ -143,12 +138,12 @@ class TestGetJobMethods:
assert pending_results[0].status == ProcessingStatus.PENDING
# Execute - get processing jobs
processing_results = await job_service.get_jobs_by_status(ProcessingStatus.PROCESSING)
processing_results = job_service.get_jobs_by_status(ProcessingStatus.PROCESSING)
assert len(processing_results) == 1
assert processing_results[0].status == ProcessingStatus.PROCESSING
# Execute - get completed jobs
completed_results = await job_service.get_jobs_by_status(ProcessingStatus.COMPLETED)
completed_results = job_service.get_jobs_by_status(ProcessingStatus.COMPLETED)
assert len(completed_results) == 1
assert completed_results[0].status == ProcessingStatus.COMPLETED
@@ -156,8 +151,7 @@ class TestGetJobMethods:
class TestUpdateStatus:
"""Tests for mark_job_as_started method."""
@pytest.mark.asyncio
async def test_i_can_mark_pending_job_as_started(
def test_i_can_mark_pending_job_as_started(
self,
job_service,
sample_document_id,
@@ -165,11 +159,11 @@ class TestUpdateStatus:
):
"""Test marking pending job as started (PENDING → PROCESSING)."""
# Create a pending job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
assert created_job.status == ProcessingStatus.PENDING
# Execute
result = await job_service.mark_job_as_started(created_job.id)
result = job_service.mark_job_as_started(created_job.id)
# Verify status transition
assert result is not None
@@ -177,11 +171,10 @@ class TestUpdateStatus:
assert result.status == ProcessingStatus.PROCESSING
# Verify in database
updated_job = await job_service.get_job_by_id(created_job.id)
updated_job = job_service.get_job_by_id(created_job.id)
assert updated_job.status == ProcessingStatus.PROCESSING
@pytest.mark.asyncio
async def test_i_cannot_mark_processing_job_as_started(
def test_i_cannot_mark_processing_job_as_started(
self,
job_service,
sample_document_id,
@@ -189,19 +182,18 @@ class TestUpdateStatus:
):
"""Test that processing job cannot be marked as started."""
# Create and start a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
await job_service.mark_job_as_started(created_job.id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
job_service.mark_job_as_started(created_job.id)
# Try to start it again
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_started(created_job.id)
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.PROCESSING
assert exc_info.value.target_status == ProcessingStatus.PROCESSING
@pytest.mark.asyncio
async def test_i_cannot_mark_completed_job_as_started(
def test_i_cannot_mark_completed_job_as_started(
self,
job_service,
sample_document_id,
@@ -209,20 +201,19 @@ class TestUpdateStatus:
):
"""Test that completed job cannot be marked as started."""
# Create, start, and complete a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
await job_service.mark_job_as_started(created_job.id)
await job_service.mark_job_as_completed(created_job.id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_completed(created_job.id)
# Try to start it again
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_started(created_job.id)
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.COMPLETED
assert exc_info.value.target_status == ProcessingStatus.PROCESSING
@pytest.mark.asyncio
async def test_i_cannot_mark_failed_job_as_started(
def test_i_cannot_mark_failed_job_as_started(
self,
job_service,
sample_document_id,
@@ -230,20 +221,19 @@ class TestUpdateStatus:
):
"""Test that failed job cannot be marked as started."""
# Create, start, and fail a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
await job_service.mark_job_as_started(created_job.id)
await job_service.mark_job_as_failed(created_job.id, "Test error")
created_job = job_service.create_job(sample_document_id, sample_task_id)
job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_failed(created_job.id, "Test error")
# Try to start it again
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_started(created_job.id)
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.FAILED
assert exc_info.value.target_status == ProcessingStatus.PROCESSING
@pytest.mark.asyncio
async def test_i_can_mark_processing_job_as_completed(
def test_i_can_mark_processing_job_as_completed(
self,
job_service,
sample_document_id,
@@ -251,11 +241,11 @@ class TestUpdateStatus:
):
"""Test marking processing job as completed (PROCESSING → COMPLETED)."""
# Create and start a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
started_job = await job_service.mark_job_as_started(created_job.id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
started_job = job_service.mark_job_as_started(created_job.id)
# Execute
result = await job_service.mark_job_as_completed(created_job.id)
result = job_service.mark_job_as_completed(created_job.id)
# Verify status transition
assert result is not None
@@ -263,11 +253,10 @@ class TestUpdateStatus:
assert result.status == ProcessingStatus.COMPLETED
# Verify in database
updated_job = await job_service.get_job_by_id(created_job.id)
updated_job = job_service.get_job_by_id(created_job.id)
assert updated_job.status == ProcessingStatus.COMPLETED
@pytest.mark.asyncio
async def test_i_cannot_mark_pending_job_as_completed(
def test_i_cannot_mark_pending_job_as_completed(
self,
job_service,
sample_document_id,
@@ -275,18 +264,17 @@ class TestUpdateStatus:
):
"""Test that pending job cannot be marked as completed."""
# Create a pending job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
# Try to complete it directly
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_completed(created_job.id)
job_service.mark_job_as_completed(created_job.id)
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.PENDING
assert exc_info.value.target_status == ProcessingStatus.COMPLETED
@pytest.mark.asyncio
async def test_i_cannot_mark_completed_job_as_completed(
def test_i_cannot_mark_completed_job_as_completed(
self,
job_service,
sample_document_id,
@@ -294,20 +282,19 @@ class TestUpdateStatus:
):
"""Test that completed job cannot be marked as completed again."""
# Create, start, and complete a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
await job_service.mark_job_as_started(created_job.id)
await job_service.mark_job_as_completed(created_job.id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_completed(created_job.id)
# Try to complete it again
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_completed(created_job.id)
job_service.mark_job_as_completed(created_job.id)
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.COMPLETED
assert exc_info.value.target_status == ProcessingStatus.COMPLETED
@pytest.mark.asyncio
async def test_i_cannot_mark_failed_job_as_completed(
def test_i_cannot_mark_failed_job_as_completed(
self,
job_service,
sample_document_id,
@@ -315,20 +302,19 @@ class TestUpdateStatus:
):
"""Test that failed job cannot be marked as completed."""
# Create, start, and fail a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
await job_service.mark_job_as_started(created_job.id)
await job_service.mark_job_as_failed(created_job.id, "Test error")
created_job = job_service.create_job(sample_document_id, sample_task_id)
job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_failed(created_job.id, "Test error")
# Try to complete it
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_completed(created_job.id)
job_service.mark_job_as_completed(created_job.id)
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.FAILED
assert exc_info.value.target_status == ProcessingStatus.COMPLETED
@pytest.mark.asyncio
async def test_i_can_mark_processing_job_as_failed_with_error_message(
def test_i_can_mark_processing_job_as_failed_with_error_message(
self,
job_service,
sample_document_id,
@@ -336,13 +322,13 @@ class TestUpdateStatus:
):
"""Test marking processing job as failed with error message."""
# Create and start a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
started_job = await job_service.mark_job_as_started(created_job.id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
started_job = job_service.mark_job_as_started(created_job.id)
error_message = "Processing failed due to invalid file format"
# Execute
result = await job_service.mark_job_as_failed(created_job.id, error_message)
result = job_service.mark_job_as_failed(created_job.id, error_message)
# Verify status transition
assert result is not None
@@ -351,12 +337,11 @@ class TestUpdateStatus:
assert result.error_message == error_message
# Verify in database
updated_job = await job_service.get_job_by_id(created_job.id)
updated_job = job_service.get_job_by_id(created_job.id)
assert updated_job.status == ProcessingStatus.FAILED
assert updated_job.error_message == error_message
@pytest.mark.asyncio
async def test_i_can_mark_processing_job_as_failed_without_error_message(
def test_i_can_mark_processing_job_as_failed_without_error_message(
self,
job_service,
sample_document_id,
@@ -364,19 +349,18 @@ class TestUpdateStatus:
):
"""Test marking processing job as failed without error message."""
# Create and start a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
await job_service.mark_job_as_started(created_job.id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
job_service.mark_job_as_started(created_job.id)
# Execute without error message
result = await job_service.mark_job_as_failed(created_job.id)
result = job_service.mark_job_as_failed(created_job.id)
# Verify status transition
assert result is not None
assert result.status == ProcessingStatus.FAILED
assert result.error_message is None
@pytest.mark.asyncio
async def test_i_cannot_mark_pending_job_as_failed(
def test_i_cannot_mark_pending_job_as_failed(
self,
job_service,
sample_document_id,
@@ -384,18 +368,17 @@ class TestUpdateStatus:
):
"""Test that pending job cannot be marked as failed."""
# Create a pending job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
# Try to fail it directly
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_failed(created_job.id, "Test error")
job_service.mark_job_as_failed(created_job.id, "Test error")
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.PENDING
assert exc_info.value.target_status == ProcessingStatus.FAILED
@pytest.mark.asyncio
async def test_i_cannot_mark_completed_job_as_failed(
def test_i_cannot_mark_completed_job_as_failed(
self,
job_service,
sample_document_id,
@@ -403,20 +386,19 @@ class TestUpdateStatus:
):
"""Test that completed job cannot be marked as failed."""
# Create, start, and complete a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
await job_service.mark_job_as_started(created_job.id)
await job_service.mark_job_as_completed(created_job.id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_completed(created_job.id)
# Try to fail it
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_failed(created_job.id, "Test error")
job_service.mark_job_as_failed(created_job.id, "Test error")
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.COMPLETED
assert exc_info.value.target_status == ProcessingStatus.FAILED
@pytest.mark.asyncio
async def test_i_cannot_mark_failed_job_as_failed(
def test_i_cannot_mark_failed_job_as_failed(
self,
job_service,
sample_document_id,
@@ -424,13 +406,13 @@ class TestUpdateStatus:
):
"""Test that failed job cannot be marked as failed again."""
# Create, start, and fail a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
await job_service.mark_job_as_started(created_job.id)
await job_service.mark_job_as_failed(created_job.id, "First error")
created_job = job_service.create_job(sample_document_id, sample_task_id)
job_service.mark_job_as_started(created_job.id)
job_service.mark_job_as_failed(created_job.id, "First error")
# Try to fail it again
with pytest.raises(InvalidStatusTransitionError) as exc_info:
await job_service.mark_job_as_failed(created_job.id, "Second error")
job_service.mark_job_as_failed(created_job.id, "Second error")
# Verify exception details
assert exc_info.value.current_status == ProcessingStatus.FAILED
@@ -440,8 +422,7 @@ class TestUpdateStatus:
class TestDeleteJob:
"""Tests for delete_job method."""
@pytest.mark.asyncio
async def test_i_can_delete_existing_job(
def test_i_can_delete_existing_job(
self,
job_service,
sample_document_id,
@@ -449,30 +430,29 @@ class TestDeleteJob:
):
"""Test deleting an existing job."""
# Create a job
created_job = await job_service.create_job(sample_document_id, sample_task_id)
created_job = job_service.create_job(sample_document_id, sample_task_id)
# Verify job exists
job_before_delete = await job_service.get_job_by_id(created_job.id)
job_before_delete = job_service.get_job_by_id(created_job.id)
assert job_before_delete is not None
# Execute deletion
result = await job_service.delete_job(created_job.id)
result = job_service.delete_job(created_job.id)
# Verify deletion
assert result is True
# Verify job no longer exists
deleted_job = await job_service.get_job_by_id(created_job.id)
deleted_job = job_service.get_job_by_id(created_job.id)
assert deleted_job is None
@pytest.mark.asyncio
async def test_i_cannot_delete_nonexistent_job(
def test_i_cannot_delete_nonexistent_job(
self,
job_service
):
"""Test deleting a nonexistent job returns False."""
# Execute deletion with random ObjectId
result = await job_service.delete_job(ObjectId())
result = job_service.delete_job(ObjectId())
# Verify
assert result is False
@@ -481,8 +461,7 @@ class TestDeleteJob:
class TestStatusTransitionValidation:
"""Tests for status transition validation across different scenarios."""
@pytest.mark.asyncio
async def test_valid_job_lifecycle_flow(
def test_valid_job_lifecycle_flow(
self,
job_service,
sample_document_id,
@@ -490,19 +469,18 @@ class TestStatusTransitionValidation:
):
"""Test complete valid job lifecycle: PENDING → PROCESSING → COMPLETED."""
# Create job (PENDING)
job = await job_service.create_job(sample_document_id, sample_task_id)
job = job_service.create_job(sample_document_id, sample_task_id)
assert job.status == ProcessingStatus.PENDING
# Start job (PENDING → PROCESSING)
started_job = await job_service.mark_job_as_started(job.id)
started_job = job_service.mark_job_as_started(job.id)
assert started_job.status == ProcessingStatus.PROCESSING
# Complete job (PROCESSING → COMPLETED)
completed_job = await job_service.mark_job_as_completed(job.id)
completed_job = job_service.mark_job_as_completed(job.id)
assert completed_job.status == ProcessingStatus.COMPLETED
@pytest.mark.asyncio
async def test_valid_job_failure_flow(
def test_valid_job_failure_flow(
self,
job_service,
sample_document_id,
@@ -510,69 +488,31 @@ class TestStatusTransitionValidation:
):
"""Test valid job failure: PENDING → PROCESSING → FAILED."""
# Create job (PENDING)
job = await job_service.create_job(sample_document_id, sample_task_id)
job = job_service.create_job(sample_document_id, sample_task_id)
assert job.status == ProcessingStatus.PENDING
# Start job (PENDING → PROCESSING)
started_job = await job_service.mark_job_as_started(job.id)
started_job = job_service.mark_job_as_started(job.id)
assert started_job.status == ProcessingStatus.PROCESSING
# Fail job (PROCESSING → FAILED)
failed_job = await job_service.mark_job_as_failed(job.id, "Test failure")
failed_job = job_service.mark_job_as_failed(job.id, "Test failure")
assert failed_job.status == ProcessingStatus.FAILED
assert failed_job.error_message == "Test failure"
class TestEdgeCases:
"""Tests for edge cases and error conditions."""
#
# @pytest.mark.asyncio
# async def test_multiple_jobs_for_same_file(
# self,
# job_service,
# sample_document_id
# ):
# """Test handling multiple jobs for the same file."""
# # Create multiple jobs for same file
# job1 = await job_service.create_job(sample_document_id, "task-1")
# job2 = await job_service.create_job(sample_document_id, "task-2")
# job3 = await job_service.create_job(sample_document_id, "task-3")
#
# # Verify all jobs exist and are independent
# jobs_for_file = await job_service.get_jobs_by_file_id(sample_document_id)
# assert len(jobs_for_file) == 3
#
# job_ids = [job.id for job in jobs_for_file]
# assert job1.id in job_ids
# assert job2.id in job_ids
# assert job3.id in job_ids
#
# # Verify status transitions work independently
# await job_service.mark_job_as_started(job1.id)
# await job_service.mark_job_as_completed(job1.id)
#
# # Other jobs should still be pending
# updated_job2 = await job_service.get_job_by_id(job2.id)
# updated_job3 = await job_service.get_job_by_id(job3.id)
#
# assert updated_job2.status == ProcessingStatus.PENDING
# assert updated_job3.status == ProcessingStatus.PENDING
@pytest.mark.asyncio
async def test_job_operations_with_empty_database(
def test_job_operations_with_empty_database(
self,
job_service
):
"""Test job operations when database is empty."""
# Try to get nonexistent job
result = await job_service.get_job_by_id(ObjectId())
result = job_service.get_job_by_id(ObjectId())
assert result is None
# Try to get jobs by status when none exist
pending_jobs = await job_service.get_jobs_by_status(ProcessingStatus.PENDING)
pending_jobs = job_service.get_jobs_by_status(ProcessingStatus.PENDING)
assert pending_jobs == []
# Try to delete nonexistent job
delete_result = await job_service.delete_job(ObjectId())
delete_result = job_service.delete_job(ObjectId())
assert delete_result is False