fix: address all 12 Copilot review comments

- setup.py: add include_package_data + MANIFEST.in for .md files
- setup.py: bump python_requires to >=3.10 (repl_skin.py uses PEP 604)
- client.py: is_alive() now uses self.get() with auth; get() allows timeout override
- wiremock_cli.py: consistent --json output (raw API JSON, no envelope)
- wiremock_cli.py: add @file support in stub create
- wiremock_cli.py: shutdown respects --json mode in error path
- wiremock_cli.py: remove unused sys import and pass_client
- stubs.py: use `is not None` instead of truthiness for limit/offset
- requests_log.py: use `is not None` instead of truthiness for limit/since
- scenarios.py: URL-encode scenario name in path
- README.md: fix WIREMOCK.md path reference
- SKILL.md: remove incorrect envelope documentation
This commit is contained in:
fabio.mantelmacher
2026-03-31 23:59:16 +03:00
parent 2e685e1b16
commit bb063d8d2c
9 changed files with 91 additions and 37 deletions
+1
View File
@@ -0,0 +1 @@
recursive-include cli_anything *.md
@@ -44,4 +44,4 @@ export WIREMOCK_PORT=8080
export WIREMOCK_SCHEME=http
```
See `WIREMOCK.md` at the repository root for the full SOP.
See `../WIREMOCK.md` for the full SOP.
@@ -7,9 +7,9 @@ class RequestsLog:
def list(self, limit: int = None, since: str = None) -> dict:
params = {}
if limit:
if limit is not None:
params["limit"] = limit
if since:
if since is not None:
params["since"] = since
r = self.client.get("/requests", params=params)
r.raise_for_status()
@@ -1,3 +1,5 @@
from urllib.parse import quote
from cli_anything.wiremock.utils.client import WireMockClient
@@ -15,5 +17,5 @@ class ScenariosManager:
r.raise_for_status()
def set_state(self, name: str, state: str) -> None:
r = self.client.put(f"/scenarios/{name}/state", json={"state": state})
r = self.client.put(f"/scenarios/{quote(name, safe='')}/state", json={"state": state})
r.raise_for_status()
@@ -8,9 +8,9 @@ class StubsManager:
def list(self, limit: int = None, offset: int = None) -> dict:
params = {}
if limit:
if limit is not None:
params["limit"] = limit
if offset:
if offset is not None:
params["offset"] = offset
r = self.client.get("/mappings", params=params)
r.raise_for_status()
@@ -85,7 +85,7 @@ cli-anything-wiremock stub create '{
# Verify a POST was made exactly once
cli-anything-wiremock --json request count '{"method":"POST","url":"/api/orders"}'
# → {"status":"ok","message":"...","data":{"count":1}}
# → {"count": 1}
# Scenario: advance state
cli-anything-wiremock scenario set "cart-flow" "item-added"
@@ -100,11 +100,18 @@ cli-anything-wiremock record stop
### Always use `--json` in agent contexts
Use `--json` for all invocations in scripts or agent tool calls. This produces a stable, parseable envelope:
Use `--json` for all invocations in scripts or agent tool calls. This returns the raw WireMock API response JSON directly (no envelope wrapper):
```json
{ "status": "ok", "message": "Created stub abc-123", "data": { ... } }
{ "status": "error", "message": "Connection refused" }
```bash
# Example: create a stub and get the raw WireMock response
cli-anything-wiremock --json stub quick GET /api/hello 200 --body '{"hello":"world"}'
# → {"id": "abc-123", "request": {...}, "response": {...}, ...}
# Commands with no response body return:
# → {"status": "ok"}
# Errors return:
# → {"status": "error", "message": "Connection refused"}
```
### Connection via environment
@@ -134,4 +141,4 @@ export WIREMOCK_PORT=8080
### Error handling
Non-zero exit code on all errors. Check `status` field in JSON output. The `data` field is `null` on errors.
Non-zero exit code on all errors. In `--json` mode, errors return `{"status": "error", "message": "..."}`. Success returns the raw WireMock API response.
@@ -17,8 +17,9 @@ class WireMockClient:
return f"{self.scheme}://{self.host}:{self.port}{self.admin_prefix}"
def get(self, path: str, **kwargs) -> requests.Response:
timeout = kwargs.pop("timeout", self.timeout)
return requests.get(
f"{self.base_url()}{path}", auth=self.auth, timeout=self.timeout, **kwargs
f"{self.base_url()}{path}", auth=self.auth, timeout=timeout, **kwargs
)
def post(self, path: str, json=None, **kwargs) -> requests.Response:
@@ -55,7 +56,7 @@ class WireMockClient:
def is_alive(self) -> bool:
try:
r = requests.get(f"{self.base_url()}/health", timeout=3)
r = self.get("/health", timeout=3)
return r.status_code == 200
except Exception:
return False
@@ -1,5 +1,4 @@
import json
import sys
import click
from cli_anything.wiremock.utils.client import WireMockClient
from cli_anything.wiremock.utils.output import error, print_json, print_table, success
@@ -10,9 +9,6 @@ from cli_anything.wiremock.core.session import Session
from cli_anything.wiremock.core.settings import SettingsManager
from cli_anything.wiremock.core.stubs import StubsManager
pass_client = click.make_pass_decorator(WireMockClient)
@click.group()
@click.option(
"--host",
@@ -139,9 +135,16 @@ def stub_create(ctx, mapping_json):
client = ctx.obj
json_mode = ctx.meta.get("json_mode", False)
try:
mapping = json.loads(mapping_json)
if mapping_json.startswith("@"):
with open(mapping_json[1:]) as f:
mapping = json.load(f)
else:
mapping = json.loads(mapping_json)
data = StubsManager(client).create(mapping)
success(f"Created stub {data.get('id')}", data, json_mode)
if json_mode:
print_json(data)
else:
success(f"Created stub {data.get('id')}", data)
except Exception as e:
error(str(e), json_mode)
@@ -159,7 +162,10 @@ def stub_quick(ctx, method, url, status, body, content_type):
json_mode = ctx.meta.get("json_mode", False)
try:
data = StubsManager(client).quick_stub(method, url, status, body, content_type)
success(f"Created stub {data.get('id')}", data, json_mode)
if json_mode:
print_json(data)
else:
success(f"Created stub {data.get('id')}", data)
except Exception as e:
error(str(e), json_mode)
@@ -173,7 +179,10 @@ def stub_delete(ctx, stub_id):
json_mode = ctx.meta.get("json_mode", False)
try:
StubsManager(client).delete(stub_id)
success(f"Deleted stub {stub_id}", json_mode=json_mode)
if json_mode:
print_json({"status": "ok"})
else:
success(f"Deleted stub {stub_id}")
except Exception as e:
error(str(e), json_mode)
@@ -186,7 +195,10 @@ def stub_reset(ctx):
json_mode = ctx.meta.get("json_mode", False)
try:
StubsManager(client).reset()
success("Stubs reset to default mappings", json_mode=json_mode)
if json_mode:
print_json({"status": "ok"})
else:
success("Stubs reset to default mappings")
except Exception as e:
error(str(e), json_mode)
@@ -199,7 +211,10 @@ def stub_save(ctx):
json_mode = ctx.meta.get("json_mode", False)
try:
StubsManager(client).save()
success("Mappings saved to disk", json_mode=json_mode)
if json_mode:
print_json({"status": "ok"})
else:
success("Mappings saved to disk")
except Exception as e:
error(str(e), json_mode)
@@ -215,7 +230,10 @@ def stub_import(ctx, file_path):
with open(file_path) as f:
data = json.load(f)
result = StubsManager(client).import_stubs(data)
success("Stubs imported", result, json_mode)
if json_mode:
print_json(result)
else:
success("Stubs imported", result)
except Exception as e:
error(str(e), json_mode)
@@ -316,7 +334,10 @@ def request_reset(ctx):
json_mode = ctx.meta.get("json_mode", False)
try:
RequestsLog(client).reset()
success("Request journal cleared", json_mode=json_mode)
if json_mode:
print_json({"status": "ok"})
else:
success("Request journal cleared")
except Exception as e:
error(str(e), json_mode)
@@ -370,7 +391,10 @@ def scenario_set(ctx, name, state):
json_mode = ctx.meta.get("json_mode", False)
try:
ScenariosManager(client).set_state(name, state)
success(f"Scenario '{name}' set to state '{state}'", json_mode=json_mode)
if json_mode:
print_json({"status": "ok"})
else:
success(f"Scenario '{name}' set to state '{state}'")
except Exception as e:
error(str(e), json_mode)
@@ -383,7 +407,10 @@ def scenario_reset(ctx):
json_mode = ctx.meta.get("json_mode", False)
try:
ScenariosManager(client).reset_all()
success("All scenarios reset", json_mode=json_mode)
if json_mode:
print_json({"status": "ok"})
else:
success("All scenarios reset")
except Exception as e:
error(str(e), json_mode)
@@ -412,7 +439,10 @@ def record_start(ctx, target_url, match_header):
data = RecordingManager(client).start(
target_url, list(match_header) or None
)
success(f"Recording started → {target_url}", data, json_mode)
if json_mode:
print_json(data)
else:
success(f"Recording started → {target_url}", data)
except Exception as e:
error(str(e), json_mode)
@@ -429,7 +459,7 @@ def record_stop(ctx):
print_json(data)
else:
count = len(data.get("mappings", []))
success(f"Recording stopped. {count} stubs captured.", data, json_mode)
success(f"Recording stopped. {count} stubs captured.", data)
except Exception as e:
error(str(e), json_mode)
@@ -458,8 +488,11 @@ def record_snapshot(ctx):
json_mode = ctx.meta.get("json_mode", False)
try:
data = RecordingManager(client).snapshot()
count = len(data.get("mappings", []))
success(f"Snapshot: {count} stubs captured", data, json_mode)
if json_mode:
print_json(data)
else:
count = len(data.get("mappings", []))
success(f"Snapshot: {count} stubs captured", data)
except Exception as e:
error(str(e), json_mode)
@@ -538,7 +571,10 @@ def reset_all(ctx):
try:
r = client.post("/reset")
r.raise_for_status()
success("Full reset complete", json_mode=json_mode)
if json_mode:
print_json({"status": "ok"})
else:
success("Full reset complete")
except Exception as e:
error(str(e), json_mode)
@@ -552,10 +588,16 @@ def shutdown(ctx):
json_mode = ctx.meta.get("json_mode", False)
try:
client.post("/shutdown")
success("Shutdown signal sent", json_mode=json_mode)
except Exception as e:
if json_mode:
print_json({"status": "ok", "message": "Shutdown signal sent"})
else:
success("Shutdown signal sent")
except Exception:
# Server may drop connection before responding
click.echo("Shutdown signal sent (connection may have closed)")
if json_mode:
print_json({"status": "ok", "message": "Shutdown signal sent"})
else:
click.echo("Shutdown signal sent (connection may have closed)")
if __name__ == "__main__":
+2 -1
View File
@@ -4,11 +4,12 @@ setup(
name="cli-anything-wiremock",
version="0.1.0",
packages=find_namespace_packages(include=["cli_anything.*"]),
include_package_data=True,
install_requires=["click>=8.0", "requests>=2.28", "rich>=13.0"],
entry_points={
"console_scripts": [
"cli-anything-wiremock=cli_anything.wiremock.wiremock_cli:cli"
]
},
python_requires=">=3.9",
python_requires=">=3.10",
)