Initial release — APIClient - Agent v2.0.0
AI-first API testing desktop client built with Python + PyQt6. Features: - Multi-tab HTTP request editor with params/headers/body/auth/tests - KeyValueTable with per-row enable/disable checkboxes and 36px rows - Format JSON button, syntax highlighting, pre-request & test scripts - Collections, environments, history, import/export (Postman v2.1, cURL) - OpenAPI 3.x / Swagger 2.0 local parser (no AI tokens) - EKIKA Odoo API Framework generator — JSON-API, REST JSON, GraphQL, Custom REST JSON with all auth types (instant, no AI tokens) - Persistent AI chat sidebar (Claude-powered co-pilot) with streaming, context-aware suggestions, and one-click Apply to request editor - AI collection generator from any docs URL or pasted spec - WebSocket client, Mock server, Collection runner, Code generator - Dark/light theme engine (global QSS, object-name selectors) - SSL error detection with actionable hints - MIT License Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
86
app/core/test_runner.py
Normal file
86
app/core/test_runner.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""Run test scripts after a response is received."""
|
||||
import json
|
||||
from app.models import HttpResponse, TestResult
|
||||
|
||||
|
||||
class TestContext:
|
||||
"""Exposes pm.test(), pm.expect(), pm.response in test scripts."""
|
||||
|
||||
def __init__(self, response: HttpResponse):
|
||||
self.results: list[TestResult] = []
|
||||
self.response = _ResponseProxy(response)
|
||||
self.expect = _expect
|
||||
|
||||
def test(self, name: str, fn):
|
||||
try:
|
||||
fn()
|
||||
self.results.append(TestResult(name=name, passed=True))
|
||||
except AssertionError as e:
|
||||
self.results.append(TestResult(name=name, passed=False, message=str(e)))
|
||||
except Exception as e:
|
||||
self.results.append(TestResult(name=name, passed=False, message=f"Error: {e}"))
|
||||
|
||||
|
||||
class _ResponseProxy:
|
||||
def __init__(self, resp: HttpResponse):
|
||||
self._resp = resp
|
||||
self.status = resp.status
|
||||
self.responseTime = resp.elapsed_ms
|
||||
self.text = resp.body
|
||||
try:
|
||||
self._json = json.loads(resp.body)
|
||||
except Exception:
|
||||
self._json = None
|
||||
|
||||
def json(self):
|
||||
return self._json
|
||||
|
||||
def to_have_status(self, code: int):
|
||||
assert self._resp.status == code, f"Expected status {code}, got {self._resp.status}"
|
||||
|
||||
|
||||
class _Assertion:
|
||||
def __init__(self, value):
|
||||
self._value = value
|
||||
|
||||
def to_equal(self, expected):
|
||||
assert self._value == expected, f"Expected {expected!r}, got {self._value!r}"
|
||||
return self
|
||||
|
||||
def to_be_truthy(self):
|
||||
assert self._value, f"Expected truthy, got {self._value!r}"
|
||||
return self
|
||||
|
||||
def to_include(self, substr):
|
||||
assert substr in str(self._value), f"Expected {substr!r} in {self._value!r}"
|
||||
return self
|
||||
|
||||
def to_be_below(self, n):
|
||||
assert self._value < n, f"Expected {self._value} < {n}"
|
||||
return self
|
||||
|
||||
def to_have_property(self, key):
|
||||
assert hasattr(self._value, key) or (isinstance(self._value, dict) and key in self._value), \
|
||||
f"Expected property {key!r}"
|
||||
return self
|
||||
|
||||
|
||||
def _expect(value) -> _Assertion:
|
||||
return _Assertion(value)
|
||||
|
||||
|
||||
def run_tests(script: str, response: HttpResponse) -> list[TestResult]:
|
||||
if not script or not script.strip():
|
||||
return []
|
||||
|
||||
ctx = TestContext(response)
|
||||
namespace = {
|
||||
"pm": ctx,
|
||||
"response": ctx.response,
|
||||
"expect": _expect,
|
||||
}
|
||||
try:
|
||||
exec(script, namespace)
|
||||
except Exception as e:
|
||||
ctx.results.append(TestResult(name="Script Error", passed=False, message=str(e)))
|
||||
return ctx.results
|
||||
Reference in New Issue
Block a user