From 4d331a4ac932b766cdbd715daed975adbbbc9349 Mon Sep 17 00:00:00 2001 From: phernandez Date: Sat, 21 Dec 2024 16:49:30 -0600 Subject: [PATCH] fix tests for markdown --- tests/markdown/test_frontmatter_parser.py | 36 +++++++++++----------- tests/markdown/test_metadata_parser.py | 26 +++++++--------- tests/markdown/test_parse_observation.py | 17 ++++++----- tests/markdown/test_parse_relation.py | 15 ++++----- tests/markdown/test_parser_edge_cases.py | 37 ++++++++++++----------- 5 files changed, 66 insertions(+), 65 deletions(-) diff --git a/tests/markdown/test_frontmatter_parser.py b/tests/markdown/test_frontmatter_parser.py index 8c20ccfe..3703434a 100644 --- a/tests/markdown/test_frontmatter_parser.py +++ b/tests/markdown/test_frontmatter_parser.py @@ -1,12 +1,13 @@ """Tests for frontmatter parsing.""" -from datetime import datetime +from datetime import datetime, UTC from textwrap import dedent import pytest +from basic_memory.markdown import EntityFrontmatter from basic_memory.markdown.exceptions import ParseError -from basic_memory.markdown.frontmatter_parser import FrontmatterParser + def test_parse_frontmatter(): """Test parsing basic frontmatter.""" @@ -15,18 +16,18 @@ def test_parse_frontmatter(): id: test/basic created: 2024-12-21T14:00:00Z modified: 2024-12-21T14:00:00Z - tags: [test, base] + tags: test, base """) - parser = FrontmatterParser() - result = parser.parse(text) + result = EntityFrontmatter.from_text(text) assert result.type == "component" assert result.id == "test/basic" - assert result.created == datetime(2024, 12, 21, 14, 0) - assert result.modified == datetime(2024, 12, 21, 14, 0) + assert result.created == datetime(2024, 12, 21, 14, 0, tzinfo=UTC) + assert result.modified == datetime(2024, 12, 21, 14, 0, tzinfo=UTC) assert result.tags == ["test", "base"] + def test_parse_frontmatter_comma_tags(): """Test parsing frontmatter with comma-separated tags.""" text = dedent(""" @@ -37,11 +38,11 @@ def test_parse_frontmatter_comma_tags(): tags: first, second, third """) - parser = FrontmatterParser() - result = parser.parse(text) + result = EntityFrontmatter.from_text(text) assert result.tags == ["first", "second", "third"] + def test_parse_frontmatter_missing_required(): """Test error on missing required fields.""" text = dedent(""" @@ -52,9 +53,9 @@ def test_parse_frontmatter_missing_required(): tags: [] """) - parser = FrontmatterParser() with pytest.raises(ParseError): - parser.parse(text) + EntityFrontmatter.from_text(text) + def test_parse_frontmatter_invalid_date(): """Test error on invalid date format.""" @@ -63,12 +64,12 @@ def test_parse_frontmatter_invalid_date(): id: test/dates created: not-a-date modified: 2024-12-21T14:00:00Z - tags: [] + tags: """) - parser = FrontmatterParser() with pytest.raises(ParseError): - parser.parse(text) + EntityFrontmatter.from_text(text) + def test_parse_frontmatter_whitespace(): """Test handling of various whitespace in frontmatter.""" @@ -77,12 +78,11 @@ def test_parse_frontmatter_whitespace(): id: test/whitespace created: 2024-12-21T14:00:00Z modified: 2024-12-21T14:00:00Z - tags: [ one, two , three ] + tags: one, two , three """) - parser = FrontmatterParser() - result = parser.parse(text) + result = EntityFrontmatter.from_text(text) assert result.type == "component" assert result.id == "test/whitespace" - assert result.tags == ["one", "two", "three"] \ No newline at end of file + assert result.tags == ["one", "two", "three"] diff --git a/tests/markdown/test_metadata_parser.py b/tests/markdown/test_metadata_parser.py index c591917e..1bd9e9f1 100644 --- a/tests/markdown/test_metadata_parser.py +++ b/tests/markdown/test_metadata_parser.py @@ -2,10 +2,8 @@ from textwrap import dedent -import pytest +from basic_memory.markdown import EntityMetadata -from basic_memory.markdown.exceptions import ParseError -from basic_memory.markdown.metadata_parser import MetadataParser def test_parse_metadata(): """Test parsing basic metadata.""" @@ -15,22 +13,21 @@ def test_parse_metadata(): status: active """) - parser = MetadataParser() - result = parser.parse(text) + result = EntityMetadata.from_text(text) assert result.metadata["owner"] == "team-auth" assert result.metadata["priority"] == "high" assert result.metadata["status"] == "active" + def test_parse_metadata_empty(): """Test parsing empty metadata.""" text = "" - parser = MetadataParser() - result = parser.parse(text) - + result = EntityMetadata.from_text(text) assert result.metadata == {} + def test_parse_metadata_whitespace(): """Test handling of various whitespace in metadata.""" text = dedent(""" @@ -39,13 +36,13 @@ def test_parse_metadata_whitespace(): status: active """) - parser = MetadataParser() - result = parser.parse(text) + result = EntityMetadata.from_text(text) assert result.metadata["owner"] == "team-auth" assert result.metadata["priority"] == "high" assert result.metadata["status"] == "active" + def test_parse_metadata_multiline_values(): """Test handling of multiline metadata values.""" text = dedent(""" @@ -56,13 +53,13 @@ def test_parse_metadata_multiline_values(): status: active """) - parser = MetadataParser() - result = parser.parse(text) + result = EntityMetadata.from_text(text) assert result.metadata["owner"] == "team-auth" assert result.metadata["status"] == "active" assert len(result.metadata["description"].splitlines()) == 3 + def test_parse_metadata_invalid(): """Test handling of invalid metadata format.""" text = dedent(""" @@ -70,8 +67,7 @@ def test_parse_metadata_invalid(): priority: high """) - parser = MetadataParser() - result = parser.parse(text) + result = EntityMetadata.from_text(text) assert "priority" in result.metadata - assert "owner" not in result.metadata \ No newline at end of file + assert "owner" not in result.metadata diff --git a/tests/markdown/test_parse_observation.py b/tests/markdown/test_parse_observation.py index a83e71e8..a3a4de4b 100644 --- a/tests/markdown/test_parse_observation.py +++ b/tests/markdown/test_parse_observation.py @@ -2,6 +2,7 @@ import pytest +from basic_memory.markdown import Observation from basic_memory.markdown.parser import EntityParser, ParseError @@ -9,7 +10,7 @@ def test_parse_observation_basic(): """Test basic observation parsing with category and tags.""" parser = EntityParser() - obs = parser._parse_observation("- [design] Core feature #important #mvp") + obs = Observation.from_line("- [design] Core feature #important #mvp") assert obs is not None assert obs.category == "design" @@ -22,7 +23,7 @@ def test_parse_observation_with_context(): """Test observation parsing with context in parentheses.""" parser = EntityParser() - obs = parser._parse_observation( + obs = Observation.from_line( "- [feature] Authentication system #security #auth (Required for MVP)" ) assert obs is not None @@ -37,25 +38,25 @@ def test_parse_observation_edge_cases(): parser = EntityParser() # Multiple word tags - obs = parser._parse_observation("- [tech] Database #high-priority #needs-review") + obs = Observation.from_line("- [tech] Database #high-priority #needs-review") assert obs is not None assert set(obs.tags) == {"high-priority", "needs-review"} # Multiple word category - obs = parser._parse_observation("- [user experience] Design #ux") + obs = Observation.from_line("- [user experience] Design #ux") assert obs is not None assert obs.category == "user experience" # Parentheses in content shouldn't be treated as context - obs = parser._parse_observation("- [code] Function (x) returns y #function") + obs = Observation.from_line("- [code] Function (x) returns y #function") assert obs is not None assert obs.content == "Function (x) returns y" assert obs.context is None # Multiple hashtags together - obs = parser._parse_observation("- [test] Feature #important#urgent#now") + obs = Observation.from_line("- [test] Feature #important#urgent#now") assert obs is not None assert set(obs.tags) == {"important", "urgent", "now"} @@ -66,8 +67,8 @@ def test_parse_observation_errors(): # Missing category brackets with pytest.raises(ParseError, match="missing category"): - parser._parse_observation("- Design without brackets #test") + Observation.from_line("- Design without brackets #test") # Unclosed category with pytest.raises(ParseError, match="unclosed category"): - parser._parse_observation("- [design Core feature #test") + Observation.from_line("- [design Core feature #test") diff --git a/tests/markdown/test_parse_relation.py b/tests/markdown/test_parse_relation.py index f59c266c..3082a602 100644 --- a/tests/markdown/test_parse_relation.py +++ b/tests/markdown/test_parse_relation.py @@ -2,6 +2,7 @@ import pytest +from basic_memory.markdown import Relation from basic_memory.markdown.parser import EntityParser, ParseError @@ -9,7 +10,7 @@ def test_parse_relation_basic(): """Test basic relation parsing.""" parser = EntityParser() - rel = parser._parse_relation("- implements [[Auth Service]]") + rel = Relation.from_line("- implements [[Auth Service]]") assert rel is not None assert rel.type == "implements" assert rel.target == "Auth Service" @@ -20,7 +21,7 @@ def test_parse_relation_with_context(): """Test relation parsing with context.""" parser = EntityParser() - rel = parser._parse_relation("- depends_on [[Database]] (Required for persistence)") + rel = Relation.from_line("- depends_on [[Database]] (Required for persistence)") assert rel is not None assert rel.type == "depends_on" assert rel.target == "Database" @@ -32,17 +33,17 @@ def test_parse_relation_edge_cases(): parser = EntityParser() # Multiple word type - rel = parser._parse_relation("- is used by [[Client App]] (Primary consumer)") + rel = Relation.from_line("- is used by [[Client App]] (Primary consumer)") assert rel is not None assert rel.type == "is used by" # Brackets in context - rel = parser._parse_relation("- implements [[API]] (Follows [OpenAPI] spec)") + rel = Relation.from_line("- implements [[API]] (Follows [OpenAPI] spec)") assert rel is not None assert rel.context == "Follows [OpenAPI] spec" # Extra spaces - rel = parser._parse_relation("- specifies [[Format]] (Documentation)") + rel = Relation.from_line("- specifies [[Format]] (Documentation)") assert rel is not None assert rel.type == "specifies" assert rel.target == "Format" @@ -54,8 +55,8 @@ def test_parse_relation_errors(): # Missing target brackets with pytest.raises(ParseError, match="missing \\[\\["): - parser._parse_relation("- implements Auth Service") + Relation.from_line("- implements Auth Service") # Unclosed target with pytest.raises(ParseError, match="missing ]]"): - parser._parse_relation("- implements [[Auth Service") + Relation.from_line("- implements [[Auth Service") diff --git a/tests/markdown/test_parser_edge_cases.py b/tests/markdown/test_parser_edge_cases.py index 348e75a8..c4936be7 100644 --- a/tests/markdown/test_parser_edge_cases.py +++ b/tests/markdown/test_parser_edge_cases.py @@ -1,6 +1,5 @@ """Tests for edge cases in markdown parsing.""" -from datetime import datetime from textwrap import dedent import pytest @@ -8,15 +7,12 @@ import pytest from basic_memory.markdown import ( EntityParser, ParseError, - Entity, - EntityFrontmatter, - EntityContent, - EntityMetadata, ) + def test_unicode_content(tmp_path): """Test handling of Unicode content including emoji and non-Latin scripts.""" - content = dedent(''' + content = dedent(""" --- type: test id: test/unicode @@ -41,10 +37,10 @@ def test_unicode_content(tmp_path): category: test status: active --- - ''') + """) test_file = tmp_path / "unicode.md" - test_file.write_text(content, encoding='utf-8') + test_file.write_text(content, encoding="utf-8") parser = EntityParser() entity = parser.parse_file(test_file) @@ -54,12 +50,13 @@ def test_unicode_content(tmp_path): assert "👍" in entity.content.observations[0].content assert "测试" in entity.content.observations[1].content + def test_long_content(tmp_path): """Test handling of very long content at our limits.""" # Create a long observation right at our length limit long_obs = "x" * 995 + " #tag" # 1000 chars with tag - content = dedent(f''' + content = dedent(f""" --- type: test id: test/long @@ -78,7 +75,7 @@ def test_long_content(tmp_path): ## Relations - related_to [[{"Very long entity name " * 10}]] (Long context test) - ''') + """) test_file = tmp_path / "long.md" test_file.write_text(content) @@ -90,6 +87,7 @@ def test_long_content(tmp_path): assert len(entity.content.observations[0].content) == 995 assert len(entity.content.description) > 1000 + def test_missing_sections(tmp_path): """Test handling of files missing required sections.""" content = dedent(""" @@ -104,9 +102,10 @@ def test_missing_sections(tmp_path): with pytest.raises(ParseError): parser.parse_file(test_file) + def test_nested_structures(tmp_path): """Test handling of nested markdown structures.""" - content = dedent(''' + content = dedent(""" --- type: test id: test/nested @@ -126,7 +125,7 @@ def test_nested_structures(tmp_path): - contains [[Sub Entity]] - and [[Another Entity]] - also [[Third Entity]] - ''') + """) test_file = tmp_path / "nested.md" test_file.write_text(content) @@ -138,21 +137,25 @@ def test_nested_structures(tmp_path): assert len(entity.content.observations) == 1 assert len(entity.content.relations) == 1 + def test_mixed_newlines(tmp_path): """Test handling of different newline styles (\n, \r\n, \r).""" - content = "---\\ntype: test\\r\\nid: test/newlines\\ncreated: 2024-12-21T14:00:00Z\\rmodified: 2024-12-21T14:00:00Z\\ntags: [test]\\n---\\n\\r\\n# Test\\r\\n## Observations\\n- [test] Line 1\\r- [test] Line 2\\n".replace('\\n', '\n').replace('\\r', '\r') + content = "---\\ntype: test\\r\\nid: test/newlines\\ncreated: 2024-12-21T14:00:00Z\\rmodified: 2024-12-21T14:00:00Z\\ntags: [test]\\n---\\n\\r\\n# Test\\r\\n## Observations\\n- [test] Line 1\\r- [test] Line 2\\n".replace( + "\\n", "\n" + ).replace("\\r", "\r") test_file = tmp_path / "newlines.md" - test_file.write_text(content, encoding='utf-8') + test_file.write_text(content, encoding="utf-8") parser = EntityParser() entity = parser.parse_file(test_file) assert len(entity.content.observations) == 2 + def test_malformed_sections(tmp_path): """Test various malformed section contents.""" - content = dedent(''' + content = dedent(""" --- type: test id: test/malformed @@ -174,7 +177,7 @@ def test_malformed_sections(tmp_path): - missing type [[Entity]] - incomplete [[ - ]] backwards - ''') + """) test_file = tmp_path / "malformed.md" test_file.write_text(content) @@ -184,4 +187,4 @@ def test_malformed_sections(tmp_path): # Should skip invalid entries but not fail completely assert len(entity.content.observations) == 0 - assert len(entity.content.relations) == 0 \ No newline at end of file + assert len(entity.content.relations) == 0