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.
Install
npx skills add https://github.com/naodeng/awesome-qa-skills/tree/main/skills/en/testing-types/api-test-supertest
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install naodeng-awesome-qa-skills@llmmart
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
- Open
SKILL.mdin this folder and confirm this skill fits your task. - In your AI tool, call
@skill api-test-supertest, 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-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
- 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-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.
Reviews (0)
No reviews yet.
No comments yet.