Files
SnowLuma/packages/onebot/tests/action-docs.test.ts
T
DrSmoothl 9b92f3825d refactor(onebot): declarative action-kit for param coercion + validation
Introduce packages/onebot/src/action-kit.ts — a zero-dependency,
type-inferring declarative layer for OneBot action params. Each action
declares its param contract (f.* coercers + optional cross-field rules)
and a `run` handler that receives typed, already-validated params.
Validation failures now return BAD_REQUEST naming the offending field
instead of falling through to ApiHandler's INTERNAL_ERROR catch.

- 141/142 actions migrated to defineAction/groupAction/groupUserAction
  across info/message/friend/group-*/request/extended; only
  .handle_quick_operation stays legacy (it needs the ApiHandler instance,
  which run() does not receive).
- f.messageId() distinguishes signed/non-zero message ids from positive
  group/user ids (f.uint()); int({min:0}).default(1800) fixes duration=0
  (unmute), which the old `|| 1800` silently swallowed.
- run(p, ctx, raw) exposes the raw params as an escape hatch for the
  irregular tail (alias-key fallbacks, arbitrary objects).
- action-docs.ts renders self-generated API docs from describe().
- ApiHandler dispatch / try-catch / response-wrapping unchanged (narrow seam).

Registered action name set is byte-identical before/after (142, verified
by runtime enumeration). Typecheck clean; onebot suite green (the
pre-existing websocket native-addon test failure is unrelated).
2026-06-02 15:21:46 +08:00

32 lines
1.4 KiB
TypeScript

// Tests the self-generated docs (D4): the describe()-walker aggregates every
// declarative action and renders coherent metadata/markdown.
import { describe, it, expect } from 'vitest';
import { collectActionDocs, renderActionDocsMarkdown } from '../src/action-docs';
describe('action-docs', () => {
const docs = collectActionDocs();
it('covers the migrated declarative actions with unique names', () => {
expect(docs.length).toBeGreaterThanOrEqual(120);
const names = docs.map((d) => d.name);
expect(new Set(names).size).toBe(names.length); // no duplicates
});
it('surfaces preset-injected fields + defaults (set_group_ban)', () => {
const ban = docs.find((d) => d.name === 'set_group_ban');
expect(ban).toBeDefined();
expect(ban!.params.map((p) => p.name)).toEqual(['group_id', 'user_id', 'duration']);
const groupId = ban!.params.find((p) => p.name === 'group_id')!;
expect(groupId).toMatchObject({ type: 'uint', required: true });
const duration = ban!.params.find((p) => p.name === 'duration')!;
expect(duration).toMatchObject({ type: 'int', required: false, default: 1800 });
});
it('renders markdown with header + an action section', () => {
const md = renderActionDocsMarkdown(docs);
expect(md).toContain('# OneBot Actions');
expect(md).toContain('`set_group_ban`');
expect(md).toContain('| 参数 | 类型 | 必填 | 默认 | 说明 |');
});
});