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

टेस्ट चलाना

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

# यूनिट टेस्ट वॉच मोड में चलाएं
bun run test

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

# बिना बिल्ड किए टाइप-चेक करें
bunx tsc --noEmit

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

यूनिट टेस्ट

यूनिट टेस्ट __tests__/ में रहते हैं और Vitest को happy-dom के साथ उपयोग करते हैं।
__tests__/
  hooks/
    builtin-policies.test.ts      # प्रत्येक बिल्टइन के लिए नीति तर्क
    hooks-config.test.ts          # कॉन्फिग लोडिंग और स्कोप मर्जिंग
    policy-evaluator.test.ts      # पैरम इंजेक्शन और मूल्यांकन क्रम
    custom-hooks-registry.test.ts # globalThis रजिस्ट्री जोड़ें/प्राप्त करें/साफ़ करें
    custom-hooks-loader.test.ts   # ESM लोडर, ट्रांजिटिव आयात, त्रुटि हैंडलिंग
    manager.test.ts               # इंस्टॉल/निकालें/सूची संचालन
  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());
  });
});

एंड-टू-एंड टेस्ट

E2E टेस्ट असली failproofai बाइनरी को subprocess के रूप में आह्वान करते हैं, stdin को JSON पेलोड पाइप करते हैं, और stdout आउटपुट और एक्जिट कोड पर जोर देते हैं। यह पूर्ण एकीकरण पथ का परीक्षण करता है जो 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      # बाइनरी स्पॉन करें, पेलोड JSON पाइप करें, एक्जिट कोड + 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      # प्रोजेक्ट/लोकल/ग्लोबल में कॉन्फिग मर्जिंग
    policy-params.e2e.test.ts      # प्रत्येक पैरामीटरयुक्त नीति के लिए पैरामीटर इंजेक्शन

E2E हेल्पर्स का उपयोग करना

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

const env = createFixtureEnv();
// env.cwd    - टेम्प डायरेक्टरी; payload.cwd के रूप में पास करें .failproofai/policies-config.json उठाने के लिए
// 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 → empty stdout
  });
});

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

निर्णयएक्जिट कोडstdout
PreToolUse निषेध0{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"..."}}
PostToolUse निषेध0{"hookSpecificOutput":{"additionalContext":"Blocked ... because: ..."}}
निर्देश (गैर-Stop)0{"hookSpecificOutput":{"additionalContext":"Instruction from failproofai: ..."}}
Stop निर्देश2खाली stdout; stderr में कारण
अनुमति0खाली स्ट्रिंग

Vitest कॉन्फिग

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

CI

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