1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package config
5
6import (
7	"testing"
8
9	"github.com/mattermost/mattermost-server/v6/model"
10
11	"github.com/stretchr/testify/require"
12)
13
14func defaultConfigGen() *model.Config {
15	cfg := &model.Config{}
16	cfg.SetDefaults()
17	return cfg
18}
19
20func BenchmarkDiff(b *testing.B) {
21	b.Run("equal empty", func(b *testing.B) {
22		baseCfg := &model.Config{}
23		actualCfg := &model.Config{}
24		b.ResetTimer()
25		for i := 0; i < b.N; i++ {
26			_, _ = Diff(baseCfg, actualCfg)
27		}
28	})
29
30	b.Run("equal with defaults", func(b *testing.B) {
31		baseCfg := defaultConfigGen()
32		actualCfg := defaultConfigGen()
33		b.ResetTimer()
34		for i := 0; i < b.N; i++ {
35			_, _ = Diff(baseCfg, actualCfg)
36		}
37	})
38
39	b.Run("actual empty", func(b *testing.B) {
40		baseCfg := defaultConfigGen()
41		actualCfg := &model.Config{}
42		b.ResetTimer()
43		for i := 0; i < b.N; i++ {
44			_, _ = Diff(baseCfg, actualCfg)
45		}
46	})
47
48	b.Run("base empty", func(b *testing.B) {
49		baseCfg := &model.Config{}
50		actualCfg := defaultConfigGen()
51		b.ResetTimer()
52		for i := 0; i < b.N; i++ {
53			_, _ = Diff(baseCfg, actualCfg)
54		}
55	})
56
57	b.Run("some diffs", func(b *testing.B) {
58		baseCfg := defaultConfigGen()
59		actualCfg := defaultConfigGen()
60		baseCfg.ServiceSettings.SiteURL = model.NewString("http://localhost")
61		baseCfg.ServiceSettings.ReadTimeout = model.NewInt(300)
62		baseCfg.SqlSettings.QueryTimeout = model.NewInt(0)
63		actualCfg.PluginSettings.EnableUploads = nil
64		actualCfg.TeamSettings.MaxChannelsPerTeam = model.NewInt64(100000)
65		actualCfg.FeatureFlags = nil
66		actualCfg.SqlSettings.DataSourceReplicas = []string{
67			"ds0",
68			"ds1",
69			"ds2",
70		}
71		b.ResetTimer()
72		for i := 0; i < b.N; i++ {
73			_, _ = Diff(baseCfg, actualCfg)
74		}
75	})
76}
77
78func TestDiff(t *testing.T) {
79	tcs := []struct {
80		name   string
81		base   *model.Config
82		actual *model.Config
83		diffs  ConfigDiffs
84		err    string
85	}{
86		{
87			"nil",
88			nil,
89			nil,
90			nil,
91			"input configs should not be nil",
92		},
93		{
94			"empty",
95			&model.Config{},
96			&model.Config{},
97			nil,
98			"",
99		},
100		{
101			"defaults",
102			defaultConfigGen(),
103			defaultConfigGen(),
104			nil,
105			"",
106		},
107		{
108			"default base, actual empty",
109			defaultConfigGen(),
110			&model.Config{},
111			ConfigDiffs{
112				{
113					"",
114					*defaultConfigGen(),
115					model.Config{},
116				},
117			},
118			"",
119		},
120		{
121			"empty base, actual default",
122			&model.Config{},
123			defaultConfigGen(),
124			ConfigDiffs{
125				{
126					"",
127					model.Config{},
128					*defaultConfigGen(),
129				},
130			},
131			"",
132		},
133		{
134			"string change",
135			defaultConfigGen(),
136			func() *model.Config {
137				cfg := defaultConfigGen()
138				cfg.ServiceSettings.SiteURL = model.NewString("http://changed")
139				return cfg
140			}(),
141			ConfigDiffs{
142				{
143					Path:      "ServiceSettings.SiteURL",
144					BaseVal:   *defaultConfigGen().ServiceSettings.SiteURL,
145					ActualVal: "http://changed",
146				},
147			},
148			"",
149		},
150		{
151			"string nil",
152			defaultConfigGen(),
153			func() *model.Config {
154				cfg := defaultConfigGen()
155				cfg.ServiceSettings.SiteURL = nil
156				return cfg
157			}(),
158			ConfigDiffs{
159				{
160					Path:    "ServiceSettings.SiteURL",
161					BaseVal: defaultConfigGen().ServiceSettings.SiteURL,
162					ActualVal: func() *string {
163						return nil
164					}(),
165				},
166			},
167			"",
168		},
169		{
170			"bool change",
171			defaultConfigGen(),
172			func() *model.Config {
173				cfg := defaultConfigGen()
174				cfg.PluginSettings.Enable = model.NewBool(!*cfg.PluginSettings.Enable)
175				return cfg
176			}(),
177			ConfigDiffs{
178				{
179					Path:      "PluginSettings.Enable",
180					BaseVal:   true,
181					ActualVal: false,
182				},
183			},
184			"",
185		},
186		{
187			"bool nil",
188			defaultConfigGen(),
189			func() *model.Config {
190				cfg := defaultConfigGen()
191				cfg.PluginSettings.Enable = nil
192				return cfg
193			}(),
194			ConfigDiffs{
195				{
196					Path:    "PluginSettings.Enable",
197					BaseVal: defaultConfigGen().PluginSettings.Enable,
198					ActualVal: func() *bool {
199						return nil
200					}(),
201				},
202			},
203			"",
204		},
205		{
206			"int change",
207			defaultConfigGen(),
208			func() *model.Config {
209				cfg := defaultConfigGen()
210				cfg.ServiceSettings.ReadTimeout = model.NewInt(0)
211				return cfg
212			}(),
213			ConfigDiffs{
214				{
215					Path:      "ServiceSettings.ReadTimeout",
216					BaseVal:   *defaultConfigGen().ServiceSettings.ReadTimeout,
217					ActualVal: 0,
218				},
219			},
220			"",
221		},
222		{
223			"int nil",
224			defaultConfigGen(),
225			func() *model.Config {
226				cfg := defaultConfigGen()
227				cfg.ServiceSettings.ReadTimeout = nil
228				return cfg
229			}(),
230			ConfigDiffs{
231				{
232					Path:    "ServiceSettings.ReadTimeout",
233					BaseVal: defaultConfigGen().ServiceSettings.ReadTimeout,
234					ActualVal: func() *int {
235						return nil
236					}(),
237				},
238			},
239			"",
240		},
241		{
242			"slice addition",
243			defaultConfigGen(),
244			func() *model.Config {
245				cfg := defaultConfigGen()
246				cfg.SqlSettings.DataSourceReplicas = []string{
247					"ds0",
248					"ds1",
249				}
250				return cfg
251			}(),
252			ConfigDiffs{
253				{
254					Path:    "SqlSettings.DataSourceReplicas",
255					BaseVal: defaultConfigGen().SqlSettings.DataSourceReplicas,
256					ActualVal: []string{
257						"ds0",
258						"ds1",
259					},
260				},
261			},
262			"",
263		},
264		{
265			"slice deletion",
266			func() *model.Config {
267				cfg := defaultConfigGen()
268				cfg.SqlSettings.DataSourceReplicas = []string{
269					"ds0",
270					"ds1",
271				}
272				return cfg
273			}(),
274			func() *model.Config {
275				cfg := defaultConfigGen()
276				cfg.SqlSettings.DataSourceReplicas = []string{
277					"ds0",
278				}
279				return cfg
280			}(),
281			ConfigDiffs{
282				{
283					Path: "SqlSettings.DataSourceReplicas",
284					BaseVal: []string{
285						"ds0",
286						"ds1",
287					},
288					ActualVal: []string{
289						"ds0",
290					},
291				},
292			},
293			"",
294		},
295		{
296			"slice nil",
297			func() *model.Config {
298				cfg := defaultConfigGen()
299				cfg.SqlSettings.DataSourceReplicas = []string{
300					"ds0",
301					"ds1",
302				}
303				return cfg
304			}(),
305			func() *model.Config {
306				cfg := defaultConfigGen()
307				cfg.SqlSettings.DataSourceReplicas = nil
308				return cfg
309			}(),
310			ConfigDiffs{
311				{
312					Path: "SqlSettings.DataSourceReplicas",
313					BaseVal: []string{
314						"ds0",
315						"ds1",
316					},
317					ActualVal: func() []string {
318						return nil
319					}(),
320				},
321			},
322			"",
323		},
324		{
325			"map change",
326			defaultConfigGen(),
327			func() *model.Config {
328				cfg := defaultConfigGen()
329				cfg.PluginSettings.PluginStates["com.mattermost.nps"] = &model.PluginState{
330					Enable: !cfg.PluginSettings.PluginStates["com.mattermost.nps"].Enable,
331				}
332				return cfg
333			}(),
334			ConfigDiffs{
335				{
336					Path:    "PluginSettings.PluginStates",
337					BaseVal: defaultConfigGen().PluginSettings.PluginStates,
338					ActualVal: map[string]*model.PluginState{
339						"com.mattermost.nps": {
340							Enable: !defaultConfigGen().PluginSettings.PluginStates["com.mattermost.nps"].Enable,
341						},
342						"focalboard": {
343							Enable: true,
344						},
345						"playbooks": {
346							Enable: true,
347						},
348					},
349				},
350			},
351			"",
352		},
353		{
354			"map addition",
355			defaultConfigGen(),
356			func() *model.Config {
357				cfg := defaultConfigGen()
358				cfg.PluginSettings.PluginStates["com.mattermost.newplugin"] = &model.PluginState{
359					Enable: true,
360				}
361				return cfg
362			}(),
363			ConfigDiffs{
364				{
365					Path:    "PluginSettings.PluginStates",
366					BaseVal: defaultConfigGen().PluginSettings.PluginStates,
367					ActualVal: map[string]*model.PluginState{
368						"com.mattermost.nps": {
369							Enable: defaultConfigGen().PluginSettings.PluginStates["com.mattermost.nps"].Enable,
370						},
371						"com.mattermost.newplugin": {
372							Enable: true,
373						},
374						"focalboard": {
375							Enable: true,
376						},
377						"playbooks": {
378							Enable: true,
379						},
380					},
381				},
382			},
383			"",
384		},
385		{
386			"map deletion",
387			defaultConfigGen(),
388			func() *model.Config {
389				cfg := defaultConfigGen()
390				delete(cfg.PluginSettings.PluginStates, "com.mattermost.nps")
391				return cfg
392			}(),
393			ConfigDiffs{
394				{
395					Path:    "PluginSettings.PluginStates",
396					BaseVal: defaultConfigGen().PluginSettings.PluginStates,
397					ActualVal: map[string]*model.PluginState{
398						"focalboard": {
399							Enable: true,
400						},
401						"playbooks": {
402							Enable: true,
403						},
404					},
405				},
406			},
407			"",
408		},
409		{
410			"map nil",
411			defaultConfigGen(),
412			func() *model.Config {
413				cfg := defaultConfigGen()
414				cfg.PluginSettings.PluginStates = nil
415				return cfg
416			}(),
417			ConfigDiffs{
418				{
419					Path:    "PluginSettings.PluginStates",
420					BaseVal: defaultConfigGen().PluginSettings.PluginStates,
421					ActualVal: func() map[string]*model.PluginState {
422						return nil
423					}(),
424				},
425			},
426			"",
427		},
428		{
429			"map type change",
430			func() *model.Config {
431				cfg := defaultConfigGen()
432				cfg.PluginSettings.Plugins = map[string]map[string]interface{}{
433					"com.mattermost.newplugin": {
434						"key": true,
435					},
436				}
437				return cfg
438			}(),
439			func() *model.Config {
440				cfg := defaultConfigGen()
441				cfg.PluginSettings.Plugins = map[string]map[string]interface{}{
442					"com.mattermost.newplugin": {
443						"key": "string",
444					},
445				}
446				return cfg
447			}(),
448			ConfigDiffs{
449				{
450					Path: "PluginSettings.Plugins",
451					BaseVal: func() interface{} {
452						return map[string]map[string]interface{}{
453							"com.mattermost.newplugin": {
454								"key": true,
455							},
456						}
457					}(),
458					ActualVal: func() interface{} {
459						return map[string]map[string]interface{}{
460							"com.mattermost.newplugin": {
461								"key": "string",
462							},
463						}
464					}(),
465				},
466			},
467			"",
468		},
469	}
470
471	for _, tc := range tcs {
472		t.Run(tc.name, func(t *testing.T) {
473			diffs, err := Diff(tc.base, tc.actual)
474			if tc.err != "" {
475				require.EqualError(t, err, tc.err)
476				require.Nil(t, diffs)
477			} else {
478				require.NoError(t, err)
479			}
480			require.Equal(t, tc.diffs, diffs)
481		})
482	}
483}
484