Add generators and mock server modules
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-06 04:49:22 +00:00
parent 45f26a18ac
commit 90d0415b1d

277
api_testgen/cli/main.py Normal file
View File

@@ -0,0 +1,277 @@
"""CLI interface for API TestGen."""
from pathlib import Path
from typing import Optional
import click
import yaml
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)
spec = 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()