mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix tests for markdown
This commit is contained in:
@@ -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"]
|
||||
assert result.tags == ["one", "two", "three"]
|
||||
|
||||
@@ -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
|
||||
assert "owner" not in result.metadata
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
assert len(entity.content.relations) == 0
|
||||
|
||||
Reference in New Issue
Block a user