integration-test-builder

Integration Test Builder

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "integration-test-builder" with this command: npx skills add patricio0312rev/skills/patricio0312rev-skills-integration-test-builder

Integration Test Builder

Build comprehensive integration tests for APIs and database flows.

Test Harness Setup

// tests/setup/test-harness.ts import { PrismaClient } from "@prisma/client"; import { execSync } from "child_process";

export class TestHarness { prisma: PrismaClient;

async setup() { // Setup test database process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;

// Run migrations
execSync("npx prisma migrate deploy");

// Initialize Prisma client
this.prisma = new PrismaClient();

// Clear all data
await this.clearDatabase();

}

async teardown() { await this.prisma.$disconnect(); }

async clearDatabase() { const tables = await this.prisma.$queryRaw<{ tablename: string }[]> SELECT tablename FROM pg_tables WHERE schemaname = 'public' ;

for (const { tablename } of tables) {
  if (tablename !== "_prisma_migrations") {
    await this.prisma.$executeRawUnsafe(
      `TRUNCATE TABLE "${tablename}" CASCADE`
    );
  }
}

}

async seedFixtures() { // Seed test data await this.prisma.user.create({ data: { email: "test@example.com", name: "Test User", }, }); } }

API Integration Tests

// tests/api/users.test.ts import request from "supertest"; import { app } from "@/app"; import { TestHarness } from "../setup/test-harness";

describe("User API", () => { let harness: TestHarness;

beforeAll(async () => { harness = new TestHarness(); await harness.setup(); });

afterAll(async () => { await harness.teardown(); });

beforeEach(async () => { await harness.clearDatabase(); await harness.seedFixtures(); });

describe("POST /api/users", () => { it("should create new user", async () => { // Arrange const userData = { email: "new@example.com", name: "New User", };

  // Act
  const response = await request(app)
    .post("/api/users")
    .send(userData)
    .expect(201);

  // Assert
  expect(response.body).toMatchObject({
    email: userData.email,
    name: userData.name,
  });
  expect(response.body.id).toBeDefined();

  // Verify in database
  const user = await harness.prisma.user.findUnique({
    where: { email: userData.email },
  });
  expect(user).toBeDefined();
  expect(user!.name).toBe(userData.name);
});

it("should return 400 for invalid email", async () => {
  // Arrange
  const userData = {
    email: "invalid-email",
    name: "Test User",
  };

  // Act
  const response = await request(app)
    .post("/api/users")
    .send(userData)
    .expect(400);

  // Assert
  expect(response.body.error).toContain("Invalid email");
});

it("should return 409 for duplicate email", async () => {
  // Arrange
  const userData = {
    email: "test@example.com", // Already exists
    name: "Duplicate User",
  };

  // Act
  const response = await request(app)
    .post("/api/users")
    .send(userData)
    .expect(409);

  // Assert
  expect(response.body.error).toContain("already exists");
});

});

describe("GET /api/users/:id", () => { it("should get user by id", async () => { // Arrange const user = await harness.prisma.user.findFirst();

  // Act
  const response = await request(app)
    .get(`/api/users/${user!.id}`)
    .expect(200);

  // Assert
  expect(response.body).toMatchObject({
    id: user!.id,
    email: user!.email,
    name: user!.name,
  });
});

it("should return 404 for non-existent user", async () => {
  // Act
  const response = await request(app).get("/api/users/99999").expect(404);

  // Assert
  expect(response.body.error).toContain("not found");
});

});

describe("PUT /api/users/:id", () => { it("should update user", async () => { // Arrange const user = await harness.prisma.user.findFirst(); const updates = { name: "Updated Name" };

  // Act
  const response = await request(app)
    .put(`/api/users/${user!.id}`)
    .send(updates)
    .expect(200);

  // Assert
  expect(response.body.name).toBe("Updated Name");

  // Verify in database
  const updatedUser = await harness.prisma.user.findUnique({
    where: { id: user!.id },
  });
  expect(updatedUser!.name).toBe("Updated Name");
});

});

describe("DELETE /api/users/:id", () => { it("should delete user", async () => { // Arrange const user = await harness.prisma.user.findFirst();

  // Act
  await request(app).delete(`/api/users/${user!.id}`).expect(204);

  // Assert - verify deletion in database
  const deletedUser = await harness.prisma.user.findUnique({
    where: { id: user!.id },
  });
  expect(deletedUser).toBeNull();
});

}); });

Database Transaction Tests

// tests/integration/order-flow.test.ts describe("Order Flow", () => { it("should create order with items in transaction", async () => { // Arrange const user = await harness.prisma.user.findFirst(); const product = await harness.prisma.product.create({ data: { name: "Test Product", price: 99.99, stock: 10, }, });

const orderData = {
  userId: user!.id,
  items: [
    {
      productId: product.id,
      quantity: 2,
      price: product.price,
    },
  ],
};

// Act
const response = await request(app)
  .post("/api/orders")
  .send(orderData)
  .expect(201);

// Assert
const order = await harness.prisma.order.findUnique({
  where: { id: response.body.id },
  include: { items: true },
});

expect(order).toBeDefined();
expect(order!.items).toHaveLength(1);
expect(order!.items[0].quantity).toBe(2);

// Verify stock was decremented
const updatedProduct = await harness.prisma.product.findUnique({
  where: { id: product.id },
});
expect(updatedProduct!.stock).toBe(8); // 10 - 2

});

it("should rollback transaction if order creation fails", async () => { // Arrange const user = await harness.prisma.user.findFirst(); const product = await harness.prisma.product.create({ data: { name: "Test Product", price: 99.99, stock: 1, // Only 1 in stock }, });

const orderData = {
  userId: user!.id,
  items: [
    {
      productId: product.id,
      quantity: 10, // Requesting more than available
      price: product.price,
    },
  ],
};

// Act
await request(app).post("/api/orders").send(orderData).expect(400);

// Assert - verify rollback
const orders = await harness.prisma.order.findMany();
expect(orders).toHaveLength(0);

// Verify stock unchanged
const unchangedProduct = await harness.prisma.product.findUnique({
  where: { id: product.id },
});
expect(unchangedProduct!.stock).toBe(1);

}); });

Authentication Tests

// tests/integration/auth.test.ts describe("Authentication", () => { describe("POST /api/auth/login", () => { it("should login with valid credentials", async () => { // Arrange await harness.prisma.user.create({ data: { email: "auth@example.com", password: await hash("password123"), }, });

  // Act
  const response = await request(app)
    .post("/api/auth/login")
    .send({
      email: "auth@example.com",
      password: "password123",
    })
    .expect(200);

  // Assert
  expect(response.body.token).toBeDefined();
  expect(response.body.user.email).toBe("auth@example.com");
});

it("should reject invalid password", async () => {
  // Act
  const response = await request(app)
    .post("/api/auth/login")
    .send({
      email: "test@example.com",
      password: "wrong-password",
    })
    .expect(401);

  // Assert
  expect(response.body.error).toContain("Invalid credentials");
});

});

describe("Protected routes", () => { let authToken: string;

beforeEach(async () => {
  // Login to get token
  const response = await request(app).post("/api/auth/login").send({
    email: "test@example.com",
    password: "password123",
  });

  authToken = response.body.token;
});

it("should access protected route with valid token", async () => {
  await request(app)
    .get("/api/profile")
    .set("Authorization", `Bearer ${authToken}`)
    .expect(200);
});

it("should reject request without token", async () => {
  await request(app).get("/api/profile").expect(401);
});

it("should reject request with invalid token", async () => {
  await request(app)
    .get("/api/profile")
    .set("Authorization", "Bearer invalid-token")
    .expect(401);
});

}); });

Fixtures Management

// tests/fixtures/users.ts export const userFixtures = { admin: { email: "admin@example.com", name: "Admin User", role: "ADMIN", }, regularUser: { email: "user@example.com", name: "Regular User", role: "USER", }, testUser: { email: "test@example.com", name: "Test User", role: "USER", }, };

// tests/fixtures/products.ts export const productFixtures = { laptop: { name: "MacBook Pro", price: 2499.99, stock: 10, category: "Electronics", }, phone: { name: "iPhone 15", price: 999.99, stock: 50, category: "Electronics", }, };

// Usage in tests await harness.prisma.user.create({ data: userFixtures.admin, });

CI-Friendly Strategy

.github/workflows/integration-tests.yml

name: Integration Tests

on: [push, pull_request]

services: postgres: image: postgres:15 env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: test_db ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

jobs: test: runs-on: ubuntu-latest

steps:
  - uses: actions/checkout@v4

  - uses: actions/setup-node@v4
    with:
      node-version: "20"

  - run: npm ci

  - name: Run migrations
    run: npx prisma migrate deploy
    env:
      DATABASE_URL: postgresql://test:test@localhost:5432/test_db

  - name: Run integration tests
    run: npm run test:integration
    env:
      DATABASE_URL: postgresql://test:test@localhost:5432/test_db

Parallel Test Execution

// vitest.config.ts export default defineConfig({ test: { pool: "forks", poolOptions: { forks: { singleFork: false, // Run tests in parallel }, }, isolate: true, // Isolate each test file setupFiles: ["./tests/setup/global-setup.ts"], }, });

// Ensure each test file uses separate database const TEST_DB_PREFIX = "test_db_";

function getDatabaseUrl(): string { const workerId = process.env.VITEST_WORKER_ID || "1"; return postgresql://test:test@localhost:5432/${TEST_DB_PREFIX}${workerId}; }

Best Practices

  • Isolated tests: Each test can run independently

  • Clean state: Clear database between tests

  • Fast fixtures: Minimal data seeding

  • Transactions: Test rollbacks explicitly

  • Real database: Don't mock database in integration tests

  • CI-ready: Use Docker containers

  • Parallel execution: Independent test databases

Output Checklist

  • Test harness created

  • Database setup/teardown

  • Fixture management

  • API endpoint tests

  • Database transaction tests

  • Authentication tests

  • Error case coverage

  • CI workflow configured

  • Parallel execution support

  • Clear test naming

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

framer-motion-animator

No summary provided by upstream source.

Repository SourceNeeds Review
General

eslint-prettier-config

No summary provided by upstream source.

Repository SourceNeeds Review
General

postman-collection-generator

No summary provided by upstream source.

Repository SourceNeeds Review
General

nginx-config-optimizer

No summary provided by upstream source.

Repository SourceNeeds Review