From 23b24519831e82b68757f36ed353327dc9871b35 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 3 Nov 2026 19:16:20 +0000 Subject: [PATCH] test(e2e): bind connectors with a kind, caps and file transfers The connector specs broke when bindings began to need a kind, to hold no capability until the owner enables it, and to move file bytes by handle only. The Telegram specs now name the kind. The Slack file spec enables files.upload and pairs the sender, since a sender without a role needs a pairing to send files. A test transport that can move files gets the transfer table of the application, so its add-ons declare the files ability as installed ones do. Assisted-by: Claude:claude-sonnet-5-5 go ginkgo --- core/application/studio_connectors.go | 8 ++++++++ tests/e2e/studio_slack_test.go | 17 +++++++++++++++-- tests/e2e/studio_telegram_test.go | 8 ++++---- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/core/application/studio_connectors.go b/core/application/studio_connectors.go index 9b17872ca7..8382d5f55e 100644 --- a/core/application/studio_connectors.go +++ b/core/application/studio_connectors.go @@ -108,6 +108,14 @@ func (a *Application) startStudioConnectors(svc *studio.Service) { var tr studio.ConnectorTransport = connectorTransport{a} if connectorTransportForTests != nil { tr = connectorTransportForTests + // A test transport that can move files by handle gets the table of + // this application, so its add-ons declare the files ability. + if x, ok := tr.(interface { + EnableTransfers(*hostops.Transfers) func() + }); ok { + // The servers it starts live as long as the test process. + _ = x.EnableTransfers(a.transfers()) + } } // A token is checked with the platform when it is saved, so a typo is // refused at once instead of switching the binding off later. diff --git a/tests/e2e/studio_slack_test.go b/tests/e2e/studio_slack_test.go index e794659584..9662f095f9 100644 --- a/tests/e2e/studio_slack_test.go +++ b/tests/e2e/studio_slack_test.go @@ -171,12 +171,25 @@ var _ = Describe("Chats Slack connector", Label("Chats", "Slack"), func() { }) It("hands a file of a message to the chat", func() { - bot, appToken, id := app(obj{"allow": []string{"U100"}}) + // A binding holds no capability until the owner enables it, and a sender + // without a role needs a pairing to receive files. + bot, appToken, id := app(obj{"allow": []string{"U100"}, "caps": []string{"files.upload"}}) + code, out := chatsDo(http.MethodPost, chatsBase()+"/connectors/"+id+"/pairing", nil) + Expect(code).To(Equal(http.StatusOK), string(out)) + f().Push(appToken, slacktest.DM("D100", "U100", decode(out)["code"].(string))) + Eventually(func() int { + code, out := chatsDo(http.MethodGet, chatsBase()+"/connectors/"+id, nil) + Expect(code).To(Equal(http.StatusOK), string(out)) + paired, _ := decode(out)["paired"].([]any) + return len(paired) + }, 30*time.Second, 200*time.Millisecond).Should(Equal(1)) + // The bot confirms the pairing with a post of its own. + before := len(answers(bot)()) f().AddFile("F777", "brief.txt", "text/plain", []byte("the brief")) ev := slacktest.DM("D100", "U100", "read the brief") ev["files"] = []any{map[string]any{"id": "F777"}} f().Push(appToken, ev) - Eventually(answers(bot), 90*time.Second, 200*time.Millisecond).Should(HaveLen(1)) + Eventually(answers(bot), 90*time.Second, 200*time.Millisecond).Should(HaveLen(before + 1)) chats := chatsOf(id) Expect(chats).To(HaveLen(1)) // The file is an attachment of the user line, apart from its words. diff --git a/tests/e2e/studio_telegram_test.go b/tests/e2e/studio_telegram_test.go index 1530a84359..8dbfcef398 100644 --- a/tests/e2e/studio_telegram_test.go +++ b/tests/e2e/studio_telegram_test.go @@ -40,7 +40,7 @@ var _ = Describe("Chats Telegram connector", Label("Chats", "Telegram"), func() bind := func(token string, agent string, extra obj) string { // The specs written before the reply modes count the answers as the // messages the bot sends; the live modes have their own specs. - body := obj{"name": fmt.Sprintf("Bot %d", time.Now().UnixNano()), "token": token, "agent_id": agent, "enabled": true, "reply_mode": "off"} + body := obj{"kind": "telegram", "name": fmt.Sprintf("Bot %d", time.Now().UnixNano()), "token": token, "agent_id": agent, "enabled": true, "reply_mode": "off"} for k, v := range extra { body[k] = v } @@ -260,9 +260,9 @@ var _ = Describe("Chats Telegram connector", Label("Chats", "Telegram"), func() Expect(code).To(Equal(http.StatusCreated), string(out)) agent := decode(out)["id"].(string) DeferCleanup(func() { chatsDo(http.MethodDelete, chatsBase()+"/agents/"+agent, nil) }) - code, out = chatsDo(http.MethodPost, chatsBase()+"/connectors", obj{"name": "x", "token": "7010:x", "agent_id": agent}) + code, out = chatsDo(http.MethodPost, chatsBase()+"/connectors", obj{"kind": "telegram", "name": "x", "token": "7010:x", "agent_id": agent}) Expect(code).To(Equal(http.StatusBadRequest), string(out)) - code, out = chatsDo(http.MethodPost, chatsBase()+"/connectors", obj{"name": "x", "token": "7010:x"}) + code, out = chatsDo(http.MethodPost, chatsBase()+"/connectors", obj{"kind": "telegram", "name": "x", "token": "7010:x"}) Expect(code).To(Equal(http.StatusBadRequest), "a binding without an agent: "+string(out)) }) @@ -323,7 +323,7 @@ var _ = Describe("Chats Telegram connector", Label("Chats", "Telegram"), func() token := "7014:typo" telegram.refuseToken(token, 401) DeferCleanup(func() { telegram.refuseToken(token, 0) }) - code, out := chatsDo(http.MethodPost, chatsBase()+"/connectors", obj{"name": "Typo", "token": token, "agent_id": newAgent()}) + code, out := chatsDo(http.MethodPost, chatsBase()+"/connectors", obj{"kind": "telegram", "name": "Typo", "token": token, "agent_id": newAgent()}) Expect(code).To(Equal(http.StatusBadRequest), string(out)) Expect(string(out)).To(ContainSubstring("refused the bot token")) })