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:
220
tests/test_models.py
Normal file
220
tests/test_models.py
Normal 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
|
||||
Reference in New Issue
Block a user