""" 接口集成测试 测试内容: - 所有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