voting-app/tests/unit/test_gpt_oss_client.py
2026-05-15 15:16:18 -05:00

130 lines
5.0 KiB
Python

"""Test GPT-OSS client with mocked responses."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
from src.gpt_oss_client import GptOssClient
from src.models import Bill
@pytest.fixture
def gpt_client() -> GptOssClient:
return GptOssClient("http://localhost:4000/v1", "test-gpt-key")
@patch.object(GptOssClient, "_request")
def test_generate_summary(mock_request: MagicMock, gpt_client: GptOssClient):
mock_request.return_value = {
"choices": [
{
"message": {
"content": (
"SUMMARY: This bill establishes new regulations for data privacy.\n"
"KEY MEASURES:\n"
"- Requires companies to obtain user consent\n"
"- Mandates data breach notifications\n"
)
}
}
]
}
bill = Bill(bill_id="B100", title="Data Privacy Act", text="Full bill text about data privacy...")
summary = gpt_client.generate_summary(bill)
assert summary.bill_id == "B100"
assert "data privacy" in summary.summary_text.lower()
assert len(summary.key_measures) == 2
assert "Requires companies to obtain user consent" in summary.key_measures
assert "Mandates data breach notifications" in summary.key_measures
assert summary.model_name == "gpt-oss"
@patch.object(GptOssClient, "_request")
def test_generate_summary_no_text(mock_request: MagicMock, gpt_client: GptOssClient):
"""When text is empty and title exists, fallback text is used to call API."""
mock_request.return_value = {}
bill = Bill(bill_id="B100", title="Empty Bill", text="")
summary = gpt_client.generate_summary(bill)
assert summary.summary_text == "Summary generation failed."
assert summary.key_measures == ["Summary generation failed."]
mock_request.assert_called_once()
@patch.object(GptOssClient, "_request")
def test_generate_summary_no_text_uses_title(mock_request: MagicMock, gpt_client: GptOssClient):
"""When text is empty but title/subject exist, uses those for summary prompt."""
mock_request.return_value = {
"choices": [{"message": {"content": "SUMMARY: Some summary.\nKEY MEASURES:\n- Measure 1\n"}}]
}
bill = Bill(bill_id="B100", title="Data Privacy Act", subject="Privacy", text="")
summary = gpt_client.generate_summary(bill)
assert "Some summary" in summary.summary_text
assert summary.key_measures == ["Measure 1"]
mock_request.assert_called_once()
call_args = mock_request.call_args
messages = call_args[0][0]
assert "Data Privacy Act: Privacy" in messages[1]["content"]
@patch.object(GptOssClient, "_request")
def test_generate_summary_failure(mock_request: MagicMock, gpt_client: GptOssClient):
mock_request.return_value = {}
bill = Bill(bill_id="B100", title="Test Bill", text="Some text")
summary = gpt_client.generate_summary(bill)
assert summary.summary_text == "Summary generation failed."
assert summary.key_measures == ["Summary generation failed."]
@patch.object(GptOssClient, "_request")
def test_generate_summary_truncates_long_text(mock_request: MagicMock, gpt_client: GptOssClient):
"""Text longer than 6000 chars is truncated before sending."""
mock_request.return_value = {"choices": [{"message": {"content": "SUMMARY: Short.\nKEY MEASURES:\n- M1\n"}}]}
bill = Bill(bill_id="B100", title="Long Bill", text="x" * 10000)
gpt_client.generate_summary(bill)
call_args = mock_request.call_args
messages = call_args[0][0]
user_content = messages[1]["content"]
assert len(user_content) < 6100
def test_extract_key_measures():
text = (
"SUMMARY: This bill does important things.\n"
"KEY MEASURES:\n"
"- First measure is important\n"
"- Second measure matters too\n"
"Additional context here."
)
measures = GptOssClient._extract_key_measures(text)
assert len(measures) == 2
assert "First measure is important" in measures
assert "Second measure matters too" in measures
def test_extract_key_measures_empty():
"""Returns summary text fallback when no measures found."""
text = "This is just a regular paragraph with no key measures section."
measures = GptOssClient._extract_key_measures(text)
assert len(measures) == 1
assert measures[0] == "This is just a regular paragraph with no key measures section."
def test_extract_key_measures_partial():
"""Handles measures section followed by non-bullet text."""
text = "SUMMARY: Overview.\nKEY MEASURES:\n- First measure\nSome other text\n- This should not be included\n"
measures = GptOssClient._extract_key_measures(text)
assert len(measures) == 1
assert measures[0] == "First measure"
def test_extract_key_measures_fallback_truncated():
"""Fallback summary is truncated to 200 chars."""
text = "x" * 300
measures = GptOssClient._extract_key_measures(text)
assert len(measures) == 1
assert len(measures[0]) == 200