"""Unit tests for ProjectParser.""" import pytest from datetime import datetime from src.parsers.project_parser import ProjectParser, Project class TestProjectParser: """Tests for ProjectParser class.""" def test_parse_simple_project(self) -> None: """Test parsing a simple active project.""" line = "+myorg-assistant MyOrg Personal Assistant [active]" project = ProjectParser.parse_line(line) assert project is not None assert project.tag == "myorg-assistant" assert project.description == "MyOrg Personal Assistant" assert project.status == "active" def test_parse_project_without_status(self) -> None: """Test parsing a project without explicit status defaults to active.""" line = "+blog-post Write new blog post" project = ProjectParser.parse_line(line) assert project is not None assert project.status == "active" assert project.description == "Write new blog post" def test_parse_project_with_context(self) -> None: """Test parsing a project with context tags.""" line = "+home-office Setup home office @bcn [active]" project = ProjectParser.parse_line(line) assert project is not None assert "bcn" in project.contexts assert project.description == "Setup home office" def test_parse_project_with_goal(self) -> None: """Test parsing a project with goal reference.""" line = "+observability-blog Write observability blog [active] goal:q1-2026" project = ProjectParser.parse_line(line) assert project is not None assert "q1-2026" in project.goals assert project.description == "Write observability blog" def test_parse_project_with_due_date(self) -> None: """Test parsing a project with due date.""" line = "+renovation Kitchen renovation [waiting] due:2026-03-15" project = ProjectParser.parse_line(line) assert project is not None assert project.status == "waiting" assert project.due_date == datetime(2026, 3, 15) assert project.metadata["due"] == "2026-03-15" def test_parse_project_with_all_features(self) -> None: """Test parsing a complex project with all features.""" line = "+myorg-assistant MyOrg Personal Assistant [active] @computer-deep goal:q1-2026 priority:high due:2026-02-28" project = ProjectParser.parse_line(line) assert project is not None assert project.tag == "myorg-assistant" assert project.description == "MyOrg Personal Assistant" assert project.status == "active" assert "computer-deep" in project.contexts assert "q1-2026" in project.goals assert project.metadata["priority"] == "high" assert project.due_date == datetime(2026, 2, 28) def test_parse_waiting_project(self) -> None: """Test parsing a waiting project.""" line = "+house-docs House documentation [waiting]" project = ProjectParser.parse_line(line) assert project is not None assert project.status == "waiting" def test_parse_someday_project(self) -> None: """Test parsing a someday project.""" line = "+learn-rust Learn Rust programming [someday]" project = ProjectParser.parse_line(line) assert project is not None assert project.status == "someday" def test_parse_completed_project(self) -> None: """Test parsing a completed project.""" line = "+q4-review Q4 review [completed]" project = ProjectParser.parse_line(line) assert project is not None assert project.status == "completed" def test_parse_empty_line(self) -> None: """Test parsing an empty line returns None.""" project = ProjectParser.parse_line("") assert project is None def test_parse_comment_line(self) -> None: """Test parsing a comment line returns None.""" project = ProjectParser.parse_line("# This is a comment") assert project is None def test_parse_invalid_format(self) -> None: """Test parsing a line without project tag returns None.""" project = ProjectParser.parse_line("Not a valid project") assert project is None def test_format_simple_project(self) -> None: """Test formatting a simple project.""" formatted = ProjectParser.format_project( tag="blog-post", description="Write blog post", status="active" ) assert formatted == "+blog-post Write blog post [active]" def test_format_project_with_all_features(self) -> None: """Test formatting a complex project.""" formatted = ProjectParser.format_project( tag="myorg-assistant", description="MyOrg Assistant", status="active", contexts=["computer-deep"], goals=["q1-2026"], metadata={"priority": "high", "due": "2026-02-28"} ) assert "+myorg-assistant" in formatted assert "MyOrg Assistant" in formatted assert "[active]" in formatted assert "@computer-deep" in formatted assert "goal:q1-2026" in formatted assert "priority:high" in formatted assert "due:2026-02-28" in formatted def test_parse_file(self) -> None: """Test parsing multiple projects from file content.""" content = """# Active Projects +myorg-assistant Personal assistant [active] goal:q1-2026 +blog-post Write blog post [active] @computer-deep +renovation Kitchen renovation [waiting] due:2026-03-15 +learn-rust Learn Rust [someday]""" projects = ProjectParser.parse_file(content) assert len(projects) == 4 assert projects[0].tag == "myorg-assistant" assert projects[1].status == "active" assert projects[2].status == "waiting" assert projects[3].status == "someday" def test_filter_projects_by_status(self) -> None: """Test filtering projects by status.""" projects = [ Project( raw_line="+p1 Project 1 [active]", line_number=1, tag="p1", description="Project 1", status="active" ), Project( raw_line="+p2 Project 2 [waiting]", line_number=2, tag="p2", description="Project 2", status="waiting" ), Project( raw_line="+p3 Project 3 [active]", line_number=3, tag="p3", description="Project 3", status="active" ), ] active = ProjectParser.filter_projects(projects, status="active") assert len(active) == 2 waiting = ProjectParser.filter_projects(projects, status="waiting") assert len(waiting) == 1 def test_filter_projects_by_context(self) -> None: """Test filtering projects by context.""" projects = [ Project( raw_line="+p1 Project 1 @home", line_number=1, tag="p1", description="Project 1", contexts=["home"] ), Project( raw_line="+p2 Project 2 @work", line_number=2, tag="p2", description="Project 2", contexts=["work"] ), ] home_projects = ProjectParser.filter_projects(projects, context="home") assert len(home_projects) == 1 assert home_projects[0].tag == "p1" def test_filter_projects_by_goal(self) -> None: """Test filtering projects by goal.""" projects = [ Project( raw_line="+p1 Project 1 goal:q1-2026", line_number=1, tag="p1", description="Project 1", goals=["q1-2026"] ), Project( raw_line="+p2 Project 2 goal:q2-2026", line_number=2, tag="p2", description="Project 2", goals=["q2-2026"] ), ] q1_projects = ProjectParser.filter_projects(projects, goal="q1-2026") assert len(q1_projects) == 1 assert q1_projects[0].tag == "p1" def test_get_active_projects(self) -> None: """Test getting only active projects.""" projects = [ Project( raw_line="+p1 Project 1 [active]", line_number=1, tag="p1", description="Project 1", status="active" ), Project( raw_line="+p2 Project 2 [waiting]", line_number=2, tag="p2", description="Project 2", status="waiting" ), Project( raw_line="+p3 Project 3 [active]", line_number=3, tag="p3", description="Project 3", status="active" ), Project( raw_line="+p4 Project 4 [someday]", line_number=4, tag="p4", description="Project 4", status="someday" ), ] active = ProjectParser.get_active_projects(projects) assert len(active) == 2 assert all(p.status == "active" for p in active)