Files
wehub-resource-sync 555e282cc4
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:45 +08:00

299 lines
8.1 KiB
Python

"""Tests for parse_export_file.py — mem0 export file parser."""
from __future__ import annotations
import json
import os
import subprocess
import sys
SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "scripts")
# ---------------------------------------------------------------------------
# Direct function tests
# ---------------------------------------------------------------------------
def test_parse_blocks_single_valid_block():
"""parse_blocks returns one record for a single valid block."""
from parse_export_file import parse_blocks
content = """\
---
id: abc123
created_at: 2024-01-15T10:00:00Z
type: task_learnings
confidence: 0.85
branch: main
files: src/foo.py, src/bar.py
categories: coding_conventions, task_learnings
---
Always use context managers when opening files.
"""
records = parse_blocks(content)
assert len(records) == 1
r = records[0]
assert r["id"] == "abc123"
assert r["type"] == "task_learnings"
assert r["confidence"] == "0.85"
assert r["branch"] == "main"
assert r["files"] == ["src/foo.py", "src/bar.py"]
assert r["categories"] == ["coding_conventions", "task_learnings"]
assert "Always use context managers" in r["content"]
def test_parse_blocks_multiple_blocks():
"""parse_blocks returns the correct number of records for multiple blocks."""
from parse_export_file import parse_blocks
content = """\
---
id: mem001
type: architecture_decisions
confidence: 0.9
branch: main
files:
categories: architecture_decisions
---
Use hexagonal architecture for the core domain.
---
id: mem002
type: anti_patterns
confidence: 0.75
branch: feat/refactor
files: src/legacy.py
categories: anti_patterns
---
Avoid direct database calls from view layer.
---
id: mem003
type: coding_conventions
confidence: 0.8
branch: main
files: src/utils.py, src/helpers.py
categories:
---
Use snake_case for all Python identifiers.
"""
records = parse_blocks(content)
assert len(records) == 3
assert records[0]["id"] == "mem001"
assert records[0]["categories"] == ["architecture_decisions"]
assert "hexagonal architecture" in records[0]["content"]
assert records[1]["id"] == "mem002"
assert records[1]["files"] == ["src/legacy.py"]
assert "direct database calls" in records[1]["content"]
assert records[2]["id"] == "mem003"
assert records[2]["files"] == ["src/utils.py", "src/helpers.py"]
assert records[2]["categories"] == []
assert "snake_case" in records[2]["content"]
def test_parse_blocks_missing_optional_fields():
"""parse_blocks uses defaults when optional fields are absent."""
from parse_export_file import parse_blocks
# confidence, branch, files, categories all absent
content = """\
---
id: xyz789
type: task_learnings
---
Run tests before committing.
"""
records = parse_blocks(content)
assert len(records) == 1
r = records[0]
assert r["id"] == "xyz789"
assert r["confidence"] == "" # default empty string
assert r["branch"] == "" # default empty string
assert r["files"] == [] # default empty list
assert r["categories"] == [] # default empty list
assert "Run tests" in r["content"]
def test_parse_blocks_filters_empty_content():
"""parse_blocks skips blocks whose content is empty or whitespace-only."""
from parse_export_file import parse_blocks
content = """\
---
id: empty1
type: task_learnings
---
---
id: real1
type: task_learnings
---
This block has real content.
---
id: empty2
type: coding_conventions
---
"""
records = parse_blocks(content)
# Only the block with actual content should be returned
assert len(records) == 1
assert records[0]["id"] == "real1"
assert "real content" in records[0]["content"]
def test_parse_blocks_round_trip():
"""Content formatted by export matches what parse_blocks expects."""
from parse_export_file import parse_blocks
# Simulate the exact format produced by the export skill
memory_id = "test-id-001"
created_at = "2024-06-01T12:00:00Z"
mem_type = "architecture_decisions"
confidence = "0.92"
branch = "feat/new-feature"
files = ["src/main.py", "tests/test_main.py"]
categories = ["architecture_decisions", "coding_conventions"]
memory_content = "Use dependency injection for all service classes."
# Format exactly as the export skill would
block = (
"---\n"
f"id: {memory_id}\n"
f"created_at: {created_at}\n"
f"type: {mem_type}\n"
f"confidence: {confidence}\n"
f"branch: {branch}\n"
f"files: {', '.join(files)}\n"
f"categories: {', '.join(categories)}\n"
"---\n"
f"{memory_content}\n"
"\n"
)
records = parse_blocks(block)
assert len(records) == 1
r = records[0]
assert r["id"] == memory_id
assert r["type"] == mem_type
assert r["confidence"] == confidence
assert r["branch"] == branch
assert r["files"] == files
assert r["categories"] == categories
assert r["content"] == memory_content
def test_parse_blocks_multiline_content():
"""parse_blocks correctly captures multi-line memory content."""
from parse_export_file import parse_blocks
content = """\
---
id: multi001
type: task_learnings
---
Line one of the memory.
Line two of the memory.
Line four after blank line.
"""
records = parse_blocks(content)
assert len(records) == 1
assert "Line one" in records[0]["content"]
assert "Line two" in records[0]["content"]
assert "Line four" in records[0]["content"]
def test_parse_blocks_empty_input():
"""parse_blocks returns empty list for empty input."""
from parse_export_file import parse_blocks
assert parse_blocks("") == []
assert parse_blocks(" \n ") == []
def test_parse_blocks_no_blocks():
"""parse_blocks returns empty list for content without any --- delimiters."""
from parse_export_file import parse_blocks
assert parse_blocks("Just some text without any delimiters.") == []
def test_parse_blocks_value_with_colon():
"""parse_blocks handles values that themselves contain colons."""
from parse_export_file import parse_blocks
content = """\
---
id: colon-test
type: task_learnings
created_at: 2024-01-01T10:00:00Z
---
Timestamp values contain colons and should parse correctly.
"""
records = parse_blocks(content)
assert len(records) == 1
assert records[0]["id"] == "colon-test"
# created_at field should be captured (it's in the record if present)
assert "2024-01-01T10:00:00Z" in records[0].get("created_at", "")
# ---------------------------------------------------------------------------
# CLI / subprocess tests
# ---------------------------------------------------------------------------
def test_main_cli_outputs_json(tmp_path):
"""Running parse_export_file.py as a script outputs valid JSON."""
export_file = tmp_path / "mem0-export-test.md"
export_file.write_text("""\
---
id: cli-test-001
type: task_learnings
confidence: 0.8
branch: main
files:
categories: task_learnings
---
Prefer composition over inheritance.
""")
result = subprocess.run(
[sys.executable, os.path.join(SCRIPTS_DIR, "parse_export_file.py"), str(export_file)],
capture_output=True,
text=True,
)
assert result.returncode == 0
records = json.loads(result.stdout)
assert isinstance(records, list)
assert len(records) == 1
assert records[0]["id"] == "cli-test-001"
def test_main_cli_no_args_exits_zero():
"""Running parse_export_file.py with no arguments exits 0 and prints []."""
result = subprocess.run(
[sys.executable, os.path.join(SCRIPTS_DIR, "parse_export_file.py")],
capture_output=True,
text=True,
)
assert result.returncode == 0
assert result.stdout.strip() == "[]"
def test_main_cli_missing_file_exits_zero(tmp_path):
"""Running parse_export_file.py with a missing file exits 0 and prints []."""
result = subprocess.run(
[sys.executable, os.path.join(SCRIPTS_DIR, "parse_export_file.py"),
str(tmp_path / "nonexistent.md")],
capture_output=True,
text=True,
)
assert result.returncode == 0
assert result.stdout.strip() == "[]"