package signalr import ( "errors" "fmt" "io/ioutil" "net/http" "strings" "sync/atomic" "testing" "github.com/dapr/components-contrib/bindings" "github.com/dapr/dapr/pkg/logger" "github.com/stretchr/testify/assert" ) func TestConfigurationValid(t *testing.T) { tests := []struct { name string properties map[string]string expectedEndpoint string expectedAccessKey string expectedVersion string expectedHub string }{ { "With all properties", map[string]string{ "connectionString": "Endpoint=https://fake.service.signalr.net;AccessKey=fakekey;Version=1.0;", }, "https://fake.service.signalr.net", "fakekey", "1.0", "", }, { "With missing version", map[string]string{ "connectionString": "Endpoint=https://fake.service.signalr.net;AccessKey=fakekey;", }, "https://fake.service.signalr.net", "fakekey", "", "", }, { "With semicolon after access key", map[string]string{ "connectionString": "Endpoint=https://fake.service.signalr.net;AccessKey=fakekey", }, "https://fake.service.signalr.net", "fakekey", "", "", }, { "With trailing slash in endpoint", map[string]string{ "connectionString": "Endpoint=https://fake.service.signalr.net/;AccessKey=fakekey;Version=1.1", }, "https://fake.service.signalr.net", "fakekey", "1.1", "", }, { "With hub", map[string]string{ "connectionString": "Endpoint=https://fake.service.signalr.net/;AccessKey=fakekey;Version=1.1", "hub": "myhub", }, "https://fake.service.signalr.net", "fakekey", "1.1", "myhub", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := NewSignalR(logger.NewLogger("test")) err := s.Init(bindings.Metadata{Properties: tt.properties}) assert.Nil(t, err) assert.Equal(t, tt.expectedEndpoint, s.endpoint) assert.Equal(t, tt.expectedAccessKey, s.accessKey) assert.Equal(t, tt.expectedVersion, s.version) assert.Equal(t, tt.expectedHub, s.hub) }) } } func TestInvalidConfigurations(t *testing.T) { tests := []struct { name string properties map[string]string }{ { "Empty properties", map[string]string{}, }, { "Empty connection string", map[string]string{ "connectionString": "", }, }, { "White spaces in connection string", map[string]string{ "connectionString": " ", }, }, { "Misspelled connection string", map[string]string{ "connectionString1": "Endpoint=https://fake.service.signalr.net;AccessKey=fakekey;", }, }, { "Missing endpoint", map[string]string{ "connectionString": "AccessKey=fakekey;", }, }, { "Missing access key", map[string]string{ "connectionString1": "Endpoint=https://fake.service.signalr.net;", }, }, { "With empty endpoint value", map[string]string{ "connectionString": "Endpoint=;AccessKey=fakekey;Version=1.1", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := NewSignalR(logger.NewLogger("test")) err := s.Init(bindings.Metadata{Properties: tt.properties}) assert.NotNil(t, err) }) } } type mockTransport struct { response *http.Response errToReturn error request *http.Request requestCount int32 } func (t *mockTransport) reset() { atomic.StoreInt32(&t.requestCount, 0) t.request = nil } func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { atomic.AddInt32(&t.requestCount, 1) t.request = req return t.response, t.errToReturn } func TestWriteShouldFail(t *testing.T) { httpTransport := &mockTransport{ response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))}, } s := NewSignalR(logger.NewLogger("test")) s.endpoint = "https://fake.service.signalr.net" s.accessKey = "G7+nIt9n48+iYSltPRf1v8kE+MupFfEt/9NSNTKOdzA=" s.httpClient = &http.Client{ Transport: httpTransport, } t.Run("Missing hub should fail", func(t *testing.T) { httpTransport.reset() _, err := s.Invoke(&bindings.InvokeRequest{ Data: []byte("hello world"), Metadata: map[string]string{}, }) assert.NotNil(t, err) }) t.Run("SignalR call failed should be returned", func(t *testing.T) { httpTransport.reset() httpErr := errors.New("fake error") httpTransport.errToReturn = httpErr _, err := s.Invoke(&bindings.InvokeRequest{ Data: []byte("hello world"), Metadata: map[string]string{ hubKey: "testHub", }, }) assert.NotNil(t, err) assert.Contains(t, err.Error(), httpErr.Error()) }) t.Run("SignalR call returns status != [200, 202]", func(t *testing.T) { httpTransport.reset() httpTransport.response.StatusCode = 401 _, err := s.Invoke(&bindings.InvokeRequest{ Data: []byte("hello world"), Metadata: map[string]string{ hubKey: "testHub", }, }) assert.NotNil(t, err) }) } func TestWriteShouldSucceed(t *testing.T) { httpTransport := &mockTransport{ response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))}, } s := NewSignalR(logger.NewLogger("test")) s.endpoint = "https://fake.service.signalr.net" s.accessKey = "fakekey" s.httpClient = &http.Client{ Transport: httpTransport, } t.Run("Has authorization", func(t *testing.T) { httpTransport.reset() _, err := s.Invoke(&bindings.InvokeRequest{ Data: []byte("hello world"), Metadata: map[string]string{ hubKey: "testHub", }, }) assert.Nil(t, err) actualAuthorization := httpTransport.request.Header.Get("Authorization") assert.NotEmpty(t, actualAuthorization) assert.True(t, strings.HasPrefix(actualAuthorization, "Bearer "), fmt.Sprintf("expecting to start with 'Bearer ', but was '%s'", actualAuthorization)) }) tests := []struct { name string hubInWriteRequest string hubInMetadata string groupID string userID string expectedURL string }{ {"Broadcast receiving hub should call SignalR service", "testHub", "", "", "", "https://fake.service.signalr.net/api/v1/hubs/testHub"}, {"Broadcast with hub metadata should call SignalR service", "", "testHub", "", "", "https://fake.service.signalr.net/api/v1/hubs/testHub"}, {"Group receiving hub should call SignalR service", "testHub", "", "mygroup", "", "https://fake.service.signalr.net/api/v1/hubs/testHub/groups/mygroup"}, {"Group with hub metadata should call SignalR service", "", "testHub", "mygroup", "", "https://fake.service.signalr.net/api/v1/hubs/testHub/groups/mygroup"}, {"User receiving hub should call SignalR service", "testHub", "", "", "myuser", "https://fake.service.signalr.net/api/v1/hubs/testHub/users/myuser"}, {"User with hub metadata should call SignalR service", "", "testHub", "", "myuser", "https://fake.service.signalr.net/api/v1/hubs/testHub/users/myuser"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { httpTransport.reset() s.hub = tt.hubInMetadata _, err := s.Invoke(&bindings.InvokeRequest{ Data: []byte("hello world"), Metadata: map[string]string{ hubKey: tt.hubInWriteRequest, userKey: tt.userID, groupKey: tt.groupID, }, }) assert.Nil(t, err) assert.Equal(t, int32(1), httpTransport.requestCount) assert.Equal(t, tt.expectedURL, httpTransport.request.URL.String()) assert.NotNil(t, httpTransport.request) assert.Equal(t, "application/json", httpTransport.request.Header.Get("Content-Type")) }) } }