644 lines
24 KiB
Python
644 lines
24 KiB
Python
"""Tests for model commands."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, Mock, call, patch
|
|
|
|
import httpx
|
|
import pytest
|
|
from typer.testing import CliRunner
|
|
|
|
from openwebui_cli.errors import AuthError, NetworkError, ServerError
|
|
from openwebui_cli.main import app
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_config(tmp_path, monkeypatch):
|
|
"""Use an isolated config directory."""
|
|
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)
|
|
|
|
from openwebui_cli.config import Config, save_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)
|
|
|
|
|
|
def _mock_client(response_json, status_code=200):
|
|
"""Create a mock HTTP client with proper context manager support."""
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
response = Mock()
|
|
response.status_code = status_code
|
|
response.json.return_value = response_json
|
|
response.text = json.dumps(response_json)
|
|
client.get.return_value = response
|
|
client.post.return_value = response
|
|
client.delete.return_value = response
|
|
return client
|
|
|
|
|
|
class TestModelsList:
|
|
"""Tests for 'models list' command."""
|
|
|
|
def test_models_list_success(self, mock_keyring):
|
|
"""Test successful model list display."""
|
|
models_data = {
|
|
"data": [
|
|
{"id": "gpt-4", "name": "GPT-4", "owned_by": "openai"},
|
|
{"id": "gpt-3.5", "name": "GPT-3.5 Turbo", "owned_by": "openai"},
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(models_data)
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "test-token"})
|
|
|
|
assert result.exit_code == 0
|
|
assert "GPT-4" in result.stdout
|
|
assert "GPT-3.5 Turbo" in result.stdout
|
|
assert "openai" in result.stdout
|
|
|
|
def test_models_list_empty(self, mock_keyring):
|
|
"""Test handling of empty model list."""
|
|
models_data = {"data": []}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(models_data)
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "test-token"})
|
|
|
|
assert result.exit_code == 0
|
|
# Should display table header but with no rows
|
|
assert "Available Models" in result.stdout
|
|
|
|
def test_models_list_filter_by_provider(self, mock_keyring):
|
|
"""Test filtering models by provider."""
|
|
models_data = {
|
|
"data": [
|
|
{"id": "gpt-4", "name": "GPT-4", "owned_by": "openai"},
|
|
{"id": "claude", "name": "Claude", "owned_by": "anthropic"},
|
|
{"id": "gpt-3.5", "name": "GPT-3.5 Turbo", "owned_by": "openai"},
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(models_data)
|
|
|
|
result = runner.invoke(
|
|
app, ["models", "list", "--provider", "openai"], obj={"token": "test-token"}
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "GPT-4" in result.stdout
|
|
assert "GPT-3.5 Turbo" in result.stdout
|
|
# Claude should not appear (it's from anthropic)
|
|
assert "Claude" not in result.stdout
|
|
assert "anthropic" not in result.stdout
|
|
|
|
def test_models_list_case_insensitive_filter(self, mock_keyring):
|
|
"""Test case-insensitive provider filtering."""
|
|
models_data = {
|
|
"data": [
|
|
{"id": "gpt-4", "name": "GPT-4", "owned_by": "OpenAI"},
|
|
{"id": "claude", "name": "Claude", "owned_by": "Anthropic"},
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(models_data)
|
|
|
|
result = runner.invoke(
|
|
app, ["models", "list", "--provider", "OPENAI"], obj={"token": "test-token"}
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "GPT-4" in result.stdout
|
|
assert "Claude" not in result.stdout
|
|
|
|
def test_models_list_alternate_response_format(self, mock_keyring):
|
|
"""Test handling of alternate 'models' key instead of 'data'."""
|
|
models_data = {
|
|
"models": [
|
|
{"id": "m1", "name": "Model One", "owned_by": "provider1"},
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(models_data)
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "test-token"})
|
|
|
|
assert result.exit_code == 0
|
|
assert "Model One" in result.stdout
|
|
|
|
def test_models_list_json_format(self, mock_keyring):
|
|
"""Test JSON output format."""
|
|
models_data = {
|
|
"data": [
|
|
{"id": "gpt-4", "name": "GPT-4", "owned_by": "openai"},
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(models_data)
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["--format", "json", "models", "list"],
|
|
obj={"token": "test-token"},
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
# Should be valid JSON output
|
|
output_json = json.loads(result.stdout)
|
|
assert isinstance(output_json, list)
|
|
assert output_json[0]["id"] == "gpt-4"
|
|
|
|
def test_models_list_auth_error(self, mock_keyring):
|
|
"""Test handling of authentication error."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.side_effect = AuthError("Authentication required")
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "invalid"})
|
|
|
|
assert result.exit_code != 0
|
|
# Error is printed to stderr/stdout via the error handler
|
|
assert "Error" in result.stdout or "Error" in result.stderr or "Authentication" in str(result.exception)
|
|
|
|
def test_models_list_network_error(self, mock_keyring):
|
|
"""Test handling of network error."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.side_effect = NetworkError("Connection failed")
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "test-token"})
|
|
|
|
assert result.exit_code != 0
|
|
|
|
def test_models_list_server_error(self, mock_keyring):
|
|
"""Test handling of server error (5xx)."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.side_effect = ServerError("Server error (500)")
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "test-token"})
|
|
|
|
assert result.exit_code != 0
|
|
|
|
|
|
class TestModelsInfo:
|
|
"""Tests for 'models info' command."""
|
|
|
|
def test_models_info_success(self, mock_keyring):
|
|
"""Test successful model info display."""
|
|
info_data = {
|
|
"id": "gpt-4",
|
|
"name": "GPT-4",
|
|
"owned_by": "openai",
|
|
"parameters": "16k context",
|
|
"context_length": 8192,
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(info_data)
|
|
|
|
result = runner.invoke(
|
|
app, ["models", "info", "gpt-4"], obj={"token": "test-token"}
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "GPT-4" in result.stdout
|
|
assert "openai" in result.stdout
|
|
assert "8192" in result.stdout
|
|
|
|
def test_models_info_with_parameters(self, mock_keyring):
|
|
"""Test model info including parameters display."""
|
|
info_data = {
|
|
"id": "gpt-4",
|
|
"name": "GPT-4",
|
|
"owned_by": "openai",
|
|
"parameters": "temperature=0.7, max_tokens=2048",
|
|
"context_length": 8192,
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(info_data)
|
|
|
|
result = runner.invoke(
|
|
app, ["models", "info", "gpt-4"], obj={"token": "test-token"}
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Parameters" in result.stdout
|
|
assert "temperature" in result.stdout
|
|
|
|
def test_models_info_json_format(self, mock_keyring):
|
|
"""Test JSON output format for info."""
|
|
info_data = {
|
|
"id": "gpt-4",
|
|
"name": "GPT-4",
|
|
"owned_by": "openai",
|
|
"context_length": 8192,
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(info_data)
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["--format", "json", "models", "info", "gpt-4"],
|
|
obj={"token": "test-token"},
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
output_json = json.loads(result.stdout)
|
|
assert output_json["id"] == "gpt-4"
|
|
|
|
def test_models_info_not_found(self, mock_keyring):
|
|
"""Test handling of model not found (404)."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.side_effect = ServerError("Not found: Resource not found")
|
|
|
|
result = runner.invoke(
|
|
app, ["models", "info", "nonexistent"], obj={"token": "test-token"}
|
|
)
|
|
|
|
assert result.exit_code != 0
|
|
|
|
def test_models_info_missing_optional_fields(self, mock_keyring):
|
|
"""Test model info with missing optional fields."""
|
|
info_data = {
|
|
"id": "custom-model",
|
|
"name": "Custom Model",
|
|
"owned_by": "local",
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(info_data)
|
|
|
|
result = runner.invoke(
|
|
app, ["models", "info", "custom-model"], obj={"token": "test-token"}
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Custom Model" in result.stdout
|
|
# Should not crash on missing optional fields
|
|
|
|
|
|
class TestModelsPull:
|
|
"""Tests for 'models pull' command."""
|
|
|
|
def test_models_pull_success(self, mock_keyring):
|
|
"""Test successful model pull."""
|
|
pull_response = {
|
|
"status": "success",
|
|
"name": "test-model",
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
|
|
# Mock get response for existing check (404 = doesn't exist)
|
|
not_found_response = Mock()
|
|
not_found_response.status_code = 404
|
|
client.get.return_value = not_found_response
|
|
|
|
# Mock post response for pull
|
|
pull_resp = Mock()
|
|
pull_resp.status_code = 200
|
|
pull_resp.json.return_value = pull_response
|
|
client.post.return_value = pull_resp
|
|
|
|
mock_client_factory.return_value = client
|
|
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "pull", "test-model"]
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully pulled" in result.stdout
|
|
|
|
def test_models_pull_exists_without_force(self, mock_keyring):
|
|
"""Test pull with existing model (no force flag)."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
|
|
# Mock get response for existing check (200 = exists)
|
|
exists_response = Mock()
|
|
exists_response.status_code = 200
|
|
exists_response.json.return_value = {"id": "test-model"}
|
|
client.get.return_value = exists_response
|
|
|
|
mock_client_factory.return_value = client
|
|
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "pull", "test-model"]
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "already exists" in result.stdout
|
|
|
|
def test_models_pull_with_force_flag(self, mock_keyring):
|
|
"""Test pull with force flag to re-pull existing model."""
|
|
pull_response = {"status": "success", "name": "test-model"}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
|
|
# With --force, should skip the check and go straight to pull
|
|
pull_resp = Mock()
|
|
pull_resp.status_code = 200
|
|
pull_resp.json.return_value = pull_response
|
|
client.post.return_value = pull_resp
|
|
|
|
mock_client_factory.return_value = client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["--token", "test-token", "models", "pull", "test-model", "--force"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully pulled" in result.stdout
|
|
|
|
def test_models_pull_with_progress_flag(self, mock_keyring):
|
|
"""Test pull command respects progress flag."""
|
|
pull_response = {"status": "success", "name": "test-model"}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
|
|
not_found_response = Mock()
|
|
not_found_response.status_code = 404
|
|
client.get.return_value = not_found_response
|
|
|
|
pull_resp = Mock()
|
|
pull_resp.status_code = 200
|
|
pull_resp.json.return_value = pull_response
|
|
client.post.return_value = pull_resp
|
|
|
|
mock_client_factory.return_value = client
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["--token", "test-token", "models", "pull", "test-model", "--no-progress"],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
|
|
def test_models_pull_network_error(self, mock_keyring):
|
|
"""Test pull with network error."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.side_effect = NetworkError("Connection failed")
|
|
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "pull", "test-model"]
|
|
)
|
|
|
|
assert result.exit_code != 0
|
|
|
|
def test_models_pull_error_checking_existing_model(self, mock_keyring):
|
|
"""Test pull when checking for existing model fails."""
|
|
pull_response = {"status": "success", "name": "test-model"}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
|
|
# Simulate error when checking if model exists
|
|
check_resp = Mock()
|
|
check_resp.side_effect = Exception("Network error")
|
|
client.get.side_effect = Exception("Network error")
|
|
|
|
# Should proceed with pull despite check failure
|
|
pull_resp = Mock()
|
|
pull_resp.status_code = 200
|
|
pull_resp.json.return_value = pull_response
|
|
client.post.return_value = pull_resp
|
|
|
|
mock_client_factory.return_value = client
|
|
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "pull", "test-model"]
|
|
)
|
|
|
|
# Should succeed despite check error (exception is caught)
|
|
assert result.exit_code == 0
|
|
|
|
def test_models_pull_api_error_response(self, mock_keyring):
|
|
"""Test pull when API returns error response with status != success."""
|
|
# This is a tricky case: status_code is not 200, so the else branch executes
|
|
pull_response = {"status": "pending", "message": "Pull in progress", "error": "Still downloading"}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
|
|
not_found_response = Mock()
|
|
not_found_response.status_code = 404
|
|
client.get.return_value = not_found_response
|
|
|
|
# Return 202 Accepted instead of 200 OK
|
|
pull_resp = Mock()
|
|
pull_resp.status_code = 202
|
|
pull_resp.json.return_value = pull_response
|
|
pull_resp.text = json.dumps(pull_response)
|
|
client.post.return_value = pull_resp
|
|
|
|
mock_client_factory.return_value = client
|
|
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "pull", "test-model"]
|
|
)
|
|
|
|
# With 202 status and status != success, should print the completion message with error
|
|
# But since status_code != 200 and status != success, handle_response may throw
|
|
# Let's check that it either succeeds with completion message or errors
|
|
assert "Pull in progress" in result.stdout or result.exit_code != 0
|
|
|
|
|
|
class TestModelsDelete:
|
|
"""Tests for 'models delete' command."""
|
|
|
|
def test_models_delete_success_with_force(self, mock_keyring):
|
|
"""Test successful model deletion with force flag."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
|
|
delete_resp = Mock()
|
|
delete_resp.status_code = 200
|
|
delete_resp.json.return_value = {"success": True}
|
|
client.delete.return_value = delete_resp
|
|
|
|
mock_client_factory.return_value = client
|
|
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "delete", "test-model", "--force"]
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully deleted" in result.stdout
|
|
|
|
def test_models_delete_requires_confirmation(self, mock_keyring):
|
|
"""Test delete without force flag requires user confirmation."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
mock_client_factory.return_value = client
|
|
|
|
# Simulate user declining confirmation
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "delete", "test-model"], input="n\n"
|
|
)
|
|
|
|
# Should abort with non-zero exit code
|
|
assert result.exit_code != 0
|
|
|
|
def test_models_delete_confirmed(self, mock_keyring):
|
|
"""Test delete with user confirmation."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
|
|
delete_resp = Mock()
|
|
delete_resp.status_code = 200
|
|
delete_resp.json.return_value = {"success": True}
|
|
client.delete.return_value = delete_resp
|
|
|
|
mock_client_factory.return_value = client
|
|
|
|
# Simulate user confirming deletion
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "delete", "test-model"], input="y\n"
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "Successfully deleted" in result.stdout
|
|
|
|
def test_models_delete_not_found(self, mock_keyring):
|
|
"""Test delete of non-existent model."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.side_effect = ServerError("Not found: Resource not found")
|
|
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "delete", "nonexistent", "--force"]
|
|
)
|
|
|
|
assert result.exit_code != 0
|
|
|
|
def test_models_delete_network_error(self, mock_keyring):
|
|
"""Test delete with network error."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.side_effect = NetworkError("Connection failed")
|
|
|
|
result = runner.invoke(
|
|
app, ["--token", "test-token", "models", "delete", "test-model", "--force"]
|
|
)
|
|
|
|
assert result.exit_code != 0
|
|
|
|
|
|
class TestModelsEdgeCases:
|
|
"""Tests for edge cases and error handling."""
|
|
|
|
def test_models_list_with_malformed_response(self, mock_keyring):
|
|
"""Test handling of malformed JSON response."""
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
client = MagicMock()
|
|
client.__enter__.return_value = client
|
|
client.__exit__.return_value = None
|
|
response = Mock()
|
|
response.status_code = 200
|
|
response.json.side_effect = ValueError("Invalid JSON")
|
|
response.text = "Invalid response"
|
|
client.get.return_value = response
|
|
mock_client_factory.return_value = client
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "test-token"})
|
|
|
|
# Should still complete, might print the raw response
|
|
assert result.exit_code == 0 or result.exit_code != 0
|
|
|
|
def test_models_list_fallback_id_field(self, mock_keyring):
|
|
"""Test fallback to 'model' field when 'id' is missing."""
|
|
models_data = {
|
|
"data": [
|
|
{"model": "fallback-id", "name": "Fallback Model", "owned_by": "provider"},
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(models_data)
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "test-token"})
|
|
|
|
assert result.exit_code == 0
|
|
assert "Fallback Model" in result.stdout
|
|
|
|
def test_models_list_fallback_provider_field(self, mock_keyring):
|
|
"""Test fallback to 'provider' field when 'owned_by' is missing."""
|
|
models_data = {
|
|
"data": [
|
|
{"id": "m1", "name": "Model", "provider": "provider-fallback"},
|
|
]
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(models_data)
|
|
|
|
result = runner.invoke(app, ["models", "list"], obj={"token": "test-token"})
|
|
|
|
assert result.exit_code == 0
|
|
assert "provider-fallback" in result.stdout
|
|
|
|
def test_models_info_fallback_id_field(self, mock_keyring):
|
|
"""Test info fallback when id is missing from response."""
|
|
info_data = {
|
|
"name": "Model Name",
|
|
"owned_by": "provider",
|
|
}
|
|
|
|
with patch("openwebui_cli.commands.models.create_client") as mock_client_factory:
|
|
mock_client_factory.return_value = _mock_client(info_data)
|
|
|
|
result = runner.invoke(
|
|
app, ["models", "info", "requested-id"], obj={"token": "test-token"}
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "requested-id" in result.stdout # Should use the requested ID as fallback
|