Skip to main content
Glama
test_living_reports_storage.py9.49 kB
"""Tests for living reports storage layer.""" from __future__ import annotations import json import tempfile from pathlib import Path from unittest.mock import patch import pytest from igloo_mcp.living_reports.models import AuditEvent, Outline, ReportId from igloo_mcp.living_reports.storage import ( GlobalStorage, ReportLock, ReportStorage, ) class TestReportLock: """Test ReportLock functionality.""" def test_lock_creation(self) -> None: """Test creating a lock object.""" with tempfile.TemporaryDirectory() as tmpdir: lock_path = Path(tmpdir) / "test.lock" lock = ReportLock(lock_path, timeout_seconds=1.0) assert lock.lock_path == lock_path assert lock.timeout_seconds == 1.0 def test_lock_context_manager_without_portalocker(self) -> None: """Test lock context manager when portalocker is not available.""" with ( patch("igloo_mcp.living_reports.storage.HAS_PORTALOCKER", False), tempfile.TemporaryDirectory() as tmpdir, ): lock_path = Path(tmpdir) / "test.lock" lock = ReportLock(lock_path) with lock: assert lock_path.exists() # Lock file should be removed after context assert not lock_path.exists() def test_lock_fallback_on_existing_file(self) -> None: """Test fallback lock raises error when file exists.""" with ( patch("igloo_mcp.living_reports.storage.HAS_PORTALOCKER", False), tempfile.TemporaryDirectory() as tmpdir, ): lock_path = Path(tmpdir) / "test.lock" lock_path.write_text("existing") lock = ReportLock(lock_path) with pytest.raises(RuntimeError, match="Lock file exists"), lock: pass class TestReportStorage: """Test ReportStorage functionality.""" def test_storage_creation(self) -> None: """Test creating storage for a report directory.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) assert storage.report_dir == report_dir assert storage.outline_path == report_dir / "outline.json" assert storage.audit_path == report_dir / "audit.jsonl" assert storage.backups_dir == report_dir / "backups" def test_load_outline_nonexistent(self) -> None: """Test loading outline when file doesn't exist.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) with pytest.raises(FileNotFoundError): storage.load_outline() def test_save_and_load_outline(self) -> None: """Test saving and loading an outline.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) # Create test outline outline = Outline( report_id=str(ReportId.new()), title="Test Report", created_at="2024-01-01T00:00:00Z", updated_at="2024-01-01T00:00:00Z", ) # Save outline storage.save_outline(outline) # Load outline loaded = storage.load_outline() assert loaded.title == "Test Report" assert loaded.report_id == outline.report_id # Check backup was created backup_files = list(storage.backups_dir.glob("*.bak")) assert len(backup_files) == 0 # No backup on first save # Save again to create backup outline.title = "Updated Report" storage.save_outline(outline) backup_files = list(storage.backups_dir.glob("*.bak")) assert len(backup_files) == 1 def test_invalid_outline_json(self) -> None: """Test loading invalid JSON raises ValueError.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) # Write invalid JSON storage.outline_path.write_text("invalid json") with pytest.raises(ValueError): storage.load_outline() def test_invalid_outline_schema(self) -> None: """Test loading outline with invalid schema raises ValueError.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) # Write valid JSON but invalid Outline invalid_data = {"invalid_field": "value"} storage.outline_path.write_text(json.dumps(invalid_data)) with pytest.raises(ValueError): storage.load_outline() def test_append_audit_event(self) -> None: """Test appending audit events.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) event1 = AuditEvent( action_id="550e8400-e29b-41d4-a716-446655440000", report_id=str(ReportId.new()), ts="2024-01-01T00:00:00Z", actor="cli", action_type="create", ) event2 = AuditEvent( action_id="550e8400-e29b-41d4-a716-446655440001", report_id=str(ReportId.new()), ts="2024-01-01T00:01:00Z", actor="agent", action_type="evolve", ) storage.append_audit_event(event1) storage.append_audit_event(event2) events = storage.load_audit_events() assert len(events) == 2 assert events[0].action_type == "create" assert events[1].action_type == "evolve" def test_load_audit_events_empty(self) -> None: """Test loading audit events when file doesn't exist.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) events = storage.load_audit_events() assert events == [] def test_detect_manual_edits_valid(self) -> None: """Test manual edit detection with valid outline.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) outline = Outline( report_id=str(ReportId.new()), title="Test Report", created_at="2024-01-01T00:00:00Z", updated_at="2024-01-01T00:00:00Z", ) storage.save_outline(outline) assert not storage.detect_manual_edits() def test_detect_manual_edits_invalid_json(self) -> None: """Test manual edit detection with invalid JSON.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) storage.outline_path.write_text("invalid json") assert storage.detect_manual_edits() def test_lock_context_manager(self) -> None: """Test storage lock context manager.""" with tempfile.TemporaryDirectory() as tmpdir: report_dir = Path(tmpdir) / "report1" storage = ReportStorage(report_dir) # Should not raise any exceptions with storage.lock(): pass class TestGlobalStorage: """Test GlobalStorage functionality.""" def test_global_storage_creation(self) -> None: """Test creating global storage.""" with tempfile.TemporaryDirectory() as tmpdir: reports_root = Path(tmpdir) / "reports" storage = GlobalStorage(reports_root) assert storage.reports_root == reports_root assert storage.index_path == reports_root / "index.jsonl" def test_get_report_storage(self) -> None: """Test getting report storage instance.""" with tempfile.TemporaryDirectory() as tmpdir: reports_root = Path(tmpdir) / "reports" storage = GlobalStorage(reports_root) report_storage = storage.get_report_storage("test_report") assert isinstance(report_storage, ReportStorage) assert report_storage.report_dir == reports_root / "by_id" / "test_report" def test_save_index_entry(self) -> None: """Test saving index entries.""" with tempfile.TemporaryDirectory() as tmpdir: reports_root = Path(tmpdir) / "reports" storage = GlobalStorage(reports_root) entry = { "report_id": str(ReportId.new()), "current_title": "Test Report", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "tags": ["test"], "status": "active", "path": "by_id/test", } storage.save_index_entry(entry) # Check file was created and contains entry assert storage.index_path.exists() content = storage.index_path.read_text() assert entry["current_title"] in content

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Evan-Kim2028/igloo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server