Coverage for src / mcp_server_langgraph / core / test_helpers.py: 87%

45 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-03 00:43 +0000

1""" 

2Test Helper Functions 

3 

4This module provides convenient helper functions for creating test instances 

5of agents, servers, settings, and other components. 

6 

7Usage: 

8 from mcp_server_langgraph.core.test_helpers import ( 

9 create_test_agent, 

10 create_test_server, 

11 create_test_settings, 

12 create_test_container, 

13 create_mock_llm_response, 

14 create_mock_mcp_request, 

15 ) 

16 

17 # In your tests 

18 def test_my_feature(): 

19 agent = create_test_agent() 

20 assert agent.invoke({"message": "test"}) is not None 

21""" 

22 

23from __future__ import annotations 

24 

25from datetime import datetime, timedelta, UTC 

26from typing import Any 

27from unittest.mock import MagicMock 

28 

29from mcp_server_langgraph.core.config import Settings 

30from mcp_server_langgraph.core.container import ApplicationContainer 

31from mcp_server_langgraph.core.container import create_test_container as _create_container 

32 

33# ============================================================================== 

34# Container Helpers 

35# ============================================================================== 

36 

37 

38def create_test_container(settings: Settings | None = None) -> ApplicationContainer: 

39 """ 

40 Create a test container with no-op providers. 

41 

42 This is a convenience wrapper around the container module's create_test_container. 

43 

44 Args: 

45 settings: Optional custom settings 

46 

47 Returns: 

48 ApplicationContainer configured for testing 

49 

50 Example: 

51 container = create_test_container() 

52 agent = create_agent(container.settings, container.get_telemetry()) 

53 """ 

54 return _create_container(settings=settings) 

55 

56 

57# ============================================================================== 

58# Settings Helpers 

59# ============================================================================== 

60 

61 

62def create_test_settings(**kwargs: Any) -> Settings: 

63 """ 

64 Create test settings with safe defaults. 

65 

66 Args: 

67 **kwargs: Override any settings attributes 

68 

69 Returns: 

70 Settings configured for testing 

71 

72 Example: 

73 settings = create_test_settings(model_name="test-model", log_level="ERROR") 

74 """ 

75 defaults = { 

76 "environment": "test", 

77 "log_level": "DEBUG", 

78 "jwt_secret_key": "test-secret-key-for-testing-only", 

79 "hipaa_integrity_secret": "test-hipaa-secret-for-testing-only", 

80 "openfga_store_id": "", 

81 "openfga_model_id": "", 

82 "anthropic_api_key": "test-anthropic-key", 

83 "enable_tracing": False, 

84 "enable_metrics": False, 

85 } 

86 defaults.update(kwargs) 

87 return Settings(**defaults) # type: ignore[arg-type] 

88 

89 

90# ============================================================================== 

91# Agent Helpers 

92# ============================================================================== 

93 

94 

95def create_test_agent(settings: Settings | None = None, container: ApplicationContainer | None = None) -> Any: 

96 """ 

97 Create a test agent instance with dependency injection support. 

98 

99 Now uses the create_agent() factory function which supports containers. 

100 

101 Args: 

102 settings: Optional custom settings (if container not provided) 

103 container: Optional container to use for dependencies 

104 

105 Returns: 

106 Agent instance (LangGraph CompiledStateGraph) 

107 

108 Example: 

109 agent = create_test_agent() 

110 result = agent.invoke({"messages": [{"role": "user", "content": "test"}]}) 

111 """ 

112 # Import the new DI-enabled agent creation function 

113 from mcp_server_langgraph.core.agent import create_agent 

114 

115 # Use the new factory function with container support 

116 agent = create_agent(settings=settings, container=container) 

117 

118 return agent 

119 

120 

121# ============================================================================== 

122# Server Helpers 

123# ============================================================================== 

124 

125 

126def create_test_server(container: ApplicationContainer | None = None) -> Any: 

127 """ 

128 Create a test MCP server instance. 

129 

130 Args: 

131 container: Optional container to use for dependencies 

132 

133 Returns: 

134 MCP server instance 

135 

136 Example: 

137 server = create_test_server() 

138 # Use server in tests 

139 """ 

140 if container is None: 

141 container = create_test_container() 

142 

143 # For now, return a mock server 

144 # TODO: When we refactor server to use container, this will create a real test server 

145 mock_server = MagicMock() 

146 mock_server.server = MagicMock() 

147 mock_server.auth = container.get_auth() 

148 mock_server.settings = container.settings 

149 

150 return mock_server 

151 

152 

153# ============================================================================== 

154# Mock Helpers 

155# ============================================================================== 

156 

157 

158def create_mock_llm_response(content: str = "Test response", model: str = "test-model", **kwargs: Any) -> Any: 

159 """ 

160 Create a mock LLM response compatible with LangChain. 

161 

162 Args: 

163 content: Response content 

164 model: Model name 

165 **kwargs: Additional response attributes 

166 

167 Returns: 

168 Mock message object 

169 

170 Example: 

171 response = create_mock_llm_response(content="Hello, world!") 

172 """ 

173 from langchain_core.messages import AIMessage 

174 

175 return AIMessage(content=content, response_metadata={"model": model, **kwargs}) 

176 

177 

178def create_mock_llm_stream(chunks: list[str]) -> list[Any]: 

179 """ 

180 Create a mock LLM stream response. 

181 

182 Args: 

183 chunks: List of content chunks to stream 

184 

185 Returns: 

186 List of mock chunk objects 

187 

188 Example: 

189 stream = create_mock_llm_stream(["Hello", " ", "World"]) 

190 for chunk in stream: 

191 print(chunk.content) 

192 """ 

193 from langchain_core.messages import AIMessageChunk 

194 

195 return [AIMessageChunk(content=chunk) for chunk in chunks] 

196 

197 

198def create_mock_mcp_request( 

199 method: str = "tools/call", params: dict[str, Any] | None = None, request_id: int = 1 

200) -> dict[str, Any]: 

201 """ 

202 Create a mock MCP JSON-RPC request. 

203 

204 Args: 

205 method: JSON-RPC method name 

206 params: Method parameters 

207 request_id: Request ID 

208 

209 Returns: 

210 Dict representing MCP request 

211 

212 Example: 

213 request = create_mock_mcp_request( 

214 method="tools/call", 

215 params={"name": "chat", "arguments": {"message": "test"}} 

216 ) 

217 """ 

218 return {"jsonrpc": "2.0", "id": request_id, "method": method, "params": params or {}} 

219 

220 

221def create_mock_jwt_token(user_id: str = "test-user", expiry_hours: int = 1) -> str: 

222 """ 

223 Create a mock JWT token for testing. 

224 

225 Args: 

226 user_id: User ID to include in token 

227 expiry_hours: Hours until token expires 

228 

229 Returns: 

230 JWT token string 

231 

232 Example: 

233 token = create_mock_jwt_token(user_id="alice") 

234 # Use token in auth tests 

235 """ 

236 import jwt 

237 

238 payload = { 

239 "sub": user_id, 

240 "exp": datetime.now(UTC) + timedelta(hours=expiry_hours), 

241 "iat": datetime.now(UTC), 

242 } 

243 

244 # nosemgrep: python.jwt.security.jwt-hardcode.jwt-python-hardcoded-secret 

245 return jwt.encode(payload, "test-secret-key", algorithm="HS256") # type: ignore[no-any-return] 

246 

247 

248# ============================================================================== 

249# Assertion Helpers 

250# ============================================================================== 

251 

252 

253def assert_valid_mcp_response(response: dict[str, Any]) -> None: 

254 """ 

255 Assert that a response is a valid MCP JSON-RPC response. 

256 

257 Args: 

258 response: Response to validate 

259 

260 Raises: 

261 AssertionError: If response is invalid 

262 

263 Example: 

264 response = server.handle_request(request) 

265 assert_valid_mcp_response(response) 

266 """ 

267 assert "jsonrpc" in response 

268 assert response["jsonrpc"] == "2.0" 

269 assert "id" in response 

270 assert ("result" in response) or ("error" in response) 

271 

272 

273def assert_valid_agent_state(state: dict[str, Any]) -> None: 

274 """ 

275 Assert that a state dict is a valid LangGraph agent state. 

276 

277 Args: 

278 state: State to validate 

279 

280 Raises: 

281 AssertionError: If state is invalid 

282 

283 Example: 

284 state = agent.invoke({"messages": [...]}) 

285 assert_valid_agent_state(state) 

286 """ 

287 assert "messages" in state 

288 assert isinstance(state["messages"], list)