diff --git a/api_testgen/mocks/generator.py b/api_testgen/mocks/generator.py new file mode 100644 index 0000000..4c0e099 --- /dev/null +++ b/api_testgen/mocks/generator.py @@ -0,0 +1,278 @@ +"""Mock server generator for Prism/OpenAPI mock support.""" + +import json +from pathlib import Path +from typing import Any, Dict, List, Optional + +from ..core import SpecParser + + +class MockServerGenerator: + """Generate Prism/OpenAPI mock server configurations.""" + + DEFAULT_PORT = 4010 + DEFAULT_HOST = "0.0.0.0" + + def __init__( + self, + spec_parser: SpecParser, + output_dir: str = ".", + ): + """Initialize the mock server generator. + + Args: + spec_parser: The OpenAPI specification parser. + output_dir: Directory for generated configuration files. + """ + self.spec_parser = spec_parser + self.output_dir = Path(output_dir) + + def generate( + self, + prism_config: bool = True, + docker_compose: bool = True, + dockerfile: bool = True, + ) -> List[Path]: + """Generate mock server configuration files. + + Args: + prism_config: Whether to generate prism-config.json. + docker_compose: Whether to generate docker-compose.yml. + dockerfile: Whether to generate Dockerfile. + + Returns: + List of generated file paths. + """ + self.output_dir.mkdir(parents=True, exist_ok=True) + + generated_files = [] + + if prism_config: + generated_files.append(self.generate_prism_config()) + + if docker_compose: + generated_files.append(self.generate_docker_compose()) + + if dockerfile: + generated_files.append(self.generate_dockerfile()) + + return generated_files + + def generate_prism_config(self, output_file: Optional[str] = None) -> Path: + """Generate Prism mock server configuration. + + Args: + output_file: Optional output file path. + + Returns: + Path to the generated file. + """ + if output_file: + output_path = Path(output_file) + else: + output_path = self.output_dir / "prism-config.json" + + config = { + "mock": { + "host": self.DEFAULT_HOST, + "port": self.DEFAULT_PORT, + "cors": { + "enabled": True, + "allowOrigin": "*", + "allowMethods": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"], + "allowHeaders": ["Content-Type", "Authorization", "X-API-Key"], + }, + "Validation": { + "request": True, + "response": True, + }, + }, + "logging": { + "level": "info", + "format": "json", + }, + } + + output_path.write_text(json.dumps(config, indent=2)) + return output_path + + def generate_docker_compose(self, output_file: Optional[str] = None) -> Path: + """Generate Docker Compose configuration for mock server. + + Args: + output_file: Optional output file path. + + Returns: + Path to the generated file. + """ + if output_file: + output_path = Path(output_file) + else: + output_path = self.output_dir / "docker-compose.yml" + + spec_info = self.spec_parser.get_info() + + compose_content = f'''version: '3.8' + +services: + mock-server: + image: stoplight/prism:latest + container_name: "{spec_info['title']}-mock-server" + command: > + mock + --spec /app/openapi.yaml + --port {self.DEFAULT_PORT} + --host {self.DEFAULT_HOST} + ports: + - "{self.DEFAULT_PORT}:{self.DEFAULT_PORT}" + volumes: + - ./:/app + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-q", "--spider", f"http://localhost:{self.DEFAULT_PORT}/health"] + interval: 30s + timeout: 10s + retries: 3 + + mock-server-https: + image: stoplight/prism:latest + container_name: "{spec_info['title']}-mock-server-https" + command: > + mock + --spec /app/openapi.yaml + --port {self.DEFAULT_PORT + 1} + --host {self.DEFAULT_HOST} + ports: + - "{self.DEFAULT_PORT + 1}:{self.DEFAULT_PORT + 1}" + volumes: + - ./:/app + restart: unless-stopped +''' + + output_path.write_text(compose_content) + return output_path + + def generate_dockerfile(self, output_file: Optional[str] = None) -> Path: + """Generate Dockerfile for mock server. + + Args: + output_file: Optional output file path. + + Returns: + Path to the generated file. + """ + if output_file: + output_path = Path(output_file) + else: + output_path = self.output_dir / "Dockerfile" + + spec_info = self.spec_parser.get_info() + + dockerfile_content = f'''FROM stoplight/prism:latest + +LABEL maintainer="developer@example.com" +LABEL description="Mock server for {spec_info['title']} API" + +WORKDIR /app + +COPY openapi.yaml . + +EXPOSE {self.DEFAULT_PORT} + +CMD ["mock", "--spec", "openapi.yaml", "--port", "{self.DEFAULT_PORT}", "--host", "0.0.0.0"] +''' + + output_path.write_text(dockerfile_content) + return output_path + + def generate_start_script(self, output_file: Optional[str] = None) -> Path: + """Generate shell script to start mock server. + + Args: + output_file: Optional output file path. + + Returns: + Path to the generated file. + """ + if output_file: + output_path = Path(output_file) + else: + output_path = self.output_dir / "start-mock-server.sh" + + script_content = f'''#!/bin/bash + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${{BASH_SOURCE[0]}}")" && pwd)" +cd "$SCRIPT_DIR" + +PORT={self.DEFAULT_PORT} +HOST="0.0.0.0" + +echo "Starting mock server for API..." +echo "Mock server will be available at: http://localhost:$PORT" + +docker compose up -d mock-server + +echo "Mock server started successfully!" +echo "To stop: docker compose down" +''' + + output_path.write_text(script_content) + output_path.chmod(0o755) + return output_path + + def generate_health_check(self) -> Path: + """Generate health check endpoint configuration. + + Returns: + Path to the generated file. + """ + output_path = self.output_dir / "health-check.json" + + spec_info = self.spec_parser.get_info() + + health_check = { + "openapi": self.spec_parser.version, + "info": { + "title": f"{spec_info['title']} - Health Check", + "version": spec_info["version"], + "description": "Health check endpoint for the mock server", + }, + "paths": { + "/health": { + "get": { + "summary": "Health check endpoint", + "description": "Returns the health status of the mock server", + "operationId": "healthCheck", + "responses": { + "200": { + "description": "Mock server is healthy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["healthy"], + }, + "api": { + "type": "string", + }, + "version": { + "type": "string", + }, + }, + }, + } + }, + } + }, + } + } + }, + } + + output_path.write_text(json.dumps(health_check, indent=2)) + return output_path