/archestra-dev-backend-tests
Use when writing or modifying Archestra backend unit tests (platform/backend/src/**/*.test.ts) — mocking modules, stubbing globals, database fixtures, vitest projects/isolation, or test performance.
$ npx -y skills add archestra-ai/archestra --skill archestra-dev-backend-tests --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/archestra-dev-backend-tests
Context preview
The summary Claude sees to decide when to auto-load this skill.
Use when writing or modifying Archestra backend unit tests (platform/backend/src/**/*.test.ts) — mocking modules, stubbing globals, database fixtures, vitest projects/isolation, or test performance.
SKILL.md
archestra-dev-backend-tests.SKILL.mdname: archestra-dev-backend-tests
description: Use when writing or modifying Archestra backend unit tests (platform/backend/src/**/*.test.ts) — mocking modules, stubbing globals, database fixtures, vitest projects/isolation, or test performance.
Archestra Backend Unit Tests
Run commands from `platform/` unless specifically instructed otherwise. Run a single file with `cd backend && npx vitest run <file>`.
The two vitest projects (why mocking style matters)
`backend/vitest.config.ts` splits test files into two projects at config-load time by grepping file content:
- **`clean`** — files with NO `vi.mock`/`vi.doMock`/`vi.hoisted` run with `isolate: false`: worker threads share the module cache, so the backend module graph is imported once per worker instead of once per file. This is the fast path.
- **`mocked`** — files using module mocking keep full isolation, because Vitest never resets the module-mock registry between files in a shared worker (vitest-dev/vitest#4894).
Consequences:
- **Prefer not mocking modules at all.** Every file that drops its last `vi.mock` automatically joins the fast project. Mock at the process boundary instead (fetch, network) when possible.
- Routing is automatic — never maintain a file list; adding `vi.mock` to a file safely moves it to the isolated project on the next run.
HTTP boundary mocking (instead of vi.mock on client libraries)
Never `vi.mock` an HTTP client library (`jira.js`, `@gitbeaker/rest`, `openai`, ...). Use MSW via the `useMswServer` helper — the real client runs and only the network is faked (MSW intercepts axios, fetch, and undici alike):
import { http, HttpResponse } from "msw";
import { useMswServer } from "@/test/msw";
const server = useMswServer();
test("...", async () => {
server.use(
http.get("https://example.atlassian.net/rest/api/3/search/jql", () =>
HttpResponse.json({ issues: [] }),
),
);
});Unhandled requests fail the test loudly. The helper's lifecycle is per test (it must be — the shared setup restores `globalThis.fetch` after every test); don't hand-roll `setupServer` with `beforeAll` listen.
Wire-level gotchas learned in past conversions: clients retry — gitbeaker retries 429/502 up to 10× with backoff and openai retries 429/5xx (serve a non-retried status like 500, or account for the retries); MSW 2.x route paths use path-to-regexp 8, which rejects RegExp paths and bare `*` — use `:param` segments (URL-encoded slashes like `%2F` stay one segment and decode in the param).
Module mocking rules
- **`@/auth`**: activate with a bare `vi.mock("@/auth");` — Vitest resolves the Jest-style `src/auth/__mocks__/index.ts`, which re-exports the canonical factory (`src/test/mocks/auth.ts`: every export a bare `vi.fn()`). Configure behavior per test via `vi.mocked(...)`:
vi.mock("@/auth");
import { hasPermission } from "@/auth";
beforeEach(() => {
vi.mocked(hasPermission).mockResolvedValue({ success: true, error: null });
});- **`@/auth/utils`**: bare `vi.mock("@/auth/utils");` (resolves `src/auth/__mocks__/utils.ts`). Needed separately from `@/auth` when the code under test imports from "@/auth/utils" directly — module mocks match specifiers, not re-exports.
- **`@/observability`**: bare `vi.mock("@/observability");` (resolves `src/observability/__mocks__/index.ts`). `metrics`/`tracing` are memoized proxy trees — any `metrics.<ns>.<fn>` access yields a stable `vi.fn()`, so assert via `vi.mocked(metrics.llm.someFn)` with no factory.
- **`@/cache-manager`**: bare `vi.mock("@/cache-manager");` (resolves `src/__mocks__/cache-manager.ts`) — a Map-backed `cacheManager` fake with real cache semantics that needs no `start()`, auto-reset before every test; `CacheKey` and `LRUCacheManager` stay real. Tests needing a cache MISS mid-test call `await cacheManager.delete(key)`.
- **`@/logging`**: do NOT mock just to silence output — the shared setup already runs the real logger at level `silent`. Only mock when a test asserts on logger calls, with a bare `vi.mock("@/logging");` (resolves `src/logging/__mocks__/index.ts`). `vi.spyOn(logger, ...)` does not work — the export is a Proxy binding methods to a private pino instance.
- **`@/config`**: use the canonical deep-merge factory so unspecified keys keep their real values:
vi.mock("@/config", async () =>
(await import("@/test/mocks/config")).configModuleMock({
kb: { taskWorkerPollIntervalSeconds: 1 },
}),
);Never hand-roll a partial `{ default: { kb: {...} } }` — it silently drops the rest of the config. And prefer no config mock at all: direct mutation (next bullet) keeps the file in the fast project; the factory is only NEEDED when the code under test reads config at module-import time (module-level `const` captures), which a runtime mutation can't reach.
- **Mutating the real config** (`config.skillsSandbox.enabled = true` style, common in clean-project files): set it in `beforeEach` or inside the test, NOT in `beforeAll` or at module scope — the shared setup restores the pristine config before AND after every test in the shared-worker project, so a once-per-file mutation evaporates after the first test. No manual restore needed.
- **Fire-and-forget async DB work in product code** (a promise launched without `await`, like `InteractionModel.create`'s usage-tracking update) must be registered with `trackBackgroundWork` from `@/utils/background-work` — the shared teardown drains the registry before swapping out the file's PGlite. An untracked background promise that outlives its file runs its remaining queries against the NEXT file's database and can wedge it (a batch of consecutive 30s timeouts).
- **Never write a bespoke partial factory** for a module that has a canonical mock, and **never mix a bare `vi.mock("x")` with a factory `vi.mock("x", ...)` for the same specifier across files** — Vitest can silently skip one depending on execution order (vitest-dev/vitest#10145).
-
Read more
name: archestra-dev-backend-tests description: Use when writing or modifying Archestra backend unit tests (platform/backend/src/**/*.test.ts) — mocking modules, stubbing globals, database fixtures, vitest projects/isolation, or test performance.
Archestra Backend Unit Tests
Run commands from `platform/` unless specifically instructed otherwise. Run a single file with `cd backend && npx vitest run <file>`.
The two vitest projects (why mocking style matters)
`backend/vitest.config.ts` splits test files into two projects at config-load time by grepping file content:
- **`clean`** — files with NO `vi.mock`/`vi.doMock`/`vi.hoisted` run with `isolate: false`: worker threads share the module cache, so the backend module graph is imported once per worker instead of once per file. This is the fast path.
- **`mocked`** — files using module mocking keep full isolation, because Vitest never resets the module-mock registry between files in a shared worker (vitest-dev/vitest#4894).
Consequences:
- **Prefer not mocking modules at all.** Every file that drops its last `vi.mock` automatically joins the fast project. Mock at the process boundary instead (fetch, network) when possible.
- Routing is automatic — never maintain a file list; adding `vi.mock` to a file safely moves it to the isolated project on the next run.
HTTP boundary mocking (instead of vi.mock on client libraries)
Never `vi.mock` an HTTP client library (`jira.js`, `@gitbeaker/rest`, `openai`, ...). Use MSW via the `useMswServer` helper — the real client runs and only the network is faked (MSW intercepts axios, fetch, and undici alike):
import { http, HttpResponse } from "msw";
import { useMswServer } from "@/test/msw";
const server = useMswServer();
test("...", async () => {
server.use(
http.get("https://example.atlassian.net/rest/api/3/search/jql", () =>
HttpResponse.json({ issues: [] }),
),
);
});Unhandled requests fail the test loudly. The helper's lifecycle is per test (it must be — the shared setup restores `globalThis.fetch` after every test); don't hand-roll `setupServer` with `beforeAll` listen.
Wire-level gotchas learned in past conversions: clients retry — gitbeaker retries 429/502 up to 10× with backoff and openai retries 429/5xx (serve a non-retried status like 500, or account for the retries); MSW 2.x route paths use path-to-regexp 8, which rejects RegExp paths and bare `*` — use `:param` segments (URL-encoded slashes like `%2F` stay one segment and decode in the param).
Module mocking rules
- **`@/auth`**: activate with a bare `vi.mock("@/auth");` — Vitest resolves the Jest-style `src/auth/__mocks__/index.ts`, which re-exports the canonical factory (`src/test/mocks/auth.ts`: every export a bare `vi.fn()`). Configure behavior per test via `vi.mocked(...)`:
vi.mock("@/auth");
import { hasPermission } from "@/auth";
beforeEach(() => {
vi.mocked(hasPermission).mockResolvedValue({ success: true, error: null });
});- **`@/auth/utils`**: bare `vi.mock("@/auth/utils");` (resolves `src/auth/__mocks__/utils.ts`). Needed separately from `@/auth` when the code under test imports from "@/auth/utils" directly — module mocks match specifiers, not re-exports.
- **`@/observability`**: bare `vi.mock("@/observability");` (resolves `src/observability/__mocks__/index.ts`). `metrics`/`tracing` are memoized proxy trees — any `metrics.<ns>.<fn>` access yields a stable `vi.fn()`, so assert via `vi.mocked(metrics.llm.someFn)` with no factory.
- **`@/cache-manager`**: bare `vi.mock("@/cache-manager");` (resolves `src/__mocks__/cache-manager.ts`) — a Map-backed `cacheManager` fake with real cache semantics that needs no `start()`, auto-reset before every test; `CacheKey` and `LRUCacheManager` stay real. Tests needing a cache MISS mid-test call `await cacheManager.delete(key)`.
- **`@/logging`**: do NOT mock just to silence output — the shared setup already runs the real logger at level `silent`. Only mock when a test asserts on logger calls, with a bare `vi.mock("@/logging");` (resolves `src/logging/__mocks__/index.ts`). `vi.spyOn(logger, ...)` does not work — the export is a Proxy binding methods to a private pino instance.
- **`@/config`**: use the canonical deep-merge factory so unspecified keys keep their real values:
vi.mock("@/config", async () =>
(await import("@/test/mocks/config")).configModuleMock({
kb: { taskWorkerPollIntervalSeconds: 1 },
}),
);Never hand-roll a partial `{ default: { kb: {...} } }` — it silently drops the rest of the config. And prefer no config mock at all: direct mutation (next bullet) keeps the file in the fast project; the factory is only NEEDED when the code under test reads config at module-import time (module-level `const` captures), which a runtime mutation can't reach.
- **Mutating the real config** (`config.skillsSandbox.enabled = true` style, common in clean-project files): set it in `beforeEach` or inside the test, NOT in `beforeAll` or at module scope — the shared setup restores the pristine config before AND after every test in the shared-worker project, so a once-per-file mutation evaporates after the first test. No manual restore needed.
- **Fire-and-forget async DB work in product code** (a promise launched without `await`, like `InteractionModel.create`'s usage-tracking update) must be registered with `trackBackgroundWork` from `@/utils/background-work` — the shared teardown drains the registry before swapping out the file's PGlite. An untracked background promise that outlives its file runs its remaining queries against the NEXT file's database and can wedge it (a batch of consecutive 30s timeouts).
- **Never write a bespoke partial factory** for a module that has a canonical mock, and **never mix a bare `vi.mock("x")` with a factory `vi.mock("x", ...)` for the same specifier across files** — Vitest can silently skip one depending on execution order (vitest-dev/vitest#10145).
-
Enterprise AI Platform with guardrails, MCP registry, gateway & orchestrator
Repo: archestra-ai/archestra
Other skills on archestra.
- /archestra-dev-backend
Use when adding or changing Archestra backend routes, models, services, API request/response schemas, endpoint permissions, or OpenAPI/codegen for the generated API client.
Open skill - /archestra-dev-bench-analysis
Map-reduce a finished archestra-bench run into a Tier-1/Tier-2 improvement report using Claude subagents (same analysis as the Rust analyzer, no API key).
Open skill - /archestra-dev-e2e
Use when writing, debugging, or running Archestra Playwright e2e tests, API/UI fixtures, WireMock-backed tests, local/CI e2e setup, or test selectors.
Open skill - /archestra-dev-frontend
Use when modifying Archestra frontend Next.js/React code, UI components, forms, TanStack Query hooks, generated API client usage, frontend copy, or documentation links.
Open skill - /archestra-dev-interactions-migrations
Use BEFORE writing or running any Drizzle migration that touches the `interactions` table (or any other very large, write-hot table). The interactions table is the platform's biggest, append-heavy table — every LLM proxy call writes a row — so a careless migration can take a
Open skill - /archestra-dev-investigate
Use when investigating Archestra bugs or incidents — staging issues, backend 50x errors, Drizzle failed queries, DB connection pressure, deploy regressions, or Kubernetes/runtime symptoms. Orientation only; defers the process to /investigate.
Open skill

