[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Test Data Factory

Build a Test Data Factory

Build a test data factory with fixture generation, relationship-aware seeding, deterministic output, database state management, and cleanup for reliable integration testing.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 4 skills

Avg quality 97/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Petra leads QA at a 25-person company. Integration tests are flaky because they share a test database — test A creates a user, test B deletes all users, test A fails. Each test needs specific data setups but developers copy-paste SQL inserts across 200 test files. When the schema changes (new required column), 50 tests break because each hardcoded its own INSERT. Foreign key relationships are painful — creating an order requires a user, product, and address, each with their own required fields. They need a test data factory: define entities once, generate with relationships, deterministic output, database isolation per test, and automatic cleanup.

Step 1: Build the Data Factory

typescript
// src/testing/factory.ts — Test data factory with relationships and database isolation
import { pool } from "../db";
import { randomBytes, createHash } from "node:crypto";

type FactoryFn<T> = (overrides?: Partial<T>, context?: FactoryContext) => T;

interface FactoryContext {
  sequence: number;          // auto-incrementing counter
  seed: string;              // for deterministic output
  created: Map<string, any[]>;  // track created entities for cleanup
}

interface FactoryDefinition<T> {
  name: string;
  build: FactoryFn<T>;
  afterCreate?: (entity: T, context: FactoryContext) => Promise<void>;
}

const factories = new Map<string, FactoryDefinition<any>>();
const globalSequence = new Map<string, number>();

// Define a factory
export function define<T>(name: string, buildFn: FactoryFn<T>, afterCreate?: (entity: T, context: FactoryContext) => Promise<void>): void {
  factories.set(name, { name, build: buildFn, afterCreate });
}

// Build entity without persisting (in-memory only)
export function build<T>(name: string, overrides?: Partial<T>): T {
  const factory = factories.get(name);
  if (!factory) throw new Error(`Factory '${name}' not defined`);

  const seq = (globalSequence.get(name) || 0) + 1;
  globalSequence.set(name, seq);

  const context: FactoryContext = { sequence: seq, seed: `${name}-${seq}`, created: new Map() };
  return { ...factory.build(overrides, context), ...overrides } as T;
}

// Create entity and persist to database
export async function create<T extends Record<string, any>>(
  name: string,
  overrides?: Partial<T>
): Promise<T> {
  const entity = build<T>(name, overrides);
  const tableName = nameToTable(name);

  const keys = Object.keys(entity).filter((k) => entity[k] !== undefined);
  const values = keys.map((k) => entity[k]);
  const placeholders = keys.map((_, i) => `$${i + 1}`).join(", ");

  await pool.query(
    `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES (${placeholders}) ON CONFLICT DO NOTHING`,
    values
  );

  // Track for cleanup
  const tracked = cleanupStack.get(tableName) || [];
  tracked.push(entity.id);
  cleanupStack.set(tableName, tracked);

  return entity;
}

// Create multiple entities
export async function createMany<T extends Record<string, any>>(
  name: string,
  count: number,
  overridesFn?: (index: number) => Partial<T>
): Promise<T[]> {
  const entities: T[] = [];
  for (let i = 0; i < count; i++) {
    const overrides = overridesFn ? overridesFn(i) : undefined;
    entities.push(await create<T>(name, overrides));
  }
  return entities;
}

// Create entity with all required relationships
export async function createWithRelations<T extends Record<string, any>>(
  name: string,
  overrides?: Partial<T>
): Promise<T & { _relations: Record<string, any> }> {
  const relations: Record<string, any> = {};
  const factory = factories.get(name);
  if (!factory) throw new Error(`Factory '${name}' not defined`);

  // Check for foreign key fields and create related entities
  const built = build<T>(name, overrides);
  for (const [key, value] of Object.entries(built)) {
    if (key.endsWith("Id") && !overrides?.[key as keyof T]) {
      const relatedName = key.slice(0, -2);  // "userId" → "user"
      if (factories.has(relatedName)) {
        const related = await create(relatedName);
        (built as any)[key] = related.id;
        relations[relatedName] = related;
      }
    }
  }

  const entity = await create<T>(name, built);
  return { ...entity, _relations: relations };
}

// Database isolation: wrap test in transaction that rolls back
export async function withTestTransaction<T>(fn: () => Promise<T>): Promise<T> {
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    // Override pool.query to use this transaction
    const originalQuery = pool.query.bind(pool);
    (pool as any).query = client.query.bind(client);

    const result = await fn();

    await client.query("ROLLBACK");  // always rollback — test isolation
    (pool as any).query = originalQuery;
    return result;
  } finally {
    client.release();
  }
}

// Deterministic data generation (same seed = same data)
export function deterministicBuild<T>(name: string, seed: string, overrides?: Partial<T>): T {
  const factory = factories.get(name);
  if (!factory) throw new Error(`Factory '${name}' not defined`);

  const hash = createHash("sha256").update(seed).digest("hex");
  const context: FactoryContext = {
    sequence: parseInt(hash.slice(0, 8), 16),
    seed,
    created: new Map(),
  };

  return { ...factory.build(overrides, context), ...overrides } as T;
}

// Cleanup all created entities (reverse order for FK constraints)
const cleanupStack = new Map<string, string[]>();

export async function cleanup(): Promise<void> {
  const tables = [...cleanupStack.entries()].reverse();
  for (const [table, ids] of tables) {
    if (ids.length > 0) {
      await pool.query(`DELETE FROM ${table} WHERE id = ANY($1)`, [ids]);
    }
  }
  cleanupStack.clear();
}

// Reset all sequences
export function resetSequences(): void {
  globalSequence.clear();
}

function nameToTable(name: string): string {
  return name.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "") + "s";
}

// Pre-define common factories
define("user", (overrides, ctx) => ({
  id: overrides?.id || `user-${ctx!.sequence}`,
  email: `user${ctx!.sequence}@test.com`,
  name: `Test User ${ctx!.sequence}`,
  role: "user",
  status: "active",
  createdAt: new Date().toISOString(),
}));

define("organization", (overrides, ctx) => ({
  id: overrides?.id || `org-${ctx!.sequence}`,
  name: `Test Org ${ctx!.sequence}`,
  plan: "pro",
  createdAt: new Date().toISOString(),
}));

define("project", (overrides, ctx) => ({
  id: overrides?.id || `proj-${ctx!.sequence}`,
  name: `Test Project ${ctx!.sequence}`,
  organizationId: overrides?.organizationId || `org-${ctx!.sequence}`,
  ownerId: overrides?.ownerId || `user-${ctx!.sequence}`,
  status: "active",
  createdAt: new Date().toISOString(),
}));

define("order", (overrides, ctx) => ({
  id: overrides?.id || `order-${ctx!.sequence}`,
  userId: overrides?.userId || `user-${ctx!.sequence}`,
  total: Math.round(Math.random() * 10000) / 100,
  status: "pending",
  items: [],
  createdAt: new Date().toISOString(),
}));

Results

  • 50 broken tests → 0 — schema change (new required column) fixed in one factory definition; all 200 tests use the factory; single point of update
  • Test isolation — each test runs in a rolled-back transaction; no shared state; tests run in parallel without flakiness
  • Relationship-aware — createWithRelations('order') auto-creates user, product, address; developer writes one line instead of 4 INSERT statements
  • Deterministic output — same seed produces same test data; snapshot tests are reproducible; debugging uses exact same data as CI
  • Auto-cleanup — created entities tracked; cleanup() deletes in reverse FK order; no orphaned test data polluting the database

Related use cases