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:
2026-03-23 09:40:46 -03:00
parent 4e67062c99
commit 2735562b65
6 changed files with 1302 additions and 0 deletions

220
tests/test_models.py Normal file
View File

@@ -0,0 +1,220 @@
"""Tests for data models."""
from datetime import datetime, date
import pytest
from tracker.models import Project, Session, Note, NoteType, Change
class TestProjectCreation:
"""Tests for Project model creation."""
def test_project_creation_with_all_fields(self, sample_project_data):
"""Test creating a project with all fields specified."""
project = Project(**sample_project_data)
assert project.id == sample_project_data["id"]
assert project.name == sample_project_data["name"]
assert project.slug == sample_project_data["slug"]
assert project.description == sample_project_data["description"]
assert project.type == sample_project_data["type"]
assert project.status == sample_project_data["status"]
assert project.tags == sample_project_data["tags"]
assert project.root_path == sample_project_data["root_path"]
assert project.repo_path == sample_project_data["repo_path"]
assert project.created_at == sample_project_data["created_at"]
assert project.updated_at == sample_project_data["updated_at"]
assert project.last_session_at is None
def test_project_creation_with_defaults(self):
"""Test creating a project with default values."""
now = datetime.now()
project = Project(
id="test-id",
name="Test",
slug="test",
created_at=now,
updated_at=now,
)
assert project.description == ""
assert project.type == "misc"
assert project.status == "inbox"
assert project.tags == []
assert project.root_path == ""
assert project.repo_path is None
assert project.last_session_at is None
def test_project_status_validation(self):
"""Test that project status can be set to valid values."""
valid_statuses = ["inbox", "next", "active", "blocked", "waiting", "done", "archived"]
for status in valid_statuses:
project = Project(
id="test-id",
name="Test",
slug="test",
created_at=datetime.now(),
updated_at=datetime.now(),
status=status,
)
assert project.status == status
def test_project_type_validation(self):
"""Test that project type can be set to valid values."""
valid_types = ["code", "homelab", "automation", "agent", "research", "misc"]
for project_type in valid_types:
project = Project(
id="test-id",
name="Test",
slug="test",
created_at=datetime.now(),
updated_at=datetime.now(),
type=project_type,
)
assert project.type == project_type
class TestSessionCreation:
"""Tests for Session model creation."""
def test_session_creation_with_all_fields(self, mock_session):
"""Test creating a session with all fields specified."""
assert mock_session.id == "session-123"
assert mock_session.project_slug == "test-project"
assert mock_session.started_at == datetime(2024, 1, 15, 10, 0, 0)
assert mock_session.ended_at == datetime(2024, 1, 15, 11, 30, 0)
assert mock_session.duration_minutes == 90
assert mock_session.objective == "Complete initial implementation"
assert mock_session.summary == "Worked on core features"
assert len(mock_session.work_done) == 2
assert len(mock_session.changes) == 1
assert len(mock_session.decisions) == 1
assert len(mock_session.blockers) == 0
assert len(mock_session.next_steps) == 2
assert len(mock_session.references) == 1
assert len(mock_session.raw_notes) == 2
def test_session_creation_with_defaults(self):
"""Test creating a session with required fields only."""
session = Session(
id="session-1",
project_slug="test-project",
started_at=datetime.now(),
)
assert session.ended_at is None
assert session.duration_minutes is None
assert session.objective == ""
assert session.summary == ""
assert session.work_done == []
assert session.changes == []
assert session.decisions == []
assert session.blockers == []
assert session.next_steps == []
assert session.references == []
assert session.raw_notes == []
def test_session_with_optional_datetime_fields(self):
"""Test session with ended_at but no duration_minutes."""
session = Session(
id="session-1",
project_slug="test-project",
started_at=datetime(2024, 1, 15, 10, 0, 0),
ended_at=datetime(2024, 1, 15, 11, 0, 0),
)
assert session.ended_at is not None
assert session.duration_minutes is None
class TestNoteTypes:
"""Tests for Note and NoteType models."""
def test_note_creation(self, sample_note):
"""Test creating a note."""
assert sample_note.type == NoteType.WORK
assert sample_note.text == "Completed the implementation of feature X"
assert sample_note.created_at == datetime(2024, 1, 15, 10, 30, 0)
def test_note_type_enum_values(self):
"""Test that NoteType enum has expected values."""
assert NoteType.WORK.value == "work"
assert NoteType.CHANGE.value == "change"
assert NoteType.BLOCKER.value == "blocker"
assert NoteType.DECISION.value == "decision"
assert NoteType.IDEA.value == "idea"
assert NoteType.REFERENCE.value == "reference"
def test_note_type_assignment(self):
"""Test assigning different note types to a note."""
for note_type in NoteType:
note = Note(type=note_type, text="Test note")
assert note.type == note_type
def test_note_default_created_at(self):
"""Test that note has default created_at timestamp."""
note = Note(type=NoteType.WORK, text="Test note")
assert note.created_at is not None
assert isinstance(note.created_at, datetime)
def test_note_raw_notes_structure(self, mock_session):
"""Test that raw_notes in session have expected structure."""
assert len(mock_session.raw_notes) == 2
assert mock_session.raw_notes[0]["type"] == "work"
assert mock_session.raw_notes[0]["text"] == "Working on feature A"
assert "timestamp" in mock_session.raw_notes[0]
class TestChangeValidation:
"""Tests for Change model validation."""
def test_change_creation(self, sample_change):
"""Test creating a change."""
assert sample_change.date == date(2024, 1, 15)
assert sample_change.type == "code"
assert sample_change.title == "Added user authentication"
assert sample_change.impact == "Improved security"
assert sample_change.references == ["#123"]
def test_change_creation_with_defaults(self):
"""Test creating a change with default values."""
change = Change(
date=date(2024, 1, 15),
type="docs",
title="Updated documentation",
)
assert change.impact == ""
assert change.references == []
def test_change_date_as_date_not_datetime(self, sample_change):
"""Test that change date is stored as date object."""
assert isinstance(sample_change.date, date)
assert not isinstance(sample_change.date, datetime)
def test_change_types(self):
"""Test valid change types."""
valid_types = ["code", "infra", "config", "docs", "automation", "decision"]
for change_type in valid_types:
change = Change(
date=date.today(),
type=change_type,
title=f"Test {change_type}",
)
assert change.type == change_type
def test_change_with_multiple_references(self):
"""Test change with multiple references."""
change = Change(
date=date.today(),
type="code",
title="Major refactor",
references=["#100", "#101", "#102"],
)
assert len(change.references) == 3
assert "#100" in change.references
assert "#102" in change.references