test_forum_runtime_helpers.py 7.42 KB
from __future__ import annotations

import shutil
import uuid
from pathlib import Path

from apps.web_api.runtime.forum_runtime import (
    ForumRuntime,
    parse_forum_log_line,
)


PROJECT_ROOT = Path(__file__).resolve().parents[3]


def _make_workspace_temp_dir() -> Path:
    path = PROJECT_ROOT / "var" / f"forum-runtime-test-{uuid.uuid4().hex}"
    path.mkdir(parents=True, exist_ok=False)
    return path


def test_parse_forum_log_line_extracts_timestamp_source_and_normalized_content():
    parsed = parse_forum_log_line(
        "[12:30:15] [FORUM] line one\\nline two"
    )

    assert parsed == {
        "timestamp": "12:30:15",
        "source": "FORUM",
        "content": "line one\nline two",
    }


def test_parse_forum_log_line_defaults_to_system_source_without_explicit_channel():
    parsed = parse_forum_log_line(
        "[12:30:15] Forum monitor started\\r\\nready"
    )

    assert parsed == {
        "timestamp": "12:30:15",
        "source": "SYSTEM",
        "content": "Forum monitor started\r\nready",
    }


def test_parse_forum_log_line_falls_back_to_unknown_source_for_plain_text():
    parsed = parse_forum_log_line("forum runtime heartbeat")

    assert parsed == {
        "timestamp": "",
        "source": "UNKNOWN",
        "content": "forum runtime heartbeat",
    }


def test_get_forum_log_history_returns_empty_payload_when_log_file_is_missing():
    temp_dir = _make_workspace_temp_dir()
    try:
        payload = ForumRuntime().get_log_history(
            log_dir=temp_dir,
            start_position=0,
            max_lines=50,
        )

        assert payload == {
            "success": True,
            "log_lines": [],
            "position": 0,
            "has_more": False,
            "engine_result": {
                "engine_name": "forum",
                "status": "stopped",
                "success": True,
                "summary": "Forum discussion is stopped",
                "artifacts": {
                    "parsed_messages": [],
                    "participants": [],
                },
                "metrics": {
                    "log_line_count": 0,
                    "parsed_message_count": 0,
                    "participant_count": 0,
                    "host_summary_count": 0,
                },
                "logs_ref": str(temp_dir / "forum.log"),
            },
        }
    finally:
        shutil.rmtree(temp_dir, ignore_errors=True)


def test_get_forum_log_history_skips_blank_lines_and_tracks_cursor_position():
    log_dir = _make_workspace_temp_dir()
    try:
        forum_log = log_dir / "forum.log"
        forum_log.write_text("line-1\n\nline-2\nline-3\n", encoding="utf-8")

        runtime = ForumRuntime()
        first_payload = runtime.get_log_history(log_dir=log_dir, start_position=0, max_lines=2)
        second_payload = runtime.get_log_history(
            log_dir=log_dir,
            start_position=first_payload["position"],
            max_lines=2,
        )

        assert first_payload["success"] is True
        assert first_payload["log_lines"] == ["line-1", "line-2"]
        assert first_payload["has_more"] is True
        assert first_payload["engine_result"] == {
            "engine_name": "forum",
            "status": "stopped",
            "success": True,
            "summary": "line-2",
            "artifacts": {
                "parsed_messages": [
                    {
                        "timestamp": "",
                        "source": "UNKNOWN",
                        "content": "line-1",
                    },
                    {
                        "timestamp": "",
                        "source": "UNKNOWN",
                        "content": "line-2",
                    },
                ],
                "participants": ["UNKNOWN"],
            },
            "metrics": {
                "log_line_count": 2,
                "parsed_message_count": 2,
                "participant_count": 1,
                "host_summary_count": 0,
            },
            "logs_ref": str(log_dir / "forum.log"),
        }
        assert second_payload == {
            "success": True,
            "log_lines": ["line-3"],
            "position": forum_log.stat().st_size,
            "has_more": False,
            "engine_result": {
                "engine_name": "forum",
                "status": "stopped",
                "success": True,
                "summary": "line-3",
                "artifacts": {
                    "parsed_messages": [
                        {
                            "timestamp": "",
                            "source": "UNKNOWN",
                            "content": "line-3",
                        }
                    ],
                    "participants": ["UNKNOWN"],
                },
                "metrics": {
                    "log_line_count": 1,
                    "parsed_message_count": 1,
                    "participant_count": 1,
                    "host_summary_count": 0,
                },
                "logs_ref": str(log_dir / "forum.log"),
            },
        }
    finally:
        shutil.rmtree(log_dir, ignore_errors=True)


def test_get_forum_log_history_maps_host_summary_to_completed_engine_result():
    log_dir = _make_workspace_temp_dir()
    try:
        forum_log = log_dir / "forum.log"
        forum_log.write_text(
            "\n".join(
                [
                    "[2026-04-22T10:50:00Z][Query Agent] Collected venue review snippets",
                    "[2026-04-22T10:50:02Z][HOST] Summary: curation and staff guidance stand out across engines",
                ]
            )
            + "\n",
            encoding="utf-8",
        )

        payload = ForumRuntime().get_log_history(log_dir=log_dir, start_position=0, max_lines=10)

        assert payload == {
            "success": True,
            "log_lines": [
                "[2026-04-22T10:50:00Z][Query Agent] Collected venue review snippets",
                "[2026-04-22T10:50:02Z][HOST] Summary: curation and staff guidance stand out across engines",
            ],
            "position": forum_log.stat().st_size,
            "has_more": False,
            "engine_result": {
                "engine_name": "forum",
                "status": "completed",
                "success": True,
                "summary": "Summary: curation and staff guidance stand out across engines",
                "artifacts": {
                    "parsed_messages": [
                        {
                            "timestamp": "2026-04-22T10:50:00Z",
                            "source": "Query Agent",
                            "content": "Collected venue review snippets",
                        },
                        {
                            "timestamp": "2026-04-22T10:50:02Z",
                            "source": "HOST",
                            "content": "Summary: curation and staff guidance stand out across engines",
                        },
                    ],
                    "participants": ["HOST", "Query Agent"],
                    "host_summary": "Summary: curation and staff guidance stand out across engines",
                },
                "metrics": {
                    "log_line_count": 2,
                    "parsed_message_count": 2,
                    "participant_count": 2,
                    "host_summary_count": 1,
                },
                "logs_ref": str(log_dir / "forum.log"),
            },
        }
    finally:
        shutil.rmtree(log_dir, ignore_errors=True)