الانتقال إلى المحتوى الرئيسي

title: الاختبارات description: “اختبارات الوحدات واختبارات الشامل (E2E) ومساعدات الاختبارات” icon: flask-vial

يتضمن failproofai مجموعتي اختبارات: اختبارات الوحدات (سريعة، محاكاة) واختبارات الشامل (استدعاءات عملية فرعية حقيقية).

تشغيل الاختبارات

# تشغيل جميع اختبارات الوحدات مرة واحدة
bun run test:run

# تشغيل اختبارات الوحدات في وضع المراقبة
bun run test

# تشغيل اختبارات الشامل (يتطلب إعدادًا - انظر أدناه)
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 الثنائي الفعلي كعملية فرعية، وترسل حمولة JSON إلى stdin، وتتحقق من مخرجات stdout وكود الخروج. يختبر هذا مسار التكامل الكامل الذي يستخدمه Claude Code.

الإعداد

تشغل اختبارات الشامل الملف الثنائي مباشرة من مصدر المستودع. قبل التشغيل الأول، قم بناء حزمة CJS التي تستخدمها ملفات hook المخصصة عند الاستيراد من 'failproofai':
bun build src/index.ts --outdir dist --target node --format cjs
ثم قم بتشغيل الاختبارات:
bun run test:e2e
أعد بناء dist/ في أي وقت تغير فيه API hook العام (src/hooks/custom-hooks-registry.ts أو src/hooks/policy-helpers.ts أو src/hooks/policy-types.ts).

هيكل اختبار الشامل

__tests__/e2e/
  helpers/
    hook-runner.ts      # تشغيل الملف الثنائي وإرسال حمولة JSON والتقاط كود الخروج والمخرجات والأخطاء
    fixture-env.ts      # دليل مؤقت معزول لكل اختبار مع ملفات الإعدادات
    payloads.ts         # مصانع الحمولة الدقيقة لـ Claude لكل نوع حدث
  hooks/
    builtin-policies.e2e.test.ts   # كل سياسة مدمجة مع عملية فرعية حقيقية
    custom-hooks.e2e.test.ts       # تحميل وتقييم hook المخصص
    config-scopes.e2e.test.ts      # دمج الإعدادات عبر المشروع/المحلي/العام
    policy-params.e2e.test.ts      # حقن المعاملات لكل سياسة معاملات

استخدام مساعدات الشامل

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)

كتابة اختبار شامل

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
  });
});

أشكال استجابة الشامل

القراركود الخروجstdout
PreToolUse deny0{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"..."}}
PostToolUse deny0{"hookSpecificOutput":{"additionalContext":"Blocked ... because: ..."}}
Instruct (غير Stop)0{"hookSpecificOutput":{"additionalContext":"Instruction from failproofai: ..."}}
تعليمات Stop2stdout فارغة؛ السبب في stderr
السماح0سلسلة فارغة

إعدادات Vitest

تستخدم اختبارات الشامل vitest.config.e2e.mts مع:
  • environment: "node" - لا توجد حاجة لمتغيرات المتصفح العام
  • pool: "forks" - عزل العملية الحقيقي (اختبارات تشغيل العمليات الفرعية)
  • testTimeout: 20_000 - 20 ثانية لكل اختبار (بدء الملف الثنائي وتقييم hook)
مجموعة forks مهمة: تشارك العمال المستندة إلى الخيط globalThis، وهو ما قد يتداخل مع اختبارات توليد العمليات الفرعية. تتجنب الفروع المستندة إلى العملية هذا.

CI

يجب أن تنجح عملية CI الكاملة (bun run lint && bunx tsc --noEmit && bun run test:run && bun run build) قبل الدمج. تعمل مجموعة الشامل كوظيفة CI منفصلة بالتوازي. انظر المساهمة للحصول على قائمة التحقق الكاملة قبل الدمج.