first commit
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/fixtures/__init__.py
vendored
Normal file
0
tests/fixtures/__init__.py
vendored
Normal file
11
tests/fixtures/test_myorg/calendar.txt
vendored
Normal file
11
tests/fixtures/test_myorg/calendar.txt
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# MyOrg Calendar - Test Data
|
||||
|
||||
2026-02-01 09:00 Morning standup @telefon +work
|
||||
2026-02-01 11:00 Deep work session @computer-deep +myorg-assistant
|
||||
2026-02-01 14:00 Dentist appointment @personal
|
||||
2026-02-02 10:00 Team planning meeting @telefon +work
|
||||
2026-02-03 Coffee with friend @bcn
|
||||
2026-02-05 09:00 Weekly team sync @telefon +work
|
||||
2026-02-10 Birthday celebration @personal
|
||||
2026-02-15 19:00 Dinner reservation @restaurant location:Downtown
|
||||
2026-02-20 15:00 Project review meeting +myorg-assistant @telefon
|
||||
8
tests/fixtures/test_myorg/projects.txt
vendored
Normal file
8
tests/fixtures/test_myorg/projects.txt
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# MyOrg Projects - Test Data
|
||||
|
||||
+myorg-assistant MyOrg Personal Assistant [active] @computer-deep goal:q1-2026 due:2026-02-28
|
||||
+observability-blog Write observability blog post [active] @computer-deep goal:q1-2026 due:2026-02-15
|
||||
+k3s Setup and maintain k3s cluster [active] @bcn goal:q1-2026
|
||||
+home-renovation Kitchen renovation project [waiting] due:2026-03-15
|
||||
+learn-rust Learn Rust programming [someday] @computer-deep
|
||||
+q4-review Complete Q4 2025 review [completed]
|
||||
55
tests/fixtures/test_myorg/telos.md
vendored
Normal file
55
tests/fixtures/test_myorg/telos.md
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# Telos - Life Vision & Missions
|
||||
|
||||
**Last Updated**: 2026-01-31
|
||||
|
||||
## Life Vision
|
||||
|
||||
To live a balanced, meaningful life focused on continuous growth, helping others through technology, and maintaining strong relationships.
|
||||
|
||||
## Core Missions
|
||||
|
||||
### 1. Professional Growth (+Carrera)
|
||||
Build expertise in AI, distributed systems, and software engineering. Share knowledge through writing and teaching.
|
||||
|
||||
**Key Goals**:
|
||||
- Publish technical content regularly
|
||||
- Contribute to open source projects
|
||||
- Mentor junior developers
|
||||
|
||||
### 2. Personal Development (+CreixementPersonal)
|
||||
Continuous learning and self-improvement in technical and non-technical areas.
|
||||
|
||||
**Key Goals**:
|
||||
- Learn new programming languages
|
||||
- Improve productivity systems
|
||||
- Develop better habits
|
||||
|
||||
### 3. Health & Well-being (+Salut)
|
||||
Maintain physical and mental health through exercise, nutrition, and mindfulness.
|
||||
|
||||
**Key Goals**:
|
||||
- Regular exercise routine
|
||||
- Healthy eating habits
|
||||
- Adequate sleep and rest
|
||||
|
||||
### 4. Relationships (+Relacions)
|
||||
Nurture meaningful relationships with family, friends, and community.
|
||||
|
||||
**Key Goals**:
|
||||
- Quality time with loved ones
|
||||
- Stay connected with friends
|
||||
- Build community connections
|
||||
|
||||
### 5. Financial Stability (+Finances)
|
||||
Achieve financial independence through smart planning and investments.
|
||||
|
||||
**Key Goals**:
|
||||
- Emergency fund
|
||||
- Retirement savings
|
||||
- Smart investments
|
||||
|
||||
## Current Focus (Q1 2026)
|
||||
|
||||
1. **MyOrg Assistant**: Build AI-powered personal assistant (+myorg-assistant)
|
||||
2. **Technical Writing**: Publish observability blog post (+observability-blog)
|
||||
3. **Infrastructure**: Stabilize k3s cluster setup (+k3s)
|
||||
12
tests/fixtures/test_myorg/todo.txt
vendored
Normal file
12
tests/fixtures/test_myorg/todo.txt
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# MyOrg Todo List - Test Data
|
||||
|
||||
(A) 2026-01-31 Write blog post about observability +observability-blog @computer-deep due:2026-02-15
|
||||
(A) 2026-01-30 Finish myorg assistant Phase 0 +myorg-assistant @computer-deep
|
||||
(B) 2026-01-31 Review k3s ingress configuration +k3s @bcn
|
||||
Buy milk and groceries @recados due:2026-02-01
|
||||
Call dentist to schedule appointment @telefon @recados
|
||||
(C) Update documentation for deployment process +k3s @computer-light
|
||||
Research Claude Agent SDK features +myorg-assistant @computer-deep
|
||||
x 2026-01-30 Set up project repository +myorg-assistant
|
||||
x 2026-01-30 Create parsers for todo.txt format +myorg-assistant
|
||||
x 2026-01-29 Install dependencies +myorg-assistant
|
||||
228
tests/test_calendar_parser.py
Normal file
228
tests/test_calendar_parser.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""Unit tests for CalendarParser."""
|
||||
import pytest
|
||||
from datetime import datetime, time
|
||||
from src.parsers.calendar_parser import CalendarParser, Event
|
||||
|
||||
|
||||
class TestCalendarParser:
|
||||
"""Tests for CalendarParser class."""
|
||||
|
||||
def test_parse_event_with_time(self) -> None:
|
||||
"""Test parsing an event with specific time."""
|
||||
line = "2026-02-01 09:00 Team standup"
|
||||
event = CalendarParser.parse_line(line)
|
||||
|
||||
assert event is not None
|
||||
assert event.date == datetime(2026, 2, 1)
|
||||
assert event.time == time(9, 0)
|
||||
assert event.description == "Team standup"
|
||||
assert event.all_day == False
|
||||
|
||||
def test_parse_all_day_event(self) -> None:
|
||||
"""Test parsing an all-day event."""
|
||||
line = "2026-02-15 Birthday party"
|
||||
event = CalendarParser.parse_line(line)
|
||||
|
||||
assert event is not None
|
||||
assert event.date == datetime(2026, 2, 15)
|
||||
assert event.time is None
|
||||
assert event.description == "Birthday party"
|
||||
assert event.all_day == True
|
||||
|
||||
def test_parse_event_with_context(self) -> None:
|
||||
"""Test parsing an event with context tags."""
|
||||
line = "2026-02-01 14:30 Doctor appointment @personal"
|
||||
event = CalendarParser.parse_line(line)
|
||||
|
||||
assert event is not None
|
||||
assert "personal" in event.contexts
|
||||
assert event.description == "Doctor appointment"
|
||||
|
||||
def test_parse_event_with_project(self) -> None:
|
||||
"""Test parsing an event with project tags."""
|
||||
line = "2026-02-05 10:00 Project kickoff +myorg-assistant"
|
||||
event = CalendarParser.parse_line(line)
|
||||
|
||||
assert event is not None
|
||||
assert "myorg-assistant" in event.projects
|
||||
assert event.description == "Project kickoff"
|
||||
|
||||
def test_parse_event_with_multiple_tags(self) -> None:
|
||||
"""Test parsing an event with multiple contexts and projects."""
|
||||
line = "2026-02-10 15:00 Team meeting @telefon +work +team-sync"
|
||||
event = CalendarParser.parse_line(line)
|
||||
|
||||
assert event is not None
|
||||
assert "telefon" in event.contexts
|
||||
assert "work" in event.projects
|
||||
assert "team-sync" in event.projects
|
||||
assert event.description == "Team meeting"
|
||||
|
||||
def test_parse_event_with_tags(self) -> None:
|
||||
"""Test parsing an event with custom tags."""
|
||||
line = "2026-02-20 18:00 Dinner location:restaurant duration:2h"
|
||||
event = CalendarParser.parse_line(line)
|
||||
|
||||
assert event is not None
|
||||
assert event.tags.get("location") == "restaurant"
|
||||
assert event.tags.get("duration") == "2h"
|
||||
|
||||
def test_parse_empty_line(self) -> None:
|
||||
"""Test parsing an empty line returns None."""
|
||||
event = CalendarParser.parse_line("")
|
||||
assert event is None
|
||||
|
||||
def test_parse_comment_line(self) -> None:
|
||||
"""Test parsing a comment line returns None."""
|
||||
event = CalendarParser.parse_line("# This is a comment")
|
||||
assert event is None
|
||||
|
||||
def test_parse_invalid_format(self) -> None:
|
||||
"""Test parsing invalid format returns None."""
|
||||
event = CalendarParser.parse_line("Not a valid event format")
|
||||
assert event is None
|
||||
|
||||
def test_format_event_with_time(self) -> None:
|
||||
"""Test formatting an event with time."""
|
||||
formatted = CalendarParser.format_event(
|
||||
date=datetime(2026, 2, 1),
|
||||
time=time(9, 0),
|
||||
description="Team standup"
|
||||
)
|
||||
assert formatted == "2026-02-01 09:00 Team standup"
|
||||
|
||||
def test_format_all_day_event(self) -> None:
|
||||
"""Test formatting an all-day event."""
|
||||
formatted = CalendarParser.format_event(
|
||||
date=datetime(2026, 2, 15),
|
||||
description="Birthday party"
|
||||
)
|
||||
assert formatted == "2026-02-15 Birthday party"
|
||||
|
||||
def test_format_event_with_all_features(self) -> None:
|
||||
"""Test formatting an event with all features."""
|
||||
formatted = CalendarParser.format_event(
|
||||
date=datetime(2026, 2, 10),
|
||||
time=time(15, 0),
|
||||
description="Team meeting",
|
||||
contexts=["telefon"],
|
||||
projects=["work"],
|
||||
tags={"duration": "1h"}
|
||||
)
|
||||
assert "2026-02-10 15:00" in formatted
|
||||
assert "Team meeting" in formatted
|
||||
assert "@telefon" in formatted
|
||||
assert "+work" in formatted
|
||||
assert "duration:1h" in formatted
|
||||
|
||||
def test_parse_file(self) -> None:
|
||||
"""Test parsing multiple events from file content."""
|
||||
content = """# Calendar
|
||||
2026-02-01 09:00 Morning meeting +work
|
||||
2026-02-01 14:00 Afternoon appointment @personal
|
||||
2026-02-15 Birthday party
|
||||
|
||||
2026-02-20 10:00 Project review +myorg-assistant"""
|
||||
|
||||
events = CalendarParser.parse_file(content)
|
||||
assert len(events) == 4
|
||||
# Events should be sorted by datetime
|
||||
assert events[0].description == "Morning meeting"
|
||||
assert events[1].description == "Afternoon appointment"
|
||||
|
||||
def test_filter_events_by_date_range(self) -> None:
|
||||
"""Test filtering events by date range."""
|
||||
events = [
|
||||
Event(
|
||||
raw_line="2026-02-01 Event 1",
|
||||
line_number=1,
|
||||
date=datetime(2026, 2, 1),
|
||||
description="Event 1"
|
||||
),
|
||||
Event(
|
||||
raw_line="2026-02-05 Event 2",
|
||||
line_number=2,
|
||||
date=datetime(2026, 2, 5),
|
||||
description="Event 2"
|
||||
),
|
||||
Event(
|
||||
raw_line="2026-02-10 Event 3",
|
||||
line_number=3,
|
||||
date=datetime(2026, 2, 10),
|
||||
description="Event 3"
|
||||
),
|
||||
]
|
||||
|
||||
filtered = CalendarParser.filter_events(
|
||||
events,
|
||||
start_date=datetime(2026, 2, 3),
|
||||
end_date=datetime(2026, 2, 8)
|
||||
)
|
||||
assert len(filtered) == 1
|
||||
assert filtered[0].description == "Event 2"
|
||||
|
||||
def test_filter_events_by_context(self) -> None:
|
||||
"""Test filtering events by context."""
|
||||
events = [
|
||||
Event(
|
||||
raw_line="Event 1 @work",
|
||||
line_number=1,
|
||||
date=datetime(2026, 2, 1),
|
||||
description="Event 1",
|
||||
contexts=["work"]
|
||||
),
|
||||
Event(
|
||||
raw_line="Event 2 @personal",
|
||||
line_number=2,
|
||||
date=datetime(2026, 2, 2),
|
||||
description="Event 2",
|
||||
contexts=["personal"]
|
||||
),
|
||||
]
|
||||
|
||||
work_events = CalendarParser.filter_events(events, context="work")
|
||||
assert len(work_events) == 1
|
||||
assert work_events[0].description == "Event 1"
|
||||
|
||||
def test_filter_events_by_project(self) -> None:
|
||||
"""Test filtering events by project."""
|
||||
events = [
|
||||
Event(
|
||||
raw_line="Event 1 +project1",
|
||||
line_number=1,
|
||||
date=datetime(2026, 2, 1),
|
||||
description="Event 1",
|
||||
projects=["project1"]
|
||||
),
|
||||
Event(
|
||||
raw_line="Event 2 +project2",
|
||||
line_number=2,
|
||||
date=datetime(2026, 2, 2),
|
||||
description="Event 2",
|
||||
projects=["project2"]
|
||||
),
|
||||
]
|
||||
|
||||
project1_events = CalendarParser.filter_events(events, project="project1")
|
||||
assert len(project1_events) == 1
|
||||
assert project1_events[0].description == "Event 1"
|
||||
|
||||
def test_event_datetime_property(self) -> None:
|
||||
"""Test the datetime property of Event."""
|
||||
event = Event(
|
||||
raw_line="2026-02-01 15:30 Meeting",
|
||||
line_number=1,
|
||||
date=datetime(2026, 2, 1),
|
||||
time=time(15, 30),
|
||||
description="Meeting"
|
||||
)
|
||||
assert event.datetime == datetime(2026, 2, 1, 15, 30)
|
||||
|
||||
all_day_event = Event(
|
||||
raw_line="2026-02-01 Party",
|
||||
line_number=1,
|
||||
date=datetime(2026, 2, 1),
|
||||
description="Party",
|
||||
all_day=True
|
||||
)
|
||||
assert all_day_event.datetime == datetime(2026, 2, 1)
|
||||
266
tests/test_project_parser.py
Normal file
266
tests/test_project_parser.py
Normal file
@@ -0,0 +1,266 @@
|
||||
"""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)
|
||||
207
tests/test_todo_parser.py
Normal file
207
tests/test_todo_parser.py
Normal file
@@ -0,0 +1,207 @@
|
||||
"""Unit tests for TodoParser."""
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from src.parsers.todo_parser import TodoParser, Task
|
||||
|
||||
|
||||
class TestTodoParser:
|
||||
"""Tests for TodoParser class."""
|
||||
|
||||
def test_parse_simple_task(self) -> None:
|
||||
"""Test parsing a simple task without metadata."""
|
||||
line = "Buy milk"
|
||||
task = TodoParser.parse_line(line)
|
||||
|
||||
assert task is not None
|
||||
assert task.description == "Buy milk"
|
||||
assert task.completed == False
|
||||
assert task.priority is None
|
||||
assert len(task.projects) == 0
|
||||
assert len(task.contexts) == 0
|
||||
|
||||
def test_parse_task_with_priority(self) -> None:
|
||||
"""Test parsing a task with priority."""
|
||||
line = "(A) Write blog post"
|
||||
task = TodoParser.parse_line(line)
|
||||
|
||||
assert task is not None
|
||||
assert task.priority == "A"
|
||||
assert task.description == "Write blog post"
|
||||
|
||||
def test_parse_task_with_creation_date(self) -> None:
|
||||
"""Test parsing a task with creation date."""
|
||||
line = "(B) 2026-01-31 Finish implementation"
|
||||
task = TodoParser.parse_line(line)
|
||||
|
||||
assert task is not None
|
||||
assert task.priority == "B"
|
||||
assert task.creation_date == datetime(2026, 1, 31)
|
||||
assert task.description == "Finish implementation"
|
||||
|
||||
def test_parse_task_with_projects(self) -> None:
|
||||
"""Test parsing a task with project tags."""
|
||||
line = "Write tests +myorg-assistant +testing"
|
||||
task = TodoParser.parse_line(line)
|
||||
|
||||
assert task is not None
|
||||
assert "myorg-assistant" in task.projects
|
||||
assert "testing" in task.projects
|
||||
assert task.description == "Write tests"
|
||||
|
||||
def test_parse_task_with_contexts(self) -> None:
|
||||
"""Test parsing a task with context tags."""
|
||||
line = "Call dentist @telefon @recados"
|
||||
task = TodoParser.parse_line(line)
|
||||
|
||||
assert task is not None
|
||||
assert "telefon" in task.contexts
|
||||
assert "recados" in task.contexts
|
||||
assert task.description == "Call dentist"
|
||||
|
||||
def test_parse_task_with_due_date(self) -> None:
|
||||
"""Test parsing a task with due date metadata."""
|
||||
line = "(A) Submit report +work due:2026-02-15"
|
||||
task = TodoParser.parse_line(line)
|
||||
|
||||
assert task is not None
|
||||
assert task.due_date == datetime(2026, 2, 15)
|
||||
assert "due" in task.metadata
|
||||
assert task.metadata["due"] == "2026-02-15"
|
||||
|
||||
def test_parse_completed_task(self) -> None:
|
||||
"""Test parsing a completed task."""
|
||||
line = "x 2026-01-31 Buy groceries"
|
||||
task = TodoParser.parse_line(line)
|
||||
|
||||
assert task is not None
|
||||
assert task.completed == True
|
||||
assert task.completion_date == datetime(2026, 1, 31)
|
||||
assert task.description == "Buy groceries"
|
||||
|
||||
def test_parse_complex_task(self) -> None:
|
||||
"""Test parsing a complex task with all features."""
|
||||
line = "(A) 2026-01-31 Write observability blog post +observability-blog @computer-deep due:2026-02-15 priority:high"
|
||||
task = TodoParser.parse_line(line)
|
||||
|
||||
assert task is not None
|
||||
assert task.priority == "A"
|
||||
assert task.creation_date == datetime(2026, 1, 31)
|
||||
assert task.description == "Write observability blog post"
|
||||
assert "observability-blog" in task.projects
|
||||
assert "computer-deep" in task.contexts
|
||||
assert task.due_date == datetime(2026, 2, 15)
|
||||
assert task.metadata["priority"] == "high"
|
||||
|
||||
def test_parse_empty_line(self) -> None:
|
||||
"""Test parsing an empty line returns None."""
|
||||
task = TodoParser.parse_line("")
|
||||
assert task is None
|
||||
|
||||
def test_parse_comment_line(self) -> None:
|
||||
"""Test parsing a comment line returns None."""
|
||||
task = TodoParser.parse_line("# This is a comment")
|
||||
assert task is None
|
||||
|
||||
def test_format_simple_task(self) -> None:
|
||||
"""Test formatting a simple task."""
|
||||
formatted = TodoParser.format_task(description="Buy milk")
|
||||
assert formatted == "Buy milk"
|
||||
|
||||
def test_format_task_with_priority(self) -> None:
|
||||
"""Test formatting a task with priority."""
|
||||
formatted = TodoParser.format_task(
|
||||
description="Write tests",
|
||||
priority="A"
|
||||
)
|
||||
assert formatted == "(A) Write tests"
|
||||
|
||||
def test_format_task_with_all_features(self) -> None:
|
||||
"""Test formatting a complex task."""
|
||||
formatted = TodoParser.format_task(
|
||||
description="Write blog post",
|
||||
priority="B",
|
||||
creation_date=datetime(2026, 1, 31),
|
||||
projects=["observability-blog"],
|
||||
contexts=["computer-deep"],
|
||||
metadata={"due": "2026-02-15"}
|
||||
)
|
||||
assert "(B)" in formatted
|
||||
assert "2026-01-31" in formatted
|
||||
assert "Write blog post" in formatted
|
||||
assert "+observability-blog" in formatted
|
||||
assert "@computer-deep" in formatted
|
||||
assert "due:2026-02-15" in formatted
|
||||
|
||||
def test_format_completed_task(self) -> None:
|
||||
"""Test formatting a completed task."""
|
||||
formatted = TodoParser.format_task(
|
||||
description="Buy groceries",
|
||||
completed=True,
|
||||
completion_date=datetime(2026, 1, 31)
|
||||
)
|
||||
assert formatted.startswith("x 2026-01-31")
|
||||
assert "Buy groceries" in formatted
|
||||
|
||||
def test_parse_file(self) -> None:
|
||||
"""Test parsing multiple tasks from file content."""
|
||||
content = """# Tasks
|
||||
(A) Write tests +myorg-assistant
|
||||
Buy milk @recados
|
||||
x 2026-01-30 Completed task
|
||||
|
||||
(B) 2026-01-31 Another task +project due:2026-02-01"""
|
||||
|
||||
tasks = TodoParser.parse_file(content)
|
||||
assert len(tasks) == 4
|
||||
assert tasks[0].description == "Write tests"
|
||||
assert tasks[1].description == "Buy milk"
|
||||
assert tasks[2].completed == True
|
||||
assert tasks[3].priority == "B"
|
||||
|
||||
def test_filter_tasks_by_completion(self) -> None:
|
||||
"""Test filtering tasks by completion status."""
|
||||
tasks = [
|
||||
Task(raw_line="x Task 1", line_number=1, completed=True, description="Task 1"),
|
||||
Task(raw_line="Task 2", line_number=2, completed=False, description="Task 2"),
|
||||
Task(raw_line="Task 3", line_number=3, completed=False, description="Task 3"),
|
||||
]
|
||||
|
||||
active = TodoParser.filter_tasks(tasks, completed=False)
|
||||
assert len(active) == 2
|
||||
|
||||
completed = TodoParser.filter_tasks(tasks, completed=True)
|
||||
assert len(completed) == 1
|
||||
|
||||
def test_filter_tasks_by_priority(self) -> None:
|
||||
"""Test filtering tasks by priority."""
|
||||
tasks = [
|
||||
Task(raw_line="(A) Task 1", line_number=1, priority="A", description="Task 1"),
|
||||
Task(raw_line="(B) Task 2", line_number=2, priority="B", description="Task 2"),
|
||||
Task(raw_line="Task 3", line_number=3, description="Task 3"),
|
||||
]
|
||||
|
||||
high_priority = TodoParser.filter_tasks(tasks, priority="A")
|
||||
assert len(high_priority) == 1
|
||||
assert high_priority[0].description == "Task 1"
|
||||
|
||||
def test_filter_tasks_by_project(self) -> None:
|
||||
"""Test filtering tasks by project tag."""
|
||||
tasks = [
|
||||
Task(raw_line="Task 1 +project1", line_number=1, description="Task 1", projects=["project1"]),
|
||||
Task(raw_line="Task 2 +project2", line_number=2, description="Task 2", projects=["project2"]),
|
||||
Task(raw_line="Task 3 +project1", line_number=3, description="Task 3", projects=["project1"]),
|
||||
]
|
||||
|
||||
project1_tasks = TodoParser.filter_tasks(tasks, project="project1")
|
||||
assert len(project1_tasks) == 2
|
||||
|
||||
def test_filter_tasks_by_context(self) -> None:
|
||||
"""Test filtering tasks by context tag."""
|
||||
tasks = [
|
||||
Task(raw_line="Task 1 @home", line_number=1, description="Task 1", contexts=["home"]),
|
||||
Task(raw_line="Task 2 @work", line_number=2, description="Task 2", contexts=["work"]),
|
||||
Task(raw_line="Task 3 @home", line_number=3, description="Task 3", contexts=["home"]),
|
||||
]
|
||||
|
||||
home_tasks = TodoParser.filter_tasks(tasks, context="home")
|
||||
assert len(home_tasks) == 2
|
||||
Reference in New Issue
Block a user