"""Unit tests for ArtifactService. TDD Cycle: RED → GREEN → REFACTOR """ from datetime import datetime from unittest.mock import AsyncMock, MagicMock from uuid import UUID, uuid4 import pytest from notebooklm_agent.core.exceptions import ( NotebookLMError, NotFoundError, ) from notebooklm_agent.services.artifact_service import ArtifactService @pytest.mark.unit class TestArtifactServiceInit: """Test suite for ArtifactService initialization.""" async def test_get_client_returns_existing_client(self): """Should return existing client if already initialized.""" # Arrange mock_client = AsyncMock() service = ArtifactService(client=mock_client) # Act client = await service._get_client() # Assert assert client == mock_client @pytest.mark.unit class TestArtifactServiceGenerateAudio: """Test suite for generate_audio method.""" async def test_generate_audio_returns_generation_response(self): """Should start audio generation and return response.""" # Arrange notebook_id = uuid4() mock_client = AsyncMock() mock_notebook = AsyncMock() mock_result = MagicMock() mock_result.id = str(uuid4()) mock_result.status = "processing" mock_notebook.generate_audio.return_value = mock_result mock_client.notebooks.get.return_value = mock_notebook service = ArtifactService(client=mock_client) # Act result = await service.generate_audio( notebook_id, instructions="Make it engaging", format="deep-dive", length="long", language="en", ) # Assert assert result.status == "processing" assert str(result.artifact_id) == str(mock_result.id) assert result.estimated_time_seconds == 600 assert "Audio generation started" in result.message async def test_generate_audio_notebook_not_found_raises_not_found(self): """Should raise NotFoundError if notebook not found.""" # Arrange notebook_id = uuid4() mock_client = AsyncMock() mock_client.notebooks.get.side_effect = Exception("notebook not found") service = ArtifactService(client=mock_client) # Act & Assert with pytest.raises(NotFoundError) as exc_info: await service.generate_audio(notebook_id) assert str(notebook_id) in str(exc_info.value) @pytest.mark.unit class TestArtifactServiceGenerateVideo: """Test suite for generate_video method.""" async def test_generate_video_returns_generation_response(self): """Should start video generation and return response.""" # Arrange notebook_id = uuid4() mock_client = AsyncMock() mock_notebook = AsyncMock() mock_result = MagicMock() mock_result.id = str(uuid4()) mock_result.status = "processing" mock_notebook.generate_video.return_value = mock_result mock_client.notebooks.get.return_value = mock_notebook service = ArtifactService(client=mock_client) # Act result = await service.generate_video( notebook_id, instructions="Create engaging video", style="whiteboard", language="en", ) # Assert assert result.status == "processing" assert result.estimated_time_seconds == 1800 @pytest.mark.unit class TestArtifactServiceGenerateQuiz: """Test suite for generate_quiz method.""" async def test_generate_quiz_returns_generation_response(self): """Should start quiz generation and return response.""" # Arrange notebook_id = uuid4() mock_client = AsyncMock() mock_notebook = AsyncMock() mock_result = MagicMock() mock_result.id = str(uuid4()) mock_result.status = "processing" mock_notebook.generate_quiz.return_value = mock_result mock_client.notebooks.get.return_value = mock_notebook service = ArtifactService(client=mock_client) # Act result = await service.generate_quiz( notebook_id, difficulty="medium", quantity="standard", ) # Assert assert result.status == "processing" assert result.estimated_time_seconds == 600 @pytest.mark.unit class TestArtifactServiceGenerateMindMap: """Test suite for generate_mind_map method.""" async def test_generate_mind_map_returns_generation_response(self): """Should generate mind map (instant) and return response.""" # Arrange notebook_id = uuid4() mock_client = AsyncMock() mock_notebook = AsyncMock() mock_result = MagicMock() mock_result.id = str(uuid4()) mock_result.status = "completed" mock_notebook.generate_mind_map.return_value = mock_result mock_client.notebooks.get.return_value = mock_notebook service = ArtifactService(client=mock_client) # Act result = await service.generate_mind_map(notebook_id) # Assert assert result.status == "completed" assert result.estimated_time_seconds == 10 @pytest.mark.unit class TestArtifactServiceListArtifacts: """Test suite for list_artifacts method.""" async def test_list_artifacts_returns_list(self): """Should return list of artifacts.""" # Arrange notebook_id = uuid4() mock_client = AsyncMock() mock_notebook = AsyncMock() mock_artifact = MagicMock() mock_artifact.id = str(uuid4()) mock_artifact.type = "audio" mock_artifact.title = "Podcast" mock_artifact.status = "completed" mock_artifact.created_at = datetime.utcnow() mock_artifact.completed_at = datetime.utcnow() mock_artifact.download_url = "https://example.com/download" mock_notebook.artifacts.list.return_value = [mock_artifact] mock_client.notebooks.get.return_value = mock_notebook service = ArtifactService(client=mock_client) # Act result = await service.list_artifacts(notebook_id) # Assert assert len(result) == 1 assert result[0].type == "audio" assert result[0].status == "completed" async def test_list_artifacts_empty_returns_empty_list(self): """Should return empty list if no artifacts.""" # Arrange notebook_id = uuid4() mock_client = AsyncMock() mock_notebook = AsyncMock() mock_notebook.artifacts.list.return_value = [] mock_client.notebooks.get.return_value = mock_notebook service = ArtifactService(client=mock_client) # Act result = await service.list_artifacts(notebook_id) # Assert assert result == [] @pytest.mark.unit class TestArtifactServiceGetStatus: """Test suite for get_status method.""" async def test_get_status_returns_artifact(self): """Should return artifact with status.""" # Arrange notebook_id = uuid4() artifact_id = str(uuid4()) mock_client = AsyncMock() mock_notebook = AsyncMock() mock_artifact = MagicMock() mock_artifact.id = artifact_id mock_artifact.type = "video" mock_artifact.title = "Video" mock_artifact.status = "processing" mock_artifact.created_at = datetime.utcnow() mock_artifact.completed_at = None mock_artifact.download_url = None mock_notebook.artifacts.get.return_value = mock_artifact mock_client.notebooks.get.return_value = mock_notebook service = ArtifactService(client=mock_client) # Act result = await service.get_status(notebook_id, artifact_id) # Assert assert str(result.id) == artifact_id assert result.type == "video" assert result.status == "processing" async def test_get_status_artifact_not_found_raises_not_found(self): """Should raise NotFoundError if artifact not found.""" # Arrange notebook_id = uuid4() artifact_id = str(uuid4()) mock_client = AsyncMock() mock_notebook = AsyncMock() mock_notebook.artifacts.get.side_effect = Exception("artifact not found") mock_client.notebooks.get.return_value = mock_notebook service = ArtifactService(client=mock_client) # Act & Assert with pytest.raises(NotFoundError) as exc_info: await service.get_status(notebook_id, artifact_id) assert artifact_id in str(exc_info.value) @pytest.mark.unit class TestArtifactServiceEstimatedTimes: """Test suite for estimated times.""" def test_estimated_times_defined(self): """Should have estimated times for all artifact types.""" # Arrange service = ArtifactService() # Assert assert service.ESTIMATED_TIMES["audio"] == 600 assert service.ESTIMATED_TIMES["video"] == 1800 assert service.ESTIMATED_TIMES["mind-map"] == 10