From 0da21f7cf1908b60fa1bbaf8f59a7c9f1f1190c9 Mon Sep 17 00:00:00 2001 From: Andrei Varabyeu <andrei_varabyeu@epam.com> Date: Tue, 8 Aug 2023 10:57:34 +0200 Subject: [PATCH 1/2] Generalizes activity Handler interface. Adds HandlerFuncsMap struct that allows mapping handlers for any action type --- core/activity/handler.go | 39 +++++++---- core/bot_framework_adapter_test.go | 100 +++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 12 deletions(-) diff --git a/core/activity/handler.go b/core/activity/handler.go index 897562e..7b2e600 100644 --- a/core/activity/handler.go +++ b/core/activity/handler.go @@ -28,9 +28,20 @@ import ( // Handler acts as the interface for the client program to define actions on various events from connector service. type Handler interface { - OnMessage(context *TurnContext) (schema.Activity, error) - OnInvoke(context *TurnContext) (schema.Activity, error) - OnConversationUpdate(context *TurnContext) (schema.Activity, error) + On(context *TurnContext) (schema.Activity, error) +} + +// HandlerFuncsMap is an adaptor to let client program handler functions +// for all activity types, including not supported by HandlerFuncs adaptor +type HandlerFuncsMap struct { + Funcs map[schema.ActivityTypes]func(turn *TurnContext) (schema.Activity, error) +} + +func (hf HandlerFuncsMap) On(context *TurnContext) (schema.Activity, error) { + if handler, ok := hf.Funcs[context.Activity.Type]; ok { + return handler(context) + } + return schema.Activity{}, fmt.Errorf("Activity type %s not supported", context.Activity.Type) } // HandlerFuncs is an adaptor to let client program specify as many or @@ -42,6 +53,18 @@ type HandlerFuncs struct { OnConversationUpdateFunc func(turn *TurnContext) (schema.Activity, error) } +func (r HandlerFuncs) On(context *TurnContext) (schema.Activity, error) { + switch context.Activity.Type { + case schema.Message: + return r.OnMessage(context) + case schema.Invoke: + return r.OnInvoke(context) + case schema.ConversationUpdate: + return r.OnConversationUpdate(context) + } + return schema.Activity{}, fmt.Errorf("Activity type %s not supported yet", context.Activity.Type) +} + // OnMessage handles a 'message' event from connector service. func (r HandlerFuncs) OnMessage(turn *TurnContext) (schema.Activity, error) { if r.OnMessageFunc != nil { @@ -69,13 +92,5 @@ func (r HandlerFuncs) OnInvoke(turn *TurnContext) (schema.Activity, error) { // PrepareActivityContext routes the received Activity to respective handler function. // Returns the result of the handler function. func PrepareActivityContext(handler Handler, context *TurnContext) (schema.Activity, error) { - switch context.Activity.Type { - case schema.Message: - return handler.OnMessage(context) - case schema.Invoke: - return handler.OnInvoke(context) - case schema.ConversationUpdate: - return handler.OnConversationUpdate(context) - } - return schema.Activity{}, fmt.Errorf("Activity type %s not supported yet", context.Activity.Type) + return handler.On(context) } diff --git a/core/bot_framework_adapter_test.go b/core/bot_framework_adapter_test.go index 6e80657..5838287 100644 --- a/core/bot_framework_adapter_test.go +++ b/core/bot_framework_adapter_test.go @@ -177,3 +177,103 @@ func TestActivityUpdate(t *testing.T) { handler.ServeHTTP(rr, req) assert.Equal(t, rr.Code, 200, "Expect 200 response status") } + +func TestActivityCustom(t *testing.T) { + srv := serverMock(t) + + type args struct { + Activity schema.Activity + Handler activity.Handler + } + + tests := []struct { + name string + args args + wantErr bool + }{ + { + args: args{ + Activity: schema.Activity{ + Type: schema.InstallationUpdate, + Conversation: schema.ConversationAccount{ + ID: "abcd1234", + Name: "Convo1", + }, + MembersAdded: []schema.ChannelAccount{ + { + ID: "123456", + Name: "Some member name", + Role: schema.BOT, + }, + }, + Text: "Message from Teams Client", + ReplyToID: "5d5cdc723", + ServiceURL: srv.URL, + }, + Handler: activity.HandlerFuncsMap{ + Funcs: map[schema.ActivityTypes]func(turn *activity.TurnContext) (schema.Activity, error){ + schema.InstallationUpdate: func(turn *activity.TurnContext) (schema.Activity, error) { + return turn.SendActivity(activity.MsgOptionText("supported")) + }, + }, + }, + }, + }, + { + args: args{ + Activity: schema.Activity{ + Type: schema.InstallationUpdate, + Conversation: schema.ConversationAccount{ + ID: "abcd1234", + Name: "Convo1", + }, + MembersAdded: []schema.ChannelAccount{ + { + ID: "123456", + Name: "Some member name", + Role: schema.BOT, + }, + }, + Text: "Message from Teams Client", + ReplyToID: "5d5cdc723", + ServiceURL: srv.URL, + }, + Handler: activity.HandlerFuncs{}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + ctx := context.Background() + setting := core.AdapterSetting{ + AppID: "asdasd", + AppPassword: "cfg.MicrosoftTeams.AppPassword", + } + setting.CredentialProvider = auth.SimpleCredentialProvider{ + AppID: setting.AppID, + Password: setting.AppPassword, + } + clientConfig, err := client.NewClientConfig(setting.CredentialProvider, auth.ToChannelFromBotLoginURL[0]) + assert.Nil(t, err, fmt.Sprintf("Failed with error %s", err)) + connectorClient, err := client.NewClient(clientConfig) + assert.Nil(t, err, fmt.Sprintf("Failed with error %s", err)) + adapter := core.BotFrameworkAdapter{setting, &core.MockTokenValidator{}, connectorClient} + act, err := adapter.ParseRequest(ctx, req) + + err = adapter.ProcessActivity(ctx, act, tt.args.Handler) + assert.True(t, (err != nil) == tt.wantErr, fmt.Sprintf("Failed with error %s", err)) + }) + rr := httptest.NewRecorder() + bodyJSON, err := json.Marshal(tt.args.Activity) + assert.Nil(t, err, fmt.Sprintf("Failed with error %s", err)) + bodyBytes := bytes.NewReader(bodyJSON) + req, err := http.NewRequest(http.MethodPost, "/api/messages", bodyBytes) + assert.Nil(t, err, fmt.Sprintf("Failed with error %s", err)) + req.Header.Set("Authorization", "Bearer abc123") + handler.ServeHTTP(rr, req) + assert.Equal(t, rr.Code, 200, "Expect 200 response status") + }) + } +} From 503ace3d3e8143a14b4d09a9444a68784ab5c9c3 Mon Sep 17 00:00:00 2001 From: Andrei Varabyeu <andrei_varabyeu@epam.com> Date: Tue, 22 Aug 2023 11:59:43 +0200 Subject: [PATCH 2/2] make HandlerFuncsMap as map type instead of struct --- core/activity/handler.go | 6 ++---- core/bot_framework_adapter_test.go | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/core/activity/handler.go b/core/activity/handler.go index 7b2e600..593c4ca 100644 --- a/core/activity/handler.go +++ b/core/activity/handler.go @@ -33,12 +33,10 @@ type Handler interface { // HandlerFuncsMap is an adaptor to let client program handler functions // for all activity types, including not supported by HandlerFuncs adaptor -type HandlerFuncsMap struct { - Funcs map[schema.ActivityTypes]func(turn *TurnContext) (schema.Activity, error) -} +type HandlerFuncsMap map[schema.ActivityTypes]func(turn *TurnContext) (schema.Activity, error) func (hf HandlerFuncsMap) On(context *TurnContext) (schema.Activity, error) { - if handler, ok := hf.Funcs[context.Activity.Type]; ok { + if handler, ok := hf[context.Activity.Type]; ok { return handler(context) } return schema.Activity{}, fmt.Errorf("Activity type %s not supported", context.Activity.Type) diff --git a/core/bot_framework_adapter_test.go b/core/bot_framework_adapter_test.go index 5838287..749564f 100644 --- a/core/bot_framework_adapter_test.go +++ b/core/bot_framework_adapter_test.go @@ -211,10 +211,8 @@ func TestActivityCustom(t *testing.T) { ServiceURL: srv.URL, }, Handler: activity.HandlerFuncsMap{ - Funcs: map[schema.ActivityTypes]func(turn *activity.TurnContext) (schema.Activity, error){ - schema.InstallationUpdate: func(turn *activity.TurnContext) (schema.Activity, error) { - return turn.SendActivity(activity.MsgOptionText("supported")) - }, + schema.InstallationUpdate: func(turn *activity.TurnContext) (schema.Activity, error) { + return turn.SendActivity(activity.MsgOptionText("supported")) }, }, },