Files
Memoh/internal/channel/adapters/telegram/markdown_test.go
Ran 6acdd191c7 Squashed commit of the following:
commit bcdb026ae43e4f95d0b2c4f9bd440a2df9d6b514
Author: Ran <16112591+chen-ran@users.noreply.github.com>
Date:   Thu Feb 12 17:10:32 2026 +0800

    chore: update DEVELOPMENT.md

commit 30281742ef
Merge: ca5c6a1 5b05f13
Author: BBQ <bbq@BBQdeMacBook-Air.local>
Date:   Thu Feb 12 15:49:17 2026 +0800

    merge(github/main): integrate fx dependency injection framework

    Merge upstream fx refactor and adapt all services to use go.uber.org/fx
    for dependency injection. Resolve conflicts in main.go, server.go,
    and service constructors while preserving our domain model changes.

    - Fix telegram adapter panic on shutdown (double close channel)
    - Fix feishu adapter processing messages after stop
    - Increase directory lookup timeout from 2s to 5s

commit ca5c6a1866
Author: BBQ <bbq@BBQdeMacBook-Air.local>
Date:   Thu Feb 12 15:33:09 2026 +0800

    refactor(core): restructure conversation, channel and message domains

    - Rename chat module to conversation with flow-based architecture
    - Move channelidentities into channel/identities subpackage
    - Add channel/route for routing logic
    - Add message service with event hub
    - Add MCP providers: container, directory, schedule
    - Refactor Feishu/Telegram adapters with directory and stream support
    - Add platform management page and channel badges in web UI
    - Update database schema for conversations, messages and channel routes
    - Add @memoh/shared package for cross-package type definitions

commit 75e2ef0467
Merge: d99ba38 01cb6c8
Author: BBQ <bbq@BBQdeMacBook-Air.local>
Date:   Thu Feb 12 14:45:49 2026 +0800

    merge(github): merge github/main, resolve index.ts URL conflict

    Keep our defensive absolute-URL check in createAuthFetcher.

commit d99ba38b7d
Merge: 860e20f 35ce7d1
Author: BBQ <bbq@BBQdeMacBook-Air.local>
Date:   Thu Feb 12 05:20:18 2026 +0800

    merge(github): merge github/main, keep our code and docs/spec

commit 860e20fe70
Author: BBQ <bbq@BBQdeMacBook-Air.local>
Date:   Wed Feb 11 22:13:27 2026 +0800

    docs(docs): add concepts and style guides for VitePress site

    - Add concepts: identity-and-binding, index (en/zh)
    - Add style: terminology (en/zh)
    - Update index and zh/index
    - Update .vitepress/config.ts

commit a75fdb8040
Author: BBQ <bbq@BBQdeMacBook-Air.local>
Date:   Wed Feb 11 17:37:16 2026 +0800

    refactor(mcp): standardize unified tool gateway on go-sdk

    Split business executors from federation sources and migrate unified tool/federation transports to the official go-sdk for stricter MCP compliance and safer session lifecycle handling. Add targeted regression tests for accept compatibility, initialization retries, pending cleanup, and include updated swagger artifacts.

commit 02b33c8e85
Author: BBQ <bbq@BBQdeMacBook-Air.local>
Date:   Wed Feb 11 15:42:21 2026 +0800

    refactor(core): finalize user-centric identity and policy cleanup

    Unify auth and chat identity semantics around user_id, enforce personal-bot owner-only authorization, and remove legacy compatibility branches in integration tests.

commit 06e8619a37
Author: BBQ <bbq@BBQdeMacBook-Air.local>
Date:   Wed Feb 11 14:47:03 2026 +0800

    refactor(core): migrate channel identity and binding across app

    Align channel identity and bind flow across backend and app-facing layers, including generated swagger artifacts and package lock updates while excluding docs content changes.
2026-02-12 17:13:03 +08:00

210 lines
5.6 KiB
Go

package telegram
import (
"strings"
"testing"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/memohai/memoh/internal/channel"
)
func TestMarkdownToTelegramHTML(t *testing.T) {
tests := []struct {
name string
input string
contains []string
absent []string
}{
{
name: "bold",
input: "this is **bold** text",
contains: []string{"<b>bold</b>"},
absent: []string{"**"},
},
{
name: "italic",
input: "this is *italic* text",
contains: []string{"<i>italic</i>"},
},
{
name: "bold and italic",
input: "**bold** and *italic*",
contains: []string{"<b>bold</b>", "<i>italic</i>"},
},
{
name: "strikethrough",
input: "this is ~~deleted~~ text",
contains: []string{"<s>deleted</s>"},
absent: []string{"~~"},
},
{
name: "inline code",
input: "use `fmt.Println` here",
contains: []string{"<code>fmt.Println</code>"},
absent: []string{"`fmt.Println`"},
},
{
name: "link",
input: "visit [Google](https://google.com)",
contains: []string{`<a href="https://google.com">Google</a>`},
},
{
name: "heading",
input: "# Title\nsome text",
contains: []string{"<b>Title</b>"},
absent: []string{"# Title"},
},
{
name: "unordered list",
input: "- first\n- second\n+ third",
contains: []string{"• first", "• second", "• third"},
},
{
name: "fenced code block",
input: "```go\nfmt.Println(\"hello\")\n```",
contains: []string{`<pre><code class="language-go">`, "fmt.Println", "</code></pre>"},
},
{
name: "fenced code block without language",
input: "```\nplain code\n```",
contains: []string{"<pre>plain code</pre>"},
},
{
name: "html entities escaped",
input: "a < b && c > d",
contains: []string{"&lt;", "&amp;&amp;", "&gt;"},
absent: []string{"< b", "> d"},
},
{
name: "code block preserves content",
input: "```\n**not bold** <tag>\n```",
contains: []string{"**not bold**", "&lt;tag&gt;"},
absent: []string{"<b>", "<tag>"},
},
{
name: "inline code preserves content",
input: "use `**not bold**` inline",
contains: []string{"<code>**not bold**</code>"},
absent: []string{"<b>"},
},
{
name: "blockquote",
input: "> quoted line\n> another line",
contains: []string{"<blockquote>"},
},
{
name: "empty input",
input: "",
contains: nil,
},
{
name: "plain text no conversion",
input: "just plain text here",
contains: []string{"just plain text here"},
},
{
name: "link with ampersand in url",
input: "[search](https://example.com?a=1&b=2)",
contains: []string{`<a href="https://example.com?a=1&amp;b=2">search</a>`},
},
{
name: "bold inside link",
input: "**[click here](https://example.com)**",
contains: []string{"<b>", `<a href="https://example.com">click here</a>`, "</b>"},
},
{
name: "mixed formatting",
input: "# Summary\n\n**Hello** world, visit [docs](https://docs.io).\n\n- item one\n- item two\n\n```python\nprint(\"hi\")\n```",
contains: []string{"<b>Summary</b>", "<b>Hello</b>", `<a href="https://docs.io">docs</a>`, "• item one", `class="language-python"`},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := markdownToTelegramHTML(tt.input)
for _, want := range tt.contains {
if !strings.Contains(result, want) {
t.Errorf("expected result to contain %q, got:\n%s", want, result)
}
}
for _, absent := range tt.absent {
if strings.Contains(result, absent) {
t.Errorf("expected result NOT to contain %q, got:\n%s", absent, result)
}
}
})
}
}
func TestFormatTelegramOutput(t *testing.T) {
tests := []struct {
name string
text string
format channel.MessageFormat
wantMode string
wantContains string
}{
{
name: "markdown format returns html mode",
text: "**bold**",
format: channel.MessageFormatMarkdown,
wantMode: tgbotapi.ModeHTML,
wantContains: "<b>bold</b>",
},
{
name: "plain format returns empty mode",
text: "hello",
format: channel.MessageFormatPlain,
wantMode: "",
},
{
name: "empty text returns empty mode",
text: "",
format: channel.MessageFormatMarkdown,
wantMode: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
text, mode := formatTelegramOutput(tt.text, tt.format)
if mode != tt.wantMode {
t.Errorf("expected mode %q, got %q", tt.wantMode, mode)
}
if tt.wantContains != "" && !strings.Contains(text, tt.wantContains) {
t.Errorf("expected text to contain %q, got %q", tt.wantContains, text)
}
})
}
}
func TestSplitCodeBlocks(t *testing.T) {
tests := []struct {
name string
input string
want int // expected number of segments
}{
{name: "no code blocks", input: "hello world", want: 1},
{name: "one code block", input: "before```code```after", want: 3},
{name: "two code blocks", input: "a```b```c```d```e", want: 5},
{name: "unclosed code block", input: "before```unclosed", want: 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
segments := splitCodeBlocks(tt.input)
if len(segments) != tt.want {
t.Errorf("expected %d segments, got %d: %v", tt.want, len(segments), segments)
}
})
}
}
func TestTelegramEscapeHTML(t *testing.T) {
input := `a < b & c > d`
result := telegramEscapeHTML(input)
if result != "a &lt; b &amp; c &gt; d" {
t.Errorf("unexpected escape result: %s", result)
}
}