|
| 1 | +"""Tests for the stringflow Python API. |
| 2 | +
|
| 3 | +E2E tests require a running llama-server on localhost:8080. |
| 4 | +Run with: uv run pytest py/stringflow/test_api.py |
| 5 | +""" |
| 6 | + |
| 7 | +import pytest |
| 8 | + |
| 9 | +import stringflow as sf |
| 10 | + |
| 11 | + |
| 12 | +# ============================================================================ |
| 13 | +# Unit tests (no server required) |
| 14 | +# ============================================================================ |
| 15 | + |
| 16 | + |
| 17 | +class TestChatInput: |
| 18 | + def test_string_message_builds_history(self): |
| 19 | + """chat() should accept a string and build a user message tuple.""" |
| 20 | + # We can't call chat() without a server, but we can test the TypeError path |
| 21 | + with pytest.raises(TypeError): |
| 22 | + sf.chat(42) # type: ignore |
| 23 | + |
| 24 | + def test_rejects_invalid_type(self): |
| 25 | + with pytest.raises(TypeError, match="must be str or list"): |
| 26 | + sf.chat(123) # type: ignore |
| 27 | + |
| 28 | + def test_connection_error_without_server(self): |
| 29 | + """chat() should raise ConnectionError when no server is running.""" |
| 30 | + with pytest.raises((ConnectionError, Exception)): |
| 31 | + sf.chat("hi", base_url="http://localhost:19999") |
| 32 | + |
| 33 | + |
| 34 | +class TestDefaults: |
| 35 | + def test_default_url(self): |
| 36 | + assert sf.DEFAULT_URL == "http://localhost:8080" |
| 37 | + |
| 38 | + def test_exports(self): |
| 39 | + assert hasattr(sf, "chat") |
| 40 | + assert hasattr(sf, "health_check") |
| 41 | + assert hasattr(sf, "DEFAULT_URL") |
| 42 | + assert hasattr(sf, "Message") |
| 43 | + |
| 44 | + |
| 45 | +# ============================================================================ |
| 46 | +# E2E tests (require running llama-server on localhost:8080) |
| 47 | +# ============================================================================ |
| 48 | + |
| 49 | + |
| 50 | +@pytest.mark.e2e |
| 51 | +class TestChatE2E: |
| 52 | + def test_simple_chat(self): |
| 53 | + result = sf.chat("Reply with exactly the word 'pong' and nothing else.") |
| 54 | + assert isinstance(result, list) |
| 55 | + assert len(result) == 2 |
| 56 | + assert result[0] == ( |
| 57 | + "user", |
| 58 | + "Reply with exactly the word 'pong' and nothing else.", |
| 59 | + ) |
| 60 | + assert result[1][0] == "assistant" |
| 61 | + assert len(result[1][1]) > 0 |
| 62 | + |
| 63 | + def test_multi_turn(self): |
| 64 | + r1 = sf.chat("My name is TestBot.") |
| 65 | + assert len(r1) == 2 |
| 66 | + r2 = sf.chat("What is my name?", r1) |
| 67 | + assert len(r2) == 4 |
| 68 | + assert r2[2] == ("user", "What is my name?") |
| 69 | + assert r2[3][0] == "assistant" |
| 70 | + |
| 71 | + def test_message_list_input(self): |
| 72 | + messages = [("user", "Reply with exactly 'hello'.")] |
| 73 | + result = sf.chat(messages) |
| 74 | + assert len(result) == 2 |
| 75 | + assert result[1][0] == "assistant" |
| 76 | + |
| 77 | + def test_wire_format_completions(self): |
| 78 | + result = sf.chat("Say hi.", wire_format="completions") |
| 79 | + assert len(result) == 2 |
| 80 | + assert result[1][0] == "assistant" |
| 81 | + |
| 82 | + def test_wire_format_responses(self): |
| 83 | + result = sf.chat("Say hi.", wire_format="responses") |
| 84 | + assert len(result) == 2 |
| 85 | + assert result[1][0] == "assistant" |
| 86 | + |
| 87 | + |
| 88 | +@pytest.mark.e2e |
| 89 | +class TestHealthCheckE2E: |
| 90 | + def test_health_check(self): |
| 91 | + result = sf.health_check() |
| 92 | + assert isinstance(result, str) |
0 commit comments