github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/product_notices_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "errors" 8 "fmt" 9 "net/http" 10 "net/http/httptest" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/require" 16 17 "github.com/mattermost/mattermost-server/v5/model" 18 "github.com/mattermost/mattermost-server/v5/store/storetest/mocks" 19 ) 20 21 func TestNoticeValidation(t *testing.T) { 22 th := SetupWithStoreMock(t) 23 mockStore := th.App.Srv().Store.(*mocks.Store) 24 mockRoleStore := mocks.RoleStore{} 25 mockSystemStore := mocks.SystemStore{} 26 mockUserStore := mocks.UserStore{} 27 mockPostStore := mocks.PostStore{} 28 mockPreferenceStore := mocks.PreferenceStore{} 29 mockStore.On("Role").Return(&mockRoleStore) 30 mockStore.On("System").Return(&mockSystemStore) 31 mockStore.On("User").Return(&mockUserStore) 32 mockStore.On("Post").Return(&mockPostStore) 33 mockStore.On("Preference").Return(&mockPreferenceStore) 34 mockSystemStore.On("SaveOrUpdate", &model.System{Name: "ActiveLicenseId", Value: ""}).Return(nil) 35 mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) 36 mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) 37 mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil) 38 mockSystemStore.On("Get").Return(make(model.StringMap), nil) 39 40 mockUserStore.On("Count", model.UserCountOptions{IncludeBotAccounts: false, IncludeDeleted: true, ExcludeRegularUsers: false, TeamId: "", ChannelId: "", ViewRestrictions: (*model.ViewUsersRestrictions)(nil), Roles: []string(nil), ChannelRoles: []string(nil), TeamRoles: []string(nil)}).Return(int64(1), nil) 41 mockPreferenceStore.On("Get", "test", "Stuff", "Data").Return(&model.Preference{Value: "test2"}, nil) 42 mockPreferenceStore.On("Get", "test", "Stuff", "Data2").Return(&model.Preference{Value: "test"}, nil) 43 mockPreferenceStore.On("Get", "test", "Stuff", "Data3").Return(nil, errors.New("Error!")) 44 mockPostStore.On("GetMaxPostSize").Return(65535, nil) 45 46 th.App.UpdateConfig(func(cfg *model.Config) { 47 *cfg.AnnouncementSettings.AdminNoticesEnabled = true 48 *cfg.AnnouncementSettings.UserNoticesEnabled = true 49 }) 50 51 defer th.TearDown() 52 53 type args struct { 54 client model.NoticeClientType 55 clientVersion string 56 sku string 57 postCount, userCount int64 58 cloud bool 59 teamAdmin bool 60 systemAdmin bool 61 serverVersion string 62 notice *model.ProductNotice 63 } 64 messages := map[string]model.NoticeMessageInternal{ 65 "en": { 66 Description: "descr", 67 Title: "title", 68 }, 69 } 70 tests := []struct { 71 name string 72 args args 73 wantErr bool 74 wantOk bool 75 }{ 76 { 77 name: "general notice", 78 args: args{ 79 client: "mobile", 80 clientVersion: "1.2.3", 81 82 notice: &model.ProductNotice{ 83 Conditions: model.Conditions{}, 84 ID: "123", 85 LocalizedMessages: messages, 86 }, 87 }, 88 wantErr: false, 89 wantOk: true, 90 }, 91 { 92 name: "mobile notice", 93 args: args{ 94 client: "desktop", 95 clientVersion: "1.2.3", 96 97 notice: &model.ProductNotice{ 98 Conditions: model.Conditions{ 99 ClientType: model.NewNoticeClientType(model.NoticeClientType_Mobile), 100 }, 101 }, 102 }, 103 wantErr: false, 104 wantOk: false, 105 }, 106 { 107 name: "notice with config check", 108 args: args{ 109 notice: &model.ProductNotice{ 110 Conditions: model.Conditions{ 111 ServerConfig: map[string]interface{}{"ServiceSettings.LetsEncryptCertificateCacheFile": "./config/letsencrypt.cache"}, 112 }, 113 }, 114 }, 115 wantErr: false, 116 wantOk: true, 117 }, 118 { 119 name: "notice with failing config check", 120 args: args{ 121 notice: &model.ProductNotice{ 122 Conditions: model.Conditions{ 123 ServerConfig: map[string]interface{}{"ServiceSettings.ZZ": "test"}, 124 }, 125 }, 126 }, 127 wantErr: false, 128 wantOk: false, 129 }, 130 { 131 name: "notice with failing user check due to bad format", 132 args: args{ 133 notice: &model.ProductNotice{ 134 Conditions: model.Conditions{ 135 UserConfig: map[string]interface{}{"Stuff": "test"}, 136 }, 137 }, 138 }, 139 wantErr: true, 140 wantOk: false, 141 }, 142 { 143 name: "notice with failing user check due to mismatch", 144 args: args{ 145 notice: &model.ProductNotice{ 146 Conditions: model.Conditions{ 147 UserConfig: map[string]interface{}{"Stuff.Data": "test"}, 148 }, 149 }, 150 }, 151 wantErr: false, 152 wantOk: false, 153 }, 154 { 155 name: "notice with working user check", 156 args: args{ 157 notice: &model.ProductNotice{ 158 Conditions: model.Conditions{ 159 UserConfig: map[string]interface{}{"Stuff.Data2": "test"}, 160 }, 161 }, 162 }, 163 wantErr: false, 164 wantOk: true, 165 }, 166 { 167 name: "notice with user check for property not in database", 168 args: args{ 169 notice: &model.ProductNotice{ 170 Conditions: model.Conditions{ 171 UserConfig: map[string]interface{}{"Stuff.Data3": "stuff"}, 172 }, 173 }, 174 }, 175 wantErr: false, 176 wantOk: false, 177 }, 178 { 179 name: "notice with server version check", 180 args: args{ 181 notice: &model.ProductNotice{ 182 Conditions: model.Conditions{ 183 ServerVersion: []string{"> 4.0.0 < 99.0.0"}, 184 }, 185 }, 186 }, 187 wantErr: false, 188 wantOk: true, 189 }, 190 { 191 name: "notice with server version check that doesn't match", 192 args: args{ 193 notice: &model.ProductNotice{ 194 Conditions: model.Conditions{ 195 ServerVersion: []string{"> 99.0.0"}, 196 }, 197 }, 198 }, 199 wantErr: false, 200 wantOk: false, 201 }, 202 { 203 name: "notice with server version check that matches a const", 204 args: args{ 205 serverVersion: "99.1.1", 206 notice: &model.ProductNotice{ 207 Conditions: model.Conditions{ 208 ServerVersion: []string{"> 99.0.0"}, 209 }, 210 }, 211 }, 212 wantErr: false, 213 wantOk: true, 214 }, 215 216 { 217 name: "notice with server version check that matches a const", 218 args: args{ 219 serverVersion: "99.1.1", 220 notice: &model.ProductNotice{ 221 Conditions: model.Conditions{ 222 ServerVersion: []string{"> 99.0.0"}, 223 }, 224 }, 225 }, 226 wantErr: false, 227 wantOk: true, 228 }, 229 230 { 231 name: "notice with server version check that has rc", 232 args: args{ 233 serverVersion: "99.1.1-rc2", 234 notice: &model.ProductNotice{ 235 Conditions: model.Conditions{ 236 ServerVersion: []string{"> 99.0.0 < 100.2.2"}, 237 }, 238 }, 239 }, 240 wantErr: false, 241 wantOk: true, 242 }, 243 244 { 245 name: "notice with server version check that has rc and hash", 246 args: args{ 247 serverVersion: "99.1.1-rc2.abcdef", 248 notice: &model.ProductNotice{ 249 Conditions: model.Conditions{ 250 ServerVersion: []string{"> 99.0.0 < 100.2.2"}, 251 }, 252 }, 253 }, 254 wantErr: false, 255 wantOk: true, 256 }, 257 258 { 259 name: "notice with server version check that has release and hash", 260 args: args{ 261 serverVersion: "release-99.1.1.abcdef", 262 notice: &model.ProductNotice{ 263 Conditions: model.Conditions{ 264 ServerVersion: []string{"> 99.0.0 < 100.2.2"}, 265 }, 266 }, 267 }, 268 wantErr: false, 269 wantOk: true, 270 }, 271 272 { 273 name: "notice with server version check that has cloud version", 274 args: args{ 275 serverVersion: "cloud.54.abcdef", 276 notice: &model.ProductNotice{ 277 Conditions: model.Conditions{ 278 ServerVersion: []string{"> 99.0.0 < 100.2.2"}, 279 }, 280 }, 281 }, 282 wantErr: false, 283 wantOk: false, 284 }, 285 { 286 name: "notice with server version check on cloud should ignore version", 287 args: args{ 288 cloud: true, 289 serverVersion: "cloud.54.abcdef", 290 notice: &model.ProductNotice{ 291 Conditions: model.Conditions{ 292 ServerVersion: []string{"> 99.0.0 < 100.2.2"}, 293 }, 294 }, 295 }, 296 wantErr: false, 297 wantOk: true, 298 }, 299 300 { 301 name: "notice with server version check that is invalid", 302 args: args{ 303 notice: &model.ProductNotice{ 304 Conditions: model.Conditions{ 305 ServerVersion: []string{"99.0.0 + 1.0.0"}, 306 }, 307 }, 308 }, 309 wantErr: true, 310 wantOk: false, 311 }, 312 { 313 name: "notice with user count", 314 args: args{ 315 userCount: 300, 316 notice: &model.ProductNotice{ 317 Conditions: model.Conditions{ 318 NumberOfUsers: model.NewInt64(400), 319 }, 320 }, 321 }, 322 wantErr: false, 323 wantOk: false, 324 }, 325 { 326 name: "notice with good user count and bad post count", 327 args: args{ 328 userCount: 500, 329 postCount: 2000, 330 notice: &model.ProductNotice{ 331 Conditions: model.Conditions{ 332 NumberOfUsers: model.NewInt64(400), 333 NumberOfPosts: model.NewInt64(3000), 334 }, 335 }, 336 }, 337 wantErr: false, 338 wantOk: false, 339 }, 340 { 341 name: "notice with date check", 342 args: args{ 343 notice: &model.ProductNotice{ 344 Conditions: model.Conditions{ 345 DisplayDate: model.NewString("> 2000-03-01T00:00:00Z <= 2999-04-01T00:00:00Z"), 346 }, 347 }, 348 }, 349 wantErr: false, 350 wantOk: true, 351 }, 352 { 353 name: "notice with specific date check", 354 args: args{ 355 notice: &model.ProductNotice{ 356 Conditions: model.Conditions{ 357 DisplayDate: model.NewString(fmt.Sprintf("= %sT00:00:00Z", time.Now().Format("2006-01-02"))), 358 }, 359 }, 360 }, 361 wantErr: false, 362 wantOk: true, 363 }, 364 { 365 name: "notice with date check that doesn't match", 366 args: args{ 367 notice: &model.ProductNotice{ 368 Conditions: model.Conditions{ 369 DisplayDate: model.NewString("> 2999-03-01T00:00:00Z <= 3000-04-01T00:00:00Z"), 370 }, 371 }, 372 }, 373 wantErr: false, 374 wantOk: false, 375 }, 376 { 377 name: "notice with bad date check", 378 args: args{ 379 notice: &model.ProductNotice{ 380 Conditions: model.Conditions{ 381 DisplayDate: model.NewString("> 2000 -03-01T00:00:00Z <= 2999-04-01T00:00:00Z"), 382 }, 383 }, 384 }, 385 wantErr: true, 386 wantOk: false, 387 }, 388 { 389 name: "notice with audience check (admin)", 390 args: args{ 391 systemAdmin: true, 392 notice: &model.ProductNotice{ 393 Conditions: model.Conditions{ 394 Audience: model.NewNoticeAudience(model.NoticeAudience_Sysadmin), 395 }, 396 }, 397 }, 398 wantErr: false, 399 wantOk: true, 400 }, 401 { 402 name: "notice with failing audience check (admin)", 403 args: args{ 404 systemAdmin: false, 405 notice: &model.ProductNotice{ 406 Conditions: model.Conditions{ 407 Audience: model.NewNoticeAudience(model.NoticeAudience_Sysadmin), 408 }, 409 }, 410 }, 411 wantErr: false, 412 wantOk: false, 413 }, 414 { 415 name: "notice with audience check (team)", 416 args: args{ 417 teamAdmin: true, 418 notice: &model.ProductNotice{ 419 Conditions: model.Conditions{ 420 Audience: model.NewNoticeAudience(model.NoticeAudience_TeamAdmin), 421 }, 422 }, 423 }, 424 wantErr: false, 425 wantOk: true, 426 }, 427 { 428 name: "notice with failing audience check (team)", 429 args: args{ 430 teamAdmin: false, 431 notice: &model.ProductNotice{ 432 Conditions: model.Conditions{ 433 Audience: model.NewNoticeAudience(model.NoticeAudience_TeamAdmin), 434 }, 435 }, 436 }, 437 wantErr: false, 438 wantOk: false, 439 }, 440 { 441 name: "notice with audience check (member)", 442 args: args{ 443 notice: &model.ProductNotice{ 444 Conditions: model.Conditions{ 445 Audience: model.NewNoticeAudience(model.NoticeAudience_Member), 446 }, 447 }, 448 }, 449 wantErr: false, 450 wantOk: true, 451 }, 452 { 453 name: "notice with failing audience check (member)", 454 args: args{ 455 systemAdmin: true, 456 notice: &model.ProductNotice{ 457 Conditions: model.Conditions{ 458 Audience: model.NewNoticeAudience(model.NoticeAudience_Member), 459 }, 460 }, 461 }, 462 wantErr: false, 463 wantOk: false, 464 }, 465 { 466 name: "notice with correct sku", 467 args: args{ 468 sku: "e20", 469 notice: &model.ProductNotice{ 470 Conditions: model.Conditions{ 471 Sku: model.NewNoticeSKU(model.NoticeSKU_E20), 472 }, 473 }, 474 }, 475 wantErr: false, 476 wantOk: true, 477 }, 478 { 479 name: "notice with incorrect sku", 480 args: args{ 481 sku: "e20", 482 notice: &model.ProductNotice{ 483 Conditions: model.Conditions{ 484 Sku: model.NewNoticeSKU(model.NoticeSKU_E10), 485 }, 486 }, 487 }, 488 wantErr: false, 489 wantOk: false, 490 }, 491 { 492 name: "notice with team sku", 493 args: args{ 494 sku: "", 495 notice: &model.ProductNotice{ 496 Conditions: model.Conditions{ 497 Sku: model.NewNoticeSKU(model.NoticeSKU_Team), 498 }, 499 }, 500 }, 501 wantErr: false, 502 wantOk: true, 503 }, 504 { 505 name: "notice with sku check for all", 506 args: args{ 507 notice: &model.ProductNotice{ 508 Conditions: model.Conditions{ 509 Sku: model.NewNoticeSKU(model.NoticeSKU_All), 510 }, 511 }, 512 }, 513 wantErr: false, 514 wantOk: true, 515 }, 516 { 517 name: "notice with instance check cloud", 518 args: args{ 519 cloud: true, 520 notice: &model.ProductNotice{ 521 Conditions: model.Conditions{ 522 InstanceType: model.NewNoticeInstanceType(model.NoticeInstanceType_Cloud), 523 }, 524 }, 525 }, 526 wantErr: false, 527 wantOk: true, 528 }, 529 { 530 name: "notice with instance check both", 531 args: args{ 532 notice: &model.ProductNotice{ 533 Conditions: model.Conditions{ 534 InstanceType: model.NewNoticeInstanceType(model.NoticeInstanceType_Both), 535 }, 536 }, 537 }, 538 wantErr: false, 539 wantOk: true, 540 }, 541 } 542 for _, tt := range tests { 543 t.Run(tt.name, func(t *testing.T) { 544 clientVersion := tt.args.clientVersion 545 if clientVersion == "" { 546 clientVersion = "1.2.3" 547 } 548 model.BuildNumber = tt.args.serverVersion 549 if model.BuildNumber == "" { 550 model.BuildNumber = "5.26.1" 551 } 552 if ok, err := noticeMatchesConditions( 553 th.App.Config(), 554 th.App.Srv().Store.Preference(), 555 "test", 556 tt.args.client, 557 clientVersion, 558 tt.args.postCount, 559 tt.args.userCount, 560 tt.args.systemAdmin, 561 tt.args.teamAdmin, 562 tt.args.cloud, 563 tt.args.sku, 564 tt.args.notice, 565 ); (err != nil) != tt.wantErr { 566 t.Errorf("noticeMatchesConditions() error = %v, wantErr %v", err, tt.wantErr) 567 } else if ok != tt.wantOk { 568 t.Errorf("noticeMatchesConditions() result = %v, wantOk %v", ok, tt.wantOk) 569 } 570 }) 571 } 572 } 573 574 func TestNoticeFetch(t *testing.T) { 575 th := Setup(t).InitBasic() 576 defer th.TearDown() 577 578 notices := model.ProductNotices{model.ProductNotice{ 579 Conditions: model.Conditions{}, 580 ID: "123", 581 LocalizedMessages: map[string]model.NoticeMessageInternal{ 582 "en": { 583 Description: "description", 584 Title: "title", 585 }, 586 }, 587 Repeatable: nil, 588 }} 589 noticesBytes, err := notices.Marshal() 590 require.NoError(t, err) 591 592 notices2 := model.ProductNotices{model.ProductNotice{ 593 Conditions: model.Conditions{ 594 NumberOfPosts: model.NewInt64(99999), 595 }, 596 ID: "333", 597 LocalizedMessages: map[string]model.NoticeMessageInternal{ 598 "en": { 599 Description: "description", 600 Title: "title", 601 }, 602 }, 603 Repeatable: nil, 604 }} 605 noticesBytes2, err := notices2.Marshal() 606 require.NoError(t, err) 607 server1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 608 if strings.HasSuffix(r.URL.Path, "notices.json") { 609 w.Write(noticesBytes) 610 } else { 611 w.Write(noticesBytes2) 612 } 613 })) 614 defer server1.Close() 615 th.App.UpdateConfig(func(cfg *model.Config) { 616 *cfg.AnnouncementSettings.AdminNoticesEnabled = true 617 *cfg.AnnouncementSettings.UserNoticesEnabled = true 618 *cfg.AnnouncementSettings.NoticesURL = fmt.Sprintf("http://%s/notices.json", server1.Listener.Addr().String()) 619 }) 620 621 // fetch fake notices 622 appErr := th.App.UpdateProductNotices() 623 require.Nil(t, appErr) 624 625 // get them for specified user 626 messages, appErr := th.App.GetProductNotices(th.BasicUser.Id, th.BasicTeam.Id, model.NoticeClientType_All, "1.2.3", "en") 627 require.Nil(t, appErr) 628 require.Len(t, messages, 1) 629 630 // mark notices as viewed 631 appErr = th.App.UpdateViewedProductNotices(th.BasicUser.Id, []string{messages[0].ID}) 632 require.Nil(t, appErr) 633 634 // get them again, see that none are returned 635 messages, appErr = th.App.GetProductNotices(th.BasicUser.Id, th.BasicTeam.Id, model.NoticeClientType_All, "1.2.3", "en") 636 require.Nil(t, appErr) 637 require.Len(t, messages, 0) 638 639 // validate views table 640 views, err := th.App.Srv().Store.ProductNotices().GetViews(th.BasicUser.Id) 641 require.NoError(t, err) 642 require.Len(t, views, 1) 643 644 // fetch another set 645 th.App.UpdateConfig(func(cfg *model.Config) { 646 *cfg.AnnouncementSettings.NoticesURL = fmt.Sprintf("http://%s/notices2.json", server1.Listener.Addr().String()) 647 }) 648 649 // fetch fake notices 650 appErr = th.App.UpdateProductNotices() 651 require.Nil(t, appErr) 652 653 // get them again, since conditions don't match we should be zero 654 messages, appErr = th.App.GetProductNotices(th.BasicUser.Id, th.BasicTeam.Id, model.NoticeClientType_All, "1.2.3", "en") 655 require.Nil(t, appErr) 656 require.Len(t, messages, 0) 657 658 // even though UpdateViewedProductNotices was called previously, the table should be empty, since there's cleanup done during UpdateProductNotices 659 views, err = th.App.Srv().Store.ProductNotices().GetViews(th.BasicUser.Id) 660 require.NoError(t, err) 661 require.Len(t, views, 0) 662 }