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  }