Token导航 LogoToken导航

Agent Skill · 研究检索

api-test-suite-generator(API 测试 suite 生成器)

用于辅助 API 设计、接口文档、请求响应结构和服务集成说明。它适合让 Agent 梳理 endpoint、生成 OpenAPI 草稿、检查字段命名、整理错误码或辅助前后端联调。使用时需要确认真实业务语义、鉴权方式、分页和错误处理规则;涉及生成接口文档时,应避免凭空补字段,最好从现有代码、schema 或接口样例中提取事实。

api-test-suite-generator 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为接触敏感数据;许可证为 MIT,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 29,038。

总安装 3,120周安装 125GitHub Stars 33下载 1,010核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:api-test-suite-generator(API 测试 suite 生成器)
来源仓库:https://github.com/patricio0312rev/skills
仓库路径:skills/api-test-suite-generator
安装命令:
npx skills add https://github.com/patricio0312rev/skills --skill api-test-suite-generator
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/patricio0312rev/skills --skill api-test-suite-generator

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

用于从路由定义自动生成完整的 API 测试套件,提升自动化测试效率。支持扫描所有 API 路由、提取合约信息并生成带断言与边缘案例的测试文件。内置 fixtures 与数据库 seeding 机制,便于集成测试数据准备。

安装方式:通过 GitHub 仓库安装,命令为 npx skills add <repo> --skill api-test-suite-generator。推荐将生成的测试组织为 integration/unit/e2e 分层结构便于维护。

来源仓库内路径:skills/api-test-suite-generator

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/patricio0312rev/skills --skill api-test-suite-…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要接触敏感数据,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

api-test-suite-generator 怎么安装?

在终端执行 npx skills add https://github.com/patricio0312rev/skills --skill api-test-suite-generator。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

api-test-suite-generator 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

api-test-suite-generator 需要什么权限?

权限级别是接触敏感数据。该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

api-test-suite-generator 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

api-test-suite-generator 用的人多吗?

公开数据是累计安装 3,120 次,近一周安装 125 次,下载 1,010 次,GitHub 33 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

api-test-suite-generator 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 需要接触敏感数据,装到常用环境前要先评估影响面。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 环境里有大量密钥、凭证,不方便给第三方 Skill 读取的人。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
api-test-suite-generator

API 测试 suite 生成器 · 本页

3,12033接触敏感数据MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

API Test Suite Generator

Generate comprehensive API test suites automatically from your route definitions.

Core Workflow

  1. Scan routes: Find all API route definitions
  2. Analyze contracts: Extract request/response schemas
  3. Generate tests: Create test files for each resource
  4. Add assertions: Status codes, response structure, headers
  5. Include edge cases: Invalid inputs, auth, not found
  6. Setup fixtures: Test data and database seeding

Test Structure

tests/
├── setup.ts              # Global test setup
├── fixtures/             # Test data
│   ├── users.ts
│   └── products.ts
├── integration/          # API integration tests
│   ├── users.test.ts
│   ├── products.test.ts
│   └── auth.test.ts
└── helpers/              # Test utilities
    ├── api-client.ts
    └── auth.ts

Test Setup (Vitest/Jest)

// tests/setup.ts
import { beforeAll, afterAll, beforeEach, afterEach } from "vitest";
import { createServer } from "../src/server";
import { prisma } from "../src/db";

let server: ReturnType<typeof createServer>;

beforeAll(async () => {
  server = await createServer();
  await server.listen({ port: 0 }); // Random port
  process.env.TEST_BASE_URL = `http://localhost:${server.address().port}`;
});

afterAll(async () => {
  await server.close();
  await prisma.$disconnect();
});

beforeEach(async () => {
  // Clean database before each test
  await prisma.$executeRaw`TRUNCATE TABLE users CASCADE`;
});

afterEach(async () => {
  // Cleanup after each test
});

export { server };

API Test Client

// tests/helpers/api-client.ts
import supertest from "supertest";

const baseUrl = process.env.TEST_BASE_URL || "http://localhost:3000";

export const api = supertest(baseUrl);

export async function authenticatedApi(token?: string) {
  const authToken = token || (await getTestAuthToken());
  return {
    get: (url: string) => api.get(url).set("Authorization", `Bearer ${authToken}`),
    post: (url: string) => api.post(url).set("Authorization", `Bearer ${authToken}`),
    put: (url: string) => api.put(url).set("Authorization", `Bearer ${authToken}`),
    patch: (url: string) => api.patch(url).set("Authorization", `Bearer ${authToken}`),
    delete: (url: string) => api.delete(url).set("Authorization", `Bearer ${authToken}`),
  };
}

async function getTestAuthToken(): Promise<string> {
  const response = await api.post("/api/auth/login").send({
    email: "test@example.com",
    password: "testpassword",
  });
  return response.body.token;
}

Test Generator Script

// scripts/generate-api-tests.ts
import * as fs from "fs";
import * as path from "path";

interface RouteInfo {
  method: string;
  path: string;
  name: string;
  params?: { name: string; type: "path" | "query" }[];
  requestBody?: object;
  responseSchema?: object;
  auth?: boolean;
}

interface TestCase {
  name: string;
  description: string;
  method: string;
  path: string;
  body?: object;
  expectedStatus: number;
  expectedBody?: object;
  headers?: Record<string, string>;
  auth?: boolean;
}

function generateTestFile(
  resource: string,
  routes: RouteInfo[]
): string {
  const lines: string[] = [];

  // Imports
  lines.push(`import { describe, it, expect, beforeEach, afterEach } from "vitest";`);
  lines.push(`import { api, authenticatedApi } from "../helpers/api-client";`);
  lines.push(`import { create${capitalize(resource)} } from "../fixtures/${resource}";`);
  lines.push("");

  // Test suite
  lines.push(`describe("${capitalize(resource)} API", () => {`);

  for (const route of routes) {
    const testCases = generateTestCases(route);

    lines.push(`  describe("${route.method} ${route.path}", () => {`);

    for (const testCase of testCases) {
      lines.push(generateTestCase(testCase, route));
    }

    lines.push(`  });`);
    lines.push("");
  }

  lines.push(`});`);

  return lines.join("\n");
}

function generateTestCases(route: RouteInfo): TestCase[] {
  const cases: TestCase[] = [];

  // Success case
  cases.push({
    name: `should ${getActionVerb(route.method)} successfully`,
    description: `Happy path for ${route.method} ${route.path}`,
    method: route.method,
    path: route.path,
    body: route.requestBody,
    expectedStatus: getExpectedStatus(route.method),
    auth: route.auth,
  });

  // Auth failure case (if auth required)
  if (route.auth) {
    cases.push({
      name: "should return 401 without auth token",
      description: "Unauthorized access attempt",
      method: route.method,
      path: route.path,
      expectedStatus: 401,
      auth: false,
    });
  }

  // Not found case (if has path params)
  if (route.params?.some((p) => p.type === "path")) {
    cases.push({
      name: "should return 404 for non-existent resource",
      description: "Resource not found",
      method: route.method,
      path: route.path.replace(/:(\w+)/g, "non-existent-id"),
      expectedStatus: 404,
      auth: route.auth,
    });
  }

  // Validation error case (for POST/PUT/PATCH)
  if (["POST", "PUT", "PATCH"].includes(route.method)) {
    cases.push({
      name: "should return 400 for invalid request body",
      description: "Validation failure",
      method: route.method,
      path: route.path,
      body: {},
      expectedStatus: 400,
      auth: route.auth,
    });
  }

  return cases;
}

function generateTestCase(testCase: TestCase, route: RouteInfo): string {
  const lines: string[] = [];
  const indent = "    ";

  lines.push(`${indent}it("${testCase.name}", async () => {`);

  // Setup
  if (route.params?.some((p) => p.type === "path")) {
    lines.push(`${indent}  // Setup: Create test resource`);
    lines.push(`${indent}  const resource = await createTestResource();`);
    lines.push(`${indent}  const url = "${route.path}".replace(":id", resource.id);`);
  } else {
    lines.push(`${indent}  const url = "${route.path}";`);
  }

  // Make request
  lines.push("");
  if (testCase.auth) {
    lines.push(`${indent}  const client = await authenticatedApi();`);
    lines.push(
      `${indent}  const response = await client.${testCase.method.toLowerCase()}(url)`
    );
  } else {
    lines.push(
      `${indent}  const response = await api.${testCase.method.toLowerCase()}(url)`
    );
  }

  if (testCase.body) {
    lines.push(`${indent}    .send(${JSON.stringify(testCase.body, null, 2).replace(/\n/g, `\n${indent}    `)})`);
  }

  lines.push(`${indent}    .expect(${testCase.expectedStatus});`);

  // Assertions
  lines.push("");
  if (testCase.expectedStatus < 400) {
    lines.push(`${indent}  expect(response.body).toBeDefined();`);
    if (testCase.method === "POST") {
      lines.push(`${indent}  expect(response.body.id).toBeDefined();`);
    }
  } else {
    lines.push(`${indent}  expect(response.body.error).toBeDefined();`);
  }

  lines.push(`${indent}});`);
  lines.push("");

  return lines.join("\n");
}

function getActionVerb(method: string): string {
  const verbs: Record<string, string> = {
    GET: "retrieve",
    POST: "create",
    PUT: "update",
    PATCH: "partially update",
    DELETE: "delete",
  };
  return verbs[method] || "process";
}

function getExpectedStatus(method: string): number {
  const statuses: Record<string, number> = {
    GET: 200,
    POST: 201,
    PUT: 200,
    PATCH: 200,
    DELETE: 204,
  };
  return statuses[method] || 200;
}

function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

Example Generated Tests

// tests/integration/users.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import { api, authenticatedApi } from "../helpers/api-client";
import { createUser, createUsers } from "../fixtures/users";

describe("Users API", () => {
  describe("GET /api/users", () => {
    it("should return paginated list of users", async () => {
      // Setup
      await createUsers(15);

      // Request
      const client = await authenticatedApi();
      const response = await client
        .get("/api/users")
        .query({ page: 1, limit: 10 })
        .expect(200);

      // Assertions
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveLength(10);
      expect(response.body.meta.total).toBe(15);
      expect(response.body.meta.page).toBe(1);
      expect(response.body.meta.total_pages).toBe(2);
    });

    it("should return 401 without auth token", async () => {
      const response = await api.get("/api/users").expect(401);

      expect(response.body.error.code).toBe("UNAUTHORIZED");
    });
  });

  describe("GET /api/users/:id", () => {
    it("should return user by ID", async () => {
      const user = await createUser({ name: "Test User" });

      const client = await authenticatedApi();
      const response = await client
        .get(`/api/users/${user.id}`)
        .expect(200);

      expect(response.body.data.id).toBe(user.id);
      expect(response.body.data.name).toBe("Test User");
    });

    it("should return 404 for non-existent user", async () => {
      const client = await authenticatedApi();
      const response = await client
        .get("/api/users/non-existent-id")
        .expect(404);

      expect(response.body.error.code).toBe("NOT_FOUND");
    });
  });

  describe("POST /api/users", () => {
    it("should create new user", async () => {
      const client = await authenticatedApi();
      const response = await client
        .post("/api/users")
        .send({
          name: "New User",
          email: "new@example.com",
          role: "user",
        })
        .expect(201);

      expect(response.body.data.id).toBeDefined();
      expect(response.body.data.name).toBe("New User");
      expect(response.body.data.email).toBe("new@example.com");
    });

    it("should return 400 for invalid request body", async () => {
      const client = await authenticatedApi();
      const response = await client
        .post("/api/users")
        .send({})
        .expect(400);

      expect(response.body.error.code).toBe("VALIDATION_ERROR");
      expect(response.body.error.details).toBeDefined();
    });

    it("should return 409 for duplicate email", async () => {
      await createUser({ email: "existing@example.com" });

      const client = await authenticatedApi();
      const response = await client
        .post("/api/users")
        .send({
          name: "New User",
          email: "existing@example.com",
        })
        .expect(409);

      expect(response.body.error.code).toBe("CONFLICT");
    });
  });

  describe("PUT /api/users/:id", () => {
    it("should update user", async () => {
      const user = await createUser({ name: "Original Name" });

      const client = await authenticatedApi();
      const response = await client
        .put(`/api/users/${user.id}`)
        .send({
          name: "Updated Name",
          email: user.email,
        })
        .expect(200);

      expect(response.body.data.name).toBe("Updated Name");
    });

    it("should return 404 for non-existent user", async () => {
      const client = await authenticatedApi();
      const response = await client
        .put("/api/users/non-existent-id")
        .send({ name: "Test" })
        .expect(404);

      expect(response.body.error.code).toBe("NOT_FOUND");
    });
  });

  describe("DELETE /api/users/:id", () => {
    it("should delete user", async () => {
      const user = await createUser();

      const client = await authenticatedApi();
      await client.delete(`/api/users/${user.id}`).expect(204);

      // Verify deletion
      await client.get(`/api/users/${user.id}`).expect(404);
    });

    it("should return 404 for non-existent user", async () => {
      const client = await authenticatedApi();
      await client.delete("/api/users/non-existent-id").expect(404);
    });
  });
});

Test Fixtures

// tests/fixtures/users.ts
import { prisma } from "../../src/db";
import { faker } from "@faker-js/faker";

interface CreateUserOptions {
  name?: string;
  email?: string;
  role?: string;
}

export async function createUser(options: CreateUserOptions = {}) {
  return prisma.user.create({
    data: {
      name: options.name ?? faker.person.fullName(),
      email: options.email ?? faker.internet.email(),
      role: options.role ?? "user",
      password: await hashPassword("testpassword"),
    },
  });
}

export async function createUsers(count: number) {
  const users = Array.from({ length: count }, () => ({
    name: faker.person.fullName(),
    email: faker.internet.email(),
    role: "user",
    password: "hashed-password",
  }));

  return prisma.user.createMany({ data: users });
}

export async function createAdminUser() {
  return createUser({ role: "admin" });
}

Contract Testing

// tests/contract/users.contract.test.ts
import { describe, it, expect } from "vitest";
import { api, authenticatedApi } from "../helpers/api-client";
import { z } from "zod";

// Response schemas
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  email: z.string().email(),
  role: z.enum(["user", "admin"]),
  createdAt: z.string().datetime(),
});

const PaginatedUsersSchema = z.object({
  success: z.literal(true),
  data: z.array(UserSchema),
  meta: z.object({
    page: z.number(),
    limit: z.number(),
    total: z.number(),
    total_pages: z.number(),
  }),
});

describe("Users API Contract", () => {
  it("GET /api/users should match contract", async () => {
    const client = await authenticatedApi();
    const response = await client.get("/api/users").expect(200);

    // Validate against schema
    const result = PaginatedUsersSchema.safeParse(response.body);
    expect(result.success).toBe(true);
  });

  it("GET /api/users/:id should match contract", async () => {
    const user = await createUser();

    const client = await authenticatedApi();
    const response = await client.get(`/api/users/${user.id}`).expect(200);

    // Validate against schema
    const result = UserSchema.safeParse(response.body.data);
    expect(result.success).toBe(true);
  });
});

CLI Script

#!/usr/bin/env node
// scripts/test-gen.ts
import * as fs from "fs";
import * as path from "path";
import { program } from "commander";

program
  .name("test-gen")
  .description("Generate API test suite from routes")
  .option("-f, --framework <type>", "Framework (express|nextjs|fastify)", "express")
  .option("-s, --source <path>", "Source directory", "./src")
  .option("-o, --output <path>", "Output directory", "./tests/integration")
  .option("-t, --test-runner <type>", "Test runner (vitest|jest)", "vitest")
  .parse();

const options = program.opts();

async function main() {
  const routes = await scanRoutes(options.framework, options.source);
  const groupedRoutes = groupRoutesByResource(routes);

  if (!fs.existsSync(options.output)) {
    fs.mkdirSync(options.output, { recursive: true });
  }

  for (const [resource, resourceRoutes] of Object.entries(groupedRoutes)) {
    const content = generateTestFile(resource, resourceRoutes);
    const filePath = path.join(options.output, `${resource}.test.ts`);
    fs.writeFileSync(filePath, content);
    console.log(`Generated ${filePath}`);
  }
}

main();

Best Practices

  1. Isolate tests: Each test should be independent
  2. Clean state: Reset database between tests
  3. Use fixtures: Create reusable test data factories
  4. Test edge cases: Invalid input, auth, not found
  5. Contract testing: Validate response schemas
  6. Descriptive names: Tests should read like documentation
  7. Fast execution: Use transactions for database cleanup
  8. CI integration: Run tests on every PR

Output Checklist

  • Test setup with server lifecycle
  • API client helper with auth support
  • Test files for each resource
  • Happy path tests for all endpoints
  • Authentication failure tests
  • Validation error tests
  • Not found tests for path params
  • Conflict/duplicate tests where applicable
  • Contract/schema validation tests
  • Test fixtures for each resource

平台分布

Claude Code

25.78%
按下载量换算260

OpenCode

23.64%
按下载量换算239

Gemini CLI

19.47%
按下载量换算197

Antigravity

13.64%
按下载量换算138

windsurf

8.88%
按下载量换算90

github-copilot

3.93%
按下载量换算40

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

接触敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills