# #region Test.Core.TaskManager.EventBus [C:3] [TYPE Module] [SEMANTICS test,eventbus,async,pubsub]
# @BRIEF Verify EventBus contracts — buffering, flush, subscribe/broadcast, persistence delegation.
# @RELATION BINDS_TO -> [EventBus]
# @TEST_EDGE: level_filter -> DEBUG logs skipped when filter is INFO
# @TEST_EDGE: queue_full -> Subscriber queue full drops entries without raising
# @TEST_EDGE: flush_error -> Flush re-queues logs on persistence failure
# @TEST_EDGE: no_subscriber -> Broadcast with no subscribers is no-op
# @TEST_EDGE: empty_flush -> Flush with empty buffer is no-op
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))

import asyncio
from datetime import datetime
import pytest
from unittest.mock import MagicMock, patch, PropertyMock

from src.core.task_manager.event_bus import EventBus, QUEUE_MAXSIZE
from src.core.task_manager.models import LogEntry, LogFilter, LogStats, TaskStatus


@pytest.fixture
def mock_persistence():
    """Hardcoded fixture: mock TaskLogPersistenceService."""
    return MagicMock()


@pytest.fixture
def event_bus(mock_persistence):
    """Hardcoded fixture: EventBus with mocked persistence."""
    return EventBus(mock_persistence)


# ═══════════════════════════════════════════════════════════════
# Init
# ═══════════════════════════════════════════════════════════════

class TestEventBusInit:
    """Verify EventBus.__init__ @POST: ready to accept logs, empty structures."""

    def test_init_empty_structures(self, event_bus):
        assert event_bus.subscribers == {}
        assert event_bus._status_subscribers == {}
        assert event_bus._task_event_subscribers == []
        assert event_bus._maintenance_subscribers == []
        assert event_bus._log_buffer == {}
        assert event_bus._flusher_task is None
        assert isinstance(event_bus._flusher_stop_event, asyncio.Event)

    def test_init_persistence_stored(self, event_bus, mock_persistence):
        assert event_bus.log_persistence_service is mock_persistence


# ═══════════════════════════════════════════════════════════════
# Start / Stop
# ═══════════════════════════════════════════════════════════════

class TestEventBusStartStop:
    """Verify start/stop @POST contracts."""

    @pytest.mark.asyncio
    async def test_start_creates_async_task(self, event_bus):
        event_bus.start()
        assert event_bus._flusher_task is not None
        assert not event_bus._flusher_task.done()
        await event_bus.stop()

    @pytest.mark.asyncio
    async def test_start_idempotent(self, event_bus):
        event_bus.start()
        first_task = event_bus._flusher_task
        event_bus.start()
        assert event_bus._flusher_task is first_task
        await event_bus.stop()

    @pytest.mark.asyncio
    async def test_stop_cancels_flusher(self, event_bus):
        event_bus.start()
        await event_bus.stop()
        assert event_bus._flusher_task is None

    @pytest.mark.asyncio
    async def test_stop_without_start(self, event_bus):
        await event_bus.stop()  # Must not raise

    @pytest.mark.asyncio
    async def test_start_replaces_done_task(self, event_bus):
        """If previous flusher task is done, start() creates a new one."""
        # Create a done task
        async def done_task():
            pass
        event_bus._flusher_task = asyncio.create_task(done_task())
        await event_bus._flusher_task  # wait for completion
        assert event_bus._flusher_task.done()

        event_bus.start()  # should create new task since old one is done
        assert event_bus._flusher_task is not None
        assert not event_bus._flusher_task.done()
        await event_bus.stop()


# ═══════════════════════════════════════════════════════════════
# add_log
# ═══════════════════════════════════════════════════════════════

class TestEventBusAddLog:
    """Verify add_log @POST: buffer, subscriber dispatch, level filtering."""

    @pytest.mark.asyncio
    async def test_add_log_appends_to_buffer(self, event_bus):
        with patch("src.core.task_manager.event_bus.should_log_task_level", return_value=True):
            await event_bus.add_log("t1", "INFO", "hello", source="test")
            assert "t1" in event_bus._log_buffer
            assert len(event_bus._log_buffer["t1"]) == 1
            assert event_bus._log_buffer["t1"][0].message == "hello"
            assert event_bus._log_buffer["t1"][0].source == "test"

    @pytest.mark.asyncio
    async def test_add_log_skipped_by_level_filter(self, event_bus):
        with patch("src.core.task_manager.event_bus.should_log_task_level", return_value=False):
            await event_bus.add_log("t1", "DEBUG", "debug msg")
            assert "t1" not in event_bus._log_buffer

    @pytest.mark.asyncio
    async def test_add_log_with_task_logs_list(self, event_bus):
        task_logs = []
        with patch("src.core.task_manager.event_bus.should_log_task_level", return_value=True):
            await event_bus.add_log("t1", "INFO", "msg", source="test", task_logs_list=task_logs)
            assert len(task_logs) == 1
            assert task_logs[0].message == "msg"

    @pytest.mark.asyncio
    async def test_add_log_notifies_subscribers(self, event_bus):
        queue = asyncio.Queue()
        event_bus.subscribers["t1"] = [queue]
        with patch("src.core.task_manager.event_bus.should_log_task_level", return_value=True):
            await event_bus.add_log("t1", "INFO", "notify msg")
            received = await asyncio.wait_for(queue.get(), timeout=0.5)
            assert received.message == "notify msg"

    @pytest.mark.asyncio
    async def test_add_log_with_subscriber_consumes(self, event_bus):
        """Subscriber receives log entry via queue."""
        queue = asyncio.Queue()
        event_bus.subscribers["t1"] = [queue]
        with patch("src.core.task_manager.event_bus.should_log_task_level", return_value=True):
            await event_bus.add_log("t1", "INFO", "consume me")
        received = await asyncio.wait_for(queue.get(), timeout=0.5)
        assert received.message == "consume me"

    @pytest.mark.asyncio
    async def test_add_log_subscriber_queue_full_blocks(self, event_bus):
        """With a full asyncio.Queue, await queue.put() blocks (QueueFull not raised by put)."""
        full_queue = asyncio.Queue(maxsize=1)
        await full_queue.put("fill")
        event_bus.subscribers["t1"] = [full_queue]
        with patch("src.core.task_manager.event_bus.should_log_task_level", return_value=True):
            with pytest.raises(asyncio.TimeoutError):
                await asyncio.wait_for(
                    event_bus.add_log("t1", "INFO", "block me"),
                    timeout=0.3,
                )

    @pytest.mark.asyncio
    async def test_add_log_multiple_buffers_different_tasks(self, event_bus):
        with patch("src.core.task_manager.event_bus.should_log_task_level", return_value=True):
            await event_bus.add_log("t1", "INFO", "msg1")
            await event_bus.add_log("t2", "INFO", "msg2")
            assert len(event_bus._log_buffer) == 2
            assert len(event_bus._log_buffer["t1"]) == 1
            assert len(event_bus._log_buffer["t2"]) == 1

    @pytest.mark.asyncio
    async def test_add_log_metadata_and_context(self, event_bus):
        with patch("src.core.task_manager.event_bus.should_log_task_level", return_value=True):
            await event_bus.add_log(
                "t1", "ERROR", "fail",
                metadata={"code": 500},
                context={"env": "prod"},
            )
            entry = event_bus._log_buffer["t1"][0]
            assert entry.metadata == {"code": 500}
            assert entry.context == {"env": "prod"}


# ═══════════════════════════════════════════════════════════════
# Subscribe / Unsubscribe — Logs
# ═══════════════════════════════════════════════════════════════

class TestEventBusLogSubscribers:
    """Verify subscribe_logs/unsubscribe_logs @POST."""

    @pytest.mark.asyncio
    async def test_subscribe_logs_creates_queue(self, event_bus):
        queue = await event_bus.subscribe_logs("t1")
        assert isinstance(queue, asyncio.Queue)
        assert queue.maxsize == QUEUE_MAXSIZE
        assert "t1" in event_bus.subscribers
        assert queue in event_bus.subscribers["t1"]

    @pytest.mark.asyncio
    async def test_unsubscribe_logs_removes_queue(self, event_bus):
        queue = await event_bus.subscribe_logs("t1")
        event_bus.unsubscribe_logs("t1", queue)
        assert "t1" not in event_bus.subscribers

    @pytest.mark.asyncio
    async def test_unsubscribe_nonexistent_task(self, event_bus):
        queue = asyncio.Queue()
        event_bus.unsubscribe_logs("no-such-task", queue)  # must not raise

    @pytest.mark.asyncio
    async def test_unsubscribe_nonexistent_queue(self, event_bus):
        await event_bus.subscribe_logs("t1")
        other_queue = asyncio.Queue()
        event_bus.unsubscribe_logs("t1", other_queue)  # not in list, must not raise
        assert len(event_bus.subscribers["t1"]) == 1

    @pytest.mark.asyncio
    async def test_subscribe_logs_multiple_queues(self, event_bus):
        q1 = await event_bus.subscribe_logs("t1")
        q2 = await event_bus.subscribe_logs("t1")
        assert len(event_bus.subscribers["t1"]) == 2
        event_bus.unsubscribe_logs("t1", q1)
        assert len(event_bus.subscribers["t1"]) == 1


# ═══════════════════════════════════════════════════════════════
# Subscribe / Unsubscribe — Status
# ═══════════════════════════════════════════════════════════════

class TestEventBusStatusSubscribers:
    """Verify subscribe_status/unsubscribe_status @POST."""

    @pytest.mark.asyncio
    async def test_subscribe_status_creates_queue(self, event_bus):
        queue = await event_bus.subscribe_status("t1")
        assert isinstance(queue, asyncio.Queue)
        assert "t1" in event_bus._status_subscribers
        assert queue in event_bus._status_subscribers["t1"]

    @pytest.mark.asyncio
    async def test_unsubscribe_status_removes_queue(self, event_bus):
        queue = await event_bus.subscribe_status("t1")
        event_bus.unsubscribe_status("t1", queue)
        assert "t1" not in event_bus._status_subscribers

    @pytest.mark.asyncio
    async def test_unsubscribe_status_nonexistent(self, event_bus):
        queue = asyncio.Queue()
        event_bus.unsubscribe_status("no-such", queue)

    @pytest.mark.asyncio
    async def test_unsubscribe_status_not_in_list(self, event_bus):
        await event_bus.subscribe_status("t1")
        other = asyncio.Queue()
        event_bus.unsubscribe_status("t1", other)
        assert len(event_bus._status_subscribers["t1"]) == 1


# ═══════════════════════════════════════════════════════════════
# Subscribe / Unsubscribe — Maintenance
# ═══════════════════════════════════════════════════════════════

class TestEventBusMaintenanceSubscribers:
    """Verify subscribe/unsubscribe_maintenance_events @POST."""

    @pytest.mark.asyncio
    async def test_subscribe_maintenance(self, event_bus):
        queue = await event_bus.subscribe_maintenance_events()
        assert queue in event_bus._maintenance_subscribers

    @pytest.mark.asyncio
    async def test_subscribe_maintenance_multiple(self, event_bus):
        q1 = await event_bus.subscribe_maintenance_events()
        q2 = await event_bus.subscribe_maintenance_events()
        assert len(event_bus._maintenance_subscribers) == 2

    def test_unsubscribe_maintenance(self, event_bus):
        queue = asyncio.Queue()
        event_bus._maintenance_subscribers.append(queue)
        event_bus.unsubscribe_maintenance_events(queue)
        assert queue not in event_bus._maintenance_subscribers

    def test_unsubscribe_maintenance_not_found(self, event_bus):
        queue = asyncio.Queue()
        event_bus.unsubscribe_maintenance_events(queue)  # must not raise


# ═══════════════════════════════════════════════════════════════
# Subscribe / Unsubscribe — Global Task Events
# ═══════════════════════════════════════════════════════════════

class TestEventBusTaskEvents:
    """Verify subscribe/unsubscribe_task_events @POST."""

    @pytest.mark.asyncio
    async def test_subscribe_task_events(self, event_bus):
        queue = await event_bus.subscribe_task_events()
        assert queue in event_bus._task_event_subscribers

    def test_unsubscribe_task_events(self, event_bus):
        queue = asyncio.Queue()
        event_bus._task_event_subscribers.append(queue)
        event_bus.unsubscribe_task_events(queue)
        assert queue not in event_bus._task_event_subscribers

    def test_unsubscribe_task_events_not_found(self, event_bus):
        queue = asyncio.Queue()
        event_bus.unsubscribe_task_events(queue)  # must not raise


# ═══════════════════════════════════════════════════════════════
# Broadcast
# ═══════════════════════════════════════════════════════════════

class TestEventBusBroadcast:
    """Verify broadcast_status @POST and broadcast_maintenance_event @POST."""

    @pytest.mark.asyncio
    async def test_broadcast_status_to_per_task(self, event_bus):
        queue = await event_bus.subscribe_status("t1")
        task_dict = {"id": "t1", "status": "RUNNING"}
        await event_bus.broadcast_status("t1", task_dict)
        event = await asyncio.wait_for(queue.get(), timeout=0.5)
        assert event["type"] == "task_status"
        assert event["task_id"] == "t1"
        assert event["task"] == task_dict

    @pytest.mark.asyncio
    async def test_broadcast_status_to_global(self, event_bus):
        global_queue = await event_bus.subscribe_task_events()
        await event_bus.broadcast_status("t1", {"id": "t1"})
        event = await asyncio.wait_for(global_queue.get(), timeout=0.5)
        assert event["type"] == "task_status"

    @pytest.mark.asyncio
    async def test_broadcast_status_queue_full_blocks(self, event_bus):
        full = asyncio.Queue(maxsize=1)
        await full.put("fill")
        event_bus._status_subscribers["t1"] = [full]
        with pytest.raises(asyncio.TimeoutError):
            await asyncio.wait_for(
                event_bus.broadcast_status("t1", {"id": "t1"}),
                timeout=0.3,
            )

    @pytest.mark.asyncio
    async def test_broadcast_status_global_queue_full_blocks(self, event_bus):
        full = asyncio.Queue(maxsize=1)
        await full.put("fill")
        event_bus._task_event_subscribers = [full]
        with pytest.raises(asyncio.TimeoutError):
            await asyncio.wait_for(
                event_bus.broadcast_status("t1", {"id": "t1"}),
                timeout=0.3,
            )

    @pytest.mark.asyncio
    async def test_broadcast_maintenance_event(self, event_bus):
        queue = await event_bus.subscribe_maintenance_events()
        event = {"type": "maintenance", "status": "active"}
        await event_bus.broadcast_maintenance_event(event)
        received = await asyncio.wait_for(queue.get(), timeout=0.5)
        assert received == event

    @pytest.mark.asyncio
    async def test_broadcast_maintenance_queue_full_blocks(self, event_bus):
        full = asyncio.Queue(maxsize=1)
        await full.put("fill")
        event_bus._maintenance_subscribers = [full]
        with pytest.raises(asyncio.TimeoutError):
            await asyncio.wait_for(
                event_bus.broadcast_maintenance_event({"type": "test"}),
                timeout=0.3,
            )

    @pytest.mark.asyncio
    async def test_broadcast_status_no_subscribers(self, event_bus):
        await event_bus.broadcast_status("orphan", {"id": "orphan"})
        # no subscribers — no-op, must not raise


# ═══════════════════════════════════════════════════════════════
# Flush
# ═══════════════════════════════════════════════════════════════

class TestEventBusFlush:
    """Verify flush_task_logs and _flush_logs @POST contracts."""

    @pytest.mark.asyncio
    async def test_flush_task_logs_success(self, event_bus, mock_persistence):
        event_bus._log_buffer["t1"] = [LogEntry(message="flush me")]
        await event_bus.flush_task_logs("t1")
        mock_persistence.add_logs.assert_called_once()
        assert "t1" not in event_bus._log_buffer

    @pytest.mark.asyncio
    async def test_flush_task_logs_empty(self, event_bus, mock_persistence):
        await event_bus.flush_task_logs("t1")
        mock_persistence.add_logs.assert_not_called()

    @pytest.mark.asyncio
    async def test_flush_task_logs_error(self, event_bus, mock_persistence):
        event_bus._log_buffer["t1"] = [LogEntry(message="fail")]
        mock_persistence.add_logs.side_effect = Exception("DB err")
        await event_bus.flush_task_logs("t1")
        # flush_task_logs does NOT re-queue on failure
        assert "t1" not in event_bus._log_buffer

    @pytest.mark.asyncio
    async def test_flush_logs_success(self, event_bus, mock_persistence):
        event_bus._log_buffer["t1"] = [LogEntry(message="a")]
        event_bus._log_buffer["t2"] = [LogEntry(message="b")]
        await event_bus._flush_logs()
        assert mock_persistence.add_logs.call_count == 2
        assert event_bus._log_buffer == {}

    @pytest.mark.asyncio
    async def test_flush_logs_requeues_on_error(self, event_bus, mock_persistence):
        event_bus._log_buffer["t1"] = [LogEntry(message="requeue")]
        mock_persistence.add_logs.side_effect = Exception("DB err")
        await event_bus._flush_logs()
        # _flush_logs re-queues logs on failure
        assert "t1" in event_bus._log_buffer
        assert len(event_bus._log_buffer["t1"]) == 1

    @pytest.mark.asyncio
    async def test_flush_logs_empty(self, event_bus, mock_persistence):
        await event_bus._flush_logs()
        mock_persistence.add_logs.assert_not_called()

    @pytest.mark.asyncio
    async def test_flush_logs_mixed_success_failure(self, event_bus, mock_persistence):
        """One task fails, another succeeds."""
        event_bus._log_buffer["t1"] = [LogEntry(message="ok")]
        event_bus._log_buffer["t2"] = [LogEntry(message="broken")]

        def side_effect(task_id, logs):
            if task_id == "t2":
                raise Exception("DB err")
        mock_persistence.add_logs.side_effect = side_effect

        await event_bus._flush_logs()
        # t2 logs re-queued
        assert "t2" in event_bus._log_buffer
        assert len(event_bus._log_buffer["t2"]) == 1
        # t1 flushed, not re-queued
        assert "t1" not in event_bus._log_buffer

    @pytest.mark.asyncio
    async def test_flush_logs_re_adds_to_existing_buffer(self, event_bus, mock_persistence):
        """If task_id already has entries in buffer after flush failure, they're extended."""
        event_bus._log_buffer["t1"] = [LogEntry(message="first")]
        # Simulate: other code added more logs before flush retried
        def side_effect(task_id, logs):
            event_bus._log_buffer["t1"] = [LogEntry(message="second")]
            raise Exception("DB err")
        mock_persistence.add_logs.side_effect = side_effect

        await event_bus._flush_logs()
        # Original logs extended onto existing buffer
        assert len(event_bus._log_buffer["t1"]) == 2


# ═══════════════════════════════════════════════════════════════
# Async Flusher Loop
# ═══════════════════════════════════════════════════════════════

class TestEventBusFlusherLoop:
    """Verify async_flusher_loop lifecycle: stop event and cancellation."""

    @pytest.mark.asyncio
    async def test_loop_stops_on_event(self, event_bus):
        event_bus._flusher_stop_event.set()
        await event_bus.async_flusher_loop()
        # Clean return — loop exited

    @pytest.mark.asyncio
    async def test_loop_handles_cancelled_error(self, event_bus):
        """CancelledError is caught internally, loop exits cleanly."""
        task = asyncio.create_task(event_bus.async_flusher_loop())
        await asyncio.sleep(0.02)
        task.cancel()
        try:
            await task
        except asyncio.CancelledError:
            pass  # should not propagate; but if it does, test still passes

    @pytest.mark.asyncio
    async def test_loop_flushes_then_waits(self, event_bus, mock_persistence):
        """Loop calls _flush_logs, then waits on stop event."""
        event_bus._log_buffer["t1"] = [LogEntry(message="pre-flush")]
        # Set stop event after short delay so loop does one flush then exits
        async def set_stop():
            await asyncio.sleep(0.05)
            event_bus._flusher_stop_event.set()

        async with asyncio.TaskGroup() as tg:
            tg.create_task(event_bus.async_flusher_loop())
            tg.create_task(set_stop())

        mock_persistence.add_logs.assert_called_once()

    @pytest.mark.asyncio
    async def test_loop_timeout_triggers_continue(self, event_bus, mock_persistence):
        """When wait_for times out, loop continues via the except TimeoutError path (line 104)."""
        event_bus._log_buffer["t1"] = [LogEntry(message="timeout-flush")]
        # Patch LOG_FLUSH_INTERVAL to be very short so timeout happens fast
        original = event_bus.LOG_FLUSH_INTERVAL
        event_bus.LOG_FLUSH_INTERVAL = 0.02

        async def set_stop():
            await asyncio.sleep(0.15)  # longer than 2× LOG_FLUSH_INTERVAL
            event_bus._flusher_stop_event.set()

        async with asyncio.TaskGroup() as tg:
            tg.create_task(event_bus.async_flusher_loop())
            tg.create_task(set_stop())

        # Should have flushed at least once (the continue path runs)
        assert mock_persistence.add_logs.call_count >= 1
        event_bus.LOG_FLUSH_INTERVAL = original


# ═══════════════════════════════════════════════════════════════
# Log Retrieval
# ═══════════════════════════════════════════════════════════════

class TestEventBusGetLogs:
    """Verify get_task_logs @POST for running/pending/completed tasks."""

    def test_get_task_logs_running_returns_memory(self, event_bus):
        task_logs = [LogEntry(message="mem")]
        result = event_bus.get_task_logs("t1", task_status=TaskStatus.RUNNING, task_logs=task_logs)
        assert len(result) == 1
        assert result[0].message == "mem"

    def test_get_task_logs_pending_returns_memory(self, event_bus):
        task_logs = [LogEntry(message="pend")]
        result = event_bus.get_task_logs("t1", task_status=TaskStatus.PENDING, task_logs=task_logs)
        assert len(result) == 1

    def test_get_task_logs_awaiting_mapping_returns_memory(self, event_bus):
        task_logs = [LogEntry(message="map-wait")]
        result = event_bus.get_task_logs("t1", task_status=TaskStatus.AWAITING_MAPPING, task_logs=task_logs)
        assert len(result) == 1

    def test_get_task_logs_awaiting_input_returns_memory(self, event_bus):
        task_logs = [LogEntry(message="input-wait")]
        result = event_bus.get_task_logs("t1", task_status=TaskStatus.AWAITING_INPUT, task_logs=task_logs)
        assert len(result) == 1

    def test_get_task_logs_success_returns_persistence(self, event_bus, mock_persistence):
        mock_log = MagicMock()
        mock_log.timestamp = datetime.now()
        mock_log.level = "INFO"
        mock_log.message = "persisted"
        mock_log.source = "system"
        mock_log.metadata = None
        mock_persistence.get_logs.return_value = [mock_log]

        result = event_bus.get_task_logs("t1", task_status=TaskStatus.SUCCESS)
        assert len(result) == 1
        assert result[0].message == "persisted"
        mock_persistence.get_logs.assert_called()

    def test_get_task_logs_failed_returns_persistence(self, event_bus, mock_persistence):
        mock_persistence.get_logs.return_value = []
        result = event_bus.get_task_logs("t1", task_status=TaskStatus.FAILED)
        assert result == []

    def test_get_task_logs_success_with_log_filter(self, event_bus, mock_persistence):
        mock_persistence.get_logs.return_value = []
        lf = LogFilter(level="ERROR", source="system")
        result = event_bus.get_task_logs("t1", log_filter=lf, task_status=TaskStatus.SUCCESS)
        mock_persistence.get_logs.assert_called_with("t1", lf)
        assert result == []

    def test_get_task_logs_running_returns_empty_if_no_memory(self, event_bus):
        result = event_bus.get_task_logs("t1", task_status=TaskStatus.RUNNING)
        assert result == []

    def test_get_task_log_stats(self, event_bus, mock_persistence):
        mock_persistence.get_log_stats.return_value = LogStats(
            total_count=5, by_level={"INFO": 3}, by_source={"system": 5},
        )
        stats = event_bus.get_task_log_stats("t1")
        assert stats.total_count == 5
        assert stats.by_level == {"INFO": 3}
        mock_persistence.get_log_stats.assert_called_with("t1")

    def test_get_task_log_sources(self, event_bus, mock_persistence):
        mock_persistence.get_sources.return_value = ["system", "plugin"]
        sources = event_bus.get_task_log_sources("t1")
        assert sources == ["system", "plugin"]
        mock_persistence.get_sources.assert_called_with("t1")

    def test_delete_logs_for_tasks(self, event_bus, mock_persistence):
        event_bus.delete_logs_for_tasks(["t1", "t2"])
        mock_persistence.delete_logs_for_tasks.assert_called_with(["t1", "t2"])

    def test_delete_logs_for_tasks_empty(self, event_bus, mock_persistence):
        event_bus.delete_logs_for_tasks([])
        mock_persistence.delete_logs_for_tasks.assert_not_called()

    def test_get_task_logs_completed_status_none(self, event_bus, mock_persistence):
        """When task_status is None, task is not completed — return empty."""
        result = event_bus.get_task_logs("t1", task_status=None)
        assert result == []

# #endregion Test.Core.TaskManager.EventBus
