"""Tests for session_state module marker functionality."""
import json
import pytest
import maniple_mcp.session_state as session_state
from maniple_mcp.session_state import (
ITERM_MARKER_PREFIX,
ITERM_MARKER_SUFFIX,
MARKER_PREFIX,
MARKER_SUFFIX,
extract_iterm_session_id,
extract_marker_session_id,
find_jsonl_by_iterm_id,
find_jsonl_by_tmux_id,
generate_marker_message,
)
class TestMarkerConstants:
"""Test marker constant definitions."""
def test_internal_marker_format(self):
"""Internal marker should use maniple-session format."""
assert MARKER_PREFIX == "<!maniple-session:"
assert MARKER_SUFFIX == "!>"
def test_iterm_marker_format(self):
"""iTerm marker should use maniple-iterm format."""
assert ITERM_MARKER_PREFIX == "<!maniple-iterm:"
assert ITERM_MARKER_SUFFIX == "!>"
class TestExtractMarkerSessionId:
"""Tests for extract_marker_session_id function."""
def test_extracts_session_id(self):
"""Should extract session ID from marker text."""
text = "Some text <!maniple-session:abc123!> more text"
assert extract_marker_session_id(text) == "abc123"
def test_extracts_legacy_session_id(self):
"""Should extract legacy session ID from marker text."""
text = "Some text <!claude-team-session:abc123!> more text"
assert extract_marker_session_id(text) == "abc123"
def test_returns_none_for_no_marker(self):
"""Should return None when no marker present."""
text = "No marker here"
assert extract_marker_session_id(text) is None
def test_returns_none_for_incomplete_marker(self):
"""Should return None for incomplete marker."""
text = "<!maniple-session:abc123 no closing"
assert extract_marker_session_id(text) is None
def test_handles_uuid_format(self):
"""Should handle UUID-style session IDs."""
text = "<!maniple-session:550e8400-e29b-41d4-a716-446655440000!>"
assert extract_marker_session_id(text) == "550e8400-e29b-41d4-a716-446655440000"
class TestExtractItermSessionId:
"""Tests for extract_iterm_session_id function."""
def test_extracts_iterm_session_id(self):
"""Should extract iTerm session ID from marker text."""
text = "Some text <!maniple-iterm:ABC-DEF-123!> more text"
assert extract_iterm_session_id(text) == "ABC-DEF-123"
def test_extracts_legacy_iterm_session_id(self):
"""Should extract legacy iTerm session ID from marker text."""
text = "Some text <!claude-team-iterm:ABC-DEF-123!> more text"
assert extract_iterm_session_id(text) == "ABC-DEF-123"
def test_returns_none_for_no_marker(self):
"""Should return None when no iTerm marker present."""
text = "No marker here"
assert extract_iterm_session_id(text) is None
def test_returns_none_for_wrong_marker_type(self):
"""Should return None for internal marker (not iTerm)."""
text = "<!maniple-session:abc123!>"
assert extract_iterm_session_id(text) is None
def test_handles_iterm_uuid_format(self):
"""Should handle iTerm2's UUID format."""
text = "<!maniple-iterm:C67C391C-7605-43A3-A6A2-F8A577049271!>"
assert extract_iterm_session_id(text) == "C67C391C-7605-43A3-A6A2-F8A577049271"
def test_extracts_iterm_when_both_markers_present(self):
"""Should extract iTerm ID when both markers are present."""
text = """<!maniple-session:internal-123!>
<!maniple-iterm:ITERM-ABC-456!>
More content here"""
assert extract_iterm_session_id(text) == "ITERM-ABC-456"
assert extract_marker_session_id(text) == "internal-123"
def test_extracts_iterm_when_mixed_markers_present(self):
"""Should extract IDs when marker namespaces are mixed."""
text = """<!claude-team-session:internal-123!>
<!maniple-iterm:ITERM-ABC-456!>
More content here"""
assert extract_iterm_session_id(text) == "ITERM-ABC-456"
assert extract_marker_session_id(text) == "internal-123"
class TestGenerateMarkerMessage:
"""Tests for generate_marker_message function."""
def test_includes_internal_marker(self):
"""Message should include internal session marker."""
message = generate_marker_message("abc123")
assert "<!maniple-session:abc123!>" in message
def test_no_iterm_marker_by_default(self):
"""Message should not include iTerm marker when not provided."""
message = generate_marker_message("abc123")
assert "<!maniple-iterm:" not in message
def test_includes_iterm_marker_when_provided(self):
"""Message should include iTerm marker when iterm_session_id provided."""
message = generate_marker_message("abc123", iterm_session_id="ITERM-XYZ")
assert "<!maniple-iterm:ITERM-XYZ!>" in message
def test_both_markers_when_iterm_provided(self):
"""Message should include both markers when iTerm ID provided."""
message = generate_marker_message("internal-id", iterm_session_id="iterm-id")
assert "<!maniple-session:internal-id!>" in message
assert "<!maniple-iterm:iterm-id!>" in message
def test_includes_identification_instruction(self):
"""Message should instruct Claude to respond with 'Identified!'."""
message = generate_marker_message("test")
assert "Identified!" in message
class TestFindJsonlByItermId:
"""Tests for find_jsonl_by_iterm_id scanning legacy and new markers."""
def test_accepts_maniple_marker_prefixes(self, tmp_path, monkeypatch):
projects_dir = tmp_path / "projects"
project_dir = projects_dir / "fake-project"
project_dir.mkdir(parents=True)
jsonl_path = project_dir / "session-1.jsonl"
content = "<!maniple-session:internal-123!>\n<!maniple-iterm:ITERM-1!>\n"
entry = {"type": "user", "parentUuid": None, "message": {"content": content}}
jsonl_path.write_text(json.dumps(entry) + "\n")
real_project_path = tmp_path / "real-project"
real_project_path.mkdir()
monkeypatch.setattr(session_state, "CLAUDE_PROJECTS_DIR", projects_dir)
monkeypatch.setattr(
session_state, "unslugify_path", lambda slug: str(real_project_path)
)
match = find_jsonl_by_iterm_id("ITERM-1", max_age_seconds=3600)
assert match is not None
assert match.iterm_session_id == "ITERM-1"
assert match.internal_session_id == "internal-123"
assert match.project_path == str(real_project_path)
assert match.jsonl_path == jsonl_path
def test_accepts_legacy_marker_prefixes(self, tmp_path, monkeypatch):
projects_dir = tmp_path / "projects"
project_dir = projects_dir / "fake-project"
project_dir.mkdir(parents=True)
jsonl_path = project_dir / "session-1.jsonl"
content = "<!claude-team-session:internal-123!>\n<!claude-team-iterm:ITERM-1!>\n"
entry = {"type": "user", "parentUuid": None, "message": {"content": content}}
jsonl_path.write_text(json.dumps(entry) + "\n")
real_project_path = tmp_path / "real-project"
real_project_path.mkdir()
monkeypatch.setattr(session_state, "CLAUDE_PROJECTS_DIR", projects_dir)
monkeypatch.setattr(
session_state, "unslugify_path", lambda slug: str(real_project_path)
)
match = find_jsonl_by_iterm_id("ITERM-1", max_age_seconds=3600)
assert match is not None
assert match.iterm_session_id == "ITERM-1"
assert match.internal_session_id == "internal-123"
assert match.project_path == str(real_project_path)
assert match.jsonl_path == jsonl_path
class TestFindJsonlByTmuxId:
"""Tests for find_jsonl_by_tmux_id scanning legacy and new markers."""
def test_accepts_maniple_marker_prefixes(self, tmp_path, monkeypatch):
projects_dir = tmp_path / "projects"
project_dir = projects_dir / "fake-project"
project_dir.mkdir(parents=True)
jsonl_path = project_dir / "session-1.jsonl"
content = "<!maniple-session:internal-123!>\n<!maniple-tmux:%1!>\n"
entry = {"type": "user", "parentUuid": None, "message": {"content": content}}
jsonl_path.write_text(json.dumps(entry) + "\n")
real_project_path = tmp_path / "real-project"
real_project_path.mkdir()
monkeypatch.setattr(session_state, "CLAUDE_PROJECTS_DIR", projects_dir)
monkeypatch.setattr(
session_state, "unslugify_path", lambda slug: str(real_project_path)
)
match = find_jsonl_by_tmux_id("%1", max_age_seconds=3600)
assert match is not None
assert match.tmux_pane_id == "%1"
assert match.internal_session_id == "internal-123"
assert match.project_path == str(real_project_path)
assert match.jsonl_path == jsonl_path
def test_accepts_legacy_marker_prefixes(self, tmp_path, monkeypatch):
projects_dir = tmp_path / "projects"
project_dir = projects_dir / "fake-project"
project_dir.mkdir(parents=True)
jsonl_path = project_dir / "session-1.jsonl"
content = "<!claude-team-session:internal-123!>\n<!claude-team-tmux:%1!>\n"
entry = {"type": "user", "parentUuid": None, "message": {"content": content}}
jsonl_path.write_text(json.dumps(entry) + "\n")
real_project_path = tmp_path / "real-project"
real_project_path.mkdir()
monkeypatch.setattr(session_state, "CLAUDE_PROJECTS_DIR", projects_dir)
monkeypatch.setattr(
session_state, "unslugify_path", lambda slug: str(real_project_path)
)
match = find_jsonl_by_tmux_id("%1", max_age_seconds=3600)
assert match is not None
assert match.tmux_pane_id == "%1"
assert match.internal_session_id == "internal-123"
assert match.project_path == str(real_project_path)
assert match.jsonl_path == jsonl_path
def test_finds_marker_with_non_null_parent_uuid(self, tmp_path, monkeypatch):
projects_dir = tmp_path / "projects"
project_dir = projects_dir / "fake-project"
project_dir.mkdir(parents=True)
jsonl_path = project_dir / "session-1.jsonl"
content = "<!maniple-session:internal-123!>\n<!maniple-tmux:%1!>\n"
entry = {"type": "user", "parentUuid": "parent-uuid", "message": {"content": content}}
jsonl_path.write_text(json.dumps(entry) + "\n")
real_project_path = tmp_path / "real-project"
real_project_path.mkdir()
monkeypatch.setattr(session_state, "CLAUDE_PROJECTS_DIR", projects_dir)
monkeypatch.setattr(
session_state, "unslugify_path", lambda slug: str(real_project_path)
)
match = find_jsonl_by_tmux_id("%1", max_age_seconds=3600)
assert match is not None
assert match.tmux_pane_id == "%1"
assert match.internal_session_id == "internal-123"
assert match.project_path == str(real_project_path)
assert match.jsonl_path == jsonl_path