Files
api-testgen-cli/api_testgen/cli/main.py
CI Bot d369d3b1f8
Some checks failed
CI / test (3.10) (push) Failing after 1m21s
CI / test (3.11) (push) Failing after 1m19s
CI / test (3.9) (push) Failing after 1m22s
CI / lint (push) Failing after 43s
fix: Apply black formatting to resolve CI formatting issues
2026-02-06 07:56:02 +00:00

279 lines
7.6 KiB
Python

"""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()