mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-27 17:11:46 +00:00
* feat(config): add canonical streaming config helpers * refactor(runtime): prefer canonical streaming accessors * feat(config): normalize preview channel streaming shape * test(config): lock streaming normalization followups * fix(config): polish streaming migration edges * chore(config): refresh streaming baseline hash
233 lines
7.0 KiB
TypeScript
233 lines
7.0 KiB
TypeScript
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
collectTelegramAllowFromUsernameWarnings,
|
|
collectTelegramEmptyAllowlistExtraWarnings,
|
|
collectTelegramGroupPolicyWarnings,
|
|
maybeRepairTelegramAllowFromUsernames,
|
|
scanTelegramAllowFromUsernameEntries,
|
|
telegramDoctor,
|
|
} from "./doctor.js";
|
|
|
|
const resolveCommandSecretRefsViaGatewayMock = vi.hoisted(() => vi.fn());
|
|
const listTelegramAccountIdsMock = vi.hoisted(() => vi.fn());
|
|
const inspectTelegramAccountMock = vi.hoisted(() => vi.fn());
|
|
const lookupTelegramChatIdMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("openclaw/plugin-sdk/runtime", async () => {
|
|
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/runtime")>(
|
|
"openclaw/plugin-sdk/runtime",
|
|
);
|
|
return {
|
|
...actual,
|
|
resolveCommandSecretRefsViaGateway: resolveCommandSecretRefsViaGatewayMock,
|
|
};
|
|
});
|
|
|
|
vi.mock("./accounts.js", async () => {
|
|
const actual = await vi.importActual<typeof import("./accounts.js")>("./accounts.js");
|
|
return {
|
|
...actual,
|
|
listTelegramAccountIds: listTelegramAccountIdsMock,
|
|
};
|
|
});
|
|
|
|
vi.mock("./account-inspect.js", async () => {
|
|
const actual =
|
|
await vi.importActual<typeof import("./account-inspect.js")>("./account-inspect.js");
|
|
return {
|
|
...actual,
|
|
inspectTelegramAccount: inspectTelegramAccountMock,
|
|
};
|
|
});
|
|
|
|
vi.mock("./api-fetch.js", async () => {
|
|
const actual = await vi.importActual<typeof import("./api-fetch.js")>("./api-fetch.js");
|
|
return {
|
|
...actual,
|
|
lookupTelegramChatId: lookupTelegramChatIdMock,
|
|
};
|
|
});
|
|
|
|
describe("telegram doctor", () => {
|
|
beforeEach(() => {
|
|
resolveCommandSecretRefsViaGatewayMock.mockReset().mockImplementation(async ({ config }) => ({
|
|
resolvedConfig: config,
|
|
diagnostics: [],
|
|
targetStatesByPath: {},
|
|
hadUnresolvedTargets: false,
|
|
}));
|
|
listTelegramAccountIdsMock.mockReset().mockReturnValue(["default"]);
|
|
inspectTelegramAccountMock.mockReset().mockReturnValue({
|
|
enabled: true,
|
|
token: "tok",
|
|
tokenSource: "config",
|
|
tokenStatus: "configured",
|
|
});
|
|
lookupTelegramChatIdMock.mockReset();
|
|
});
|
|
|
|
it("normalizes legacy telegram streaming aliases into the nested streaming shape", () => {
|
|
const normalize = telegramDoctor.normalizeCompatibilityConfig;
|
|
expect(normalize).toBeDefined();
|
|
if (!normalize) {
|
|
return;
|
|
}
|
|
|
|
const result = normalize({
|
|
cfg: {
|
|
channels: {
|
|
telegram: {
|
|
streamMode: "block",
|
|
chunkMode: "newline",
|
|
blockStreaming: true,
|
|
draftChunk: {
|
|
minChars: 120,
|
|
},
|
|
accounts: {
|
|
work: {
|
|
streaming: false,
|
|
blockStreamingCoalesce: {
|
|
idleMs: 250,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as never,
|
|
});
|
|
|
|
expect(result.config.channels?.telegram?.streaming).toEqual({
|
|
mode: "block",
|
|
chunkMode: "newline",
|
|
block: {
|
|
enabled: true,
|
|
},
|
|
preview: {
|
|
chunk: {
|
|
minChars: 120,
|
|
},
|
|
},
|
|
});
|
|
expect(result.config.channels?.telegram?.accounts?.work?.streaming).toEqual({
|
|
mode: "off",
|
|
block: {
|
|
coalesce: {
|
|
idleMs: 250,
|
|
},
|
|
},
|
|
});
|
|
expect(result.changes).toEqual(
|
|
expect.arrayContaining([
|
|
"Moved channels.telegram.streamMode → channels.telegram.streaming.mode (block).",
|
|
"Moved channels.telegram.chunkMode → channels.telegram.streaming.chunkMode.",
|
|
"Moved channels.telegram.blockStreaming → channels.telegram.streaming.block.enabled.",
|
|
"Moved channels.telegram.draftChunk → channels.telegram.streaming.preview.chunk.",
|
|
"Moved channels.telegram.accounts.work.streaming (boolean) → channels.telegram.accounts.work.streaming.mode (off).",
|
|
"Moved channels.telegram.accounts.work.blockStreamingCoalesce → channels.telegram.accounts.work.streaming.block.coalesce.",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("does not duplicate streaming.mode change messages when streamMode wins over boolean streaming", () => {
|
|
const normalize = telegramDoctor.normalizeCompatibilityConfig;
|
|
expect(normalize).toBeDefined();
|
|
if (!normalize) {
|
|
return;
|
|
}
|
|
|
|
const result = normalize({
|
|
cfg: {
|
|
channels: {
|
|
telegram: {
|
|
streamMode: "block",
|
|
streaming: false,
|
|
},
|
|
},
|
|
} as never,
|
|
});
|
|
|
|
expect(result.config.channels?.telegram?.streaming).toEqual({
|
|
mode: "block",
|
|
});
|
|
expect(
|
|
result.changes.filter((change) => change.includes("channels.telegram.streaming.mode")),
|
|
).toEqual(["Moved channels.telegram.streamMode → channels.telegram.streaming.mode (block)."]);
|
|
});
|
|
|
|
it("finds username allowFrom entries across scopes", () => {
|
|
const hits = scanTelegramAllowFromUsernameEntries({
|
|
channels: {
|
|
telegram: {
|
|
allowFrom: ["@top"],
|
|
accounts: {
|
|
work: {
|
|
allowFrom: ["tg:@work"],
|
|
groups: { "-100123": { topics: { "99": { allowFrom: ["@topic"] } } } },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as unknown as OpenClawConfig);
|
|
|
|
expect(hits).toEqual([
|
|
{ path: "channels.telegram.allowFrom", entry: "@top" },
|
|
{ path: "channels.telegram.accounts.work.allowFrom", entry: "tg:@work" },
|
|
{
|
|
path: "channels.telegram.accounts.work.groups.-100123.topics.99.allowFrom",
|
|
entry: "@topic",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("formats group-policy and empty-allowlist warnings", () => {
|
|
const warnings = collectTelegramGroupPolicyWarnings({
|
|
account: {
|
|
botToken: "123:abc",
|
|
groupPolicy: "allowlist",
|
|
groups: { ops: { allow: true } },
|
|
},
|
|
prefix: "channels.telegram",
|
|
});
|
|
expect(warnings[0]).toContain('groupPolicy is "allowlist"');
|
|
|
|
expect(
|
|
collectTelegramEmptyAllowlistExtraWarnings({
|
|
account: {
|
|
botToken: "123:abc",
|
|
groupPolicy: "allowlist",
|
|
groups: { ops: { allow: true } },
|
|
},
|
|
channelName: "telegram",
|
|
prefix: "channels.telegram",
|
|
}),
|
|
).toHaveLength(1);
|
|
});
|
|
|
|
it("repairs @username entries to numeric ids", async () => {
|
|
lookupTelegramChatIdMock.mockResolvedValue("111");
|
|
|
|
const result = await maybeRepairTelegramAllowFromUsernames({
|
|
channels: {
|
|
telegram: {
|
|
botToken: "123:abc",
|
|
allowFrom: ["@testuser"],
|
|
},
|
|
},
|
|
} as unknown as OpenClawConfig);
|
|
|
|
expect(result.config.channels?.telegram?.allowFrom).toEqual(["111"]);
|
|
expect(result.changes[0]).toContain("@testuser");
|
|
});
|
|
|
|
it("formats username repair warnings", () => {
|
|
const warnings = collectTelegramAllowFromUsernameWarnings({
|
|
hits: [{ path: "channels.telegram.allowFrom", entry: "@top" }],
|
|
doctorFixCommand: "openclaw doctor --fix",
|
|
});
|
|
|
|
expect(warnings[0]).toContain("non-numeric entries");
|
|
expect(warnings[1]).toContain("openclaw doctor --fix");
|
|
});
|
|
});
|