Files
LocalAI/addons/telegram/transport/runtime_test.go
T
Ettore Di Giacinto c5f636d1b9 refactor(addons): move connector file bytes by handle only
A connector add-on no longer carries file bytes in the connector
envelope. The ops media and send_file need a transfer handle, and the
gateway refuses a call to them without one, or for a kind that declares
no files.

- The manifest field files.transfer, the inline file of a request and
  of an answer (ConnectorFile) and the 1 MiB cap
  (MaxConnectorFileBytes) are removed. The 2 MiB limit of a connector
  call between hosts drops to 1 MiB, because it holds text and no file.
- A kind that declares no files moves no file: the host answers a sender
  who attaches one with a plain sentence and does not offer attach_file.
  A direction left out of a declared files block still gets the host
  ceiling.
- The inline layer is gone from the file limit, from the API
  (inline_bytes) and from the Files section of the connector page.
- The Telegram and Slack add-ons keep only the streaming paths. The
  tests moved to the handle path, and the studio test rig gives every
  connector add-on a table of handles.

Assisted-by: Claude:claude-sonnet-5-5 go test node
2026-11-03 16:57:04 +00:00

119 lines
5.5 KiB
Go

// SPDX-License-Identifier: MIT
package transport_test
import (
"context"
"strings"
"github.com/mudler/LocalAI/addons/telegram/transport"
"github.com/mudler/LocalAI/pkg/addons/contract"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("the connector route", func() {
ctx := context.Background()
run := func(f *fakeBot) transport.Runtime { return transport.Runtime{API: f.client()} }
It("polls and answers the next offset past the last update, skipped ones included", func() {
f := newFakeBot()
f.reply["getUpdates"] = func(map[string]any) (int, string) {
return 200, `{"ok":true,"result":[{"update_id":7,"message":{"chat":{"id":1,"type":"private"},"from":{"id":1},"text":"a"}},{"update_id":8,"edited_message":{}}]}`
}
out, err := run(f).Connector(ctx, contract.ConnectorRequest{Op: "poll", Kind: "telegram", Token: secretToken, Offset: 5, TimeoutSeconds: 99})
Expect(err).NotTo(HaveOccurred())
Expect(out.Error).To(BeNil())
Expect(out.Updates).To(HaveLen(2))
Expect(out.NextOffset).To(Equal(int64(9)))
Expect(f.called("getUpdates")[0].Body["timeout"]).To(Equal(float64(contract.MaxPollSeconds)), "the wait is capped")
})
It("keeps the offset when there is no update", func() {
f := newFakeBot()
f.reply["getUpdates"] = func(map[string]any) (int, string) { return 200, `{"ok":true,"result":[]}` }
out, err := run(f).Connector(ctx, contract.ConnectorRequest{Op: "poll", Kind: "telegram", Token: secretToken, Offset: 5})
Expect(err).NotTo(HaveOccurred())
Expect(out.NextOffset).To(Equal(int64(5)))
})
It("splits a long text into parts of at most 4096 characters", func() {
f := newFakeBot()
f.reply["sendMessage"] = func(map[string]any) (int, string) { return 200, `{"ok":true,"result":{"message_id":4}}` }
_, err := run(f).Connector(ctx, contract.ConnectorRequest{Op: "send", Kind: "telegram", Token: secretToken, Chat: "9", Text: strings.Repeat("word ", 1900)})
Expect(err).NotTo(HaveOccurred())
calls := f.called("sendMessage")
Expect(calls).To(HaveLen(3))
for _, c := range calls {
Expect(len([]rune(c.Body["text"].(string)))).To(BeNumerically("<=", 4096))
}
})
DescribeTable("answers a failure of Telegram with a typed error",
func(code int, body, want string) {
f := newFakeBot()
f.reply["getUpdates"] = func(map[string]any) (int, string) { return code, body }
out, err := run(f).Connector(ctx, contract.ConnectorRequest{Op: "poll", Kind: "telegram", Token: secretToken})
Expect(err).NotTo(HaveOccurred())
Expect(out.Error).NotTo(BeNil())
Expect(out.Error.Code).To(Equal(want))
Expect(out.Error.Message).NotTo(ContainSubstring("SECRET"))
},
Entry("a refused token", 401, `{"ok":false,"error_code":401,"description":"x"}`, contract.ConnectorUnauthorized),
Entry("another reader", 409, `{"ok":false,"error_code":409,"description":"x"}`, contract.ConnectorConflict),
Entry("a wait", 429, `{"ok":false,"error_code":429,"description":"x","parameters":{"retry_after":3}}`, contract.ConnectorRateLimited),
)
It("answers who the bot is, types and edits", func() {
f := newFakeBot()
f.reply["getMe"] = func(map[string]any) (int, string) {
return 200, `{"ok":true,"result":{"id":99,"username":"helper_bot"}}`
}
r := run(f)
me, err := r.Connector(ctx, contract.ConnectorRequest{Op: "me", Kind: "telegram", Token: secretToken})
Expect(err).NotTo(HaveOccurred())
Expect([]string{me.BotID, me.BotUsername}).To(Equal([]string{"99", "helper_bot"}))
for _, req := range []contract.ConnectorRequest{{Op: "typing", Chat: "5"}, {Op: "edit", Chat: "5", MessageID: "6", Text: "x"}} {
req.Kind, req.Token = "telegram", secretToken
out, err := r.Connector(ctx, req)
Expect(err).NotTo(HaveOccurred())
Expect(out.Error).To(BeNil())
}
Expect(f.called("sendChatAction")).To(HaveLen(1))
Expect(f.called("editMessageText")).To(HaveLen(1))
})
It("refuses another kind, a missing token and an unknown op", func() {
r := run(newFakeBot())
for _, req := range []contract.ConnectorRequest{{Op: "poll", Kind: "slack", Token: "t"}, {Op: "poll", Kind: "telegram"}, {Op: "nope", Kind: "telegram", Token: "t"}} {
_, err := r.Connector(ctx, req)
Expect(err).To(MatchError(contract.ErrRefused))
}
})
It("replaces a working message with the answer and sends a file into the topic of the conversation", func() {
f := newFakeBot()
f.reply["sendDocument"] = func(map[string]any) (int, string) { return 200, `{"ok":true,"result":{"message_id":8}}` }
r := run(f)
r.Host = &memHost{src: []byte("hello")}
out, err := r.Connector(ctx, contract.ConnectorRequest{Op: "send", Kind: "telegram", Token: secretToken, Chat: "5", MessageID: "6", Text: "the answer"})
Expect(err).NotTo(HaveOccurred())
Expect(out.Error).To(BeNil())
Expect(out.MessageID).To(Equal("6"))
Expect(f.called("editMessageText")).To(HaveLen(1))
Expect(f.called("sendMessage")).To(BeEmpty())
out, err = r.Connector(ctx, contract.ConnectorRequest{Op: "send_file", Kind: "telegram", Token: secretToken, Chat: "5", Thread: "77",
Transfer: &contract.ConnectorTransfer{Handle: "h", Name: "notes.txt", MIME: "text/plain", Size: 5}})
Expect(err).NotTo(HaveOccurred())
Expect(out.Error).To(BeNil())
Expect(out.MessageID).To(Equal("8"))
Expect(f.called("sendDocument")[0].Body).To(HaveKeyWithValue("message_thread_id", "77"))
})
It("names no message when Telegram answers without its ID", func() {
f := newFakeBot()
out, err := run(f).Connector(ctx, contract.ConnectorRequest{Op: "send", Kind: "telegram", Token: secretToken, Chat: "5", Text: "x"})
Expect(err).NotTo(HaveOccurred())
Expect(out.MessageID).To(BeEmpty())
})
})