233 lines
8.1 KiB
Python
233 lines
8.1 KiB
Python
"""
|
|
Tests for minimax_api_client.py - HTTP utilities, auth, polling, downloads.
|
|
"""
|
|
|
|
import json
|
|
import pytest
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
import minimax_api_client as mac
|
|
|
|
|
|
class TestFindMinimaxApiKey:
|
|
"""Test API key discovery."""
|
|
|
|
def test_find_key_from_env(self, monkeypatch):
|
|
monkeypatch.setenv('MINIMAX_API_KEY', 'test-minimax-key')
|
|
with patch.object(mac, 'CENTRALIZED_RESOLVER_AVAILABLE', False):
|
|
assert mac.find_minimax_api_key() == 'test-minimax-key'
|
|
|
|
def test_find_key_not_found(self, monkeypatch):
|
|
monkeypatch.delenv('MINIMAX_API_KEY', raising=False)
|
|
with patch.object(mac, 'CENTRALIZED_RESOLVER_AVAILABLE', False):
|
|
result = mac.find_minimax_api_key()
|
|
assert result is None
|
|
|
|
def test_find_key_via_centralized_resolver(self, monkeypatch):
|
|
mock_resolve = Mock(return_value='resolved-key')
|
|
with patch.object(mac, 'CENTRALIZED_RESOLVER_AVAILABLE', True), \
|
|
patch.object(mac, 'resolve_env', mock_resolve, create=True):
|
|
result = mac.find_minimax_api_key()
|
|
assert result == 'resolved-key'
|
|
mock_resolve.assert_called_once_with(
|
|
'MINIMAX_API_KEY', skill='ai-multimodal'
|
|
)
|
|
|
|
|
|
class TestGetHeaders:
|
|
"""Test header generation."""
|
|
|
|
def test_headers_contain_bearer_token(self):
|
|
headers = mac.get_headers('my-api-key')
|
|
assert headers['Authorization'] == 'Bearer my-api-key'
|
|
assert headers['Content-Type'] == 'application/json'
|
|
|
|
def test_headers_with_different_key(self):
|
|
headers = mac.get_headers('another-key-123')
|
|
assert 'another-key-123' in headers['Authorization']
|
|
|
|
|
|
class TestApiPost:
|
|
"""Test POST request handling."""
|
|
|
|
@patch('minimax_api_client.requests.post')
|
|
def test_successful_post(self, mock_post):
|
|
mock_resp = Mock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json.return_value = {
|
|
"base_resp": {"status_code": 0},
|
|
"data": {"result": "ok"}
|
|
}
|
|
mock_post.return_value = mock_resp
|
|
|
|
result = mac.api_post("test_endpoint", {"key": "val"}, "api-key")
|
|
assert result["data"]["result"] == "ok"
|
|
mock_post.assert_called_once()
|
|
|
|
@patch('minimax_api_client.requests.post')
|
|
def test_http_error_raises(self, mock_post):
|
|
mock_resp = Mock()
|
|
mock_resp.status_code = 401
|
|
mock_resp.text = "Unauthorized"
|
|
mock_post.return_value = mock_resp
|
|
|
|
with pytest.raises(Exception, match="HTTP 401"):
|
|
mac.api_post("endpoint", {}, "bad-key")
|
|
|
|
@patch('minimax_api_client.requests.post')
|
|
def test_minimax_error_code_raises(self, mock_post):
|
|
mock_resp = Mock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json.return_value = {
|
|
"base_resp": {"status_code": 1002, "status_msg": "Rate limit"}
|
|
}
|
|
mock_post.return_value = mock_resp
|
|
|
|
with pytest.raises(Exception, match="code 1002.*Rate limit"):
|
|
mac.api_post("endpoint", {}, "api-key")
|
|
|
|
@patch('minimax_api_client.requests.post')
|
|
def test_custom_timeout(self, mock_post):
|
|
mock_resp = Mock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json.return_value = {"base_resp": {"status_code": 0}}
|
|
mock_post.return_value = mock_resp
|
|
|
|
mac.api_post("endpoint", {}, "key", timeout=300)
|
|
_, kwargs = mock_post.call_args
|
|
assert kwargs['timeout'] == 300
|
|
|
|
@patch('minimax_api_client.requests.post')
|
|
def test_default_timeout_is_120(self, mock_post):
|
|
mock_resp = Mock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json.return_value = {"base_resp": {"status_code": 0}}
|
|
mock_post.return_value = mock_resp
|
|
|
|
mac.api_post("endpoint", {}, "key")
|
|
_, kwargs = mock_post.call_args
|
|
assert kwargs['timeout'] == 120
|
|
|
|
@patch('minimax_api_client.requests.post')
|
|
def test_verbose_prints_url(self, mock_post, capsys):
|
|
mock_resp = Mock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json.return_value = {"base_resp": {"status_code": 0}}
|
|
mock_post.return_value = mock_resp
|
|
|
|
mac.api_post("image_generation", {}, "key", verbose=True)
|
|
captured = capsys.readouterr()
|
|
assert "image_generation" in captured.err
|
|
|
|
|
|
class TestApiGet:
|
|
"""Test GET request handling."""
|
|
|
|
@patch('minimax_api_client.requests.get')
|
|
def test_successful_get(self, mock_get):
|
|
mock_resp = Mock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json.return_value = {"status": "Success", "file_id": "abc"}
|
|
mock_get.return_value = mock_resp
|
|
|
|
result = mac.api_get("query/video_generation", {"task_id": "t1"}, "key")
|
|
assert result["status"] == "Success"
|
|
|
|
@patch('minimax_api_client.requests.get')
|
|
def test_get_http_error(self, mock_get):
|
|
mock_resp = Mock()
|
|
mock_resp.status_code = 500
|
|
mock_resp.text = "Server Error"
|
|
mock_get.return_value = mock_resp
|
|
|
|
with pytest.raises(Exception, match="HTTP 500"):
|
|
mac.api_get("endpoint", {}, "key")
|
|
|
|
|
|
class TestPollAsyncTask:
|
|
"""Test async task polling."""
|
|
|
|
@patch('minimax_api_client.time.sleep')
|
|
@patch('minimax_api_client.api_get')
|
|
def test_poll_success_first_try(self, mock_get, mock_sleep):
|
|
mock_get.return_value = {"status": "Success", "file_id": "f123"}
|
|
|
|
result = mac.poll_async_task("task1", "video_generation", "key")
|
|
assert result["file_id"] == "f123"
|
|
mock_sleep.assert_not_called()
|
|
|
|
@patch('minimax_api_client.time.sleep')
|
|
@patch('minimax_api_client.api_get')
|
|
def test_poll_success_after_processing(self, mock_get, mock_sleep):
|
|
mock_get.side_effect = [
|
|
{"status": "Processing"},
|
|
{"status": "Processing"},
|
|
{"status": "Success", "file_id": "f456"}
|
|
]
|
|
|
|
result = mac.poll_async_task("task2", "video_generation", "key",
|
|
poll_interval=1)
|
|
assert result["file_id"] == "f456"
|
|
assert mock_sleep.call_count == 2
|
|
|
|
@patch('minimax_api_client.time.sleep')
|
|
@patch('minimax_api_client.api_get')
|
|
def test_poll_task_failed(self, mock_get, mock_sleep):
|
|
mock_get.return_value = {"status": "Failed", "error": "bad input"}
|
|
|
|
with pytest.raises(Exception, match="Task failed"):
|
|
mac.poll_async_task("task3", "video_generation", "key")
|
|
|
|
@patch('minimax_api_client.time.sleep')
|
|
@patch('minimax_api_client.api_get')
|
|
def test_poll_timeout(self, mock_get, mock_sleep):
|
|
mock_get.return_value = {"status": "Processing"}
|
|
|
|
with pytest.raises(TimeoutError, match="timed out"):
|
|
mac.poll_async_task("task4", "video_generation", "key",
|
|
poll_interval=1, max_wait=3)
|
|
|
|
|
|
class TestDownloadFile:
|
|
"""Test file download."""
|
|
|
|
@patch('minimax_api_client.requests.get')
|
|
@patch('minimax_api_client.api_get')
|
|
def test_download_success(self, mock_api_get, mock_req_get, tmp_path):
|
|
mock_api_get.return_value = {
|
|
"file": {"download_url": "https://cdn.minimax.io/video.mp4"}
|
|
}
|
|
mock_resp = Mock()
|
|
mock_resp.raise_for_status = Mock()
|
|
mock_resp.iter_content.return_value = [b"video_data"]
|
|
mock_req_get.return_value = mock_resp
|
|
|
|
output = str(tmp_path / "test.mp4")
|
|
result = mac.download_file("file123", "key", output)
|
|
assert result == output
|
|
assert Path(output).exists()
|
|
|
|
@patch('minimax_api_client.api_get')
|
|
def test_download_no_url_raises(self, mock_api_get):
|
|
mock_api_get.return_value = {"file": {}}
|
|
|
|
with pytest.raises(Exception, match="No download URL"):
|
|
mac.download_file("file123", "key", "/tmp/test.mp4")
|
|
|
|
|
|
class TestGetOutputDir:
|
|
"""Test output directory resolution."""
|
|
|
|
def test_returns_path_object(self):
|
|
result = mac.get_output_dir()
|
|
assert isinstance(result, Path)
|
|
|
|
def test_directory_exists(self):
|
|
result = mac.get_output_dir()
|
|
assert result.exists()
|
|
assert result.is_dir()
|