























@@ -26,6 +26,12 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", async () => {
2626};
2727});
282829+function requireScenario<T extends { id: string }>(scenarios: T[], id: string): T {
30+const scenario = scenarios.find((candidate) => candidate.id === id);
31+expect(scenario).toBeDefined();
32+return scenario as T;
33+}
34+2935describe("telegram live qa runtime", () => {
3036afterEach(() => {
3137fetchWithSsrFGuardMock.mockClear();
@@ -371,28 +377,35 @@ describe("telegram live qa runtime", () => {
371377scenarios
372378.find((scenario) => scenario.id === "telegram-repeated-command-authorization")
373379?.buildRun("sut_bot").steps,
374-).toMatchObject([
375-{ driverGroupAuthorization: "deny", input: "/status@sut_bot", expectReply: false },
376-{ driverGroupAuthorization: "allow", input: "/status@sut_bot", expectReply: true },
377-{ input: "/help@sut_bot", expectReply: true },
378-{ input: "/commands@sut_bot", expectReply: true },
380+).toHaveLength(4);
381+const repeatedSteps = requireScenario(
382+scenarios,
383+"telegram-repeated-command-authorization",
384+).buildRun("sut_bot").steps;
385+expect(repeatedSteps[0]?.driverGroupAuthorization).toBe("deny");
386+expect(repeatedSteps[0]?.input).toBe("/status@sut_bot");
387+expect(repeatedSteps[0]?.expectReply).toBe(false);
388+expect(repeatedSteps[1]?.driverGroupAuthorization).toBe("allow");
389+expect(repeatedSteps[1]?.input).toBe("/status@sut_bot");
390+expect(repeatedSteps[1]?.expectReply).toBe(true);
391+expect(repeatedSteps[2]?.input).toBe("/help@sut_bot");
392+expect(repeatedSteps[2]?.expectReply).toBe(true);
393+expect(repeatedSteps[3]?.input).toBe("/commands@sut_bot");
394+expect(repeatedSteps[3]?.expectReply).toBe(true);
395+const otherBotStep = requireScenario(scenarios, "telegram-other-bot-command-gating").buildRun(
396+"sut_bot",
397+).steps[0];
398+expect(otherBotStep?.expectReply).toBe(false);
399+expect(otherBotStep?.input).toBe("/status@OpenClawQaOtherBot");
400+const statusToolStep = requireScenario(
401+scenarios,
402+"telegram-current-session-status-tool",
403+).buildRun("sut_bot").steps[0];
404+expect(statusToolStep?.expectedTextIncludes).toEqual([
405+"QA-TELEGRAM-CURRENT-SESSION-OK",
406+":telegram:group:",
379407]);
380-expect(
381-scenarios
382-.find((scenario) => scenario.id === "telegram-other-bot-command-gating")
383-?.buildRun("sut_bot").steps[0],
384-).toMatchObject({
385-expectReply: false,
386-input: "/status@OpenClawQaOtherBot",
387-});
388-expect(
389-scenarios
390-.find((scenario) => scenario.id === "telegram-current-session-status-tool")
391-?.buildRun("sut_bot").steps[0],
392-).toMatchObject({
393-expectedTextIncludes: ["QA-TELEGRAM-CURRENT-SESSION-OK", ":telegram:group:"],
394-replyToLatestSutMessage: true,
395-});
408+expect(statusToolStep?.replyToLatestSutMessage).toBe(true);
396409expect(
397410scenarios
398411.find((scenario) => scenario.id === "telegram-mentioned-message-reply")
@@ -402,41 +415,42 @@ describe("telegram live qa runtime", () => {
402415scenarios
403416.find((scenario) => scenario.id === "telegram-reply-chain-exact-marker")
404417?.buildRun("sut_bot").steps[0],
405-).toMatchObject({
406-expectedJoinedSutTextIncludes: ["QA-TELEGRAM-REPLY-CHAIN-OK"],
407-expectedSutMessageCount: 1,
408-replyToLatestSutMessage: true,
409-});
410-expect(
411-scenarios
412-.find((scenario) => scenario.id === "telegram-stream-final-single-message")
413-?.buildRun("sut_bot").steps[0],
414-).toMatchObject({
415-expectedJoinedSutTextIncludes: ["QA-TELEGRAM-STREAM-SINGLE-OK"],
416-expectedSutMessageCount: 1,
417-replyToLatestSutMessage: true,
418-});
419-expect(
420-scenarios
421-.find((scenario) => scenario.id === "telegram-long-final-reuses-preview")
422-?.buildRun("sut_bot").steps[0],
423-).toMatchObject({
424-expectedJoinedSutTextIncludes: ["TELEGRAM-LONG-FINAL-BEGIN", "TELEGRAM-LONG-FINAL-END"],
425-expectedSutMessageCountRange: [1, 2],
426-replyToLatestSutMessage: true,
427-});
428-expect(
429-scenarios
430-.find((scenario) => scenario.id === "telegram-long-final-three-chunks")
431-?.buildRun("sut_bot").steps[0],
432-).toMatchObject({
433-expectedJoinedSutTextIncludes: [
434-"TELEGRAM-LONG-FINAL-3CHUNK-BEGIN",
435-"TELEGRAM-LONG-FINAL-3CHUNK-END",
436-],
437-expectedSutMessageCount: 3,
438-replyToLatestSutMessage: true,
439-});
418+).toBeDefined();
419+const replyChainStep = requireScenario(scenarios, "telegram-reply-chain-exact-marker").buildRun(
420+"sut_bot",
421+).steps[0];
422+expect(replyChainStep?.expectedJoinedSutTextIncludes).toEqual(["QA-TELEGRAM-REPLY-CHAIN-OK"]);
423+expect(replyChainStep?.expectedSutMessageCount).toBe(1);
424+expect(replyChainStep?.replyToLatestSutMessage).toBe(true);
425+const streamSingleStep = requireScenario(
426+scenarios,
427+"telegram-stream-final-single-message",
428+).buildRun("sut_bot").steps[0];
429+expect(streamSingleStep?.expectedJoinedSutTextIncludes).toEqual([
430+"QA-TELEGRAM-STREAM-SINGLE-OK",
431+]);
432+expect(streamSingleStep?.expectedSutMessageCount).toBe(1);
433+expect(streamSingleStep?.replyToLatestSutMessage).toBe(true);
434+const longReusesStep = requireScenario(
435+scenarios,
436+"telegram-long-final-reuses-preview",
437+).buildRun("sut_bot").steps[0];
438+expect(longReusesStep?.expectedJoinedSutTextIncludes).toEqual([
439+"TELEGRAM-LONG-FINAL-BEGIN",
440+"TELEGRAM-LONG-FINAL-END",
441+]);
442+expect(longReusesStep?.expectedSutMessageCountRange).toEqual([1, 2]);
443+expect(longReusesStep?.replyToLatestSutMessage).toBe(true);
444+const longThreeChunksStep = requireScenario(
445+scenarios,
446+"telegram-long-final-three-chunks",
447+).buildRun("sut_bot").steps[0];
448+expect(longThreeChunksStep?.expectedJoinedSutTextIncludes).toEqual([
449+"TELEGRAM-LONG-FINAL-3CHUNK-BEGIN",
450+"TELEGRAM-LONG-FINAL-3CHUNK-END",
451+]);
452+expect(longThreeChunksStep?.expectedSutMessageCount).toBe(3);
453+expect(longThreeChunksStep?.replyToLatestSutMessage).toBe(true);
440454});
441455442456it("keeps mock-scripted Telegram checks out of the default live-frontier set", () => {
@@ -478,21 +492,15 @@ describe("telegram live qa runtime", () => {
478492479493it("lists default status and regression refs in the Telegram scenario catalog", () => {
480494const catalog = __testing.listTelegramQaScenarioCatalog("mock-openai");
481-expect(catalog.find((scenario) => scenario.id === "telegram-status-command")).toMatchObject({
482-defaultEnabled: true,
483-regressionRefs: ["openclaw/openclaw#74698"],
484-});
485-expect(
486-catalog.find((scenario) => scenario.id === "telegram-current-session-status-tool"),
487-).toMatchObject({
488-defaultEnabled: false,
489-});
490-expect(
491-catalog.find((scenario) => scenario.id === "telegram-stream-final-single-message"),
492-).toMatchObject({
493-defaultEnabled: true,
494-regressionRefs: ["openclaw/openclaw#39905"],
495-});
495+const status = requireScenario(catalog, "telegram-status-command");
496+expect(status.defaultEnabled).toBe(true);
497+expect(status.regressionRefs).toEqual(["openclaw/openclaw#74698"]);
498+expect(requireScenario(catalog, "telegram-current-session-status-tool").defaultEnabled).toBe(
499+false,
500+);
501+const streamSingle = requireScenario(catalog, "telegram-stream-final-single-message");
502+expect(streamSingle.defaultEnabled).toBe(true);
503+expect(streamSingle.regressionRefs).toEqual(["openclaw/openclaw#39905"]);
496504});
497505498506it("tracks Telegram live coverage against the shared transport contract", () => {
@@ -904,13 +912,10 @@ describe("telegram live qa runtime", () => {
904912expect(fetchMock).toHaveBeenCalledTimes(2);
905913expect(result.message.messageId).toBe(99);
906914expect(result.nextOffset).toBe(11);
907-expect(observedMessages).toEqual([
908-expect.objectContaining({
909-matchedScenario: true,
910-messageId: 99,
911-scenarioId: "telegram-whoami-command",
912-}),
913-]);
915+expect(observedMessages).toHaveLength(1);
916+expect(observedMessages[0]?.matchedScenario).toBe(true);
917+expect(observedMessages[0]?.messageId).toBe(99);
918+expect(observedMessages[0]?.scenarioId).toBe("telegram-whoami-command");
914919});
915920916921it("redacts observed message content by default in artifacts", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。