900 lines
31 KiB
Python
900 lines
31 KiB
Python
"""Tests for non-streaming chat modes."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
import httpx
|
|
import pytest
|
|
from typer.testing import CliRunner
|
|
|
|
from openwebui_cli.main import app
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_config(tmp_path, monkeypatch):
|
|
"""Mock configuration for testing."""
|
|
config_dir = tmp_path / "openwebui"
|
|
config_path = config_dir / "config.yaml"
|
|
|
|
monkeypatch.setattr("openwebui_cli.config.get_config_dir", lambda: config_dir)
|
|
monkeypatch.setattr("openwebui_cli.config.get_config_path", lambda: config_path)
|
|
|
|
# Create default config
|
|
from openwebui_cli.config import Config, save_config
|
|
config = Config()
|
|
save_config(config)
|
|
|
|
return config_path
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_keyring(monkeypatch):
|
|
"""Mock keyring for testing."""
|
|
token_store = {}
|
|
|
|
def get_password(service, key):
|
|
return token_store.get(f"{service}:{key}")
|
|
|
|
def set_password(service, key, password):
|
|
token_store[f"{service}:{key}"] = password
|
|
|
|
monkeypatch.setattr("keyring.get_password", get_password)
|
|
monkeypatch.setattr("keyring.set_password", set_password)
|
|
|
|
|
|
class TestNonStreamingJSON:
|
|
"""Tests for non-streaming mode with --json output."""
|
|
|
|
def test_nonstream_with_json_flag(self, mock_config, mock_keyring):
|
|
"""Test non-streaming response with --json flag."""
|
|
response_data = {
|
|
"id": "chatcmpl-123",
|
|
"object": "chat.completion",
|
|
"created": 1234567890,
|
|
"model": "test-model",
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"message": {
|
|
"role": "assistant",
|
|
"content": "Complete response from model"
|
|
},
|
|
"finish_reason": "stop"
|
|
}
|
|
],
|
|
"usage": {
|
|
"prompt_tokens": 10,
|
|
"completion_tokens": 20,
|
|
"total_tokens": 30
|
|
}
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream", "--json"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify JSON output is printed
|
|
output = json.loads(result.stdout)
|
|
assert "choices" in output
|
|
assert output["choices"][0]["message"]["content"] == "Complete response from model"
|
|
|
|
def test_nonstream_json_with_multiple_fields(self, mock_config, mock_keyring):
|
|
"""Test that --json outputs complete response object."""
|
|
response_data = {
|
|
"id": "test-id-456",
|
|
"model": "gpt-4",
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Detailed response with metadata"
|
|
},
|
|
"finish_reason": "length"
|
|
}
|
|
],
|
|
"usage": {
|
|
"prompt_tokens": 50,
|
|
"completion_tokens": 100,
|
|
"total_tokens": 150
|
|
}
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "gpt-4", "-p", "Test", "--no-stream", "--json"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
output = json.loads(result.stdout)
|
|
assert output["id"] == "test-id-456"
|
|
assert output["usage"]["total_tokens"] == 150
|
|
assert output["choices"][0]["finish_reason"] == "length"
|
|
|
|
def test_nonstream_json_preserves_full_response(self, mock_config, mock_keyring):
|
|
"""Test that complete API response is returned with --json."""
|
|
response_data = {
|
|
"custom_field": "should_be_included",
|
|
"model": "test-model",
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Response content"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Test", "--no-stream", "--json"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
output = json.loads(result.stdout)
|
|
assert output["custom_field"] == "should_be_included"
|
|
|
|
|
|
class TestNonStreamingPlainText:
|
|
"""Tests for non-streaming mode without --json (plain text output)."""
|
|
|
|
def test_nonstream_plain_text_output(self, mock_config, mock_keyring):
|
|
"""Test non-streaming response outputs plain text content."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "This is the plain text response"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Without --json, only the content text should be printed
|
|
assert "This is the plain text response" in result.stdout
|
|
# Should NOT contain JSON structure
|
|
assert "{" not in result.stdout or result.stdout.count("{") == 0
|
|
|
|
def test_nonstream_plain_text_extracts_content_only(self, mock_config, mock_keyring):
|
|
"""Test that plain text mode extracts only message content."""
|
|
response_data = {
|
|
"id": "chatcmpl-789",
|
|
"model": "gpt-3.5",
|
|
"created": 1234567890,
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"message": {
|
|
"role": "assistant",
|
|
"content": "Just the content without metadata"
|
|
},
|
|
"finish_reason": "stop"
|
|
}
|
|
],
|
|
"usage": {
|
|
"prompt_tokens": 20,
|
|
"completion_tokens": 30,
|
|
"total_tokens": 50
|
|
}
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "gpt-3.5", "-p", "Test", "--no-stream"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Only the content should appear
|
|
assert "Just the content without metadata" in result.stdout
|
|
# Metadata should NOT appear
|
|
assert "chatcmpl-789" not in result.stdout
|
|
assert "finish_reason" not in result.stdout
|
|
|
|
def test_nonstream_plain_text_multiline_response(self, mock_config, mock_keyring):
|
|
"""Test plain text output with multiline content."""
|
|
multiline_content = """This is a multiline response.
|
|
It contains multiple lines.
|
|
And some code:
|
|
|
|
```python
|
|
def hello():
|
|
print("world")
|
|
```
|
|
|
|
More text here."""
|
|
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": multiline_content
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Code request", "--no-stream"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "multiline response" in result.stdout
|
|
assert "def hello():" in result.stdout
|
|
assert 'print("world")' in result.stdout
|
|
|
|
|
|
class TestNonStreamingEdgeCases:
|
|
"""Tests for non-streaming mode edge cases."""
|
|
|
|
def test_nonstream_empty_content(self, mock_config, mock_keyring):
|
|
"""Test non-streaming response with empty content."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": ""
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
|
|
def test_nonstream_missing_content_field(self, mock_config, mock_keyring):
|
|
"""Test non-streaming with missing content field."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"role": "assistant"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
|
|
def test_nonstream_special_characters_json(self, mock_config, mock_keyring):
|
|
"""Test non-streaming JSON output with special characters."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Response with special chars: é, ñ, 中文"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream", "--json"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
output = json.loads(result.stdout)
|
|
assert "é" in output["choices"][0]["message"]["content"]
|
|
assert "中文" in output["choices"][0]["message"]["content"]
|
|
|
|
def test_nonstream_json_with_newlines_in_content(self, mock_config, mock_keyring):
|
|
"""Test JSON output correctly handles newlines in content."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Line 1\nLine 2\nLine 3"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream", "--json"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
output = json.loads(result.stdout)
|
|
content = output["choices"][0]["message"]["content"]
|
|
assert "Line 1" in content
|
|
assert "Line 2" in content
|
|
assert "Line 3" in content
|
|
|
|
|
|
class TestNonStreamingWithOptions:
|
|
"""Tests for non-streaming mode with various command options."""
|
|
|
|
def test_nonstream_with_system_prompt(self, mock_config, mock_keyring):
|
|
"""Test non-streaming with system prompt."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Response respecting system prompt"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"chat", "send",
|
|
"-m", "test-model",
|
|
"-p", "Hello",
|
|
"-s", "You are a helpful assistant",
|
|
"--no-stream"
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify system prompt was included in request
|
|
call_args = mock_http_client.post.call_args
|
|
request_body = call_args.kwargs["json"]
|
|
messages = request_body["messages"]
|
|
assert any(msg.get("role") == "system" for msg in messages)
|
|
|
|
def test_nonstream_with_temperature(self, mock_config, mock_keyring):
|
|
"""Test non-streaming with temperature parameter."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Creative response"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"chat", "send",
|
|
"-m", "test-model",
|
|
"-p", "Be creative",
|
|
"-T", "1.5",
|
|
"--no-stream"
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify temperature was included in request
|
|
call_args = mock_http_client.post.call_args
|
|
request_body = call_args.kwargs["json"]
|
|
assert request_body["temperature"] == 1.5
|
|
|
|
def test_nonstream_with_max_tokens(self, mock_config, mock_keyring):
|
|
"""Test non-streaming with max-tokens parameter."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Limited response"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"chat", "send",
|
|
"-m", "test-model",
|
|
"-p", "Be brief",
|
|
"--max-tokens", "50",
|
|
"--no-stream"
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify max_tokens was included in request
|
|
call_args = mock_http_client.post.call_args
|
|
request_body = call_args.kwargs["json"]
|
|
assert request_body["max_tokens"] == 50
|
|
|
|
def test_nonstream_with_chat_id(self, mock_config, mock_keyring):
|
|
"""Test non-streaming continuing an existing conversation."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Continuation response"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"chat", "send",
|
|
"-m", "test-model",
|
|
"-p", "Continue",
|
|
"--chat-id", "chat-abc-123",
|
|
"--no-stream"
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify chat_id was included in request
|
|
call_args = mock_http_client.post.call_args
|
|
request_body = call_args.kwargs["json"]
|
|
assert request_body["chat_id"] == "chat-abc-123"
|
|
|
|
def test_nonstream_with_rag_context(self, mock_config, mock_keyring):
|
|
"""Test non-streaming with RAG file and collection context."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Response using RAG context"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"chat", "send",
|
|
"-m", "test-model",
|
|
"-p", "Search my docs",
|
|
"--file", "file-123",
|
|
"--collection", "coll-456",
|
|
"--no-stream"
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify files context was included
|
|
call_args = mock_http_client.post.call_args
|
|
request_body = call_args.kwargs["json"]
|
|
assert "files" in request_body
|
|
assert len(request_body["files"]) == 2
|
|
|
|
def test_nonstream_post_method_called(self, mock_config, mock_keyring):
|
|
"""Test that POST method is used for non-streaming (not stream)."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Test response"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify post was called, not stream
|
|
mock_http_client.post.assert_called_once()
|
|
mock_http_client.stream.assert_not_called()
|
|
|
|
def test_nonstream_correct_endpoint(self, mock_config, mock_keyring):
|
|
"""Test that correct API endpoint is called."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Test response"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify correct endpoint
|
|
mock_http_client.post.assert_called_once()
|
|
call_args = mock_http_client.post.call_args
|
|
assert call_args[0][0] == "/api/v1/chat/completions"
|
|
|
|
|
|
class TestNonStreamingWithHistory:
|
|
"""Tests for non-streaming mode with conversation history."""
|
|
|
|
def test_nonstream_with_history_file(self, tmp_path, mock_config, mock_keyring):
|
|
"""Test non-streaming with conversation history file."""
|
|
# Create history file
|
|
history_file = tmp_path / "history.json"
|
|
history = [
|
|
{"role": "user", "content": "What is 2+2?"},
|
|
{"role": "assistant", "content": "4"},
|
|
]
|
|
with open(history_file, "w") as f:
|
|
json.dump(history, f)
|
|
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Continuing conversation"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"chat", "send",
|
|
"-m", "test-model",
|
|
"-p", "What about 3+3?",
|
|
"--history-file", str(history_file),
|
|
"--no-stream"
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify history was included in request
|
|
call_args = mock_http_client.post.call_args
|
|
request_body = call_args.kwargs["json"]
|
|
messages = request_body["messages"]
|
|
assert len(messages) == 3 # 2 from history + 1 new user message
|
|
|
|
def test_nonstream_with_history_and_system_prompt(self, tmp_path, mock_config, mock_keyring):
|
|
"""Test non-streaming with history file and system prompt."""
|
|
history_file = tmp_path / "history.json"
|
|
history = [
|
|
{"role": "user", "content": "First question"},
|
|
{"role": "assistant", "content": "First answer"},
|
|
]
|
|
with open(history_file, "w") as f:
|
|
json.dump(history, f)
|
|
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Response with both history and system prompt"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"chat", "send",
|
|
"-m", "test-model",
|
|
"-p", "Second question",
|
|
"-s", "You are a helpful assistant",
|
|
"--history-file", str(history_file),
|
|
"--no-stream",
|
|
"--json"
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
output = json.loads(result.stdout)
|
|
assert output["choices"][0]["message"]["content"]
|
|
|
|
|
|
class TestNonStreamingErrorHandling:
|
|
"""Tests for non-streaming mode error handling."""
|
|
|
|
def test_nonstream_with_stdin(self, mock_config, mock_keyring):
|
|
"""Test non-streaming with stdin input."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Response from stdin"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "--no-stream"],
|
|
input="Hello from stdin\n",
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
|
|
def test_nonstream_request_body_structure(self, mock_config, mock_keyring):
|
|
"""Test that request body has correct structure."""
|
|
response_data = {
|
|
"choices": [
|
|
{
|
|
"message": {
|
|
"content": "Test"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.chat.create_client") as mock_create_client:
|
|
mock_http_client = MagicMock()
|
|
mock_http_client.__enter__.return_value = mock_http_client
|
|
mock_http_client.__exit__.return_value = None
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = response_data
|
|
mock_http_client.post.return_value = mock_response
|
|
|
|
mock_create_client.return_value = mock_http_client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["chat", "send", "-m", "test-model", "-p", "Hello", "--no-stream"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Verify request structure
|
|
call_args = mock_http_client.post.call_args
|
|
request_body = call_args.kwargs["json"]
|
|
assert "model" in request_body
|
|
assert "messages" in request_body
|
|
assert "stream" in request_body
|
|
assert request_body["stream"] is False
|