Claude Skill

api-test-supertest

Use this skill when you need to parse multi-format API definitions and generate executable Supertest scripts; triggers include Supertest, Node.js API testing, and Supertest automation.

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-supertest-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-supertest
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-supertest (EN)

Skill Overview

Need API outputs that should land in Supertest based automation; The project is Node.js-based or already uses Supertest/Jest.

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-supertest, 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-supertest

Windows PowerShell

powershell -ExecutionPolicy Bypass -File .\scripts\install-skills-windows.ps1 -Tool codex -Lang en -Skill api-test-supertest

Skill manifest

api-test-supertest (EN)

Chinese version: See the corresponding Chinese skill.

When to Use

  • Need API outputs that should land in Supertest based automation.
  • The project is Node.js-based or already uses Supertest/Jest.

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-supertest.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: suite structure, environment setup, auth handling, priority endpoints, positive scenarios, negative and boundary scenarios, assertion focus, 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 482 B
      version: 1
      metadata:
        key: "api-test-supertest"
        last_verified: "2026-03-24"
      interface:
        display_name: "API Test Supertest"
        short_description: "Use this skill when you need to parse multi-format API definitions and generate executable Supertest scripts; triggers include Supertest, Node.js API testing,…"
        default_prompt: "Use api-test-supertest to complete the task with local scripts, prompts, and examples in this skill folder."
      policy:
        allow_implicit_invocation: true
      
  • evals
    • cases
      • basic-success.yaml 933 B
        id: basic-success
        title: "Supertest: OpenAPI/curl yields suite structure"
        description: |
          With a short OpenAPI and curl snippet, produce Supertest + Jest layout/env/assertion structure.
        
        input:
          prompt: |
            Use api-test-supertest.
            Env: SIT. Auth: Bearer {{token}} (do not hardcode real tokens). Runtime: Node.js + Jest.
            OpenAPI snippet:
              POST /v1/users
              GET /v1/users/{id}
            Desensitized curl:
              curl -X POST https://api.example.com/v1/users -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"email":"u@example.com"}'
            Produce a Supertest plan: tests/ layout, app/request helpers, priority cases and assertion focus. Avoid long code dumps.
        
        expect:
          must_contain:
            - "Supertest"
            - "Jest"
          must_not_contain:
            - "TODO"
            - "I cannot"
        
        judge:
          type: rule_based
          success:
            - output_contains:
                all:
                  - "Task Understanding"
                  - "Supertest"
        
      • edge-bad-or-neighbor.yaml 721 B
        id: edge-bad-or-neighbor
        title: "Supertest: redirect Postman/Playwright neighbor requests"
        description: |
          When the user asks for Postman/Playwright under the Supertest skill, stay on Supertest or clarify the boundary.
        
        input:
          prompt: |
            I activated api-test-supertest, but I asked: export a Postman collection and also do Playwright E2E.
            Endpoint: GET /health. Correct the scope: deliver a Supertest + Jest API plan and explain why Postman/Playwright are out of this skill.
        
        expect:
          must_contain:
            - "Supertest"
            - "Postman"
          must_not_contain:
            - "TODO"
            - "I cannot"
        
        judge:
          type: rule_based
          success:
            - output_contains:
                all:
                  - "Supertest"
                  - "Open Questions"
        
      • edge-incomplete-input.yaml 655 B
        id: edge-incomplete-input
        title: "Supertest: one-line ask still yields draft"
        description: |
          With only one sentence and no schema, still draft Supertest structure and list gaps/assumptions.
        
        input:
          prompt: |
            Use api-test-supertest. I only know: automate the points redemption API. No path, fields, or existing Jest layout.
            Give a usable Supertest draft structure 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"
                  - "Supertest"
        
    • 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-supertest.yml 655 B
        name: API Supertest CI
        
        on:
          workflow_dispatch:
          pull_request:
            paths:
              - "explore/api-test-supertest/**"
        
        jobs:
          test:
            runs-on: ubuntu-latest
            defaults:
              run:
                working-directory: explore/api-test-supertest/scripts/templates/supertest
            steps:
              - name: Checkout
                uses: actions/checkout@v4
        
              - name: Setup Node
                uses: actions/setup-node@v4
                with:
                  node-version: "20"
        
              - name: Install deps
                run: npm ci || npm install
        
              - name: Run tests
                env:
                  BASE_URL: ${{ secrets.API_BASE_URL }}
                  API_TOKEN: ${{ secrets.API_TOKEN }}
                run: npm test
        
      • Jenkinsfile.supertest 545 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 828 B
      {
        "info": {
          "name": "Sample API Collection",
          "_postman_id": "a8efc1e2-1111-4444-9999-111122223333",
          "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",
                "protocol": "https",
                "host": [
                  "api",
                  "example",
                  "com"
                ],
                "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-supertest.md 5.3 KB
      # API Test Supertest Prompt
      
      From the materials the user provides, produce a Supertest + Jest API automation plan or test-asset structure for direct implementation.
      
      ## Role
      
      - Act as a senior QA and API automation expert who turns API materials into a maintainable Node.js / Supertest 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 Node test assets (`tests/` / `__tests__/`, Jest/Mocha config, existing Supertest cases)
      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, `package.json` scripts.
      
      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/
        <resource>.test.js     # or .test.ts only when the project is already TypeScript
      jest.config.cjs          # reuse if present
      package.json             # script: "test": "jest --runInBand"
      ```
      
      **System under test entry (pick one based on materials; if unclear, state the assumption)**
      
      1. **In-process**: `request(app)` where `app` is the exported Express/Fastify/Koa instance (preferred for unit/contract)
      2. **Against a real baseUrl**: `request(process.env.BASE_URL)` for integration; use this when there is no app export
      
      **Naming**
      
      - files: `<resource>.test.js` (e.g. `orders.test.js`)
      - `describe`: resource or flow; `test`/`it`: behavior + condition (e.g. `GET /orders/:id returns 200`)
      
      **Config and auth**
      
      - Read `BASE_URL`, `API_TOKEN` from env; sample values only `http://localhost:3000` / `replace-me`
      - JSON by default; `.set('Authorization', \`Bearer ${token}\`)` with a placeholder token
      - Never commit real cookies into the suite
      
      **Assertion style**
      
      - Supertest chain: `.expect(status)` plus Jest `expect` on `res.body`
      - Minimum: status + critical fields (fields must come from materials)
      - Async with `async/await`; default `jest --runInBand` to reduce flaky shared-env races
      
      **Layers (default)**
      
      - Separate smoke / negative via files or naming; or reuse existing `testPathPatterns`
      - CI: smoke file set first, then full suite
      
      If the project already uses Mocha + chai or TypeScript, **align to it** — do not force Jest unless the user asks.
      
      ## Gotchas
      
      - **Never** hardcode real tokens, passwords, or cookies; always env vars + placeholders.
      - When migrating from curl/Postman: redact sensitive headers.
      - **Do not invent** paths, fields, status codes, or `res.body` shapes the user did not provide.
      - Do not default to Playwright E2E or other non-API stacks.
      - If there is neither an `app` export nor a `BASE_URL`, deliver structure and require one of the two in open questions — do not pretend the suite already runs.
      - If information is incomplete, still ship a usable first version (layout + describe outline + auth contract) and list assumptions.
      - Unless the user asks for runnable files, prefer structure and case outlines over huge full test-file dumps.
      
      ## Minimum coverage checklist
      
      Unless the user explicitly narrows scope, the result must cover:
      
      - suite layout and entry mode (app vs baseUrl)
      - env vars and auth handling
      - high-priority endpoints (P0/P1)
      - positive scenarios
      - negative and boundary scenarios
      - assertion focus
      - data strategy (create/cleanup)
      - CI or local run guidance
      - 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
      - chosen entry mode (app instance / baseUrl) and why
      
      ### 2. Supertest Test Plan or Structure
      - proposed tree and file responsibilities
      - Jest (or existing runner) config highlights
      - env var contract
      - default auth pattern
      - alignment with an existing Node suite (if any)
      
      ### 3. Priority Coverage
      For each P0/P1 case:
      - file name and `test` title
      - method / path (confirmed only)
      - priority and risk rationale
      - positive / negative / boundary points
      - assertion focus (status, body fields)
      - required headers / prerequisite data
      
      ### 4. Setup and Data Notes
      - local vs CI environment differences
      - test-data setup and cleanup
      - parallelism limits (why runInBand is suggested)
      
      ### 5. Execution Suggestions
      - local: `npm test` / path-filtered runs
      - 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
      - [ ] Entry mode (app/baseUrl), layout, and env placeholders are explicit
      - [ ] No real secrets; no invented paths/fields
      - [ ] P0/P1 cases have concrete titles and assertion focus
      - [ ] Local and CI run paths are actionable
      
      ## Quality bar
      
      - Stay Supertest-specific (Jest by default unless another runner already exists).
      - Prioritize by risk.
      - Separate confirmed facts from assumptions.
      - Avoid huge test-file dumps unless the user asks for runnable files.
      
  • references
    • local
      • api-testing_EN.md 491 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-supertest.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
      # Supertest Framework Specification
      
      ## 1. Directory Convention
      
      - `scripts/parse_api_sources.py`: multi-format API source parser
      - `scripts/generate_supertest_tests.py`: normalized endpoint -> Jest/Supertest tests
      - `scripts/templates/supertest/`: runnable Node.js test template
      - `generated-tests/`: output test files generated from parsed endpoints
      
      ## 2. Test Layer Convention
      
      - `smoke`: core endpoint availability
      - `contract`: status code + schema/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 thresholds for critical endpoints are recommended
      - error payload assertions for 4xx/5xx are required in negative tests
      
      ## 4. Data and Environment Rules
      
      - use environment variables for base URL and token
      - never commit secrets in test files
      - use deterministic test data or isolated seed fixtures
      - ensure 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 every PR
      - run full suite on main/nightly
      - fail on P0 regression
      - export JUnit and coverage artifacts when possible
      
    • 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 437 B
      # Setup and CI Guide
      
      ## Local
      
      ```bash
      cd scripts/templates/supertest
      npm install
      npm test
      ```
      
      ## One-Click Flow
      
      ```bash
      cd scripts
      ./run.sh ../examples
      ```
      
      ## CI Recommendation
      
      - PR: run smoke/generated subset
      - main/nightly: run full suite
      - archive junit or test summary output
      
      ## CI Template
      
      - `examples/ci/github-actions-supertest.yml`
      - `examples/ci/Jenkinsfile.supertest`
      
      ## Report Schema
      
      - `references/report-schema.md`
      
  • scripts
    • templates
      • supertest
        • tests
          • generated.api.test.js 2.1 KB
            const request = require('supertest');
            const app = require(process.env.SUPERTEST_APP || '../app');
            
            describe('Generated API Tests', () => {
            test('api_get_v1_users_page_1_1', async () => {
              const res = await request(app).get('/v1/users?page=1');
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_post_v1_login_2', async () => {
              const res = await request(app).post('/v1/login').send({});
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_get_v1_users_3', async () => {
              const res = await request(app).get('/v1/users');
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_post_v1_users_4', async () => {
              const res = await request(app).post('/v1/users').send({});
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_post_soap_getuser_5', async () => {
              const res = await request(app).post('/soap/GetUser').send({});
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_post_soap_createuser_6', async () => {
              const res = await request(app).post('/soap/CreateUser').send({});
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_get_v1_orders_7', async () => {
              const res = await request(app).get('/v1/orders');
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_post_v1_orders_8', async () => {
              const res = await request(app).post('/v1/orders').send({});
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_get_v1_products_9', async () => {
              const res = await request(app).get('/v1/products');
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_post_v1_products_10', async () => {
              const res = await request(app).post('/v1/products').send({});
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            test('api_get_v1_users_11', async () => {
              const res = await request(app).get('/v1/users');
              expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);
            });
            });
            
          • health.test.js 286 B
            const request = require('supertest');
            const app = require('../app');
            
            describe('Health API', () => {
              test('GET /health should return 200', async () => {
                const res = await request(app).get('/health');
                expect(res.status).toBe(200);
                expect(res.body.ok).toBe(true);
              });
            });
            
        • app.js 475 B
          const express = require('express');
          
          const app = express();
          app.use(express.json());
          
          app.get('/health', (req, res) => {
            res.status(200).json({ ok: true, service: 'supertest-template' });
          });
          
          app.post('/api/login', (req, res) => {
            const body = req.body || {};
            if (!body.username || !body.password) {
              return res.status(400).json({ code: 'INVALID_INPUT' });
            }
            return res.status(200).json({ token: 'fake-token', user: body.username });
          });
          
          module.exports = app;
          
        • jest.config.cjs 89 B · in bundle
        • package.json 254 B
          {
            "name": "supertest-template",
            "version": "1.0.0",
            "private": true,
            "scripts": {
              "test": "jest --runInBand"
            },
            "dependencies": {
              "express": "^4.19.2"
            },
            "devDependencies": {
              "jest": "^29.7.0",
              "supertest": "^7.0.0"
            }
          }
          
    • generate_supertest_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, i: int) -> str:
          base = f"{method}_{path}".lower()
          base = re.sub(r"[^a-z0-9]+", "_", base).strip("_")
          return f"api_{base}_{i}"
      
      
      def _to_test(method: str, path: str, title: str) -> str:
          m = method.lower()
          call = f"request(app).{m}('{path}')"
          if m in {"post", "put", "patch"}:
              call += ".send({})"
          return (
              f"test('{title}', async () => {{\n"
              f"  const res = await {call};\n"
              f"  expect([200, 201, 202, 204, 400, 401, 403, 404]).toContain(res.status);\n"
              f"}});\n"
          )
      
      
      def main() -> None:
          parser = argparse.ArgumentParser(description="Generate Supertest tests from normalized endpoint JSON")
          parser.add_argument("--input", required=True, type=Path, help="Normalized endpoint JSON from parse_api_sources.py")
          parser.add_argument("--output-dir", required=True, type=Path, help="Output directory for generated test file")
          args = parser.parse_args()
      
          obj = json.loads(args.input.read_text(encoding="utf-8"))
          endpoints = obj.get("endpoints", [])
          args.output_dir.mkdir(parents=True, exist_ok=True)
      
          lines = [
              "const request = require('supertest');",
              "const app = require(process.env.SUPERTEST_APP || '../app');",
              "",
              "describe('Generated API Tests', () => {",
          ]
          for i, ep in enumerate(endpoints, start=1):
              method = str(ep.get("method", "GET")).upper()
              path = str(ep.get("path", "/"))
              title = _safe_name(method, path, i)
              lines.append(_to_test(method, path, title).rstrip())
          lines.append("});")
          lines.append("")
      
          out = args.output_dir / "generated.api.test.js"
          out.write_text("\n".join(lines), encoding="utf-8")
          print(str(out))
      
      
      if __name__ == "__main__":
          main()
      
    • parse_api_sources.py 9.2 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 []
      
          # fallback: parse curl lines from generic text
          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 699 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/supertest/tests/test_generated_api.test.js"
      
      python3 "$SCRIPT_DIR/parse_api_sources.py" --input "$INPUT_PATH" --output "$TMP_JSON"
      python3 "$SCRIPT_DIR/generate_supertest_tests.py" --input "$TMP_JSON" --output-dir "$SCRIPT_DIR/templates/supertest/tests"
      
      if ! command -v npm >/dev/null 2>&1; then
        echo "npm not found. Tests were generated at: $OUT_TEST"
        exit 0
      fi
      
      cd "$SCRIPT_DIR/templates/supertest"
      if [ ! -d node_modules ]; then
        npm install
      fi
      npm test
      
  • README.md 812 B
    # api-test-supertest (EN)
    
    ## Skill Overview
    
    Need API outputs that should land in Supertest based automation; The project is Node.js-based or already uses Supertest/Jest.
    
    ## 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-supertest`, 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-supertest
    ```
    
    ### Windows PowerShell
    
    ```powershell
    powershell -ExecutionPolicy Bypass -File .\scripts\install-skills-windows.ps1 -Tool codex -Lang en -Skill api-test-supertest
    ```
    
  • SKILL.md 2.6 KB
    ---
    name: api-test-supertest
    description: Use this skill when you need to parse multi-format API definitions and generate executable Supertest scripts; triggers include Supertest, Node.js API testing, and Supertest automation.
    ---
    
    # api-test-supertest (EN)
    
    **Chinese version:** See the corresponding Chinese skill.
    
    ## When to Use
    
    - Need API outputs that should land in Supertest based automation.
    - The project is Node.js-based or already uses Supertest/Jest.
    
    ## 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-supertest.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: suite structure, environment setup, auth handling, priority endpoints, positive scenarios, negative and boundary scenarios, assertion focus, 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