मुख्य सामग्री पर जाएं
failproofai के दो परीक्षण सेट हैं: यूनिट परीक्षण (तेज़, मॉक किए गए) और end-to-end परीक्षण (असली subprocess आमंत्रण)।

परीक्षण चलाना

# सभी यूनिट परीक्षण एक बार चलाएं
bun run test:run

# यूनिट परीक्षण watch मोड में चलाएं
bun run test

# E2E परीक्षण चलाएं (सेटअप आवश्यक है - नीचे देखें)
bun run test:e2e

# प्रकार की जांच बिना बिल्ड किए
bunx tsc --noEmit

# लिंट करें
bun run lint

यूनिट परीक्षण

यूनिट परीक्षण __tests__/ में रहते हैं और Vitest के साथ jsdom का उपयोग करते हैं।
__tests__/
  hooks/
    builtin-policies.test.ts      # प्रत्येक बिल्ट-इन के लिए नीति तर्क
    hooks-config.test.ts          # कॉन्फ़िग लोडिंग और स्कोप मर्ज करना
    policy-evaluator.test.ts      # पैरामीटर इंजेक्शन और मूल्यांकन क्रम
    custom-hooks-registry.test.ts # globalThis रजिस्ट्री add/get/clear
    custom-hooks-loader.test.ts   # ESM लोडर, transitive आयात, त्रुटि हैंडलिंग
    manager.test.ts               # install/remove/list संचालन
  components/
    sessions-list.test.tsx        # सत्र सूची घटक
    project-list.test.tsx         # प्रोजेक्ट सूची घटक
    ...
  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

नीति यूनिट परीक्षण लिखना

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("denies sudo commands", () => {
    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("allows non-sudo commands", () => {
    const ctx = {
      eventType: "PreToolUse" as const,
      payload: {},
      toolName: "Bash",
      toolInput: { command: "ls -la" },
      params: { allowPatterns: [] },
    };
    expect(policy.fn(ctx)).toEqual(allow());
  });

  it("allows patterns in allowPatterns", () => {
    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());
  });
});

End-to-end परीक्षण

E2E परीक्षण असली failproofai बाइनरी को subprocess के रूप में आमंत्रित करते हैं, JSON पेलोड को stdin में पाइप करते हैं, और stdout आउटपुट और exit कोड पर assertion करते हैं। यह उस संपूर्ण एकीकरण पथ को परीक्षण करता है जो Claude Code उपयोग करता है।

सेटअप

E2E परीक्षण रेपो स्रोत से सीधे बाइनरी चलाते हैं। पहली बार चलाने से पहले, CJS बंडल बनाएं जो कस्टम हुक फाइलें 'failproofai' से आयात करते समय उपयोग करती हैं:
bun build src/index.ts --outdir dist --target node --format cjs
फिर परीक्षण चलाएं:
bun run test:e2e
जब भी आप सार्वजनिक हुक API (src/hooks/custom-hooks-registry.ts, src/hooks/policy-helpers.ts, या src/hooks/policy-types.ts) को परिवर्तित करें, dist/ को पुनः बनाएं।

E2E परीक्षण संरचना

__tests__/e2e/
  helpers/
    hook-runner.ts      # बाइनरी spawn करें, पेलोड JSON पाइप करें, exit कोड + stdout + stderr कैप्चर करें
    fixture-env.ts      # प्रति-परीक्षण अलग अस्थायी निर्देशिकाएं कॉन्फ़िग फाइलों के साथ
    payloads.ts         # प्रत्येक इवेंट प्रकार के लिए Claude-सटीक पेलोड फैक्ट्रीज
  hooks/
    builtin-policies.e2e.test.ts   # असली subprocess के साथ प्रत्येक बिल्ट-इन नीति
    custom-hooks.e2e.test.ts       # कस्टम हुक लोडिंग और मूल्यांकन
    config-scopes.e2e.test.ts      # project/local/global में कॉन्फ़िग मर्ज करना
    policy-params.e2e.test.ts      # प्रत्येक पैरामीटरयुक्त नीति के लिए पैरामीटर इंजेक्शन

E2E सहायकों का उपयोग करना

FixtureEnv - प्रति-परीक्षण अलग पर्यावरण:
import { createFixtureEnv } from "../helpers/fixture-env";

const env = createFixtureEnv();
// env.cwd    - अस्थायी निर्देशिका; .failproofai/policies-config.json उठाने के लिए payload.cwd के रूप में पास करें
// env.home   - अलग होम निर्देशिका; कोई वास्तविक ~/.failproofai लीक नहीं होता

env.writeConfig({
  enabledPolicies: ["block-sudo"],
  policyParams: {
    "block-sudo": { allowPatterns: ["sudo systemctl status"] },
  },
});
createFixtureEnv() स्वचालित रूप से afterEach क्लीनअप पंजीकृत करता है। runHook - बाइनरी को आमंत्रित करें:
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 - तैयार पेलोड फैक्ट्रीज:
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 परीक्षण लिखना

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("denies rm -rf", 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("allows non-recursive rm", 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("");  // allow → खाली stdout
  });
});

E2E प्रतिक्रिया आकार

निर्णयExit कोडstdout
PreToolUse deny0{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"..."}}
PostToolUse deny0{"hookSpecificOutput":{"additionalContext":"Blocked ... because: ..."}}
Instruct (non-Stop)0{"hookSpecificOutput":{"additionalContext":"Instruction from failproofai: ..."}}
Stop निर्देश2खाली stdout; stderr में कारण
Allow0खाली स्ट्रिंग

Vitest कॉन्फ़िग

E2E परीक्षण vitest.config.e2e.mts का उपयोग करते हैं जिसमें है:
  • environment: "node" - कोई ब्राउज़र globals आवश्यक नहीं
  • pool: "forks" - सच्चा प्रक्रिया अलगाव (परीक्षण subprocesses spawn करते हैं)
  • testTimeout: 20_000 - प्रति परीक्षण 20s (बाइनरी स्टार्टअप + हुक eval)
forks पूल महत्वपूर्ण है: thread-आधारित workers globalThis साझा करते हैं, जो subprocess-spawning परीक्षणों में हस्तक्षेप कर सकता है। Process-आधारित forks इससे बचते हैं।

CI

संपूर्ण CI चलाव (bun run lint && bunx tsc --noEmit && bun run test:run && bun run build) को मर्ज करने से पहले पास होना आवश्यक है। E2E सेट एक अलग CI कार्य के रूप में समानांतर में चलता है। पूर्ण प्री-मर्ज चेकलिस्ट के लिए Contributing देखें।