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