Fix API compatibility and add user/role/permission and asset import/export
This commit is contained in:
426
backend_new/tests/api/test_api_integration.py
Normal file
426
backend_new/tests/api/test_api_integration.py
Normal file
@@ -0,0 +1,426 @@
|
||||
"""
|
||||
接口集成测试
|
||||
|
||||
测试内容:
|
||||
- 所有API接口功能测试
|
||||
- 参数验证测试
|
||||
- 错误处理测试
|
||||
- 响应时间测试
|
||||
- 并发测试
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import time
|
||||
import asyncio
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
# from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
# class TestAPIEndpoints:
|
||||
# """测试所有API端点"""
|
||||
#
|
||||
# def test_health_check(self, client: TestClient):
|
||||
# """测试健康检查接口"""
|
||||
# response = client.get("/health")
|
||||
# assert response.status_code == 200
|
||||
# assert response.json()["status"] == "healthy"
|
||||
#
|
||||
# def test_api_root(self, client: TestClient):
|
||||
# """测试API根路径"""
|
||||
# response = client.get("/api/v1/")
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
# assert "version" in data
|
||||
# assert "name" in data
|
||||
|
||||
|
||||
# class TestParameterValidation:
|
||||
# """测试参数验证"""
|
||||
#
|
||||
# def test_query_parameter_validation(self, client: TestClient, auth_headers):
|
||||
# """测试查询参数验证"""
|
||||
# # 无效的分页参数
|
||||
# response = client.get(
|
||||
# "/api/v1/assets?page=-1&page_size=0",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# # 超大的page_size
|
||||
# response = client.get(
|
||||
# "/api/v1/assets?page_size=10000",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# def test_path_parameter_validation(self, client: TestClient, auth_headers):
|
||||
# """测试路径参数验证"""
|
||||
# # 无效的ID
|
||||
# response = client.get(
|
||||
# "/api/v1/assets/abc",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# # 负数ID
|
||||
# response = client.get(
|
||||
# "/api/v1/assets/-1",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# def test_request_body_validation(self, client: TestClient, auth_headers):
|
||||
# """测试请求体验证"""
|
||||
# # 缺少必填字段
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# json={"asset_name": "测试"} # 缺少device_type_id
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# # 无效的数据类型
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# json={
|
||||
# "asset_name": "测试",
|
||||
# "device_type_id": "not_a_number", # 应该是数字
|
||||
# "organization_id": 1
|
||||
# }
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# # 超长字符串
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# json={
|
||||
# "asset_name": "a" * 300, # 超过最大长度
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1
|
||||
# }
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# def test_enum_validation(self, client: TestClient, auth_headers):
|
||||
# """测试枚举值验证"""
|
||||
# # 无效的状态值
|
||||
# response = client.get(
|
||||
# "/api/v1/assets?status=invalid_status",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# def test_date_validation(self, client: TestClient, auth_headers):
|
||||
# """测试日期格式验证"""
|
||||
# # 无效的日期格式
|
||||
# response = client.get(
|
||||
# "/api/v1/assets?purchase_date_start=invalid-date",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
#
|
||||
# # 结束日期早于开始日期
|
||||
# response = client.get(
|
||||
# "/api/v1/assets?purchase_date_start=2024-12-31&purchase_date_end=2024-01-01",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 400
|
||||
|
||||
|
||||
# class TestErrorHandling:
|
||||
# """测试错误处理"""
|
||||
#
|
||||
# def test_404_not_found(self, client: TestClient, auth_headers):
|
||||
# """测试404错误"""
|
||||
# response = client.get(
|
||||
# "/api/v1/assets/999999",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 404
|
||||
# data = response.json()
|
||||
# assert "message" in data
|
||||
#
|
||||
# def test_401_unauthorized(self, client: TestClient):
|
||||
# """测试401未授权错误"""
|
||||
# response = client.get("/api/v1/assets")
|
||||
# assert response.status_code == 401
|
||||
#
|
||||
# def test_403_forbidden(self, client: TestClient, auth_headers):
|
||||
# """测试403禁止访问"""
|
||||
# # 使用普通用户token访问管理员接口
|
||||
# response = client.delete(
|
||||
# "/api/v1/assets/1",
|
||||
# headers=auth_headers # 普通用户token
|
||||
# )
|
||||
# assert response.status_code == 403
|
||||
#
|
||||
# def test_409_conflict(self, client: TestClient, auth_headers):
|
||||
# """测试409冲突错误"""
|
||||
# # 尝试创建重复的资源
|
||||
# asset_data = {
|
||||
# "asset_name": "测试资产",
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1,
|
||||
# "serial_number": "UNIQUE-SN-001"
|
||||
# }
|
||||
#
|
||||
# # 第一次创建成功
|
||||
# client.post("/api/v1/assets", headers=auth_headers, json=asset_data)
|
||||
#
|
||||
# # 第二次创建应该返回409
|
||||
# response = client.post("/api/v1/assets", headers=auth_headers, json=asset_data)
|
||||
# assert response.status_code == 409
|
||||
#
|
||||
# def test_422_validation_error(self, client: TestClient, auth_headers):
|
||||
# """测试422验证错误"""
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# json={}
|
||||
# )
|
||||
# assert response.status_code == 422
|
||||
# data = response.json()
|
||||
# assert "errors" in data
|
||||
#
|
||||
# def test_500_internal_error(self, client: TestClient, auth_headers):
|
||||
# """测试500服务器错误"""
|
||||
# # 这个测试需要mock一个会抛出异常的场景
|
||||
# pass
|
||||
#
|
||||
# def test_error_response_format(self, client: TestClient, auth_headers):
|
||||
# """测试错误响应格式"""
|
||||
# response = client.get(
|
||||
# "/api/v1/assets/999999",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 404
|
||||
#
|
||||
# data = response.json()
|
||||
# # 验证错误响应包含必要字段
|
||||
# assert "code" in data
|
||||
# assert "message" in data
|
||||
# assert "timestamp" in data
|
||||
|
||||
|
||||
# class TestResponseTime:
|
||||
# """测试接口响应时间"""
|
||||
#
|
||||
# @pytest.mark.parametrize("endpoint,expected_max_time", [
|
||||
# ("/api/v1/assets", 0.5), # 资产列表应该在500ms内返回
|
||||
# ("/api/v1/assets/1", 0.3), # 资产详情应该在300ms内返回
|
||||
# ("/api/v1/statistics/overview", 1.0), # 统计概览在1秒内返回
|
||||
# ])
|
||||
# def test_response_time_within_limit(self, client, auth_headers, endpoint, expected_max_time):
|
||||
# """测试响应时间在限制内"""
|
||||
# start_time = time.time()
|
||||
#
|
||||
# response = client.get(endpoint, headers=auth_headers)
|
||||
#
|
||||
# elapsed_time = time.time() - start_time
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# assert elapsed_time < expected_max_time, \
|
||||
# f"响应时间 {elapsed_time:.2f}s 超过限制 {expected_max_time}s"
|
||||
#
|
||||
# def test_concurrent_requests_performance(self, client, auth_headers):
|
||||
# """测试并发请求性能"""
|
||||
# urls = ["/api/v1/assets"] * 10
|
||||
#
|
||||
# start_time = time.time()
|
||||
#
|
||||
# with ThreadPoolExecutor(max_workers=5) as executor:
|
||||
# futures = [
|
||||
# executor.submit(
|
||||
# client.get,
|
||||
# url,
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# for url in urls
|
||||
# ]
|
||||
# responses = [f.result() for f in futures]
|
||||
#
|
||||
# elapsed_time = time.time() - start_time
|
||||
#
|
||||
# # 所有请求都应该成功
|
||||
# assert all(r.status_code == 200 for r in responses)
|
||||
#
|
||||
# # 10个并发请求应该在3秒内完成
|
||||
# assert elapsed_time < 3.0
|
||||
#
|
||||
# def test_large_list_response_time(self, client, auth_headers, db):
|
||||
# """测试大数据量列表响应时间"""
|
||||
# # 创建1000条测试数据
|
||||
# # ... 创建数据
|
||||
#
|
||||
# start_time = time.time()
|
||||
# response = client.get("/api/v1/assets?page=1&page_size=100", headers=auth_headers)
|
||||
# elapsed_time = time.time() - start_time
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# assert elapsed_time < 1.0 # 100条记录应该在1秒内返回
|
||||
#
|
||||
# def test_complex_query_response_time(self, client, auth_headers):
|
||||
# """测试复杂查询响应时间"""
|
||||
# params = {
|
||||
# "keyword": "联想",
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1,
|
||||
# "status": "in_use",
|
||||
# "purchase_date_start": "2024-01-01",
|
||||
# "purchase_date_end": "2024-12-31",
|
||||
# "page": 1,
|
||||
# "page_size": 20
|
||||
# }
|
||||
#
|
||||
# start_time = time.time()
|
||||
# response = client.get("/api/v1/assets", params=params, headers=auth_headers)
|
||||
# elapsed_time = time.time() - start_time
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# assert elapsed_time < 1.0
|
||||
|
||||
|
||||
# class TestConcurrentRequests:
|
||||
# """测试并发请求"""
|
||||
#
|
||||
# def test_concurrent_asset_creation(self, client, auth_headers):
|
||||
# """测试并发创建资产"""
|
||||
# asset_data = {
|
||||
# "asset_name": "并发测试资产",
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1
|
||||
# }
|
||||
#
|
||||
# def create_asset(i):
|
||||
# data = asset_data.copy()
|
||||
# data["asset_name"] = f"并发测试资产-{i}"
|
||||
# return client.post("/api/v1/assets", headers=auth_headers, json=data)
|
||||
#
|
||||
# with ThreadPoolExecutor(max_workers=10) as executor:
|
||||
# futures = [executor.submit(create_asset, i) for i in range(50)]
|
||||
# responses = [f.result() for f in futures]
|
||||
#
|
||||
# # 所有请求都应该成功
|
||||
# success_count = sum(1 for r in responses if r.status_code == 201)
|
||||
# assert success_count == 50
|
||||
#
|
||||
# def test_concurrent_same_resource_update(self, client, auth_headers, test_asset):
|
||||
# """测试并发更新同一资源"""
|
||||
# def update_asset(i):
|
||||
# return client.put(
|
||||
# f"/api/v1/assets/{test_asset.id}",
|
||||
# headers=auth_headers,
|
||||
# json={"location": f"位置-{i}"}
|
||||
# )
|
||||
#
|
||||
# with ThreadPoolExecutor(max_workers=5) as executor:
|
||||
# futures = [executor.submit(update_asset, i) for i in range(10)]
|
||||
# responses = [f.result() for f in futures]
|
||||
#
|
||||
# # 所有请求都应该成功(乐观锁会处理并发)
|
||||
# assert all(r.status_code in [200, 409] for r in responses)
|
||||
#
|
||||
# @pytest.mark.slow
|
||||
# def test_high_concurrent_load(self, client, auth_headers):
|
||||
# """测试高并发负载"""
|
||||
# def make_request():
|
||||
# return client.get("/api/v1/assets", headers=auth_headers)
|
||||
#
|
||||
# # 模拟100个并发请求
|
||||
# with ThreadPoolExecutor(max_workers=20) as executor:
|
||||
# futures = [executor.submit(make_request) for _ in range(100)]
|
||||
# responses = [f.result() for f in futures]
|
||||
#
|
||||
# success_count = sum(1 for r in responses if r.status_code == 200)
|
||||
# success_rate = success_count / 100
|
||||
#
|
||||
# # 成功率应该大于95%
|
||||
# assert success_rate > 0.95
|
||||
#
|
||||
# def test_rate_limiting(self, client):
|
||||
# """测试请求频率限制"""
|
||||
# # 登录接口限制10次/分钟
|
||||
# responses = []
|
||||
# for i in range(12):
|
||||
# response = client.post(
|
||||
# "/api/v1/auth/login",
|
||||
# json={
|
||||
# "username": "test",
|
||||
# "password": "test",
|
||||
# "captcha": "1234",
|
||||
# "captcha_key": f"test-{i}"
|
||||
# }
|
||||
# )
|
||||
# responses.append(response)
|
||||
#
|
||||
# # 应该有部分请求被限流
|
||||
# rate_limited_count = sum(1 for r in responses if r.status_code == 429)
|
||||
# assert rate_limited_count >= 1
|
||||
|
||||
|
||||
# class TestDataIntegrity:
|
||||
# """测试数据完整性"""
|
||||
#
|
||||
# def test_create_and_retrieve_asset(self, client, auth_headers):
|
||||
# """测试创建后获取数据一致性"""
|
||||
# # 创建资产
|
||||
# asset_data = {
|
||||
# "asset_name": "数据完整性测试",
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1,
|
||||
# "model": "测试型号"
|
||||
# }
|
||||
#
|
||||
# create_response = client.post("/api/v1/assets", headers=auth_headers, json=asset_data)
|
||||
# assert create_response.status_code == 201
|
||||
# created_asset = create_response.json()["data"]
|
||||
#
|
||||
# # 获取资产
|
||||
# get_response = client.get(
|
||||
# f"/api/v1/assets/{created_asset['id']}",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert get_response.status_code == 200
|
||||
# retrieved_asset = get_response.json()["data"]
|
||||
#
|
||||
# # 验证数据一致性
|
||||
# assert retrieved_asset["asset_name"] == asset_data["asset_name"]
|
||||
# assert retrieved_asset["model"] == asset_data["model"]
|
||||
#
|
||||
# def test_update_and_retrieve_asset(self, client, auth_headers, test_asset):
|
||||
# """测试更新后获取数据一致性"""
|
||||
# # 更新资产
|
||||
# updated_data = {"asset_name": "更新后的名称"}
|
||||
# client.put(
|
||||
# f"/api/v1/assets/{test_asset.id}",
|
||||
# headers=auth_headers,
|
||||
# json=updated_data
|
||||
# )
|
||||
#
|
||||
# # 获取资产
|
||||
# response = client.get(
|
||||
# f"/api/v1/assets/{test_asset.id}",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# asset = response.json()["data"]
|
||||
#
|
||||
# # 验证更新生效
|
||||
# assert asset["asset_name"] == updated_data["asset_name"]
|
||||
#
|
||||
# def test_delete_and_verify_asset(self, client, auth_headers, test_asset):
|
||||
# """测试删除后无法获取"""
|
||||
# # 删除资产
|
||||
# delete_response = client.delete(
|
||||
# f"/api/v1/assets/{test_asset.id}",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert delete_response.status_code == 200
|
||||
#
|
||||
# # 验证无法获取
|
||||
# get_response = client.get(
|
||||
# f"/api/v1/assets/{test_asset.id}",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert get_response.status_code == 404
|
||||
Reference in New Issue
Block a user