"""Tests for todo parser."""
import json
from pathlib import Path
import pytest
from ccsession.parsers.todos import TodoParser, TodoItem, TodoList
def test_todo_item_from_dict():
"""Test creating TodoItem from dictionary."""
data = {
"content": "Implement feature",
"status": "in_progress",
"activeForm": "Implementing feature",
}
item = TodoItem.from_dict(data)
assert item.content == "Implement feature"
assert item.status == "in_progress"
assert item.active_form == "Implementing feature"
def test_todo_item_from_dict_missing_fields():
"""Test TodoItem with missing fields uses defaults."""
data = {"content": "Test task"}
item = TodoItem.from_dict(data)
assert item.content == "Test task"
assert item.status == "pending"
assert item.active_form == ""
def test_todo_list_properties():
"""Test TodoList properties."""
todos = TodoList(
pending=[TodoItem("Task 1", "pending", "Doing task 1")],
in_progress=[TodoItem("Task 2", "in_progress", "Doing task 2")],
completed=[
TodoItem("Task 3", "completed", "Doing task 3"),
TodoItem("Task 4", "completed", "Doing task 4"),
],
)
assert todos.total_count == 4
assert len(todos.all_items) == 4
assert todos.all_items[0].content == "Task 1"
assert todos.all_items[-1].content == "Task 4"
def test_todo_list_to_dict():
"""Test TodoList serialization to dict."""
todos = TodoList(
pending=[TodoItem("Task 1", "pending", "Doing task 1")],
in_progress=[],
completed=[TodoItem("Task 2", "completed", "Doing task 2")],
)
result = todos.to_dict()
assert len(result["pending"]) == 1
assert result["pending"][0]["content"] == "Task 1"
assert len(result["in_progress"]) == 0
assert len(result["completed"]) == 1
def test_todo_parser_parse_file(sample_todos_path):
"""Test parsing a todo file."""
todos_path, _ = sample_todos_path
parser = TodoParser()
items = parser.parse_file(todos_path)
assert len(items) == 4
assert items[0].content == "Implement authentication feature"
assert items[0].status == "completed"
assert items[2].status == "in_progress"
assert items[3].status == "pending"
def test_todo_parser_parse_nonexistent_file(tmp_path):
"""Test parsing nonexistent file returns empty list."""
parser = TodoParser()
items = parser.parse_file(tmp_path / "nonexistent.json")
assert items == []
def test_todo_parser_parse_invalid_json(tmp_path):
"""Test parsing invalid JSON returns empty list."""
bad_file = tmp_path / "bad.json"
bad_file.write_text("not valid json")
parser = TodoParser()
items = parser.parse_file(bad_file)
assert items == []
def test_todo_parser_parse_non_list_json(tmp_path):
"""Test parsing non-list JSON returns empty list."""
bad_file = tmp_path / "object.json"
bad_file.write_text('{"not": "a list"}')
parser = TodoParser()
items = parser.parse_file(bad_file)
assert items == []
def test_todo_parser_get_todos_for_session(sample_todos_path):
"""Test getting todos for a specific session."""
todos_path, session_id = sample_todos_path
todos_dir = todos_path.parent
parser = TodoParser(todos_dir=todos_dir)
todos = parser.get_todos_for_session(session_id)
assert todos.total_count == 4
assert len(todos.completed) == 2
assert len(todos.in_progress) == 1
assert len(todos.pending) == 1
def test_todo_parser_get_todos_for_session_with_agent_files(tmp_path):
"""Test getting todos across main session and agent spawns."""
todos_dir = tmp_path / "todos"
todos_dir.mkdir()
session_id = "73cc9f9a-1234-5678-9abc-def012345678"
# Main session file
main_file = todos_dir / f"{session_id}.json"
main_file.write_text(
json.dumps(
[
{"content": "Main task 1", "status": "completed", "activeForm": "Doing main task 1"},
{"content": "Main task 2", "status": "pending", "activeForm": "Doing main task 2"},
]
)
)
# Agent spawn file
agent_file = todos_dir / f"{session_id}-agent-explore-01.json"
agent_file.write_text(
json.dumps(
[
{"content": "Agent task 1", "status": "completed", "activeForm": "Doing agent task 1"},
]
)
)
parser = TodoParser(todos_dir=todos_dir)
todos = parser.get_todos_for_session(session_id)
assert todos.total_count == 3
assert len(todos.completed) == 2
assert len(todos.pending) == 1
def test_todo_parser_get_latest_todos(tmp_path):
"""Test getting todos from most recent file."""
todos_dir = tmp_path / "todos"
todos_dir.mkdir()
# Create two session files
old_file = todos_dir / "old-session.json"
old_file.write_text(
json.dumps(
[{"content": "Old task", "status": "completed", "activeForm": "Doing old task"}]
)
)
import time
time.sleep(0.01) # Ensure different mtime
new_file = todos_dir / "new-session.json"
new_file.write_text(
json.dumps(
[{"content": "New task", "status": "in_progress", "activeForm": "Doing new task"}]
)
)
parser = TodoParser(todos_dir=todos_dir)
todos = parser.get_latest_todos()
assert todos.total_count == 1
assert todos.in_progress[0].content == "New task"
def test_todo_parser_get_latest_todos_empty_dir(tmp_path):
"""Test getting latest todos from empty directory."""
todos_dir = tmp_path / "todos"
todos_dir.mkdir()
parser = TodoParser(todos_dir=todos_dir)
todos = parser.get_latest_todos()
assert todos.total_count == 0
def test_todo_parser_extract_session_id_from_transcript():
"""Test extracting session ID from transcript path."""
parser = TodoParser()
path = "/tmp/claude-code-transcripts/73cc9f9a-1234-5678-9abc-def012345678.jsonl"
session_id = parser.extract_session_id_from_transcript_path(path)
assert session_id == "73cc9f9a-1234-5678-9abc-def012345678"
def test_todo_parser_extract_session_id_invalid_format():
"""Test extracting session ID from invalid path."""
parser = TodoParser()
path = "/tmp/not-a-valid-uuid.jsonl"
session_id = parser.extract_session_id_from_transcript_path(path)
assert session_id is None
def test_todo_parser_get_todo_files_for_session(tmp_path):
"""Test getting all todo files for a session."""
todos_dir = tmp_path / "todos"
todos_dir.mkdir()
session_id = "test-session-id"
# Create multiple files for session
(todos_dir / f"{session_id}.json").touch()
(todos_dir / f"{session_id}-agent-1.json").touch()
(todos_dir / f"{session_id}-agent-2.json").touch()
(todos_dir / "other-session.json").touch()
parser = TodoParser(todos_dir=todos_dir)
files = parser.get_todo_files_for_session(session_id)
assert len(files) == 3
assert all(session_id in f.name for f in files)