"""Tests for complete flow: init -> start -> note -> stop -> show.""" from datetime import datetime from pathlib import Path from unittest.mock import patch import pytest import yaml from tracker.models import Session, Note, NoteType from tracker.services import ( create_project, get_project, ensure_project_structure, set_active_session, get_active_session, clear_active_session, validate_no_other_active_session, add_note, consolidate_notes, get_projects_root, ) from tracker.storage import FileStorage class TestInitProjectFlow: """Tests for project initialization flow.""" def test_init_project_creates_structure(self, tmp_path, sample_project_data): """Test that project initialization creates required directory structure.""" slug = sample_project_data["slug"] with patch("tracker.services.project_service._PROJECTS_ROOT", tmp_path): project = create_project( name=sample_project_data["name"], slug=slug, description=sample_project_data["description"], type=sample_project_data["type"], ) ensure_project_structure(slug) project_root = tmp_path / slug assert (project_root / "sessions").is_dir() assert (project_root / "docs").is_dir() assert (project_root / "assets").is_dir() assert (project_root / "meta").is_dir() def test_init_project_creates_meta_file(self, tmp_path, sample_project_data): """Test that project initialization creates meta/project.yaml.""" slug = sample_project_data["slug"] storage = FileStorage(tmp_path) with patch("tracker.services.project_service._PROJECTS_ROOT", tmp_path): project = create_project( name=sample_project_data["name"], slug=slug, description=sample_project_data["description"], type=sample_project_data["type"], ) # Write the project meta (simulating what storage would do) meta_data = { "id": project.id, "name": project.name, "slug": project.slug, "description": project.description, "type": project.type, "status": project.status, "tags": project.tags, "created_at": project.created_at.isoformat(), "updated_at": project.updated_at.isoformat(), } storage.write_project_meta(slug, meta_data) meta_path = tmp_path / slug / "meta" / "project.yaml" assert meta_path.exists() with open(meta_path, "r") as f: data = yaml.safe_load(f) assert data["name"] == sample_project_data["name"] assert data["slug"] == slug class TestStartSessionFlow: """Tests for starting a session.""" def test_start_session(self, tmp_path, sample_project_data, monkeypatch): """Test starting a session creates active session.""" slug = sample_project_data["slug"] # Create a fake active session path fake_path = tmp_path / ".active_session.json" def mock_get_active_session_path(): return fake_path monkeypatch.setattr( "tracker.services.session_service.get_active_session_path", mock_get_active_session_path ) with patch("tracker.services.project_service._PROJECTS_ROOT", tmp_path): project = create_project( name=sample_project_data["name"], slug=slug, ) ensure_project_structure(slug) session = Session( id="session-1", project_slug=slug, started_at=datetime.now(), objective="Test objective", ) set_active_session(session) active = get_active_session() assert active is not None assert active.id == "session-1" assert active.project_slug == slug def test_start_session_fails_if_other_active(self, tmp_path, sample_project_data, monkeypatch): """Test that starting a session fails if another project has an active session.""" slug1 = "project-1" slug2 = "project-2" fake_path = tmp_path / ".active_session.json" def mock_get_active_session_path(): return fake_path monkeypatch.setattr( "tracker.services.session_service.get_active_session_path", mock_get_active_session_path ) with patch("tracker.services.project_service._PROJECTS_ROOT", tmp_path): session1 = Session( id="session-1", project_slug=slug1, started_at=datetime.now(), ) set_active_session(session1) is_valid = True if not validate_no_other_active_session(slug2): is_valid = False assert is_valid is False class TestAddNoteFlow: """Tests for adding notes during a session.""" def test_add_note(self, tmp_path, sample_project_data, monkeypatch): """Test adding a note during a session.""" slug = sample_project_data["slug"] fake_path = tmp_path / ".active_session.json" def mock_get_active_session_path(): return fake_path monkeypatch.setattr( "tracker.services.session_service.get_active_session_path", mock_get_active_session_path ) with patch("tracker.services.project_service._PROJECTS_ROOT", tmp_path): session = Session( id="session-1", project_slug=slug, started_at=datetime.now(), ) set_active_session(session) add_note(session, "work", "Test work note") assert len(session.raw_notes) == 1 assert session.raw_notes[0]["type"] == "work" assert session.raw_notes[0]["text"] == "Test work note" def test_add_multiple_notes(self, tmp_path, sample_project_data, monkeypatch): """Test adding multiple notes during a session.""" slug = sample_project_data["slug"] fake_path = tmp_path / ".active_session.json" def mock_get_active_session_path(): return fake_path monkeypatch.setattr( "tracker.services.session_service.get_active_session_path", mock_get_active_session_path ) with patch("tracker.services.project_service._PROJECTS_ROOT", tmp_path): session = Session( id="session-1", project_slug=slug, started_at=datetime.now(), ) add_note(session, "work", "Work note") add_note(session, "idea", "Idea note") add_note(session, "blocker", "Blocker note") assert len(session.raw_notes) == 3 assert session.raw_notes[0]["type"] == "work" assert session.raw_notes[1]["type"] == "idea" assert session.raw_notes[2]["type"] == "blocker" class TestStopSessionFlow: """Tests for stopping a session.""" def test_stop_session(self, tmp_path, sample_project_data, monkeypatch): """Test stopping a session clears active session.""" slug = sample_project_data["slug"] fake_path = tmp_path / ".active_session.json" def mock_get_active_session_path(): return fake_path monkeypatch.setattr( "tracker.services.session_service.get_active_session_path", mock_get_active_session_path ) with patch("tracker.services.project_service._PROJECTS_ROOT", tmp_path): session = Session( id="session-1", project_slug=slug, started_at=datetime.now(), ) set_active_session(session) session.ended_at = datetime.now() session.duration_minutes = 30 clear_active_session() active = get_active_session() assert active is None class TestShowProjectFlow: """Tests for showing project information.""" def test_show_project(self, tmp_path, sample_project_data): """Test showing project information.""" slug = sample_project_data["slug"] storage = FileStorage(tmp_path) meta_data = { "id": "test-id", "name": sample_project_data["name"], "slug": slug, "description": sample_project_data["description"], "type": sample_project_data["type"], "status": sample_project_data["status"], "tags": sample_project_data["tags"], "created_at": datetime.now().isoformat(), "updated_at": datetime.now().isoformat(), } storage.write_project_meta(slug, meta_data) with patch("tracker.services.project_service._PROJECTS_ROOT", tmp_path): project = get_project(slug) assert project is not None assert project.name == sample_project_data["name"] assert project.slug == slug class TestConsolidateNotes: """Tests for consolidating notes after session.""" def test_consolidate_notes_categorizes_work(self): """Test that consolidate_notes categorizes work notes.""" raw_notes = [ {"type": "work", "text": "Implemented feature A", "timestamp": "2024-01-15T10:00:00"}, {"type": "work", "text": "Wrote tests", "timestamp": "2024-01-15T10:30:00"}, ] consolidated = consolidate_notes(raw_notes) assert len(consolidated["work_done"]) == 2 assert "Implemented feature A" in consolidated["work_done"] assert "Wrote tests" in consolidated["work_done"] def test_consolidate_notes_categorizes_blockers(self): """Test that consolidate_notes categorizes blockers.""" raw_notes = [ {"type": "blocker", "text": "Waiting for API access", "timestamp": "2024-01-15T10:00:00"}, ] consolidated = consolidate_notes(raw_notes) assert len(consolidated["blockers"]) == 1 assert "Waiting for API access" in consolidated["blockers"] def test_consolidate_notes_categorizes_decisions(self): """Test that consolidate_notes categorizes decisions.""" raw_notes = [ {"type": "decision", "text": "Use PostgreSQL for storage", "timestamp": "2024-01-15T10:00:00"}, ] consolidated = consolidate_notes(raw_notes) assert len(consolidated["decisions"]) == 1 assert "Use PostgreSQL for storage" in consolidated["decisions"] def test_consolidate_notes_categorizes_references(self): """Test that consolidate_notes categorizes references.""" raw_notes = [ {"type": "reference", "text": "https://docs.example.com", "timestamp": "2024-01-15T10:00:00"}, ] consolidated = consolidate_notes(raw_notes) assert len(consolidated["references"]) == 1 assert "https://docs.example.com" in consolidated["references"] def test_consolidate_notes_categorizes_changes(self): """Test that consolidate_notes categorizes changes.""" raw_notes = [ {"type": "change", "text": "Refactored authentication module", "timestamp": "2024-01-15T10:00:00"}, ] consolidated = consolidate_notes(raw_notes) assert len(consolidated["changes"]) == 1 assert "Refactored authentication module" in consolidated["changes"]