Implement Sprint 1: Notebook Management CRUD
- Add NotebookService with full CRUD operations
- Add POST /api/v1/notebooks (create notebook)
- Add GET /api/v1/notebooks (list with pagination)
- Add GET /api/v1/notebooks/{id} (get by ID)
- Add PATCH /api/v1/notebooks/{id} (partial update)
- Add DELETE /api/v1/notebooks/{id} (delete)
- Add Pydantic models for requests/responses
- Add custom exceptions (ValidationError, NotFoundError, NotebookLMError)
- Add comprehensive unit tests (31 tests, 97% coverage)
- Add API integration tests (26 tests)
- Fix router prefix duplication
- Fix JSON serialization in error responses
BREAKING CHANGE: None
162 lines
4.1 KiB
Python
162 lines
4.1 KiB
Python
"""Tests for core exceptions."""
|
|
|
|
import pytest
|
|
|
|
from notebooklm_agent.core.exceptions import (
|
|
AuthenticationError,
|
|
NotebookLMAgentError,
|
|
NotebookLMError,
|
|
NotFoundError,
|
|
RateLimitError,
|
|
ValidationError,
|
|
WebhookError,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestNotebookLMAgentError:
|
|
"""Test suite for base exception."""
|
|
|
|
def test_default_code(self):
|
|
"""Should have default error code."""
|
|
# Arrange & Act
|
|
error = NotebookLMAgentError("Test message")
|
|
|
|
# Assert
|
|
assert error.code == "AGENT_ERROR"
|
|
assert str(error) == "Test message"
|
|
|
|
def test_custom_code(self):
|
|
"""Should accept custom error code."""
|
|
# Arrange & Act
|
|
error = NotebookLMAgentError("Test message", code="CUSTOM_ERROR")
|
|
|
|
# Assert
|
|
assert error.code == "CUSTOM_ERROR"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestValidationError:
|
|
"""Test suite for ValidationError."""
|
|
|
|
def test_default_details(self):
|
|
"""Should have empty details by default."""
|
|
# Arrange & Act
|
|
error = ValidationError("Validation failed")
|
|
|
|
# Assert
|
|
assert error.code == "VALIDATION_ERROR"
|
|
assert error.details == []
|
|
|
|
def test_with_details(self):
|
|
"""Should store validation details."""
|
|
# Arrange
|
|
details = [{"field": "title", "error": "Required"}]
|
|
|
|
# Act
|
|
error = ValidationError("Validation failed", details=details)
|
|
|
|
# Assert
|
|
assert error.details == details
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestAuthenticationError:
|
|
"""Test suite for AuthenticationError."""
|
|
|
|
def test_default_message(self):
|
|
"""Should have default error message."""
|
|
# Arrange & Act
|
|
error = AuthenticationError()
|
|
|
|
# Assert
|
|
assert error.code == "AUTH_ERROR"
|
|
assert str(error) == "Authentication failed"
|
|
|
|
def test_custom_message(self):
|
|
"""Should accept custom message."""
|
|
# Arrange & Act
|
|
error = AuthenticationError("Custom auth error")
|
|
|
|
# Assert
|
|
assert str(error) == "Custom auth error"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestNotFoundError:
|
|
"""Test suite for NotFoundError."""
|
|
|
|
def test_message_formatting(self):
|
|
"""Should format message with resource info."""
|
|
# Arrange & Act
|
|
error = NotFoundError("Notebook", "abc123")
|
|
|
|
# Assert
|
|
assert error.code == "NOT_FOUND"
|
|
assert error.resource == "Notebook"
|
|
assert error.resource_id == "abc123"
|
|
assert "Notebook" in str(error)
|
|
assert "abc123" in str(error)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestNotebookLMError:
|
|
"""Test suite for NotebookLMError."""
|
|
|
|
def test_stores_original_error(self):
|
|
"""Should store original exception."""
|
|
# Arrange
|
|
original = ValueError("Original error")
|
|
|
|
# Act
|
|
error = NotebookLMError("NotebookLM failed", original_error=original)
|
|
|
|
# Assert
|
|
assert error.code == "NOTEBOOKLM_ERROR"
|
|
assert error.original_error is original
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestRateLimitError:
|
|
"""Test suite for RateLimitError."""
|
|
|
|
def test_default_values(self):
|
|
"""Should have default message and no retry_after."""
|
|
# Arrange & Act
|
|
error = RateLimitError()
|
|
|
|
# Assert
|
|
assert error.code == "RATE_LIMITED"
|
|
assert str(error) == "Rate limit exceeded"
|
|
assert error.retry_after is None
|
|
|
|
def test_with_retry_after(self):
|
|
"""Should store retry_after value."""
|
|
# Arrange & Act
|
|
error = RateLimitError(retry_after=60)
|
|
|
|
# Assert
|
|
assert error.retry_after == 60
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestWebhookError:
|
|
"""Test suite for WebhookError."""
|
|
|
|
def test_without_webhook_id(self):
|
|
"""Should work without webhook_id."""
|
|
# Arrange & Act
|
|
error = WebhookError("Webhook failed")
|
|
|
|
# Assert
|
|
assert error.code == "WEBHOOK_ERROR"
|
|
assert error.webhook_id is None
|
|
|
|
def test_with_webhook_id(self):
|
|
"""Should store webhook_id."""
|
|
# Arrange & Act
|
|
error = WebhookError("Webhook failed", webhook_id="webhook123")
|
|
|
|
# Assert
|
|
assert error.webhook_id == "webhook123"
|