import { describe, expect, it } from "vitest"; import { buildTelegramThreadParams, buildTypingThreadParams, expandTextLinks, normalizeForwardedContext, resolveTelegramForumThreadId, } from "./helpers.js"; describe("resolveTelegramForumThreadId", () => { it("returns undefined for non-forum groups even with messageThreadId", () => { // Reply threads in regular groups should not create separate sessions expect(resolveTelegramForumThreadId({ isForum: false, messageThreadId: 42 })).toBeUndefined(); }); it("returns undefined for non-forum groups without messageThreadId", () => { expect( resolveTelegramForumThreadId({ isForum: false, messageThreadId: undefined }), ).toBeUndefined(); expect( resolveTelegramForumThreadId({ isForum: undefined, messageThreadId: 99 }), ).toBeUndefined(); }); it("returns General topic (1) for forum groups without messageThreadId", () => { expect(resolveTelegramForumThreadId({ isForum: true, messageThreadId: undefined })).toBe(1); expect(resolveTelegramForumThreadId({ isForum: true, messageThreadId: null })).toBe(1); }); it("returns the topic id for forum groups with messageThreadId", () => { expect(resolveTelegramForumThreadId({ isForum: true, messageThreadId: 99 })).toBe(99); }); }); describe("buildTelegramThreadParams", () => { it("omits General topic thread id for message sends", () => { expect(buildTelegramThreadParams(1)).toBeUndefined(); }); it("includes non-General topic thread ids", () => { expect(buildTelegramThreadParams(99)).toEqual({ message_thread_id: 99 }); }); it("normalizes thread ids to integers", () => { expect(buildTelegramThreadParams(42.9)).toEqual({ message_thread_id: 42 }); }); }); describe("buildTypingThreadParams", () => { it("returns undefined when no thread id is provided", () => { expect(buildTypingThreadParams(undefined)).toBeUndefined(); }); it("includes General topic thread id for typing indicators", () => { expect(buildTypingThreadParams(1)).toEqual({ message_thread_id: 1 }); }); it("normalizes thread ids to integers", () => { expect(buildTypingThreadParams(42.9)).toEqual({ message_thread_id: 42 }); }); }); describe("normalizeForwardedContext", () => { it("handles forward_origin users", () => { const ctx = normalizeForwardedContext({ forward_origin: { type: "user", sender_user: { first_name: "Ada", last_name: "Lovelace", username: "ada", id: 42 }, date: 123, }, } as any); expect(ctx).not.toBeNull(); expect(ctx?.from).toBe("Ada Lovelace (@ada)"); expect(ctx?.fromType).toBe("user"); expect(ctx?.fromId).toBe("42"); expect(ctx?.fromUsername).toBe("ada"); expect(ctx?.fromTitle).toBe("Ada Lovelace"); expect(ctx?.date).toBe(123); }); it("handles hidden forward_origin names", () => { const ctx = normalizeForwardedContext({ forward_origin: { type: "hidden_user", sender_user_name: "Hidden Name", date: 456 }, } as any); expect(ctx).not.toBeNull(); expect(ctx?.from).toBe("Hidden Name"); expect(ctx?.fromType).toBe("hidden_user"); expect(ctx?.fromTitle).toBe("Hidden Name"); expect(ctx?.date).toBe(456); }); it("handles legacy forwards with signatures", () => { const ctx = normalizeForwardedContext({ forward_from_chat: { title: "OpenClaw Updates", username: "openclaw", id: 99, type: "channel", }, forward_signature: "Stan", forward_date: 789, } as any); expect(ctx).not.toBeNull(); expect(ctx?.from).toBe("OpenClaw Updates (Stan)"); expect(ctx?.fromType).toBe("legacy_channel"); expect(ctx?.fromId).toBe("99"); expect(ctx?.fromUsername).toBe("openclaw"); expect(ctx?.fromTitle).toBe("OpenClaw Updates"); expect(ctx?.fromSignature).toBe("Stan"); expect(ctx?.date).toBe(789); }); it("handles legacy hidden sender names", () => { const ctx = normalizeForwardedContext({ forward_sender_name: "Legacy Hidden", forward_date: 111, } as any); expect(ctx).not.toBeNull(); expect(ctx?.from).toBe("Legacy Hidden"); expect(ctx?.fromType).toBe("legacy_hidden_user"); expect(ctx?.date).toBe(111); }); }); describe("expandTextLinks", () => { it("returns text unchanged when no entities are provided", () => { expect(expandTextLinks("Hello world")).toBe("Hello world"); expect(expandTextLinks("Hello world", null)).toBe("Hello world"); expect(expandTextLinks("Hello world", [])).toBe("Hello world"); }); it("returns text unchanged when there are no text_link entities", () => { const entities = [ { type: "mention", offset: 0, length: 5 }, { type: "bold", offset: 6, length: 5 }, ]; expect(expandTextLinks("@user hello", entities)).toBe("@user hello"); }); it("expands a single text_link entity", () => { const text = "Check this link for details"; const entities = [{ type: "text_link", offset: 11, length: 4, url: "https://example.com" }]; expect(expandTextLinks(text, entities)).toBe( "Check this [link](https://example.com) for details", ); }); it("expands multiple text_link entities", () => { const text = "Visit Google or GitHub for more"; const entities = [ { type: "text_link", offset: 6, length: 6, url: "https://google.com" }, { type: "text_link", offset: 16, length: 6, url: "https://github.com" }, ]; expect(expandTextLinks(text, entities)).toBe( "Visit [Google](https://google.com) or [GitHub](https://github.com) for more", ); }); it("handles adjacent text_link entities", () => { const text = "AB"; const entities = [ { type: "text_link", offset: 0, length: 1, url: "https://a.example" }, { type: "text_link", offset: 1, length: 1, url: "https://b.example" }, ]; expect(expandTextLinks(text, entities)).toBe("[A](https://a.example)[B](https://b.example)"); }); it("preserves offsets from the original string", () => { const text = " Hello world"; const entities = [{ type: "text_link", offset: 1, length: 5, url: "https://example.com" }]; expect(expandTextLinks(text, entities)).toBe(" [Hello](https://example.com) world"); }); });