mvp:stage
This commit is contained in:
parent
7333cc8a2a
commit
cf0df24530
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
|
||||||
|
|||||||
5024
log_stage.txt
5024
log_stage.txt
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user