mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:10:43 +00:00
perf: slim telegram sticker tests
This commit is contained in:
32
extensions/telegram/src/bot-message-dispatch.media.ts
Normal file
32
extensions/telegram/src/bot-message-dispatch.media.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export type TelegramMediaContextPayload = {
|
||||
MediaPath?: string;
|
||||
MediaUrl?: string;
|
||||
MediaType?: string;
|
||||
MediaPaths?: string[];
|
||||
MediaUrls?: string[];
|
||||
MediaTypes?: string[];
|
||||
};
|
||||
|
||||
export function pruneStickerMediaFromContext(
|
||||
ctxPayload: TelegramMediaContextPayload,
|
||||
opts?: { stickerMediaIncluded?: boolean },
|
||||
) {
|
||||
if (opts?.stickerMediaIncluded === false) {
|
||||
return;
|
||||
}
|
||||
const nextMediaPaths = Array.isArray(ctxPayload.MediaPaths)
|
||||
? ctxPayload.MediaPaths.slice(1)
|
||||
: undefined;
|
||||
const nextMediaUrls = Array.isArray(ctxPayload.MediaUrls)
|
||||
? ctxPayload.MediaUrls.slice(1)
|
||||
: undefined;
|
||||
const nextMediaTypes = Array.isArray(ctxPayload.MediaTypes)
|
||||
? ctxPayload.MediaTypes.slice(1)
|
||||
: undefined;
|
||||
ctxPayload.MediaPaths = nextMediaPaths && nextMediaPaths.length > 0 ? nextMediaPaths : undefined;
|
||||
ctxPayload.MediaUrls = nextMediaUrls && nextMediaUrls.length > 0 ? nextMediaUrls : undefined;
|
||||
ctxPayload.MediaTypes = nextMediaTypes && nextMediaTypes.length > 0 ? nextMediaTypes : undefined;
|
||||
ctxPayload.MediaPath = ctxPayload.MediaPaths?.[0];
|
||||
ctxPayload.MediaUrl = ctxPayload.MediaUrls?.[0] ?? ctxPayload.MediaPath;
|
||||
ctxPayload.MediaType = ctxPayload.MediaTypes?.[0];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { pruneStickerMediaFromContext } from "./bot-message-dispatch.js";
|
||||
import { pruneStickerMediaFromContext } from "./bot-message-dispatch.media.js";
|
||||
|
||||
type MediaCtx = {
|
||||
MediaPath?: string;
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
resolveAgentDir,
|
||||
resolveDefaultModelForAgent,
|
||||
} from "./bot-message-dispatch.agent.runtime.js";
|
||||
import { pruneStickerMediaFromContext } from "./bot-message-dispatch.media.js";
|
||||
import {
|
||||
generateTopicLabel,
|
||||
getAgentScopedMediaLocalRoots,
|
||||
@@ -77,6 +78,8 @@ import {
|
||||
import { editMessageTelegram } from "./send.js";
|
||||
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
|
||||
|
||||
export { pruneStickerMediaFromContext } from "./bot-message-dispatch.media.js";
|
||||
|
||||
const EMPTY_RESPONSE_FALLBACK = "No response generated. Please try again.";
|
||||
const silentReplyDispatchLogger = createSubsystemLogger("telegram/silent-reply-dispatch");
|
||||
|
||||
@@ -97,37 +100,6 @@ async function resolveStickerVisionSupport(cfg: OpenClawConfig, agentId: string)
|
||||
}
|
||||
}
|
||||
|
||||
export function pruneStickerMediaFromContext(
|
||||
ctxPayload: {
|
||||
MediaPath?: string;
|
||||
MediaUrl?: string;
|
||||
MediaType?: string;
|
||||
MediaPaths?: string[];
|
||||
MediaUrls?: string[];
|
||||
MediaTypes?: string[];
|
||||
},
|
||||
opts?: { stickerMediaIncluded?: boolean },
|
||||
) {
|
||||
if (opts?.stickerMediaIncluded === false) {
|
||||
return;
|
||||
}
|
||||
const nextMediaPaths = Array.isArray(ctxPayload.MediaPaths)
|
||||
? ctxPayload.MediaPaths.slice(1)
|
||||
: undefined;
|
||||
const nextMediaUrls = Array.isArray(ctxPayload.MediaUrls)
|
||||
? ctxPayload.MediaUrls.slice(1)
|
||||
: undefined;
|
||||
const nextMediaTypes = Array.isArray(ctxPayload.MediaTypes)
|
||||
? ctxPayload.MediaTypes.slice(1)
|
||||
: undefined;
|
||||
ctxPayload.MediaPaths = nextMediaPaths && nextMediaPaths.length > 0 ? nextMediaPaths : undefined;
|
||||
ctxPayload.MediaUrls = nextMediaUrls && nextMediaUrls.length > 0 ? nextMediaUrls : undefined;
|
||||
ctxPayload.MediaTypes = nextMediaTypes && nextMediaTypes.length > 0 ? nextMediaTypes : undefined;
|
||||
ctxPayload.MediaPath = ctxPayload.MediaPaths?.[0];
|
||||
ctxPayload.MediaUrl = ctxPayload.MediaUrls?.[0] ?? ctxPayload.MediaPath;
|
||||
ctxPayload.MediaType = ctxPayload.MediaTypes?.[0];
|
||||
}
|
||||
|
||||
type DispatchTelegramMessageParams = {
|
||||
context: TelegramMessageContext;
|
||||
bot: Bot;
|
||||
|
||||
@@ -861,55 +861,6 @@ describe("createTelegramBot", () => {
|
||||
expect(replySpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks unauthorized DM media before download and sends pairing reply", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
channels: { telegram: { dmPolicy: "pairing" } },
|
||||
});
|
||||
readChannelAllowFromStore.mockResolvedValue([]);
|
||||
upsertChannelPairingRequest.mockResolvedValue({ code: "PAIRME12", created: true });
|
||||
sendMessageSpy.mockClear();
|
||||
replySpy.mockClear();
|
||||
const senderId = Number(`${Date.now()}01`.slice(-9));
|
||||
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(
|
||||
async () =>
|
||||
new Response(new Uint8Array([0xff, 0xd8, 0xff, 0x00]), {
|
||||
status: 200,
|
||||
headers: { "content-type": "image/jpeg" },
|
||||
}),
|
||||
);
|
||||
const getFileSpy = vi.fn(async () => ({ file_path: "photos/p1.jpg" }));
|
||||
|
||||
try {
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
|
||||
await handler({
|
||||
message: {
|
||||
chat: { id: 1234, type: "private" },
|
||||
message_id: 411,
|
||||
date: 1736380800,
|
||||
photo: [{ file_id: "p1" }],
|
||||
from: { id: senderId, username: "random" },
|
||||
},
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: getFileSpy,
|
||||
});
|
||||
|
||||
expect(getFileSpy).not.toHaveBeenCalled();
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
|
||||
const pairingText = String(sendMessageSpy.mock.calls[0]?.[1]);
|
||||
expect(pairingText).toContain("Pairing code:");
|
||||
expect(pairingText).toContain("<pre><code>");
|
||||
expect(sendMessageSpy.mock.calls[0]?.[2]).toEqual(
|
||||
expect.objectContaining({ parse_mode: "HTML" }),
|
||||
);
|
||||
expect(replySpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
fetchSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
it("blocks DM media downloads completely when dmPolicy is disabled", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
channels: { telegram: { dmPolicy: "disabled" } },
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import path from "node:path";
|
||||
import { loadJsonFile, saveJsonFile } from "openclaw/plugin-sdk/json-store";
|
||||
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const CACHE_VERSION = 1;
|
||||
|
||||
@@ -41,6 +40,10 @@ function saveCache(cache: StickerCache): void {
|
||||
saveJsonFile(getCacheFile(), cache);
|
||||
}
|
||||
|
||||
function normalizeStickerSearchText(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cached sticker by its unique ID.
|
||||
*/
|
||||
@@ -63,12 +66,12 @@ export function cacheSticker(sticker: CachedSticker): void {
|
||||
*/
|
||||
export function searchStickers(query: string, limit = 10): CachedSticker[] {
|
||||
const cache = loadCache();
|
||||
const queryLower = normalizeLowercaseStringOrEmpty(query);
|
||||
const queryLower = normalizeStickerSearchText(query);
|
||||
const results: Array<{ sticker: CachedSticker; score: number }> = [];
|
||||
|
||||
for (const sticker of Object.values(cache.stickers)) {
|
||||
let score = 0;
|
||||
const descLower = normalizeLowercaseStringOrEmpty(sticker.description);
|
||||
const descLower = normalizeStickerSearchText(sticker.description);
|
||||
|
||||
// Exact substring match in description
|
||||
if (descLower.includes(queryLower)) {
|
||||
@@ -90,7 +93,7 @@ export function searchStickers(query: string, limit = 10): CachedSticker[] {
|
||||
}
|
||||
|
||||
// Set name match
|
||||
if (normalizeLowercaseStringOrEmpty(sticker.setName).includes(queryLower)) {
|
||||
if (normalizeStickerSearchText(sticker.setName).includes(queryLower)) {
|
||||
score += 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as stickerCache from "./sticker-cache-store.js";
|
||||
|
||||
const TEST_CACHE_DIR = "/tmp/openclaw-test-sticker-cache/telegram";
|
||||
const TEST_CACHE_FILE = path.join(TEST_CACHE_DIR, "sticker-cache.json");
|
||||
const jsonStoreMocks = vi.hoisted(() => {
|
||||
const store: { value: unknown } = { value: null };
|
||||
return {
|
||||
store,
|
||||
loadJsonFile: vi.fn(() => store.value),
|
||||
saveJsonFile: vi.fn((_file: string, value: unknown) => {
|
||||
store.value = structuredClone(value);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/json-store", () => ({
|
||||
loadJsonFile: jsonStoreMocks.loadJsonFile,
|
||||
saveJsonFile: jsonStoreMocks.saveJsonFile,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/state-paths", () => ({
|
||||
resolveStateDir: () => "/tmp/openclaw-test-sticker-cache",
|
||||
}));
|
||||
|
||||
describe("sticker-cache", () => {
|
||||
beforeEach(() => {
|
||||
process.env.OPENCLAW_STATE_DIR = "/tmp/openclaw-test-sticker-cache";
|
||||
fs.rmSync("/tmp/openclaw-test-sticker-cache", { recursive: true, force: true });
|
||||
fs.mkdirSync(TEST_CACHE_DIR, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync("/tmp/openclaw-test-sticker-cache", { recursive: true, force: true });
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
jsonStoreMocks.store.value = null;
|
||||
jsonStoreMocks.loadJsonFile.mockClear();
|
||||
jsonStoreMocks.saveJsonFile.mockClear();
|
||||
});
|
||||
|
||||
describe("getCachedSticker", () => {
|
||||
@@ -40,7 +50,7 @@ describe("sticker-cache", () => {
|
||||
expect(result).toEqual(sticker);
|
||||
});
|
||||
|
||||
it("returns null after cache is cleared", () => {
|
||||
it("returns null after backing store is cleared", () => {
|
||||
const sticker = {
|
||||
fileId: "file123",
|
||||
fileUniqueId: "unique123",
|
||||
@@ -51,8 +61,7 @@ describe("sticker-cache", () => {
|
||||
stickerCache.cacheSticker(sticker);
|
||||
expect(stickerCache.getCachedSticker("unique123")).not.toBeNull();
|
||||
|
||||
// Manually clear the cache file
|
||||
fs.rmSync(TEST_CACHE_FILE, { force: true });
|
||||
jsonStoreMocks.store.value = null;
|
||||
|
||||
expect(stickerCache.getCachedSticker("unique123")).toBeNull();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user