mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-04 16:38:49 +00:00
* refactor(channels): own setup and instance contracts * refactor(channels): isolate management contracts * refactor(channels): normalize activation contracts * fix(channels): enforce management contracts * refactor(channels): finish setup ownership migration * fix(channels): harden management contracts * fix(channels): enforce lazy loading and runtime ownership * fix(feishu): make multi-instance startup idempotent * fix(webui): render channel setup contracts cleanly * fix(feishu): stop websocket clients cleanly * fix(channels): enforce persistence and activation gates * fix(channels): preserve global feature action scope * fix(channels): apply defaults for single plugins * fix(channels): enforce management contract boundaries * refactor(feishu): remove identity helper indirection * fix(channels): preserve management setup contracts * refactor(channels): generalize instance settings UI * refactor(channels): package channel plugins with web UI metadata * refactor(channels): make built-ins self-contained packages * test(channels): colocate tests with channel packages * fix(dingtalk): use official brand icon * feat(channels): colocate webui translations * docs(channels): clarify plugin ownership * test(exec): remove output wait race * refactor(channels): unify plugin descriptors * fix(channels): enforce descriptor-owned contracts * refactor(channels): finish package-owned plugin setup * refactor(channels): use repository-owned packages only * fix(channels): self-describe dependencies and runtime state * fix(channels): warn about legacy entry points
147 lines
4.2 KiB
TypeScript
147 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
channelIsRunning,
|
|
channelSetup,
|
|
channelStatusLabel,
|
|
channelToggleChecked,
|
|
} from "@/components/settings/channels/ChannelIdentity";
|
|
import type { NanobotFeatureInfo } from "@/lib/types";
|
|
|
|
function feature(overrides: Partial<NanobotFeatureInfo>): NanobotFeatureInfo {
|
|
return {
|
|
name: "plugin-chat",
|
|
display_name: "Plugin Chat",
|
|
type: "channel",
|
|
enabled: false,
|
|
installed: true,
|
|
ready: false,
|
|
status: "not_enabled",
|
|
install_supported: true,
|
|
requires_restart: true,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("channelSetup", () => {
|
|
it("builds editable fields for a plugin-owned backend contract", () => {
|
|
const setup = channelSetup(feature({
|
|
setup: {
|
|
fields: [
|
|
{
|
|
key: "channels.plugin-chat.apiToken",
|
|
field: "apiToken",
|
|
kind: "secret",
|
|
choices: [],
|
|
required: true,
|
|
},
|
|
{
|
|
key: "channels.plugin-chat.region",
|
|
field: "region",
|
|
kind: "enum",
|
|
choices: ["us", "eu"],
|
|
required: false,
|
|
},
|
|
],
|
|
official_url: "https://plugin.example/setup",
|
|
},
|
|
}));
|
|
|
|
expect(setup.officialUrl).toBe("https://plugin.example/setup");
|
|
expect(setup.officialLabel).toBe("Open official setup");
|
|
expect(setup.fields).toEqual([
|
|
expect.objectContaining({
|
|
key: "channels.plugin-chat.apiToken",
|
|
label: "Api Token",
|
|
secret: true,
|
|
optional: false,
|
|
}),
|
|
expect.objectContaining({
|
|
key: "channels.plugin-chat.region",
|
|
options: [
|
|
{ value: "us", label: "Us" },
|
|
{ value: "eu", label: "Eu" },
|
|
],
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("filters catalog-only fields that the backend does not accept", () => {
|
|
const setup = channelSetup(feature({
|
|
name: "discord",
|
|
display_name: "Discord",
|
|
webui: "webui/index.ts",
|
|
setup: {
|
|
fields: [{
|
|
key: "channels.discord.token",
|
|
field: "token",
|
|
kind: "secret",
|
|
choices: [],
|
|
required: true,
|
|
}],
|
|
},
|
|
}));
|
|
|
|
expect(setup.fields?.map((field) => field.key)).toEqual(["channels.discord.token"]);
|
|
expect(setup.manualFields).toBeUndefined();
|
|
});
|
|
|
|
it("uses backend defaults and choices with catalog presentation labels", () => {
|
|
const setup = channelSetup(feature({
|
|
name: "discord",
|
|
display_name: "Discord",
|
|
webui: "webui/index.ts",
|
|
setup: {
|
|
fields: [{
|
|
key: "channels.discord.groupPolicy",
|
|
field: "groupPolicy",
|
|
kind: "enum",
|
|
choices: ["open"],
|
|
required: false,
|
|
default_value: "open",
|
|
}],
|
|
},
|
|
}));
|
|
|
|
expect(setup.fields).toEqual([
|
|
expect.objectContaining({
|
|
key: "channels.discord.groupPolicy",
|
|
label: "Group behavior",
|
|
defaultValue: "open",
|
|
options: [{ value: "open", label: "All messages" }],
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("loads setup copy from the channel-owned locale", () => {
|
|
const setup = channelSetup(feature({
|
|
name: "dingtalk",
|
|
display_name: "DingTalk",
|
|
webui: "webui/index.ts",
|
|
}), "zh-CN");
|
|
|
|
expect(setup.summary).toBe("钉钉需要 Stream 模式的应用凭据。");
|
|
expect(setup.steps[0]).toBe("创建或选择一个已启用 Stream 模式的钉钉应用。");
|
|
expect(setup.fields).toContainEqual(expect.objectContaining({
|
|
key: "channels.dingtalk.allowFrom",
|
|
label: "允许的用户",
|
|
}));
|
|
});
|
|
});
|
|
|
|
describe("channel runtime state", () => {
|
|
const tx = (_key: string, fallback: string) => fallback;
|
|
|
|
it("only reports a channel on when the runtime is explicitly running", () => {
|
|
const running = feature({ enabled: true, runtime_status: "running" });
|
|
const unknown = feature({ enabled: true });
|
|
|
|
expect(channelIsRunning(running)).toBe(true);
|
|
expect(channelToggleChecked(running)).toBe(true);
|
|
expect(channelStatusLabel(running, tx)).toBe("On");
|
|
expect(channelIsRunning(unknown)).toBe(false);
|
|
expect(channelToggleChecked(unknown)).toBe(false);
|
|
expect(channelStatusLabel(unknown, tx)).toBe("Not running");
|
|
});
|
|
});
|