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 }