Skip to content

Commit 3de91d9

Browse files
committed
fix: stabilize line and feishu ci shards
1 parent aeb9ad5 commit 3de91d9

7 files changed

Lines changed: 148 additions & 32 deletions

File tree

extensions/feishu/src/bot.broadcast.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ describe("broadcast dispatch", () => {
8989
routing: {
9090
resolveAgentRoute: (params: unknown) => mockResolveAgentRoute(params),
9191
},
92+
session: {
93+
resolveStorePath: vi.fn(() => "/tmp/feishu-session-store.json"),
94+
},
9295
reply: {
9396
resolveEnvelopeFormatOptions: resolveEnvelopeFormatOptionsMock,
9497
formatAgentEnvelope: vi.fn((params: { body: string }) => params.body),

extensions/line/src/bot-handlers.test.ts

Lines changed: 134 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,146 @@ import type { LineAccountConfig } from "./types.js";
55

66
// Avoid pulling in globals/pairing/media dependencies; this suite only asserts
77
// allowlist/groupPolicy gating and message-context wiring.
8-
vi.mock("openclaw/plugin-sdk/runtime-env", async () => {
9-
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/runtime-env")>(
10-
"openclaw/plugin-sdk/runtime-env",
11-
);
12-
return {
13-
...actual,
14-
danger: (text: string) => text,
15-
logVerbose: () => {},
16-
shouldLogVerbose: () => false,
17-
};
18-
});
8+
vi.mock("openclaw/plugin-sdk/channel-inbound", () => ({
9+
buildMentionRegexes: () => [],
10+
matchesMentionPatterns: () => false,
11+
resolveMentionGatingWithBypass: ({
12+
isGroup,
13+
requireMention,
14+
canDetectMention,
15+
wasMentioned,
16+
hasAnyMention,
17+
allowTextCommands,
18+
hasControlCommand,
19+
commandAuthorized,
20+
}: {
21+
isGroup: boolean;
22+
requireMention: boolean;
23+
canDetectMention: boolean;
24+
wasMentioned: boolean;
25+
hasAnyMention: boolean;
26+
allowTextCommands: boolean;
27+
hasControlCommand: boolean;
28+
commandAuthorized: boolean;
29+
}) => ({
30+
shouldSkip:
31+
isGroup &&
32+
requireMention &&
33+
canDetectMention &&
34+
!wasMentioned &&
35+
!(allowTextCommands && hasControlCommand && commandAuthorized && !hasAnyMention),
36+
}),
37+
}));
38+
vi.mock("openclaw/plugin-sdk/channel-pairing", () => ({
39+
createChannelPairingChallengeIssuer:
40+
({ upsertPairingRequest }: { upsertPairingRequest: (args: unknown) => Promise<unknown> }) =>
41+
async ({ senderId, onCreated }: { senderId: string; onCreated?: () => void }) => {
42+
await upsertPairingRequest({ id: senderId, meta: {} });
43+
onCreated?.();
44+
},
45+
}));
46+
vi.mock("openclaw/plugin-sdk/command-auth", () => ({
47+
hasControlCommand: (text: string) => text.trim().startsWith("!"),
48+
resolveControlCommandGate: ({
49+
hasControlCommand,
50+
authorizers,
51+
}: {
52+
hasControlCommand: boolean;
53+
authorizers: Array<{ configured: boolean; allowed: boolean }>;
54+
}) => ({
55+
commandAuthorized:
56+
hasControlCommand && authorizers.some((entry) => entry.allowed || entry.configured === false),
57+
}),
58+
}));
59+
vi.mock("openclaw/plugin-sdk/config-runtime", () => ({
60+
resolveAllowlistProviderRuntimeGroupPolicy: ({
61+
groupPolicy,
62+
defaultGroupPolicy,
63+
}: {
64+
groupPolicy?: string;
65+
defaultGroupPolicy: string;
66+
}) => ({
67+
groupPolicy: groupPolicy ?? defaultGroupPolicy,
68+
providerMissingFallbackApplied: false,
69+
}),
70+
resolveDefaultGroupPolicy: (cfg: { channels?: { line?: { groupPolicy?: string } } }) =>
71+
cfg.channels?.line?.groupPolicy ?? "open",
72+
warnMissingProviderGroupPolicyFallbackOnce: () => {},
73+
}));
74+
vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
75+
danger: (text: string) => text,
76+
logVerbose: () => {},
77+
}));
78+
vi.mock("openclaw/plugin-sdk/group-access", () => ({
79+
evaluateMatchedGroupAccessForPolicy: ({
80+
groupPolicy,
81+
hasMatchInput,
82+
allowlistConfigured,
83+
allowlistMatched,
84+
}: {
85+
groupPolicy: string;
86+
hasMatchInput: boolean;
87+
allowlistConfigured: boolean;
88+
allowlistMatched: boolean;
89+
}) => {
90+
if (groupPolicy === "disabled") {
91+
return { allowed: false, reason: "disabled" };
92+
}
93+
if (groupPolicy !== "allowlist") {
94+
return { allowed: true, reason: null };
95+
}
96+
if (!hasMatchInput) {
97+
return { allowed: false, reason: "missing_match_input" };
98+
}
99+
if (!allowlistConfigured) {
100+
return { allowed: false, reason: "empty_allowlist" };
101+
}
102+
if (!allowlistMatched) {
103+
return { allowed: false, reason: "not_allowlisted" };
104+
}
105+
return { allowed: true, reason: null };
106+
},
107+
}));
108+
vi.mock("openclaw/plugin-sdk/reply-history", () => ({
109+
DEFAULT_GROUP_HISTORY_LIMIT: 20,
110+
clearHistoryEntriesIfEnabled: ({
111+
historyMap,
112+
historyKey,
113+
}: {
114+
historyMap: Map<string, HistoryEntry[]>;
115+
historyKey: string;
116+
}) => {
117+
historyMap.delete(historyKey);
118+
},
119+
recordPendingHistoryEntryIfEnabled: ({
120+
historyMap,
121+
historyKey,
122+
limit,
123+
entry,
124+
}: {
125+
historyMap: Map<string, HistoryEntry[]>;
126+
historyKey: string;
127+
limit: number;
128+
entry: HistoryEntry;
129+
}) => {
130+
const existing = historyMap.get(historyKey) ?? [];
131+
historyMap.set(historyKey, [...existing, entry].slice(-limit));
132+
},
133+
}));
134+
vi.mock("openclaw/plugin-sdk/routing", () => ({
135+
resolveAgentRoute: () => ({ agentId: "default" }),
136+
}));
19137

20138
const { readAllowFromStoreMock, upsertPairingRequestMock } = vi.hoisted(() => ({
21139
readAllowFromStoreMock: vi.fn(async () => [] as string[]),
22140
upsertPairingRequestMock: vi.fn(async () => ({ code: "CODE", created: true })),
23141
}));
24142

25-
vi.mock("openclaw/plugin-sdk/conversation-runtime", async () => {
26-
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/conversation-runtime")>(
27-
"openclaw/plugin-sdk/conversation-runtime",
28-
);
29-
return {
30-
...actual,
31-
resolvePairingIdLabel: () => "lineUserId",
32-
readChannelAllowFromStore: readAllowFromStoreMock,
33-
upsertChannelPairingRequest: upsertPairingRequestMock,
34-
};
35-
});
143+
vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({
144+
resolvePairingIdLabel: () => "lineUserId",
145+
readChannelAllowFromStore: readAllowFromStoreMock,
146+
upsertChannelPairingRequest: upsertPairingRequestMock,
147+
}));
36148

37149
vi.mock("./download.js", () => ({
38150
downloadLineMedia: async () => {

extensions/line/src/gateway.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
} from "./channel-api.js";
1111
import { getLineRuntime } from "./runtime.js";
1212

13-
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime.js"));
13+
const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime.js"));
14+
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime.js"));
1415

1516
export const lineGatewayAdapter: NonNullable<ChannelPlugin<ResolvedLineAccount>["gateway"]> = {
1617
startAccount: async (ctx) => {
@@ -30,7 +31,7 @@ export const lineGatewayAdapter: NonNullable<ChannelPlugin<ResolvedLineAccount>[
3031

3132
let lineBotLabel = "";
3233
try {
33-
const probe = await (await loadLineChannelRuntime()).probeLineBot(token, 2500);
34+
const probe = await (await loadLineProbeRuntime()).probeLineBot(token, 2500);
3435
const displayName = probe.ok ? probe.bot?.displayName?.trim() : null;
3536
if (displayName) {
3637
lineBotLabel = ` (${displayName})`;
@@ -45,7 +46,7 @@ export const lineGatewayAdapter: NonNullable<ChannelPlugin<ResolvedLineAccount>[
4546

4647
const monitorLineProvider =
4748
getLineRuntime().channel.line?.monitorLineProvider ??
48-
(await loadLineChannelRuntime()).monitorLineProvider;
49+
(await loadLineMonitorRuntime()).monitorLineProvider;
4950

5051
return await monitorLineProvider({
5152
channelAccessToken: token,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { monitorLineProvider } from "./monitor.js";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { probeLineBot } from "./probe.js";

extensions/line/src/setup-surface.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ vi.mock("@line/bot-sdk", () => ({
2828
}));
2929

3030
const lineConfigure = createPluginSetupWizardConfigure(linePlugin);
31-
let probeLineBot: typeof import("./probe.js").probeLineBot;
3231
const LINE_SRC_PREFIX = `../../${bundledPluginRoot("line")}/src/`;
3332

3433
function normalizeModuleSpecifier(specifier: string): string | null {
@@ -296,10 +295,6 @@ describe("line setup wizard", () => {
296295
});
297296

298297
describe("probeLineBot", () => {
299-
beforeAll(async () => {
300-
({ probeLineBot } = await import("./probe.js"));
301-
});
302-
303298
beforeEach(() => {
304299
getBotInfoMock.mockReset();
305300
MessagingApiClientMock.mockReset();
@@ -315,6 +310,7 @@ describe("probeLineBot", () => {
315310
});
316311

317312
it("returns timeout when bot info stalls", async () => {
313+
const { probeLineBot } = await import("./probe.js");
318314
vi.useFakeTimers();
319315
getBotInfoMock.mockImplementation(() => new Promise(() => {}));
320316

@@ -327,6 +323,7 @@ describe("probeLineBot", () => {
327323
});
328324

329325
it("returns bot info when available", async () => {
326+
const { probeLineBot } = await import("./probe.js");
330327
getBotInfoMock.mockResolvedValue({
331328
displayName: "OpenClaw",
332329
userId: "U123",
@@ -343,6 +340,7 @@ describe("probeLineBot", () => {
343340

344341
describe("linePlugin status.probeAccount", () => {
345342
it("falls back to the direct probe helper when runtime is not initialized", async () => {
343+
const { probeLineBot } = await import("./probe.js");
346344
MessagingApiClientMock.mockReset();
347345
MessagingApiClientMock.mockImplementation(function () {
348346
return { getBotInfo: getBotInfoMock };

extensions/line/src/status.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { hasLineCredentials } from "./account-helpers.js";
99
import { DEFAULT_ACCOUNT_ID, type ChannelPlugin, type ResolvedLineAccount } from "./channel-api.js";
1010

11-
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime.js"));
11+
const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime.js"));
1212

1313
const collectLineStatusIssues = createDependentCredentialStatusIssueCollector({
1414
channel: "line",
@@ -23,7 +23,7 @@ export const lineStatusAdapter: NonNullable<ChannelPlugin<ResolvedLineAccount>["
2323
collectStatusIssues: collectLineStatusIssues,
2424
buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
2525
probeAccount: async ({ account, timeoutMs }) =>
26-
await (await loadLineChannelRuntime()).probeLineBot(account.channelAccessToken, timeoutMs),
26+
await (await loadLineProbeRuntime()).probeLineBot(account.channelAccessToken, timeoutMs),
2727
resolveAccountSnapshot: ({ account }) => ({
2828
accountId: account.accountId,
2929
name: account.name,

0 commit comments

Comments
 (0)