diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/commands/add.py b/commands/add.py index 1b1a943..dba12e4 100644 --- a/commands/add.py +++ b/commands/add.py @@ -1,22 +1,8 @@ """Add task command.""" import json -from pathlib import Path - - -def get_tasks_file(): - """Get path to tasks file.""" - return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" - - -def validate_description(description): - """Validate task description.""" - # NOTE: Validation logic scattered here - should be in utils (refactor bounty) - if not description: - raise ValueError("Description cannot be empty") - if len(description) > 200: - raise ValueError("Description too long (max 200 chars)") - return description.strip() +from utils.paths import get_tasks_file +from utils.validation import validate_description def add_task(description): diff --git a/commands/done.py b/commands/done.py index c9dfd42..995f058 100644 --- a/commands/done.py +++ b/commands/done.py @@ -1,20 +1,8 @@ """Mark task done command.""" import json -from pathlib import Path - - -def get_tasks_file(): - """Get path to tasks file.""" - return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" - - -def validate_task_id(tasks, task_id): - """Validate task ID exists.""" - # NOTE: Validation logic scattered here - should be in utils (refactor bounty) - if task_id < 1 or task_id > len(tasks): - raise ValueError(f"Invalid task ID: {task_id}") - return task_id +from utils.paths import get_tasks_file +from utils.validation import validate_task_id def mark_done(task_id): diff --git a/commands/list.py b/commands/list.py index 714315d..a4b0b87 100644 --- a/commands/list.py +++ b/commands/list.py @@ -1,27 +1,13 @@ """List tasks command.""" import json -from pathlib import Path - - -def get_tasks_file(): - """Get path to tasks file.""" - return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" - - -def validate_task_file(): - """Validate tasks file exists.""" - # NOTE: Validation logic scattered here - should be in utils (refactor bounty) - tasks_file = get_tasks_file() - if not tasks_file.exists(): - return [] - return tasks_file +from utils.paths import get_tasks_file +from utils.validation import validate_task_file def list_tasks(): """List all tasks.""" - # NOTE: No --json flag support yet (feature bounty) - tasks_file = validate_task_file() + tasks_file = validate_task_file(get_tasks_file()) if not tasks_file: print("No tasks yet!") return diff --git a/task.py b/task.py index 53cc8ed..65d7906 100644 --- a/task.py +++ b/task.py @@ -11,9 +11,14 @@ def load_config(): - """Load configuration from file.""" + """Load configuration from file. Creates default config if missing.""" config_path = Path.home() / ".config" / "task-cli" / "config.yaml" - # NOTE: This will crash if config doesn't exist - known bug for bounty testing + if not config_path.exists(): + # Gracefully create default config instead of crashing + config_path.parent.mkdir(parents=True, exist_ok=True) + default_config = "# task-cli configuration\nversion: 1\n" + config_path.write_text(default_config) + return default_config with open(config_path) as f: return f.read() diff --git a/test_task.py b/test_task.py index ba98e43..a9be8d8 100644 --- a/test_task.py +++ b/test_task.py @@ -1,10 +1,12 @@ """Basic tests for task CLI.""" -import json import pytest +import tempfile from pathlib import Path -from commands.add import add_task, validate_description -from commands.done import validate_task_id +from unittest.mock import patch + +from utils.validation import validate_description, validate_task_id +from task import load_config def test_validate_description(): @@ -28,3 +30,23 @@ def test_validate_task_id(): with pytest.raises(ValueError): validate_task_id(tasks, 99) + + +def test_load_config_missing_file(tmp_path): + """Test load_config creates default config when file is missing.""" + fake_config = tmp_path / ".config" / "task-cli" / "config.yaml" + with patch("task.Path.home", return_value=tmp_path): + result = load_config() + assert "version: 1" in result + assert fake_config.exists() + + +def test_load_config_existing_file(tmp_path): + """Test load_config reads existing config.""" + config_dir = tmp_path / ".config" / "task-cli" + config_dir.mkdir(parents=True) + config_file = config_dir / "config.yaml" + config_file.write_text("version: 2\ncustom: true\n") + with patch("task.Path.home", return_value=tmp_path): + result = load_config() + assert "version: 2" in result diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/paths.py b/utils/paths.py new file mode 100644 index 0000000..4ce0eef --- /dev/null +++ b/utils/paths.py @@ -0,0 +1,8 @@ +"""Shared path helpers.""" + +from pathlib import Path + + +def get_tasks_file(): + """Get path to tasks file.""" + return Path.home() / ".local" / "share" / "task-cli" / "tasks.json" diff --git a/utils/validation.py b/utils/validation.py new file mode 100644 index 0000000..80854ef --- /dev/null +++ b/utils/validation.py @@ -0,0 +1,24 @@ +"""Shared validation helpers.""" + + +def validate_description(description): + """Validate task description.""" + if not description: + raise ValueError("Description cannot be empty") + if len(description) > 200: + raise ValueError("Description too long (max 200 chars)") + return description.strip() + + +def validate_task_file(tasks_file): + """Validate tasks file exists.""" + if not tasks_file.exists(): + return [] + return tasks_file + + +def validate_task_id(tasks, task_id): + """Validate task ID exists.""" + if task_id < 1 or task_id > len(tasks): + raise ValueError(f"Invalid task ID: {task_id}") + return task_id