Compare commits

...

3 commits

Author SHA1 Message Date
f00d230fd3
test: Add test for parsing circular env vars
Some checks failed
website / build (push) Has been cancelled
website / deploy (push) Has been cancelled
2025-12-08 17:57:44 +01:00
0820b686e5
fix: Add edge case tests and improve write permissions error 2025-12-08 17:57:44 +01:00
a2c5cf281b
fix: Remove receiving output from io.err method
The method never returns anything so we should not receive output.
2025-12-08 17:57:40 +01:00
3 changed files with 84 additions and 3 deletions

View file

@ -7,6 +7,7 @@ import pytest
from topen import TConf, build_config from topen import TConf, build_config
class TestTConf: class TestTConf:
def test_paths_are_expanded(self): def test_paths_are_expanded(self):
cfg = TConf.from_dict( cfg = TConf.from_dict(
@ -62,6 +63,17 @@ class TestBuildConfigPrecedence:
cfg = build_config() cfg = build_config()
assert cfg.notes_ext == "from-env" assert cfg.notes_ext == "from-env"
def test_circular_env_vars(self, isolate_env, monkeypatch, fake_id):
"""Test environment variables with circular references."""
for k, v in {
"TOPEN_NOTES_DIR": "$TOPEN_NOTES_DIR/subdir",
"EDITOR": "${EDITOR}_backup",
}.items():
monkeypatch.setenv(k, v)
cfg = build_config()
assert cfg.notes_dir == Path("$TOPEN_NOTES_DIR/subdir/subdir")
assert cfg.notes_editor == "nano"
def test_cli_overrides_env(self, fake_rc, monkeypatch, isolate_env): def test_cli_overrides_env(self, fake_rc, monkeypatch, isolate_env):
fake_rc.write_text("notes.ext=from-rc\n") fake_rc.write_text("notes.ext=from-rc\n")
monkeypatch.setenv("TOPEN_NOTES_EXT", "from-env") monkeypatch.setenv("TOPEN_NOTES_EXT", "from-env")

View file

@ -0,0 +1,64 @@
import configparser
import os
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
from topen import _ensure_parent_dir, get_notes_file, get_task, parse_env, parse_rc
class TestFSEdgeCases:
"""Test edge cases for TaskWarrior integration and file system operations."""
def test_nonexistent_task_id(self, tmp_path):
"""Test raised error for non-existent task IDs."""
with patch("tasklib.TaskWarrior") as mock_tw:
mock_tw.return_value.tasks.get.side_effect = [
Exception("Task not found"),
]
with pytest.raises(
Exception,
match="Task matching query does not exist. Lookup parameters were {'uuid': '999999'}",
):
get_task("999999", tmp_path)
def test_read_only_notes_directory(self, tmp_path):
"""Test raised error when notes directory is read-only."""
notes_dir = tmp_path / "read_only_notes"
notes_dir.mkdir()
# Make directory read-only
os.chmod(notes_dir, 0o444)
fpath = notes_dir / "subdir_cant_be_written" / "uuid.md"
with pytest.raises(PermissionError):
_ensure_parent_dir(fpath)
def test_symlink_notes_directory(self, tmp_path):
"""Test behavior with symlinked notes directory,
reading the linked dir instead of the real dir. """
real_dir = tmp_path / "real_notes"
real_dir.mkdir()
link_dir = tmp_path / "linked_notes"
link_dir.symlink_to(real_dir)
fpath = get_notes_file("test-uuid", link_dir, "md")
assert fpath.parent == link_dir
def test_empty_taskrc_file(self, tmp_path):
"""Test functional handling of empty taskrc file."""
fake_rc: Path = tmp_path / "empty.taskrc"
fake_rc.touch()
assert parse_rc(fake_rc) == {}
def test_taskrc_with_invalid_syntax(self, tmp_path):
"""Test functional handling of taskrc with invalid syntax."""
invalid_rc: Path = tmp_path / "invalid.taskrc"
invalid_rc.write_text(
"invalid line == [MMMM] with too many = = equals sign\ndata.location = valid_value\n"
)
assert parse_rc(invalid_rc)

View file

@ -51,19 +51,24 @@ def main(cfg: "TConf | None" = None, io: "_IO | None" = None) -> int:
io = _IO(quiet=cfg.notes_quiet) io = _IO(quiet=cfg.notes_quiet)
if not cfg.task_id: if not cfg.task_id:
_ = io.err("Please provide task ID as argument.\n") io.err("Please provide task ID as argument.\n")
return 1 return 1
try: try:
task = get_task(id=cfg.task_id, data_location=cfg.task_data) task = get_task(id=cfg.task_id, data_location=cfg.task_data)
uuid = cast(str, task["uuid"]) uuid = cast(str, task["uuid"])
except Task.DoesNotExist: except Task.DoesNotExist:
_ = io.err(f"Could not find task for ID: {cfg.task_id}.\n") io.err(f"Could not find task for ID: {cfg.task_id}.\n")
return 1 return 1
fpath = get_notes_file(uuid, notes_dir=cfg.notes_dir, notes_ext=cfg.notes_ext) fpath = get_notes_file(uuid, notes_dir=cfg.notes_dir, notes_ext=cfg.notes_ext)
try:
_ensure_parent_dir(fpath) _ensure_parent_dir(fpath)
except PermissionError:
io.err(f"Could not write required directories for path: {fpath}.\n")
return 1
io.out(f"Editing note: {fpath}") io.out(f"Editing note: {fpath}")
open_editor(fpath, editor=cfg.notes_editor, io=io) open_editor(fpath, editor=cfg.notes_editor, io=io)