github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/services/telemetry/telemetry_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package telemetry 5 6 import ( 7 "crypto/ecdsa" 8 "encoding/json" 9 "io/ioutil" 10 "net/http" 11 "net/http/httptest" 12 "os" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/mock" 19 "github.com/stretchr/testify/require" 20 21 "github.com/mattermost/mattermost-server/v5/mlog" 22 "github.com/mattermost/mattermost-server/v5/model" 23 "github.com/mattermost/mattermost-server/v5/plugin" 24 "github.com/mattermost/mattermost-server/v5/plugin/plugintest" 25 "github.com/mattermost/mattermost-server/v5/services/httpservice" 26 "github.com/mattermost/mattermost-server/v5/services/searchengine" 27 "github.com/mattermost/mattermost-server/v5/services/telemetry/mocks" 28 storeMocks "github.com/mattermost/mattermost-server/v5/store/storetest/mocks" 29 ) 30 31 type FakeConfigService struct { 32 cfg *model.Config 33 } 34 35 func (fcs *FakeConfigService) Config() *model.Config { return fcs.cfg } 36 func (fcs *FakeConfigService) AddConfigListener(f func(old, current *model.Config)) string { return "" } 37 func (fcs *FakeConfigService) RemoveConfigListener(key string) {} 38 func (fcs *FakeConfigService) AsymmetricSigningKey() *ecdsa.PrivateKey { return nil } 39 40 func initializeMocks(cfg *model.Config) (*mocks.ServerIface, *storeMocks.Store, func(t *testing.T), func()) { 41 serverIfaceMock := &mocks.ServerIface{} 42 43 configService := &FakeConfigService{cfg} 44 serverIfaceMock.On("Config").Return(cfg) 45 serverIfaceMock.On("IsLeader").Return(true) 46 47 pluginDir, _ := ioutil.TempDir("", "") 48 webappPluginDir, _ := ioutil.TempDir("", "") 49 cleanUp := func() { 50 os.RemoveAll(pluginDir) 51 os.RemoveAll(webappPluginDir) 52 } 53 pluginsAPIMock := &plugintest.API{} 54 pluginEnv, _ := plugin.NewEnvironment(func(m *model.Manifest) plugin.API { return pluginsAPIMock }, pluginDir, webappPluginDir, mlog.NewLogger(&mlog.LoggerConfiguration{}), nil) 55 serverIfaceMock.On("GetPluginsEnvironment").Return(pluginEnv, nil) 56 57 serverIfaceMock.On("License").Return(model.NewTestLicense(), nil) 58 serverIfaceMock.On("GetRoleByName", "system_admin").Return(&model.Role{Permissions: []string{"sa-test1", "sa-test2"}}, nil) 59 serverIfaceMock.On("GetRoleByName", "system_user").Return(&model.Role{Permissions: []string{"su-test1", "su-test2"}}, nil) 60 serverIfaceMock.On("GetRoleByName", "system_user_manager").Return(&model.Role{Permissions: []string{"sum-test1", "sum-test2"}}, nil) 61 serverIfaceMock.On("GetRoleByName", "system_manager").Return(&model.Role{Permissions: []string{"sm-test1", "sm-test2"}}, nil) 62 serverIfaceMock.On("GetRoleByName", "system_read_only_admin").Return(&model.Role{Permissions: []string{"sra-test1", "sra-test2"}}, nil) 63 serverIfaceMock.On("GetRoleByName", "team_admin").Return(&model.Role{Permissions: []string{"ta-test1", "ta-test2"}}, nil) 64 serverIfaceMock.On("GetRoleByName", "team_user").Return(&model.Role{Permissions: []string{"tu-test1", "tu-test2"}}, nil) 65 serverIfaceMock.On("GetRoleByName", "team_guest").Return(&model.Role{Permissions: []string{"tg-test1", "tg-test2"}}, nil) 66 serverIfaceMock.On("GetRoleByName", "channel_admin").Return(&model.Role{Permissions: []string{"ca-test1", "ca-test2"}}, nil) 67 serverIfaceMock.On("GetRoleByName", "channel_user").Return(&model.Role{Permissions: []string{"cu-test1", "cu-test2"}}, nil) 68 serverIfaceMock.On("GetRoleByName", "channel_guest").Return(&model.Role{Permissions: []string{"cg-test1", "cg-test2"}}, nil) 69 serverIfaceMock.On("GetSchemes", "team", 0, 100).Return([]*model.Scheme{}, nil) 70 serverIfaceMock.On("HttpService").Return(httpservice.MakeHTTPService(configService)) 71 72 storeMock := &storeMocks.Store{} 73 storeMock.On("GetDbVersion", false).Return("5.24.0", nil) 74 75 systemStore := storeMocks.SystemStore{} 76 props := model.StringMap{} 77 props[model.SYSTEM_TELEMETRY_ID] = "test" 78 systemStore.On("Get").Return(props, nil) 79 systemStore.On("GetByName", model.ADVANCED_PERMISSIONS_MIGRATION_KEY).Return(nil, nil) 80 systemStore.On("GetByName", model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2).Return(nil, nil) 81 82 userStore := storeMocks.UserStore{} 83 userStore.On("Count", model.UserCountOptions{IncludeBotAccounts: false, IncludeDeleted: true, ExcludeRegularUsers: false, TeamId: "", ViewRestrictions: nil}).Return(int64(10), nil) 84 userStore.On("Count", model.UserCountOptions{IncludeBotAccounts: true, IncludeDeleted: false, ExcludeRegularUsers: true, TeamId: "", ViewRestrictions: nil}).Return(int64(100), nil) 85 userStore.On("Count", model.UserCountOptions{Roles: []string{model.SYSTEM_MANAGER_ROLE_ID}}).Return(int64(5), nil) 86 userStore.On("Count", model.UserCountOptions{Roles: []string{model.SYSTEM_USER_MANAGER_ROLE_ID}}).Return(int64(10), nil) 87 userStore.On("Count", model.UserCountOptions{Roles: []string{model.SYSTEM_READ_ONLY_ADMIN_ROLE_ID}}).Return(int64(15), nil) 88 userStore.On("AnalyticsGetGuestCount").Return(int64(11), nil) 89 userStore.On("AnalyticsActiveCount", mock.Anything, model.UserCountOptions{IncludeBotAccounts: false, IncludeDeleted: false, ExcludeRegularUsers: false, TeamId: "", ViewRestrictions: nil}).Return(int64(5), nil) 90 userStore.On("AnalyticsGetInactiveUsersCount").Return(int64(8), nil) 91 userStore.On("AnalyticsGetSystemAdminCount").Return(int64(9), nil) 92 93 teamStore := storeMocks.TeamStore{} 94 teamStore.On("AnalyticsTeamCount", false).Return(int64(3), nil) 95 teamStore.On("GroupSyncedTeamCount").Return(int64(16), nil) 96 97 channelStore := storeMocks.ChannelStore{} 98 channelStore.On("AnalyticsTypeCount", "", "O").Return(int64(25), nil) 99 channelStore.On("AnalyticsTypeCount", "", "P").Return(int64(26), nil) 100 channelStore.On("AnalyticsTypeCount", "", "D").Return(int64(27), nil) 101 channelStore.On("AnalyticsDeletedTypeCount", "", "O").Return(int64(22), nil) 102 channelStore.On("AnalyticsDeletedTypeCount", "", "P").Return(int64(23), nil) 103 channelStore.On("GroupSyncedChannelCount").Return(int64(17), nil) 104 105 postStore := storeMocks.PostStore{} 106 postStore.On("AnalyticsPostCount", "", false, false).Return(int64(1000), nil) 107 postStore.On("AnalyticsPostCountsByDay", &model.AnalyticsPostCountsOptions{TeamId: "", BotsOnly: false, YesterdayOnly: true}).Return(model.AnalyticsRows{}, nil) 108 postStore.On("AnalyticsPostCountsByDay", &model.AnalyticsPostCountsOptions{TeamId: "", BotsOnly: true, YesterdayOnly: true}).Return(model.AnalyticsRows{}, nil) 109 110 commandStore := storeMocks.CommandStore{} 111 commandStore.On("AnalyticsCommandCount", "").Return(int64(15), nil) 112 113 webhookStore := storeMocks.WebhookStore{} 114 webhookStore.On("AnalyticsIncomingCount", "").Return(int64(16), nil) 115 webhookStore.On("AnalyticsOutgoingCount", "").Return(int64(17), nil) 116 117 groupStore := storeMocks.GroupStore{} 118 groupStore.On("GroupCount").Return(int64(25), nil) 119 groupStore.On("GroupTeamCount").Return(int64(26), nil) 120 groupStore.On("GroupChannelCount").Return(int64(27), nil) 121 groupStore.On("GroupMemberCount").Return(int64(32), nil) 122 groupStore.On("DistinctGroupMemberCount").Return(int64(22), nil) 123 groupStore.On("GroupCountWithAllowReference").Return(int64(13), nil) 124 125 schemeStore := storeMocks.SchemeStore{} 126 schemeStore.On("CountByScope", "channel").Return(int64(8), nil) 127 schemeStore.On("CountByScope", "team").Return(int64(7), nil) 128 schemeStore.On("CountWithoutPermission", "channel", "create_post", model.RoleScopeChannel, model.RoleTypeUser).Return(int64(6), nil) 129 schemeStore.On("CountWithoutPermission", "channel", "create_post", model.RoleScopeChannel, model.RoleTypeGuest).Return(int64(7), nil) 130 schemeStore.On("CountWithoutPermission", "channel", "add_reaction", model.RoleScopeChannel, model.RoleTypeUser).Return(int64(8), nil) 131 schemeStore.On("CountWithoutPermission", "channel", "add_reaction", model.RoleScopeChannel, model.RoleTypeGuest).Return(int64(9), nil) 132 schemeStore.On("CountWithoutPermission", "channel", "manage_public_channel_members", model.RoleScopeChannel, model.RoleTypeUser).Return(int64(10), nil) 133 schemeStore.On("CountWithoutPermission", "channel", "use_channel_mentions", model.RoleScopeChannel, model.RoleTypeUser).Return(int64(11), nil) 134 schemeStore.On("CountWithoutPermission", "channel", "use_channel_mentions", model.RoleScopeChannel, model.RoleTypeGuest).Return(int64(12), nil) 135 136 storeMock.On("System").Return(&systemStore) 137 storeMock.On("User").Return(&userStore) 138 storeMock.On("Team").Return(&teamStore) 139 storeMock.On("Channel").Return(&channelStore) 140 storeMock.On("Post").Return(&postStore) 141 storeMock.On("Command").Return(&commandStore) 142 storeMock.On("Webhook").Return(&webhookStore) 143 storeMock.On("Group").Return(&groupStore) 144 storeMock.On("Scheme").Return(&schemeStore) 145 146 return serverIfaceMock, storeMock, func(t *testing.T) { 147 serverIfaceMock.AssertExpectations(t) 148 storeMock.AssertExpectations(t) 149 systemStore.AssertExpectations(t) 150 pluginsAPIMock.AssertExpectations(t) 151 }, cleanUp 152 } 153 154 func TestPluginSetting(t *testing.T) { 155 settings := &model.PluginSettings{ 156 Plugins: map[string]map[string]interface{}{ 157 "test": { 158 "foo": "bar", 159 }, 160 }, 161 } 162 assert.Equal(t, "bar", pluginSetting(settings, "test", "foo", "asd")) 163 assert.Equal(t, "asd", pluginSetting(settings, "test", "qwe", "asd")) 164 } 165 166 func TestPluginActivated(t *testing.T) { 167 states := map[string]*model.PluginState{ 168 "foo": { 169 Enable: true, 170 }, 171 "bar": { 172 Enable: false, 173 }, 174 } 175 assert.True(t, pluginActivated(states, "foo")) 176 assert.False(t, pluginActivated(states, "bar")) 177 assert.False(t, pluginActivated(states, "none")) 178 } 179 180 func TestPluginVersion(t *testing.T) { 181 plugins := []*model.BundleInfo{ 182 { 183 Manifest: &model.Manifest{ 184 Id: "test.plugin", 185 Version: "1.2.3", 186 }, 187 }, 188 { 189 Manifest: &model.Manifest{ 190 Id: "test.plugin2", 191 Version: "4.5.6", 192 }, 193 }, 194 } 195 assert.Equal(t, "1.2.3", pluginVersion(plugins, "test.plugin")) 196 assert.Equal(t, "4.5.6", pluginVersion(plugins, "test.plugin2")) 197 assert.Empty(t, pluginVersion(plugins, "unknown.plugin")) 198 } 199 200 func TestRudderTelemetry(t *testing.T) { 201 if testing.Short() { 202 t.SkipNow() 203 } 204 205 type batch struct { 206 MessageId string 207 UserId string 208 Event string 209 Timestamp time.Time 210 Properties map[string]interface{} 211 } 212 213 type payload struct { 214 MessageId string 215 SentAt time.Time 216 Batch []struct { 217 MessageId string 218 UserId string 219 Event string 220 Timestamp time.Time 221 Properties map[string]interface{} 222 } 223 Context struct { 224 Library struct { 225 Name string 226 Version string 227 } 228 } 229 } 230 231 data := make(chan payload, 100) 232 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 233 body, err := ioutil.ReadAll(r.Body) 234 require.NoError(t, err) 235 236 var p payload 237 err = json.Unmarshal(body, &p) 238 require.NoError(t, err) 239 240 data <- p 241 })) 242 defer server.Close() 243 244 marketplaceServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 245 res.WriteHeader(http.StatusOK) 246 json, err := json.Marshal([]*model.MarketplacePlugin{{ 247 BaseMarketplacePlugin: &model.BaseMarketplacePlugin{ 248 Manifest: &model.Manifest{ 249 Id: "testplugin", 250 }, 251 }, 252 }}) 253 require.NoError(t, err) 254 res.Write(json) 255 })) 256 257 defer func() { marketplaceServer.Close() }() 258 259 telemetryID := "test-telemetry-id-12345" 260 261 cfg := &model.Config{} 262 cfg.SetDefaults() 263 serverIfaceMock, storeMock, deferredAssertions, cleanUp := initializeMocks(cfg) 264 defer cleanUp() 265 defer deferredAssertions(t) 266 267 telemetryService := New(serverIfaceMock, storeMock, searchengine.NewBroker(cfg, nil), mlog.NewLogger(&mlog.LoggerConfiguration{})) 268 telemetryService.TelemetryID = telemetryID 269 telemetryService.rudderClient = nil 270 telemetryService.initRudder(server.URL, RudderKey) 271 272 assertPayload := func(t *testing.T, actual payload, event string, properties map[string]interface{}) { 273 t.Helper() 274 assert.NotEmpty(t, actual.MessageId) 275 assert.False(t, actual.SentAt.IsZero()) 276 if assert.Len(t, actual.Batch, 1) { 277 assert.NotEmpty(t, actual.Batch[0].MessageId, "message id should not be empty") 278 assert.Equal(t, telemetryID, actual.Batch[0].UserId) 279 if event != "" { 280 assert.Equal(t, event, actual.Batch[0].Event) 281 } 282 assert.False(t, actual.Batch[0].Timestamp.IsZero(), "batch timestamp should not be the zero value") 283 if properties != nil { 284 assert.Equal(t, properties, actual.Batch[0].Properties) 285 } 286 } 287 assert.Equal(t, "analytics-go", actual.Context.Library.Name) 288 assert.Equal(t, "3.0.0", actual.Context.Library.Version) 289 } 290 291 collectInfo := func(info *[]string) { 292 t.Helper() 293 for { 294 select { 295 case result := <-data: 296 assertPayload(t, result, "", nil) 297 *info = append(*info, result.Batch[0].Event) 298 case <-time.After(time.Second * 1): 299 return 300 } 301 } 302 } 303 304 collectBatches := func(info *[]batch) { 305 t.Helper() 306 for { 307 select { 308 case result := <-data: 309 assertPayload(t, result, "", nil) 310 *info = append(*info, result.Batch[0]) 311 case <-time.After(time.Second * 1): 312 return 313 } 314 } 315 } 316 317 // Should send a client identify message 318 select { 319 case identifyMessage := <-data: 320 assertPayload(t, identifyMessage, "", nil) 321 case <-time.After(time.Second * 1): 322 require.Fail(t, "Did not receive ID message") 323 } 324 325 t.Run("Send", func(t *testing.T) { 326 testValue := "test-send-value-6789" 327 telemetryService.sendTelemetry("Testing Telemetry", map[string]interface{}{ 328 "hey": testValue, 329 }) 330 select { 331 case result := <-data: 332 assertPayload(t, result, "Testing Telemetry", map[string]interface{}{ 333 "hey": testValue, 334 }) 335 case <-time.After(time.Second * 1): 336 require.Fail(t, "Did not receive telemetry") 337 } 338 }) 339 340 // Plugins remain disabled at this point 341 t.Run("SendDailyTelemetryPluginsDisabled", func(t *testing.T) { 342 telemetryService.sendDailyTelemetry(true) 343 344 var info []string 345 // Collect the info sent. 346 collectInfo(&info) 347 348 for _, item := range []string{ 349 TrackConfigService, 350 TrackConfigTeam, 351 TrackConfigSQL, 352 TrackConfigLog, 353 TrackConfigNotificationLog, 354 TrackConfigFile, 355 TrackConfigRate, 356 TrackConfigEmail, 357 TrackConfigPrivacy, 358 TrackConfigOauth, 359 TrackConfigLDAP, 360 TrackConfigCompliance, 361 TrackConfigLocalization, 362 TrackConfigSAML, 363 TrackConfigPassword, 364 TrackConfigCluster, 365 TrackConfigMetrics, 366 TrackConfigSupport, 367 TrackConfigNativeApp, 368 TrackConfigExperimental, 369 TrackConfigAnalytics, 370 TrackConfigPlugin, 371 TrackActivity, 372 TrackServer, 373 TrackConfigMessageExport, 374 TrackPlugins, 375 } { 376 require.Contains(t, info, item) 377 } 378 }) 379 380 // Enable plugins for the remainder of the tests. 381 // th.Server.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true }) 382 383 t.Run("SendDailyTelemetry", func(t *testing.T) { 384 telemetryService.sendDailyTelemetry(true) 385 386 var info []string 387 // Collect the info sent. 388 collectInfo(&info) 389 390 for _, item := range []string{ 391 TrackConfigService, 392 TrackConfigTeam, 393 TrackConfigSQL, 394 TrackConfigLog, 395 TrackConfigNotificationLog, 396 TrackConfigFile, 397 TrackConfigRate, 398 TrackConfigEmail, 399 TrackConfigPrivacy, 400 TrackConfigOauth, 401 TrackConfigLDAP, 402 TrackConfigCompliance, 403 TrackConfigLocalization, 404 TrackConfigSAML, 405 TrackConfigPassword, 406 TrackConfigCluster, 407 TrackConfigMetrics, 408 TrackConfigSupport, 409 TrackConfigNativeApp, 410 TrackConfigExperimental, 411 TrackConfigAnalytics, 412 TrackConfigPlugin, 413 TrackActivity, 414 TrackServer, 415 TrackConfigMessageExport, 416 TrackPlugins, 417 } { 418 require.Contains(t, info, item) 419 } 420 }) 421 t.Run("Telemetry for Marketplace plugins is returned", func(t *testing.T) { 422 telemetryService.trackPluginConfig(telemetryService.srv.Config(), marketplaceServer.URL) 423 424 var batches []batch 425 collectBatches(&batches) 426 427 for _, b := range batches { 428 if b.Event == TrackConfigPlugin { 429 assert.Contains(t, b.Properties, "enable_testplugin") 430 assert.Contains(t, b.Properties, "version_testplugin") 431 432 // Confirm known plugins are not present 433 assert.NotContains(t, b.Properties, "enable_jira") 434 assert.NotContains(t, b.Properties, "version_jira") 435 } 436 } 437 }) 438 439 t.Run("Telemetry for known plugins is returned, if request to Marketplace fails", func(t *testing.T) { 440 telemetryService.trackPluginConfig(telemetryService.srv.Config(), "http://some.random.invalid.url") 441 442 var batches []batch 443 collectBatches(&batches) 444 445 for _, b := range batches { 446 if b.Event == TrackConfigPlugin { 447 assert.NotContains(t, b.Properties, "enable_testplugin") 448 assert.NotContains(t, b.Properties, "version_testplugin") 449 450 // Confirm known plugins are present 451 assert.Contains(t, b.Properties, "enable_jira") 452 assert.Contains(t, b.Properties, "version_jira") 453 } 454 } 455 }) 456 457 t.Run("SendDailyTelemetryNoRudderKey", func(t *testing.T) { 458 if !strings.Contains(RudderKey, "placeholder") { 459 t.Skipf("Skipping telemetry on production builds") 460 } 461 telemetryService.sendDailyTelemetry(false) 462 463 select { 464 case <-data: 465 require.Fail(t, "Should not send telemetry when the rudder key is not set") 466 case <-time.After(time.Second * 1): 467 // Did not receive telemetry 468 } 469 }) 470 471 t.Run("SendDailyTelemetryDisabled", func(t *testing.T) { 472 if !strings.Contains(RudderKey, "placeholder") { 473 t.Skipf("Skipping telemetry on production builds") 474 } 475 *cfg.LogSettings.EnableDiagnostics = false 476 defer func() { 477 *cfg.LogSettings.EnableDiagnostics = true 478 }() 479 480 telemetryService.sendDailyTelemetry(true) 481 482 select { 483 case <-data: 484 require.Fail(t, "Should not send telemetry when they are disabled") 485 case <-time.After(time.Second * 1): 486 // Did not receive telemetry 487 } 488 }) 489 490 t.Run("TestInstallationType", func(t *testing.T) { 491 os.Unsetenv(EnvVarInstallType) 492 telemetryService.sendDailyTelemetry(true) 493 494 var batches []batch 495 collectBatches(&batches) 496 497 for _, b := range batches { 498 if b.Event == TrackServer { 499 assert.Equal(t, b.Properties["installation_type"], "") 500 } 501 } 502 503 os.Setenv(EnvVarInstallType, "docker") 504 defer os.Unsetenv(EnvVarInstallType) 505 506 batches = []batch{} 507 collectBatches(&batches) 508 509 for _, b := range batches { 510 if b.Event == TrackServer { 511 assert.Equal(t, b.Properties["installation_type"], "docker") 512 } 513 } 514 }) 515 516 t.Run("RudderConfigUsesConfigForValues", func(t *testing.T) { 517 if !strings.Contains(RudderKey, "placeholder") { 518 t.Skipf("Skipping telemetry on production builds") 519 } 520 os.Setenv("RudderKey", "abc123") 521 os.Setenv("RudderDataplaneURL", "arudderstackplace") 522 defer os.Unsetenv("RudderKey") 523 defer os.Unsetenv("RudderDataplaneURL") 524 525 config := telemetryService.getRudderConfig() 526 527 assert.Equal(t, "arudderstackplace", config.DataplaneUrl) 528 assert.Equal(t, "abc123", config.RudderKey) 529 }) 530 } 531 532 func TestIsDefaultArray(t *testing.T) { 533 assert.True(t, isDefaultArray([]string{"one", "two"}, []string{"one", "two"})) 534 assert.False(t, isDefaultArray([]string{"one", "two"}, []string{"one", "two", "three"})) 535 assert.False(t, isDefaultArray([]string{"one", "two"}, []string{"one", "three"})) 536 }