mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
100% Coverage
This commit is contained in:
@@ -2,9 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar
|
||||
from uuid import uuid4
|
||||
|
||||
import anthropic as anthropic_mod
|
||||
import pytest
|
||||
from roboco.llm import ToonAdapter
|
||||
from roboco.models import MessageType
|
||||
from roboco.models.extraction import (
|
||||
ExtractionConfig,
|
||||
@@ -123,7 +126,8 @@ async def test_extract_splits_on_double_newlines(
|
||||
) -> None:
|
||||
content = "First paragraph here.\n\nSecond paragraph here."
|
||||
result = await svc.extract(_ctx(content))
|
||||
assert len(result.messages) == 2
|
||||
_PARAS = 2
|
||||
assert len(result.messages) == _PARAS
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -145,8 +149,9 @@ async def test_extract_keeps_code_blocks_intact(svc: ExtractionService) -> None:
|
||||
async def test_extract_respects_max_segments() -> None:
|
||||
svc = ExtractionService(ExtractionConfig(max_segments_per_buffer=2))
|
||||
content = "First.\n\nSecond.\n\nThird.\n\nFourth.\n\nFifth."
|
||||
_PARAS = 2
|
||||
result = await svc.extract(_ctx(content))
|
||||
assert len(result.messages) <= 2
|
||||
assert len(result.messages) <= _PARAS
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -205,10 +210,130 @@ async def test_pipeline_swallows_callback_errors() -> None:
|
||||
"""Callback failure should not abort the pipeline."""
|
||||
pipeline = ExtractionPipeline()
|
||||
|
||||
async def bad_callback(msg) -> None:
|
||||
async def bad_callback(_msg) -> None:
|
||||
raise RuntimeError("boom")
|
||||
|
||||
pipeline.on_message(bad_callback)
|
||||
# Should complete without raising despite callback error.
|
||||
result = await pipeline.process_buffer(_ctx("Hello there.\n\nGoodbye."))
|
||||
assert result is not None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mentions extraction (line 209)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_logs_mentions_found(svc: ExtractionService) -> None:
|
||||
"""When @mentions are present, the debug branch fires (line 209)."""
|
||||
content = "@be-dev-1 can you take a look at this code please?"
|
||||
result = await svc.extract(_ctx(content))
|
||||
assert len(result.messages) >= 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_skips_empty_segments_from_segmenter(
|
||||
svc: ExtractionService, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""If _segment_content yields a whitespace-only segment, it's skipped.
|
||||
|
||||
Triggers the defensive `if not segment.strip(): continue` (line 193).
|
||||
"""
|
||||
monkeypatch.setattr(
|
||||
svc, "_segment_content", lambda _content: [" ", "Real content here."]
|
||||
)
|
||||
result = await svc.extract(_ctx("Long enough content to bypass min length."))
|
||||
# Only the non-empty segment becomes a message.
|
||||
assert len(result.messages) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# extract_with_llm (lines 323-404)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_with_llm_falls_back_on_error(
|
||||
svc: ExtractionService, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""If the Anthropic call fails, falls back to pattern-based extract."""
|
||||
|
||||
class _BadClient:
|
||||
def __init__(self, **_kwargs: object) -> None:
|
||||
self.messages = self
|
||||
|
||||
async def create(self, **_kwargs: object) -> object:
|
||||
raise RuntimeError("API down")
|
||||
|
||||
monkeypatch.setattr(anthropic_mod, "AsyncAnthropic", _BadClient)
|
||||
result = await svc.extract_with_llm(_ctx("I'm thinking about this."))
|
||||
# Pattern fallback path executes; messages list is not empty.
|
||||
assert result is not None
|
||||
assert len(result.messages) >= 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_with_llm_parses_response(
|
||||
svc: ExtractionService, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""Mock a successful LLM response and verify it gets parsed."""
|
||||
|
||||
class _Block:
|
||||
text = "json-or-toon"
|
||||
|
||||
class _Resp:
|
||||
content: ClassVar = [_Block()]
|
||||
|
||||
class _OkClient:
|
||||
def __init__(self, **_kwargs: object) -> None:
|
||||
self.messages = self
|
||||
|
||||
async def create(self, **_kwargs: object) -> _Resp:
|
||||
return _Resp()
|
||||
|
||||
def _decode_dicts(_self: ToonAdapter, _text: str) -> list[dict[str, object]]:
|
||||
return [
|
||||
{"type": "reasoning", "content": "thinking", "confidence": 0.9},
|
||||
{"type": "action", "content": "doing", "confidence": 0.95},
|
||||
]
|
||||
|
||||
monkeypatch.setattr(anthropic_mod, "AsyncAnthropic", _OkClient)
|
||||
monkeypatch.setattr(ToonAdapter, "decode", _decode_dicts)
|
||||
|
||||
result = await svc.extract_with_llm(_ctx("Some content for the LLM."))
|
||||
assert result is not None
|
||||
_COUNT = 2
|
||||
assert len(result.messages) == _COUNT
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_with_llm_handles_non_dict_segments(
|
||||
svc: ExtractionService, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""When the toon decoder returns non-dict segments, fallback type."""
|
||||
|
||||
class _Block:
|
||||
text = "raw response"
|
||||
|
||||
class _Resp:
|
||||
content: ClassVar = [_Block()]
|
||||
|
||||
class _OkClient:
|
||||
def __init__(self, **_kwargs: object) -> None:
|
||||
self.messages = self
|
||||
|
||||
async def create(self, **_kwargs: object) -> _Resp:
|
||||
return _Resp()
|
||||
|
||||
# Patch toon to yield strings instead of dicts.
|
||||
def _decode_strings(_self: ToonAdapter, _text: str) -> list[str]:
|
||||
return ["plain string segment", "another"]
|
||||
|
||||
monkeypatch.setattr(anthropic_mod, "AsyncAnthropic", _OkClient)
|
||||
monkeypatch.setattr(ToonAdapter, "decode", _decode_strings)
|
||||
|
||||
result = await svc.extract_with_llm(_ctx("Some content for the LLM."))
|
||||
assert result is not None
|
||||
_COUNT = 2
|
||||
assert len(result.messages) == _COUNT
|
||||
|
||||
Reference in New Issue
Block a user