khoj/tests/test_orgnode.py

429 lines
14 KiB
Python
Raw Normal View History

2022-06-17 18:13:11 +02:00
import datetime
from khoj.processor.content.org_mode import orgnode
2022-06-17 18:13:11 +02:00
# Test
# ----------------------------------------------------------------------------------------------------
def test_parse_entry_with_no_headings(tmp_path):
"Test parsing of entry with minimal fields"
# Arrange
entry = f"""Body Line 1"""
orgfile = create_file(tmp_path, entry)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 1
assert entries[0].heading == f"{orgfile}"
assert entries[0].tags == list()
assert entries[0].body == "Body Line 1"
assert entries[0].priority == ""
assert entries[0].Property("ID") == ""
assert entries[0].closed == ""
assert entries[0].scheduled == ""
assert entries[0].deadline == ""
2022-06-17 18:13:11 +02:00
# ----------------------------------------------------------------------------------------------------
def test_parse_minimal_entry(tmp_path):
"Test parsing of entry with minimal fields"
# Arrange
entry = f"""
2022-06-17 18:13:11 +02:00
* Heading
Body Line 1"""
2022-06-17 18:13:11 +02:00
orgfile = create_file(tmp_path, entry)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
2022-06-17 18:13:11 +02:00
# Assert
assert len(entries) == 1
assert entries[0].heading == "Heading"
assert entries[0].tags == list()
assert entries[0].body == "Body Line 1\n\n"
assert entries[0].priority == ""
2022-06-17 18:13:11 +02:00
assert entries[0].Property("ID") == ""
assert entries[0].closed == ""
assert entries[0].scheduled == ""
assert entries[0].deadline == ""
2022-06-17 18:13:11 +02:00
# ----------------------------------------------------------------------------------------------------
def test_parse_complete_entry(tmp_path):
"Test parsing of entry with all important fields"
# Arrange
entry = f"""
*** DONE [#A] Heading :Tag1:TAG2:tag3:
2022-06-17 18:13:11 +02:00
CLOSED: [1984-04-01 Sun 12:00] SCHEDULED: <1984-04-01 Sun 09:00> DEADLINE: <1984-04-01 Sun>
:PROPERTIES:
:ID: 123-456-789-4234-1231
:END:
:LOGBOOK:
CLOCK: [1984-04-01 Sun 09:00]--[1984-04-01 Sun 12:00] => 3:00
- Clocked Log 1
:END:
Body Line 1
Body Line 2"""
2022-06-17 18:13:11 +02:00
orgfile = create_file(tmp_path, entry)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
2022-06-17 18:13:11 +02:00
# Assert
assert len(entries) == 1
assert entries[0].heading == "Heading"
assert entries[0].todo == "DONE"
assert entries[0].tags == ["Tag1", "TAG2", "tag3"]
assert entries[0].body == "- Clocked Log 1\n\nBody Line 1\n\nBody Line 2\n\n"
assert entries[0].priority == "A"
2022-06-17 18:13:11 +02:00
assert entries[0].Property("ID") == "id:123-456-789-4234-1231"
assert entries[0].closed == datetime.date(1984, 4, 1)
assert entries[0].scheduled == datetime.date(1984, 4, 1)
assert entries[0].deadline == datetime.date(1984, 4, 1)
assert entries[0].logbook == [(datetime.datetime(1984, 4, 1, 9, 0, 0), datetime.datetime(1984, 4, 1, 12, 0, 0))]
2022-06-17 18:13:11 +02:00
# ----------------------------------------------------------------------------------------------------
def test_render_entry_with_property_drawer_and_empty_body(tmp_path):
"Render heading entry with property drawer"
# Arrange
entry_to_render = f"""
*** [#A] Heading1 :tag1:
:PROPERTIES:
:ID: 111-111-111-1111-1111
:END:
\t\r \n
"""
orgfile = create_file(tmp_path, entry_to_render)
expected_entry = f"""*** [#A] Heading1 :tag1:
:PROPERTIES:
:LINE: file:{orgfile}::2
:ID: id:111-111-111-1111-1111
:SOURCE: [[file:{orgfile}::*Heading1]]
:END:
"""
# Act
parsed_entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert f"{parsed_entries[0]}" == expected_entry
2022-06-17 18:13:11 +02:00
# ----------------------------------------------------------------------------------------------------
def test_all_links_to_entry_rendered(tmp_path):
"Ensure all links to entry rendered in property drawer from entry"
# Arrange
entry = f"""
2022-06-17 18:13:11 +02:00
*** [#A] Heading :tag1:
:PROPERTIES:
:ID: 123-456-789-4234-1231
:END:
Body Line 1
*** Heading2
Body Line 2
"""
2022-06-17 18:13:11 +02:00
orgfile = create_file(tmp_path, entry)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
2022-06-17 18:13:11 +02:00
# Assert
# SOURCE link rendered with Heading
assert f":SOURCE: [[file:{orgfile}::*{entries[0].heading}]]" in f"{entries[0]}"
2022-06-17 18:13:11 +02:00
# ID link rendered with ID
assert f":ID: id:123-456-789-4234-1231" in f"{entries[0]}"
2022-06-17 18:13:11 +02:00
# LINE link rendered with line number
assert f":LINE: file:{orgfile}::2" in f"{entries[0]}"
2022-06-17 18:13:11 +02:00
# ----------------------------------------------------------------------------------------------------
def test_source_link_to_entry_escaped_for_rendering(tmp_path):
"Test SOURCE link renders with square brackets in filename, heading escaped for org-mode rendering"
# Arrange
entry = f"""
2022-06-17 18:13:11 +02:00
*** [#A] Heading[1] :tag1:
:PROPERTIES:
:ID: 123-456-789-4234-1231
:END:
Body Line 1"""
2022-06-17 18:13:11 +02:00
orgfile = create_file(tmp_path, entry, filename="test[1].org")
# Act
entries = orgnode.makelist_with_filepath(orgfile)
2022-06-17 18:13:11 +02:00
# Assert
assert len(entries) == 1
# parsed heading from entry
assert entries[0].heading == "Heading[1]"
# track ancestors of entry
assert entries[0].ancestors == [f"{orgfile}"]
2022-06-17 18:13:11 +02:00
# ensure SOURCE link has square brackets in filename, heading escaped in rendered entries
escaped_orgfile = f"{orgfile}".replace("[1]", "\\[1\\]")
assert f":SOURCE: [[file:{escaped_orgfile}::*Heading\\[1\\]" in f"{entries[0]}"
2022-06-17 18:13:11 +02:00
# ----------------------------------------------------------------------------------------------------
def test_parse_multiple_entries(tmp_path):
"Test parsing of multiple entries"
# Arrange
content = f"""
*** FAILED [#A] Heading1 :tag1:
2022-06-17 18:13:11 +02:00
CLOSED: [1984-04-01 Sun 12:00] SCHEDULED: <1984-04-01 Sun 09:00> DEADLINE: <1984-04-01 Sun>
:PROPERTIES:
:ID: 123-456-789-4234-0001
:END:
:LOGBOOK:
CLOCK: [1984-04-01 Sun 09:00]--[1984-04-01 Sun 12:00] => 3:00
- Clocked Log 1
:END:
Body 1
*** CANCELLED [#A] Heading2 :tag2:
2022-06-17 18:13:11 +02:00
CLOSED: [1984-04-02 Sun 12:00] SCHEDULED: <1984-04-02 Sun 09:00> DEADLINE: <1984-04-02 Sun>
:PROPERTIES:
:ID: 123-456-789-4234-0002
:END:
:LOGBOOK:
CLOCK: [1984-04-02 Mon 09:00]--[1984-04-02 Mon 12:00] => 3:00
2022-06-17 18:13:11 +02:00
- Clocked Log 2
:END:
Body 2
"""
2022-06-17 18:13:11 +02:00
orgfile = create_file(tmp_path, content)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
2022-06-17 18:13:11 +02:00
# Assert
assert len(entries) == 2
for index, entry in enumerate(entries):
assert entry.heading == f"Heading{index+1}"
assert entry.todo == "FAILED" if index == 0 else "CANCELLED"
assert entry.tags == [f"tag{index+1}"]
assert entry.body == f"- Clocked Log {index+1}\n\nBody {index+1}\n\n"
assert entry.priority == "A"
2022-06-17 18:13:11 +02:00
assert entry.Property("ID") == f"id:123-456-789-4234-000{index+1}"
assert entry.closed == datetime.date(1984, 4, index + 1)
assert entry.scheduled == datetime.date(1984, 4, index + 1)
assert entry.deadline == datetime.date(1984, 4, index + 1)
assert entry.logbook == [
(datetime.datetime(1984, 4, index + 1, 9, 0, 0), datetime.datetime(1984, 4, index + 1, 12, 0, 0))
]
2022-06-17 18:13:11 +02:00
# ----------------------------------------------------------------------------------------------------
def test_parse_entry_with_empty_title(tmp_path):
"Test parsing of entry with minimal fields"
# Arrange
entry = f"""#+TITLE:
Body Line 1"""
orgfile = create_file(tmp_path, entry)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 1
assert entries[0].heading == f"{orgfile}"
assert entries[0].tags == list()
assert entries[0].body == "Body Line 1"
assert entries[0].priority == ""
assert entries[0].Property("ID") == ""
assert entries[0].closed == ""
assert entries[0].scheduled == ""
assert entries[0].deadline == ""
# ----------------------------------------------------------------------------------------------------
def test_parse_entry_with_title_and_no_headings(tmp_path):
"Test parsing of entry with minimal fields"
# Arrange
entry = f"""#+TITLE: test
Body Line 1"""
orgfile = create_file(tmp_path, entry)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 1
assert entries[0].heading == "test"
assert entries[0].tags == list()
assert entries[0].body == "Body Line 1"
assert entries[0].priority == ""
assert entries[0].Property("ID") == ""
assert entries[0].closed == ""
assert entries[0].scheduled == ""
assert entries[0].deadline == ""
assert entries[0].ancestors == ["test"]
# ----------------------------------------------------------------------------------------------------
def test_parse_entry_with_multiple_titles_and_no_headings(tmp_path):
"Test parsing of entry with minimal fields"
# Arrange
entry = f"""#+TITLE: title1
Body Line 1
#+TITLE: title2 """
orgfile = create_file(tmp_path, entry)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 1
assert entries[0].heading == "title1 title2"
assert entries[0].tags == list()
assert entries[0].body == "Body Line 1\n"
assert entries[0].priority == ""
assert entries[0].Property("ID") == ""
assert entries[0].closed == ""
assert entries[0].scheduled == ""
assert entries[0].deadline == ""
assert entries[0].ancestors == ["title1 title2"]
# ----------------------------------------------------------------------------------------------------
def test_parse_org_with_intro_text_before_heading(tmp_path):
"Test parsing of org file with intro text before heading"
# Arrange
body = f"""#+TITLE: Title
intro body
* Entry Heading
entry body
"""
orgfile = create_file(tmp_path, body)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 2
assert entries[0].heading == "Title"
assert entries[0].body == "intro body\n"
assert entries[0].ancestors == ["Title"]
assert entries[1].heading == "Entry Heading"
assert entries[1].body == "entry body\n\n"
assert entries[1].ancestors == ["Title"]
# ----------------------------------------------------------------------------------------------------
def test_parse_org_with_intro_text_multiple_titles_and_heading(tmp_path):
"Test parsing of org file with intro text, multiple titles and heading entry"
# Arrange
body = f"""#+TITLE: Title1
intro body
* Entry Heading
entry body
#+TITLE: Title2 """
orgfile = create_file(tmp_path, body)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 2
assert entries[0].heading == "Title1 Title2"
assert entries[0].body == "intro body\n"
assert entries[0].ancestors == ["Title1 Title2"]
assert entries[1].heading == "Entry Heading"
assert entries[1].body == "entry body\n\n"
assert entries[0].ancestors == ["Title1 Title2"]
# ----------------------------------------------------------------------------------------------------
def test_parse_org_with_single_ancestor_heading(tmp_path):
"Parse org entries with parent headings context"
# Arrange
body = f"""
* Heading 1
body 1
** Sub Heading 1
"""
orgfile = create_file(tmp_path, body)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 2
assert entries[0].heading == "Heading 1"
assert entries[0].ancestors == [f"{orgfile}"]
assert entries[1].heading == "Sub Heading 1"
assert entries[1].ancestors == [f"{orgfile}", "Heading 1"]
# ----------------------------------------------------------------------------------------------------
def test_parse_org_with_multiple_ancestor_headings(tmp_path):
"Parse org entries with parent headings context"
# Arrange
body = f"""
* Heading 1
body 1
** Sub Heading 1
*** Sub Sub Heading 1
sub sub body 1
"""
orgfile = create_file(tmp_path, body)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 3
assert entries[0].heading == "Heading 1"
assert entries[0].ancestors == [f"{orgfile}"]
assert entries[1].heading == "Sub Heading 1"
assert entries[1].ancestors == [f"{orgfile}", "Heading 1"]
assert entries[2].heading == "Sub Sub Heading 1"
assert entries[2].ancestors == [f"{orgfile}", "Heading 1", "Sub Heading 1"]
# ----------------------------------------------------------------------------------------------------
def test_parse_org_with_multiple_ancestor_headings_of_siblings(tmp_path):
"Parse org entries with parent headings context"
# Arrange
body = f"""
* Heading 1
body 1
** Sub Heading 1
*** Sub Sub Heading 1
sub sub body 1
*** Sub Sub Heading 2
** Sub Heading 2
*** Sub Sub Heading 3
"""
orgfile = create_file(tmp_path, body)
# Act
entries = orgnode.makelist_with_filepath(orgfile)
# Assert
assert len(entries) == 6
assert entries[0].heading == "Heading 1"
assert entries[0].ancestors == [f"{orgfile}"]
assert entries[1].heading == "Sub Heading 1"
assert entries[1].ancestors == [f"{orgfile}", "Heading 1"]
assert entries[2].heading == "Sub Sub Heading 1"
assert entries[2].ancestors == [f"{orgfile}", "Heading 1", "Sub Heading 1"]
assert entries[3].heading == "Sub Sub Heading 2"
assert entries[3].ancestors == [f"{orgfile}", "Heading 1", "Sub Heading 1"]
assert entries[4].heading == "Sub Heading 2"
assert entries[4].ancestors == [f"{orgfile}", "Heading 1"]
assert entries[5].heading == "Sub Sub Heading 3"
assert entries[5].ancestors == [f"{orgfile}", "Heading 1", "Sub Heading 2"]
2022-06-17 18:13:11 +02:00
# Helper Functions
def create_file(tmp_path, entry, filename="test.org"):
org_file = tmp_path / f"notes/{filename}"
org_file.parent.mkdir()
org_file.touch()
org_file.write_text(entry)
return org_file