File size: 3,063 Bytes
01a873b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"""
Shared fixtures and mock builders for the Beacon test suite.

Factory functions (not fixtures) build Anthropic mock objects so they can be
called from any scope without fixture injection.
"""
from __future__ import annotations

from typing import Iterator
from unittest.mock import MagicMock

import pytest

from models import PatientProfile


# ---------------------------------------------------------------------------
# Anthropic mock-object factories
# ---------------------------------------------------------------------------

def make_text_block(text: str) -> MagicMock:
    """Return a mock ContentBlock with type='text' and .text=text."""
    block = MagicMock()
    block.type = "text"
    block.text = text
    return block


def make_tool_use_block(name: str, input_data: dict, tool_use_id: str = "tu_001") -> MagicMock:
    """Return a mock ContentBlock with type='tool_use'."""
    block = MagicMock()
    block.type = "tool_use"
    block.name = name
    block.id = tool_use_id
    block.input = input_data
    return block


def make_message(
    content: list,
    stop_reason: str = "end_turn",
    model: str = "claude-sonnet-4-6",
) -> MagicMock:
    """Return a mock Message with .content, .stop_reason, and .model."""
    msg = MagicMock()
    msg.content = content
    msg.stop_reason = stop_reason
    msg.model = model
    return msg


class FakeStream:
    """
    Minimal stand-in for the object returned by ``client.messages.stream()``.

    Usage::

        fake = FakeStream(tokens=["Hello", " world"], final_message=make_message([...]))
        with fake as stream:
            for chunk in stream.text_stream:
                ...
            msg = stream.get_final_message()
    """

    def __init__(self, tokens: list[str], final_message: MagicMock) -> None:
        self._tokens = tokens
        self._final_message = final_message

    def __enter__(self) -> "FakeStream":
        return self

    def __exit__(self, *args: object) -> None:
        pass

    @property
    def text_stream(self) -> Iterator[str]:
        yield from self._tokens

    def get_final_message(self) -> MagicMock:
        return self._final_message


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def als_patient() -> PatientProfile:
    """ALS patient fixture used across most tests."""
    return PatientProfile(
        disease="Amyotrophic Lateral Sclerosis",
        age=52,
        onset_months=18,
        diagnosis_months=12,
        benchmarks={"alsfrs_r": "38", "forced_vital_capacity_percent": "72"},
        zip_code="02115",
        country_code="US",
        lat=42.3370,
        lon=-71.1061,
        radius_miles=100,
        phases=["2", "3"],
        include_eap=False,
        include_observational=False,
        lang="en",
    )


@pytest.fixture
def mock_client() -> MagicMock:
    """Bare MagicMock Anthropic client — callers set side_effects as needed."""
    return MagicMock()