Ana içeriğe atla
failproofai iki test paketi içerir: birim testleri (hızlı, mock’lanmış) ve uçtan uca testler (gerçek alt işlem çağrıları).

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 (kurulum gerekli - aşağıya bakınız)
bun run test:e2e

# Derlenmeden tip kontrolü yap
bunx tsc --noEmit

# Lint'i çalıştır
bun run lint

Birim testleri

Birim testleri __tests__/ dizininde bulunur ve jsdom ile birlikte Vitest kullanır.
__tests__/
  hooks/
    builtin-policies.test.ts      # Her yerleşik politika için politika mantığı
    hooks-config.test.ts          # Config 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çe aktarımlar, hata işleme
    manager.test.ts               # yükleme/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

Politika 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 testler

E2E testleri gerçek failproofai ikili dosyasını alt işlem olarak çağırır, stdin’e JSON yükü aktarır ve stdout çıktısı ile çıkış kodunu doğrular. Bu, Claude Code tarafından kullanılan tam entegrasyon yolunu test eder.

Kurulum

E2E testleri, ikili dosyayı doğrudan repo kaynağından çalıştırır. İlk çalıştırmadan önce, özel kanca dosyalarının 'failproofai'’den içe aktarırken kullandığı 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 (src/hooks/custom-hooks-registry.ts, src/hooks/policy-helpers.ts veya src/hooks/policy-types.ts) dist/ dizinini yeniden derleyin.

E2E test yapısı

__tests__/e2e/
  helpers/
    hook-runner.ts      # İkili dosyayı başlat, yükü JSON olarak aktar, çıkış kodu + stdout + stderr'ı yakala
    fixture-env.ts      # Config dosyaları ile test başına izole edilmiş temp dizinler
    payloads.ts         # Her olay türü için Claude'a uygun yükü üreten fabrikalar
  hooks/
    builtin-policies.e2e.test.ts   # Her yerleşik politika gerçek alt işlem ile
    custom-hooks.e2e.test.ts       # Özel kanca yükleme ve değerlendirmesi
    config-scopes.e2e.test.ts      # Proje/yerel/küresel arasında config birleştirme
    policy-params.e2e.test.ts      # Parametreli her politika 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    - temp dizin; .failproofai/policies-config.json almak için payload.cwd olarak geçir
// env.home   - izole ev dizini; gerçek ~/.failproofai sızıntısı olmaz

env.writeConfig({
  enabledPolicies: ["block-sudo"],
  policyParams: {
    "block-sudo": { allowPatterns: ["sudo systemctl status"] },
  },
});
createFixtureEnv() afterEach temizliğini otomatik olarak kaydeder. runHook - ikili dosyayı ç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ü üreten 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 test 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 komutunu 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("tekrarsız 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: ..."}}
Talimat (Stop dışı)0{"hookSpecificOutput":{"additionalContext":"Instruction from failproofai: ..."}}
Stop talimatı2boş stdout; sebep stderr’de
İzin ver0boş dize

Vitest config

E2E testleri vitest.config.e2e.mts dosyasını şu ayarlarla kullanır:
  • environment: "node" - tarayıcı küresellerine gerek yok
  • pool: "forks" - gerçek işlem izolasyonu (testler alt işlemleri başlatır)
  • testTimeout: 20_000 - test başına 20sn (ikili başlatma + kanca değerlendirmesi)
forks havuzu önemlidir: thread tabanlı çalışanlar globalThis paylaşır ve bu alt işlemleri başlatan testlerde müdahale edebilir. İşlem tabanlı fork’lar 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şi olarak çalışır. Tam ön birleştirme kontrol listesi için Katkıda Bulunma bölümüne bakınız.