Skip to main content
Glama
test_section_prose_content.py35.3 kB
"""Regression tests for section prose content feature (v0.3.2). Sections now support optional prose content fields: - content: str (markdown, html, or plain text) - content_format: Literal["markdown", "html", "plain"] (default: "markdown") This is a minimum viable test suite covering the critical functionality. """ import datetime as dt from pathlib import Path import pytest from igloo_mcp.config import Config, SnowflakeConfig from igloo_mcp.living_reports.service import ReportService from igloo_mcp.mcp.tools.evolve_report import EvolveReportTool from igloo_mcp.mcp.tools.render_report import RenderReportTool class TestSectionProseContentSmoke: """Smoke tests for section prose content (minimum viable coverage).""" @pytest.mark.asyncio async def test_add_section_with_markdown_content(self, tmp_path: Path): """Smoke test: section can have markdown prose content.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Prose Content Test", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add section with prose content", proposed_changes={ "sections_to_add": [ { "title": "Executive Summary", "order": 1, "content": "## Key Findings\n\n- Revenue up 25%\n- Costs stable", "content_format": "markdown", } ] }, ) assert result["status"] == "success" assert result["summary"]["sections_added"] == 1 # Verify content persisted outline = report_service.get_report_outline(report_id) section = outline.sections[0] assert section.content == "## Key Findings\n\n- Revenue up 25%\n- Costs stable" assert section.content_format == "markdown" @pytest.mark.asyncio async def test_content_format_defaults_to_markdown(self, tmp_path: Path): """Test that content_format defaults to 'markdown' when not specified.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Test", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add section with content but no format", proposed_changes={ "sections_to_add": [ { "title": "Summary", "order": 1, "content": "Plain text content", # No content_format specified } ] }, ) assert result["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] assert section.content == "Plain text content" assert section.content_format == "markdown" # Should default @pytest.mark.asyncio async def test_modify_section_prose_content(self, tmp_path: Path): """Test modifying existing section's prose content.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Test", template="empty") tool = EvolveReportTool(config, report_service) # Add section with initial content result1 = await tool.execute( report_selector=report_id, instruction="Add section", proposed_changes={"sections_to_add": [{"title": "Summary", "order": 1, "content": "Initial content"}]}, ) section_id = result1["summary"]["section_ids_added"][0] # Modify content result2 = await tool.execute( report_selector=report_id, instruction="Update content", proposed_changes={ "sections_to_modify": [ { "section_id": section_id, "content": "Updated content with **bold** text", } ] }, ) assert result2["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] assert section.content == "Updated content with **bold** text" @pytest.mark.asyncio async def test_setting_content_clears_notes(self, tmp_path: Path): """Setting formatted content should drop legacy notes field automatically.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Notes Cleanup", template="empty") tool = EvolveReportTool(config, report_service) add_result = await tool.execute( report_selector=report_id, instruction="Seed notes", proposed_changes={ "sections_to_add": [ { "title": "Overview", "order": 1, "notes": "Raw scratch notes", } ] }, ) section_id = add_result["summary"]["section_ids_added"][0] outline = report_service.get_report_outline(report_id) assert outline.sections[0].notes == "Raw scratch notes" assert outline.sections[0].content is None await tool.execute( report_selector=report_id, instruction="Promote to content", proposed_changes={ "sections_to_modify": [ { "section_id": section_id, "content": "## Clean Content\n\nUp-leveled summary.", } ] }, ) updated_outline = report_service.get_report_outline(report_id) section = updated_outline.sections[0] assert section.content == "## Clean Content\n\nUp-leveled summary." assert section.notes is None @pytest.mark.asyncio async def test_section_and_insight_timestamps_update_on_changes(self, tmp_path: Path): """Sections and insights should track created/updated timestamps.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Timestamp Test", template="empty") tool = EvolveReportTool(config, report_service) add_result = await tool.execute( report_selector=report_id, instruction="Add section and inline insight", proposed_changes={ "sections_to_add": [ { "title": "Exec Summary", "order": 1, "content": "Initial content", "insights": [ { "summary": "Finding", "importance": 7, "citations": [ { "source": "query", "execution_id": "exec-ts-1", } ], } ], } ] }, ) assert add_result["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] insight = outline.get_insight(outline.sections[0].insight_ids[0]) section_created = dt.datetime.fromisoformat(section.created_at) section_updated = dt.datetime.fromisoformat(section.updated_at) insight_created = dt.datetime.fromisoformat(insight.created_at) insight_updated = dt.datetime.fromisoformat(insight.updated_at) assert section_updated >= section_created assert insight_updated >= insight_created modify_result = await tool.execute( report_selector=report_id, instruction="Update section and insight", proposed_changes={ "sections_to_modify": [ { "section_id": section.section_id, "content": "Updated content", } ], "insights_to_modify": [ { "insight_id": insight.insight_id, "importance": 9, } ], }, ) assert modify_result["status"] == "success" updated_outline = report_service.get_report_outline(report_id) updated_section = updated_outline.sections[0] updated_insight = updated_outline.get_insight(insight.insight_id) assert updated_section.created_at == section.created_at assert dt.datetime.fromisoformat(updated_section.updated_at) > section_updated assert updated_insight.created_at == insight.created_at assert dt.datetime.fromisoformat(updated_insight.updated_at) > insight_updated @pytest.mark.asyncio async def test_html_and_plain_content_formats(self, tmp_path: Path): """Test that html and plain content formats are supported.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Test", template="empty") tool = EvolveReportTool(config, report_service) # Add HTML section result1 = await tool.execute( report_selector=report_id, instruction="Add HTML section", proposed_changes={ "sections_to_add": [ { "title": "HTML Summary", "order": 1, "content": "<h2>Findings</h2><p>Revenue <strong>increased</strong></p>", "content_format": "html", } ] }, ) assert result1["status"] == "success" # Add plain text section result2 = await tool.execute( report_selector=report_id, instruction="Add plain text section", proposed_changes={ "sections_to_add": [ { "title": "Plain Summary", "order": 2, "content": "Just plain text, no formatting", "content_format": "plain", } ] }, ) assert result2["status"] == "success" outline = report_service.get_report_outline(report_id) assert outline.sections[0].content_format == "html" assert outline.sections[1].content_format == "plain" @pytest.mark.asyncio async def test_prose_content_in_render_output(self, tmp_path: Path): """Smoke test: prose content appears in rendered output.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") # Create report with prose content report_id = report_service.create_report(title="Test", template="empty") evolve_tool = EvolveReportTool(config, report_service) await evolve_tool.execute( report_selector=report_id, instruction="Add section with prose", proposed_changes={ "sections_to_add": [ { "title": "Executive Summary", "order": 1, "content": "## Q4 Performance\n\nRevenue exceeded targets by **15%**.", } ] }, ) # Render and check QMD contains prose render_tool = RenderReportTool(config, report_service) result = await render_tool.execute(report_selector=report_id, format="html", dry_run=True, include_preview=True) assert result["status"] == "success" assert "preview" in result # Preview should contain the prose content preview = result["preview"] assert "Q4 Performance" in preview assert "Revenue exceeded targets" in preview @pytest.mark.asyncio async def test_notes_only_sections_render_in_html(self, tmp_path: Path): """Notes-only sections should still appear when rendering via Quarto dry run.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Notes Render", template="empty") evolve_tool = EvolveReportTool(config, report_service) await evolve_tool.execute( report_selector=report_id, instruction="Add notes", proposed_changes={ "sections_to_add": [ { "title": "Scratch", "order": 1, "notes": "Temporary finding awaiting formatting.", } ] }, ) render_tool = RenderReportTool(config, report_service) result = await render_tool.execute(report_selector=report_id, format="html", dry_run=True, include_preview=True) assert result["status"] == "success" assert "Temporary finding awaiting formatting." in result["preview"] @pytest.mark.asyncio async def test_section_template_generation_and_feedback(self, tmp_path: Path): """Sections can be generated from markdown templates with clean formatting.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Templates", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add templated findings", proposed_changes={ "sections_to_add": [ { "title": "North Star", "order": 1, "template": "findings_list", "template_data": { "heading": "North Star Metrics", "findings": [ { "title": "Activation climbed", "metric": {"name": "Activation", "value": "62%", "trend": "+4 pp"}, "description": "Week-over-week activation improved on the new onboarding path.", } ], }, } ] }, ) assert result["status"] == "success" assert result["summary"]["sections_added"] == 1 feedback = result["formatting_feedback"] assert feedback["score"] == 100 outline = report_service.get_report_outline(report_id) section = outline.sections[0] assert "## North Star Metrics" in section.content assert "### 1. Activation climbed" in section.content @pytest.mark.asyncio async def test_formatting_feedback_detects_wall_of_text(self, tmp_path: Path): """Dense unstructured prose should surface formatting warnings.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Formatting", template="empty") tool = EvolveReportTool(config, report_service) long_paragraph = ( "This is a very long paragraph without any markdown structure that keeps going and going to simulate a " "wall of text focused on burying the reader in detail without providing headings or bullet lists. " ) * 20 result = await tool.execute( report_selector=report_id, instruction="Add messy prose", proposed_changes={ "sections_to_add": [ { "title": "Dense Notes", "order": 1, "content": long_paragraph, } ] }, ) assert result["status"] == "success" feedback = result["formatting_feedback"] assert feedback["score"] < 100 assert any("very long paragraphs" in warning for warning in feedback["warnings"]) assert feedback["section_feedback"], "Section-specific feedback should be populated" @pytest.mark.asyncio async def test_executive_summary_template(self, tmp_path: Path): """Executive summary template should generate structured content.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Exec Summary", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add executive summary", proposed_changes={ "sections_to_add": [ { "title": "Executive Summary", "order": 1, "template": "executive_summary", "template_data": { "headline": "Q4 Performance Summary", "context": "This quarter saw significant growth across all business units.", "key_points": [ {"title": "Revenue Growth", "detail": "Up 25% YoY"}, {"title": "Customer Retention", "detail": "Improved to 95%"}, "Expanded into 3 new markets", ], "recommendation": "Continue investment in customer success initiatives.", "conclusion": "Overall, Q4 exceeded expectations.", }, } ] }, ) assert result["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] assert "## Q4 Performance Summary" in section.content assert "### Key Takeaways" in section.content assert "**Revenue Growth**" in section.content assert "### Recommendation" in section.content assert "> Continue investment" in section.content @pytest.mark.asyncio async def test_action_items_template_with_table(self, tmp_path: Path): """Action items template with owner/due data should render as table.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Action Items", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add action items", proposed_changes={ "sections_to_add": [ { "title": "Next Steps", "order": 1, "template": "action_items", "template_data": { "heading": "Follow-up Actions", "actions": [ { "description": "Review Q4 metrics", "owner": "Alice", "due": "2024-01-20", "priority": "High", }, { "description": "Schedule team retrospective", "owner": "Bob", "due": "2024-01-25", "priority": "Medium", }, ], }, } ] }, ) assert result["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] assert "## Follow-up Actions" in section.content assert "| # | Action | Owner | Due | Priority |" in section.content assert "Alice" in section.content assert "High" in section.content @pytest.mark.asyncio async def test_action_items_template_simple_list(self, tmp_path: Path): """Action items without owner/due data should render as numbered list.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Simple Actions", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add simple actions", proposed_changes={ "sections_to_add": [ { "title": "Next Steps", "order": 1, "template": "action_items", "template_data": { "actions": [ "Complete documentation review", "Submit final report", "Schedule follow-up meeting", ], }, } ] }, ) assert result["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] assert "## Action Items" in section.content assert "1. Complete documentation review" in section.content assert "2. Submit final report" in section.content # Should NOT have table headers for simple list assert "| # | Action |" not in section.content class TestSectionTemplateEdgeCases: """Edge case tests for section templates - empty/null/special character handling.""" @pytest.mark.asyncio async def test_findings_template_empty_findings_array_raises(self, tmp_path: Path): """findings template with empty findings array should raise error.""" from igloo_mcp.mcp.exceptions import MCPExecutionError config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Edge Cases", template="empty") tool = EvolveReportTool(config, report_service) with pytest.raises(MCPExecutionError, match="findings template requires"): await tool.execute( report_selector=report_id, instruction="Add section with empty findings", proposed_changes={ "sections_to_add": [ { "title": "Empty Findings", "order": 1, "template": "findings_list", "template_data": { "findings": [], }, } ] }, ) @pytest.mark.asyncio async def test_findings_template_missing_findings_raises(self, tmp_path: Path): """findings template without findings key should raise error.""" from igloo_mcp.mcp.exceptions import MCPExecutionError config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Edge Cases", template="empty") tool = EvolveReportTool(config, report_service) with pytest.raises(MCPExecutionError, match="findings template requires"): await tool.execute( report_selector=report_id, instruction="Add section without findings", proposed_changes={ "sections_to_add": [ { "title": "Missing Findings", "order": 1, "template": "findings_list", "template_data": {}, } ] }, ) @pytest.mark.asyncio async def test_findings_template_special_characters(self, tmp_path: Path): """findings template should handle special characters in content.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Special Chars", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add findings with special characters", proposed_changes={ "sections_to_add": [ { "title": "Special Characters Test", "order": 1, "template": "findings_list", "template_data": { "heading": "Test <script>alert('xss')</script>", "findings": [ { "title": "Finding with | pipe & ampersand", "description": 'Has <html> tags and "quotes"', "metric": { "name": "O'Brien's Metric", "value": "$1,000 < $2,000", "trend": "↑ +50%", }, } ], }, } ] }, ) assert result["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] # Content should be generated (markdown) assert section.content is not None # Special characters should be preserved in markdown (not escaped yet) assert "<script>" in section.content or "&lt;script&gt;" in section.content @pytest.mark.asyncio async def test_bullet_list_empty_items_raises(self, tmp_path: Path): """bullet_list template with empty items should raise error.""" from igloo_mcp.mcp.exceptions import MCPExecutionError config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Edge Cases", template="empty") tool = EvolveReportTool(config, report_service) with pytest.raises(MCPExecutionError, match="bullet_list template requires"): await tool.execute( report_selector=report_id, instruction="Add section with empty items", proposed_changes={ "sections_to_add": [ { "title": "Empty Bullets", "order": 1, "template": "bullet_list", "template_data": { "items": [], }, } ] }, ) @pytest.mark.asyncio async def test_executive_summary_template_minimal(self, tmp_path: Path): """executive_summary template should work with minimal data.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Minimal Summary", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add minimal executive summary", proposed_changes={ "sections_to_add": [ { "title": "Executive Summary", "order": 1, "template": "executive_summary", "template_data": {}, # All fields optional } ] }, ) assert result["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] # Should use section title as headline assert "## Executive Summary" in section.content @pytest.mark.asyncio async def test_action_items_mixed_formats(self, tmp_path: Path): """action_items template should handle mixed string and dict actions.""" config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Mixed Actions", template="empty") tool = EvolveReportTool(config, report_service) result = await tool.execute( report_selector=report_id, instruction="Add mixed action items", proposed_changes={ "sections_to_add": [ { "title": "Actions", "order": 1, "template": "action_items", "template_data": { "actions": [ {"description": "With owner", "owner": "Alice"}, "Simple string action", ], }, } ] }, ) assert result["status"] == "success" outline = report_service.get_report_outline(report_id) section = outline.sections[0] # Should render as table when any action has metadata fields assert section.content is not None @pytest.mark.asyncio async def test_metrics_template_empty_metrics_raises(self, tmp_path: Path): """metrics template with empty metrics should raise error.""" from igloo_mcp.mcp.exceptions import MCPExecutionError config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Edge Cases", template="empty") tool = EvolveReportTool(config, report_service) with pytest.raises(MCPExecutionError, match="metrics template requires"): await tool.execute( report_selector=report_id, instruction="Add section with empty metrics", proposed_changes={ "sections_to_add": [ { "title": "Empty Metrics", "order": 1, "template": "metrics", "template_data": { "metrics": [], }, } ] }, ) @pytest.mark.asyncio async def test_unknown_template_raises(self, tmp_path: Path): """Unknown template name should raise error.""" from igloo_mcp.mcp.exceptions import MCPExecutionError config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Edge Cases", template="empty") tool = EvolveReportTool(config, report_service) with pytest.raises(MCPExecutionError, match="Unknown section template"): await tool.execute( report_selector=report_id, instruction="Add section with unknown template", proposed_changes={ "sections_to_add": [ { "title": "Unknown Template", "order": 1, "template": "nonexistent_template", "template_data": {}, } ] }, ) @pytest.mark.asyncio async def test_template_data_without_template_raises(self, tmp_path: Path): """template_data without template should raise error.""" from igloo_mcp.mcp.exceptions import MCPExecutionError config = Config(snowflake=SnowflakeConfig(profile="TEST_PROFILE")) report_service = ReportService(reports_root=tmp_path / "reports") report_id = report_service.create_report(title="Edge Cases", template="empty") tool = EvolveReportTool(config, report_service) with pytest.raises(MCPExecutionError, match="template_data provided without template"): await tool.execute( report_selector=report_id, instruction="Add section with template_data but no template", proposed_changes={ "sections_to_add": [ { "title": "Missing Template", "order": 1, "template_data": {"key": "value"}, } ] }, )

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