Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ python -m pytest test_task.py

## Configuration

Copy `config.yaml.example` to `~/.config/task-cli/config.yaml` and customize.
A default configuration file is automatically created at `~/.config/task-cli/config.yaml` on first run if one does not already exist. To customize, edit this file or copy `config.yaml.example` as a starting point.
43 changes: 38 additions & 5 deletions task.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,43 @@
from commands.list import list_tasks
from commands.done import mark_done

DEFAULT_CONFIG = """\
# Task CLI configuration
# See config.yaml.example for all options

# Task storage settings
storage:
format: json
max_tasks: 1000

# Display settings
display:
color: true
unicode: true
"""


def get_config_path():
"""Get path to config file."""
return Path.home() / ".config" / "task-cli" / "config.yaml"


def load_config():
"""Load configuration from file."""
config_path = Path.home() / ".config" / "task-cli" / "config.yaml"
# NOTE: This will crash if config doesn't exist - known bug for bounty testing
with open(config_path) as f:
return f.read()
"""Load configuration from file.

If the config file does not exist, creates a default one and returns it.
If the config directory does not exist, creates it first.

Returns:
The configuration file contents as a string.
"""
config_path = get_config_path()

if not config_path.exists():
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text(DEFAULT_CONFIG)

return config_path.read_text()


def main():
Expand All @@ -35,6 +65,9 @@ def main():

args = parser.parse_args()

# Load config gracefully — creates default if missing
config = load_config()

if args.command == "add":
add_task(args.description)
elif args.command == "list":
Expand Down
52 changes: 52 additions & 0 deletions test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import json
import pytest
import subprocess
from pathlib import Path
from commands.add import add_task, validate_description
from commands.done import validate_task_id
from task import load_config, get_config_path, DEFAULT_CONFIG


def test_validate_description():
Expand All @@ -28,3 +30,53 @@ def test_validate_task_id():

with pytest.raises(ValueError):
validate_task_id(tasks, 99)


# ── Config file tests ────────────────────────────────────────────────────


def test_load_config_creates_default_when_missing(tmp_path, monkeypatch):
"""Test that load_config creates a default config when file is missing."""
config_path = tmp_path / "config" / "task-cli" / "config.yaml"
monkeypatch.setattr("task.get_config_path", lambda: config_path)

result = load_config()

assert config_path.exists(), "Config file should be created"
assert result == DEFAULT_CONFIG
assert "storage:" in result
assert "display:" in result


def test_load_config_reads_existing(tmp_path, monkeypatch):
"""Test that load_config reads an existing config without overwriting."""
config_path = tmp_path / "config.yaml"
custom_config = "custom: true\n"
config_path.write_text(custom_config)
monkeypatch.setattr("task.get_config_path", lambda: config_path)

result = load_config()

assert result == custom_config


def test_load_config_creates_parent_dirs(tmp_path, monkeypatch):
"""Test that load_config creates parent directories if needed."""
config_path = tmp_path / "deep" / "nested" / "dir" / "config.yaml"
monkeypatch.setattr("task.get_config_path", lambda: config_path)

load_config()

assert config_path.exists()
assert config_path.parent.is_dir()


def test_cli_no_crash_without_config():
"""Test that CLI doesn't crash when config file is missing."""
result = subprocess.run(
["python3", "task.py", "list"],
capture_output=True, text=True, cwd=str(Path(__file__).parent),
)
# Should not crash with FileNotFoundError
assert result.returncode == 0
assert "FileNotFoundError" not in result.stderr