github.com/minio/console@v1.4.1/api/admin_config_test.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"reflect"
    25  	"testing"
    26  
    27  	"github.com/go-openapi/swag"
    28  	"github.com/stretchr/testify/assert"
    29  
    30  	"github.com/minio/console/models"
    31  	"github.com/minio/madmin-go/v3"
    32  )
    33  
    34  const (
    35  	NotifyPostgresSubSys     = "notify_postgres"
    36  	PostgresFormat           = "format"
    37  	PostgresConnectionString = "connection_string"
    38  	PostgresTable            = "table"
    39  	PostgresQueueDir         = "queue_dir"
    40  	PostgresQueueLimit       = "queue_limit"
    41  )
    42  
    43  func TestListConfig(t *testing.T) {
    44  	assert := assert.New(t)
    45  	adminClient := AdminClientMock{}
    46  	function := "listConfig()"
    47  	// Test-1 : listConfig() get list of two configurations and ensure is output correctly
    48  	configListMock := []madmin.HelpKV{
    49  		{
    50  			Key:         "region",
    51  			Description: "label the location of the server",
    52  		},
    53  		{
    54  			Key:         "notify_nsq",
    55  			Description: "publish bucket notifications to NSQ endpoints",
    56  		},
    57  	}
    58  	mockConfigList := madmin.Help{
    59  		SubSys:          "sys",
    60  		Description:     "desc",
    61  		MultipleTargets: false,
    62  		KeysHelp:        configListMock,
    63  	}
    64  	expectedKeysDesc := mockConfigList.KeysHelp
    65  	// mock function response from listConfig()
    66  	minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
    67  		return mockConfigList, nil
    68  	}
    69  	configList, err := listConfig(adminClient)
    70  	if err != nil {
    71  		t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
    72  	}
    73  	// verify length of keys is correct
    74  	assert.Equal(len(expectedKeysDesc), len(configList), fmt.Sprintf("Failed on %s: length of Configs's lists is not the same", function))
    75  	// verify KeysHelp content
    76  	for i, kv := range configList {
    77  		assert.Equal(expectedKeysDesc[i].Key, kv.Key)
    78  		assert.Equal(expectedKeysDesc[i].Description, kv.Description)
    79  	}
    80  
    81  	// Test-2 : listConfig() Return error and see that the error is handled correctly and returned
    82  	// mock function response from listConfig()
    83  	minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
    84  		return madmin.Help{}, errors.New("error")
    85  	}
    86  	_, err = listConfig(adminClient)
    87  	if assert.Error(err) {
    88  		assert.Equal("error", err.Error())
    89  	}
    90  }
    91  
    92  func TestSetConfig(t *testing.T) {
    93  	assert := assert.New(t)
    94  	adminClient := AdminClientMock{}
    95  	function := "setConfig()"
    96  	// mock function response from setConfig()
    97  	minioSetConfigKVMock = func(_ string) (restart bool, err error) {
    98  		return false, nil
    99  	}
   100  	configName := "notify_postgres"
   101  	kvs := []*models.ConfigurationKV{
   102  		{
   103  			Key:   "enable",
   104  			Value: "off",
   105  		},
   106  		{
   107  			Key:   "connection_string",
   108  			Value: "",
   109  		},
   110  	}
   111  
   112  	ctx, cancel := context.WithCancel(context.Background())
   113  	defer cancel()
   114  	// Test-1 : setConfig() sets a config with two key value pairs
   115  	restart, err := setConfig(ctx, adminClient, &configName, kvs)
   116  	if err != nil {
   117  		t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
   118  	}
   119  	assert.Equal(restart, false)
   120  
   121  	// Test-2 : setConfig() returns error, handle properly
   122  	minioSetConfigKVMock = func(_ string) (restart bool, err error) {
   123  		return false, errors.New("error")
   124  	}
   125  	restart, err = setConfig(ctx, adminClient, &configName, kvs)
   126  	if assert.Error(err) {
   127  		assert.Equal("error", err.Error())
   128  	}
   129  	assert.Equal(restart, false)
   130  
   131  	// Test-4 : setConfig() set config, need restart
   132  	minioSetConfigKVMock = func(_ string) (restart bool, err error) {
   133  		return true, nil
   134  	}
   135  	restart, err = setConfig(ctx, adminClient, &configName, kvs)
   136  	if err != nil {
   137  		t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
   138  	}
   139  	assert.Equal(restart, true)
   140  }
   141  
   142  func TestDelConfig(t *testing.T) {
   143  	assert := assert.New(t)
   144  	adminClient := AdminClientMock{}
   145  	function := "resetConfig()"
   146  	// mock function response from setConfig()
   147  	minioDelConfigKVMock = func(_ string) (err error) {
   148  		return nil
   149  	}
   150  	configName := "region"
   151  
   152  	ctx, cancel := context.WithCancel(context.Background())
   153  	defer cancel()
   154  	// Test-1 : resetConfig() resets a config with the config name
   155  	err := resetConfig(ctx, adminClient, &configName)
   156  	if err != nil {
   157  		t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
   158  	}
   159  
   160  	// Test-2 : resetConfig() returns error, handle properly
   161  	minioDelConfigKVMock = func(_ string) (err error) {
   162  		return errors.New("error")
   163  	}
   164  
   165  	err = resetConfig(ctx, adminClient, &configName)
   166  	if assert.Error(err) {
   167  		assert.Equal("error", err.Error())
   168  	}
   169  }
   170  
   171  func Test_buildConfig(t *testing.T) {
   172  	type args struct {
   173  		configName *string
   174  		kvs        []*models.ConfigurationKV
   175  	}
   176  	tests := []struct {
   177  		name string
   178  		args args
   179  		want *string
   180  	}{
   181  		// Test-1: buildConfig() format correctly configuration as "config_name k=v k2=v2"
   182  		{
   183  			name: "format correctly",
   184  			args: args{
   185  				configName: swag.String("notify_postgres"),
   186  				kvs: []*models.ConfigurationKV{
   187  					{
   188  						Key:   "enable",
   189  						Value: "off",
   190  					},
   191  					{
   192  						Key:   "connection_string",
   193  						Value: "",
   194  					},
   195  				},
   196  			},
   197  			want: swag.String("notify_postgres enable=\"off\" connection_string=\"\""),
   198  		},
   199  		// Test-2: buildConfig() format correctly configuration as "config_name k=v k2=v2 k2=v3" with duplicate keys
   200  		{
   201  			name: "duplicated keys in config",
   202  			args: args{
   203  				configName: swag.String("notify_postgres"),
   204  				kvs: []*models.ConfigurationKV{
   205  					{
   206  						Key:   "enable",
   207  						Value: "off",
   208  					},
   209  					{
   210  						Key:   "connection_string",
   211  						Value: "",
   212  					},
   213  					{
   214  						Key:   "connection_string",
   215  						Value: "x",
   216  					},
   217  				},
   218  			},
   219  			want: swag.String("notify_postgres enable=\"off\" connection_string=\"\" connection_string=\"x\""),
   220  		},
   221  	}
   222  	for _, tt := range tests {
   223  		t.Run(tt.name, func(_ *testing.T) {
   224  			if got := buildConfig(tt.args.configName, tt.args.kvs); !reflect.DeepEqual(got, tt.want) {
   225  				t.Errorf("buildConfig() = %s, want %s", *got, *tt.want)
   226  			}
   227  		})
   228  	}
   229  }
   230  
   231  func Test_setConfigWithARN(t *testing.T) {
   232  	assert := assert.New(t)
   233  	client := AdminClientMock{}
   234  
   235  	type args struct {
   236  		ctx        context.Context
   237  		client     MinioAdmin
   238  		configName *string
   239  		kvs        []*models.ConfigurationKV
   240  		arn        string
   241  	}
   242  	tests := []struct {
   243  		name          string
   244  		args          args
   245  		mockSetConfig func(kv string) (restart bool, err error)
   246  		wantErr       bool
   247  		expected      bool
   248  	}{
   249  		{
   250  			name: "Set valid config with arn",
   251  			args: args{
   252  				ctx:        context.Background(),
   253  				client:     client,
   254  				configName: swag.String("notify_kafka"),
   255  				kvs: []*models.ConfigurationKV{
   256  					{
   257  						Key:   "brokers",
   258  						Value: "http://localhost:8080/broker1,http://localhost:8080/broker2",
   259  					},
   260  				},
   261  				arn: "1",
   262  			},
   263  			mockSetConfig: func(_ string) (restart bool, err error) {
   264  				return false, nil
   265  			},
   266  			wantErr:  false,
   267  			expected: false,
   268  		},
   269  		{
   270  			name: "Set valid config, expect restart",
   271  			args: args{
   272  				ctx:        context.Background(),
   273  				client:     client,
   274  				configName: swag.String("notify_kafka"),
   275  				kvs: []*models.ConfigurationKV{
   276  					{
   277  						Key:   "brokers",
   278  						Value: "http://localhost:8080/broker1,http://localhost:8080/broker2",
   279  					},
   280  				},
   281  				arn: "1",
   282  			},
   283  			mockSetConfig: func(_ string) (restart bool, err error) {
   284  				return true, nil
   285  			},
   286  			wantErr:  false,
   287  			expected: true,
   288  		},
   289  		{
   290  			name: "Set valid config without arn",
   291  			args: args{
   292  				ctx:        context.Background(),
   293  				client:     client,
   294  				configName: swag.String("region"),
   295  				kvs: []*models.ConfigurationKV{
   296  					{
   297  						Key:   "name",
   298  						Value: "us-west-1",
   299  					},
   300  				},
   301  				arn: "",
   302  			},
   303  			mockSetConfig: func(_ string) (restart bool, err error) {
   304  				return false, nil
   305  			},
   306  			wantErr:  false,
   307  			expected: false,
   308  		},
   309  		{
   310  			name: "Setting an incorrect config",
   311  			args: args{
   312  				ctx:        context.Background(),
   313  				client:     client,
   314  				configName: swag.String("oorgle"),
   315  				kvs: []*models.ConfigurationKV{
   316  					{
   317  						Key:   "name",
   318  						Value: "us-west-1",
   319  					},
   320  				},
   321  				arn: "",
   322  			},
   323  			mockSetConfig: func(_ string) (restart bool, err error) {
   324  				return false, errors.New("error")
   325  			},
   326  			wantErr:  true,
   327  			expected: false,
   328  		},
   329  	}
   330  	for _, tt := range tests {
   331  		t.Run(tt.name, func(_ *testing.T) {
   332  			// mock function response from setConfig()
   333  			minioSetConfigKVMock = tt.mockSetConfig
   334  			restart, err := setConfigWithARNAccountID(tt.args.ctx, tt.args.client, tt.args.configName, tt.args.kvs, tt.args.arn)
   335  			if (err != nil) != tt.wantErr {
   336  				t.Errorf("setConfigWithARNAccountID() error = %v, wantErr %v", err, tt.wantErr)
   337  			}
   338  			assert.Equal(restart, tt.expected)
   339  		})
   340  	}
   341  }
   342  
   343  func Test_getConfig(t *testing.T) {
   344  	client := AdminClientMock{}
   345  	type args struct {
   346  		client MinioAdmin
   347  		name   string
   348  	}
   349  	tests := []struct {
   350  		name    string
   351  		args    args
   352  		mock    func()
   353  		want    []*models.Configuration
   354  		wantErr bool
   355  	}{
   356  		{
   357  			name: "get config",
   358  			args: args{
   359  				client: client,
   360  				name:   "notify_postgres",
   361  			},
   362  			mock: func() {
   363  				// mock function response from getConfig()
   364  				minioGetConfigKVMock = func(_ string) ([]byte, error) {
   365  					return []byte(`notify_postgres:_ connection_string="host=localhost dbname=minio_events user=postgres password=password port=5432 sslmode=disable" table=bucketevents`), nil
   366  				}
   367  
   368  				configListMock := []madmin.HelpKV{
   369  					{
   370  						Key:         PostgresConnectionString,
   371  						Description: `Postgres server connection-string e.g. "host=localhost port=5432 dbname=minio_events user=postgres password=password sslmode=disable"`,
   372  						Type:        "string",
   373  					},
   374  					{
   375  						Key:         PostgresTable,
   376  						Description: "DB table name to store/update events, table is auto-created",
   377  						Type:        "string",
   378  					},
   379  					{
   380  						Key:         PostgresFormat,
   381  						Description: "desc",
   382  						Type:        "namespace*|access",
   383  					},
   384  					{
   385  						Key:         PostgresQueueDir,
   386  						Description: "des",
   387  						Optional:    true,
   388  						Type:        "path",
   389  					},
   390  					{
   391  						Key:         PostgresQueueLimit,
   392  						Description: "desc",
   393  						Optional:    true,
   394  						Type:        "number",
   395  					},
   396  					{
   397  						Key:         madmin.CommentKey,
   398  						Description: "",
   399  						Optional:    true,
   400  						Type:        "sentence",
   401  					},
   402  				}
   403  				mockConfigList := madmin.Help{
   404  					SubSys:          NotifyPostgresSubSys,
   405  					Description:     "publish bucket notifications to Postgres databases",
   406  					MultipleTargets: true,
   407  					KeysHelp:        configListMock,
   408  				}
   409  				// mock function response from listConfig()
   410  				minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
   411  					return mockConfigList, nil
   412  				}
   413  			},
   414  			want: []*models.Configuration{
   415  				{
   416  					KeyValues: []*models.ConfigurationKV{
   417  						{
   418  							Key:   PostgresConnectionString,
   419  							Value: "host=localhost dbname=minio_events user=postgres password=password port=5432 sslmode=disable",
   420  						},
   421  						{
   422  							Key:   PostgresTable,
   423  							Value: "bucketevents",
   424  						},
   425  					}, Name: "notify_postgres",
   426  				},
   427  			},
   428  			wantErr: false,
   429  		},
   430  		{
   431  			name: "valid config, but server returned empty",
   432  			args: args{
   433  				client: client,
   434  				name:   NotifyPostgresSubSys,
   435  			},
   436  			mock: func() {
   437  				// mock function response from getConfig()
   438  				minioGetConfigKVMock = func(_ string) ([]byte, error) {
   439  					return []byte(`notify_postgres:_`), nil
   440  				}
   441  
   442  				configListMock := []madmin.HelpKV{
   443  					{
   444  						Key:         PostgresConnectionString,
   445  						Description: `Postgres server connection-string e.g. "host=localhost port=5432 dbname=minio_events user=postgres password=password sslmode=disable"`,
   446  						Type:        "string",
   447  					},
   448  					{
   449  						Key:         PostgresTable,
   450  						Description: "DB table name to store/update events, table is auto-created",
   451  						Type:        "string",
   452  					},
   453  					{
   454  						Key:         PostgresFormat,
   455  						Description: "desc",
   456  						Type:        "namespace*|access",
   457  					},
   458  					{
   459  						Key:         PostgresQueueDir,
   460  						Description: "des",
   461  						Optional:    true,
   462  						Type:        "path",
   463  					},
   464  					{
   465  						Key:         PostgresQueueLimit,
   466  						Description: "desc",
   467  						Optional:    true,
   468  						Type:        "number",
   469  					},
   470  					{
   471  						Key:         madmin.CommentKey,
   472  						Description: "optionally add a comment to this setting",
   473  						Optional:    true,
   474  						Type:        "sentence",
   475  					},
   476  				}
   477  				mockConfigList := madmin.Help{
   478  					SubSys:          NotifyPostgresSubSys,
   479  					Description:     "publish bucket notifications to Postgres databases",
   480  					MultipleTargets: true,
   481  					KeysHelp:        configListMock,
   482  				}
   483  				// mock function response from listConfig()
   484  				minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
   485  					return mockConfigList, nil
   486  				}
   487  			},
   488  			want:    nil,
   489  			wantErr: false,
   490  		},
   491  		{
   492  			name: "random bytes coming out of getConfigKv",
   493  			args: args{
   494  				client: client,
   495  				name:   "notify_postgres",
   496  			},
   497  			mock: func() {
   498  				// mock function response from getConfig()
   499  				minioGetConfigKVMock = func(_ string) ([]byte, error) {
   500  					x := make(map[string]string)
   501  					x["x"] = "x"
   502  					j, _ := json.Marshal(x)
   503  					return j, nil
   504  				}
   505  
   506  				configListMock := []madmin.HelpKV{
   507  					{
   508  						Key:         PostgresConnectionString,
   509  						Description: `Postgres server connection-string e.g. "host=localhost port=5432 dbname=minio_events user=postgres password=password sslmode=disable"`,
   510  						Type:        "string",
   511  					},
   512  					{
   513  						Key:         PostgresTable,
   514  						Description: "DB table name to store/update events, table is auto-created",
   515  						Type:        "string",
   516  					},
   517  					{
   518  						Key:         PostgresFormat,
   519  						Description: "desc",
   520  						Type:        "namespace*|access",
   521  					},
   522  					{
   523  						Key:         PostgresQueueDir,
   524  						Description: "des",
   525  						Optional:    true,
   526  						Type:        "path",
   527  					},
   528  					{
   529  						Key:         PostgresQueueLimit,
   530  						Description: "desc",
   531  						Optional:    true,
   532  						Type:        "number",
   533  					},
   534  					{
   535  						Key:         madmin.CommentKey,
   536  						Description: "optionally add a comment to this setting",
   537  						Optional:    true,
   538  						Type:        "sentence",
   539  					},
   540  				}
   541  				mockConfigList := madmin.Help{
   542  					SubSys:          NotifyPostgresSubSys,
   543  					Description:     "publish bucket notifications to Postgres databases",
   544  					MultipleTargets: true,
   545  					KeysHelp:        configListMock,
   546  				}
   547  				// mock function response from listConfig()
   548  				minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
   549  					return mockConfigList, nil
   550  				}
   551  			},
   552  			want:    nil,
   553  			wantErr: true,
   554  		},
   555  		{
   556  			name: "bad config",
   557  			args: args{
   558  				client: client,
   559  				name:   "notify_postgresx",
   560  			},
   561  			mock: func() {
   562  				// mock function response from getConfig()
   563  				minioGetConfigKVMock = func(_ string) ([]byte, error) {
   564  					return nil, errors.New("invalid config")
   565  				}
   566  
   567  				mockConfigList := madmin.Help{}
   568  				// mock function response from listConfig()
   569  				minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
   570  					return mockConfigList, nil
   571  				}
   572  			},
   573  			want:    nil,
   574  			wantErr: true,
   575  		},
   576  		{
   577  			name: "no help",
   578  			args: args{
   579  				client: client,
   580  				name:   "notify_postgresx",
   581  			},
   582  			mock: func() {
   583  				// mock function response from getConfig()
   584  				minioGetConfigKVMock = func(_ string) ([]byte, error) {
   585  					return nil, errors.New("invalid config")
   586  				}
   587  				// mock function response from listConfig()
   588  				minioHelpConfigKVMock = func(_, _ string, _ bool) (madmin.Help, error) {
   589  					return madmin.Help{}, errors.New("no help")
   590  				}
   591  			},
   592  			want:    nil,
   593  			wantErr: true,
   594  		},
   595  	}
   596  	for _, tt := range tests {
   597  		tt.mock()
   598  		t.Run(tt.name, func(_ *testing.T) {
   599  			got, err := getConfig(context.Background(), tt.args.client, tt.args.name)
   600  			if (err != nil) != tt.wantErr {
   601  				t.Errorf("getConfig() error = %v, wantErr %v", err, tt.wantErr)
   602  				return
   603  			}
   604  			if !reflect.DeepEqual(got, tt.want) {
   605  				t.Errorf("getConfig() got = %v, want %v", got, tt.want)
   606  			}
   607  		})
   608  	}
   609  }