import pytest from app.models import CaptureRequest from app.org_writer import format_capture, normalize_tags def make_capture(**kwargs) -> CaptureRequest: defaults = dict( id="phone-20260517-143122-a8f2", created_at="2026-05-17T14:31:22-04:00", kind="note", body="hello world", tags=[], device="android", ) defaults.update(kwargs) return CaptureRequest(**defaults) class TestNormalizeTags: def test_lowercase(self): assert normalize_tags(["Home", "ERRANDS"]) == ["home", "errands"] def test_strips_hash(self): assert normalize_tags(["#home"]) == ["home"] def test_replaces_spaces(self): assert normalize_tags(["my tag"]) == ["my_tag"] def test_removes_invalid_chars(self): assert normalize_tags(["tag!"]) == ["tag"] def test_deduplicates(self): assert normalize_tags(["home", "home"]) == ["home"] def test_omits_empty(self): assert normalize_tags(["", " "]) == [] def test_empty_list(self): assert normalize_tags([]) == [] class TestFormatCapture: def test_todo_with_tags(self): c = make_capture(kind="todo", body="buy printer paper", tags=["home", "errands"]) result = format_capture(c) assert result.startswith("* TODO buy printer paper :home:errands:\n") assert ":CREATED:" in result assert ":SOURCE: android" in result assert ":ID: phone-20260517-143122-a8f2" in result def test_todo_no_tags(self): c = make_capture(kind="todo", body="do the thing", tags=[]) result = format_capture(c) assert result.startswith("* TODO do the thing\n") def test_note_single_line(self): c = make_capture(kind="note", body="mobile capture should stay dumb and append-only.", tags=["retcon"]) result = format_capture(c) assert result.startswith("* note :retcon:\n") assert "mobile capture should stay dumb and append-only." in result assert ":PROPERTIES:" in result def test_note_no_tags(self): c = make_capture(kind="note", body="simple note", tags=[]) result = format_capture(c) assert result.startswith("* note\n") assert "simple note" in result def test_note_multiline(self): body = "retcon capture idea\n\nphone should produce records, not edit org files." c = make_capture(kind="note", body=body, tags=["retcon"]) result = format_capture(c) assert result.startswith("* note: retcon capture idea :retcon:\n") assert "retcon capture idea" in result assert "phone should produce records, not edit org files." in result assert ":PROPERTIES:" in result def test_created_timestamp_format(self): c = make_capture(created_at="2026-05-17T14:31:22-04:00") result = format_capture(c) assert ":CREATED: [2026-05-17 sun 14:31]" in result def test_property_drawer_complete(self): c = make_capture() result = format_capture(c) assert ":PROPERTIES:" in result assert ":END:" in result def test_todo_body_below_drawer_absent(self): c = make_capture(kind="todo", body="simple todo") result = format_capture(c) lines = result.strip().split("\n") # heading, drawer block — no extra body section assert lines[0].startswith("* TODO") assert ":END:" in result # body should not appear after :END: end_idx = result.index(":END:") after_end = result[end_idx + len(":END:"):].strip() assert after_end == "" def test_note_body_appears_after_drawer(self): c = make_capture(kind="note", body="my note body") result = format_capture(c) end_idx = result.index(":END:") after_end = result[end_idx + len(":END:"):].strip() assert "my note body" in after_end