mvp:stage
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -5,6 +5,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
from typing import List, Dict, Any, Callable, Optional, Union
|
from typing import List, Dict, Any, Callable, Optional, Union
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
# Add Pydantic BaseModel for APIOperationSpec
|
# Add Pydantic BaseModel for APIOperationSpec
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -336,20 +337,27 @@ class ExecutedStageStepResult:
|
|||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
vps_details = []
|
vps_details = []
|
||||||
if self.validation_points:
|
if self.validation_points: # self.validation_points is List[Dict[str, Any]]
|
||||||
for vp in self.validation_points:
|
for vp_dict in self.validation_points: # vp_dict is a Dict from ValidationResult.to_dict()
|
||||||
if vp.details and isinstance(vp.details, dict):
|
# Access dictionary keys instead of object attributes
|
||||||
# 尝试序列化 details,如果包含复杂对象
|
vp_details_content = vp_dict.get('details')
|
||||||
|
vp_passed = vp_dict.get('passed', False)
|
||||||
|
vp_message = vp_dict.get('message', '')
|
||||||
|
|
||||||
|
processed_detail = {"passed": vp_passed, "message": vp_message}
|
||||||
|
|
||||||
|
if vp_details_content and isinstance(vp_details_content, dict):
|
||||||
try:
|
try:
|
||||||
# 只取部分关键信息或确保可序列化
|
#只取部分关键信息或确保可序列化
|
||||||
serializable_details = {"passed": vp.passed, "message": vp.message}
|
if "status_code" in vp_details_content:
|
||||||
if "status_code" in vp.details: serializable_details["status_code"] = vp.details["status_code"]
|
processed_detail["status_code"] = vp_details_content["status_code"]
|
||||||
# 不直接序列化整个 response body 以免过大
|
# 不直接序列化整个 response body 以免过大
|
||||||
vps_details.append(serializable_details)
|
# You might want to add other relevant serializable fields from vp_details_content
|
||||||
except TypeError:
|
except TypeError:
|
||||||
vps_details.append({"passed": vp.passed, "message": f"{vp.message} (Details not serializable)"})
|
# Update message if details were not serializable, though this part might be less reachable now
|
||||||
else:
|
processed_detail["message"] = f"{vp_message} (Details not fully serializable)"
|
||||||
vps_details.append({"passed": vp.passed, "message": vp.message})
|
|
||||||
|
vps_details.append(processed_detail)
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -358,7 +366,7 @@ class ExecutedStageStepResult:
|
|||||||
"lookup_key": self.lookup_key if isinstance(self.lookup_key, str) else str(self.lookup_key), # <--- 添加到输出 (确保字符串化)
|
"lookup_key": self.lookup_key if isinstance(self.lookup_key, str) else str(self.lookup_key), # <--- 添加到输出 (确保字符串化)
|
||||||
"resolved_endpoint": self.resolved_endpoint, # <--- 添加到输出
|
"resolved_endpoint": self.resolved_endpoint, # <--- 添加到输出
|
||||||
"status": self.status.value,
|
"status": self.status.value,
|
||||||
"message": self.message or "; ".join([vp.message for vp in self.validation_points if not vp.passed]),
|
"message": self.message or "; ".join([vp_dict.get('message', '') for vp_dict in self.validation_points if not vp_dict.get('passed')]),
|
||||||
"duration_seconds": f"{self.duration:.4f}",
|
"duration_seconds": f"{self.duration:.4f}",
|
||||||
"timestamp": time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime(self.timestamp)),
|
"timestamp": time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime(self.timestamp)),
|
||||||
"validation_points": vps_details,
|
"validation_points": vps_details,
|
||||||
@@ -386,23 +394,24 @@ class ExecutedStageResult:
|
|||||||
description: Optional[str] = None): # <--- 添加 description 参数
|
description: Optional[str] = None): # <--- 添加 description 参数
|
||||||
self.stage_id = stage_id
|
self.stage_id = stage_id
|
||||||
self.stage_name = stage_name
|
self.stage_name = stage_name
|
||||||
self.description = description # <--- 存储 description
|
self.description = description
|
||||||
self.api_group_metadata = api_group_metadata or {}
|
self.api_group_metadata = api_group_metadata
|
||||||
self.overall_status: ExecutedStageResult.Status = ExecutedStageResult.Status.PENDING # 默认为 PENDING
|
self.overall_status: ExecutedStageResult.Status = ExecutedStageResult.Status.PENDING
|
||||||
self.executed_steps: List[ExecutedStageStepResult] = []
|
self.start_time: datetime = datetime.now()
|
||||||
self.start_time: float = time.time()
|
self.end_time: Optional[datetime] = None
|
||||||
self.end_time: Optional[float] = None
|
|
||||||
self.duration: float = 0.0
|
self.duration: float = 0.0
|
||||||
self.message: str = "" # 整个阶段的总结性消息,例如跳过原因或关键失败点
|
self.message: Optional[str] = None
|
||||||
self.final_stage_context: Optional[Dict[str, Any]] = None # 最终的 stage_context 内容 (敏感数据需谨慎处理)
|
self.executed_steps: List[ExecutedStageStepResult] = [] # 确保初始化为空列表
|
||||||
|
self.final_context: Optional[Dict[str, Any]] = None
|
||||||
|
# executed_steps_count 应该是一个属性,或者在 to_dict 中计算
|
||||||
|
|
||||||
def add_step_result(self, step_result: ExecutedStageStepResult):
|
def add_step_result(self, step_result: ExecutedStageStepResult):
|
||||||
self.executed_steps.append(step_result)
|
self.executed_steps.append(step_result)
|
||||||
|
|
||||||
def finalize_stage_result(self, final_context: Optional[Dict[str, Any]] = None):
|
def finalize_stage_result(self, final_context: Optional[Dict[str, Any]] = None):
|
||||||
self.end_time = time.time()
|
self.end_time = datetime.now()
|
||||||
self.duration = self.end_time - self.start_time
|
self.duration = (self.end_time - self.start_time).total_seconds()
|
||||||
self.final_stage_context = final_context
|
self.final_context = final_context
|
||||||
|
|
||||||
if not self.executed_steps and self.overall_status == ExecutedStageResult.Status.SKIPPED:
|
if not self.executed_steps and self.overall_status == ExecutedStageResult.Status.SKIPPED:
|
||||||
# 如果没有执行任何步骤且状态是初始的 SKIPPED,则保持
|
# 如果没有执行任何步骤且状态是初始的 SKIPPED,则保持
|
||||||
@@ -427,10 +436,10 @@ class ExecutedStageResult:
|
|||||||
# else: 状态保持为初始的 SKIPPED,message也应该在之前设置了
|
# else: 状态保持为初始的 SKIPPED,message也应该在之前设置了
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
# 对 final_stage_context 进行处理,避免过大或敏感信息直接输出
|
# 对 final_context 进行处理,避免过大或敏感信息直接输出
|
||||||
processed_context = {}
|
processed_context = {}
|
||||||
if self.final_stage_context:
|
if self.final_context:
|
||||||
for k, v in self.final_stage_context.items():
|
for k, v in self.final_context.items():
|
||||||
if isinstance(v, (str, bytes)) and len(v) > 200: # 截断长字符串
|
if isinstance(v, (str, bytes)) and len(v) > 200: # 截断长字符串
|
||||||
processed_context[k] = str(v)[:200] + '...'
|
processed_context[k] = str(v)[:200] + '...'
|
||||||
elif isinstance(v, (dict, list)): # 对于字典和列表,只显示键或少量元素
|
elif isinstance(v, (dict, list)): # 对于字典和列表,只显示键或少量元素
|
||||||
@@ -445,10 +454,10 @@ class ExecutedStageResult:
|
|||||||
"api_group_name": self.api_group_metadata.get("name", "N/A"),
|
"api_group_name": self.api_group_metadata.get("name", "N/A"),
|
||||||
"overall_status": self.overall_status.value,
|
"overall_status": self.overall_status.value,
|
||||||
"duration_seconds": f"{self.duration:.2f}",
|
"duration_seconds": f"{self.duration:.2f}",
|
||||||
"start_time": time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime(self.start_time)),
|
"start_time": self.start_time.strftime('%Y-%m-%dT%H:%M:%S%z'),
|
||||||
"end_time": time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime(self.end_time)) if self.end_time else None,
|
"end_time": self.end_time.strftime('%Y-%m-%dT%H:%M:%S%z') if self.end_time else None,
|
||||||
"message": self.message,
|
"message": self.message,
|
||||||
"executed_steps_count": len(self.executed_steps),
|
"executed_steps_count": len(self.executed_steps),
|
||||||
"executed_steps": [step.to_dict() for step in self.executed_steps],
|
"executed_steps": [step.to_dict() for step in self.executed_steps],
|
||||||
# "final_stage_context_summary": processed_context # 可选: 输出处理后的上下文摘要
|
"final_stage_context_summary": processed_context # 可选: 输出处理后的上下文摘要
|
||||||
}
|
}
|
||||||
@@ -2187,11 +2187,11 @@ class APITestOrchestrator:
|
|||||||
headers=final_headers,
|
headers=final_headers,
|
||||||
body=final_body # APIRequest 会通过 model_post_init 将 body 赋给 json_data
|
body=final_body # APIRequest 会通过 model_post_init 将 body 赋给 json_data
|
||||||
)
|
)
|
||||||
current_step_result.request_details = api_request.model_dump() # Use model_dump for Pydantic v2
|
current_step_result.request_details = api_request.model_dump(mode='json') # Use model_dump for Pydantic v2
|
||||||
|
|
||||||
self.logger.info(f"Stage '{stage_instance.id}', Step '{step_name}': Executing API call {api_request.method} {api_request.url}") # Log the full URL
|
self.logger.info(f"Stage '{stage_instance.id}', Step '{step_name}': Executing API call {api_request.method} {api_request.url}") # Log the full URL
|
||||||
api_response, api_call_detail = self.api_caller.call_api(api_request) # APICaller.call_api expects APIRequest
|
api_response, api_call_detail = self.api_caller.call_api(api_request) # APICaller.call_api expects APIRequest
|
||||||
current_step_result.api_call_details = api_call_detail.model_dump() # Use model_dump for Pydantic v2
|
current_step_result.api_call_details = api_call_detail.model_dump(mode='json') # Use model_dump for Pydantic v2
|
||||||
|
|
||||||
self.logger.debug(f"Stage '{stage_instance.id}', Step '{step_name}': Validating response. Status: {api_response.status_code}")
|
self.logger.debug(f"Stage '{stage_instance.id}', Step '{step_name}': Validating response. Status: {api_response.status_code}")
|
||||||
|
|
||||||
@@ -2343,6 +2343,7 @@ class APITestOrchestrator:
|
|||||||
elif stage_result.message: stage_result.message += f" | after_stage hook failed: {e_asg}"
|
elif stage_result.message: stage_result.message += f" | after_stage hook failed: {e_asg}"
|
||||||
else: stage_result.message = f"after_stage hook failed: {e_asg}"
|
else: stage_result.message = f"after_stage hook failed: {e_asg}"
|
||||||
|
|
||||||
|
stage_result.executed_steps = executed_steps_results # <--- 将局部步骤结果列表赋值给最终结果对象
|
||||||
stage_result.finalize_stage_result(final_context=stage_context)
|
stage_result.finalize_stage_result(final_context=stage_context)
|
||||||
self.logger.info(f"Stage '{stage_instance.id}' execution finished. API Group: '{api_group_name}', Final Status: {stage_result.overall_status.value}, Duration: {stage_result.duration:.2f}s") # Corrected duration_seconds to duration
|
self.logger.info(f"Stage '{stage_instance.id}' execution finished. API Group: '{api_group_name}', Final Status: {stage_result.overall_status.value}, Duration: {stage_result.duration:.2f}s") # Corrected duration_seconds to duration
|
||||||
return stage_result
|
return stage_result
|
||||||
|
|||||||
+2513
-2511
File diff suppressed because it is too large
Load Diff
+2037
-2065
File diff suppressed because it is too large
Load Diff
+460
-190
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user