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
|
||||
from typing import List, Dict, Any, Callable, Optional, Union
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
|
||||
# Add Pydantic BaseModel for APIOperationSpec
|
||||
from pydantic import BaseModel
|
||||
@ -336,20 +337,27 @@ class ExecutedStageStepResult:
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
vps_details = []
|
||||
if self.validation_points:
|
||||
for vp in self.validation_points:
|
||||
if vp.details and isinstance(vp.details, dict):
|
||||
# 尝试序列化 details,如果包含复杂对象
|
||||
if self.validation_points: # self.validation_points is List[Dict[str, Any]]
|
||||
for vp_dict in self.validation_points: # vp_dict is a Dict from ValidationResult.to_dict()
|
||||
# Access dictionary keys instead of object attributes
|
||||
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:
|
||||
# 只取部分关键信息或确保可序列化
|
||||
serializable_details = {"passed": vp.passed, "message": vp.message}
|
||||
if "status_code" in vp.details: serializable_details["status_code"] = vp.details["status_code"]
|
||||
#只取部分关键信息或确保可序列化
|
||||
if "status_code" in vp_details_content:
|
||||
processed_detail["status_code"] = vp_details_content["status_code"]
|
||||
# 不直接序列化整个 response body 以免过大
|
||||
vps_details.append(serializable_details)
|
||||
# You might want to add other relevant serializable fields from vp_details_content
|
||||
except TypeError:
|
||||
vps_details.append({"passed": vp.passed, "message": f"{vp.message} (Details not serializable)"})
|
||||
else:
|
||||
vps_details.append({"passed": vp.passed, "message": vp.message})
|
||||
# Update message if details were not serializable, though this part might be less reachable now
|
||||
processed_detail["message"] = f"{vp_message} (Details not fully serializable)"
|
||||
|
||||
vps_details.append(processed_detail)
|
||||
|
||||
|
||||
return {
|
||||
@ -358,7 +366,7 @@ class ExecutedStageStepResult:
|
||||
"lookup_key": self.lookup_key if isinstance(self.lookup_key, str) else str(self.lookup_key), # <--- 添加到输出 (确保字符串化)
|
||||
"resolved_endpoint": self.resolved_endpoint, # <--- 添加到输出
|
||||
"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}",
|
||||
"timestamp": time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime(self.timestamp)),
|
||||
"validation_points": vps_details,
|
||||
@ -386,23 +394,24 @@ class ExecutedStageResult:
|
||||
description: Optional[str] = None): # <--- 添加 description 参数
|
||||
self.stage_id = stage_id
|
||||
self.stage_name = stage_name
|
||||
self.description = description # <--- 存储 description
|
||||
self.api_group_metadata = api_group_metadata or {}
|
||||
self.overall_status: ExecutedStageResult.Status = ExecutedStageResult.Status.PENDING # 默认为 PENDING
|
||||
self.executed_steps: List[ExecutedStageStepResult] = []
|
||||
self.start_time: float = time.time()
|
||||
self.end_time: Optional[float] = None
|
||||
self.description = description
|
||||
self.api_group_metadata = api_group_metadata
|
||||
self.overall_status: ExecutedStageResult.Status = ExecutedStageResult.Status.PENDING
|
||||
self.start_time: datetime = datetime.now()
|
||||
self.end_time: Optional[datetime] = None
|
||||
self.duration: float = 0.0
|
||||
self.message: str = "" # 整个阶段的总结性消息,例如跳过原因或关键失败点
|
||||
self.final_stage_context: Optional[Dict[str, Any]] = None # 最终的 stage_context 内容 (敏感数据需谨慎处理)
|
||||
self.message: Optional[str] = None
|
||||
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):
|
||||
self.executed_steps.append(step_result)
|
||||
|
||||
def finalize_stage_result(self, final_context: Optional[Dict[str, Any]] = None):
|
||||
self.end_time = time.time()
|
||||
self.duration = self.end_time - self.start_time
|
||||
self.final_stage_context = final_context
|
||||
self.end_time = datetime.now()
|
||||
self.duration = (self.end_time - self.start_time).total_seconds()
|
||||
self.final_context = final_context
|
||||
|
||||
if not self.executed_steps and self.overall_status == ExecutedStageResult.Status.SKIPPED:
|
||||
# 如果没有执行任何步骤且状态是初始的 SKIPPED,则保持
|
||||
@ -427,10 +436,10 @@ class ExecutedStageResult:
|
||||
# else: 状态保持为初始的 SKIPPED,message也应该在之前设置了
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
# 对 final_stage_context 进行处理,避免过大或敏感信息直接输出
|
||||
# 对 final_context 进行处理,避免过大或敏感信息直接输出
|
||||
processed_context = {}
|
||||
if self.final_stage_context:
|
||||
for k, v in self.final_stage_context.items():
|
||||
if self.final_context:
|
||||
for k, v in self.final_context.items():
|
||||
if isinstance(v, (str, bytes)) and len(v) > 200: # 截断长字符串
|
||||
processed_context[k] = str(v)[:200] + '...'
|
||||
elif isinstance(v, (dict, list)): # 对于字典和列表,只显示键或少量元素
|
||||
@ -445,10 +454,10 @@ class ExecutedStageResult:
|
||||
"api_group_name": self.api_group_metadata.get("name", "N/A"),
|
||||
"overall_status": self.overall_status.value,
|
||||
"duration_seconds": f"{self.duration:.2f}",
|
||||
"start_time": time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime(self.start_time)),
|
||||
"end_time": time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime(self.end_time)) if self.end_time else None,
|
||||
"start_time": self.start_time.strftime('%Y-%m-%dT%H:%M:%S%z'),
|
||||
"end_time": self.end_time.strftime('%Y-%m-%dT%H:%M:%S%z') if self.end_time else None,
|
||||
"message": self.message,
|
||||
"executed_steps_count": len(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,
|
||||
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
|
||||
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}")
|
||||
|
||||
@ -2343,6 +2343,7 @@ class APITestOrchestrator:
|
||||
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}"
|
||||
|
||||
stage_result.executed_steps = executed_steps_results # <--- 将局部步骤结果列表赋值给最终结果对象
|
||||
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
|
||||
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