Claude
Skill
api-test-pytest
Use this skill when you need to parse multi-format API definitions and generate Pytest API automation; triggers include Pytest API tests and API automation with Pytest.
Virus-scanned
Reviewed automatically before listing.
Download
naodeng-awesome-qa-skills-skills_en_testing-types_api-test-pytest-c44b892.zip · 18 KB
Install
skills CLI
npx skills add https://github.com/naodeng/awesome-qa-skills/tree/main/skills/en/testing-types/api-test-pytest
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install naodeng-awesome-qa-skills@llmmart
Git
git clone https://github.com/naodeng/awesome-qa-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole naodeng/awesome-qa-skills collection as a plugin from our marketplace. Git is the plain clone.
README
api-test-pytest (EN)
Skill Overview
Need API outputs that should land in pytest-based automation; The project is already Python-first or wants pytest-style structure.
How to Use
- Open
SKILL.mdin this folder and confirm this skill fits your task. - In your AI tool, call
@skill api-test-pytest, then add your real project context and goal. - If you need a specific output format (table, checklist, report), include it directly in your request.
One-Click Install Script
Run from the repository root:
macOS / Linux
bash ./scripts/install-skills-mac.sh --tool codex --lang en --skill api-test-pytest
Windows PowerShell
powershell -ExecutionPolicy Bypass -File .\scripts\install-skills-windows.ps1 -Tool codex -Lang en -Skill api-test-pytest
Skill manifest
api-test-pytest (EN)
Chinese version: See the corresponding Chinese skill.
When to Use
- Need API outputs that should land in pytest-based automation.
- The project is already Python-first or wants pytest-style structure.
Workflow
- Read and follow the main prompt listed under Progressive disclosure (coverage, structure, quality bar).
- Add only project context that changes the result: scope, environment, constraints, risks, dependencies, expected deliverable.
- If input is incomplete, return a usable first draft and explicitly mark assumptions and gaps.
- Default to Markdown; switch formats only when the user asks.
Core Constraints
- Prioritize by risk / business impact — do not treat everything equally.
- Separate confirmed facts from current assumptions.
- Do not invent endpoints, fields, environments, or root causes the user did not provide.
- Use placeholders or env-var semantics for auth/secrets; never hardcode real credentials.
- Keep output executable: concrete scenarios, clear priority, clear next steps.
Progressive Disclosure
- Before producing output, read and follow
prompts/api-test-pytest.md(minimum coverage, output structure, quality bar). - When a ready-made template fits: use matching files under
output-templates/. - When the user wants examples or alignment with existing assets: read relevant
examples/. - For deep framework/troubleshoot/schema notes: read only the relevant file(s) under
references/, do not load the whole directory. - For format conversion or helper checks: prefer existing
scripts/over reinventing. - For evaluating/regressing this skill: use
evals/with skill-up.
Pre-delivery Checklist
- Followed the main prompt's output structure
- Minimum coverage focus: module structure, fixture strategy, auth handling, priority endpoints, positive scenarios, negative and boundary scenarios, assertion focus, test data strategy, ... (details in main prompt)
- Covered the minimum checklist, or explained omissions
- High-risk items have explicit priority
- Did not invent details the user did not provide
- Assumptions and gaps are marked
Common Pitfalls
- Do not pretend completeness when scope/context is missing.
- Do not treat every item as equally important.
- Do not skip assumptions and information gaps.
- Do not dump generic theory unrelated to the current toolchain.
Files (awesome-qa-skills)
-
agents
-
openai.yaml 474 B
version: 1 metadata: key: "api-test-pytest" last_verified: "2026-03-24" interface: display_name: "API Test Pytest" short_description: "Use this skill when you need to parse multi-format API definitions and generate Pytest API automation; triggers include Pytest API tests and API automation wit…" default_prompt: "Use api-test-pytest to complete the task with local scripts, prompts, and examples in this skill folder." policy: allow_implicit_invocation: true
-
-
evals
-
cases
-
basic-success.yaml 928 B
id: basic-success title: "Pytest: OpenAPI/curl yields suite structure" description: | With a short OpenAPI and curl snippet, produce pytest + requests layout/fixture/case structure. input: prompt: | Use api-test-pytest. Env: SIT. Auth: Bearer {{token}} (do not hardcode real tokens). OpenAPI snippet: POST /v1/payments/callback GET /v1/orders/{id} Desensitized curl: curl -X POST https://api.example.com/v1/payments/callback -H "Content-Type: application/json" -d '{"orderId":"ORD-1","status":"PAID"}' Produce a pytest plan: tests/ layout, conftest fixtures, parametrization and assertion focus; highlight callback idempotency. Avoid long code dumps. expect: must_contain: - "pytest" - "fixture" must_not_contain: - "TODO" - "I cannot" judge: type: rule_based success: - output_contains: all: - "Task Understanding" - "pytest" -
edge-bad-or-neighbor.yaml 715 B
id: edge-bad-or-neighbor title: "Pytest: redirect RestAssured/Bruno neighbor requests" description: | When the user asks for RestAssured/Bruno under the pytest skill, stay on pytest or clarify the boundary. input: prompt: | I activated api-test-pytest, but I asked: write RestAssured Java classes, or export Bruno .bru files. Endpoint: GET /v1/health. Correct the scope: deliver a pytest + requests plan and explain why RestAssured/Bruno assets are out of this skill. expect: must_contain: - "pytest" - "RestAssured" must_not_contain: - "TODO" - "I cannot" judge: type: rule_based success: - output_contains: all: - "pytest" - "Open Questions" -
edge-incomplete-input.yaml 647 B
id: edge-incomplete-input title: "Pytest: one-line ask still yields draft" description: | With only one sentence and no schema, still draft pytest structure and list gaps/assumptions. input: prompt: | Use api-test-pytest. I only know: we need to test points redemption. No path, fields, or auth notes. Give a usable pytest draft (layout + fixture approach) and list required gaps and assumptions. expect: must_contain: - "assumption" - "Open Questions" must_not_contain: - "TODO" - "I cannot" judge: type: rule_based success: - output_contains: all: - "assumption" - "pytest"
-
-
fixtures
-
openapi-orders.yaml 1.8 KB
openapi: 3.0.3 info: title: Sample Orders API (desensitized fixture) version: 0.1.0 paths: /orders: post: summary: Create order operationId: createOrder security: - bearerAuth: [] requestBody: required: true content: application/json: schema: type: object required: [skuId, qty] properties: skuId: type: string qty: type: integer minimum: 1 responses: "201": description: Created content: application/json: schema: type: object properties: orderId: type: string status: type: string "401": description: Unauthorized /payments/callback: post: summary: Payment callback operationId: paymentCallback requestBody: required: true content: application/json: schema: type: object required: [orderId, result] properties: orderId: type: string result: type: string enum: [SUCCESS, FAIL] responses: "200": description: OK /orders/{id}: get: summary: Get order operationId: getOrder security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string responses: "200": description: OK components: securitySchemes: bearerAuth: type: http scheme: bearer -
sample-curls.sh 551 B
# Desensitized curl examples for API automation fixtures. # Use placeholders only — never real tokens. # Create order curl -sS -X POST "${BASE_URL}/orders" \ -H "Authorization: Bearer ${API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"skuId":"SKU-1001","qty":1}' # Payment callback curl -sS -X POST "${BASE_URL}/payments/callback" \ -H "Content-Type: application/json" \ -d '{"orderId":"ORD-PLACEHOLDER","result":"SUCCESS"}' # Get order curl -sS "${BASE_URL}/orders/ORD-PLACEHOLDER" \ -H "Authorization: Bearer ${API_TOKEN}"
-
-
eval.yaml 559 B
schema_version: v1alpha1 environment: type: none skills: - source: local_path path: . engine: name: claude_code # model is optional; omit to use engine default # model: # provider: anthropic # name: claude-sonnet-4-6 cases: files: - evals/cases/basic-success.yaml - evals/cases/edge-incomplete-input.yaml - evals/cases/edge-bad-or-neighbor.yaml defaults: timeout_seconds: 180 max_turns: 8 expect: exit_code: 0 must_not_contain: - "TODO" - "I cannot" report: formats: [json]
-
-
examples
-
bruno
-
sample.bru 137 B · in bundle
-
-
ci
-
github-actions-pytest.yml 909 B
name: API Pytest CI on: workflow_dispatch: pull_request: paths: - "explore/api-test-pytest/**" jobs: test: runs-on: ubuntu-latest defaults: run: working-directory: explore/api-test-pytest/scripts/templates/pytest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install deps run: pip install -r requirements.txt - name: Run tests env: BASE_URL: ${{ secrets.API_BASE_URL }} API_TOKEN: ${{ secrets.API_TOKEN }} run: pytest -q --junitxml=pytest-report.xml - name: Upload report if: always() uses: actions/upload-artifact@v4 with: name: pytest-report path: explore/api-test-pytest/scripts/templates/pytest/pytest-report.xml -
Jenkinsfile.pytest 739 B · in bundle
-
-
sample.curl 212 B · in bundle
-
sample.insomnia.json 502 B
{ "_type": "export", "__export_format": 4, "__export_date": "2026-03-23T00:00:00.000Z", "__export_source": "insomnia.desktop.app:v9.0.0", "resources": [ { "_id": "req_1", "_type": "request", "name": "List Products", "method": "GET", "url": "https://api.example.com/v1/products" }, { "_id": "req_2", "_type": "request", "name": "Create Product", "method": "POST", "url": "https://api.example.com/v1/products" } ] } -
sample.openapi.yaml 240 B
openapi: 3.0.3 info: title: Sample API version: 1.0.0 paths: /v1/users: get: summary: list users post: summary: create user /v1/users/{id}: get: summary: get user delete: summary: delete user -
sample.opencollection.json 243 B
{ "name": "OpenCollection Sample", "requests": [ { "name": "Get Orders", "method": "GET", "path": "/v1/orders" }, { "name": "Create Order", "method": "POST", "path": "/v1/orders" } ] } -
sample.postman_collection.json 704 B
{ "info": { "name": "Sample API Collection", "_postman_id": "c01f6f20-3333-4444-9999-777788889999", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "Get Users", "request": { "method": "GET", "url": { "raw": "https://api.example.com/v1/users", "path": [ "v1", "users" ] } } }, { "name": "Create User", "request": { "method": "POST", "url": { "raw": "https://api.example.com/v1/users", "path": [ "v1", "users" ] } } } ] } -
sample.wsdl 534 B · in bundle
-
-
output-templates
-
template-markdown.md 83 B
# Output Template ## Summary - Scope: - Key Points: ## Details - Item 1 - Item 2
-
-
prompts
-
api-test-pytest.md 5.5 KB
# API Test Pytest Prompt From the materials the user provides, produce a pytest + requests API automation plan or test-asset structure that is practical to implement. ## Role - Act as a senior QA and API automation expert who turns API materials into a maintainable pytest suite. ## Input parsing order Parse in this priority order. Higher priority wins on conflicts; when sources disagree, state the conflict and source — **do not silently invent a merged “truth”**: 1. Existing pytest assets (`tests/`, `conftest.py`, fixtures, markers) 2. OpenAPI / Swagger 3. Postman Collection, Insomnia, Bruno, or OpenCollection 4. curl examples 5. Loose notes (tables, Markdown, verbal endpoint lists) Also absorb when present: business scope, auth, environments, release priority, CI (`pytest.ini` / GitHub Actions), dependency versions. Extract only paths, methods, params, fields, and sample values that **actually appear** in the materials. Put gaps in “missing information”. ## Defaults (use these unless the user specifies otherwise) Prefer defaults; do not present a framework menu. **Directory layout** ```text tests/ conftest.py # base_url / auth / api_client fixtures test_<resource>.py # one file per resource or critical flow ``` Optional (only when the user wants a runnable skeleton): `requirements.txt` (`pytest`, `requests`), `pytest.ini` (markers). **Naming** - files: `test_<resource>.py` (e.g. `test_orders.py`) - tests: `test_<action>_<condition>` (e.g. `test_create_order_success`, `test_get_user_unauthorized`) - fixtures: `base_url`, `auth_token`, `api_client` (session-scoped client; function-scoped header overrides when needed) **Config and auth** - Read `BASE_URL`, `API_TOKEN` from environment variables; in-code defaults may only be placeholders such as `https://api.example.com` / `replace-me` - `api_client`: `requests.Session`, JSON Content-Type by default; Bearer Authorization placeholder unless materials specify another scheme - Join relative paths to `base_url`; never embed secret-bearing full URLs in tests **Assertion style** - Minimum: `status_code` + critical JSON fields (when schema/examples exist) - Negative tests: assert status and **documented** error-body fields; if undocumented, assert status class (4xx/5xx) and mark the assumption - Boundaries: `@pytest.mark.parametrize`; share auth/data via fixtures — do not copy-paste client setup **Layers and markers (default)** - `@pytest.mark.smoke` / `contract` / `negative` (if the project has no markers yet, define them in the plan and show CI filters) If a suite already exists, **align to it** and apply defaults only for gaps. ## Gotchas - **Never** hardcode real tokens, passwords, or cookies; examples must use `os.getenv(...)` plus placeholder defaults. - When migrating from curl/Postman: redact sensitive headers. - **Do not invent** paths, fields, status codes, error codes, or response schemas the user did not provide. - Do not switch the stack to httpx/Playwright/another language unless the user asks. - Do not treat load testing as a default pytest job; optional latency checks must be marked as non-load tests. - If information is incomplete, still ship a usable first version (layout + fixtures + confirmed case outlines) and list assumptions. - Unless the user asks for runnable files, prefer structure and case outlines over huge full source dumps. ## Minimum coverage checklist Unless the user explicitly narrows scope, the result must cover: - module / file structure - fixture strategy (scope, client, auth) - how auth and permission cases are organized - high-priority endpoints (P0/P1) - positive scenarios - negative and boundary scenarios - assertion focus - test-data setup and cleanup needs - run commands and CI filters (smoke vs full) - missing information and assumptions ## Output Return results in this order: ### 1. Task Understanding - API / domain under test - test goal - in-scope endpoints or flows - out-of-scope or unclear areas - input sources and conflict handling ### 2. Pytest Test Plan or Structure - proposed tree and file responsibilities - fixture inventory (name, scope, role) - env var contract (`BASE_URL`, `API_TOKEN`, …) - marker / layer strategy - alignment with an existing suite (if any) ### 3. Priority Coverage For each P0/P1 case or case group: - suggested `test_*.py` and function name - method / path (confirmed only) - priority and risk rationale - positive / negative / boundary points - assertion focus - required fixtures or parametrization ### 4. Fixture and Data Notes - how auth is obtained/refreshed (if no login endpoint is provided, mark the gap — do not invent a login flow) - test-data create / isolation / cleanup - parametrization tables (known boundaries only) ### 5. Execution Suggestions - local commands: `pytest -m smoke`, `pytest tests/test_orders.py` - smoke vs regression scope - minimal CI steps and secret variable names - release-blocking checks ### 6. Open Questions - gaps and assumptions used this round ## Pre-delivery checklist - [ ] Inputs followed the parsing order; conflicts and gaps are called out - [ ] Layout / fixtures / env placeholders match defaults (or explain reuse of existing) - [ ] No real secrets; no invented paths/fields/schemas - [ ] P0/P1 cases have concrete names and assertions — not vague “happy/unhappy” - [ ] Smoke markers and CI path are actionable ## Quality bar - Stay pytest + requests specific: file names, fixtures, markers. - Prioritize by risk. - Separate confirmed facts from assumptions. - Avoid huge source dumps unless the user asks for runnable files.
-
-
references
-
local
-
api-testing_EN.md 488 B
# Archived Local Reference This file is a lightweight legacy note. The previous long snapshot was removed to avoid duplicate and outdated guidance. ## Use Instead - Main prompt: `prompts/api-test-pytest.md` - Main entry: `SKILL.md` ## Notes - Keep using the current prompt and `SKILL.md` as the source of truth. - Load `references/`, `examples/`, or `scripts/` only when the task really needs extra detail. - Do not rely on this file for the latest prompt wording or workflow rules.
-
-
framework-spec.md 1.5 KB
# Pytest API Framework Specification ## 1. Directory Convention - `scripts/parse_api_sources.py`: multi-format API source parser - `scripts/generate_pytest_tests.py`: normalized endpoint -> pytest tests - `scripts/templates/pytest/`: runnable pytest baseline project - `generated-tests/`: output test files generated from parsed endpoints ## 2. Test Layer Convention - `smoke`: core endpoint availability - `contract`: status code + response shape checks - `business`: key API flows and state transitions - `negative`: invalid params, auth failure, boundary inputs ## 3. Assertion Rules - status code assertions are mandatory - response shape assertions for key fields are mandatory - latency threshold checks for critical endpoints are recommended - negative tests should validate error payload structure ## 4. Data and Environment Rules - use environment variables for base URL and token - never commit secrets in test files - use deterministic data or isolated seed fixtures - define cleanup strategy for mutable endpoints ## 5. Parsing Scope Rules Supported parser inputs: - curl - Postman collections - Swagger/OpenAPI (including v3) - Bruno - OpenCollection - Insomnia - WSDL - ZIP containing any supported format Parser output must be normalized endpoint JSON with: - method - path - source format - optional base URL, headers, query/body hints ## 6. CI Guidance - run smoke tests on each PR - run full suite on main/nightly - fail on P0 regression - export JUnit/XML reports for trend tracking -
report-schema.md 800 B
# Unified API Test Report Schema Use these fields for all generated API test reports: - `run_id`: unique run identifier - `tool`: `supertest` / `pytest` / `bruno` / `restassured` - `env`: environment name (staging/prod-like) - `case_id`: stable case identifier - `api_name`: human-readable endpoint name - `method`: HTTP method - `path`: endpoint path - `status`: `pass` / `fail` / `skip` - `status_code`: actual HTTP status code - `expected_status_code`: expected status code or range - `latency_ms`: measured request latency - `error_rate`: optional aggregated failure rate per run - `assertions_total`: total assertions - `assertions_passed`: passed assertions - `message`: failure or debug message - `timestamp`: ISO8601 UTC timestamp Recommended rule: - P0 failures should fail CI pipelines. -
setup-and-ci.md 448 B
# Setup and CI Guide ## Local ```bash cd scripts/templates/pytest pip install -r requirements.txt pytest -q ``` ## One-Click Flow ```bash cd scripts ./run.sh ../examples ``` ## CI Recommendation - PR: run smoke/generated subset - main/nightly: run full suite - export junit xml for trend tracking ## CI Template - `examples/ci/github-actions-pytest.yml` - `examples/ci/Jenkinsfile.pytest` ## Report Schema - `references/report-schema.md`
-
-
scripts
-
templates
-
pytest
-
tests
-
test_health.py 127 B
def test_health_example(api_client): resp = api_client.request("get", "/health") assert resp.status_code in [200, 404]
-
-
conftest.py 738 B
import os import requests import pytest @pytest.fixture(scope="session") def base_url(): return os.getenv("BASE_URL", "https://api.example.com") @pytest.fixture(scope="session") def auth_token(): return os.getenv("API_TOKEN", "replace-me") @pytest.fixture() def api_client(base_url, auth_token): session = requests.Session() session.headers.update( { "Content-Type": "application/json", "Authorization": f"Bearer {auth_token}", } ) class Client: def request(self, method, path, json=None, params=None): url = f"{base_url}{path}" return session.request(method=method, url=url, json=json, params=params, timeout=10) return Client() -
requirements.txt 31 B
pytest==8.3.3 requests==2.32.3
-
-
-
generate_pytest_tests.py 1.8 KB
#!/usr/bin/env python3 import argparse import json import re from pathlib import Path def _safe_name(method: str, path: str, idx: int) -> str: base = f"{method}_{path}_{idx}".lower() return re.sub(r"[^a-z0-9]+", "_", base).strip("_") def _case_block(method: str, path: str, name: str) -> str: method_low = method.lower() body = "{}" if method_low in {"post", "put", "patch"} else "None" return ( f"def test_{name}(api_client):\n" f" resp = api_client.request('{method_low}', '{path}', json={body})\n" f" assert resp.status_code in [200, 201, 202, 204, 400, 401, 403, 404]\n" f" assert resp.elapsed.total_seconds() < 3\n\n" ) def main() -> None: parser = argparse.ArgumentParser(description="Generate pytest tests from normalized endpoint JSON") parser.add_argument("--input", required=True, type=Path, help="Normalized endpoint JSON") parser.add_argument("--output", required=True, type=Path, help="Output pytest file path") args = parser.parse_args() payload = json.loads(args.input.read_text(encoding="utf-8")) endpoints = payload.get("endpoints", []) lines = [ '"""Auto-generated pytest API tests."""', "", "def test_generated_collection_not_empty():", f" assert {len(endpoints)} >= 0", "", ] for i, ep in enumerate(endpoints, start=1): method = str(ep.get("method", "GET")).upper() path = str(ep.get("path", "/")) name = _safe_name(method, path, i) lines.append(_case_block(method, path, name).rstrip()) lines.append("") args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8") print(str(args.output)) if __name__ == "__main__": main() -
parse_api_sources.py 9.1 KB
#!/usr/bin/env python3 import argparse import json import re import zipfile from pathlib import Path from typing import Dict, List, Optional from xml.etree import ElementTree as ET try: import yaml # type: ignore except Exception: yaml = None METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"} def _safe_text(raw: bytes) -> str: return raw.decode("utf-8", errors="ignore") def _append_endpoint(out: List[Dict], method: str, path: str, source: str, **extra) -> None: method = method.upper().strip() path = (path or "").strip() if method not in METHODS or not path: return rec = {"method": method, "path": path, "source": source} rec.update({k: v for k, v in extra.items() if v is not None}) out.append(rec) def _parse_curl(text: str, source: str) -> List[Dict]: out: List[Dict] = [] for line in [x.strip() for x in text.splitlines() if "curl " in x]: m_method = re.search(r"-X\s+([A-Za-z]+)", line) method = m_method.group(1).upper() if m_method else "GET" m_url = re.search(r"(https?://[^\s'\"\\]+)", line) if not m_url: continue url = m_url.group(1) m_path = re.match(r"https?://[^/]+(.*)", url) path = m_path.group(1) if m_path else "/" if not path: path = "/" _append_endpoint(out, method, path, source, raw_url=url, format="curl") return out def _parse_postman(obj: Dict, source: str) -> List[Dict]: out: List[Dict] = [] def walk(items: List[Dict]) -> None: for it in items or []: if "request" in it: req = it.get("request", {}) method = str(req.get("method", "GET")).upper() url_obj = req.get("url", {}) path = "/" raw_url = None if isinstance(url_obj, dict): raw_url = url_obj.get("raw") path_parts = url_obj.get("path") if isinstance(path_parts, list) and path_parts: path = "/" + "/".join(str(p) for p in path_parts) elif isinstance(raw_url, str): m = re.match(r"https?://[^/]+(.*)", raw_url) path = m.group(1) if m and m.group(1) else "/" elif isinstance(url_obj, str): raw_url = url_obj m = re.match(r"https?://[^/]+(.*)", url_obj) path = m.group(1) if m and m.group(1) else "/" _append_endpoint(out, method, path, source, raw_url=raw_url, format="postman") if isinstance(it.get("item"), list): walk(it["item"]) walk(obj.get("item", [])) return out def _parse_openapi(obj: Dict, source: str) -> List[Dict]: out: List[Dict] = [] paths = obj.get("paths", {}) if not isinstance(paths, dict): return out for path, methods in paths.items(): if not isinstance(methods, dict): continue for m in methods.keys(): method = str(m).upper() if method in METHODS: _append_endpoint(out, method, str(path), source, format="openapi") return out def _parse_insomnia(obj: Dict, source: str) -> List[Dict]: out: List[Dict] = [] resources = obj.get("resources", []) if not isinstance(resources, list): return out for r in resources: if not isinstance(r, dict): continue if r.get("_type") == "request": method = str(r.get("method", "GET")).upper() url = str(r.get("url", "")) path = "/" m = re.match(r"https?://[^/]+(.*)", url) if m and m.group(1): path = m.group(1) elif url.startswith("/"): path = url _append_endpoint(out, method, path, source, raw_url=url, format="insomnia") return out def _parse_opencollection(obj: Dict, source: str) -> List[Dict]: out: List[Dict] = [] requests = obj.get("requests") if isinstance(requests, list): for r in requests: if not isinstance(r, dict): continue method = str(r.get("method", "GET")).upper() path = str(r.get("path") or r.get("url") or "/") _append_endpoint(out, method, path, source, format="opencollection") return out def _parse_wsdl(text: str, source: str) -> List[Dict]: out: List[Dict] = [] try: root = ET.fromstring(text.encode("utf-8")) except Exception: return out ns = { "wsdl": "http://schemas.xmlsoap.org/wsdl/", "soap": "http://schemas.xmlsoap.org/wsdl/soap/", } soap_address = root.find(".//soap:address", ns) location = soap_address.attrib.get("location") if soap_address is not None else None for op in root.findall(".//wsdl:operation", ns): name = op.attrib.get("name") if not name: continue path = f"/soap/{name}" _append_endpoint(out, "POST", path, source, raw_url=location, format="wsdl", operation=name) return out def _parse_bruno(text: str, source: str) -> List[Dict]: out: List[Dict] = [] method = None url = None for line in text.splitlines(): s = line.strip() block = re.match(r"^(get|post|put|patch|delete|head|options)\s*\{", s, re.IGNORECASE) if block: method = block.group(1).upper() continue m = re.match(r"method:\s*([A-Za-z]+)", s, re.IGNORECASE) if m: method = m.group(1).upper() continue u = re.match(r"url:\s*(.+)$", s, re.IGNORECASE) if u: url = u.group(1).strip() if method and url: m = re.match(r"https?://[^/]+(.*)", url) path = m.group(1) if m and m.group(1) else (url if url.startswith("/") else f"/{url}") _append_endpoint(out, method, path, source, raw_url=url, format="bruno") return out def _load_structured(path: Path, raw: str) -> Optional[Dict]: if path.suffix.lower() == ".json": try: return json.loads(raw) except Exception: return None if path.suffix.lower() in {".yaml", ".yml"} and yaml is not None: try: obj = yaml.safe_load(raw) return obj if isinstance(obj, dict) else None except Exception: return None return None def parse_file(path: Path, raw: str) -> List[Dict]: lower_name = path.name.lower() ext = path.suffix.lower() if ext == ".wsdl" or (ext == ".xml" and "<definitions" in raw and "wsdl" in raw.lower()): return _parse_wsdl(raw, str(path)) if ext == ".bru": return _parse_bruno(raw, str(path)) if ext in {".curl", ".sh", ".txt"}: return _parse_curl(raw, str(path)) obj = _load_structured(path, raw) if obj is not None: if "openapi" in obj or "swagger" in obj: return _parse_openapi(obj, str(path)) if lower_name.endswith(".postman_collection.json") or "info" in obj and "item" in obj: return _parse_postman(obj, str(path)) if "resources" in obj: return _parse_insomnia(obj, str(path)) if lower_name.endswith(".opencollection.json") or "requests" in obj: return _parse_opencollection(obj, str(path)) return [] return _parse_curl(raw, str(path)) def parse_zip(path: Path) -> List[Dict]: out: List[Dict] = [] with zipfile.ZipFile(path) as zf: for info in zf.infolist(): if info.is_dir(): continue p = Path(info.filename) raw = _safe_text(zf.read(info)) if p.suffix.lower() == ".zip": continue out.extend(parse_file(p, raw)) return out def parse_input(path: Path) -> List[Dict]: if path.is_dir(): out: List[Dict] = [] for f in path.rglob("*"): if f.is_file(): raw = f.read_text(encoding="utf-8", errors="ignore") out.extend(parse_file(f, raw)) return out if path.suffix.lower() == ".zip": return parse_zip(path) raw = path.read_text(encoding="utf-8", errors="ignore") return parse_file(path, raw) def dedupe(endpoints: List[Dict]) -> List[Dict]: seen = set() out = [] for e in endpoints: key = (e.get("method"), e.get("path"), e.get("source")) if key in seen: continue seen.add(key) out.append(e) return out def main() -> None: parser = argparse.ArgumentParser(description="Parse API sources into normalized endpoint inventory") parser.add_argument("--input", required=True, type=Path, help="Input file/folder/zip path") parser.add_argument("--output", type=Path, help="Output JSON path") args = parser.parse_args() endpoints = dedupe(parse_input(args.input)) payload = { "input": str(args.input), "count": len(endpoints), "endpoints": endpoints, } if args.output: args.output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print(str(args.output)) else: print(json.dumps(payload, ensure_ascii=False, indent=2)) if __name__ == "__main__": main() -
run.sh 632 B
#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" INPUT_PATH="${1:-$ROOT_DIR/examples}" TMP_JSON="$ROOT_DIR/.tmp.normalized.json" OUT_TEST="$SCRIPT_DIR/templates/pytest/tests/test_generated_api.py" python3 "$SCRIPT_DIR/parse_api_sources.py" --input "$INPUT_PATH" --output "$TMP_JSON" python3 "$SCRIPT_DIR/generate_pytest_tests.py" --input "$TMP_JSON" --output "$OUT_TEST" cd "$SCRIPT_DIR/templates/pytest" python3 -m pytest -q || { echo "pytest run failed (likely missing dependencies). Install with:" echo "pip install -r requirements.txt" exit 1 }
-
-
README.md 805 B
# api-test-pytest (EN) ## Skill Overview Need API outputs that should land in pytest-based automation; The project is already Python-first or wants pytest-style structure. ## How to Use 1. Open `SKILL.md` in this folder and confirm this skill fits your task. 2. In your AI tool, call `@skill api-test-pytest`, then add your real project context and goal. 3. If you need a specific output format (table, checklist, report), include it directly in your request. ## One-Click Install Script Run from the repository root: ### macOS / Linux ```bash bash ./scripts/install-skills-mac.sh --tool codex --lang en --skill api-test-pytest ``` ### Windows PowerShell ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\install-skills-windows.ps1 -Tool codex -Lang en -Skill api-test-pytest ``` -
SKILL.md 2.6 KB
--- name: api-test-pytest description: Use this skill when you need to parse multi-format API definitions and generate Pytest API automation; triggers include Pytest API tests and API automation with Pytest. --- # api-test-pytest (EN) **Chinese version:** See the corresponding Chinese skill. ## When to Use - Need API outputs that should land in pytest-based automation. - The project is already Python-first or wants pytest-style structure. ## Workflow 1. Read and follow the main prompt listed under Progressive disclosure (coverage, structure, quality bar). 2. Add only project context that changes the result: scope, environment, constraints, risks, dependencies, expected deliverable. 3. If input is incomplete, return a usable first draft and explicitly mark assumptions and gaps. 4. Default to Markdown; switch formats only when the user asks. ## Core Constraints - Prioritize by risk / business impact — do not treat everything equally. - Separate confirmed facts from current assumptions. - Do not invent endpoints, fields, environments, or root causes the user did not provide. - Use placeholders or env-var semantics for auth/secrets; never hardcode real credentials. - Keep output executable: concrete scenarios, clear priority, clear next steps. ## Progressive Disclosure - Before producing output, read and follow `prompts/api-test-pytest.md` (minimum coverage, output structure, quality bar). - When a ready-made template fits: use matching files under `output-templates/`. - When the user wants examples or alignment with existing assets: read relevant `examples/`. - For deep framework/troubleshoot/schema notes: read only the relevant file(s) under `references/`, do not load the whole directory. - For format conversion or helper checks: prefer existing `scripts/` over reinventing. - For evaluating/regressing this skill: use `evals/` with skill-up. ## Pre-delivery Checklist - [ ] Followed the main prompt's output structure - [ ] Minimum coverage focus: module structure, fixture strategy, auth handling, priority endpoints, positive scenarios, negative and boundary scenarios, assertion focus, test data strategy, ... (details in main prompt) - [ ] Covered the minimum checklist, or explained omissions - [ ] High-risk items have explicit priority - [ ] Did not invent details the user did not provide - [ ] Assumptions and gaps are marked ## Common Pitfalls - Do not pretend completeness when scope/context is missing. - Do not treat every item as equally important. - Do not skip assumptions and information gaps. - Do not dump generic theory unrelated to the current toolchain.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.