feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
"""测试API客户端"""
|
||||
|
||||
import pytest
|
||||
import time
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
import requests
|
||||
from apitest.client.api_client import APIClient
|
||||
from apitest.models.exceptions import RequestException
|
||||
from apitest.models.test_models import HTTPMethod, PerformanceMetrics
|
||||
|
||||
|
||||
class TestAPIClient:
|
||||
"""测试APIClient类"""
|
||||
|
||||
def test_init(self):
|
||||
"""测试初始化"""
|
||||
client = APIClient("http://localhost:8080", timeout=5000, max_retries=3)
|
||||
assert client.base_url == "http://localhost:8080"
|
||||
assert client.timeout == 5.0
|
||||
assert client.max_retries == 3
|
||||
assert "Content-Type" in client._default_headers
|
||||
assert "Accept" in client._default_headers
|
||||
|
||||
def test_init_with_logger(self):
|
||||
"""测试带日志记录器的初始化"""
|
||||
logger = Mock()
|
||||
client = APIClient("http://localhost:8080", logger=logger)
|
||||
assert client.logger == logger
|
||||
|
||||
def test_set_default_headers(self):
|
||||
"""测试设置默认请求头"""
|
||||
client = APIClient("http://localhost:8080")
|
||||
client.set_default_headers({"X-Custom-Header": "value"})
|
||||
assert "X-Custom-Header" in client._default_headers
|
||||
assert client._default_headers["X-Custom-Header"] == "value"
|
||||
|
||||
def test_set_auth_token(self):
|
||||
"""测试设置认证token"""
|
||||
client = APIClient("http://localhost:8080")
|
||||
client.set_auth_token("test-token")
|
||||
assert "Authorization" in client._default_headers
|
||||
assert client._default_headers["Authorization"] == "Bearer test-token"
|
||||
|
||||
def test_build_url(self):
|
||||
"""测试构建URL"""
|
||||
client = APIClient("http://localhost:8080")
|
||||
url = client._build_url("/api/test")
|
||||
assert url == "http://localhost:8080/api/test"
|
||||
|
||||
def test_build_url_with_leading_slash(self):
|
||||
"""测试构建URL(带前导斜杠)"""
|
||||
client = APIClient("http://localhost:8080")
|
||||
url = client._build_url("api/test")
|
||||
assert url == "http://localhost:8080/api/test"
|
||||
|
||||
def test_merge_headers(self):
|
||||
"""测试合并请求头"""
|
||||
client = APIClient("http://localhost:8080")
|
||||
client.set_default_headers({"X-Default": "default"})
|
||||
merged = client._merge_headers({"X-Custom": "custom"})
|
||||
assert merged["X-Default"] == "default"
|
||||
assert merged["X-Custom"] == "custom"
|
||||
|
||||
def test_merge_headers_override(self):
|
||||
"""测试合并请求头(覆盖)"""
|
||||
client = APIClient("http://localhost:8080")
|
||||
client.set_default_headers({"Content-Type": "application/xml"})
|
||||
merged = client._merge_headers({"Content-Type": "application/json"})
|
||||
assert merged["Content-Type"] == "application/json"
|
||||
|
||||
@patch('requests.Session.get')
|
||||
def test_request_success(self, mock_get):
|
||||
"""测试成功请求"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"success": True}
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.request(HTTPMethod.GET, "/api/test")
|
||||
|
||||
assert result["status_code"] == 200
|
||||
assert result["response_body"] == {"success": True}
|
||||
assert "performance" in result
|
||||
|
||||
@patch('requests.Session.get')
|
||||
def test_request_with_params(self, mock_get):
|
||||
"""测试带参数的请求"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"success": True}
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.request(HTTPMethod.GET, "/api/test", params={"page": 1, "size": 10})
|
||||
|
||||
assert result["status_code"] == 200
|
||||
assert result["response_body"] == {"success": True}
|
||||
mock_get.assert_called_once()
|
||||
|
||||
@patch('requests.Session.post')
|
||||
def test_request_with_body(self, mock_post):
|
||||
"""测试带请求体的请求"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"success": True}
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.request(HTTPMethod.POST, "/api/test", body={"name": "test"})
|
||||
|
||||
assert result["status_code"] == 200
|
||||
assert result["response_body"] == {"success": True}
|
||||
mock_post.assert_called_once()
|
||||
|
||||
@patch('requests.Session.get')
|
||||
def test_request_with_headers(self, mock_get):
|
||||
"""测试带自定义请求头的请求"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"success": True}
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.request(HTTPMethod.GET, "/api/test", headers={"X-Custom": "value"})
|
||||
|
||||
assert result["status_code"] == 200
|
||||
assert result["response_body"] == {"success": True}
|
||||
mock_get.assert_called_once()
|
||||
|
||||
@patch('requests.Session.get')
|
||||
def test_request_retry_on_timeout(self, mock_get):
|
||||
"""测试超时重试"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"success": True}
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
|
||||
mock_get.side_effect = [
|
||||
requests.Timeout(),
|
||||
requests.Timeout(),
|
||||
mock_response
|
||||
]
|
||||
|
||||
client = APIClient("http://localhost:8080", max_retries=3)
|
||||
result = client.request(HTTPMethod.GET, "/api/test")
|
||||
|
||||
assert result["status_code"] == 200
|
||||
assert result["response_body"] == {"success": True}
|
||||
assert mock_get.call_count == 3
|
||||
|
||||
@patch('requests.Session.get')
|
||||
def test_request_failure_after_retries(self, mock_get):
|
||||
"""测试重试后仍然失败"""
|
||||
mock_get.side_effect = requests.Timeout()
|
||||
|
||||
client = APIClient("http://localhost:8080", max_retries=2)
|
||||
|
||||
with pytest.raises(RequestException):
|
||||
client.request(HTTPMethod.GET, "/api/test")
|
||||
|
||||
@patch('requests.Session.get')
|
||||
def test_request_invalid_json(self, mock_get):
|
||||
"""测试无效JSON响应"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.side_effect = ValueError("Invalid JSON")
|
||||
mock_response.text = "plain text"
|
||||
mock_response.headers = {"Content-Type": "text/plain"}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.request(HTTPMethod.GET, "/api/test")
|
||||
|
||||
assert result["status_code"] == 200
|
||||
assert result["response_body"] == "plain text"
|
||||
|
||||
@patch('requests.Session.get')
|
||||
def test_get(self, mock_get):
|
||||
"""测试GET请求"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"success": True}
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.get("/api/test")
|
||||
|
||||
assert result["status_code"] == 200
|
||||
assert result["response_body"] == {"success": True}
|
||||
|
||||
@patch('requests.Session.post')
|
||||
def test_post(self, mock_post):
|
||||
"""测试POST请求"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 201
|
||||
mock_response.json.return_value = {"success": True}
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.post("/api/test", {"name": "test"})
|
||||
|
||||
assert result["status_code"] == 201
|
||||
assert result["response_body"] == {"success": True}
|
||||
|
||||
@patch('requests.Session.put')
|
||||
def test_put(self, mock_put):
|
||||
"""测试PUT请求"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"success": True}
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_put.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.put("/api/test/1", {"name": "updated"})
|
||||
|
||||
assert result["status_code"] == 200
|
||||
assert result["response_body"] == {"success": True}
|
||||
|
||||
@patch('requests.Session.delete')
|
||||
def test_delete(self, mock_delete):
|
||||
"""测试DELETE请求"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 204
|
||||
mock_response.headers = {"Content-Type": "application/json"}
|
||||
mock_delete.return_value = mock_response
|
||||
|
||||
client = APIClient("http://localhost:8080")
|
||||
result = client.delete("/api/test/1")
|
||||
|
||||
assert result["status_code"] == 204
|
||||
|
||||
def test_close(self):
|
||||
"""测试关闭客户端"""
|
||||
client = APIClient("http://localhost:8080")
|
||||
session = client._session
|
||||
client.close()
|
||||
assert client._session == session
|
||||
@@ -0,0 +1,370 @@
|
||||
"""测试认证管理器"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from apitest.client.auth_manager import AuthManager
|
||||
from apitest.models.exceptions import AuthException
|
||||
import time
|
||||
|
||||
|
||||
class TestAuthManager:
|
||||
"""测试AuthManager类"""
|
||||
|
||||
def test_init(self):
|
||||
"""测试初始化"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
assert auth_manager.base_url == "http://localhost:8080"
|
||||
assert auth_manager.credentials == credentials
|
||||
assert auth_manager.logger == logger
|
||||
assert auth_manager._token is None
|
||||
assert auth_manager._refresh_token is None
|
||||
assert auth_manager._token_expiry is None
|
||||
assert auth_manager._login_endpoint == "/sys/auth/login"
|
||||
|
||||
def test_set_login_endpoint(self):
|
||||
"""测试设置登录端点"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager.set_login_endpoint("/api/custom/login")
|
||||
assert auth_manager._login_endpoint == "/api/custom/login"
|
||||
|
||||
def test_set_credentials(self):
|
||||
"""测试设置认证凭据"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager.set_credentials("newuser", "newpassword")
|
||||
assert auth_manager.credentials == {"username": "newuser", "password": "newpassword"}
|
||||
|
||||
def test_set_token(self):
|
||||
"""测试设置token"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager.set_token("test-token", 3600)
|
||||
assert auth_manager._token == "test-token"
|
||||
assert auth_manager._token_expiry is not None
|
||||
|
||||
def test_get_token(self):
|
||||
"""测试获取token"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager._token = "test-token"
|
||||
assert auth_manager.get_token() == "test-token"
|
||||
|
||||
def test_get_token_none(self):
|
||||
"""测试获取token(未设置)"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
assert auth_manager.get_token() is None
|
||||
|
||||
def test_is_token_valid(self):
|
||||
"""测试token有效性检查"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager.set_token("test-token", 3600)
|
||||
assert auth_manager.is_token_valid() is True
|
||||
|
||||
def test_is_token_valid_expired(self):
|
||||
"""测试token有效性检查(已过期)"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager.set_token("test-token", -1)
|
||||
assert auth_manager.is_token_valid() is False
|
||||
|
||||
def test_is_token_valid_none(self):
|
||||
"""测试token有效性检查(未设置)"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
assert auth_manager.is_token_valid() is False
|
||||
|
||||
@patch('requests.post')
|
||||
def test_login_success(self, mock_post):
|
||||
"""测试成功登录"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"data": {
|
||||
"token": "test-token",
|
||||
"refreshToken": "refresh-token",
|
||||
"expiresIn": 3600
|
||||
}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
result = auth_manager.login()
|
||||
|
||||
assert result["data"]["token"] == "test-token"
|
||||
assert auth_manager._token == "test-token"
|
||||
assert auth_manager._refresh_token == "refresh-token"
|
||||
assert auth_manager._token_expiry is not None
|
||||
|
||||
@patch('requests.post')
|
||||
def test_login_failure(self, mock_post):
|
||||
"""测试登录失败"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 401
|
||||
mock_response.json.return_value = {
|
||||
"data": {"error": "Invalid credentials"}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "wrong-password"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
|
||||
with pytest.raises(AuthException):
|
||||
auth_manager.login()
|
||||
|
||||
@patch('requests.post')
|
||||
def test_login_no_token_in_response(self, mock_post):
|
||||
"""测试登录(响应中无token)"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"data": {"message": "success"}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
|
||||
with pytest.raises(AuthException):
|
||||
auth_manager.login()
|
||||
|
||||
@patch('requests.post')
|
||||
def test_login_http_error(self, mock_post):
|
||||
"""测试登录(HTTP错误)"""
|
||||
import requests
|
||||
mock_post.side_effect = requests.RequestException("Network error")
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
|
||||
with pytest.raises(AuthException):
|
||||
auth_manager.login()
|
||||
|
||||
@patch('requests.post')
|
||||
def test_refresh_token_success(self, mock_post):
|
||||
"""测试成功刷新token"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"data": {
|
||||
"token": "new-token",
|
||||
"refreshToken": "new-refresh-token",
|
||||
"expiresIn": 3600
|
||||
}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager._refresh_token = "old-refresh-token"
|
||||
result = auth_manager.refresh_token()
|
||||
|
||||
assert result is True
|
||||
assert auth_manager._token == "new-token"
|
||||
assert auth_manager._refresh_token == "new-refresh-token"
|
||||
|
||||
@patch('requests.post')
|
||||
def test_refresh_token_failure(self, mock_post):
|
||||
"""测试刷新token失败"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 401
|
||||
mock_response.json.return_value = {
|
||||
"data": {"error": "Invalid refresh token"}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager._refresh_token = "old-refresh-token"
|
||||
result = auth_manager.refresh_token()
|
||||
|
||||
assert result is False
|
||||
|
||||
def test_refresh_token_no_refresh_token(self):
|
||||
"""测试刷新token(无refresh token)"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
result = auth_manager.refresh_token()
|
||||
|
||||
assert result is False
|
||||
|
||||
@patch('requests.post')
|
||||
def test_refresh_token_exception(self, mock_post):
|
||||
"""测试刷新token异常"""
|
||||
mock_post.side_effect = Exception("Network error")
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager._refresh_token = "old-refresh-token"
|
||||
result = auth_manager.refresh_token()
|
||||
|
||||
assert result is False
|
||||
|
||||
def test_logout(self):
|
||||
"""测试登出"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager._token = "test-token"
|
||||
auth_manager._refresh_token = "refresh-token"
|
||||
auth_manager._token_expiry = time.time() + 3600
|
||||
|
||||
auth_manager.logout()
|
||||
|
||||
assert auth_manager._token is None
|
||||
assert auth_manager._refresh_token is None
|
||||
assert auth_manager._token_expiry is None
|
||||
|
||||
@patch('requests.post')
|
||||
def test_ensure_authenticated_with_valid_token(self, mock_post):
|
||||
"""测试确保已认证(有效token)"""
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager.set_token("test-token", 3600)
|
||||
|
||||
token = auth_manager.ensure_authenticated()
|
||||
assert token == "test-token"
|
||||
assert mock_post.call_count == 0
|
||||
|
||||
@patch('requests.post')
|
||||
def test_ensure_authenticated_with_expired_token(self, mock_post):
|
||||
"""测试确保已认证(过期token,刷新成功)"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"data": {
|
||||
"token": "new-token",
|
||||
"refreshToken": "new-refresh-token",
|
||||
"expiresIn": 3600
|
||||
}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager._token = "old-token"
|
||||
auth_manager._refresh_token = "old-refresh-token"
|
||||
from datetime import datetime, timedelta
|
||||
auth_manager._token_expiry = datetime.now() - timedelta(seconds=100)
|
||||
|
||||
token = auth_manager.ensure_authenticated()
|
||||
assert token == "new-token"
|
||||
assert auth_manager._token == "new-token"
|
||||
assert auth_manager._refresh_token == "new-refresh-token"
|
||||
|
||||
@patch('requests.post')
|
||||
def test_ensure_authenticated_no_token(self, mock_post):
|
||||
"""测试确保已认证(无token,登录成功)"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"data": {
|
||||
"token": "test-token",
|
||||
"refreshToken": "refresh-token",
|
||||
"expiresIn": 3600
|
||||
}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
|
||||
token = auth_manager.ensure_authenticated()
|
||||
assert token == "test-token"
|
||||
|
||||
@patch('requests.post')
|
||||
def test_ensure_authenticated_refresh_failed_login(self, mock_post):
|
||||
"""测试确保已认证(刷新失败,重新登录)"""
|
||||
refresh_response = Mock()
|
||||
refresh_response.status_code = 401
|
||||
refresh_response.json.return_value = {
|
||||
"data": {"error": "Invalid refresh token"}
|
||||
}
|
||||
|
||||
login_response = Mock()
|
||||
login_response.status_code = 200
|
||||
login_response.json.return_value = {
|
||||
"data": {
|
||||
"token": "test-token",
|
||||
"refreshToken": "refresh-token",
|
||||
"expiresIn": 3600
|
||||
}
|
||||
}
|
||||
|
||||
mock_post.side_effect = [refresh_response, login_response]
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
auth_manager._token = "old-token"
|
||||
auth_manager._refresh_token = "old-refresh-token"
|
||||
from datetime import datetime, timedelta
|
||||
auth_manager._token_expiry = datetime.now() - timedelta(seconds=100)
|
||||
|
||||
token = auth_manager.ensure_authenticated()
|
||||
assert token == "test-token"
|
||||
assert auth_manager._token == "test-token"
|
||||
|
||||
@patch('requests.post')
|
||||
def test_ensure_authenticated_all_failed(self, mock_post):
|
||||
"""测试确保已认证(全部失败)"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 401
|
||||
mock_response.json.return_value = {
|
||||
"data": {"error": "Authentication failed"}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
|
||||
with pytest.raises(AuthException):
|
||||
auth_manager.ensure_authenticated()
|
||||
|
||||
@patch('requests.post')
|
||||
def test_get_auth_headers(self, mock_post):
|
||||
"""测试获取认证请求头"""
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"data": {
|
||||
"token": "test-token",
|
||||
"refreshToken": "refresh-token",
|
||||
"expiresIn": 3600
|
||||
}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
logger = Mock()
|
||||
credentials = {"username": "admin", "password": "password123"}
|
||||
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
|
||||
|
||||
headers = auth_manager.get_auth_headers()
|
||||
|
||||
assert headers["Authorization"] == "Bearer test-token"
|
||||
assert headers["Content-Type"] == "application/json"
|
||||
@@ -0,0 +1,383 @@
|
||||
"""CLI接口单元测试"""
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from pathlib import Path
|
||||
import json
|
||||
import tempfile
|
||||
from apitest.cli_module import cli
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner():
|
||||
"""创建CLI测试运行器"""
|
||||
return CliRunner()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_test_cases(tmp_path):
|
||||
"""创建示例测试用例文件"""
|
||||
test_data = [
|
||||
{
|
||||
"id": "TC001",
|
||||
"name": "测试用例1",
|
||||
"description": "测试用例1",
|
||||
"module": "test",
|
||||
"endpoint": "/api/test1",
|
||||
"method": "GET",
|
||||
"headers": {},
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"id": "TC002",
|
||||
"name": "测试用例2",
|
||||
"description": "测试用例2",
|
||||
"module": "user",
|
||||
"endpoint": "/api/user",
|
||||
"method": "POST",
|
||||
"headers": {},
|
||||
"enabled": True,
|
||||
"tags": ["smoke", "regression"],
|
||||
"priority": 1
|
||||
}
|
||||
]
|
||||
|
||||
test_file = tmp_path / "test_cases.json"
|
||||
with open(test_file, "w", encoding="utf-8") as f:
|
||||
json.dump(test_data, f)
|
||||
|
||||
return test_file
|
||||
|
||||
|
||||
def test_cli_version(runner):
|
||||
"""测试CLI版本命令"""
|
||||
result = runner.invoke(cli, ["--version"])
|
||||
assert result.exit_code == 0
|
||||
assert "1.0.0" in result.output
|
||||
|
||||
|
||||
def test_cli_help(runner):
|
||||
"""测试CLI帮助命令"""
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "黑盒API测试工具" in result.output
|
||||
|
||||
|
||||
def test_list_command(runner, sample_test_cases):
|
||||
"""测试列出测试用例命令"""
|
||||
result = runner.invoke(cli, ["list", str(sample_test_cases)])
|
||||
assert result.exit_code == 0
|
||||
assert "测试用例总数: 2" in result.output
|
||||
assert "TC001" in result.output
|
||||
assert "TC002" in result.output
|
||||
|
||||
|
||||
def test_list_command_with_module_filter(runner, sample_test_cases):
|
||||
"""测试列出测试用例命令(模块过滤)"""
|
||||
result = runner.invoke(cli, ["list", str(sample_test_cases), "--module", "test"])
|
||||
assert result.exit_code == 0
|
||||
assert "TC001" in result.output
|
||||
assert "TC002" not in result.output
|
||||
|
||||
|
||||
def test_list_command_with_tag_filter(runner, sample_test_cases):
|
||||
"""测试列出测试用例命令(标签过滤)"""
|
||||
result = runner.invoke(cli, ["list", str(sample_test_cases), "--tag", "smoke"])
|
||||
assert result.exit_code == 0
|
||||
assert "TC001" not in result.output
|
||||
assert "TC002" in result.output
|
||||
|
||||
|
||||
def test_list_command_with_priority_filter(runner, sample_test_cases):
|
||||
"""测试列出测试用例命令(优先级过滤)"""
|
||||
result = runner.invoke(cli, ["list", str(sample_test_cases), "--priority", "1"])
|
||||
assert result.exit_code == 0
|
||||
assert "TC001" not in result.output
|
||||
assert "TC002" in result.output
|
||||
|
||||
|
||||
def test_validate_command(runner, sample_test_cases):
|
||||
"""测试验证测试用例命令"""
|
||||
result = runner.invoke(cli, ["validate", str(sample_test_cases)])
|
||||
assert result.exit_code == 0
|
||||
assert "验证通过" in result.output
|
||||
|
||||
|
||||
def test_validate_command_invalid_file(runner, tmp_path):
|
||||
"""测试验证测试用例命令(无效文件)"""
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
f.write("{ invalid json }")
|
||||
|
||||
result = runner.invoke(cli, ["validate", str(invalid_file)])
|
||||
assert result.exit_code != 0
|
||||
|
||||
|
||||
def test_config_command(runner):
|
||||
"""测试配置命令"""
|
||||
result = runner.invoke(cli, ["config"])
|
||||
assert result.exit_code == 0
|
||||
assert "当前配置" in result.output
|
||||
|
||||
|
||||
def test_config_command_with_key(runner):
|
||||
"""测试配置命令(指定键)"""
|
||||
result = runner.invoke(cli, ["config", "--key", "target.base_url"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_run_command_no_test_cases(runner):
|
||||
"""测试运行命令(无测试用例)"""
|
||||
result = runner.invoke(cli, ["run"])
|
||||
assert result.exit_code != 0
|
||||
assert "请指定测试用例文件路径" in result.output or "错误:" in result.output
|
||||
|
||||
|
||||
def test_run_command_help(runner):
|
||||
"""测试运行命令帮助"""
|
||||
result = runner.invoke(cli, ["run", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "运行测试用例" in result.output
|
||||
|
||||
|
||||
def test_list_command_help(runner):
|
||||
"""测试列出命令帮助"""
|
||||
result = runner.invoke(cli, ["list", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "列出测试用例" in result.output
|
||||
|
||||
|
||||
def test_validate_command_help(runner):
|
||||
"""测试验证命令帮助"""
|
||||
result = runner.invoke(cli, ["validate", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "验证测试用例文件" in result.output
|
||||
|
||||
|
||||
def test_config_command_help(runner):
|
||||
"""测试配置命令帮助"""
|
||||
result = runner.invoke(cli, ["config", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "查看配置" in result.output
|
||||
|
||||
|
||||
def test_run_command_with_verbose(runner, sample_test_cases):
|
||||
"""测试运行命令(详细模式)"""
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--verbose"])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_stop_on_failure(runner, sample_test_cases):
|
||||
"""测试运行命令(失败时停止)"""
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--stop-on-failure"])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_no_report(runner, sample_test_cases):
|
||||
"""测试运行命令(不生成报告)"""
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--no-report"])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_report_format_json(runner, sample_test_cases):
|
||||
"""测试运行命令(JSON报告格式)"""
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--report-format", "json"])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_report_path(runner, sample_test_cases, tmp_path):
|
||||
"""测试运行命令(指定报告路径)"""
|
||||
report_path = tmp_path / "report.html"
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--report-path", str(report_path)])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_module_filter(runner, sample_test_cases):
|
||||
"""测试运行命令(模块过滤)"""
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--module", "test"])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_tag_filter(runner, sample_test_cases):
|
||||
"""测试运行命令(标签过滤)"""
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--tag", "smoke"])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_priority_filter(runner, sample_test_cases):
|
||||
"""测试运行命令(优先级过滤)"""
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--priority", "1"])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_no_matching_cases(runner, sample_test_cases):
|
||||
"""测试运行命令(无匹配测试用例)"""
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--module", "nonexistent"])
|
||||
assert result.exit_code == 0
|
||||
assert "没有匹配的测试用例" in result.output or "警告" in result.output
|
||||
|
||||
|
||||
def test_run_command_with_test_data(runner, sample_test_cases, tmp_path):
|
||||
"""测试运行命令(带测试数据)"""
|
||||
test_data = [
|
||||
{"username": "test1", "password": "pass1"},
|
||||
{"username": "test2", "password": "pass2"}
|
||||
]
|
||||
test_data_file = tmp_path / "test_data.csv"
|
||||
with open(test_data_file, "w", encoding="utf-8") as f:
|
||||
f.write("username,password\n")
|
||||
f.write("test1,pass1\n")
|
||||
f.write("test2,pass2\n")
|
||||
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--test-data", str(test_data_file)])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_run_command_with_exception(runner, tmp_path):
|
||||
"""测试运行命令(异常处理)"""
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
f.write("{ invalid json }")
|
||||
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(invalid_file)])
|
||||
assert result.exit_code == 1
|
||||
assert "执行测试时出错" in result.output or "错误:" in result.output
|
||||
|
||||
|
||||
def test_run_command_with_verbose_exception(runner, tmp_path):
|
||||
"""测试运行命令(详细模式异常)"""
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
f.write("{ invalid json }")
|
||||
|
||||
result = runner.invoke(cli, ["run", "--test-cases", str(invalid_file), "--verbose"])
|
||||
assert result.exit_code == 1
|
||||
|
||||
|
||||
def test_list_command_with_exception(runner, tmp_path):
|
||||
"""测试列出命令(异常处理)"""
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
f.write("{ invalid json }")
|
||||
|
||||
result = runner.invoke(cli, ["list", str(invalid_file)])
|
||||
assert result.exit_code == 1
|
||||
assert "列出测试用例时出错" in result.output or "错误:" in result.output
|
||||
|
||||
|
||||
def test_validate_command_with_missing_fields(runner, tmp_path):
|
||||
"""测试验证命令(缺少字段)"""
|
||||
invalid_test_data = [
|
||||
{
|
||||
"id": "",
|
||||
"name": "测试用例",
|
||||
"endpoint": "/api/test",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
json.dump(invalid_test_data, f)
|
||||
|
||||
result = runner.invoke(cli, ["validate", str(invalid_file)])
|
||||
assert result.exit_code == 1
|
||||
assert "验证失败" in result.output
|
||||
|
||||
|
||||
def test_validate_command_with_missing_id(runner, tmp_path):
|
||||
"""测试验证命令(缺少ID)"""
|
||||
invalid_test_data = [
|
||||
{
|
||||
"name": "测试用例",
|
||||
"endpoint": "/api/test",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
json.dump(invalid_test_data, f)
|
||||
|
||||
result = runner.invoke(cli, ["validate", str(invalid_file)])
|
||||
assert result.exit_code == 1
|
||||
|
||||
|
||||
def test_validate_command_with_missing_name(runner, tmp_path):
|
||||
"""测试验证命令(缺少名称)"""
|
||||
invalid_test_data = [
|
||||
{
|
||||
"id": "TC001",
|
||||
"endpoint": "/api/test",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
json.dump(invalid_test_data, f)
|
||||
|
||||
result = runner.invoke(cli, ["validate", str(invalid_file)])
|
||||
assert result.exit_code == 1
|
||||
|
||||
|
||||
def test_validate_command_with_missing_endpoint(runner, tmp_path):
|
||||
"""测试验证命令(缺少端点)"""
|
||||
invalid_test_data = [
|
||||
{
|
||||
"id": "TC001",
|
||||
"name": "测试用例",
|
||||
"method": "GET"
|
||||
}
|
||||
]
|
||||
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
json.dump(invalid_test_data, f)
|
||||
|
||||
result = runner.invoke(cli, ["validate", str(invalid_file)])
|
||||
assert result.exit_code == 1
|
||||
|
||||
|
||||
def test_validate_command_with_exception(runner, tmp_path):
|
||||
"""测试验证命令(异常处理)"""
|
||||
invalid_file = tmp_path / "invalid.json"
|
||||
with open(invalid_file, "w", encoding="utf-8") as f:
|
||||
f.write("{ invalid json }")
|
||||
|
||||
result = runner.invoke(cli, ["validate", str(invalid_file)])
|
||||
assert result.exit_code == 1
|
||||
assert "验证测试用例时出错" in result.output or "错误:" in result.output
|
||||
|
||||
|
||||
def test_config_command_with_exception(runner):
|
||||
"""测试配置命令(异常处理)"""
|
||||
result = runner.invoke(cli, ["config", "--key", "nonexistent.key"])
|
||||
assert result.exit_code in [0, 1]
|
||||
|
||||
|
||||
def test_list_command_with_tags_and_dependencies(runner, tmp_path):
|
||||
"""测试列出命令(显示标签和依赖)"""
|
||||
test_data = [
|
||||
{
|
||||
"id": "TC001",
|
||||
"name": "测试用例1",
|
||||
"description": "测试用例1",
|
||||
"module": "test",
|
||||
"endpoint": "/api/test1",
|
||||
"method": "GET",
|
||||
"headers": {},
|
||||
"enabled": True,
|
||||
"tags": ["smoke", "regression"],
|
||||
"dependencies": ["TC000"]
|
||||
}
|
||||
]
|
||||
|
||||
test_file = tmp_path / "test_cases.json"
|
||||
with open(test_file, "w", encoding="utf-8") as f:
|
||||
json.dump(test_data, f)
|
||||
|
||||
result = runner.invoke(cli, ["list", str(test_file)])
|
||||
assert result.exit_code == 0
|
||||
assert "标签" in result.output
|
||||
assert "依赖" in result.output
|
||||
@@ -0,0 +1,224 @@
|
||||
import pytest
|
||||
import yaml
|
||||
import os
|
||||
from pathlib import Path
|
||||
from apitest.config.config_manager import ConfigManager
|
||||
from apitest.models.exceptions import ConfigException
|
||||
|
||||
|
||||
class TestConfigManager:
|
||||
"""测试ConfigManager配置管理器"""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_config_file(self, tmp_path):
|
||||
"""创建临时配置文件"""
|
||||
config_data = {
|
||||
"target": {
|
||||
"base_url": "http://localhost:8080",
|
||||
"timeout": 5000,
|
||||
"max_retries": 3
|
||||
},
|
||||
"auth": {
|
||||
"username": "test_user",
|
||||
"password": "test_pass",
|
||||
"login_endpoint": "/sys/auth/login"
|
||||
},
|
||||
"test": {
|
||||
"data_dir": "data",
|
||||
"test_cases_dir": "test_cases",
|
||||
"parallel": True,
|
||||
"parallel_threads": 4,
|
||||
"retry_count": 2,
|
||||
"stop_on_failure": False,
|
||||
"max_response_time": 5000
|
||||
},
|
||||
"report": {
|
||||
"output_dir": "reports",
|
||||
"format": "html"
|
||||
},
|
||||
"logging": {
|
||||
"level": "INFO",
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"file": "logs/api_test.log"
|
||||
}
|
||||
}
|
||||
|
||||
config_file = tmp_path / "config.yaml"
|
||||
with open(config_file, "w", encoding="utf-8") as f:
|
||||
yaml.dump(config_data, f)
|
||||
|
||||
return config_file
|
||||
|
||||
def test_config_manager_initialization(self, temp_config_file):
|
||||
"""测试配置管理器初始化"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.config_path == temp_config_file
|
||||
assert config_manager._config is not None
|
||||
|
||||
def test_config_manager_nonexistent_file(self):
|
||||
"""测试配置文件不存在"""
|
||||
with pytest.raises(ConfigException, match="配置文件不存在"):
|
||||
ConfigManager("/nonexistent/config.yaml")
|
||||
|
||||
def test_get_config_value(self, temp_config_file):
|
||||
"""测试获取配置值"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
|
||||
assert config_manager.get("target.base_url") == "http://localhost:8080"
|
||||
assert config_manager.get("target.timeout") == 5000
|
||||
assert config_manager.get("target.max_retries") == 3
|
||||
|
||||
def test_get_nested_config_value(self, temp_config_file):
|
||||
"""测试获取嵌套配置值"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
|
||||
assert config_manager.get("auth.username") == "test_user"
|
||||
assert config_manager.get("auth.password") == "test_pass"
|
||||
assert config_manager.get("auth.login_endpoint") == "/sys/auth/login"
|
||||
|
||||
def test_get_config_value_with_default(self, temp_config_file):
|
||||
"""测试获取配置值(带默认值)"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
|
||||
assert config_manager.get("nonexistent.key", "default_value") == "default_value"
|
||||
assert config_manager.get("target.nonexistent", 0) == 0
|
||||
|
||||
def test_get_target_config(self, temp_config_file):
|
||||
"""测试获取目标系统配置"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
target_config = config_manager.get_target_config()
|
||||
|
||||
assert target_config["base_url"] == "http://localhost:8080"
|
||||
assert target_config["timeout"] == 5000
|
||||
assert target_config["max_retries"] == 3
|
||||
|
||||
def test_get_auth_config(self, temp_config_file):
|
||||
"""测试获取认证配置"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
auth_config = config_manager.get_auth_config()
|
||||
|
||||
assert auth_config["username"] == "test_user"
|
||||
assert auth_config["password"] == "test_pass"
|
||||
assert auth_config["login_endpoint"] == "/sys/auth/login"
|
||||
|
||||
def test_get_test_config(self, temp_config_file):
|
||||
"""测试获取测试配置"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
test_config = config_manager.get_test_config()
|
||||
|
||||
assert test_config["data_dir"] == "data"
|
||||
assert test_config["test_cases_dir"] == "test_cases"
|
||||
assert test_config["parallel"] == True
|
||||
assert test_config["parallel_threads"] == 4
|
||||
|
||||
def test_get_report_config(self, temp_config_file):
|
||||
"""测试获取报告配置"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
report_config = config_manager.get_report_config()
|
||||
|
||||
assert report_config["output_dir"] == "reports"
|
||||
assert report_config["format"] == "html"
|
||||
|
||||
def test_get_logging_config(self, temp_config_file):
|
||||
"""测试获取日志配置"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
logging_config = config_manager.get_logging_config()
|
||||
|
||||
assert logging_config["level"] == "INFO"
|
||||
assert logging_config["file"] == "logs/api_test.log"
|
||||
|
||||
def test_get_base_url(self, temp_config_file):
|
||||
"""测试获取基础URL"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_base_url() == "http://localhost:8080"
|
||||
|
||||
def test_get_timeout(self, temp_config_file):
|
||||
"""测试获取超时时间"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_timeout() == 5000
|
||||
|
||||
def test_get_max_retries(self, temp_config_file):
|
||||
"""测试获取最大重试次数"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_max_retries() == 3
|
||||
|
||||
def test_get_login_endpoint(self, temp_config_file):
|
||||
"""测试获取登录端点"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_login_endpoint() == "/sys/auth/login"
|
||||
|
||||
def test_is_parallel_enabled(self, temp_config_file):
|
||||
"""测试是否启用并行执行"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.is_parallel_enabled() == True
|
||||
|
||||
def test_get_parallel_threads(self, temp_config_file):
|
||||
"""测试获取并行线程数"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_parallel_threads() == 4
|
||||
|
||||
def test_get_retry_count(self, temp_config_file):
|
||||
"""测试获取重试次数"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_retry_count() == 2
|
||||
|
||||
def test_should_stop_on_failure(self, temp_config_file):
|
||||
"""测试是否在失败时停止"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.should_stop_on_failure() == False
|
||||
|
||||
def test_get_max_response_time(self, temp_config_file):
|
||||
"""测试获取最大响应时间"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_max_response_time() == 5000
|
||||
|
||||
def test_get_report_format(self, temp_config_file):
|
||||
"""测试获取报告格式"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_report_format() == "html"
|
||||
|
||||
def test_get_log_level(self, temp_config_file):
|
||||
"""测试获取日志级别"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
assert config_manager.get_log_level() == "INFO"
|
||||
|
||||
def test_get_log_format(self, temp_config_file):
|
||||
"""测试获取日志格式"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
expected_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
assert config_manager.get_log_format() == expected_format
|
||||
|
||||
def test_get_log_file(self, temp_config_file):
|
||||
"""测试获取日志文件路径"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
log_file = config_manager.get_log_file()
|
||||
assert log_file is not None
|
||||
assert log_file.name == "api_test.log"
|
||||
|
||||
def test_get_auth_credentials_with_env(self, temp_config_file, monkeypatch):
|
||||
"""测试获取认证凭据(环境变量)"""
|
||||
monkeypatch.setenv("TEST_USERNAME", "env_user")
|
||||
monkeypatch.setenv("TEST_PASSWORD", "env_pass")
|
||||
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
credentials = config_manager.get_auth_credentials()
|
||||
|
||||
assert credentials["username"] == "env_user"
|
||||
assert credentials["password"] == "env_pass"
|
||||
|
||||
def test_reload_config(self, temp_config_file):
|
||||
"""测试重新加载配置"""
|
||||
config_manager = ConfigManager(str(temp_config_file))
|
||||
|
||||
original_url = config_manager.get_base_url()
|
||||
assert original_url == "http://localhost:8080"
|
||||
|
||||
with open(temp_config_file, "r+", encoding="utf-8") as f:
|
||||
config_data = yaml.safe_load(f)
|
||||
config_data["target"]["base_url"] = "http://new-url:8080"
|
||||
f.seek(0)
|
||||
yaml.dump(config_data, f)
|
||||
f.truncate()
|
||||
|
||||
config_manager.reload()
|
||||
assert config_manager.get_base_url() == "http://new-url:8080"
|
||||
@@ -0,0 +1,137 @@
|
||||
import pytest
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
from apitest.config.config_manager import ConfigManager
|
||||
from apitest.config.logger_manager import LoggerManager
|
||||
|
||||
|
||||
class TestLoggerManager:
|
||||
"""测试LoggerManager日志管理器"""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_manager(self):
|
||||
"""模拟配置管理器"""
|
||||
config_manager = MagicMock(spec=ConfigManager)
|
||||
config_manager.get_log_level.return_value = "INFO"
|
||||
config_manager.get_log_format.return_value = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
config_manager.get_log_file.return_value = None
|
||||
return config_manager
|
||||
|
||||
@pytest.fixture
|
||||
def logger_manager(self, mock_config_manager):
|
||||
"""创建日志管理器实例"""
|
||||
return LoggerManager(mock_config_manager)
|
||||
|
||||
def test_logger_manager_initialization(self, mock_config_manager):
|
||||
"""测试日志管理器初始化"""
|
||||
logger_manager = LoggerManager(mock_config_manager)
|
||||
assert logger_manager.config_manager == mock_config_manager
|
||||
assert isinstance(logger_manager._loggers, dict)
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_manager_with_file(self, tmp_path):
|
||||
"""模拟带日志文件的配置管理器"""
|
||||
config_manager = MagicMock(spec=ConfigManager)
|
||||
config_manager.get_log_level.return_value = "DEBUG"
|
||||
config_manager.get_log_format.return_value = "%(name)s - %(levelname)s - %(message)s"
|
||||
config_manager.get_log_file.return_value = tmp_path / "test.log"
|
||||
return config_manager
|
||||
|
||||
def test_logger_manager_with_file(self, mock_config_manager_with_file):
|
||||
"""测试带日志文件的日志管理器初始化"""
|
||||
logger_manager = LoggerManager(mock_config_manager_with_file)
|
||||
assert logger_manager.config_manager == mock_config_manager_with_file
|
||||
|
||||
def test_get_logger(self, logger_manager):
|
||||
"""测试获取日志记录器"""
|
||||
logger = logger_manager.get_logger("test_logger")
|
||||
assert isinstance(logger, logging.Logger)
|
||||
assert logger.name == "test_logger"
|
||||
assert "test_logger" in logger_manager._loggers
|
||||
|
||||
def test_get_logger_cached(self, logger_manager):
|
||||
"""测试获取缓存的日志记录器"""
|
||||
logger1 = logger_manager.get_logger("test_logger")
|
||||
logger2 = logger_manager.get_logger("test_logger")
|
||||
assert logger1 is logger2
|
||||
|
||||
def test_set_level(self, logger_manager):
|
||||
"""测试设置日志级别"""
|
||||
logger_manager.set_level("DEBUG")
|
||||
root_logger = logging.getLogger()
|
||||
assert root_logger.level == logging.DEBUG
|
||||
|
||||
def test_add_file_handler(self, logger_manager, tmp_path):
|
||||
"""测试添加文件处理器"""
|
||||
log_file = tmp_path / "test_add_handler.log"
|
||||
|
||||
initial_handler_count = len([h for h in logging.getLogger().handlers if isinstance(h, logging.FileHandler)])
|
||||
|
||||
logger_manager.add_file_handler(log_file)
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
file_handlers = [h for h in root_logger.handlers if isinstance(h, logging.FileHandler)]
|
||||
assert len(file_handlers) > initial_handler_count
|
||||
|
||||
new_handlers = file_handlers[initial_handler_count:]
|
||||
assert len(new_handlers) > 0
|
||||
assert str(log_file) in new_handlers[0].baseFilename
|
||||
|
||||
def test_add_file_handler_with_level(self, logger_manager, tmp_path):
|
||||
"""测试添加带级别的文件处理器"""
|
||||
log_file = tmp_path / "test_add_handler_level.log"
|
||||
|
||||
initial_handler_count = len([h for h in logging.getLogger().handlers if isinstance(h, logging.FileHandler)])
|
||||
|
||||
logger_manager.add_file_handler(log_file, "ERROR")
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
file_handlers = [h for h in root_logger.handlers if isinstance(h, logging.FileHandler)]
|
||||
assert len(file_handlers) > initial_handler_count
|
||||
|
||||
new_handlers = file_handlers[initial_handler_count:]
|
||||
assert len(new_handlers) > 0
|
||||
assert new_handlers[0].level == logging.ERROR
|
||||
|
||||
def test_remove_all_handlers(self, logger_manager):
|
||||
"""测试移除所有处理器"""
|
||||
original_handler_count = len(logging.getLogger().handlers)
|
||||
logger_manager.remove_all_handlers()
|
||||
assert len(logging.getLogger().handlers) == 0
|
||||
|
||||
@patch('apitest.config.logger_manager.setup_logger')
|
||||
def test_setup_logger_function(self, mock_setup):
|
||||
"""测试setup_logger函数"""
|
||||
from apitest.config.logger_manager import setup_logger
|
||||
mock_config = MagicMock(spec=ConfigManager)
|
||||
setup_logger(mock_config)
|
||||
mock_setup.assert_called_once_with(mock_config)
|
||||
|
||||
|
||||
class TestSetupLoggerIntegration:
|
||||
"""测试setup_logger集成"""
|
||||
|
||||
def test_setup_logger_creates_logger_manager(self):
|
||||
"""测试setup_logger创建日志管理器"""
|
||||
config_data = {
|
||||
"logging": {
|
||||
"level": "INFO",
|
||||
"format": "%(name)s - %(levelname)s - %(message)s",
|
||||
"file": None
|
||||
}
|
||||
}
|
||||
|
||||
with patch('apitest.config.config_manager.ConfigManager') as mock_config_class:
|
||||
mock_config = MagicMock(spec=ConfigManager)
|
||||
mock_config.get_log_level.return_value = "INFO"
|
||||
mock_config.get_log_format.return_value = "%(name)s - %(levelname)s - %(message)s"
|
||||
mock_config.get_log_file.return_value = None
|
||||
mock_config_class.return_value = mock_config
|
||||
|
||||
from apitest.config.logger_manager import setup_logger
|
||||
logger_manager = setup_logger(mock_config)
|
||||
|
||||
assert isinstance(logger_manager, LoggerManager)
|
||||
assert logger_manager.config_manager == mock_config
|
||||
@@ -0,0 +1,306 @@
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from unittest.mock import patch, MagicMock
|
||||
from apitest.main import cli
|
||||
import sys
|
||||
|
||||
|
||||
def test_cli_version():
|
||||
"""测试版本信息"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['--version'])
|
||||
assert result.exit_code == 0
|
||||
assert '1.0.0' in result.output
|
||||
|
||||
|
||||
def test_cli_with_no_command():
|
||||
"""测试无命令时显示帮助信息"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code == 0
|
||||
assert '黑盒API测试工具' in result.output
|
||||
|
||||
|
||||
def test_cli_run_with_valid_options():
|
||||
"""测试有效命令执行"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
with patch('apitest.main.LoggerManager') as mock_logger:
|
||||
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
|
||||
with patch('apitest.main.ReportManager') as mock_report:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_logger.return_value = MagicMock()
|
||||
mock_orchestrator.return_value = MagicMock()
|
||||
mock_report.return_value = MagicMock()
|
||||
|
||||
mock_results = MagicMock()
|
||||
mock_results.passed = 10
|
||||
mock_results.failed = 0
|
||||
mock_results.skipped = 0
|
||||
mock_orchestrator.return_value.run_suite.return_value = mock_results
|
||||
|
||||
result = runner.invoke(cli, ['run', '--suite', 'test-suite'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert mock_orchestrator.return_value.run_suite.called
|
||||
|
||||
|
||||
def test_cli_run_with_filter():
|
||||
"""测试带过滤器的运行命令"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
with patch('apitest.main.LoggerManager') as mock_logger:
|
||||
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
|
||||
with patch('apitest.main.ReportManager') as mock_report:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_logger.return_value = MagicMock()
|
||||
mock_orchestrator.return_value = MagicMock()
|
||||
mock_report.return_value = MagicMock()
|
||||
|
||||
mock_results = MagicMock()
|
||||
mock_results.passed = 5
|
||||
mock_results.failed = 0
|
||||
mock_results.skipped = 0
|
||||
mock_orchestrator.return_value.run_suite.return_value = mock_results
|
||||
|
||||
result = runner.invoke(cli, ['run', '--filter', 'priority=high'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_cli_run_with_parallel():
|
||||
"""测试并发执行"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
with patch('apitest.main.LoggerManager') as mock_logger:
|
||||
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
|
||||
with patch('apitest.main.ReportManager') as mock_report:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_logger.return_value = MagicMock()
|
||||
mock_orchestrator.return_value = MagicMock()
|
||||
mock_report.return_value = MagicMock()
|
||||
|
||||
mock_results = MagicMock()
|
||||
mock_results.passed = 10
|
||||
mock_results.failed = 0
|
||||
mock_results.skipped = 0
|
||||
mock_orchestrator.return_value.run_suite.return_value = mock_results
|
||||
|
||||
result = runner.invoke(cli, ['run', '--parallel', '--threads', '8'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_cli_run_with_failed_tests():
|
||||
"""测试有失败测试的情况"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
with patch('apitest.main.LoggerManager') as mock_logger:
|
||||
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
|
||||
with patch('apitest.main.ReportManager') as mock_report:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_logger.return_value = MagicMock()
|
||||
mock_orchestrator.return_value = MagicMock()
|
||||
mock_report.return_value = MagicMock()
|
||||
|
||||
mock_results = MagicMock()
|
||||
mock_results.passed = 5
|
||||
mock_results.failed = 2
|
||||
mock_results.skipped = 0
|
||||
mock_orchestrator.return_value.run_suite.return_value = mock_results
|
||||
|
||||
result = runner.invoke(cli, ['run'])
|
||||
|
||||
assert result.exit_code == 1
|
||||
|
||||
|
||||
def test_cli_run_with_exception():
|
||||
"""测试异常处理"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
mock_config.side_effect = Exception("Config error")
|
||||
|
||||
result = runner.invoke(cli, ['run'])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert '执行测试时出错' in result.output
|
||||
|
||||
|
||||
def test_cli_list_command():
|
||||
"""测试list命令"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
with patch('apitest.main.LoggerManager') as mock_logger:
|
||||
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_logger.return_value = MagicMock()
|
||||
mock_orchestrator.return_value = MagicMock()
|
||||
|
||||
mock_test_case = MagicMock()
|
||||
mock_test_case.id = 'test-001'
|
||||
mock_test_case.name = 'Test Case 1'
|
||||
mock_test_case.module = 'user'
|
||||
mock_test_case.method.value = 'GET'
|
||||
mock_test_case.endpoint = '/api/user'
|
||||
mock_test_case.priority = 'high'
|
||||
mock_test_case.enabled = True
|
||||
|
||||
mock_orchestrator.return_value.list_test_cases.return_value = [mock_test_case]
|
||||
|
||||
result = runner.invoke(cli, ['list'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert '测试套件' in result.output
|
||||
assert 'test-001' in result.output
|
||||
|
||||
|
||||
def test_cli_list_with_filter():
|
||||
"""测试带过滤器的list命令"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
with patch('apitest.main.LoggerManager') as mock_logger:
|
||||
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_logger.return_value = MagicMock()
|
||||
mock_orchestrator.return_value = MagicMock()
|
||||
|
||||
mock_orchestrator.return_value.list_test_cases.return_value = []
|
||||
|
||||
result = runner.invoke(cli, ['list', '--filter', 'priority=high'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_cli_list_with_exception():
|
||||
"""测试list命令异常处理"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
mock_config.side_effect = Exception("List error")
|
||||
|
||||
result = runner.invoke(cli, ['list'])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert '列出测试用例时出错' in result.output
|
||||
|
||||
|
||||
def test_cli_report_command():
|
||||
"""测试report命令"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
with patch('apitest.main.LoggerManager') as mock_logger:
|
||||
with patch('apitest.main.ReportManager') as mock_report:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_logger.return_value = MagicMock()
|
||||
mock_report.return_value = MagicMock()
|
||||
|
||||
result = runner.invoke(cli, ['report'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert '报告已生成' in result.output
|
||||
|
||||
|
||||
def test_cli_report_with_output():
|
||||
"""测试带输出路径的report命令"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
with patch('apitest.main.LoggerManager') as mock_logger:
|
||||
with patch('apitest.main.ReportManager') as mock_report:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_logger.return_value = MagicMock()
|
||||
mock_report.return_value = MagicMock()
|
||||
|
||||
result = runner.invoke(cli, ['report', '--output', '/tmp/report.html'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_cli_report_with_exception():
|
||||
"""测试report命令异常处理"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
mock_config.side_effect = Exception("Report error")
|
||||
|
||||
result = runner.invoke(cli, ['report'])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert '生成报告时出错' in result.output
|
||||
|
||||
|
||||
def test_cli_config_get():
|
||||
"""测试config get命令"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_config.return_value.get.return_value = 'test-value'
|
||||
|
||||
result = runner.invoke(cli, ['config', '--get', 'test-key'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert 'test-key = test-value' in result.output
|
||||
|
||||
|
||||
def test_cli_config_set():
|
||||
"""测试config set命令"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
|
||||
result = runner.invoke(cli, ['config', '--set', 'test.key=test-value'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert '配置已设置' in result.output
|
||||
mock_config.return_value.set.assert_called_once_with('test.key', 'test-value')
|
||||
|
||||
|
||||
def test_cli_config_validate():
|
||||
"""测试config validate命令"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_config.return_value.validate.return_value = (True, [])
|
||||
|
||||
result = runner.invoke(cli, ['config', '--validate'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert '配置验证通过' in result.output
|
||||
|
||||
|
||||
def test_cli_config_validate_with_errors():
|
||||
"""测试config validate命令带错误"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
mock_config.return_value = MagicMock()
|
||||
mock_config.return_value.validate.return_value = (False, ['Error 1', 'Error 2'])
|
||||
|
||||
result = runner.invoke(cli, ['config', '--validate'])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert '配置验证失败' in result.output
|
||||
|
||||
|
||||
def test_cli_config_with_exception():
|
||||
"""测试config命令异常处理"""
|
||||
runner = CliRunner()
|
||||
|
||||
with patch('apitest.main.ConfigManager') as mock_config:
|
||||
mock_config.side_effect = Exception("Config error")
|
||||
|
||||
result = runner.invoke(cli, ['config'])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert '配置管理时出错' in result.output
|
||||
@@ -0,0 +1,378 @@
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from apitest.models.test_models import (
|
||||
HTTPMethod,
|
||||
ValidationRule,
|
||||
TestCase,
|
||||
PerformanceMetrics,
|
||||
TestResult,
|
||||
TestSuiteResult
|
||||
)
|
||||
from apitest.models.exceptions import (
|
||||
APITestException,
|
||||
ConfigException,
|
||||
DataException,
|
||||
AuthException,
|
||||
RequestException,
|
||||
ValidationException,
|
||||
TestRunException,
|
||||
ReportException
|
||||
)
|
||||
|
||||
|
||||
class TestHTTPMethod:
|
||||
"""测试HTTPMethod枚举"""
|
||||
|
||||
def test_http_methods(self):
|
||||
"""测试所有HTTP方法"""
|
||||
assert HTTPMethod.GET.value == "GET"
|
||||
assert HTTPMethod.POST.value == "POST"
|
||||
assert HTTPMethod.PUT.value == "PUT"
|
||||
assert HTTPMethod.DELETE.value == "DELETE"
|
||||
assert HTTPMethod.PATCH.value == "PATCH"
|
||||
assert HTTPMethod.HEAD.value == "HEAD"
|
||||
assert HTTPMethod.OPTIONS.value == "OPTIONS"
|
||||
|
||||
|
||||
class TestValidationRule:
|
||||
"""测试ValidationRule数据类"""
|
||||
|
||||
def test_validation_rule_creation(self):
|
||||
"""测试创建验证规则"""
|
||||
rule = ValidationRule(
|
||||
type="status_code",
|
||||
expected=200,
|
||||
message="状态码应为200"
|
||||
)
|
||||
assert rule.type == "status_code"
|
||||
assert rule.expected == 200
|
||||
assert rule.message == "状态码应为200"
|
||||
assert rule.json_path is None
|
||||
|
||||
def test_validation_rule_with_json_path(self):
|
||||
"""测试带JSON路径的验证规则"""
|
||||
rule = ValidationRule(
|
||||
type="json_path",
|
||||
expected="success",
|
||||
json_path="$.status",
|
||||
message="状态应为success"
|
||||
)
|
||||
assert rule.json_path == "$.status"
|
||||
|
||||
def test_validation_rule_immutability(self):
|
||||
"""测试验证规则的不可变性"""
|
||||
rule = ValidationRule(type="status_code", expected=200)
|
||||
with pytest.raises(Exception):
|
||||
rule.expected = 201
|
||||
|
||||
|
||||
class TestTestCaseModel:
|
||||
"""测试TestCase数据类"""
|
||||
|
||||
def test_test_case_creation(self):
|
||||
"""测试创建测试用例"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用户登录",
|
||||
description="验证用户登录功能",
|
||||
module="user",
|
||||
endpoint="/api/auth/login",
|
||||
method=HTTPMethod.POST,
|
||||
headers={"Content-Type": "application/json"},
|
||||
body={"username": "admin", "password": "admin123"}
|
||||
)
|
||||
assert test_case.id == "TC001"
|
||||
assert test_case.name == "测试用户登录"
|
||||
assert test_case.method == HTTPMethod.POST
|
||||
assert test_case.auth_required == True
|
||||
assert test_case.enabled == True
|
||||
assert test_case.dependencies == []
|
||||
assert test_case.validations == []
|
||||
assert test_case.tags == []
|
||||
|
||||
def test_test_case_with_dependencies(self):
|
||||
"""测试带依赖的测试用例"""
|
||||
test_case = TestCase(
|
||||
id="TC002",
|
||||
name="测试获取用户信息",
|
||||
description="验证获取用户信息功能",
|
||||
module="user",
|
||||
endpoint="/api/user/info",
|
||||
method=HTTPMethod.GET,
|
||||
headers={"Content-Type": "application/json"},
|
||||
dependencies=["TC001"]
|
||||
)
|
||||
assert len(test_case.dependencies) == 1
|
||||
assert "TC001" in test_case.dependencies
|
||||
|
||||
def test_test_case_with_validations(self):
|
||||
"""测试带验证规则的测试用例"""
|
||||
test_case = TestCase(
|
||||
id="TC003",
|
||||
name="测试创建用户",
|
||||
description="验证创建用户功能",
|
||||
module="user",
|
||||
endpoint="/api/user",
|
||||
method=HTTPMethod.POST,
|
||||
headers={"Content-Type": "application/json"},
|
||||
body={"username": "test", "password": "test123"},
|
||||
validations=[
|
||||
{"type": "status_code", "expected": 201},
|
||||
{"type": "json_path", "json_path": "$.success", "expected": True}
|
||||
]
|
||||
)
|
||||
assert len(test_case.validations) == 2
|
||||
assert test_case.validations[0]["type"] == "status_code"
|
||||
|
||||
def test_test_case_immutability(self):
|
||||
"""测试测试用例的不可变性"""
|
||||
test_case = TestCase(
|
||||
id="TC004",
|
||||
name="测试用例",
|
||||
description="测试",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
with pytest.raises(Exception):
|
||||
test_case.name = "修改后的名称"
|
||||
|
||||
|
||||
class TestPerformanceMetrics:
|
||||
"""测试PerformanceMetrics数据类"""
|
||||
|
||||
def test_performance_metrics_creation(self):
|
||||
"""测试创建性能指标"""
|
||||
metrics = PerformanceMetrics(
|
||||
response_time=500,
|
||||
request_size=100,
|
||||
response_size=200,
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
assert metrics.response_time == 500
|
||||
assert metrics.request_size == 100
|
||||
assert metrics.response_size == 200
|
||||
|
||||
def test_performance_metrics_to_dict(self):
|
||||
"""测试性能指标转换为字典"""
|
||||
timestamp = datetime.now()
|
||||
metrics = PerformanceMetrics(
|
||||
response_time=500,
|
||||
request_size=100,
|
||||
response_size=200,
|
||||
timestamp=timestamp
|
||||
)
|
||||
result = metrics.to_dict()
|
||||
assert result["response_time"] == 500
|
||||
assert result["request_size"] == 100
|
||||
assert result["response_size"] == 200
|
||||
assert result["timestamp"] == timestamp.isoformat()
|
||||
|
||||
|
||||
class TestTestResultModel:
|
||||
"""测试TestResult数据类"""
|
||||
|
||||
def test_test_result_creation(self):
|
||||
"""测试创建测试结果"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
result = TestResult(
|
||||
test_case=test_case,
|
||||
passed=True,
|
||||
status_code=200,
|
||||
response_body={"success": True},
|
||||
response_headers={"Content-Type": "application/json"}
|
||||
)
|
||||
assert result.passed == True
|
||||
assert result.status_code == 200
|
||||
assert result.execution_time == 0.0
|
||||
assert result.retry_count == 0
|
||||
assert result.timestamp is not None
|
||||
|
||||
def test_test_result_with_performance(self):
|
||||
"""测试带性能指标的测试结果"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
performance = PerformanceMetrics(
|
||||
response_time=500,
|
||||
request_size=100,
|
||||
response_size=200,
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
result = TestResult(
|
||||
test_case=test_case,
|
||||
passed=True,
|
||||
status_code=200,
|
||||
response_body={"success": True},
|
||||
response_headers={},
|
||||
performance=performance,
|
||||
execution_time=0.5
|
||||
)
|
||||
assert result.performance == performance
|
||||
assert result.execution_time == 0.5
|
||||
|
||||
def test_test_result_to_dict(self):
|
||||
"""测试测试结果转换为字典"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
result = TestResult(
|
||||
test_case=test_case,
|
||||
passed=True,
|
||||
status_code=200,
|
||||
response_body={"success": True},
|
||||
response_headers={}
|
||||
)
|
||||
result_dict = result.to_dict()
|
||||
assert result_dict["test_case_id"] == "TC001"
|
||||
assert result_dict["test_case_name"] == "测试用例"
|
||||
assert result_dict["passed"] == True
|
||||
assert result_dict["status_code"] == 200
|
||||
|
||||
|
||||
class TestTestSuiteResultModel:
|
||||
"""测试TestSuiteResult数据类"""
|
||||
|
||||
def test_test_suite_result_creation(self):
|
||||
"""测试创建测试套件结果"""
|
||||
suite_result = TestSuiteResult(
|
||||
suite_name="user",
|
||||
total=10,
|
||||
passed=8,
|
||||
failed=1,
|
||||
skipped=1,
|
||||
results=[],
|
||||
start_time=datetime.now()
|
||||
)
|
||||
assert suite_result.suite_name == "user"
|
||||
assert suite_result.total == 10
|
||||
assert suite_result.passed == 8
|
||||
assert suite_result.failed == 1
|
||||
assert suite_result.skipped == 1
|
||||
assert suite_result.end_time is None
|
||||
|
||||
def test_test_suite_result_duration(self):
|
||||
"""测试测试套件执行时长"""
|
||||
start_time = datetime.now()
|
||||
end_time = datetime.now()
|
||||
suite_result = TestSuiteResult(
|
||||
suite_name="user",
|
||||
total=10,
|
||||
passed=8,
|
||||
failed=1,
|
||||
skipped=1,
|
||||
results=[],
|
||||
start_time=start_time,
|
||||
end_time=end_time
|
||||
)
|
||||
assert suite_result.duration >= 0
|
||||
|
||||
def test_test_suite_result_pass_rate(self):
|
||||
"""测试测试套件通过率"""
|
||||
suite_result = TestSuiteResult(
|
||||
suite_name="user",
|
||||
total=10,
|
||||
passed=8,
|
||||
failed=1,
|
||||
skipped=1,
|
||||
results=[],
|
||||
start_time=datetime.now()
|
||||
)
|
||||
assert suite_result.pass_rate == 80.0
|
||||
|
||||
def test_test_suite_result_pass_rate_zero_total(self):
|
||||
"""测试总数为0时的通过率"""
|
||||
suite_result = TestSuiteResult(
|
||||
suite_name="user",
|
||||
total=0,
|
||||
passed=0,
|
||||
failed=0,
|
||||
skipped=0,
|
||||
results=[],
|
||||
start_time=datetime.now()
|
||||
)
|
||||
assert suite_result.pass_rate == 0.0
|
||||
|
||||
def test_test_suite_result_to_dict(self):
|
||||
"""测试测试套件结果转换为字典"""
|
||||
suite_result = TestSuiteResult(
|
||||
suite_name="user",
|
||||
total=10,
|
||||
passed=8,
|
||||
failed=1,
|
||||
skipped=1,
|
||||
results=[],
|
||||
start_time=datetime.now()
|
||||
)
|
||||
result_dict = suite_result.to_dict()
|
||||
assert result_dict["suite_name"] == "user"
|
||||
assert result_dict["total"] == 10
|
||||
assert result_dict["passed"] == 8
|
||||
assert result_dict["failed"] == 1
|
||||
assert result_dict["skipped"] == 1
|
||||
assert result_dict["pass_rate"] == 80.0
|
||||
|
||||
|
||||
class TestExceptions:
|
||||
"""测试异常类"""
|
||||
|
||||
def test_api_test_exception(self):
|
||||
"""测试API测试基础异常"""
|
||||
with pytest.raises(APITestException):
|
||||
raise APITestException("测试异常")
|
||||
|
||||
def test_config_exception(self):
|
||||
"""测试配置异常"""
|
||||
with pytest.raises(ConfigException):
|
||||
raise ConfigException("配置错误")
|
||||
|
||||
def test_data_exception(self):
|
||||
"""测试数据异常"""
|
||||
with pytest.raises(DataException):
|
||||
raise DataException("数据错误")
|
||||
|
||||
def test_auth_exception(self):
|
||||
"""测试认证异常"""
|
||||
with pytest.raises(AuthException):
|
||||
raise AuthException("认证失败")
|
||||
|
||||
def test_request_exception(self):
|
||||
"""测试请求异常"""
|
||||
with pytest.raises(RequestException):
|
||||
raise RequestException("请求失败")
|
||||
|
||||
def test_validation_exception(self):
|
||||
"""测试验证异常"""
|
||||
with pytest.raises(ValidationException):
|
||||
raise ValidationException("验证失败")
|
||||
|
||||
def test_test_execution_exception(self):
|
||||
"""测试测试执行异常"""
|
||||
with pytest.raises(TestRunException):
|
||||
raise TestRunException("执行失败")
|
||||
|
||||
def test_report_exception(self):
|
||||
"""测试报告生成异常"""
|
||||
with pytest.raises(ReportException):
|
||||
raise ReportException("报告生成失败")
|
||||
@@ -0,0 +1,355 @@
|
||||
"""报告管理器单元测试"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from unittest.mock import Mock, patch
|
||||
from apitest.report.report_manager import ReportManager
|
||||
from apitest.models.test_models import TestCase, TestResult, TestSuiteResult, HTTPMethod, PerformanceMetrics
|
||||
from apitest.models.exceptions import ReportException
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def report_manager():
|
||||
"""创建报告管理器实例"""
|
||||
logger = Mock()
|
||||
return ReportManager(logger)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_suite_result():
|
||||
"""创建测试套件结果"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试用例1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
enabled=True
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试用例2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.POST,
|
||||
headers={},
|
||||
enabled=True
|
||||
)
|
||||
]
|
||||
|
||||
results = [
|
||||
TestResult(
|
||||
test_case=test_cases[0],
|
||||
passed=True,
|
||||
status_code=200,
|
||||
response_body={"message": "success"},
|
||||
response_headers={"Content-Type": "application/json"},
|
||||
performance=PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=123,
|
||||
request_size=0,
|
||||
response_size=0
|
||||
)
|
||||
),
|
||||
TestResult(
|
||||
test_case=test_cases[1],
|
||||
passed=False,
|
||||
status_code=500,
|
||||
response_body={"error": "internal error"},
|
||||
response_headers={"Content-Type": "application/json"},
|
||||
performance=PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=456,
|
||||
request_size=0,
|
||||
response_size=0
|
||||
),
|
||||
error_message="服务器内部错误"
|
||||
)
|
||||
]
|
||||
|
||||
return TestSuiteResult(
|
||||
suite_name="Test Suite",
|
||||
total=2,
|
||||
passed=1,
|
||||
failed=1,
|
||||
skipped=0,
|
||||
results=results,
|
||||
start_time=datetime.now()
|
||||
)
|
||||
|
||||
|
||||
def test_generate_html_report_success(report_manager, test_suite_result, tmp_path):
|
||||
"""测试生成HTML报告(成功)"""
|
||||
report_path = tmp_path / "test_report.html"
|
||||
|
||||
result_path = report_manager.generate_html_report(
|
||||
test_suite_result,
|
||||
report_path,
|
||||
title="测试报告"
|
||||
)
|
||||
|
||||
assert result_path == str(report_path)
|
||||
assert report_path.exists()
|
||||
|
||||
content = report_path.read_text(encoding="utf-8")
|
||||
assert "测试报告" in content
|
||||
assert "Test Suite" in content
|
||||
assert "TC001" in content
|
||||
assert "TC002" in content
|
||||
assert "测试用例1" in content
|
||||
assert "测试用例2" in content
|
||||
assert "50.0%" in content or "50.0" in content
|
||||
|
||||
|
||||
def test_generate_html_report_create_directory(report_manager, test_suite_result, tmp_path):
|
||||
"""测试生成HTML报告(创建目录)"""
|
||||
report_path = tmp_path / "reports" / "nested" / "test_report.html"
|
||||
|
||||
result_path = report_manager.generate_html_report(
|
||||
test_suite_result,
|
||||
report_path
|
||||
)
|
||||
|
||||
assert result_path == str(report_path)
|
||||
assert report_path.exists()
|
||||
assert report_path.parent.exists()
|
||||
|
||||
|
||||
def test_generate_json_report_success(report_manager, test_suite_result, tmp_path):
|
||||
"""测试生成JSON报告(成功)"""
|
||||
report_path = tmp_path / "test_report.json"
|
||||
|
||||
result_path = report_manager.generate_json_report(
|
||||
test_suite_result,
|
||||
report_path
|
||||
)
|
||||
|
||||
assert result_path == str(report_path)
|
||||
assert report_path.exists()
|
||||
|
||||
import json
|
||||
content = report_path.read_text(encoding="utf-8")
|
||||
data = json.loads(content)
|
||||
|
||||
assert data["suite_name"] == "Test Suite"
|
||||
assert data["total"] == 2
|
||||
assert data["passed"] == 1
|
||||
assert data["failed"] == 1
|
||||
assert data["skipped"] == 0
|
||||
assert len(data["results"]) == 2
|
||||
|
||||
|
||||
def test_generate_json_report_create_directory(report_manager, test_suite_result, tmp_path):
|
||||
"""测试生成JSON报告(创建目录)"""
|
||||
report_path = tmp_path / "reports" / "nested" / "test_report.json"
|
||||
|
||||
result_path = report_manager.generate_json_report(
|
||||
test_suite_result,
|
||||
report_path
|
||||
)
|
||||
|
||||
assert result_path == str(report_path)
|
||||
assert report_path.exists()
|
||||
assert report_path.parent.exists()
|
||||
|
||||
|
||||
def test_generate_html_report_logger_call(report_manager, test_suite_result, tmp_path):
|
||||
"""测试生成HTML报告时调用日志记录器"""
|
||||
report_path = tmp_path / "test_report.html"
|
||||
|
||||
report_manager.generate_html_report(
|
||||
test_suite_result,
|
||||
report_path
|
||||
)
|
||||
|
||||
report_manager.logger.info.assert_called()
|
||||
|
||||
|
||||
def test_generate_json_report_logger_call(report_manager, test_suite_result, tmp_path):
|
||||
"""测试生成JSON报告时调用日志记录器"""
|
||||
report_path = tmp_path / "test_report.json"
|
||||
|
||||
report_manager.generate_json_report(
|
||||
test_suite_result,
|
||||
report_path
|
||||
)
|
||||
|
||||
report_manager.logger.info.assert_called()
|
||||
|
||||
|
||||
def test_generate_html_report_content_structure(report_manager, test_suite_result, tmp_path):
|
||||
"""测试HTML报告内容结构"""
|
||||
report_path = tmp_path / "test_report.html"
|
||||
|
||||
report_manager.generate_html_report(
|
||||
test_suite_result,
|
||||
report_path,
|
||||
title="API测试报告"
|
||||
)
|
||||
|
||||
content = report_path.read_text(encoding="utf-8")
|
||||
|
||||
assert "<!DOCTYPE html>" in content
|
||||
assert "<html" in content
|
||||
assert "<head>" in content
|
||||
assert "<body>" in content
|
||||
assert "API测试报告" in content
|
||||
assert "总用例数" in content
|
||||
assert "通过" in content
|
||||
assert "失败" in content
|
||||
assert "跳过" in content
|
||||
assert "通过率" in content
|
||||
assert "执行时长" in content
|
||||
assert "测试结果详情" in content
|
||||
assert "</html>" in content
|
||||
|
||||
|
||||
def test_generate_html_report_with_all_passed(report_manager, tmp_path):
|
||||
"""测试生成HTML报告(全部通过)"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试用例1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
enabled=True
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试用例2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.POST,
|
||||
headers={},
|
||||
enabled=True
|
||||
)
|
||||
]
|
||||
|
||||
results = [
|
||||
TestResult(
|
||||
test_case=test_cases[0],
|
||||
passed=True,
|
||||
status_code=200,
|
||||
response_body={"message": "success"},
|
||||
response_headers={"Content-Type": "application/json"},
|
||||
performance=PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=123,
|
||||
request_size=0,
|
||||
response_size=0
|
||||
)
|
||||
),
|
||||
TestResult(
|
||||
test_case=test_cases[1],
|
||||
passed=True,
|
||||
status_code=200,
|
||||
response_body={"message": "success"},
|
||||
response_headers={"Content-Type": "application/json"},
|
||||
performance=PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=456,
|
||||
request_size=0,
|
||||
response_size=0
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
test_suite_result = TestSuiteResult(
|
||||
suite_name="Test Suite",
|
||||
total=2,
|
||||
passed=2,
|
||||
failed=0,
|
||||
skipped=0,
|
||||
results=results,
|
||||
start_time=datetime.now()
|
||||
)
|
||||
|
||||
report_path = tmp_path / "test_report.html"
|
||||
report_manager.generate_html_report(test_suite_result, report_path)
|
||||
|
||||
content = report_path.read_text(encoding="utf-8")
|
||||
assert "100.0%" in content or "100.0" in content
|
||||
|
||||
|
||||
def test_generate_html_report_with_all_failed(report_manager, tmp_path):
|
||||
"""测试生成HTML报告(全部失败)"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试用例1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
enabled=True
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试用例2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.POST,
|
||||
headers={},
|
||||
enabled=True
|
||||
)
|
||||
]
|
||||
|
||||
results = [
|
||||
TestResult(
|
||||
test_case=test_cases[0],
|
||||
passed=False,
|
||||
status_code=500,
|
||||
response_body={"error": "error"},
|
||||
response_headers={"Content-Type": "application/json"},
|
||||
performance=PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=123,
|
||||
request_size=0,
|
||||
response_size=0
|
||||
),
|
||||
error_message="错误1"
|
||||
),
|
||||
TestResult(
|
||||
test_case=test_cases[1],
|
||||
passed=False,
|
||||
status_code=404,
|
||||
response_body={"error": "not found"},
|
||||
response_headers={"Content-Type": "application/json"},
|
||||
performance=PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=456,
|
||||
request_size=0,
|
||||
response_size=0
|
||||
),
|
||||
error_message="错误2"
|
||||
)
|
||||
]
|
||||
|
||||
test_suite_result = TestSuiteResult(
|
||||
suite_name="Test Suite",
|
||||
total=2,
|
||||
passed=0,
|
||||
failed=2,
|
||||
skipped=0,
|
||||
results=results,
|
||||
start_time=datetime.now()
|
||||
)
|
||||
|
||||
report_path = tmp_path / "test_report.html"
|
||||
report_manager.generate_html_report(test_suite_result, report_path)
|
||||
|
||||
content = report_path.read_text(encoding="utf-8")
|
||||
assert "0.0%" in content or "0.0" in content
|
||||
assert "错误1" in content
|
||||
assert "错误2" in content
|
||||
@@ -0,0 +1,297 @@
|
||||
"""测试数据管理器单元测试"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
from apitest.data.test_data_manager import TestDataManager
|
||||
from apitest.models.test_models import TestCase, HTTPMethod
|
||||
from apitest.models.exceptions import TestRunException
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_logger():
|
||||
"""创建模拟日志记录器"""
|
||||
return Mock()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def data_manager(mock_logger):
|
||||
"""创建测试数据管理器实例"""
|
||||
return TestDataManager(logger=mock_logger)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_test_cases():
|
||||
"""创建示例测试用例"""
|
||||
return [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试用例1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
enabled=True
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试用例2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.POST,
|
||||
headers={},
|
||||
enabled=True
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_load_test_cases_from_json_success(data_manager, tmp_path):
|
||||
"""测试从JSON文件加载测试用例(成功)"""
|
||||
import json
|
||||
|
||||
test_data = [
|
||||
{
|
||||
"id": "TC001",
|
||||
"name": "测试用例1",
|
||||
"description": "测试用例1",
|
||||
"module": "test",
|
||||
"endpoint": "/api/test1",
|
||||
"method": "GET",
|
||||
"headers": {},
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"id": "TC002",
|
||||
"name": "测试用例2",
|
||||
"description": "测试用例2",
|
||||
"module": "test",
|
||||
"endpoint": "/api/test2",
|
||||
"method": "POST",
|
||||
"headers": {},
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
|
||||
test_file = tmp_path / "test_cases.json"
|
||||
with open(test_file, "w", encoding="utf-8") as f:
|
||||
json.dump(test_data, f)
|
||||
|
||||
test_cases = data_manager.load_test_cases_from_json(test_file)
|
||||
|
||||
assert len(test_cases) == 2
|
||||
assert test_cases[0].id == "TC001"
|
||||
assert test_cases[1].id == "TC002"
|
||||
assert test_cases[0].method == HTTPMethod.GET
|
||||
assert test_cases[1].method == HTTPMethod.POST
|
||||
|
||||
|
||||
def test_load_test_cases_from_json_file_not_found(data_manager, tmp_path):
|
||||
"""测试从JSON文件加载测试用例(文件不存在)"""
|
||||
test_file = tmp_path / "nonexistent.json"
|
||||
|
||||
with pytest.raises(TestRunException) as exc_info:
|
||||
data_manager.load_test_cases_from_json(test_file)
|
||||
|
||||
assert "测试用例文件不存在" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_load_test_cases_from_json_invalid_json(data_manager, tmp_path):
|
||||
"""测试从JSON文件加载测试用例(无效JSON)"""
|
||||
test_file = tmp_path / "invalid.json"
|
||||
with open(test_file, "w", encoding="utf-8") as f:
|
||||
f.write("{ invalid json }")
|
||||
|
||||
with pytest.raises(TestRunException) as exc_info:
|
||||
data_manager.load_test_cases_from_json(test_file)
|
||||
|
||||
assert "JSON文件解析失败" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_load_test_cases_from_json_invalid_method(data_manager, tmp_path):
|
||||
"""测试从JSON文件加载测试用例(无效HTTP方法)"""
|
||||
import json
|
||||
|
||||
test_data = [
|
||||
{
|
||||
"id": "TC001",
|
||||
"name": "测试用例1",
|
||||
"description": "测试用例1",
|
||||
"module": "test",
|
||||
"endpoint": "/api/test1",
|
||||
"method": "INVALID",
|
||||
"headers": {},
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
|
||||
test_file = tmp_path / "test_cases.json"
|
||||
with open(test_file, "w", encoding="utf-8") as f:
|
||||
json.dump(test_data, f)
|
||||
|
||||
test_cases = data_manager.load_test_cases_from_json(test_file)
|
||||
|
||||
assert len(test_cases) == 1
|
||||
assert test_cases[0].method == HTTPMethod.GET
|
||||
|
||||
|
||||
def test_load_test_data_from_csv_success(data_manager, tmp_path):
|
||||
"""测试从CSV文件加载测试数据(成功)"""
|
||||
test_file = tmp_path / "test_data.csv"
|
||||
with open(test_file, "w", encoding="utf-8", newline="") as f:
|
||||
f.write("param1,param2,param3\n")
|
||||
f.write("value1,value2,value3\n")
|
||||
f.write("value4,value5,value6\n")
|
||||
|
||||
test_data = data_manager.load_test_data_from_csv(test_file)
|
||||
|
||||
assert len(test_data) == 2
|
||||
assert test_data[0] == {"param1": "value1", "param2": "value2", "param3": "value3"}
|
||||
assert test_data[1] == {"param1": "value4", "param2": "value5", "param3": "value6"}
|
||||
|
||||
|
||||
def test_load_test_data_from_csv_file_not_found(data_manager, tmp_path):
|
||||
"""测试从CSV文件加载测试数据(文件不存在)"""
|
||||
test_file = tmp_path / "nonexistent.csv"
|
||||
|
||||
with pytest.raises(TestRunException) as exc_info:
|
||||
data_manager.load_test_data_from_csv(test_file)
|
||||
|
||||
assert "测试数据文件不存在" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_parameterize_test_case_success(data_manager, sample_test_cases):
|
||||
"""测试参数化测试用例(成功)"""
|
||||
test_data = [
|
||||
{"params": {"id": "1"}, "body": {"name": "test1"}},
|
||||
{"params": {"id": "2"}, "body": {"name": "test2"}}
|
||||
]
|
||||
|
||||
parameterized_cases = data_manager.parameterize_test_case(
|
||||
sample_test_cases[0],
|
||||
test_data
|
||||
)
|
||||
|
||||
assert len(parameterized_cases) == 2
|
||||
assert parameterized_cases[0].id == "TC001_1"
|
||||
assert parameterized_cases[0].name == "测试用例1 (数据集 1)"
|
||||
assert parameterized_cases[0].params == {"id": "1"}
|
||||
assert parameterized_cases[0].body == {"name": "test1"}
|
||||
assert parameterized_cases[1].id == "TC001_2"
|
||||
assert parameterized_cases[1].name == "测试用例1 (数据集 2)"
|
||||
assert parameterized_cases[1].params == {"id": "2"}
|
||||
assert parameterized_cases[1].body == {"name": "test2"}
|
||||
|
||||
|
||||
def test_parameterize_test_case_empty_data(data_manager, sample_test_cases):
|
||||
"""测试参数化测试用例(空数据)"""
|
||||
test_data = []
|
||||
|
||||
parameterized_cases = data_manager.parameterize_test_case(
|
||||
sample_test_cases[0],
|
||||
test_data
|
||||
)
|
||||
|
||||
assert len(parameterized_cases) == 0
|
||||
|
||||
|
||||
def test_save_test_cases_to_json_success(data_manager, sample_test_cases, tmp_path):
|
||||
"""测试保存测试用例到JSON文件(成功)"""
|
||||
output_file = tmp_path / "output.json"
|
||||
|
||||
data_manager.save_test_cases_to_json(sample_test_cases, output_file)
|
||||
|
||||
assert output_file.exists()
|
||||
|
||||
import json
|
||||
with open(output_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert len(data) == 2
|
||||
assert data[0]["id"] == "TC001"
|
||||
assert data[1]["id"] == "TC002"
|
||||
|
||||
|
||||
def test_save_test_cases_to_json_create_directory(data_manager, sample_test_cases, tmp_path):
|
||||
"""测试保存测试用例到JSON文件(创建目录)"""
|
||||
output_file = tmp_path / "nested" / "dir" / "output.json"
|
||||
|
||||
data_manager.save_test_cases_to_json(sample_test_cases, output_file)
|
||||
|
||||
assert output_file.exists()
|
||||
assert output_file.parent.exists()
|
||||
|
||||
|
||||
def test_save_test_data_to_csv_success(data_manager, tmp_path):
|
||||
"""测试保存测试数据到CSV文件(成功)"""
|
||||
test_data = [
|
||||
{"param1": "value1", "param2": "value2"},
|
||||
{"param1": "value3", "param2": "value4"}
|
||||
]
|
||||
|
||||
output_file = tmp_path / "output.csv"
|
||||
|
||||
data_manager.save_test_data_to_csv(test_data, output_file)
|
||||
|
||||
assert output_file.exists()
|
||||
|
||||
import csv
|
||||
with open(output_file, "r", encoding="utf-8") as f:
|
||||
reader = csv.DictReader(f)
|
||||
rows = list(reader)
|
||||
|
||||
assert len(rows) == 2
|
||||
assert rows[0]["param1"] == "value1"
|
||||
assert rows[1]["param1"] == "value3"
|
||||
|
||||
|
||||
def test_save_test_data_to_csv_with_fieldnames(data_manager, tmp_path):
|
||||
"""测试保存测试数据到CSV文件(指定字段名)"""
|
||||
test_data = [
|
||||
{"param1": "value1", "param2": "value2", "param3": "value3"},
|
||||
{"param1": "value4", "param2": "value5", "param3": "value6"}
|
||||
]
|
||||
|
||||
output_file = tmp_path / "output.csv"
|
||||
|
||||
data_manager.save_test_data_to_csv(
|
||||
test_data,
|
||||
output_file,
|
||||
fieldnames=["param1", "param2"]
|
||||
)
|
||||
|
||||
assert output_file.exists()
|
||||
|
||||
import csv
|
||||
with open(output_file, "r", encoding="utf-8") as f:
|
||||
reader = csv.DictReader(f)
|
||||
rows = list(reader)
|
||||
fieldnames = reader.fieldnames
|
||||
|
||||
assert len(rows) == 2
|
||||
assert fieldnames == ["param1", "param2"]
|
||||
|
||||
|
||||
def test_save_test_data_to_csv_empty_data(data_manager, tmp_path):
|
||||
"""测试保存测试数据到CSV文件(空数据)"""
|
||||
test_data = []
|
||||
|
||||
output_file = tmp_path / "output.csv"
|
||||
|
||||
with pytest.raises(TestRunException) as exc_info:
|
||||
data_manager.save_test_data_to_csv(test_data, output_file)
|
||||
|
||||
assert "测试数据为空" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_save_test_data_to_csv_create_directory(data_manager, tmp_path):
|
||||
"""测试保存测试数据到CSV文件(创建目录)"""
|
||||
test_data = [{"param1": "value1"}]
|
||||
|
||||
output_file = tmp_path / "nested" / "dir" / "output.csv"
|
||||
|
||||
data_manager.save_test_data_to_csv(test_data, output_file)
|
||||
|
||||
assert output_file.exists()
|
||||
assert output_file.parent.exists()
|
||||
@@ -0,0 +1,505 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from datetime import datetime
|
||||
from apitest.models.test_models import (
|
||||
TestCase, TestResult, TestSuiteResult, HTTPMethod, PerformanceMetrics
|
||||
)
|
||||
from apitest.core.test_engine import TestEngine
|
||||
from apitest.core.validation_engine import ValidationEngine
|
||||
from apitest.models.exceptions import TestRunException
|
||||
|
||||
|
||||
class TestTestEngine:
|
||||
"""测试TestEngine测试引擎"""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_api_client(self):
|
||||
"""模拟API客户端"""
|
||||
api_client = MagicMock()
|
||||
api_client.request.return_value = {
|
||||
"status_code": 200,
|
||||
"response_body": {"message": "success"},
|
||||
"response_headers": {"Content-Type": "application/json"},
|
||||
"performance": PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=1000,
|
||||
request_size=100,
|
||||
response_size=200
|
||||
)
|
||||
}
|
||||
return api_client
|
||||
|
||||
@pytest.fixture
|
||||
def mock_auth_manager(self):
|
||||
"""模拟认证管理器"""
|
||||
auth_manager = MagicMock()
|
||||
auth_manager.get_auth_headers.return_value = {"Authorization": "Bearer token"}
|
||||
return auth_manager
|
||||
|
||||
@pytest.fixture
|
||||
def mock_validation_engine(self):
|
||||
"""模拟验证引擎"""
|
||||
validation_engine = MagicMock()
|
||||
validation_engine.validate_response.return_value = (True, "")
|
||||
return validation_engine
|
||||
|
||||
@pytest.fixture
|
||||
def test_engine(self, mock_api_client, mock_auth_manager, mock_validation_engine):
|
||||
"""创建测试引擎实例"""
|
||||
return TestEngine(
|
||||
api_client=mock_api_client,
|
||||
auth_manager=mock_auth_manager,
|
||||
validation_engine=mock_validation_engine
|
||||
)
|
||||
|
||||
def test_test_engine_initialization(self, test_engine):
|
||||
"""测试测试引擎初始化"""
|
||||
assert test_engine.api_client is not None
|
||||
assert test_engine.auth_manager is not None
|
||||
assert test_engine.validation_engine is not None
|
||||
assert test_engine._context == {}
|
||||
|
||||
def test_set_context(self, test_engine):
|
||||
"""测试设置上下文变量"""
|
||||
test_engine.set_context("user_id", "12345")
|
||||
assert test_engine.get_context("user_id") == "12345"
|
||||
|
||||
def test_get_context_with_default(self, test_engine):
|
||||
"""测试获取上下文变量(带默认值)"""
|
||||
assert test_engine.get_context("nonexistent", "default") == "default"
|
||||
|
||||
def test_topological_sort_no_dependencies(self, test_engine):
|
||||
"""测试拓扑排序(无依赖)"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
]
|
||||
|
||||
sorted_cases = test_engine._topological_sort(test_cases)
|
||||
assert len(sorted_cases) == 2
|
||||
|
||||
def test_topological_sort_with_dependencies(self, test_engine):
|
||||
"""测试拓扑排序(有依赖)"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
dependencies=[]
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
dependencies=["TC001"]
|
||||
),
|
||||
TestCase(
|
||||
id="TC003",
|
||||
name="测试3",
|
||||
description="测试用例3",
|
||||
module="test",
|
||||
endpoint="/api/test3",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
dependencies=["TC002"]
|
||||
)
|
||||
]
|
||||
|
||||
sorted_cases = test_engine._topological_sort(test_cases)
|
||||
assert len(sorted_cases) == 3
|
||||
assert sorted_cases[0].id == "TC001"
|
||||
assert sorted_cases[1].id == "TC002"
|
||||
assert sorted_cases[2].id == "TC003"
|
||||
|
||||
def test_topological_sort_circular_dependency(self, test_engine):
|
||||
"""测试拓扑排序(循环依赖)"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
dependencies=["TC002"]
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
dependencies=["TC001"]
|
||||
)
|
||||
]
|
||||
|
||||
with pytest.raises(TestRunException, match="存在循环依赖"):
|
||||
test_engine._topological_sort(test_cases)
|
||||
|
||||
def test_resolve_context_variables_string(self, test_engine):
|
||||
"""测试解析上下文变量(字符串)"""
|
||||
test_engine.set_context("user_id", "12345")
|
||||
result = test_engine._resolve_context_variables("${user_id}")
|
||||
assert result == "12345"
|
||||
|
||||
def test_resolve_context_variables_dict(self, test_engine):
|
||||
"""测试解析上下文变量(字典)"""
|
||||
test_engine.set_context("user_id", "12345")
|
||||
data = {"user_id": "${user_id}", "name": "test"}
|
||||
result = test_engine._resolve_context_variables(data)
|
||||
assert result == {"user_id": "12345", "name": "test"}
|
||||
|
||||
def test_resolve_context_variables_list(self, test_engine):
|
||||
"""测试解析上下文变量(列表)"""
|
||||
test_engine.set_context("user_id", "12345")
|
||||
data = ["${user_id}", "test"]
|
||||
result = test_engine._resolve_context_variables(data)
|
||||
assert result == ["12345", "test"]
|
||||
|
||||
def test_execute_test_case_success(self, test_engine):
|
||||
"""测试执行测试用例(成功)"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试用例描述",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "status_code", "value": 200}]
|
||||
)
|
||||
|
||||
result = test_engine._execute_test_case(test_case)
|
||||
|
||||
assert isinstance(result, TestResult)
|
||||
assert result.passed == True
|
||||
assert result.status_code == 200
|
||||
assert result.test_case == test_case
|
||||
|
||||
def test_execute_test_case_with_auth(self, test_engine):
|
||||
"""测试执行测试用例(带认证)"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试用例描述",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
auth_required=True
|
||||
)
|
||||
|
||||
result = test_engine._execute_test_case(test_case)
|
||||
|
||||
assert result.passed == True
|
||||
test_engine.auth_manager.get_auth_headers.assert_called_once()
|
||||
|
||||
def test_execute_test_case_failure(self, test_engine, mock_validation_engine):
|
||||
"""测试执行测试用例(失败)"""
|
||||
mock_validation_engine.validate_response.return_value = (False, "验证失败")
|
||||
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试用例描述",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "status_code", "value": 200}]
|
||||
)
|
||||
|
||||
result = test_engine._execute_test_case(test_case)
|
||||
|
||||
assert result.passed == False
|
||||
assert result.error_message == "验证失败"
|
||||
|
||||
def test_execute_test_case_with_setup(self, test_engine):
|
||||
"""测试执行测试用例(带前置操作)"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试用例描述",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
setup={"type": "set_context", "key": "test_key", "value": "test_value"}
|
||||
)
|
||||
|
||||
result = test_engine._execute_test_case(test_case)
|
||||
|
||||
assert test_engine.get_context("test_key") == "test_value"
|
||||
|
||||
def test_execute_test_case_with_teardown(self, test_engine):
|
||||
"""测试执行测试用例(带后置操作)"""
|
||||
test_engine.set_context("test_key", "test_value")
|
||||
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试用例描述",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
teardown={"type": "clear_context", "key": "test_key"}
|
||||
)
|
||||
|
||||
result = test_engine._execute_test_case(test_case)
|
||||
|
||||
assert test_engine.get_context("test_key") is None
|
||||
|
||||
def test_execute_test_suite(self, test_engine):
|
||||
"""测试执行测试套件"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
]
|
||||
|
||||
result = test_engine.execute_test_suite(test_cases)
|
||||
|
||||
assert isinstance(result, TestSuiteResult)
|
||||
assert len(result.results) == 2
|
||||
assert result.passed == 2
|
||||
assert result.failed == 0
|
||||
|
||||
def test_execute_test_suite_with_dependencies(self, test_engine):
|
||||
"""测试执行测试套件(有依赖)"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
dependencies=[]
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
dependencies=["TC001"]
|
||||
)
|
||||
]
|
||||
|
||||
result = test_engine.execute_test_suite(test_cases)
|
||||
|
||||
assert len(result.results) == 2
|
||||
assert result.passed == 2
|
||||
|
||||
def test_execute_test_suite_stop_on_failure(self, test_engine, mock_validation_engine):
|
||||
"""测试执行测试套件(失败时停止)"""
|
||||
mock_validation_engine.validate_response.return_value = (False, "验证失败")
|
||||
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
]
|
||||
|
||||
result = test_engine.execute_test_suite(test_cases, stop_on_failure=True)
|
||||
|
||||
assert len(result.results) == 1
|
||||
assert result.passed == 0
|
||||
assert result.failed == 1
|
||||
|
||||
def test_execute_test_suite_skip_disabled(self, test_engine):
|
||||
"""测试执行测试套件(跳过已禁用的用例)"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
enabled=False
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
enabled=True
|
||||
)
|
||||
]
|
||||
|
||||
result = test_engine.execute_test_suite(test_cases)
|
||||
|
||||
assert len(result.results) == 1
|
||||
assert result.skipped == 1
|
||||
|
||||
def test_execute_test_cases_by_filter_module(self, test_engine):
|
||||
"""测试按模块过滤执行测试用例"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="module1",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="module2",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
]
|
||||
|
||||
result = test_engine.execute_test_cases_by_filter(test_cases, module_filter="module1")
|
||||
|
||||
assert len(result.results) == 1
|
||||
assert result.results[0].test_case.module == "module1"
|
||||
|
||||
def test_execute_test_cases_by_filter_tag(self, test_engine):
|
||||
"""测试按标签过滤执行测试用例"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
tags=["smoke", "regression"]
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
tags=["regression"]
|
||||
)
|
||||
]
|
||||
|
||||
result = test_engine.execute_test_cases_by_filter(test_cases, tag_filter=["smoke"])
|
||||
|
||||
assert len(result.results) == 1
|
||||
assert "smoke" in result.results[0].test_case.tags
|
||||
|
||||
def test_execute_test_cases_by_filter_priority(self, test_engine):
|
||||
"""测试按优先级过滤执行测试用例"""
|
||||
test_cases = [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
priority=1
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
priority=2
|
||||
)
|
||||
]
|
||||
|
||||
result = test_engine.execute_test_cases_by_filter(test_cases, priority_filter=1)
|
||||
|
||||
assert len(result.results) == 1
|
||||
assert result.results[0].test_case.priority == 1
|
||||
|
||||
def test_extract_response_data(self, test_engine):
|
||||
"""测试提取响应数据到上下文"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试用例",
|
||||
description="测试用例描述",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "extract", "field": "user_id", "var_name": "extracted_id"}]
|
||||
)
|
||||
|
||||
response_body = {"user_id": "12345", "name": "test"}
|
||||
test_engine._extract_response_data(test_case, response_body)
|
||||
|
||||
assert test_engine.get_context("extracted_id") == "12345"
|
||||
@@ -0,0 +1,373 @@
|
||||
"""测试编排器单元测试"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, MagicMock, patch
|
||||
from apitest.orchestrator.test_orchestrator import TestOrchestrator
|
||||
from apitest.models.test_models import TestCase, TestSuiteResult, HTTPMethod
|
||||
from apitest.models.exceptions import TestRunException
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_manager():
|
||||
"""创建模拟配置管理器"""
|
||||
config_manager = Mock()
|
||||
config_manager.get_base_url.return_value = "http://localhost:8080"
|
||||
config_manager.get_timeout.return_value = 30
|
||||
config_manager.get_log_level.return_value = "INFO"
|
||||
config_manager.get_log_format.return_value = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
return config_manager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_logger_manager():
|
||||
"""创建模拟日志管理器"""
|
||||
logger_manager = Mock()
|
||||
logger = Mock()
|
||||
logger_manager.get_logger.return_value = logger
|
||||
return logger_manager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_orchestrator(mock_config_manager, mock_logger_manager):
|
||||
"""创建测试编排器实例"""
|
||||
return TestOrchestrator(
|
||||
config_manager=mock_config_manager,
|
||||
logger_manager=mock_logger_manager
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_test_cases():
|
||||
"""创建示例测试用例"""
|
||||
return [
|
||||
TestCase(
|
||||
id="TC001",
|
||||
name="测试用例1",
|
||||
description="测试用例1",
|
||||
module="test",
|
||||
endpoint="/api/test1",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
enabled=True
|
||||
),
|
||||
TestCase(
|
||||
id="TC002",
|
||||
name="测试用例2",
|
||||
description="测试用例2",
|
||||
module="test",
|
||||
endpoint="/api/test2",
|
||||
method=HTTPMethod.POST,
|
||||
headers={},
|
||||
enabled=True
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_init_test_orchestrator(mock_config_manager, mock_logger_manager):
|
||||
"""测试初始化测试编排器"""
|
||||
orchestrator = TestOrchestrator(
|
||||
config_manager=mock_config_manager,
|
||||
logger_manager=mock_logger_manager
|
||||
)
|
||||
|
||||
assert orchestrator.config_manager == mock_config_manager
|
||||
assert orchestrator.logger_manager == mock_logger_manager
|
||||
assert orchestrator.api_client is not None
|
||||
assert orchestrator.auth_manager is not None
|
||||
assert orchestrator.validation_engine is not None
|
||||
assert orchestrator.test_engine is not None
|
||||
assert orchestrator.report_manager is not None
|
||||
assert orchestrator.logger is not None
|
||||
|
||||
|
||||
def test_init_test_orchestrator_without_managers():
|
||||
"""测试初始化测试编排器(不提供管理器)"""
|
||||
mock_logger = Mock()
|
||||
orchestrator = TestOrchestrator(logger=mock_logger)
|
||||
|
||||
assert orchestrator.config_manager is not None
|
||||
assert orchestrator.logger_manager is not None
|
||||
assert orchestrator.api_client is not None
|
||||
assert orchestrator.auth_manager is not None
|
||||
assert orchestrator.validation_engine is not None
|
||||
assert orchestrator.test_engine is not None
|
||||
assert orchestrator.report_manager is not None
|
||||
assert orchestrator.logger is not None
|
||||
|
||||
|
||||
def test_load_test_cases_success(test_orchestrator, tmp_path):
|
||||
"""测试加载测试用例(成功)"""
|
||||
import json
|
||||
|
||||
test_data = [
|
||||
{
|
||||
"id": "TC001",
|
||||
"name": "测试用例1",
|
||||
"description": "测试用例1",
|
||||
"module": "test",
|
||||
"endpoint": "/api/test1",
|
||||
"method": "GET",
|
||||
"headers": {},
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"id": "TC002",
|
||||
"name": "测试用例2",
|
||||
"description": "测试用例2",
|
||||
"module": "test",
|
||||
"endpoint": "/api/test2",
|
||||
"method": "POST",
|
||||
"headers": {},
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
|
||||
test_file = tmp_path / "test_cases.json"
|
||||
with open(test_file, "w", encoding="utf-8") as f:
|
||||
json.dump(test_data, f)
|
||||
|
||||
test_cases = test_orchestrator.load_test_cases(test_file)
|
||||
|
||||
assert len(test_cases) == 2
|
||||
assert test_cases[0].id == "TC001"
|
||||
assert test_cases[1].id == "TC002"
|
||||
|
||||
|
||||
def test_load_test_cases_file_not_found(test_orchestrator, tmp_path):
|
||||
"""测试加载测试用例(文件不存在)"""
|
||||
test_file = tmp_path / "nonexistent.json"
|
||||
|
||||
with pytest.raises(TestRunException) as exc_info:
|
||||
test_orchestrator.load_test_cases(test_file)
|
||||
|
||||
assert "测试用例文件不存在" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_load_test_cases_invalid_json(test_orchestrator, tmp_path):
|
||||
"""测试加载测试用例(无效JSON)"""
|
||||
test_file = tmp_path / "invalid.json"
|
||||
with open(test_file, "w", encoding="utf-8") as f:
|
||||
f.write("invalid json")
|
||||
|
||||
with pytest.raises(TestRunException) as exc_info:
|
||||
test_orchestrator.load_test_cases(test_file)
|
||||
|
||||
assert "加载测试用例失败" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_load_test_cases_with_all_fields(test_orchestrator, tmp_path):
|
||||
"""测试加载测试用例(包含所有字段)"""
|
||||
import json
|
||||
|
||||
test_data = [
|
||||
{
|
||||
"id": "TC001",
|
||||
"name": "测试用例1",
|
||||
"description": "测试用例1",
|
||||
"module": "test",
|
||||
"endpoint": "/api/test1",
|
||||
"method": "GET",
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
"params": {"param1": "value1"},
|
||||
"body": {"key": "value"},
|
||||
"dependencies": ["TC002"],
|
||||
"tags": ["tag1", "tag2"],
|
||||
"priority": 1,
|
||||
"enabled": True,
|
||||
"timeout": 10,
|
||||
"validations": [{"type": "status_code", "value": 200}],
|
||||
"extract_config": [{"type": "extract", "field": "id", "var_name": "user_id"}]
|
||||
}
|
||||
]
|
||||
|
||||
test_file = tmp_path / "test_cases.json"
|
||||
with open(test_file, "w", encoding="utf-8") as f:
|
||||
json.dump(test_data, f)
|
||||
|
||||
test_cases = test_orchestrator.load_test_cases(test_file)
|
||||
|
||||
assert len(test_cases) == 1
|
||||
assert test_cases[0].id == "TC001"
|
||||
assert test_cases[0].method == HTTPMethod.GET
|
||||
assert test_cases[0].headers == {"Content-Type": "application/json"}
|
||||
assert test_cases[0].params == {"param1": "value1"}
|
||||
assert test_cases[0].body == {"key": "value"}
|
||||
assert test_cases[0].dependencies == ["TC002"]
|
||||
assert test_cases[0].tags == ["tag1", "tag2"]
|
||||
assert test_cases[0].priority == 1
|
||||
assert test_cases[0].enabled == True
|
||||
assert test_cases[0].timeout == 10
|
||||
assert len(test_cases[0].validations) == 1
|
||||
|
||||
|
||||
def test_run_test_suite_success(test_orchestrator, sample_test_cases, tmp_path):
|
||||
"""测试运行测试套件(成功)"""
|
||||
report_path = tmp_path / "test_report.html"
|
||||
|
||||
with patch.object(test_orchestrator.test_engine, 'execute_test_suite') as mock_execute:
|
||||
mock_result = Mock(spec=TestSuiteResult)
|
||||
mock_result.suite_name = "Test Suite"
|
||||
mock_result.total = 2
|
||||
mock_result.passed = 2
|
||||
mock_result.failed = 0
|
||||
mock_result.skipped = 0
|
||||
mock_result.pass_rate = 100.0
|
||||
mock_result.duration = 1.0
|
||||
mock_execute.return_value = mock_result
|
||||
|
||||
result = test_orchestrator.run_test_suite(
|
||||
sample_test_cases,
|
||||
generate_report=False
|
||||
)
|
||||
|
||||
assert result == mock_result
|
||||
mock_execute.assert_called_once_with(sample_test_cases, stop_on_failure=False)
|
||||
|
||||
|
||||
def test_run_test_suite_with_report(test_orchestrator, sample_test_cases, tmp_path):
|
||||
"""测试运行测试套件(生成报告)"""
|
||||
report_path = tmp_path / "test_report.html"
|
||||
|
||||
with patch.object(test_orchestrator.test_engine, 'execute_test_suite') as mock_execute, \
|
||||
patch.object(test_orchestrator.report_manager, 'generate_html_report') as mock_report:
|
||||
mock_result = Mock(spec=TestSuiteResult)
|
||||
mock_result.suite_name = "Test Suite"
|
||||
mock_result.total = 2
|
||||
mock_result.passed = 2
|
||||
mock_result.failed = 0
|
||||
mock_result.skipped = 0
|
||||
mock_result.pass_rate = 100.0
|
||||
mock_result.duration = 1.0
|
||||
mock_execute.return_value = mock_result
|
||||
|
||||
result = test_orchestrator.run_test_suite(
|
||||
sample_test_cases,
|
||||
generate_report=True,
|
||||
report_format="html",
|
||||
report_path=report_path
|
||||
)
|
||||
|
||||
assert result == mock_result
|
||||
mock_report.assert_called_once()
|
||||
|
||||
|
||||
def test_run_test_suite_stop_on_failure(test_orchestrator, sample_test_cases):
|
||||
"""测试运行测试套件(失败时停止)"""
|
||||
with patch.object(test_orchestrator.test_engine, 'execute_test_suite') as mock_execute:
|
||||
mock_result = Mock(spec=TestSuiteResult)
|
||||
mock_result.suite_name = "Test Suite"
|
||||
mock_result.total = 2
|
||||
mock_result.passed = 1
|
||||
mock_result.failed = 1
|
||||
mock_result.skipped = 0
|
||||
mock_result.pass_rate = 50.0
|
||||
mock_result.duration = 0.5
|
||||
mock_execute.return_value = mock_result
|
||||
|
||||
result = test_orchestrator.run_test_suite(
|
||||
sample_test_cases,
|
||||
stop_on_failure=True,
|
||||
generate_report=False
|
||||
)
|
||||
|
||||
assert result == mock_result
|
||||
mock_execute.assert_called_once_with(sample_test_cases, stop_on_failure=True)
|
||||
|
||||
|
||||
def test_run_test_suite_by_filter_module(test_orchestrator, sample_test_cases):
|
||||
"""测试按模块过滤运行测试套件"""
|
||||
with patch.object(test_orchestrator.test_engine, 'execute_test_cases_by_filter') as mock_execute:
|
||||
mock_result = Mock(spec=TestSuiteResult)
|
||||
mock_result.suite_name = "Test Suite"
|
||||
mock_result.total = 2
|
||||
mock_result.passed = 2
|
||||
mock_result.failed = 0
|
||||
mock_result.skipped = 0
|
||||
mock_result.pass_rate = 100.0
|
||||
mock_result.duration = 1.0
|
||||
mock_execute.return_value = mock_result
|
||||
|
||||
result = test_orchestrator.run_test_suite_by_filter(
|
||||
sample_test_cases,
|
||||
module_filter="test",
|
||||
generate_report=False
|
||||
)
|
||||
|
||||
assert result == mock_result
|
||||
mock_execute.assert_called_once_with(
|
||||
sample_test_cases,
|
||||
module_filter="test",
|
||||
tag_filter=None,
|
||||
priority_filter=None
|
||||
)
|
||||
|
||||
|
||||
def test_run_test_suite_by_filter_tag(test_orchestrator, sample_test_cases):
|
||||
"""测试按标签过滤运行测试套件"""
|
||||
with patch.object(test_orchestrator.test_engine, 'execute_test_cases_by_filter') as mock_execute:
|
||||
mock_result = Mock(spec=TestSuiteResult)
|
||||
mock_result.suite_name = "Test Suite"
|
||||
mock_result.total = 2
|
||||
mock_result.passed = 2
|
||||
mock_result.failed = 0
|
||||
mock_result.skipped = 0
|
||||
mock_result.pass_rate = 100.0
|
||||
mock_result.duration = 1.0
|
||||
mock_execute.return_value = mock_result
|
||||
|
||||
result = test_orchestrator.run_test_suite_by_filter(
|
||||
sample_test_cases,
|
||||
tag_filter=["smoke"],
|
||||
generate_report=False
|
||||
)
|
||||
|
||||
assert result == mock_result
|
||||
mock_execute.assert_called_once_with(
|
||||
sample_test_cases,
|
||||
module_filter=None,
|
||||
tag_filter=["smoke"],
|
||||
priority_filter=None
|
||||
)
|
||||
|
||||
|
||||
def test_run_test_suite_by_filter_priority(test_orchestrator, sample_test_cases):
|
||||
"""测试按优先级过滤运行测试套件"""
|
||||
with patch.object(test_orchestrator.test_engine, 'execute_test_cases_by_filter') as mock_execute:
|
||||
mock_result = Mock(spec=TestSuiteResult)
|
||||
mock_result.suite_name = "Test Suite"
|
||||
mock_result.total = 2
|
||||
mock_result.passed = 2
|
||||
mock_result.failed = 0
|
||||
mock_result.skipped = 0
|
||||
mock_result.pass_rate = 100.0
|
||||
mock_result.duration = 1.0
|
||||
mock_execute.return_value = mock_result
|
||||
|
||||
result = test_orchestrator.run_test_suite_by_filter(
|
||||
sample_test_cases,
|
||||
priority_filter=1,
|
||||
generate_report=False
|
||||
)
|
||||
|
||||
assert result == mock_result
|
||||
mock_execute.assert_called_once_with(
|
||||
sample_test_cases,
|
||||
module_filter=None,
|
||||
tag_filter=None,
|
||||
priority_filter=1
|
||||
)
|
||||
|
||||
|
||||
def test_set_base_url(test_orchestrator):
|
||||
"""测试设置基础URL"""
|
||||
test_orchestrator.set_base_url("http://new-api.example.com")
|
||||
|
||||
assert test_orchestrator.api_client.base_url == "http://new-api.example.com"
|
||||
|
||||
|
||||
def test_set_auth_token(test_orchestrator):
|
||||
"""测试设置认证令牌"""
|
||||
test_orchestrator.set_auth_token("test-token-123")
|
||||
|
||||
assert test_orchestrator.auth_manager.get_token() == "test-token-123"
|
||||
@@ -0,0 +1,480 @@
|
||||
import pytest
|
||||
from apitest.models.test_models import TestCase, HTTPMethod, PerformanceMetrics
|
||||
from apitest.core.validation_engine import ValidationEngine
|
||||
from apitest.models.exceptions import ValidationException
|
||||
|
||||
|
||||
class TestValidationEngine:
|
||||
"""测试ValidationEngine验证引擎"""
|
||||
|
||||
@pytest.fixture
|
||||
def validation_engine(self):
|
||||
"""创建验证引擎实例"""
|
||||
return ValidationEngine()
|
||||
|
||||
def test_validate_status_code_success(self, validation_engine):
|
||||
"""测试状态码验证成功"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试状态码",
|
||||
description="测试状态码验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "status_code", "value": 200}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"message": "success"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
assert error == ""
|
||||
|
||||
def test_validate_status_code_failure(self, validation_engine):
|
||||
"""测试状态码验证失败"""
|
||||
test_case = TestCase(
|
||||
id="TC001",
|
||||
name="测试状态码",
|
||||
description="测试状态码验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "status_code", "value": 200}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
404,
|
||||
{"message": "not found"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "状态码验证失败" in error
|
||||
|
||||
def test_validate_contains_success(self, validation_engine):
|
||||
"""测试包含验证成功"""
|
||||
test_case = TestCase(
|
||||
id="TC002",
|
||||
name="测试包含",
|
||||
description="测试包含验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "contains", "value": "success"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"message": "operation success"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
assert error == ""
|
||||
|
||||
def test_validate_contains_failure(self, validation_engine):
|
||||
"""测试包含验证失败"""
|
||||
test_case = TestCase(
|
||||
id="TC002",
|
||||
name="测试包含",
|
||||
description="测试包含验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "contains", "value": "error"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"message": "operation success"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "包含验证失败" in error
|
||||
|
||||
def test_validate_contains_with_field(self, validation_engine):
|
||||
"""测试字段包含验证"""
|
||||
test_case = TestCase(
|
||||
id="TC003",
|
||||
name="测试字段包含",
|
||||
description="测试字段包含验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "contains", "field": "message", "value": "success"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"message": "operation success"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
|
||||
def test_validate_equals_success(self, validation_engine):
|
||||
"""测试相等验证成功"""
|
||||
test_case = TestCase(
|
||||
id="TC004",
|
||||
name="测试相等",
|
||||
description="测试相等验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "equals", "field": "status", "value": "ok"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"status": "ok"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
|
||||
def test_validate_equals_failure(self, validation_engine):
|
||||
"""测试相等验证失败"""
|
||||
test_case = TestCase(
|
||||
id="TC004",
|
||||
name="测试相等",
|
||||
description="测试相等验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "equals", "field": "status", "value": "ok"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"status": "error"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "相等验证失败" in error
|
||||
|
||||
def test_validate_json_path_success(self, validation_engine):
|
||||
"""测试JSON路径验证成功"""
|
||||
test_case = TestCase(
|
||||
id="TC005",
|
||||
name="测试JSON路径",
|
||||
description="测试JSON路径验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "json_path", "path": "data.user.name", "value": "John"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"data": {"user": {"name": "John"}}},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
|
||||
def test_validate_json_path_failure(self, validation_engine):
|
||||
"""测试JSON路径验证失败"""
|
||||
test_case = TestCase(
|
||||
id="TC005",
|
||||
name="测试JSON路径",
|
||||
description="测试JSON路径验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "json_path", "path": "data.user.name", "value": "Jane"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"data": {"user": {"name": "John"}}},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "JSON路径验证失败" in error
|
||||
|
||||
def test_validate_regex_success(self, validation_engine):
|
||||
"""测试正则表达式验证成功"""
|
||||
test_case = TestCase(
|
||||
id="TC006",
|
||||
name="测试正则表达式",
|
||||
description="测试正则表达式验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "regex", "field": "email", "pattern": r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"email": "test@example.com"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
|
||||
def test_validate_regex_failure(self, validation_engine):
|
||||
"""测试正则表达式验证失败"""
|
||||
test_case = TestCase(
|
||||
id="TC006",
|
||||
name="测试正则表达式",
|
||||
description="测试正则表达式验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "regex", "field": "email", "pattern": r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"email": "invalid-email"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "正则表达式验证失败" in error
|
||||
|
||||
def test_validate_header_success(self, validation_engine):
|
||||
"""测试响应头验证成功"""
|
||||
test_case = TestCase(
|
||||
id="TC007",
|
||||
name="测试响应头",
|
||||
description="测试响应头验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "header", "name": "Content-Type", "value": "application/json"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{},
|
||||
{"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
|
||||
def test_validate_header_not_found(self, validation_engine):
|
||||
"""测试响应头不存在"""
|
||||
test_case = TestCase(
|
||||
id="TC007",
|
||||
name="测试响应头",
|
||||
description="测试响应头验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "header", "name": "X-Custom-Header"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{},
|
||||
{"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "响应头中未找到" in error
|
||||
|
||||
def test_validate_schema_success(self, validation_engine):
|
||||
"""测试结构验证成功"""
|
||||
test_case = TestCase(
|
||||
id="TC008",
|
||||
name="测试结构",
|
||||
description="测试结构验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "schema", "schema": {"name": "str", "age": "int"}}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"name": "John", "age": 30},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
|
||||
def test_validate_schema_failure(self, validation_engine):
|
||||
"""测试结构验证失败"""
|
||||
test_case = TestCase(
|
||||
id="TC008",
|
||||
name="测试结构",
|
||||
description="测试结构验证",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "schema", "schema": {"name": "str", "age": "int"}}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"name": "John", "age": "thirty"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "字段 age 类型错误" in error
|
||||
|
||||
def test_validate_performance_success(self, validation_engine):
|
||||
"""测试性能验证成功"""
|
||||
from datetime import datetime
|
||||
performance = PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=1000,
|
||||
request_size=100,
|
||||
response_size=200
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_performance(performance, 5000)
|
||||
|
||||
assert passed == True
|
||||
assert error == ""
|
||||
|
||||
def test_validate_performance_failure(self, validation_engine):
|
||||
"""测试性能验证失败"""
|
||||
from datetime import datetime
|
||||
performance = PerformanceMetrics(
|
||||
timestamp=datetime.now(),
|
||||
response_time=6000,
|
||||
request_size=100,
|
||||
response_size=200
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_performance(performance, 5000)
|
||||
|
||||
assert passed == False
|
||||
assert "响应时间超过阈值" in error
|
||||
|
||||
def test_validate_multiple_validations(self, validation_engine):
|
||||
"""测试多个验证规则"""
|
||||
test_case = TestCase(
|
||||
id="TC009",
|
||||
name="测试多验证",
|
||||
description="测试多个验证规则",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[
|
||||
{"type": "status_code", "value": 200},
|
||||
{"type": "contains", "value": "success"},
|
||||
{"type": "equals", "field": "status", "value": "ok"}
|
||||
]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"status": "ok", "message": "operation success"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
|
||||
def test_validate_multiple_validations_failure(self, validation_engine):
|
||||
"""测试多个验证规则(其中一个失败)"""
|
||||
test_case = TestCase(
|
||||
id="TC009",
|
||||
name="测试多验证",
|
||||
description="测试多个验证规则",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[
|
||||
{"type": "status_code", "value": 200},
|
||||
{"type": "contains", "value": "error"},
|
||||
{"type": "equals", "field": "status", "value": "ok"}
|
||||
]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"status": "ok", "message": "operation success"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "包含验证失败" in error
|
||||
|
||||
def test_validate_no_validations(self, validation_engine):
|
||||
"""测试无验证规则"""
|
||||
test_case = TestCase(
|
||||
id="TC010",
|
||||
name="测试无验证",
|
||||
description="测试无验证规则",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={}
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"message": "success"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == True
|
||||
assert error == ""
|
||||
|
||||
def test_validate_unsupported_type(self, validation_engine):
|
||||
"""测试不支持的验证类型"""
|
||||
test_case = TestCase(
|
||||
id="TC011",
|
||||
name="测试不支持的类型",
|
||||
description="测试不支持的验证类型",
|
||||
module="test",
|
||||
endpoint="/api/test",
|
||||
method=HTTPMethod.GET,
|
||||
headers={},
|
||||
validations=[{"type": "unsupported_type"}]
|
||||
)
|
||||
|
||||
passed, error = validation_engine.validate_response(
|
||||
test_case,
|
||||
200,
|
||||
{"message": "success"},
|
||||
{}
|
||||
)
|
||||
|
||||
assert passed == False
|
||||
assert "不支持的验证类型" in error
|
||||
Reference in New Issue
Block a user