Initial upload: mockapi - OpenAPI Mock Server Generator
This commit is contained in:
155
src/mockapi/core/server_generator.py
Normal file
155
src/mockapi/core/server_generator.py
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
"""Mock Server Generator using connexion."""
|
||||||
|
|
||||||
|
import random
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
import connexion
|
||||||
|
from connexion import App, Resolution
|
||||||
|
from connexion.resolver import Resolver
|
||||||
|
|
||||||
|
from mockapi.core.config import Config
|
||||||
|
from mockapi.core.generators import DataGenerator
|
||||||
|
from mockapi.core.delay import DelayMiddleware
|
||||||
|
|
||||||
|
|
||||||
|
class MockOperationResolver(Resolver):
|
||||||
|
"""Custom operation resolver for mock API."""
|
||||||
|
|
||||||
|
def __init__(self, mock_server_generator):
|
||||||
|
"""Initialize the resolver.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mock_server_generator: The MockServerGenerator instance
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.mock_server_generator = mock_server_generator
|
||||||
|
|
||||||
|
def resolve(self, operation):
|
||||||
|
"""Resolve an operation to a mock function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operation: The operation object from connexion
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Resolution object with the mock function
|
||||||
|
"""
|
||||||
|
operation_id = self.resolve_operation_id(operation)
|
||||||
|
for path, path_item in self.mock_server_generator.spec.get("paths", {}).items():
|
||||||
|
for method, op_def in path_item.items():
|
||||||
|
if method in ["get", "post", "put", "delete", "patch", "options", "head"]:
|
||||||
|
if op_def.get("operationId") == operation_id:
|
||||||
|
func = self.mock_server_generator._create_mock_function(op_def)
|
||||||
|
return Resolution(func, operation_id)
|
||||||
|
raise Exception(f"Operation {operation_id} not found")
|
||||||
|
|
||||||
|
|
||||||
|
class MockServerGenerator:
|
||||||
|
"""Generates a mock server from an OpenAPI specification."""
|
||||||
|
|
||||||
|
def __init__(self, spec: Dict[str, Any], config: Optional[Config] = None):
|
||||||
|
"""Initialize the mock server generator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spec: The OpenAPI specification dictionary
|
||||||
|
config: Configuration object
|
||||||
|
"""
|
||||||
|
self.spec = spec
|
||||||
|
self.config = config or Config()
|
||||||
|
schemas = spec.get("components", {}).get("schemas", {})
|
||||||
|
self.data_generator = DataGenerator(seed=self.config.seed, schemas=schemas)
|
||||||
|
self.app: Optional[App] = None
|
||||||
|
|
||||||
|
def generate(self) -> App:
|
||||||
|
"""Generate the connexion application.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Configured connexion App instance
|
||||||
|
"""
|
||||||
|
self.app = connexion.App(__name__, specification_dir=".")
|
||||||
|
|
||||||
|
self.app.add_api(
|
||||||
|
self.spec,
|
||||||
|
validate_responses=self.config.validate_responses,
|
||||||
|
strict_validation=self.config.strict_validation,
|
||||||
|
resolver=MockOperationResolver(self),
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.config.delay > 0 or self.config.random_delay:
|
||||||
|
self.app = DelayMiddleware.wrap(
|
||||||
|
self.app,
|
||||||
|
delay=self.config.delay,
|
||||||
|
random_delay=self.config.random_delay,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.app
|
||||||
|
|
||||||
|
def _create_mock_function(self, operation: Dict[str, Any]):
|
||||||
|
"""Create a mock function for an operation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operation: The OpenAPI operation definition
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Function that generates mock responses
|
||||||
|
"""
|
||||||
|
|
||||||
|
def mock_function(*args, **kwargs):
|
||||||
|
mock_config = operation.get("x-mock-config", {})
|
||||||
|
|
||||||
|
if self._should_return_error(mock_config):
|
||||||
|
return self._generate_error_response(mock_config)
|
||||||
|
|
||||||
|
response_schema = operation.get("responses", {}).get("200", {}).get(
|
||||||
|
"content", {}
|
||||||
|
).get("application/json", {}).get("schema")
|
||||||
|
|
||||||
|
if response_schema:
|
||||||
|
data = self.data_generator.generate(response_schema)
|
||||||
|
return data, 200
|
||||||
|
|
||||||
|
return {"message": "Mock response"}, 200
|
||||||
|
|
||||||
|
mock_function._mockapi_operation = operation
|
||||||
|
return mock_function
|
||||||
|
|
||||||
|
def _should_return_error(self, mock_config: Dict[str, Any]) -> bool:
|
||||||
|
"""Determine if we should return an error response.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mock_config: x-mock-config extension data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if error should be returned
|
||||||
|
"""
|
||||||
|
error_probability = mock_config.get("errorProbability", 0)
|
||||||
|
return random.random() < error_probability
|
||||||
|
|
||||||
|
def _generate_error_response(self, mock_config: Dict[str, Any]) -> tuple:
|
||||||
|
"""Generate an error response.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mock_config: x-mock-config extension data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (response_body, status_code)
|
||||||
|
"""
|
||||||
|
status_code = mock_config.get("errorCode", 500)
|
||||||
|
error_message = mock_config.get("errorMessage", "Mock error")
|
||||||
|
return {"error": error_message}, status_code
|
||||||
|
|
||||||
|
|
||||||
|
def create_mock_server(
|
||||||
|
spec: Dict[str, Any],
|
||||||
|
config: Optional[Config] = None,
|
||||||
|
) -> App:
|
||||||
|
"""Create a mock server from an OpenAPI spec.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spec: OpenAPI specification dictionary
|
||||||
|
config: Configuration object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Configured connexion App
|
||||||
|
"""
|
||||||
|
generator = MockServerGenerator(spec, config)
|
||||||
|
return generator.generate()
|
||||||
Reference in New Issue
Block a user