Add comprehensive test suite for MVP-1 Personal Tracker CLI
Implements 72 tests covering: - Model tests (Project, Session, Note, Change) - ProjectService tests (create, get, list, ensure structure) - SessionService tests (active session management) - FileStorage tests (read/write operations) - Complete flow tests (init -> start -> note -> stop -> show) - Note consolidation tests Uses pytest with tmp_path fixtures for isolated testing.
This commit is contained in:
193
tests/test_storage.py
Normal file
193
tests/test_storage.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""Tests for FileStorage."""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from tracker.models import Session
|
||||
from tracker.storage import FileStorage
|
||||
|
||||
|
||||
class TestReadWriteProjectMeta:
|
||||
"""Tests for read/write project meta operations."""
|
||||
|
||||
def test_write_and_read_project_meta(self, tmp_project_dir):
|
||||
"""Test writing and reading project meta."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
slug = "test-project"
|
||||
|
||||
meta_data = {
|
||||
"id": "test-id",
|
||||
"name": "Test Project",
|
||||
"slug": slug,
|
||||
"description": "A test project",
|
||||
"type": "code",
|
||||
"status": "active",
|
||||
"tags": ["python"],
|
||||
"created_at": datetime(2024, 1, 15, 10, 0, 0).isoformat(),
|
||||
"updated_at": datetime(2024, 1, 15, 10, 0, 0).isoformat(),
|
||||
}
|
||||
|
||||
storage.write_project_meta(slug, meta_data)
|
||||
|
||||
result = storage.read_project_meta(slug)
|
||||
assert result["name"] == "Test Project"
|
||||
assert result["slug"] == slug
|
||||
assert result["type"] == "code"
|
||||
|
||||
def test_read_project_meta_creates_parent_directories(self, tmp_project_dir):
|
||||
"""Test that write_project_meta creates parent directories."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
slug = "new-project"
|
||||
|
||||
meta_data = {"id": "test-id", "name": "Test", "slug": slug}
|
||||
storage.write_project_meta(slug, meta_data)
|
||||
|
||||
meta_path = tmp_project_dir / slug / "meta" / "project.yaml"
|
||||
assert meta_path.exists()
|
||||
|
||||
def test_read_project_meta_raises_for_nonexistent(self, tmp_project_dir):
|
||||
"""Test that reading nonexistent project raises error."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
storage.read_project_meta("nonexistent")
|
||||
|
||||
|
||||
class TestAppendToLog:
|
||||
"""Tests for append_to_log operations."""
|
||||
|
||||
def test_append_to_log_creates_log_file(self, tmp_project_dir):
|
||||
"""Test that append_to_log creates LOG.md if it doesn't exist."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
slug = "test-project"
|
||||
|
||||
# Create project directory first
|
||||
(tmp_project_dir / slug).mkdir(parents=True)
|
||||
|
||||
storage.append_to_log(slug, "# Test Log Entry\n")
|
||||
|
||||
log_path = tmp_project_dir / slug / "LOG.md"
|
||||
assert log_path.exists()
|
||||
|
||||
def test_append_to_log_appends_content(self, tmp_project_dir):
|
||||
"""Test that append_to_log appends content to LOG.md."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
slug = "test-project"
|
||||
|
||||
# Create project directory first
|
||||
(tmp_project_dir / slug).mkdir(parents=True)
|
||||
|
||||
storage.append_to_log(slug, "# First Entry\n")
|
||||
storage.append_to_log(slug, "# Second Entry\n")
|
||||
|
||||
content = storage.read_log(slug)
|
||||
assert "# First Entry" in content
|
||||
assert "# Second Entry" in content
|
||||
|
||||
def test_read_log_returns_empty_string_for_nonexistent(self, tmp_project_dir):
|
||||
"""Test that read_log returns empty string for nonexistent log."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
result = storage.read_log("nonexistent")
|
||||
assert result == ""
|
||||
|
||||
|
||||
class TestActiveSessionStorage:
|
||||
"""Tests for active session storage operations."""
|
||||
|
||||
def test_write_and_read_active_session(self, tmp_project_dir, mock_session):
|
||||
"""Test writing and reading active session."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
session_data = mock_session.model_dump(mode="json")
|
||||
session_data["started_at"] = mock_session.started_at.isoformat()
|
||||
if mock_session.ended_at:
|
||||
session_data["ended_at"] = mock_session.ended_at.isoformat()
|
||||
|
||||
storage.write_active_session(session_data)
|
||||
|
||||
result = storage.read_active_session()
|
||||
assert result is not None
|
||||
assert result["id"] == mock_session.id
|
||||
|
||||
def test_read_active_session_returns_none_when_not_exists(self, tmp_project_dir):
|
||||
"""Test that read_active_session returns None when file doesn't exist."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
result = storage.read_active_session()
|
||||
assert result is None
|
||||
|
||||
def test_delete_active_session(self, tmp_project_dir, mock_session):
|
||||
"""Test deleting active session."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
session_data = mock_session.model_dump(mode="json")
|
||||
session_data["started_at"] = mock_session.started_at.isoformat()
|
||||
storage.write_active_session(session_data)
|
||||
|
||||
storage.delete_active_session()
|
||||
|
||||
result = storage.read_active_session()
|
||||
assert result is None
|
||||
|
||||
def test_delete_active_session_when_not_exists(self, tmp_project_dir):
|
||||
"""Test deleting active session when it doesn't exist doesn't error."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
storage.delete_active_session()
|
||||
|
||||
|
||||
class TestProjectExistence:
|
||||
"""Tests for project existence checks."""
|
||||
|
||||
def test_project_exists_returns_true_for_existing_project(self, tmp_project_dir):
|
||||
"""Test that project_exists returns True for existing project."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
slug = "test-project"
|
||||
|
||||
(tmp_project_dir / slug).mkdir()
|
||||
|
||||
assert storage.project_exists(slug) is True
|
||||
|
||||
def test_project_exists_returns_false_for_nonexistent(self, tmp_project_dir):
|
||||
"""Test that project_exists returns False for nonexistent project."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
assert storage.project_exists("nonexistent") is False
|
||||
|
||||
|
||||
class TestListProjects:
|
||||
"""Tests for listing projects."""
|
||||
|
||||
def test_list_projects_returns_all_projects(self, tmp_project_dir):
|
||||
"""Test that list_projects returns all project slugs."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
(tmp_project_dir / "project-1").mkdir()
|
||||
(tmp_project_dir / "project-2").mkdir()
|
||||
|
||||
projects = storage.list_projects()
|
||||
assert "project-1" in projects
|
||||
assert "project-2" in projects
|
||||
|
||||
def test_list_projects_excludes_hidden_directories(self, tmp_project_dir):
|
||||
"""Test that list_projects excludes hidden directories."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
(tmp_project_dir / "project-1").mkdir()
|
||||
(tmp_project_dir / ".hidden").mkdir()
|
||||
|
||||
projects = storage.list_projects()
|
||||
assert "project-1" in projects
|
||||
assert ".hidden" not in projects
|
||||
|
||||
def test_list_projects_returns_empty_list_when_no_projects(self, tmp_project_dir):
|
||||
"""Test that list_projects returns empty list when no projects exist."""
|
||||
storage = FileStorage(tmp_project_dir)
|
||||
|
||||
projects = storage.list_projects()
|
||||
assert projects == []
|
||||
Reference in New Issue
Block a user