Skip to content

apighost generate crashes (undefined max_endpoints) + broken latency + masked test failures #32

Description

@Coding-Dev-Tools

Summary

Automated maintenance run found that apighost generate is currently broken and the CI lint gate is masking three failing tests on master. This issue documents the bugs and includes a ready-to-apply patch. (The maintenance runner has read + merge/issue permissions but cannot push code through the current network policy, so it cannot open the fix PR directly.)

Bugs

  1. apighost generate crashes with NameError (high severity). src/apighost/cli.py references max_endpoints in the generate command, but the corresponding Click option/parameter was never defined. Any invocation of apighost generate <spec> raises NameError: name 'max_endpoints' is not defined. (ruff F821, 5 occurrences.)

  2. Configured response latency is silently ignored. create_app(..., latency_range=...) in src/apighost/server.py accepts latency_range but the request handler never uses it — no delay is ever applied. This is why test_latency_applies_delay fails (observed delay ~0.0004s vs expected >=0.04s).

  3. Lint failing on master (15+ ruff errors) — duplicate imports (F811) in cli.py, server.py, faker_utils.py; unused import (F401); unsorted import blocks (I001). Because the test job is gated behind lint (needs: lint), these also mask 3 failing tests:

    • test_make_response_with_dict — asserts an outdated tuple shape; _make_response returns a Flask Response.
    • test_latency_applies_delay — see bug build(deps): bump actions/checkout from 4 to 6 #2.
    • test_package_data_includes_py_typedpyproject.toml declares py.typed under the "*" package-data key, but the test expects the explicit apighost key.

These block PR #30 from merging.

Fix (verified locally: ruff check clean, full suite 172 passed)

diff --git a/pyproject.toml b/pyproject.toml
index 521c21b..02c570d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -59,7 +59,7 @@ where = ["src"]
 
 
 [tool.setuptools.package-data]
-"*" = ["py.typed"]
+apighost = ["py.typed"]
 [tool.pytest.ini_options]
 testpaths = ["tests"]
 python_files = ["test_*.py"]
diff --git a/src/apighost/cli.py b/src/apighost/cli.py
index 9c6e5f7..934cc59 100644
--- a/src/apighost/cli.py
+++ b/src/apighost/cli.py
@@ -2,7 +2,6 @@
 
 from __future__ import annotations
 
-import click
 import json
 import re
 import sys
@@ -374,7 +373,14 @@ def scenario_delete(name) -> None:
     "--output", "-o", default=None, help="Output path for the generated scenario"
 )
 @click.option("--name", "-n", default=None, help="Scenario name")
-def generate(spec, output, name) -> None:
+@click.option(
+    "--max-endpoints",
+    "-m",
+    type=int,
+    default=None,
+    help="Limit the number of endpoints to generate scenarios for",
+)
+def generate(spec, output, name, max_endpoints) -> None:
     """Generate sample data and create a scenario from an OpenAPI spec."""
     api_spec = parse_spec(spec)
     scenario_name = name or f"generated-{api_spec.title.replace(' ', '-').lower()}"
diff --git a/src/apighost/faker_utils.py b/src/apighost/faker_utils.py
index a942fe2..9586558 100644
--- a/src/apighost/faker_utils.py
+++ b/src/apighost/faker_utils.py
@@ -4,7 +4,6 @@ from __future__ import annotations
 
 import random
 from collections.abc import Callable
-from faker import Faker
 from typing import Any
 
 from faker import Faker
diff --git a/src/apighost/server.py b/src/apighost/server.py
index 34fda11..8d6bb2c 100644
--- a/src/apighost/server.py
+++ b/src/apighost/server.py
@@ -6,8 +6,8 @@ import json
 import logging
 import random
 import re
+import time
 from collections.abc import Callable
-from flask import Flask, Response, jsonify, request
 from typing import Any
 
 from flask import Flask, Response, jsonify, request
@@ -128,6 +128,11 @@ def create_app(
 
         def make_handler(endpoint: Endpoint, scenario: Scenario | None) -> Callable:
             def handler(**path_params) -> Any:
+                # Apply configured latency before serving the response
+                lo, hi = latency_range
+                if hi > 0:
+                    time.sleep(random.uniform(lo, hi))
+
                 # Capture request info for recording
                 req_method = request.method
                 req_path = request.path
diff --git a/tests/test_parser_edges.py b/tests/test_parser_edges.py
index 03fc82e..4f33c00 100644
--- a/tests/test_parser_edges.py
+++ b/tests/test_parser_edges.py
@@ -2,6 +2,8 @@
 
 import json
 import tempfile
+from pathlib import Path
+
 from apighost.parser import (
     _extract_example,
     _infer_type,
@@ -13,7 +15,6 @@ from apighost.parser import (
     load_spec,
     parse_spec,
 )
-from pathlib import Path
 
 # --- _resolve_ref edge cases ---
 
diff --git a/tests/test_server.py b/tests/test_server.py
index 9a4df17..ad2282a 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -185,7 +185,7 @@ def test_extract_path_params_no_match():
 
 
 def test_make_response_with_dict():
-    """_make_response with dict body returns jsonify tuple (covers line 37)."""
+    """_make_response with dict body returns a JSON Response (covers line 37)."""
     from flask import Flask
 
     from apighost.server import _make_response
@@ -193,8 +193,8 @@ def test_make_response_with_dict():
     app = Flask(__name__)
     with app.app_context():
         resp = _make_response(201, {"id": 1, "name": "test"})
-        assert resp[1] == 201
-        data = json.loads(resp[0].get_data(as_text=True))
+        assert resp.status_code == 201
+        data = json.loads(resp.get_data(as_text=True))
         assert data["name"] == "test"
 
 

Filed automatically by the scheduled maintenance routine. The patch above was prepared and tested locally but could not be pushed (write-to-repo blocked by network policy). A maintainer or a write-enabled run can apply it.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions