diff --git a/tests/integration/repositories/test_document_repository_integration.py b/tests/integration/repositories/test_document_repository_integration.py deleted file mode 100644 index 9a9d170b..00000000 --- a/tests/integration/repositories/test_document_repository_integration.py +++ /dev/null @@ -1,487 +0,0 @@ -""" -Integration tests for document repository with real database. - -Demonstrates: -- Real database integration testing -- Transaction testing -- Performance validation -- Error scenario handling -""" - -import pytest -import sqlite3 -import asyncio -from pathlib import Path -from datetime import datetime, timezone -import tempfile -import shutil - -from tests.fixtures.markdown_samples import MarkdownDocumentBuilder, SAMPLE_COMPLEX_DOCUMENT -from tests.utils.assertions import assert_file_exists, assert_performance_within_bounds - - -class MockDocument: - """Mock document model for testing.""" - - def __init__(self, filename: str, content: str, ast_data: dict = None): - self.filename = filename - self.content = content - self.ast_data = ast_data or {} - self.created_at = datetime.now(timezone.utc) - self.updated_at = datetime.now(timezone.utc) - - -class MockDocumentRepository: - """Mock document repository that simulates real database operations.""" - - def __init__(self, db_path: Path): - self.db_path = db_path - self._init_database() - - def _init_database(self): - """Initialize database schema.""" - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - cursor.execute(""" - CREATE TABLE IF NOT EXISTS documents ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - filename TEXT UNIQUE NOT NULL, - content TEXT NOT NULL, - ast_data TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - ) - """) - - cursor.execute(""" - CREATE INDEX IF NOT EXISTS idx_documents_filename - ON documents(filename) - """) - - conn.commit() - conn.close() - - async def store_document(self, document: MockDocument) -> int: - """Store a document in the database.""" - await asyncio.sleep(0.001) # Simulate async database operation - - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - try: - cursor.execute(""" - INSERT INTO documents (filename, content, ast_data, created_at, updated_at) - VALUES (?, ?, ?, ?, ?) - """, ( - document.filename, - document.content, - str(document.ast_data), - document.created_at.isoformat(), - document.updated_at.isoformat() - )) - - document_id = cursor.lastrowid - conn.commit() - return document_id - - except sqlite3.IntegrityError as e: - conn.rollback() - raise ValueError(f"Document with filename '{document.filename}' already exists") from e - finally: - conn.close() - - async def get_document(self, document_id: int) -> MockDocument: - """Retrieve a document by ID.""" - await asyncio.sleep(0.001) # Simulate async database operation - - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - try: - cursor.execute(""" - SELECT filename, content, ast_data, created_at, updated_at - FROM documents WHERE id = ? - """, (document_id,)) - - row = cursor.fetchone() - if not row: - raise ValueError(f"Document with ID {document_id} not found") - - filename, content, ast_data, created_at, updated_at = row - document = MockDocument(filename, content, eval(ast_data) if ast_data else {}) - document.created_at = datetime.fromisoformat(created_at) - document.updated_at = datetime.fromisoformat(updated_at) - - return document - - finally: - conn.close() - - async def update_document(self, document_id: int, content: str, ast_data: dict) -> None: - """Update document content and AST data.""" - await asyncio.sleep(0.001) # Simulate async database operation - - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - try: - cursor.execute(""" - UPDATE documents - SET content = ?, ast_data = ?, updated_at = ? - WHERE id = ? - """, ( - content, - str(ast_data), - datetime.now(timezone.utc).isoformat(), - document_id - )) - - if cursor.rowcount == 0: - raise ValueError(f"Document with ID {document_id} not found") - - conn.commit() - - finally: - conn.close() - - async def delete_document(self, document_id: int) -> None: - """Delete a document.""" - await asyncio.sleep(0.001) # Simulate async database operation - - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - try: - cursor.execute("DELETE FROM documents WHERE id = ?", (document_id,)) - - if cursor.rowcount == 0: - raise ValueError(f"Document with ID {document_id} not found") - - conn.commit() - - finally: - conn.close() - - async def list_all_documents(self): - """List all documents.""" - await asyncio.sleep(0.001) # Simulate async database operation - - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - try: - cursor.execute(""" - SELECT id, filename, created_at, updated_at - FROM documents ORDER BY created_at DESC - """) - - rows = cursor.fetchall() - return [ - { - "id": row[0], - "filename": row[1], - "created_at": row[2], - "updated_at": row[3] - } - for row in rows - ] - - finally: - conn.close() - - async def search_content(self, search_term: str): - """Search documents by content.""" - await asyncio.sleep(0.005) # Simulate more expensive search operation - - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - try: - cursor.execute(""" - SELECT id, filename, content - FROM documents - WHERE content LIKE ? - ORDER BY filename - """, (f"%{search_term}%",)) - - rows = cursor.fetchall() - return [ - { - "id": row[0], - "filename": row[1], - "content": row[2] - } - for row in rows - ] - - finally: - conn.close() - - def close(self): - """Close repository (cleanup).""" - pass - - -@pytest.fixture -def test_db_path(test_workspace): - """Provide test database path.""" - return test_workspace / "integration_test.db" - - -@pytest.fixture -async def document_repository(test_db_path): - """Provide document repository with real database.""" - repo = MockDocumentRepository(test_db_path) - yield repo - repo.close() - - -@pytest.mark.integration -class TestDocumentRepositoryIntegration: - """Integration tests for document repository with real database.""" - - @pytest.mark.asyncio - async def test_store_and_retrieve_document(self, document_repository, test_db_path): - """Test storing and retrieving a document.""" - # Arrange - assert_file_exists(test_db_path) - - document = MockDocument( - filename="test.md", - content="# Test Document\nThis is a test.", - ast_data={"type": "document", "children": []} - ) - - # Act - document_id = await document_repository.store_document(document) - retrieved = await document_repository.get_document(document_id) - - # Assert - assert isinstance(document_id, int) - assert document_id > 0 - assert retrieved.filename == "test.md" - assert retrieved.content == "# Test Document\nThis is a test." - assert retrieved.ast_data["type"] == "document" - - @pytest.mark.asyncio - async def test_store_duplicate_filename_raises_error(self, document_repository): - """Test that storing duplicate filename raises error.""" - # Arrange - document1 = MockDocument("duplicate.md", "Content 1") - document2 = MockDocument("duplicate.md", "Content 2") - - # Act - await document_repository.store_document(document1) - - # Assert - with pytest.raises(ValueError, match="already exists"): - await document_repository.store_document(document2) - - @pytest.mark.asyncio - async def test_update_document_content(self, document_repository): - """Test updating document content and AST.""" - # Arrange - document = MockDocument("update.md", "Original content") - document_id = await document_repository.store_document(document) - - # Act - new_content = "Updated content" - new_ast = {"type": "document", "updated": True} - await document_repository.update_document(document_id, new_content, new_ast) - - # Verify - updated = await document_repository.get_document(document_id) - assert updated.content == "Updated content" - assert updated.ast_data["updated"] is True - - @pytest.mark.asyncio - async def test_delete_document(self, document_repository): - """Test deleting a document.""" - # Arrange - document = MockDocument("delete.md", "To be deleted") - document_id = await document_repository.store_document(document) - - # Verify document exists - retrieved = await document_repository.get_document(document_id) - assert retrieved.filename == "delete.md" - - # Act - await document_repository.delete_document(document_id) - - # Assert - with pytest.raises(ValueError, match="not found"): - await document_repository.get_document(document_id) - - @pytest.mark.asyncio - async def test_list_all_documents(self, document_repository): - """Test listing all documents.""" - # Arrange - Store multiple documents - documents = [ - MockDocument("doc1.md", "Content 1"), - MockDocument("doc2.md", "Content 2"), - MockDocument("doc3.md", "Content 3") - ] - - for doc in documents: - await document_repository.store_document(doc) - - # Act - all_docs = await document_repository.list_all_documents() - - # Assert - assert len(all_docs) == 3 - filenames = {doc["filename"] for doc in all_docs} - expected_filenames = {"doc1.md", "doc2.md", "doc3.md"} - assert filenames == expected_filenames - - @pytest.mark.asyncio - async def test_search_content(self, document_repository): - """Test content search functionality.""" - # Arrange - documents = [ - MockDocument("api.md", "API documentation for REST endpoints"), - MockDocument("guide.md", "User guide for getting started"), - MockDocument("readme.md", "Project README with API examples") - ] - - for doc in documents: - await document_repository.store_document(doc) - - # Act - api_results = await document_repository.search_content("API") - guide_results = await document_repository.search_content("guide") - - # Assert - assert len(api_results) == 2 # api.md and readme.md - api_filenames = {result["filename"] for result in api_results} - assert api_filenames == {"api.md", "readme.md"} - - assert len(guide_results) == 1 # guide.md only - assert guide_results[0]["filename"] == "guide.md" - - @pytest.mark.asyncio - async def test_bulk_operations_performance(self, document_repository, performance_timer): - """Test performance of bulk operations.""" - # Arrange - documents = [] - for i in range(50): - content = (MarkdownDocumentBuilder() - .with_heading(f"Document {i}") - .with_paragraph(f"Content for document {i}") - .build()) - documents.append(MockDocument(f"bulk_{i}.md", content)) - - # Act - Bulk storage - performance_timer.start() - document_ids = [] - for doc in documents: - doc_id = await document_repository.store_document(doc) - document_ids.append(doc_id) - performance_timer.stop() - - # Assert - assert len(document_ids) == 50 - assert_performance_within_bounds(performance_timer.elapsed, 5.0, "bulk document storage") - - # Act - Bulk retrieval - performance_timer.start() - retrieved_docs = [] - for doc_id in document_ids: - doc = await document_repository.get_document(doc_id) - retrieved_docs.append(doc) - performance_timer.stop() - - # Assert - assert len(retrieved_docs) == 50 - assert_performance_within_bounds(performance_timer.elapsed, 3.0, "bulk document retrieval") - - @pytest.mark.asyncio - async def test_concurrent_operations(self, document_repository): - """Test concurrent database operations.""" - # Arrange - async def store_document(index): - content = f"# Document {index}\nContent for document {index}" - doc = MockDocument(f"concurrent_{index}.md", content) - return await document_repository.store_document(doc) - - # Act - Concurrent storage - tasks = [store_document(i) for i in range(20)] - document_ids = await asyncio.gather(*tasks) - - # Assert - assert len(document_ids) == 20 - assert len(set(document_ids)) == 20 # All IDs should be unique - - # Verify all documents are accessible - all_docs = await document_repository.list_all_documents() - assert len(all_docs) == 20 - - @pytest.mark.asyncio - async def test_transaction_like_behavior(self, document_repository): - """Test error handling doesn't leave database in inconsistent state.""" - # Arrange - Store initial document - doc1 = MockDocument("initial.md", "Initial content") - doc_id = await document_repository.store_document(doc1) - - # Act - Try to update with invalid ID (should fail) - with pytest.raises(ValueError, match="not found"): - await document_repository.update_document(99999, "Invalid update", {}) - - # Assert - Original document should be unchanged - retrieved = await document_repository.get_document(doc_id) - assert retrieved.content == "Initial content" - - @pytest.mark.asyncio - async def test_large_document_handling(self, document_repository, performance_timer): - """Test handling of large documents.""" - # Arrange - Create large document content - from tests.fixtures.markdown_samples import LargeMarkdownGenerator - generator = LargeMarkdownGenerator(seed=42) - large_content = generator.generate_document(size="100kb") - - document = MockDocument("large.md", large_content) - - # Act - performance_timer.start() - document_id = await document_repository.store_document(document) - retrieved = await document_repository.get_document(document_id) - performance_timer.stop() - - # Assert - assert document_id > 0 - assert len(retrieved.content) > 100000 # At least 100KB - assert retrieved.content == large_content - assert_performance_within_bounds(performance_timer.elapsed, 1.0, "large document operations") - - @pytest.mark.asyncio - @pytest.mark.slow - async def test_search_performance_with_large_dataset(self, document_repository, performance_timer): - """Test search performance with large dataset.""" - # Arrange - Create many documents with searchable content - search_terms = ["API", "database", "testing", "performance", "integration"] - - documents = [] - for i in range(100): - term = search_terms[i % len(search_terms)] - content = (MarkdownDocumentBuilder() - .with_heading(f"Document {i}") - .with_paragraph(f"This document covers {term} functionality in detail.") - .with_paragraph("Additional content for search testing.") - .build()) - documents.append(MockDocument(f"search_{i}.md", content)) - - # Store all documents - for doc in documents: - await document_repository.store_document(doc) - - # Act - Perform searches - performance_timer.start() - api_results = await document_repository.search_content("API") - database_results = await document_repository.search_content("database") - performance_timer.stop() - - # Assert - assert len(api_results) >= 20 # Should find multiple documents - assert len(database_results) >= 20 - assert_performance_within_bounds(performance_timer.elapsed, 2.0, "search operations") \ No newline at end of file diff --git a/tests/unit/application/test_issue_application_service.py b/tests/unit/application/test_issue_application_service.py deleted file mode 100644 index 50ac1d5e..00000000 --- a/tests/unit/application/test_issue_application_service.py +++ /dev/null @@ -1,410 +0,0 @@ -""" -Unit tests for issue application service with enhanced testing patterns. - -Demonstrates: -- Mock-based testing with proper isolation -- Error handling scenarios -- Business logic validation -- Performance expectations -""" - -import pytest -from unittest.mock import AsyncMock, Mock -from datetime import datetime, timezone, timedelta - -from domain.issues.models import Issue, Label, IssueState -from domain.issues.exceptions import IssueNotFoundError, IssueValidationError -from tests.utils.test_builders import IssueBuilder, LabelBuilder -from tests.utils.mock_factories import MockRepositoryFactory -from tests.utils.assertions import assert_issue_equal, assert_performance_within_bounds - - -class MockIssueApplicationService: - """Mock application service for testing (simulating the future implementation).""" - - def __init__(self, issue_repository, project_repository, status_service, validation_service): - self.issue_repository = issue_repository - self.project_repository = project_repository - self.status_service = status_service - self.validation_service = validation_service - - async def get_issue_details(self, issue_number: int): - """Get detailed issue information with business logic applied.""" - # Repository call - issue = await self.issue_repository.get_issue(issue_number) - if not issue: - raise IssueNotFoundError(f"Issue {issue_number} not found") - - # Project context - project_info = await self.project_repository.get_issue_project_info(issue_number) - - # Business logic application - kanban_column = self.status_service.determine_kanban_column(issue, project_info) - priority_info = self.status_service.extract_priority_info(issue) - - return { - "issue": issue, - "kanban_column": kanban_column, - "priority_info": priority_info, - "project_context": project_info - } - - async def create_issue(self, title: str, labels: list = None, milestone_id: int = None): - """Create a new issue with validation.""" - # Validate input - self.validation_service.validate_issue_creation({ - "title": title, - "labels": labels or [] - }) - - # Create issue - issue_data = { - "title": title, - "state": IssueState.OPEN, - "labels": [Label(name) for name in (labels or [])], - "created_at": datetime.now(timezone.utc), - "updated_at": datetime.now(timezone.utc) - } - - return await self.issue_repository.create_issue(issue_data) - - async def update_issue_status(self, issue_number: int, new_status: str): - """Update issue status with business rules.""" - issue = await self.issue_repository.get_issue(issue_number) - if not issue: - raise IssueNotFoundError(f"Issue {issue_number} not found") - - # Apply status change business logic - if new_status == "closed": - issue.close() - elif new_status == "reopened" and issue.state == IssueState.CLOSED: - issue.reopen() - - return await self.issue_repository.update_issue(issue) - - -@pytest.fixture -def mock_issue_repository(): - """Provide mock issue repository.""" - return MockRepositoryFactory.create_issue_repository() - - -@pytest.fixture -def mock_project_repository(): - """Provide mock project repository.""" - return MockRepositoryFactory.create_project_repository() - - -@pytest.fixture -def mock_status_service(): - """Provide mock status service.""" - service = Mock() - service.determine_kanban_column = Mock(return_value="Todo") - service.extract_priority_info = Mock(return_value={"level": "Medium", "label": None}) - return service - - -@pytest.fixture -def mock_validation_service(): - """Provide mock validation service.""" - service = Mock() - service.validate_issue_creation = Mock() - return service - - -@pytest.fixture -def application_service(mock_issue_repository, mock_project_repository, mock_status_service, mock_validation_service): - """Provide application service with mocked dependencies.""" - return MockIssueApplicationService( - mock_issue_repository, - mock_project_repository, - mock_status_service, - mock_validation_service - ) - - -class TestIssueApplicationService: - """Test issue application service coordination logic.""" - - @pytest.mark.asyncio - async def test_get_issue_details_success(self, application_service, mock_issue_repository, mock_project_repository, mock_status_service): - """Test successful issue details retrieval.""" - # Arrange - issue = (IssueBuilder() - .with_number(123) - .with_title("Test Issue") - .with_labels("bug", "priority:high") - .build()) - - project_info = {"kanban_columns": ["Todo", "In Progress", "Done"]} - - mock_issue_repository.get_issue.return_value = issue - mock_project_repository.get_issue_project_info.return_value = project_info - mock_status_service.determine_kanban_column.return_value = "Todo" - mock_status_service.extract_priority_info.return_value = {"level": "High", "label": "priority:high"} - - # Act - result = await application_service.get_issue_details(123) - - # Assert - assert result["issue"] == issue - assert result["kanban_column"] == "Todo" - assert result["priority_info"]["level"] == "High" - assert result["project_context"] == project_info - - # Verify repository calls - mock_issue_repository.get_issue.assert_called_once_with(123) - mock_project_repository.get_issue_project_info.assert_called_once_with(123) - - # Verify business logic calls - mock_status_service.determine_kanban_column.assert_called_once_with(issue, project_info) - mock_status_service.extract_priority_info.assert_called_once_with(issue) - - @pytest.mark.asyncio - async def test_get_issue_details_issue_not_found(self, application_service, mock_issue_repository): - """Test handling of non-existent issue.""" - # Arrange - mock_issue_repository.get_issue.return_value = None - - # Act & Assert - with pytest.raises(IssueNotFoundError, match="Issue 999 not found"): - await application_service.get_issue_details(999) - - mock_issue_repository.get_issue.assert_called_once_with(999) - - @pytest.mark.asyncio - async def test_get_issue_details_repository_error(self, application_service, mock_issue_repository): - """Test handling of repository errors.""" - # Arrange - mock_issue_repository.get_issue.side_effect = Exception("Database connection failed") - - # Act & Assert - with pytest.raises(Exception, match="Database connection failed"): - await application_service.get_issue_details(123) - - @pytest.mark.asyncio - async def test_create_issue_success(self, application_service, mock_issue_repository, mock_validation_service): - """Test successful issue creation.""" - # Arrange - created_issue = (IssueBuilder() - .with_number(456) - .with_title("New Issue") - .with_labels("enhancement") - .build()) - - mock_issue_repository.create_issue.return_value = created_issue - - # Act - result = await application_service.create_issue( - title="New Issue", - labels=["enhancement"] - ) - - # Assert - assert result == created_issue - - # Verify validation was called - mock_validation_service.validate_issue_creation.assert_called_once() - call_args = mock_validation_service.validate_issue_creation.call_args[0][0] - assert call_args["title"] == "New Issue" - assert call_args["labels"] == ["enhancement"] - - # Verify repository call - mock_issue_repository.create_issue.assert_called_once() - - @pytest.mark.asyncio - async def test_create_issue_validation_error(self, application_service, mock_validation_service): - """Test issue creation with validation error.""" - # Arrange - mock_validation_service.validate_issue_creation.side_effect = IssueValidationError("Title cannot be empty") - - # Act & Assert - with pytest.raises(IssueValidationError, match="Title cannot be empty"): - await application_service.create_issue(title="") - - @pytest.mark.asyncio - async def test_update_issue_status_to_closed(self, application_service, mock_issue_repository): - """Test updating issue status to closed.""" - # Arrange - issue = (IssueBuilder() - .with_number(123) - .with_title("Issue to Close") - .build()) - - updated_issue = (IssueBuilder() - .with_number(123) - .with_title("Issue to Close") - .as_closed() - .build()) - - mock_issue_repository.get_issue.return_value = issue - mock_issue_repository.update_issue.return_value = updated_issue - - # Act - result = await application_service.update_issue_status(123, "closed") - - # Assert - assert result.state == IssueState.CLOSED - assert result.closed_at is not None - - mock_issue_repository.get_issue.assert_called_once_with(123) - mock_issue_repository.update_issue.assert_called_once() - - @pytest.mark.asyncio - async def test_update_issue_status_reopen_closed_issue(self, application_service, mock_issue_repository): - """Test reopening a closed issue.""" - # Arrange - closed_issue = (IssueBuilder() - .with_number(123) - .with_title("Closed Issue") - .as_closed() - .build()) - - reopened_issue = (IssueBuilder() - .with_number(123) - .with_title("Closed Issue") - .build()) - - mock_issue_repository.get_issue.return_value = closed_issue - mock_issue_repository.update_issue.return_value = reopened_issue - - # Act - result = await application_service.update_issue_status(123, "reopened") - - # Assert - assert result.state == IssueState.OPEN - assert result.closed_at is None - - @pytest.mark.parametrize("issue_number,title,labels,expected_kanban", [ - (1, "Bug Report", ["bug"], "Todo"), - (2, "In Progress Feature", ["enhancement", "status:in-progress"], "In Progress"), - (3, "Blocked Issue", ["bug", "status:blocked"], "Blocked"), - (4, "Ready for Review", ["enhancement", "status:review"], "Review"), - ]) - @pytest.mark.asyncio - async def test_get_issue_details_kanban_column_determination( - self, application_service, mock_issue_repository, mock_project_repository, mock_status_service, - issue_number, title, labels, expected_kanban - ): - """Test kanban column determination for various issue types.""" - # Arrange - issue = (IssueBuilder() - .with_number(issue_number) - .with_title(title) - .with_labels(*labels) - .build()) - - project_info = {"kanban_columns": ["Todo", "In Progress", "Blocked", "Review", "Done"]} - - mock_issue_repository.get_issue.return_value = issue - mock_project_repository.get_issue_project_info.return_value = project_info - mock_status_service.determine_kanban_column.return_value = expected_kanban - - # Act - result = await application_service.get_issue_details(issue_number) - - # Assert - assert result["kanban_column"] == expected_kanban - - @pytest.mark.asyncio - @pytest.mark.performance - async def test_get_issue_details_performance(self, application_service, mock_issue_repository, mock_project_repository, performance_timer): - """Test that issue details retrieval meets performance requirements.""" - # Arrange - issue = (IssueBuilder() - .with_number(123) - .with_title("Performance Test Issue") - .build()) - - mock_issue_repository.get_issue.return_value = issue - mock_project_repository.get_issue_project_info.return_value = {} - - # Act - performance_timer.start() - result = await application_service.get_issue_details(123) - performance_timer.stop() - - # Assert - assert result is not None - assert_performance_within_bounds(performance_timer.elapsed, 0.1, "issue details retrieval") - - @pytest.mark.asyncio - async def test_create_issue_with_complex_labels(self, application_service, mock_issue_repository, mock_validation_service): - """Test creating issue with complex label combinations.""" - # Arrange - labels = ["bug", "priority:critical", "status:new", "frontend", "needs-investigation"] - created_issue = (IssueBuilder() - .with_number(789) - .with_title("Complex Issue") - .with_labels(*labels) - .build()) - - mock_issue_repository.create_issue.return_value = created_issue - - # Act - result = await application_service.create_issue( - title="Complex Issue", - labels=labels - ) - - # Assert - assert result.number == 789 - assert len(result.labels) == 5 - - # Verify all labels are present - label_names = {label.name for label in result.labels} - expected_labels = set(labels) - assert label_names == expected_labels - - @pytest.mark.asyncio - async def test_concurrent_issue_operations(self, application_service, mock_issue_repository): - """Test concurrent issue operations don't interfere.""" - import asyncio - - # Arrange - issues = [ - (IssueBuilder().with_number(i).with_title(f"Issue {i}").build()) - for i in range(1, 6) - ] - - def get_issue_side_effect(number): - return issues[number - 1] - - mock_issue_repository.get_issue.side_effect = get_issue_side_effect - - # Act - Simulate concurrent requests - tasks = [] - for i in range(1, 6): - task = application_service.get_issue_details(i) - tasks.append(task) - - results = await asyncio.gather(*tasks) - - # Assert - assert len(results) == 5 - for i, result in enumerate(results, 1): - assert result["issue"].number == i - assert result["issue"].title == f"Issue {i}" - - @pytest.mark.asyncio - async def test_error_handling_preserves_state(self, application_service, mock_issue_repository, mock_validation_service): - """Test that errors don't leave the application in inconsistent state.""" - # Arrange - First call succeeds, second fails - success_issue = (IssueBuilder().with_number(1).with_title("Success").build()) - mock_issue_repository.create_issue.side_effect = [success_issue, Exception("Database error")] - - # Act - First call should succeed - result1 = await application_service.create_issue("Success Issue") - assert result1.title == "Success" - - # Second call should fail but not affect future calls - with pytest.raises(Exception, match="Database error"): - await application_service.create_issue("Failing Issue") - - # Third call should work if repository is fixed - mock_issue_repository.create_issue.side_effect = None - success_issue2 = (IssueBuilder().with_number(3).with_title("Recovery").build()) - mock_issue_repository.create_issue.return_value = success_issue2 - - result3 = await application_service.create_issue("Recovery Issue") - assert result3.title == "Recovery" \ No newline at end of file