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.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download naodeng-awesome-qa-skills-skills_en_testing-types_api-test-pytest-c44b892.zip · 18 KB
Part of naodeng/awesome-qa-skills — 97 skills

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

  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 ./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

  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.
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.

No comments yet.

Reviews (0)

No reviews yet.

Related