"""CLI interface for API TestGen.""" import click from ..core import SpecParser, AuthConfig from ..core.exceptions import InvalidOpenAPISpecError, UnsupportedVersionError from ..generators import PytestGenerator, JestGenerator, GoGenerator from ..mocks import MockServerGenerator @click.group() @click.version_option(version="0.1.0") @click.option( "--spec", "-s", type=click.Path(exists=True, file_okay=True, dir_okay=False), help="Path to OpenAPI specification file", ) @click.option( "--output", "-o", type=click.Path(file_okay=False, dir_okay=True), help="Output directory for generated files", ) @click.option( "--mock-url", default="http://localhost:4010", help="URL of the mock server", ) @click.pass_context def main( ctx: click.Context, spec: str, output: str, mock_url: str, ): """API TestGen - Generate integration tests from OpenAPI specifications.""" ctx.ensure_object(dict) ctx.obj["spec"] = spec ctx.obj["output"] = output or "./generated" ctx.obj["mock_url"] = mock_url @main.command("parse") @click.pass_context def parse_spec(ctx: click.Context): """Parse and validate an OpenAPI specification.""" spec_path = ctx.obj["spec"] if not spec_path: click.echo("Error: --spec option is required", err=True) raise click.Abort() try: parser = SpecParser(spec_path) parser.load() info = parser.get_info() endpoints = parser.get_endpoints() security_schemes = parser.get_security_schemes() click.echo(f"API: {info['title']} v{info['version']}") click.echo(f"OpenAPI Version: {parser.version}") click.echo(f"Base Path: {parser.base_path}") click.echo(f"Endpoints: {len(endpoints)}") click.echo(f"Security Schemes: {len(security_schemes)}") click.echo() for endpoint in endpoints: click.echo(f" {endpoint['method'].upper():6} {endpoint['path']}") ctx.obj["parser"] = parser except InvalidOpenAPISpecError as e: click.echo(f"Error: {e}", err=True) raise click.Abort() except UnsupportedVersionError as e: click.echo(f"Error: {e}", err=True) raise click.Abort() @main.command("generate") @click.argument("framework", type=click.Choice(["pytest", "jest", "go"])) @click.option( "--output-file", "-f", type=click.Path(file_okay=True, dir_okay=False), help="Specific output file path", ) @click.option( "--package-name", default="apitest", help="Go package name (only for go framework)", ) @click.pass_context def generate_tests( ctx: click.Context, framework: str, output_file: str, package_name: str, ): """Generate test files for a framework (pytest, jest, or go).""" spec_path = ctx.obj["spec"] if not spec_path: click.echo("Error: --spec option is required", err=True) raise click.Abort() output_dir = ctx.obj["output"] mock_url = ctx.obj["mock_url"] try: parser = SpecParser(spec_path) parser.load() if framework == "pytest": generator = PytestGenerator(parser, output_dir, mock_url) files = generator.generate(output_file) elif framework == "jest": generator = JestGenerator(parser, output_dir, mock_url) files = generator.generate(output_file) elif framework == "go": generator = GoGenerator(parser, output_dir, mock_url, package_name) files = generator.generate(output_file) click.echo(f"Generated {len(files)} test file(s):") for f in files: click.echo(f" - {f}") except Exception as e: click.echo(f"Error: {e}", err=True) raise click.Abort() @main.command("mock") @click.option( "--no-prism-config", is_flag=True, help="Skip generating prism-config.json", ) @click.option( "--no-docker-compose", is_flag=True, help="Skip generating docker-compose.yml", ) @click.option( "--no-dockerfile", is_flag=True, help="Skip generating Dockerfile", ) @click.pass_context def generate_mock( ctx: click.Context, no_prism_config: bool, no_docker_compose: bool, no_dockerfile: bool, ): """Generate mock server configuration files.""" spec_path = ctx.obj["spec"] if not spec_path: click.echo("Error: --spec option is required", err=True) raise click.Abort() output_dir = ctx.obj["output"] try: parser = SpecParser(spec_path) parser.load() generator = MockServerGenerator(parser, output_dir) files = generator.generate( prism_config=not no_prism_config, docker_compose=not no_docker_compose, dockerfile=not no_dockerfile, ) click.echo(f"Generated {len(files)} mock server file(s):") for f in files: click.echo(f" - {f}") except Exception as e: click.echo(f"Error: {e}", err=True) raise click.Abort() @main.command("all") @click.argument("framework", type=click.Choice(["pytest", "jest", "go"])) @click.pass_context def generate_all( ctx: click.Context, framework: str, ): """Generate test files and mock server configuration.""" spec_path = ctx.obj["spec"] if not spec_path: click.echo("Error: --spec option is required", err=True) raise click.Abort() output_dir = ctx.obj["output"] mock_url = ctx.obj["mock_url"] try: parser = SpecParser(spec_path) parser.load() click.echo("Generating tests...") if framework == "pytest": generator = PytestGenerator(parser, output_dir, mock_url) files = generator.generate() elif framework == "jest": generator = JestGenerator(parser, output_dir, mock_url) files = generator.generate() elif framework == "go": generator = GoGenerator(parser, output_dir, mock_url) files = generator.generate() click.echo(f"Generated {len(files)} test file(s)") click.echo("Generating mock server configuration...") mock_generator = MockServerGenerator(parser, output_dir) mock_files = mock_generator.generate() click.echo(f"Generated {len(mock_files)} mock server file(s)") click.echo("\nAll files generated successfully!") except Exception as e: click.echo(f"Error: {e}", err=True) raise click.Abort() @main.command("auth") @click.argument("scheme_name") @click.option("--type", "auth_type", type=click.Choice(["apiKey", "bearer", "basic"]), help="Authentication type") @click.option("--header", help="Header name for API key", default="X-API-Key") @click.option("--token", help="Bearer token or API key value") @click.option("--username", help="Username for Basic auth") @click.option("--password", help="Password for Basic auth") @click.pass_context def configure_auth( ctx: click.Context, scheme_name: str, auth_type: str, header: str, token: str, username: str, password: str, ): """Configure authentication for a security scheme.""" auth_config = AuthConfig() if auth_type == "apiKey": auth_config.add_api_key(scheme_name, header, token or "") elif auth_type == "bearer": auth_config.add_bearer(scheme_name, token or "") elif auth_type == "basic": auth_config.add_basic(scheme_name, username or "", password or "") click.echo(f"Authentication scheme '{scheme_name}' configured:") methods = auth_config.get_all_methods() for name, method in methods.items(): click.echo(f" - {name}: {method['type'].value}") if __name__ == "__main__": main()