first commit

This commit is contained in:
Roger Oriol
2026-02-03 23:50:19 +01:00
commit 87fb32b559
80 changed files with 8884 additions and 0 deletions

0
tests/__init__.py Normal file
View File

0
tests/fixtures/__init__.py vendored Normal file
View File

11
tests/fixtures/test_myorg/calendar.txt vendored Normal file
View 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

View 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
View 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
View 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

View 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)

View 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
View 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