Coverage for agentos/testing/fixtures.py: 45%
69 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 10:59 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 10:59 +0800
1"""
2AgentOS v0.95 Testing Fixtures — 可复用测试基础设施。
4提供 mock 对象工厂、预设配置 fixtures、临时文件上下文,
5供单元测试和集成测试共用。
6"""
8import tempfile
9from contextlib import contextmanager
10from dataclasses import dataclass, field
11from pathlib import Path
12from typing import Any, Dict, List, Optional
13from unittest.mock import MagicMock
16# ─── Mock LLM ───────────────────────────────────────────────
18@dataclass
19class MockLLMResponse:
20 """Mock LLM 响应。"""
21 content: str = "This is a mock LLM response."
22 model: str = "mock-gpt-4"
23 usage: Dict[str, int] = field(default_factory=lambda: {
24 "prompt_tokens": 50, "completion_tokens": 30, "total_tokens": 80
25 })
26 finish_reason: str = "stop"
27 tool_calls: Optional[List[Dict]] = None
30class MockLLMClient:
31 """可配置的 Mock LLM 客户端,支持预设响应序列和工具调用。"""
33 def __init__(self, responses: Optional[List[MockLLMResponse]] = None):
34 self.responses = responses or [MockLLMResponse()]
35 self._idx = 0
36 self.calls: List[Dict] = []
38 async def chat(self, messages: List[Dict], **kwargs) -> MockLLMResponse:
39 self.calls.append({"messages": messages, "kwargs": kwargs})
40 resp = self.responses[min(self._idx, len(self.responses) - 1)]
41 self._idx += 1
42 return resp
44 def reset(self):
45 self._idx = 0
46 self.calls.clear()
49# ─── Fixture 工厂 ────────────────────────────────────────────
51def mock_openai_client():
52 """创建一个完整的 mock OpenAI client。"""
53 client = MagicMock()
54 client.chat.completions.create.return_value = MagicMock(
55 choices=[MagicMock(message=MagicMock(content="mock response"))],
56 model="mock-gpt-4",
57 usage=MagicMock(prompt_tokens=10, completion_tokens=5, total_tokens=15),
58 )
59 return client
62def mock_model_response(content: str = "ok", model: str = "mock-model"):
63 return MockLLMResponse(content=content, model=model)
66def sample_config(overrides: Optional[Dict] = None) -> Dict[str, Any]:
67 """返回一份可用于测试的完整 AgentOSConfig 字典。"""
68 base = {
69 "models": {
70 "default": {"provider": "openai", "model": "gpt-4o-mini", "temperature": 0.7},
71 "fast": {"provider": "openai", "model": "gpt-4o-mini", "temperature": 0.3},
72 },
73 "loop": {"max_iterations": 10, "timeout_seconds": 30},
74 "memory": {"backend": "short_term", "max_tokens": 8000},
75 "security": {"guardrails_enabled": True, "pii_sanitize": True},
76 "observability": {"metrics_enabled": False, "tracing_enabled": False},
77 }
78 if overrides:
79 _deep_merge(base, overrides)
80 return base
83def sample_loop_config(overrides: Optional[Dict] = None) -> Dict[str, Any]:
84 """返回 LoopConfig 字典。"""
85 base = {"max_iterations": 5, "timeout_seconds": 15, "reflection_enabled": True}
86 if overrides:
87 base.update(overrides)
88 return base
91@contextmanager
92def temp_workspace(suffix: str = ""):
93 """创建临时工作目录,yield Path 对象,退出时清理。"""
94 d = tempfile.mkdtemp(suffix=f"_agentos_test{suffix}")
95 try:
96 yield Path(d)
97 finally:
98 import shutil
99 shutil.rmtree(d, ignore_errors=True)
102def mock_memory_store():
103 """返回一个 dict-backed 模拟 memory store。"""
104 store = {"messages": [], "summary": "", "entities": {}}
105 return store
108def sample_agent_state(state: str = "idle", context: Optional[Dict] = None):
109 """返回一份预设的 AgentState 字典。"""
110 return {
111 "state": state,
112 "iteration": 0,
113 "total_tokens": 0,
114 "total_cost": 0.0,
115 "context": context or {"task": "test task"},
116 "history": [],
117 }
120def sample_audit_report():
121 """返回一份预设的 AuditReport 字典。"""
122 return {
123 "findings": [
124 {"severity": "low", "category": "code_injection", "description": "eval() usage detected", "location": "test.py:42"},
125 {"severity": "info", "category": "best_practice", "description": "hardcoded secret pattern", "location": "config.py:11"},
126 ],
127 "summary": {"critical": 0, "high": 0, "medium": 0, "low": 1, "info": 1},
128 "score": 85,
129 }
132def sample_health_status(healthy: bool = True):
133 """返回一份预设的 HealthStatus 字典。"""
134 return {
135 "status": "healthy" if healthy else "degraded",
136 "checks": [
137 {"name": "openai_connectivity", "pass": True, "latency_ms": 120},
138 {"name": "disk_space", "pass": True, "free_gb": 42.0},
139 {"name": "memory", "pass": True, "used_percent": 35.0},
140 ],
141 "timestamp": "2025-01-01T00:00:00Z",
142 }
145def sample_docker_config():
146 """返回一份预设的 DockerConfig 字典。"""
147 return {
148 "image": "agentos:latest",
149 "ports": {"8000/tcp": 8000},
150 "volumes": {"./data": "/app/data"},
151 "environment": {"LOG_LEVEL": "INFO"},
152 "healthcheck": {"test": "curl -f localhost:8000/health", "interval": "30s"},
153 }
156def sample_middleware_stack():
157 """返回一份预设的 MiddlewareStack 配置字典。"""
158 return {
159 "cors": {"allowed_origins": ["*"], "allowed_methods": ["GET", "POST"]},
160 "auth": {"enabled": True, "token_header": "X-API-Key"},
161 "request_id": {"enabled": True, "header_name": "X-Request-ID"},
162 "request_log": {"enabled": True, "log_body": False},
163 }
166def sample_alert_config():
167 """返回一份预设的 AlertConfig 字典。"""
168 return {
169 "rules": [
170 {"name": "high_latency", "condition": "latency_p95 > 5000", "severity": "warning"},
171 {"name": "error_rate", "condition": "error_rate > 0.05", "severity": "critical"},
172 ],
173 "webhooks": [{"url": "https://hooks.slack.com/test", "channel": "#alerts"}],
174 }
177# ─── 辅助 ────────────────────────────────────────────────────
179def _deep_merge(base: Dict, override: Dict):
180 for k, v in override.items():
181 if isinstance(v, dict) and isinstance(base.get(k), dict):
182 _deep_merge(base[k], v)
183 else:
184 base[k] = v