Skip to content

Test capability của bạn

@agentic-ui/testing cho phép bạn test một capability như một hàm thường — không LLM, không Vue, không transport. Mỗi call vẫn chạy đủ pipeline của registry (resolve → stale check → validate → permission → confirmation → execute), nên cái gì pass ở đây sẽ hành xử y hệt trong app.

bash
pnpm add -D @agentic-ui/testing

Các ví dụ dưới đây dùng vitest, nhưng không có gì phụ thuộc vitest.

1. Gọi capability như một hàm — callCapability

ts
import { describe, expect, it } from 'vitest';
import { callCapability } from '@agentic-ui/testing';
import type { AgentCapability } from '@agentic-ui/core';

const greet: AgentCapability<{ name: string }> = {
  name: 'demo.greet',
  description: 'Greet a person by name.',
  inputSchema: {
    type: 'object',
    properties: { name: { type: 'string', minLength: 1 } },
    required: ['name'],
    additionalProperties: false,
  },
  risk: 'read',
  execute: ({ name }) => ({ message: `Hello, ${name}!` }),
};

it('greets', async () => {
  const result = await callCapability(greet, { name: 'An' });
  expect(result.status).toBe('ok');
  expect(result.structuredContent).toEqual({ message: 'Hello, An!' });
});

it('rejects bad input BEFORE execute runs', async () => {
  const result = await callCapability(greet, { name: '' });
  expect(result.status).toBe('invalid'); // schema said minLength 1
});

callCapability đăng ký definition vào một registry dùng một lần rồi trả về ToolResult. Nó không bao giờ throw cho các kết quả pipeline — hãy assert trên result.status (ok | invalid | denied | unconfirmed | unavailable | ambiguous | stale | error).

2. Test tool mutating — stub xác nhận

Một capability có risk: 'mutating' | 'destructive' bình thường sẽ chặn lại chờ con người duyệt. Trong test, stub câu trả lời:

ts
// approve → the tool runs
const ok = await callCapability(saveCap, {}, { confirm: 'approve' });
expect(ok.status).toBe('ok');

// deny → the tool NEVER executes
const denied = await callCapability(saveCap, {}, { confirm: 'deny' });
expect(denied.status).toBe('unconfirmed');

// or script it — `confirm` accepts a full ConfirmationHook
await callCapability(saveCap, {}, {
  confirm: async (req) => (req.digest === expected ? { approved: true } : { approved: false }),
});

Có một stub permission tương ứng cho permission hook (permission: falseresult.status === 'denied', hoặc truyền vào một PermissionHook đầy đủ).

3. Snapshot digest duyệt — capabilityDigest

Việc duyệt được ràng buộc với sha256(canonicalJson({ name, targetId, input })). Để assert rằng UI duyệt của bạn hiển thị đúng digest — hoặc rằng pipeline hỏi với đúng payload bạn mong đợi — hãy tính digest bằng chính thuật toán đó và quan sát request:

ts
import { callCapability, capabilityDigest } from '@agentic-ui/testing';
import type { ConfirmationRequest } from '@agentic-ui/core';

it('asks approval for exactly this payload', async () => {
  const seen: ConfirmationRequest[] = [];
  await callCapability(saveCap, {}, {
    confirm: 'approve',
    onConfirmation: (req) => seen.push(req),
  });

  expect(seen[0].digest).toBe(await capabilityDigest(saveCap, {}));
});

capabilityDigest(cap, input) dùng chính target.id của capability (truyền tham số thứ ba để override). Digest ổn định — canonical JSON sắp xếp key — nên lưu vào snapshot file là an toàn. computeDigest / canonicalJson / sha256hex được re-export cho các nhu cầu ở tầng thấp hơn.

4. Lint chính definition — expectCapabilityContract

Bắt các lỗi contract mà test happy-path bỏ sót — hãy chạy nó trên mọi capability bạn ship:

ts
import { expectCapabilityContract } from '@agentic-ui/testing';

it('honours the capability contract', () => {
  expectCapabilityContract(greet, {
    examples: [{ name: 'An' }], // each example must PASS the inputSchema
  });
});

Nó throw một error liệt kê mọi vi phạm:

  • inputSchema phải là schema mà validator thực sự enforce được: root type: 'object' (các LLM tool API yêu cầu điều này), keyword type hợp lệ, pattern compile được, operand của keyword đúng kiểu, không có tổ hợp required + additionalProperties: false bất khả thi.
  • description không rỗng và trông giống tiếng Anh — đây là tài liệu cho model.
  • risk được khai báo (read | mutating | destructive) — nó quyết định confirmation policy, đừng bao giờ để ngầm định.
  • mọi example input phải pass inputSchema (example cũng có thể nằm ngay trên definition dưới dạng mảng examples).

5. Kịch bản multi-instance / lifecycle — createTestRegistry

Cho bất cứ thứ gì vượt quá một call đơn (hai target, suspend/dispose, revision bị stale), hãy tự dựng một registry và điều khiển trực tiếp. callCapability nhận { registry } để dùng lại nó:

ts
import { callCapability, createTestRegistry } from '@agentic-ui/testing';

it('routes to the addressed instance', async () => {
  const registry = createTestRegistry({ clock: () => 42 }); // deterministic clock
  registry.register({ ...cap, target: { id: 'page' } });
  registry.register({ ...cap, target: { id: 'drawer' } });

  const result = await callCapability(cap, input, { registry, target: 'drawer' });
  expect(result.status).toBe('ok');
});

Ví dụ thực tế

Playground test năm form capability của nó (form.describe / read_values / set_values / validate / save) bằng đúng các helper này — xem playground/test/form-capabilities.test.ts.