* test: add comprehensive unit tests for domain module Add 24 comprehensive unit tests covering the open_notebook.domain module: **ObjectModel Base (5 tests)** - Create and update operations with timestamps - Get by ID with class resolution - Delete validation - Relationship creation **RecordModel Singleton (3 tests)** - Singleton pattern behavior - Async database loading - Update persistence **ModelManager (3 tests)** - Singleton pattern - Model instance caching - Default model retrieval **Notebook Domain (3 tests)** - Name validation (empty/whitespace) - Source relationship queries - Archived flag defaults **Source Domain (3 tests)** - Text vectorization and chunking - Insight validation and creation - RecordID command field parsing **Note Domain (2 tests)** - Content validation - Embedding configuration **Podcast Domain (2 tests)** - Speaker profile validation - Episode profile segment validation **Additional Tests (3 tests)** - ChatSession relationships - Transformation creation - ContentSettings defaults All tests use proper mocking to avoid database dependencies and validate both business logic and error handling. Tests follow pytest best practices with async support, fixtures, and comprehensive assertions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: add comprehensive tests for utils and graphs modules Add 56 new unit tests covering utils and graphs modules: **Utils Module Tests (36 tests)** Text Utilities (13 tests): - Text splitting with various chunk sizes - ASCII and non-printable character removal - Thinking tag parsing and cleaning (single/multiple tags) - Edge cases (empty strings, invalid input, large content) Token Utilities (4 tests): - Token counting with tiktoken - Cost calculation - Fallback behavior when tiktoken unavailable Version Utilities (7 tests): - Semantic version comparison (equal, less, greater, prerelease) - Installed package version retrieval - GitHub version fetching with URL validation Context Builder (12 tests): - ContextItem and ContextConfig creation - Builder initialization with various parameters - Priority sorting and deduplication - Token-based truncation - Response formatting - Source and notebook context building - Convenience functions **Graphs Module Tests (20 tests)** Model Provisioning (4 tests): - Default model selection - Large context model triggering (>105k tokens) - Specific model ID selection - Kwargs pass-through Tools (3 tests): - Current timestamp format validation - Timestamp validity checking - Tool decoration verification Prompt Graph (5 tests): - PatternChainState structure - Model calling with/without parser - Graph compilation and execution Transformation Graph (8 tests): - TransformationState structure - Transformation with source objects - Transformation with direct input text - Thinking content cleaning - Content validation - Graph compilation and execution - Default prompt integration All tests use proper mocking to avoid external dependencies (network, database) and validate both success paths and error handling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * improve tests --------- Co-authored-by: Claude <noreply@anthropic.com>
155 lines
5.1 KiB
Python
155 lines
5.1 KiB
Python
"""
|
|
Unit tests for the open_notebook.graphs module.
|
|
|
|
This test suite focuses on testing graph structures, tools, and validation
|
|
without heavy mocking of the actual processing logic.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
|
|
from open_notebook.graphs.prompt import PatternChainState, graph
|
|
from open_notebook.graphs.tools import get_current_timestamp
|
|
from open_notebook.graphs.transformation import (
|
|
TransformationState,
|
|
run_transformation,
|
|
graph as transformation_graph,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# TEST SUITE 1: Graph Tools
|
|
# ============================================================================
|
|
|
|
|
|
class TestGraphTools:
|
|
"""Test suite for graph tool definitions."""
|
|
|
|
def test_get_current_timestamp_format(self):
|
|
"""Test timestamp tool returns correct format."""
|
|
timestamp = get_current_timestamp.func()
|
|
|
|
assert isinstance(timestamp, str)
|
|
assert len(timestamp) == 14 # YYYYMMDDHHmmss format
|
|
assert timestamp.isdigit()
|
|
|
|
def test_get_current_timestamp_validity(self):
|
|
"""Test timestamp represents valid datetime."""
|
|
timestamp = get_current_timestamp.func()
|
|
|
|
# Parse it back to datetime to verify validity
|
|
year = int(timestamp[0:4])
|
|
month = int(timestamp[4:6])
|
|
day = int(timestamp[6:8])
|
|
hour = int(timestamp[8:10])
|
|
minute = int(timestamp[10:12])
|
|
second = int(timestamp[12:14])
|
|
|
|
# Should be valid date components
|
|
assert 2020 <= year <= 2100
|
|
assert 1 <= month <= 12
|
|
assert 1 <= day <= 31
|
|
assert 0 <= hour <= 23
|
|
assert 0 <= minute <= 59
|
|
assert 0 <= second <= 59
|
|
|
|
# Should parse as datetime
|
|
dt = datetime.strptime(timestamp, "%Y%m%d%H%M%S")
|
|
assert isinstance(dt, datetime)
|
|
|
|
def test_get_current_timestamp_is_tool(self):
|
|
"""Test that function is properly decorated as a tool."""
|
|
# Check it has tool attributes
|
|
assert hasattr(get_current_timestamp, "name")
|
|
assert hasattr(get_current_timestamp, "description")
|
|
|
|
|
|
# ============================================================================
|
|
# TEST SUITE 2: Prompt Graph State
|
|
# ============================================================================
|
|
|
|
|
|
class TestPromptGraph:
|
|
"""Test suite for prompt pattern chain graph."""
|
|
|
|
def test_pattern_chain_state_structure(self):
|
|
"""Test PatternChainState structure and fields."""
|
|
state = PatternChainState(
|
|
prompt="Test prompt",
|
|
parser=None,
|
|
input_text="Test input",
|
|
output=""
|
|
)
|
|
|
|
assert state["prompt"] == "Test prompt"
|
|
assert state["parser"] is None
|
|
assert state["input_text"] == "Test input"
|
|
assert state["output"] == ""
|
|
|
|
def test_prompt_graph_compilation(self):
|
|
"""Test that prompt graph compiles correctly."""
|
|
assert graph is not None
|
|
|
|
# Graph should have the expected structure
|
|
assert hasattr(graph, "invoke")
|
|
assert hasattr(graph, "ainvoke")
|
|
|
|
|
|
# ============================================================================
|
|
# TEST SUITE 3: Transformation Graph
|
|
# ============================================================================
|
|
|
|
|
|
class TestTransformationGraph:
|
|
"""Test suite for transformation graph workflows."""
|
|
|
|
def test_transformation_state_structure(self):
|
|
"""Test TransformationState structure and fields."""
|
|
from unittest.mock import MagicMock
|
|
from open_notebook.domain.notebook import Source
|
|
from open_notebook.domain.transformation import Transformation
|
|
|
|
mock_source = MagicMock(spec=Source)
|
|
mock_transformation = MagicMock(spec=Transformation)
|
|
|
|
state = TransformationState(
|
|
input_text="Test text",
|
|
source=mock_source,
|
|
transformation=mock_transformation,
|
|
output=""
|
|
)
|
|
|
|
assert state["input_text"] == "Test text"
|
|
assert state["source"] == mock_source
|
|
assert state["transformation"] == mock_transformation
|
|
assert state["output"] == ""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_transformation_assertion_no_content(self):
|
|
"""Test transformation raises assertion with no content."""
|
|
from unittest.mock import MagicMock
|
|
from open_notebook.domain.transformation import Transformation
|
|
|
|
mock_transformation = MagicMock(spec=Transformation)
|
|
|
|
state = {
|
|
"input_text": None,
|
|
"transformation": mock_transformation,
|
|
"source": None
|
|
}
|
|
|
|
config = {"configurable": {"model_id": None}}
|
|
|
|
with pytest.raises(AssertionError, match="No content to transform"):
|
|
await run_transformation(state, config)
|
|
|
|
def test_transformation_graph_compilation(self):
|
|
"""Test that transformation graph compiles correctly."""
|
|
assert transformation_graph is not None
|
|
assert hasattr(transformation_graph, "invoke")
|
|
assert hasattr(transformation_graph, "ainvoke")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|