Claude
Skills
Sign in
Back

build-tools-tests

Included with Lifetime
$97 forever

Build integration tests for MCP tool collections. Reads .discover.json and creates test setup, builders, helpers, and test files per collection. Use after running '/build-tools'.

AI Agents

What this skill does


# Build Tests

Generate integration tests for MCP tool collections created by `/build-tools`. This skill reads `.discover.json` and the existing tool files, then builds test infrastructure and test files one collection at a time.

**IMPORTANT: This skill ONLY creates files inside `__tests__/` directories — test files, setup, builders, and helpers. Do NOT create or modify ANY other files. This means: no tool files, no collection indexes, no registrations, no API client files (`src/umbraco-api/api/`), no generated code (`src/umbraco-api/api/generated/`), no mock handlers (`src/mocks/`). If existing code doesn't support what you need, work within the constraints — do not modify it.**

## Prerequisites

Before running, ensure:
1. You have run `/build-tools` (tool collections exist in `src/umbraco-api/tools/`)
2. The project compiles: `npm run compile`
3. The Umbraco instance is running
4. An API user exists — remind the user: **"You need to create an API user via the Umbraco backoffice UI: Settings > Users, with Client ID `umbraco-back-office-mcp` and Client Secret `1234567890`"**

## Arguments

- No arguments: build tests for all collections from `.discover.json`
- Single collection name: build tests only for that collection (e.g. `/build-tools-tests form`)

## Agents

This skill orchestrates the following agents — use them for the relevant steps:

| Agent | When to use |
|-------|-------------|
| `test-builder-helper-creator` | Creating builders and helpers (Steps 4-5) |
| `integration-test-creator` | Creating test files (Step 7) |
| `integration-test-validator` | Validating test quality (Step 8) |

## Critical Rules

**ONE FILE AT A TIME.** This applies to ALL files — builders, helpers, builder tests, and tool tests. After creating any file:
1. Compile: `npm run compile`
2. If it has tests, run them: `npm test -- path/to/file.test.ts`
3. Fix any failures
4. Only then create the next file

**RUN COMMANDS SEPARATELY.** Always run compile and test as separate Bash calls. Never chain them with `&&`.

**NEVER:**
- Create multiple files at once
- Move on while a compile error or test failure exists
- Skip the compile step
- Assume anything works without running it
- Chain commands with `&&` (run each command separately)

**ONE TEST FILE PER TOOL.** Every tool gets its own test file. Never combine tests for multiple tools into one file.

**SNAPSHOT TESTING PREFERRED.** Use `createSnapshotResult` from `@umbraco-cms/mcp-server-sdk/testing` with `toMatchSnapshot()` for success responses. Only use assertion testing (`expect(x).toBe(y)`) for error cases where `isError` is checked.

**REAL API — NO MOCKING.** These are integration tests that run against a real Umbraco instance. Do NOT set `USE_MOCK_API`. Do NOT create, modify, or reference anything in `src/mocks/`. Do NOT import `server` from mocks. Do NOT add MSW handlers. Do NOT use any mocking framework. The tests call tool handlers directly and those handlers call the real API. If a test fails, the fix is in the test or the tool — never add mock infrastructure.

## Umbraco Instance Management

If testing hits roadblocks — builders can't create data, APIs reject requests due to missing configuration, or features aren't available — you are able to manipulate the Umbraco instance to your needs. You can add connection strings, change settings, install packages, or even write C# code in `demo-site/`. **Read `instance-management.md` in this skill directory for the full process and concrete examples.**

## Workflow

Process **one collection at a time**. Complete each collection fully before starting the next.

### Step 0: Read Discovery Manifest

Read `.discover.json` from the project root:

```json
{
  "apiName": "Umbraco Forms Management API",
  "swaggerUrl": "https://localhost:44324/umbraco/swagger/forms-management/swagger.json",
  "baseUrl": "https://localhost:44324",
  "collections": ["form", "form-template", "field-type", "folder"]
}
```

If an argument was provided, filter to only that collection. If `.discover.json` doesn't exist, tell the user to run `npx @umbraco-cms/create-umbraco-mcp-server discover` first.

### Step 1: Understand Existing Tools

For each collection, read:
- `src/umbraco-api/tools/{collection}/index.ts` — to get the list of tools
- Each tool file — to understand input schemas, handler logic, and entity names
- `src/umbraco-api/api/generated/` — to identify the API client function and Zod schemas

If `src/umbraco-api/tools/{collection}/index.ts` doesn't exist, skip — tell the user to run `/build-tools` first.

### Step 2: Check for Existing Tests

**Skip if `src/umbraco-api/tools/{collection}/__tests__/setup.ts` already exists** — tests have already been created for this collection.

### Step 3: Per Collection — Create Test Setup

Create `src/umbraco-api/tools/{collection}/__tests__/setup.ts`:

```typescript
import {
  setupTestEnvironment,
  createMockRequestHandlerExtra,
  createSnapshotResult,
} from "@umbraco-cms/mcp-server-sdk/testing";
import { configureApiClient, initializeUmbracoFetch } from "@umbraco-cms/mcp-server-sdk";
import { getYourAPI } from "../../../../api/generated/yourApi.js";
import { EntityBuilder } from "./helpers/{entity}-builder.js";
import { EntityTestHelper } from "./helpers/{entity}-test-helper.js";

// Initialize fetch with credentials — required for integration tests hitting the real API
initializeUmbracoFetch({
  baseUrl: process.env.UMBRACO_BASE_URL!,
  clientId: process.env.UMBRACO_CLIENT_ID!,
  clientSecret: process.env.UMBRACO_CLIENT_SECRET!,
});

configureApiClient(() => getYourAPI());

export {
  setupTestEnvironment,
  createMockRequestHandlerExtra,
  createSnapshotResult,
  EntityBuilder,
  EntityTestHelper,
};
```

**Key rules:**
- Import the correct API client getter from `src/umbraco-api/api/generated/`
- `initializeUmbracoFetch` MUST be called before `configureApiClient` — it sets up the authenticated fetch layer
- Do NOT set `USE_MOCK_API` — these tests run against the real Umbraco instance
- Export `createSnapshotResult` for snapshot testing
- Re-export builders and helpers so test files have a single import

**Compile after creating:** `npm run compile`. Fix errors before continuing.

**Read-only collections:** If the collection has no create or delete operations (e.g. analytics — only GET/query tools), skip steps 4-6 (builder, helper, builder tests). These steps create test data lifecycle management which isn't needed for read-only collections. Proceed directly to step 7 (integration tests).

### Step 4: Per Collection — Create Test Builder

Use the `test-builder-helper-creator` agent.

Create `src/umbraco-api/tools/{collection}/__tests__/helpers/{entity}-builder.ts`:

```typescript
import { getYourAPI } from "../../../../api/generated/yourApi.js";
import { CAPTURE_RAW_HTTP_RESPONSE } from "@umbraco-cms/mcp-server-sdk";

const TEST_ENTITY_NAME = "_Test Entity";

interface EntityModel {
  name: string;
  // ... fields matching the POST body schema from the generated *.zod.ts
}

export class EntityBuilder {
  private model: EntityModel = {
    name: TEST_ENTITY_NAME,
  };

  private createdId?: string;

  withName(name: string): this {
    this.model.name = name;
    return this;
  }

  build(): EntityModel {
    return { ...this.model };
  }

  async create(): Promise<this> {
    const client = getYourAPI();
    // Call the API client's POST method directly (NOT the tool handler)
    const response: any = await client.postEntity(
      this.model as any,
      CAPTURE_RAW_HTTP_RESPONSE,
    );

    if (response.status !== 201) {
      const errorBody = await response.data?.detail || `HTTP ${response.status}`;
      throw new Error(`Failed to create entity: ${errorBody}`);
    }

    // Extract ID from Location header (Umbraco convention: /api/v1/entity/{id})
    const location = response.headers?.get?.("location") || response.headers?.location;
    this.createdId = location?.split("/").pop();

    return this;
  }

  async delete(): Promise<v

Related in AI Agents