File size: 4,366 Bytes
18d028b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""Tests for the sentence composer fallback path (no API keys configured)."""

from __future__ import annotations

import pytest

from signbridge.composer.sentence import _naive_join, _strip_quotes, compose_sentence


class TestNaiveJoin:
    def test_empty(self) -> None:
        assert _naive_join([]) == ""

    def test_single_lowercase_gloss(self) -> None:
        assert _naive_join(["hello"]) == "Hello."

    def test_single_letter(self) -> None:
        assert _naive_join(["A"]) == "A."

    def test_fingerspelled_word(self) -> None:
        assert _naive_join(["L", "U", "C", "A", "S"]) == "Lucas."

    def test_fingerspelled_then_gloss(self) -> None:
        # "L U C A S" + "hello" → "Lucas hello."
        assert _naive_join(["L", "U", "C", "A", "S", "hello"]) == "Lucas hello."

    def test_gloss_with_underscore(self) -> None:
        # thank_you should render as "thank you"
        assert "thank you" in _naive_join(["thank_you"]).lower()

    def test_existing_punctuation_not_doubled(self) -> None:
        # Naive join always appends '.', but never twice.
        out = _naive_join(["hello"])
        assert out.count(".") == 1


class TestStripQuotes:
    def test_no_quotes(self) -> None:
        assert _strip_quotes("hello") == "hello"

    def test_double_quotes(self) -> None:
        assert _strip_quotes('"hello"') == "hello"

    def test_single_quotes(self) -> None:
        assert _strip_quotes("'hello'") == "hello"

    def test_internal_quotes_preserved(self) -> None:
        assert _strip_quotes('"he said \'hi\'"') == "he said 'hi'"


class TestComposeSentenceFallback:
    """Without API keys, compose should fall back to _naive_join."""

    def test_empty(self) -> None:
        assert compose_sentence([]) == ""

    def test_falls_back_to_naive(self) -> None:
        # No AMD/OpenAI keys → falls back to _naive_join
        result = compose_sentence(["H", "I"])
        assert result == "Hi."

    def test_falls_back_for_glosses(self) -> None:
        result = compose_sentence(["hello", "name", "L", "U", "C", "A", "S"])
        assert "Lucas" in result
        assert "hello" in result.lower()
        assert "name" in result.lower()


class TestComposeSentenceWithMockProvider:
    """When an API key is set we ATTEMPT to call the provider, but on failure
    we still fall back. We simulate a working provider by monkeypatching the
    OpenAI client."""

    def test_provider_returns_clean_sentence(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("SIGNBRIDGE_PROVIDER", "openai")
        monkeypatch.setenv("OPENAI_API_KEY", "test-key")

        # Stub OpenAI client at the module level
        from signbridge.composer import sentence as sentence_mod

        class _FakeChoice:
            def __init__(self, content: str) -> None:
                self.message = type("M", (), {"content": content})()

        class _FakeResp:
            def __init__(self, content: str) -> None:
                self.choices = [_FakeChoice(content)]

        class _FakeChat:
            def __init__(self, content: str) -> None:
                self._content = content
                self.completions = self

            def create(self, **_: object) -> _FakeResp:
                return _FakeResp(self._content)

        class _FakeClient:
            def __init__(self, content: str) -> None:
                self.chat = _FakeChat(content)

        monkeypatch.setattr(
            sentence_mod,
            "_resolve_client",
            lambda: (_FakeClient("My name is Lucas."), "test-model"),
        )
        out = compose_sentence(["name", "L", "U", "C", "A", "S"])
        assert out == "My name is Lucas."

    def test_provider_failure_falls_back(self, monkeypatch: pytest.MonkeyPatch) -> None:
        from signbridge.composer import sentence as sentence_mod

        class _FailingClient:
            class _FailingChat:
                completions = type(
                    "_C",
                    (),
                    {"create": staticmethod(lambda **_: (_ for _ in ()).throw(RuntimeError("boom")))},
                )()
            chat = _FailingChat()

        monkeypatch.setattr(
            sentence_mod, "_resolve_client", lambda: (_FailingClient(), "test")
        )
        out = compose_sentence(["hello"])
        assert out == "Hello."  # fell back to naive