|
|
"""
|
|
|
HTTP 响应工具测试
|
|
|
"""
|
|
|
|
|
|
import pytest
|
|
|
from apiflask import APIBlueprint, Schema
|
|
|
from apiflask.fields import Integer, String
|
|
|
from iti.applications import create_app
|
|
|
from iti.applications.common.utils import (
|
|
|
success,
|
|
|
fail,
|
|
|
page,
|
|
|
pagination_builder,
|
|
|
PaginationSchema,
|
|
|
)
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def app():
|
|
|
"""创建测试应用"""
|
|
|
test_bp = APIBlueprint("test_http_utils", __name__)
|
|
|
|
|
|
@test_bp.get("/test/success")
|
|
|
def test_success():
|
|
|
return success({"message": "ok"}, message="操作成功")
|
|
|
|
|
|
@test_bp.get("/test/fail")
|
|
|
def test_fail():
|
|
|
return fail("这是一个错误示例", code=400)
|
|
|
|
|
|
@test_bp.get("/test/page")
|
|
|
def test_page():
|
|
|
return page(
|
|
|
[{"id": 1}, {"id": 2}, {"id": 3}],
|
|
|
pagination_builder(None, page=1, size=10, total=30),
|
|
|
message="分页测试成功",
|
|
|
)
|
|
|
|
|
|
class IndexSchema(Schema):
|
|
|
id = Integer()
|
|
|
name = String()
|
|
|
|
|
|
@test_bp.get("/")
|
|
|
@test_bp.output(IndexSchema)
|
|
|
def test_index():
|
|
|
return success({"id": 1, "name": "test"})
|
|
|
|
|
|
class TestModule:
|
|
|
name = "test_http_utils"
|
|
|
|
|
|
def register_routes(self, app):
|
|
|
app.register_blueprint(test_bp)
|
|
|
|
|
|
return create_app("test", modules=[TestModule()])
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
def client(app):
|
|
|
"""创建测试客户端"""
|
|
|
return app.test_client()
|
|
|
|
|
|
|
|
|
class TestSuccessFunction:
|
|
|
"""测试 success() 函数"""
|
|
|
|
|
|
def test_success_default(self):
|
|
|
"""测试默认参数"""
|
|
|
result = success()
|
|
|
|
|
|
assert result["code"] == 200
|
|
|
assert result["message"] == "成功"
|
|
|
assert result["data"] is None
|
|
|
|
|
|
def test_success_with_data(self):
|
|
|
"""测试带数据"""
|
|
|
data = {"id": 1, "name": "test"}
|
|
|
result = success(data)
|
|
|
|
|
|
assert result["code"] == 200
|
|
|
assert result["message"] == "成功"
|
|
|
assert result["data"] == data
|
|
|
|
|
|
def test_success_with_custom_message(self):
|
|
|
"""测试自定义消息"""
|
|
|
result = success({"id": 1}, message="查询成功")
|
|
|
|
|
|
assert result["code"] == 200
|
|
|
assert result["message"] == "查询成功"
|
|
|
assert result["data"]["id"] == 1
|
|
|
|
|
|
def test_success_with_custom_code(self):
|
|
|
"""测试自定义状态码"""
|
|
|
result = success({"id": 1}, message="创建成功", code=201)
|
|
|
|
|
|
assert result["code"] == 201
|
|
|
assert result["message"] == "创建成功"
|
|
|
|
|
|
def test_success_with_list(self):
|
|
|
"""测试列表数据"""
|
|
|
data = [{"id": 1}, {"id": 2}]
|
|
|
result = success(data)
|
|
|
|
|
|
assert result["code"] == 200
|
|
|
assert isinstance(result["data"], list)
|
|
|
assert len(result["data"]) == 2
|
|
|
|
|
|
|
|
|
class TestFailFunction:
|
|
|
"""测试 fail() 函数"""
|
|
|
|
|
|
def test_fail_default(self):
|
|
|
"""测试默认参数"""
|
|
|
result = fail()
|
|
|
|
|
|
assert result["code"] == 500
|
|
|
assert result["message"] == "操作失败"
|
|
|
assert result["data"] is None
|
|
|
|
|
|
def test_fail_with_custom_message(self):
|
|
|
"""测试自定义错误消息"""
|
|
|
result = fail("用户不存在")
|
|
|
|
|
|
assert result["code"] == 500
|
|
|
assert result["message"] == "用户不存在"
|
|
|
assert result["data"] is None
|
|
|
|
|
|
def test_fail_with_custom_code(self):
|
|
|
"""测试自定义错误码"""
|
|
|
result = fail("参数错误", code=400)
|
|
|
|
|
|
assert result["code"] == 400
|
|
|
assert result["message"] == "参数错误"
|
|
|
|
|
|
def test_fail_with_data(self):
|
|
|
"""测试带额外数据"""
|
|
|
errors = {"username": ["必填项"], "email": ["格式错误"]}
|
|
|
result = fail("验证失败", code=422, data=errors)
|
|
|
|
|
|
assert result["code"] == 422
|
|
|
assert result["message"] == "验证失败"
|
|
|
assert result["data"] == errors
|
|
|
|
|
|
def test_fail_returns_dict(self):
|
|
|
"""测试返回字典而非元组"""
|
|
|
result = fail("错误", code=404)
|
|
|
|
|
|
# 确保返回的是字典,不是元组
|
|
|
assert isinstance(result, dict)
|
|
|
assert "code" in result
|
|
|
assert "message" in result
|
|
|
assert "data" in result
|
|
|
|
|
|
|
|
|
class TestPaginationBuilder:
|
|
|
"""测试 pagination_builder() 函数"""
|
|
|
|
|
|
def test_manual_mode_basic(self):
|
|
|
"""测试手动模式基础功能"""
|
|
|
result = pagination_builder(None, page=1, size=10, total=100)
|
|
|
|
|
|
assert result["page"] == 1
|
|
|
assert result["size"] == 10
|
|
|
assert result["pages"] == 10 # ceil(100/10)
|
|
|
assert result["total"] == 100
|
|
|
|
|
|
def test_manual_mode_auto_calculate_pages(self):
|
|
|
"""测试自动计算总页数"""
|
|
|
result = pagination_builder(None, page=1, size=20, total=95)
|
|
|
|
|
|
assert result["pages"] == 5 # ceil(95/20) = 5
|
|
|
|
|
|
def test_manual_mode_with_explicit_pages(self):
|
|
|
"""测试显式指定总页数"""
|
|
|
result = pagination_builder(None, page=1, size=10, total=100, pages=8)
|
|
|
|
|
|
assert result["pages"] == 8
|
|
|
|
|
|
def test_manual_mode_zero_total(self):
|
|
|
"""测试总数为 0"""
|
|
|
result = pagination_builder(None, page=1, size=10, total=0)
|
|
|
|
|
|
assert result["pages"] == 0
|
|
|
assert result["total"] == 0
|
|
|
|
|
|
def test_manual_mode_urls_are_none_outside_request(self):
|
|
|
"""测试在请求上下文外 URL 为 None"""
|
|
|
result = pagination_builder(None, page=1, size=10, total=100)
|
|
|
|
|
|
assert result["current"] is None
|
|
|
assert result["next"] is None
|
|
|
assert result["prev"] is None
|
|
|
assert result["first"] is None
|
|
|
assert result["last"] is None
|
|
|
|
|
|
def test_auto_mode_with_mock_pagination(self):
|
|
|
"""测试自动模式(使用模拟的 Pagination 对象)"""
|
|
|
|
|
|
# 创建模拟的 Pagination 对象
|
|
|
class MockPagination:
|
|
|
page = 2
|
|
|
per_page = 10
|
|
|
pages = 10
|
|
|
total = 100
|
|
|
has_prev = True
|
|
|
has_next = True
|
|
|
prev_num = 1
|
|
|
next_num = 3
|
|
|
|
|
|
mock_pagination = MockPagination()
|
|
|
result = pagination_builder(mock_pagination)
|
|
|
|
|
|
assert result["page"] == 2
|
|
|
assert result["size"] == 10 # per_page → size
|
|
|
assert result["pages"] == 10
|
|
|
assert result["total"] == 100
|
|
|
|
|
|
|
|
|
class TestPageFunction:
|
|
|
"""测试 page() 函数"""
|
|
|
|
|
|
def test_page_with_list_and_dict(self):
|
|
|
"""测试列表 + 字典分页信息"""
|
|
|
items = [{"id": 1}, {"id": 2}]
|
|
|
pagination_info = pagination_builder(None, page=1, size=10, total=50)
|
|
|
|
|
|
result = page(items, pagination_info)
|
|
|
|
|
|
assert result["code"] == 200
|
|
|
assert result["message"] == "成功"
|
|
|
assert result["data"]["items"] == items
|
|
|
assert result["data"]["page"]["page"] == 1
|
|
|
assert result["data"]["page"]["total"] == 50
|
|
|
|
|
|
def test_page_with_custom_message(self):
|
|
|
"""测试自定义消息"""
|
|
|
items = [{"id": 1}]
|
|
|
pagination_info = pagination_builder(None, page=1, size=10, total=10)
|
|
|
|
|
|
result = page(items, pagination_info, message="获取列表成功")
|
|
|
|
|
|
assert result["message"] == "获取列表成功"
|
|
|
|
|
|
def test_page_with_custom_code(self):
|
|
|
"""测试自定义状态码"""
|
|
|
items = [{"id": 1}]
|
|
|
pagination_info = pagination_builder(None, page=1, size=10, total=10)
|
|
|
|
|
|
result = page(items, pagination_info, code=201)
|
|
|
|
|
|
assert result["code"] == 201
|
|
|
|
|
|
def test_page_with_mock_pagination_object(self):
|
|
|
"""测试直接传入 Pagination 对象"""
|
|
|
|
|
|
class MockPagination:
|
|
|
items = [{"id": 1}, {"id": 2}, {"id": 3}]
|
|
|
page = 1
|
|
|
per_page = 10
|
|
|
pages = 5
|
|
|
total = 50
|
|
|
has_prev = False
|
|
|
has_next = True
|
|
|
prev_num = None
|
|
|
next_num = 2
|
|
|
|
|
|
mock_pagination = MockPagination()
|
|
|
result = page(mock_pagination)
|
|
|
|
|
|
assert result["code"] == 200
|
|
|
assert len(result["data"]["items"]) == 3
|
|
|
assert result["data"]["page"]["page"] == 1
|
|
|
assert result["data"]["page"]["size"] == 10
|
|
|
assert result["data"]["page"]["total"] == 50
|
|
|
|
|
|
def test_page_with_items_and_pagination_object(self):
|
|
|
"""测试传入数据列表 + Pagination 对象"""
|
|
|
|
|
|
class MockPagination:
|
|
|
items = [{"id": 1}, {"id": 2}]
|
|
|
page = 2
|
|
|
per_page = 20
|
|
|
pages = 3
|
|
|
total = 60
|
|
|
has_prev = True
|
|
|
has_next = True
|
|
|
prev_num = 1
|
|
|
next_num = 3
|
|
|
|
|
|
mock_pagination = MockPagination()
|
|
|
custom_items = [{"id": 10}, {"id": 20}] # 使用自定义数据(如序列化后的)
|
|
|
|
|
|
result = page(custom_items, mock_pagination)
|
|
|
|
|
|
assert result["data"]["items"] == custom_items # 使用传入的 items
|
|
|
assert result["data"]["page"]["page"] == 2
|
|
|
assert result["data"]["page"]["size"] == 20
|
|
|
|
|
|
def test_page_empty_items(self):
|
|
|
"""测试空数据列表"""
|
|
|
items = []
|
|
|
pagination_info = pagination_builder(None, page=1, size=10, total=0)
|
|
|
|
|
|
result = page(items, pagination_info)
|
|
|
|
|
|
assert result["data"]["items"] == []
|
|
|
assert result["data"]["page"]["total"] == 0
|
|
|
assert result["data"]["page"]["pages"] == 0
|
|
|
|
|
|
|
|
|
class TestSchemaDefinitions:
|
|
|
"""测试 Schema 定义"""
|
|
|
|
|
|
def test_pagination_schema_exists(self):
|
|
|
"""测试 PaginationSchema 存在"""
|
|
|
assert PaginationSchema is not None
|
|
|
|
|
|
# 检查字段
|
|
|
schema = PaginationSchema()
|
|
|
assert "page" in schema.fields
|
|
|
assert "size" in schema.fields
|
|
|
assert "pages" in schema.fields
|
|
|
assert "total" in schema.fields
|
|
|
|
|
|
|
|
|
class TestIntegrationWithFlaskApp:
|
|
|
"""测试与 Flask 应用集成"""
|
|
|
|
|
|
def test_success_in_route(self, client):
|
|
|
"""测试 success() 在路由中使用"""
|
|
|
response = client.get("/test/success")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
data = response.get_json()
|
|
|
|
|
|
assert data["code"] == 200
|
|
|
assert data["message"] == "操作成功"
|
|
|
assert "message" in data["data"]
|
|
|
|
|
|
def test_fail_in_route(self, client):
|
|
|
"""测试 fail() 在路由中使用"""
|
|
|
response = client.get("/test/fail")
|
|
|
|
|
|
# 确保 HTTP 状态码是 200(业务错误)
|
|
|
assert response.status_code == 200
|
|
|
data = response.get_json()
|
|
|
|
|
|
assert data["code"] == 400
|
|
|
assert data["message"] == "这是一个错误示例"
|
|
|
|
|
|
def test_page_in_route(self, client):
|
|
|
"""测试 page() 在路由中使用"""
|
|
|
response = client.get("/test/page")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
data = response.get_json()
|
|
|
|
|
|
assert data["code"] == 200
|
|
|
assert data["message"] == "分页测试成功"
|
|
|
assert "items" in data["data"]
|
|
|
assert "page" in data["data"]
|
|
|
assert len(data["data"]["items"]) == 3
|
|
|
assert data["data"]["page"]["page"] == 1
|
|
|
assert data["data"]["page"]["size"] == 10
|
|
|
assert data["data"]["page"]["total"] == 30
|
|
|
|
|
|
def test_index_with_schema(self, client):
|
|
|
"""测试首页使用 Schema 验证"""
|
|
|
response = client.get("/")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
data = response.get_json()
|
|
|
|
|
|
assert data["code"] == 200
|
|
|
assert data["message"] == "成功"
|
|
|
assert "id" in data["data"]
|
|
|
assert "name" in data["data"]
|
|
|
# 验证 Schema 过滤了无关字段(如果有的话)
|
|
|
|
|
|
|
|
|
class TestEdgeCases:
|
|
|
"""测试边界情况"""
|
|
|
|
|
|
def test_pagination_builder_with_fractional_pages(self):
|
|
|
"""测试非整数页数计算"""
|
|
|
result = pagination_builder(None, page=1, size=7, total=15)
|
|
|
|
|
|
# ceil(15/7) = 3
|
|
|
assert result["pages"] == 3
|
|
|
|
|
|
def test_pagination_builder_exact_pages(self):
|
|
|
"""测试整数页数"""
|
|
|
result = pagination_builder(None, page=1, size=10, total=100)
|
|
|
|
|
|
assert result["pages"] == 10
|
|
|
|
|
|
def test_success_with_none_data(self):
|
|
|
"""测试 data 为 None"""
|
|
|
result = success(None, message="无数据")
|
|
|
|
|
|
assert result["data"] is None
|
|
|
assert result["message"] == "无数据"
|
|
|
|
|
|
def test_fail_with_empty_string_message(self):
|
|
|
"""测试空字符串消息"""
|
|
|
result = fail("", code=400)
|
|
|
|
|
|
assert result["message"] == ""
|
|
|
assert result["code"] == 400
|
|
|
|
|
|
def test_page_with_large_total(self):
|
|
|
"""测试大数据量分页"""
|
|
|
items = [{"id": i} for i in range(100)]
|
|
|
pagination_info = pagination_builder(None, page=1, size=100, total=1000000)
|
|
|
|
|
|
result = page(items, pagination_info)
|
|
|
|
|
|
assert result["data"]["page"]["total"] == 1000000
|
|
|
assert result["data"]["page"]["pages"] == 10000
|