fix(telegram): address copilot feedback, filter empty chunks and add word-boundary regression test
This commit is contained in:
parent
758a82e219
commit
bb5aa18f37
2 changed files with 65 additions and 5 deletions
|
|
@ -236,6 +236,7 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
splitIdx = smallerLen
|
splitIdx = smallerLen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we split at a valid index to guarantee forward progress.
|
||||||
if splitIdx <= 0 {
|
if splitIdx <= 0 {
|
||||||
splitIdx = 1
|
splitIdx = 1
|
||||||
}
|
}
|
||||||
|
|
@ -243,19 +244,31 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
// Attempt to split using the determined index, ensuring code block integrity is maintained.
|
// Attempt to split using the determined index, ensuring code block integrity is maintained.
|
||||||
subChunks := channels.SplitMessage(chunk, splitIdx)
|
subChunks := channels.SplitMessage(chunk, splitIdx)
|
||||||
|
|
||||||
// Force a manual split if the message remains monolithic after the attempt.
|
// Safety fallback: If SplitMessage failed to shorten the chunk, force a manual split.
|
||||||
if len(subChunks) == 1 && subChunks[0] == chunk {
|
if len(subChunks) == 1 && subChunks[0] == chunk {
|
||||||
part1 := string(runeChunk[:splitIdx])
|
part1 := string(runeChunk[:splitIdx])
|
||||||
|
subChunks = []string{part1}
|
||||||
|
|
||||||
nextStart := splitIdx
|
nextStart := splitIdx
|
||||||
if foundBreak && nextStart < len(runeChunk) {
|
if foundBreak && nextStart < len(runeChunk) {
|
||||||
nextStart++
|
nextStart++
|
||||||
}
|
}
|
||||||
|
if nextStart < len(runeChunk) {
|
||||||
part2 := string(runeChunk[nextStart:])
|
part2 := string(runeChunk[nextStart:])
|
||||||
subChunks = []string{part1, part2}
|
subChunks = append(subChunks, part2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out empty chunks to avoid sending empty messages to Telegram.
|
||||||
|
nonEmpty := make([]string, 0, len(subChunks))
|
||||||
|
for _, s := range subChunks {
|
||||||
|
if s != "" {
|
||||||
|
nonEmpty = append(nonEmpty, s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push sub-chunks back to the front of the queue
|
// Push sub-chunks back to the front of the queue
|
||||||
queue = append(subChunks, queue...)
|
queue = append(nonEmpty, queue...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,11 @@ func (s *stubCaller) Call(ctx context.Context, url string, data *ta.RequestData)
|
||||||
type stubConstructor struct{}
|
type stubConstructor struct{}
|
||||||
|
|
||||||
func (s *stubConstructor) JSONRequest(parameters any) (*ta.RequestData, error) {
|
func (s *stubConstructor) JSONRequest(parameters any) (*ta.RequestData, error) {
|
||||||
return &ta.RequestData{}, nil
|
b, _ := json.Marshal(parameters)
|
||||||
|
return &ta.RequestData{
|
||||||
|
ContentType: "application/json",
|
||||||
|
BodyRaw: b,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubConstructor) MultipartRequest(
|
func (s *stubConstructor) MultipartRequest(
|
||||||
|
|
@ -235,6 +239,49 @@ func TestSend_MarkdownShortButHTMLLong_MultipleCalls(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSend_HTMLOverflow_WordBoundary(t *testing.T) {
|
||||||
|
caller := &stubCaller{
|
||||||
|
callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
||||||
|
return successResponse(t), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ch := newTestChannel(t, caller)
|
||||||
|
|
||||||
|
// We want to force a split near index ~2600.
|
||||||
|
// Prefix of 430 bold units (6 chars each) = 2580 chars.
|
||||||
|
// Expansion per unit is 3 chars. 2580 + 430*3 = 3870.
|
||||||
|
prefix := strings.Repeat("**a** ", 430)
|
||||||
|
targetWord := "TARGETWORDTHATSTAYSTOGETHER"
|
||||||
|
suffix := strings.Repeat(" **b**", 250)
|
||||||
|
content := prefix + targetWord + suffix
|
||||||
|
|
||||||
|
err := ch.Send(context.Background(), bus.OutboundMessage{
|
||||||
|
ChatID: "123456",
|
||||||
|
Content: content,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
foundFullWord := false
|
||||||
|
for i, call := range caller.calls {
|
||||||
|
var params map[string]interface{}
|
||||||
|
_ = json.Unmarshal(call.Data.BodyRaw, ¶ms)
|
||||||
|
text, _ := params["text"].(string)
|
||||||
|
|
||||||
|
// The word might be wrapped in tags if we were unlucky,
|
||||||
|
// but since it's plain text in our 'content', it should stay plain.
|
||||||
|
hasWord := strings.Contains(text, targetWord)
|
||||||
|
t.Logf("Chunk %d length: %d, contains target word: %v", i, len(text), hasWord)
|
||||||
|
|
||||||
|
if hasWord {
|
||||||
|
foundFullWord = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, foundFullWord, "The target word should not be split between chunks")
|
||||||
|
}
|
||||||
|
|
||||||
func TestSend_NotRunning(t *testing.T) {
|
func TestSend_NotRunning(t *testing.T) {
|
||||||
caller := &stubCaller{
|
caller := &stubCaller{
|
||||||
callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue