260 lines
9.4 KiB
Python
260 lines
9.4 KiB
Python
"""Tests for ObsidianService markdown parsing and file operations."""
|
|
|
|
import os
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
# Set vault path before importing so __init__ validation passes
|
|
_test_vault_dir = None
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def vault_dir(tmp_path):
|
|
"""Create a temporary vault directory with a sample .md file."""
|
|
global _test_vault_dir
|
|
_test_vault_dir = tmp_path
|
|
|
|
# Create a sample markdown file so vault validation passes
|
|
sample = tmp_path / "sample.md"
|
|
sample.write_text("# Sample\nHello world")
|
|
|
|
with patch.dict(os.environ, {"OBSIDIAN_VAULT_PATH": str(tmp_path)}):
|
|
yield tmp_path
|
|
|
|
|
|
@pytest.fixture
|
|
def service(vault_dir):
|
|
from utils.obsidian_service import ObsidianService
|
|
|
|
return ObsidianService()
|
|
|
|
|
|
class TestParseMarkdown:
|
|
def test_extracts_frontmatter(self, service):
|
|
content = "---\ntitle: Test Note\ntags: [cat, vet]\n---\n\nBody content"
|
|
result = service.parse_markdown(content)
|
|
assert result["metadata"]["title"] == "Test Note"
|
|
assert result["metadata"]["tags"] == ["cat", "vet"]
|
|
|
|
def test_no_frontmatter(self, service):
|
|
content = "Just body content with no frontmatter"
|
|
result = service.parse_markdown(content)
|
|
assert result["metadata"] == {}
|
|
assert "Just body content" in result["content"]
|
|
|
|
def test_invalid_yaml_frontmatter(self, service):
|
|
content = "---\n: invalid: yaml: [[\n---\n\nBody"
|
|
result = service.parse_markdown(content)
|
|
assert result["metadata"] == {}
|
|
|
|
def test_extracts_tags(self, service):
|
|
content = "Some text with #tag1 and #tag2 here"
|
|
result = service.parse_markdown(content)
|
|
assert "tag1" in result["tags"]
|
|
assert "tag2" in result["tags"]
|
|
|
|
def test_extracts_wikilinks(self, service):
|
|
content = "Link to [[Other Note]] and [[Another Page]]"
|
|
result = service.parse_markdown(content)
|
|
assert "Other Note" in result["wikilinks"]
|
|
assert "Another Page" in result["wikilinks"]
|
|
|
|
def test_extracts_embeds(self, service):
|
|
content = "An embed [[!my_embed]] here"
|
|
result = service.parse_markdown(content)
|
|
assert "my_embed" in result["embeds"]
|
|
|
|
def test_cleans_wikilinks_from_content(self, service):
|
|
content = "Text with [[link]] included"
|
|
result = service.parse_markdown(content)
|
|
assert "[[" not in result["content"]
|
|
assert "]]" not in result["content"]
|
|
|
|
def test_filepath_passed_through(self, service):
|
|
result = service.parse_markdown("text", filepath=Path("/vault/note.md"))
|
|
assert result["filepath"] == "/vault/note.md"
|
|
|
|
def test_filepath_none_by_default(self, service):
|
|
result = service.parse_markdown("text")
|
|
assert result["filepath"] is None
|
|
|
|
def test_empty_content(self, service):
|
|
result = service.parse_markdown("")
|
|
assert result["metadata"] == {}
|
|
assert result["tags"] == []
|
|
assert result["wikilinks"] == []
|
|
assert result["embeds"] == []
|
|
|
|
|
|
class TestGetDailyNotePath:
|
|
def test_formats_path_correctly(self, service):
|
|
date = datetime(2026, 3, 15)
|
|
path = service.get_daily_note_path(date)
|
|
assert path == "journal/2026/2026-03-15.md"
|
|
|
|
def test_defaults_to_today(self, service):
|
|
path = service.get_daily_note_path()
|
|
today = datetime.now()
|
|
assert today.strftime("%Y-%m-%d") in path
|
|
assert path.startswith(f"journal/{today.strftime('%Y')}/")
|
|
|
|
|
|
class TestWalkVault:
|
|
def test_finds_markdown_files(self, service, vault_dir):
|
|
(vault_dir / "note1.md").write_text("# Note 1")
|
|
(vault_dir / "subdir").mkdir()
|
|
(vault_dir / "subdir" / "note2.md").write_text("# Note 2")
|
|
|
|
files = service.walk_vault()
|
|
filenames = [f.name for f in files]
|
|
assert "sample.md" in filenames
|
|
assert "note1.md" in filenames
|
|
assert "note2.md" in filenames
|
|
|
|
def test_excludes_obsidian_dir(self, service, vault_dir):
|
|
obsidian_dir = vault_dir / ".obsidian"
|
|
obsidian_dir.mkdir()
|
|
(obsidian_dir / "config.md").write_text("config")
|
|
|
|
files = service.walk_vault()
|
|
filenames = [f.name for f in files]
|
|
assert "config.md" not in filenames
|
|
|
|
def test_ignores_non_md_files(self, service, vault_dir):
|
|
(vault_dir / "image.png").write_bytes(b"\x89PNG")
|
|
|
|
files = service.walk_vault()
|
|
filenames = [f.name for f in files]
|
|
assert "image.png" not in filenames
|
|
|
|
|
|
class TestCreateNote:
|
|
def test_creates_file(self, service, vault_dir):
|
|
path = service.create_note("My Test Note", "Body content")
|
|
full_path = vault_dir / path
|
|
assert full_path.exists()
|
|
|
|
def test_sanitizes_title(self, service, vault_dir):
|
|
path = service.create_note("Hello World! @#$", "Body")
|
|
assert "hello-world" in path
|
|
assert "@" not in path
|
|
assert "#" not in path
|
|
|
|
def test_includes_frontmatter(self, service, vault_dir):
|
|
path = service.create_note("Test", "Body", tags=["cat", "vet"])
|
|
full_path = vault_dir / path
|
|
content = full_path.read_text()
|
|
assert "---" in content
|
|
assert "created_by: simbarag" in content
|
|
assert "cat" in content
|
|
assert "vet" in content
|
|
|
|
def test_custom_folder(self, service, vault_dir):
|
|
path = service.create_note("Test", "Body", folder="custom/subfolder")
|
|
assert path.startswith("custom/subfolder/")
|
|
assert (vault_dir / path).exists()
|
|
|
|
|
|
class TestDailyNoteTasks:
|
|
def test_get_tasks_from_daily_note(self, service, vault_dir):
|
|
# Create a daily note with tasks
|
|
date = datetime(2026, 1, 15)
|
|
rel_path = service.get_daily_note_path(date)
|
|
note_path = vault_dir / rel_path
|
|
note_path.parent.mkdir(parents=True, exist_ok=True)
|
|
note_path.write_text(
|
|
"---\nmodified: 2026-01-15\n---\n"
|
|
"### tasks\n\n"
|
|
"- [ ] Feed the cat\n"
|
|
"- [x] Clean litter box\n"
|
|
"- [ ] Buy cat food\n\n"
|
|
"### log\n"
|
|
)
|
|
|
|
result = service.get_daily_tasks(date)
|
|
assert result["found"] is True
|
|
assert len(result["tasks"]) == 3
|
|
assert result["tasks"][0] == {"text": "Feed the cat", "done": False}
|
|
assert result["tasks"][1] == {"text": "Clean litter box", "done": True}
|
|
assert result["tasks"][2] == {"text": "Buy cat food", "done": False}
|
|
|
|
def test_get_tasks_no_note(self, service):
|
|
date = datetime(2099, 12, 31)
|
|
result = service.get_daily_tasks(date)
|
|
assert result["found"] is False
|
|
assert result["tasks"] == []
|
|
|
|
def test_add_task_creates_note(self, service, vault_dir):
|
|
date = datetime(2026, 6, 1)
|
|
result = service.add_task_to_daily_note("Walk the cat", date)
|
|
assert result["success"] is True
|
|
assert result["created_note"] is True
|
|
|
|
# Verify file was created with the task
|
|
note_path = vault_dir / result["path"]
|
|
content = note_path.read_text()
|
|
assert "- [ ] Walk the cat" in content
|
|
|
|
def test_add_task_to_existing_note(self, service, vault_dir):
|
|
date = datetime(2026, 6, 2)
|
|
rel_path = service.get_daily_note_path(date)
|
|
note_path = vault_dir / rel_path
|
|
note_path.parent.mkdir(parents=True, exist_ok=True)
|
|
note_path.write_text(
|
|
"---\nmodified: 2026-06-02\n---\n"
|
|
"### tasks\n\n"
|
|
"- [ ] Existing task\n\n"
|
|
"### log\n"
|
|
)
|
|
|
|
result = service.add_task_to_daily_note("New task", date)
|
|
assert result["success"] is True
|
|
assert result["created_note"] is False
|
|
|
|
content = note_path.read_text()
|
|
assert "- [ ] Existing task" in content
|
|
assert "- [ ] New task" in content
|
|
|
|
def test_complete_task_exact_match(self, service, vault_dir):
|
|
date = datetime(2026, 6, 3)
|
|
rel_path = service.get_daily_note_path(date)
|
|
note_path = vault_dir / rel_path
|
|
note_path.parent.mkdir(parents=True, exist_ok=True)
|
|
note_path.write_text("### tasks\n\n" "- [ ] Feed the cat\n" "- [ ] Buy food\n")
|
|
|
|
result = service.complete_task_in_daily_note("Feed the cat", date)
|
|
assert result["success"] is True
|
|
|
|
content = note_path.read_text()
|
|
assert "- [x] Feed the cat" in content
|
|
assert "- [ ] Buy food" in content # Other task unchanged
|
|
|
|
def test_complete_task_partial_match(self, service, vault_dir):
|
|
date = datetime(2026, 6, 4)
|
|
rel_path = service.get_daily_note_path(date)
|
|
note_path = vault_dir / rel_path
|
|
note_path.parent.mkdir(parents=True, exist_ok=True)
|
|
note_path.write_text("### tasks\n\n- [ ] Feed the cat at 5pm\n")
|
|
|
|
result = service.complete_task_in_daily_note("Feed the cat", date)
|
|
assert result["success"] is True
|
|
|
|
def test_complete_task_not_found(self, service, vault_dir):
|
|
date = datetime(2026, 6, 5)
|
|
rel_path = service.get_daily_note_path(date)
|
|
note_path = vault_dir / rel_path
|
|
note_path.parent.mkdir(parents=True, exist_ok=True)
|
|
note_path.write_text("### tasks\n\n- [ ] Feed the cat\n")
|
|
|
|
result = service.complete_task_in_daily_note("Walk the dog", date)
|
|
assert result["success"] is False
|
|
assert "not found" in result["error"]
|
|
|
|
def test_complete_task_no_note(self, service):
|
|
date = datetime(2099, 12, 31)
|
|
result = service.complete_task_in_daily_note("Something", date)
|
|
assert result["success"] is False
|