Ana içeriğe atla
failproofai iki test paketi içerir: birim testleri (hızlı, simüle edilmiş) ve uçtan uca testleri (gerçek alt işlem çağırmaları).

Testleri çalıştırma

# Tüm birim testlerini bir kez çalıştır
bun run test:run

# Birim testlerini izleme modunda çalıştır
bun run test

# E2E testlerini çalıştır (setup gerektirir - aşağıya bakın)
bun run test:e2e

# Derlemeden tip kontrolü yap
bunx tsc --noEmit

# Lint
bun run lint

Birim testleri

Birim testleri __tests__/ içinde bulunur ve Vitest ile happy-dom kullanır.
__tests__/
  hooks/
    builtin-policies.test.ts      # Her yerleşik ilke için ilke mantığı
    hooks-config.test.ts          # Yapılandırma yükleme ve kapsam birleştirme
    policy-evaluator.test.ts      # Parametre enjeksiyonu ve değerlendirme sırası
    custom-hooks-registry.test.ts # globalThis kayıt defteri ekleme/alma/temizleme
    custom-hooks-loader.test.ts   # ESM yükleyici, geçişli içeri aktarmalar, hata işleme
    manager.test.ts               # kurulum/kaldırma/listeleme işlemleri
  components/
    sessions-list.test.tsx        # Oturum listesi bileşeni
    project-list.test.tsx         # Proje listesi bileşeni
    ...
  lib/
    logger.test.ts
    paths.test.ts
    date-filters.test.ts
    telemetry.test.ts
    ...
  actions/
    get-hooks-config.test.ts
    get-hook-activity.test.ts
    ...
  contexts/
    ThemeContext.test.tsx
    AutoRefreshContext.test.tsx

İlke birim testi yazma

import { describe, it, expect, beforeEach } from "vitest";
import { getBuiltinPolicies } from "../../src/hooks/builtin-policies";
import { allow, deny } from "../../src/hooks/policy-types";

describe("block-sudo", () => {
  const policy = getBuiltinPolicies().find((p) => p.name === "block-sudo")!;

  it("sudo komutlarını reddeder", () => {
    const ctx = {
      eventType: "PreToolUse" as const,
      payload: {},
      toolName: "Bash",
      toolInput: { command: "sudo apt install nodejs" },
      params: { allowPatterns: [] },
    };
    expect(policy.fn(ctx)).toEqual(deny("sudo command blocked by failproofai"));
  });

  it("sudo olmayan komutlara izin verir", () => {
    const ctx = {
      eventType: "PreToolUse" as const,
      payload: {},
      toolName: "Bash",
      toolInput: { command: "ls -la" },
      params: { allowPatterns: [] },
    };
    expect(policy.fn(ctx)).toEqual(allow());
  });

  it("allowPatterns içindeki desenlere izin verir", () => {
    const ctx = {
      eventType: "PreToolUse" as const,
      payload: {},
      toolName: "Bash",
      toolInput: { command: "sudo systemctl status nginx" },
      params: { allowPatterns: ["sudo systemctl status"] },
    };
    expect(policy.fn(ctx)).toEqual(allow());
  });
});

Uçtan uca testleri

E2E testleri gerçek failproofai ikilisini alt işlem olarak çağırır, JSON yükünü stdin’e aktarır ve stdout çıkışı ile çıkış kodu üzerinde doğrulamalar yapar. Bu, Claude Code’un kullandığı tam entegrasyon yolunu test eder.

Setup

E2E testleri doğrudan depo kaynağından ikiliyi çalıştırır. İlk çalıştırmadan önce, özel kanca dosyalarının 'failproofai'’den içeri aktarırken kullanacağı CJS paketini oluşturun:
bun build src/index.ts --outdir dist --target node --format cjs
Ardından testleri çalıştırın:
bun run test:e2e
Genel kanca API’sini değiştirdiğinizde dist/ öğesini yeniden oluşturun (src/hooks/custom-hooks-registry.ts, src/hooks/policy-helpers.ts veya src/hooks/policy-types.ts).

E2E test yapısı

__tests__/e2e/
  helpers/
    hook-runner.ts      # İkiliyi oluştur, yük JSON'unu aktar, çıkış kodu + stdout + stderr'ı yakala
    fixture-env.ts      # Test başına izole edilen geçici dizinler yapılandırma dosyalarıyla
    payloads.ts         # Her olay türü için Claude'a uygun yük fabrikaları
  hooks/
    builtin-policies.e2e.test.ts   # Her yerleşik ilke gerçek alt işlemle
    custom-hooks.e2e.test.ts       # Özel kanca yükleme ve değerlendirmesi
    config-scopes.e2e.test.ts      # Proje/yerel/genel arasında yapılandırma birleştirme
    policy-params.e2e.test.ts      # Her parametreli ilke için parametre enjeksiyonu

E2E yardımcılarını kullanma

FixtureEnv - test başına izole ortam:
import { createFixtureEnv } from "../helpers/fixture-env";

const env = createFixtureEnv();
// env.cwd    - geçici dizin; .failproofai/policies-config.json'ı almak için payload.cwd olarak geçir
// env.home   - izole ev dizini; gerçek ~/.failproofai sızıntıya uğramaz

env.writeConfig({
  enabledPolicies: ["block-sudo"],
  policyParams: {
    "block-sudo": { allowPatterns: ["sudo systemctl status"] },
  },
});
createFixtureEnv() afterEach temizlemesini otomatik olarak kaydeder. runHook - ikiliyi çağır:
import { runHook } from "../helpers/hook-runner";
import { Payloads } from "../helpers/payloads";

const result = await runHook(
  "PreToolUse",
  Payloads.preToolUse.bash("sudo apt install nodejs", env.cwd),
  { homeDir: env.home }
);

expect(result.exitCode).toBe(0);
expect(result.parsed?.hookSpecificOutput?.permissionDecision).toBe("deny");
Payloads - hazır yük fabrikaları:
Payloads.preToolUse.bash(command, cwd)
Payloads.preToolUse.write(filePath, content, cwd)
Payloads.preToolUse.read(filePath, cwd)
Payloads.postToolUse.bash(command, output, cwd)
Payloads.postToolUse.read(filePath, content, cwd)
Payloads.notification(message, cwd)
Payloads.stop(cwd)

E2E testi yazma

import { describe, it, expect } from "vitest";
import { createFixtureEnv } from "../helpers/fixture-env";
import { runHook } from "../helpers/hook-runner";
import { Payloads } from "../helpers/payloads";

describe("block-rm-rf (E2E)", () => {
  it("rm -rf'yi reddeder", async () => {
    const env = createFixtureEnv();
    env.writeConfig({ enabledPolicies: ["block-rm-rf"] });

    const result = await runHook(
      "PreToolUse",
      Payloads.preToolUse.bash("rm -rf /", env.cwd),
      { homeDir: env.home }
    );

    expect(result.exitCode).toBe(0);
    expect(result.parsed?.hookSpecificOutput?.permissionDecision).toBe("deny");
  });

  it("özyinelemeli olmayan rm'ye izin verir", async () => {
    const env = createFixtureEnv();
    env.writeConfig({ enabledPolicies: ["block-rm-rf"] });

    const result = await runHook(
      "PreToolUse",
      Payloads.preToolUse.bash("rm /tmp/file.txt", env.cwd),
      { homeDir: env.home }
    );

    expect(result.exitCode).toBe(0);
    expect(result.stdout).toBe("");  // izin ver → boş stdout
  });
});

E2E yanıt şekilleri

KararÇıkış kodustdout
PreToolUse reddet0{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"..."}}
PostToolUse reddet0{"hookSpecificOutput":{"additionalContext":"Blocked ... because: ..."}}
Talimati (Stop olmayan)0{"hookSpecificOutput":{"additionalContext":"Instruction from failproofai: ..."}}
Stop talimatı2boş stdout; neden stderr’de
İzin ver0boş dize

Vitest yapılandırması

E2E testleri vitest.config.e2e.mts kullanır:
  • environment: "node" - tarayıcı globalleri gerekli değil
  • pool: "forks" - gerçek işlem izolasyonu (testler alt işlemler oluşturur)
  • testTimeout: 20_000 - test başına 20 saniye (ikili başlatma + kanca değerlendirmesi)
forks havuzu önemlidir: iş parçacığı tabanlı işçiler globalThis paylaşırlar ve bu, alt işlem oluşturan testleri etkileyebilir. İşlem tabanlı çatallama bunu önler.

CI

Birleştirmeden önce tam CI çalıştırması (bun run lint && bunx tsc --noEmit && bun run test:run && bun run build) geçmesi gerekir. E2E paketi paralel olarak ayrı bir CI işinde çalışır. Tam ön birleştirme kontrol listesi için Contributing bölümüne bakın.