
























@@ -1,6 +1,7 @@
11import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
22import type { OpenClawConfig } from "../../config/config.js";
334+const getLoadedChannelPluginMock = vi.hoisted(() => vi.fn());
45const getChannelPluginMock = vi.hoisted(() => vi.fn());
56const getActivePluginChannelRegistryVersionMock = vi.hoisted(() => vi.fn());
67@@ -15,7 +16,11 @@ let normalizeTargetForProvider: TargetNormalizationModule["normalizeTargetForPro
1516let resetTargetNormalizerCacheForTests: TargetNormalizationModule["__testing"]["resetTargetNormalizerCacheForTests"];
16171718vi.mock("../../channels/plugins/registry-loaded-read.js", () => ({
18-getLoadedChannelPluginForRead: (...args: unknown[]) => getChannelPluginMock(...args),
19+getLoadedChannelPluginForRead: (...args: unknown[]) => getLoadedChannelPluginMock(...args),
20+}));
21+22+vi.mock("../../channels/plugins/index.js", () => ({
23+getChannelPlugin: (...args: unknown[]) => getChannelPluginMock(...args),
1924}));
20252126vi.mock("../../plugins/runtime.js", () => ({
@@ -38,6 +43,7 @@ beforeAll(async () => {
3843});
39444045beforeEach(() => {
46+getLoadedChannelPluginMock.mockReset();
4147getChannelPluginMock.mockReset();
4248getActivePluginChannelRegistryVersionMock.mockReset();
4349resetTargetNormalizerCacheForTests();
@@ -58,6 +64,7 @@ describe("normalizeTargetForProvider", () => {
5864{
5965provider: "unknown",
6066setup: () => {
67+getLoadedChannelPluginMock.mockReturnValueOnce(undefined);
6168getChannelPluginMock.mockReturnValueOnce(undefined);
6269},
6370expected: "raw-id",
@@ -66,6 +73,7 @@ describe("normalizeTargetForProvider", () => {
6673provider: "alpha",
6774setup: () => {
6875getActivePluginChannelRegistryVersionMock.mockReturnValueOnce(1);
76+getLoadedChannelPluginMock.mockReturnValueOnce(undefined);
6977getChannelPluginMock.mockReturnValueOnce(undefined);
7078},
7179expected: "raw-id",
@@ -85,7 +93,7 @@ describe("normalizeTargetForProvider", () => {
8593.mockReturnValueOnce(10)
8694.mockReturnValueOnce(10)
8795.mockReturnValueOnce(11);
88-getChannelPluginMock
96+getLoadedChannelPluginMock
8997.mockReturnValueOnce({
9098messaging: { normalizeTarget: firstNormalizer },
9199})
@@ -97,14 +105,30 @@ describe("normalizeTargetForProvider", () => {
97105expect(normalizeTargetForProvider("alpha", " def ")).toBe("DEF");
98106expect(normalizeTargetForProvider("alpha", " ghi ")).toBe("next:ghi");
99107100-expect(getChannelPluginMock).toHaveBeenCalledTimes(2);
108+expect(getLoadedChannelPluginMock).toHaveBeenCalledTimes(2);
109+expect(getChannelPluginMock).not.toHaveBeenCalled();
101110expect(firstNormalizer).toHaveBeenCalledTimes(2);
102111expect(secondNormalizer).toHaveBeenCalledTimes(1);
103112});
104113114+it("uses bundled/catalog target normalization when the channel is not loaded", () => {
115+getActivePluginChannelRegistryVersionMock.mockReturnValueOnce(30);
116+getLoadedChannelPluginMock.mockReturnValueOnce(undefined);
117+getChannelPluginMock.mockReturnValueOnce({
118+messaging: {
119+normalizeTarget: (raw: string) =>
120+raw.trim() === "-1001234567890:topic:42" ? "telegram:-1001234567890:topic:42" : undefined,
121+},
122+});
123+124+expect(normalizeTargetForProvider("telegram", " -1001234567890:topic:42 ")).toBe(
125+"telegram:-1001234567890:topic:42",
126+);
127+});
128+105129it("returns undefined when the provider normalizer resolves to an empty value", () => {
106130getActivePluginChannelRegistryVersionMock.mockReturnValueOnce(20);
107-getChannelPluginMock.mockReturnValueOnce({
131+getLoadedChannelPluginMock.mockReturnValueOnce({
108132messaging: {
109133normalizeTarget: () => "",
110134},
@@ -121,7 +145,7 @@ describe("resolveNormalizedTargetInput", () => {
121145122146it("returns raw and normalized values", () => {
123147getActivePluginChannelRegistryVersionMock.mockReturnValueOnce(1);
124-getChannelPluginMock.mockReturnValueOnce({
148+getLoadedChannelPluginMock.mockReturnValueOnce({
125149messaging: {
126150normalizeTarget: (raw: string) => raw.trim().toUpperCase(),
127151},
@@ -137,7 +161,7 @@ describe("resolveNormalizedTargetInput", () => {
137161describe("looksLikeTargetId", () => {
138162it("uses plugin looksLikeId when available", () => {
139163const pluginLooksLikeId = vi.fn((raw: string, normalized: string) => raw !== normalized);
140-getChannelPluginMock.mockReturnValueOnce({
164+getLoadedChannelPluginMock.mockReturnValueOnce({
141165messaging: {
142166targetResolver: {
143167looksLikeId: pluginLooksLikeId,
@@ -158,17 +182,38 @@ describe("looksLikeTargetId", () => {
158182it.each(["channel:C123", "@alice", "#general", "+15551234567", "conversation:abc", "foo@thread"])(
159183"falls back to built-in id-like heuristics for %s",
160184(raw) => {
185+getLoadedChannelPluginMock.mockReturnValueOnce(undefined);
161186getChannelPluginMock.mockReturnValueOnce(undefined);
162187expect(looksLikeTargetId({ channel: "workspace", raw })).toBe(true);
163188},
164189);
190+191+it("uses bundled/catalog target id detection when the channel is not loaded", () => {
192+getLoadedChannelPluginMock.mockReturnValueOnce(undefined);
193+getChannelPluginMock.mockReturnValueOnce({
194+messaging: {
195+targetResolver: {
196+looksLikeId: (raw: string, normalized?: string) =>
197+raw === "-1001234567890:topic:42" && normalized === "telegram:-1001234567890:topic:42",
198+},
199+},
200+});
201+202+expect(
203+looksLikeTargetId({
204+channel: "telegram",
205+raw: "-1001234567890:topic:42",
206+normalized: "telegram:-1001234567890:topic:42",
207+}),
208+).toBe(true);
209+});
165210});
166211167212describe("maybeResolvePluginMessagingTarget", () => {
168213const cfg = {} as OpenClawConfig;
169214170215it("returns undefined when requireIdLike is set and the target is not id-like", async () => {
171-getChannelPluginMock.mockReturnValueOnce({
216+getLoadedChannelPluginMock.mockReturnValueOnce({
172217messaging: {
173218targetResolver: {
174219looksLikeId: () => false,
@@ -194,7 +239,7 @@ describe("maybeResolvePluginMessagingTarget", () => {
194239kind: "group",
195240display: "general",
196241});
197-getChannelPluginMock
242+getLoadedChannelPluginMock
198243.mockReturnValueOnce({
199244messaging: {
200245normalizeTarget: (raw: string) => raw.trim().toUpperCase(),
@@ -234,7 +279,7 @@ describe("maybeResolvePluginMessagingTarget", () => {
234279describe("buildTargetResolverSignature", () => {
235280it("builds stable signatures from resolver hint and looksLikeId source", () => {
236281const looksLikeId = (value: string) => value.startsWith("C");
237-getChannelPluginMock.mockReturnValueOnce({
282+getLoadedChannelPluginMock.mockReturnValueOnce({
238283messaging: {
239284targetResolver: {
240285hint: "Use channel id",
@@ -244,7 +289,7 @@ describe("buildTargetResolverSignature", () => {
244289});
245290246291const first = buildTargetResolverSignature("workspace");
247-getChannelPluginMock.mockReturnValueOnce({
292+getLoadedChannelPluginMock.mockReturnValueOnce({
248293messaging: {
249294targetResolver: {
250295hint: "Use channel id",
@@ -258,7 +303,7 @@ describe("buildTargetResolverSignature", () => {
258303});
259304260305it("changes when resolver metadata changes", () => {
261-getChannelPluginMock.mockReturnValueOnce({
306+getLoadedChannelPluginMock.mockReturnValueOnce({
262307messaging: {
263308targetResolver: {
264309hint: "Use channel id",
@@ -268,7 +313,7 @@ describe("buildTargetResolverSignature", () => {
268313});
269314const first = buildTargetResolverSignature("workspace");
270315271-getChannelPluginMock.mockReturnValueOnce({
316+getLoadedChannelPluginMock.mockReturnValueOnce({
272317messaging: {
273318targetResolver: {
274319hint: "Use user id",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。