"""
Tests for the notes exporter
"""
import pytest
import json
from unittest.mock import patch, MagicMock
from datetime import datetime
from src.exporters.notes_exporter import (
MarkdownExporter,
JSONExporter,
NotesManager
)
@pytest.mark.unit
class TestMarkdownExporter:
"""Test the MarkdownExporter class"""
def test_extract_date_from_title(self):
"""Test extracting date from task title"""
exporter = MarkdownExporter()
# Valid date at start
date = exporter.extract_date_from_title("2025-12-31 Meeting")
assert date == "December 31, 2025"
# Valid date with whitespace
date = exporter.extract_date_from_title(" 2025-01-15 Task")
assert date == "January 15, 2025"
# No date
date = exporter.extract_date_from_title("No date here")
assert date is None
# Date in middle
date = exporter.extract_date_from_title("Meeting 2025-12-31 notes")
assert date is None
def test_format_date(self):
"""Test formatting ISO date string"""
exporter = MarkdownExporter()
# Valid ISO date
formatted = exporter.format_date("2025-12-31T10:30:00.000+0000")
assert "December 31, 2025" in formatted or "2025" in formatted
# Empty date
formatted = exporter.format_date("")
assert formatted == "No date"
# None date
formatted = exporter.format_date(None)
assert formatted == "No date"
def test_export_markdown(self, tmp_path, sample_tasks):
"""Test exporting tasks to markdown"""
exporter = MarkdownExporter()
output_file = tmp_path / "test_export.md"
metadata = {
'tag_name': 'test',
'project_name': 'Test Project',
'total_tasks': len(sample_tasks)
}
exporter.export(sample_tasks, str(output_file), metadata)
# Verify file was created
assert output_file.exists()
# Verify content
content = output_file.read_text()
assert "# Test Project" in content or "test" in content.lower()
assert "Total notes:" in content or str(len(sample_tasks)) in content
# Verify task titles are present
for task in sample_tasks:
assert task['title'] in content
def test_export_markdown_empty_tasks(self, tmp_path):
"""Test exporting with no tasks"""
exporter = MarkdownExporter()
output_file = tmp_path / "empty.md"
metadata = {'tag_name': 'empty', 'total_tasks': 0}
exporter.export([], str(output_file), metadata)
assert output_file.exists()
content = output_file.read_text()
assert "Total notes: 0" in content
@pytest.mark.unit
class TestJSONExporter:
"""Test the JSONExporter class"""
def test_export_json(self, tmp_path, sample_tasks):
"""Test exporting tasks to JSON"""
exporter = JSONExporter()
output_file = tmp_path / "test_export.json"
metadata = {
'tag_name': 'test',
'project_name': 'Test Project',
'total_tasks': len(sample_tasks)
}
exporter.export(sample_tasks, str(output_file), metadata)
# Verify file was created
assert output_file.exists()
# Verify valid JSON
with open(output_file) as f:
data = json.load(f)
assert 'metadata' in data
assert 'tasks' in data
assert data['metadata']['total_tasks'] == len(sample_tasks)
assert len(data['tasks']) == len(sample_tasks)
def test_export_json_structure(self, tmp_path, sample_tasks):
"""Test JSON export structure"""
exporter = JSONExporter()
output_file = tmp_path / "test.json"
metadata = {'tag_name': 'test', 'total_tasks': len(sample_tasks)}
exporter.export(sample_tasks, str(output_file), metadata)
with open(output_file) as f:
data = json.load(f)
# Check metadata
assert data['metadata']['tag_name'] == 'test'
assert 'exported_at' in data['metadata']
# Check tasks
assert data['tasks'][0]['title'] == sample_tasks[0]['title']
@pytest.mark.unit
class TestNotesManager:
"""Test the NotesManager class"""
def test_manager_initialization(self, mock_env_token):
"""Test manager initialization"""
with patch('src.exporters.notes_exporter.TickTickAPI'):
manager = NotesManager()
assert manager.access_token == 'test_token_123'
assert manager.api is not None
def test_manager_initialization_no_token(self, monkeypatch):
"""Test manager initialization without token"""
monkeypatch.delenv('TICKTICK_ACCESS_TOKEN', raising=False)
manager = NotesManager()
assert manager.access_token is None
def test_authenticate_success(self, mock_env_token, mock_ticktick_api):
"""Test successful authentication"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
result = manager.authenticate()
assert result is True
def test_authenticate_failure(self, mock_env_token):
"""Test authentication failure"""
mock_api = MagicMock()
mock_api.get_user_info.return_value = None
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_api):
manager = NotesManager()
result = manager.authenticate()
assert result is False
def test_get_projects(self, mock_env_token, mock_ticktick_api):
"""Test getting projects"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
projects = manager.get_projects()
assert len(projects) == 3
assert projects[0]['name'] == 'Work Tasks'
def test_find_project(self, mock_env_token, mock_ticktick_api):
"""Test finding a project"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
project = manager.find_project("Work Tasks")
assert project is not None
assert project['id'] == 'proj1'
def test_find_project_case_insensitive(self, mock_env_token, mock_ticktick_api):
"""Test finding project is case insensitive"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
project = manager.find_project("work tasks")
assert project is not None
assert project['id'] == 'proj1'
def test_find_project_not_found(self, mock_env_token, mock_ticktick_api):
"""Test finding non-existent project"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
project = manager.find_project("Nonexistent")
assert project is None
def test_get_tasks_by_tag(self, mock_env_token, mock_ticktick_api):
"""Test getting tasks filtered by tag"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
tasks = manager.get_tasks_by_tag('proj1', 'test')
# Should filter tasks with 'test' tag
assert len(tasks) >= 0
for task in tasks:
assert 'test' in [t.lower() for t in task.get('tags', [])]
def test_get_tasks_by_project(self, mock_env_token, mock_ticktick_api):
"""Test getting all tasks from a project"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
tasks = manager.get_tasks_by_project('proj1')
assert isinstance(tasks, list)
def test_export_by_tag(self, tmp_path, mock_env_token, mock_ticktick_api):
"""Test exporting tasks by tag"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
output_file = tmp_path / "tag_export.md"
manager.export_by_tag(
tag_name='test',
output_file=str(output_file),
project_name='Work Tasks'
)
# File should be created
assert output_file.exists()
def test_export_project(self, tmp_path, mock_env_token, mock_ticktick_api):
"""Test exporting entire project"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
output_file = tmp_path / "project_export.md"
manager.export_project(
project_name='Work Tasks',
output_file=str(output_file)
)
# File should be created
assert output_file.exists()
def test_export_with_json_exporter(self, tmp_path, mock_env_token, mock_ticktick_api):
"""Test exporting with JSON exporter"""
with patch('src.exporters.notes_exporter.TickTickAPI', return_value=mock_ticktick_api):
manager = NotesManager()
output_file = tmp_path / "export.json"
manager.export_by_tag(
tag_name='test',
output_file=str(output_file),
project_name='Work Tasks',
exporter=JSONExporter()
)
assert output_file.exists()
# Verify it's valid JSON
with open(output_file) as f:
data = json.load(f)
assert 'metadata' in data
assert 'tasks' in data