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.
221 lines
8.2 KiB
Python
221 lines
8.2 KiB
Python
"""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
|