github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/config/ui_test.go (about) 1 // These are in an external package because we need to import configfile 2 // 3 // Internal tests are in ui_internal_test.go 4 5 package config_test 6 7 import ( 8 "context" 9 "fmt" 10 "os" 11 "testing" 12 13 "github.com/rclone/rclone/fs" 14 "github.com/rclone/rclone/fs/config" 15 "github.com/rclone/rclone/fs/config/configfile" 16 "github.com/rclone/rclone/fs/config/obscure" 17 "github.com/rclone/rclone/fs/rc" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 ) 21 22 var simpleOptions = []fs.Option{{ 23 Name: "bool", 24 Default: false, 25 IsPassword: false, 26 }, { 27 Name: "pass", 28 Default: "", 29 IsPassword: true, 30 }} 31 32 func testConfigFile(t *testing.T, options []fs.Option, configFileName string) func() { 33 ctx := context.Background() 34 ci := fs.GetConfig(ctx) 35 config.ClearConfigPassword() 36 _ = os.Unsetenv("_RCLONE_CONFIG_KEY_FILE") 37 _ = os.Unsetenv("RCLONE_CONFIG_PASS") 38 // create temp config file 39 tempFile, err := os.CreateTemp("", configFileName) 40 assert.NoError(t, err) 41 path := tempFile.Name() 42 assert.NoError(t, tempFile.Close()) 43 44 // temporarily adapt configuration 45 oldOsStdout := os.Stdout 46 oldConfigPath := config.GetConfigPath() 47 oldConfig := *ci 48 oldConfigFile := config.Data() 49 oldReadLine := config.ReadLine 50 oldPassword := config.Password 51 os.Stdout = nil 52 assert.NoError(t, config.SetConfigPath(path)) 53 ci = &fs.ConfigInfo{} 54 55 configfile.Install() 56 assert.Equal(t, []string{}, config.Data().GetSectionList()) 57 58 // Fake a filesystem/backend 59 backendName := "config_test_remote" 60 if regInfo, _ := fs.Find(backendName); regInfo != nil { 61 regInfo.Options = options 62 } else { 63 fs.Register(&fs.RegInfo{ 64 Name: backendName, 65 Options: options, 66 }) 67 } 68 69 // Undo the above (except registered backend, unfortunately) 70 return func() { 71 err := os.Remove(path) 72 assert.NoError(t, err) 73 74 os.Stdout = oldOsStdout 75 assert.NoError(t, config.SetConfigPath(oldConfigPath)) 76 config.ReadLine = oldReadLine 77 config.Password = oldPassword 78 *ci = oldConfig 79 config.SetData(oldConfigFile) 80 81 _ = os.Unsetenv("_RCLONE_CONFIG_KEY_FILE") 82 _ = os.Unsetenv("RCLONE_CONFIG_PASS") 83 } 84 } 85 86 // makeReadLine makes a simple readLine which returns a fixed list of 87 // strings 88 func makeReadLine(answers []string) func() string { 89 i := 0 90 return func() string { 91 i = i + 1 92 return answers[i-1] 93 } 94 } 95 96 func TestCRUD(t *testing.T) { 97 defer testConfigFile(t, simpleOptions, "crud.conf")() 98 ctx := context.Background() 99 100 // script for creating remote 101 config.ReadLine = makeReadLine([]string{ 102 "config_test_remote", // type 103 "true", // bool value 104 "y", // type my own password 105 "secret", // password 106 "secret", // repeat 107 "n", // don't edit advanced config 108 "y", // looks good, save 109 }) 110 require.NoError(t, config.NewRemote(ctx, "test")) 111 112 assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) 113 assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) 114 assert.Equal(t, "true", config.FileGet("test", "bool")) 115 assert.Equal(t, "secret", obscure.MustReveal(config.FileGet("test", "pass"))) 116 117 // normal rename, test → asdf 118 config.ReadLine = makeReadLine([]string{ 119 "asdf", 120 "asdf", 121 "asdf", 122 }) 123 config.RenameRemote("test") 124 125 assert.Equal(t, []string{"asdf"}, config.Data().GetSectionList()) 126 assert.Equal(t, "config_test_remote", config.FileGet("asdf", "type")) 127 assert.Equal(t, "true", config.FileGet("asdf", "bool")) 128 assert.Equal(t, "secret", obscure.MustReveal(config.FileGet("asdf", "pass"))) 129 130 // delete remote 131 config.DeleteRemote("asdf") 132 assert.Equal(t, []string{}, config.Data().GetSectionList()) 133 } 134 135 func TestChooseOption(t *testing.T) { 136 defer testConfigFile(t, simpleOptions, "crud.conf")() 137 ctx := context.Background() 138 139 // script for creating remote 140 config.ReadLine = makeReadLine([]string{ 141 "config_test_remote", // type 142 "false", // bool value 143 "x", // bad choice 144 "g", // generate password 145 "1024", // very big 146 "y", // password OK 147 "y", // looks good, save 148 }) 149 config.Password = func(bits int) (string, error) { 150 assert.Equal(t, 1024, bits) 151 return "not very random password", nil 152 } 153 require.NoError(t, config.NewRemote(ctx, "test")) 154 155 assert.Equal(t, "", config.FileGet("test", "bool")) // this is the default now 156 assert.Equal(t, "not very random password", obscure.MustReveal(config.FileGet("test", "pass"))) 157 158 // script for creating remote 159 config.ReadLine = makeReadLine([]string{ 160 "config_test_remote", // type 161 "true", // bool value 162 "n", // not required 163 "y", // looks good, save 164 }) 165 require.NoError(t, config.NewRemote(ctx, "test")) 166 167 assert.Equal(t, "true", config.FileGet("test", "bool")) 168 assert.Equal(t, "", config.FileGet("test", "pass")) 169 } 170 171 func TestNewRemoteName(t *testing.T) { 172 defer testConfigFile(t, simpleOptions, "crud.conf")() 173 ctx := context.Background() 174 175 // script for creating remote 176 config.ReadLine = makeReadLine([]string{ 177 "config_test_remote", // type 178 "true", // bool value 179 "n", // not required 180 "y", // looks good, save 181 }) 182 require.NoError(t, config.NewRemote(ctx, "test")) 183 184 config.ReadLine = makeReadLine([]string{ 185 "test", // already exists 186 "", // empty string not allowed 187 "bad^characters", // bad characters 188 "newname", // OK 189 }) 190 191 assert.Equal(t, "newname", config.NewRemoteName()) 192 } 193 194 func TestCreateUpdatePasswordRemote(t *testing.T) { 195 ctx := context.Background() 196 defer testConfigFile(t, simpleOptions, "update.conf")() 197 198 for _, doObscure := range []bool{false, true} { 199 for _, noObscure := range []bool{false, true} { 200 if doObscure && noObscure { 201 break 202 } 203 t.Run(fmt.Sprintf("doObscure=%v,noObscure=%v", doObscure, noObscure), func(t *testing.T) { 204 opt := config.UpdateRemoteOpt{ 205 Obscure: doObscure, 206 NoObscure: noObscure, 207 } 208 _, err := config.CreateRemote(ctx, "test2", "config_test_remote", rc.Params{ 209 "bool": true, 210 "pass": "potato", 211 }, opt) 212 require.NoError(t, err) 213 214 assert.Equal(t, []string{"test2"}, config.Data().GetSectionList()) 215 assert.Equal(t, "config_test_remote", config.FileGet("test2", "type")) 216 assert.Equal(t, "true", config.FileGet("test2", "bool")) 217 gotPw := config.FileGet("test2", "pass") 218 if !noObscure { 219 gotPw = obscure.MustReveal(gotPw) 220 } 221 assert.Equal(t, "potato", gotPw) 222 223 wantPw := obscure.MustObscure("potato2") 224 _, err = config.UpdateRemote(ctx, "test2", rc.Params{ 225 "bool": false, 226 "pass": wantPw, 227 "spare": "spare", 228 }, opt) 229 require.NoError(t, err) 230 231 assert.Equal(t, []string{"test2"}, config.Data().GetSectionList()) 232 assert.Equal(t, "config_test_remote", config.FileGet("test2", "type")) 233 assert.Equal(t, "false", config.FileGet("test2", "bool")) 234 gotPw = config.FileGet("test2", "pass") 235 if doObscure { 236 gotPw = obscure.MustReveal(gotPw) 237 } 238 assert.Equal(t, wantPw, gotPw) 239 240 require.NoError(t, config.PasswordRemote(ctx, "test2", rc.Params{ 241 "pass": "potato3", 242 })) 243 244 assert.Equal(t, []string{"test2"}, config.Data().GetSectionList()) 245 assert.Equal(t, "config_test_remote", config.FileGet("test2", "type")) 246 assert.Equal(t, "false", config.FileGet("test2", "bool")) 247 assert.Equal(t, "potato3", obscure.MustReveal(config.FileGet("test2", "pass"))) 248 }) 249 } 250 } 251 } 252 253 func TestDefaultRequired(t *testing.T) { 254 // By default options are optional (sic), regardless if a default value is defined. 255 // Setting Required=true means empty string is no longer allowed, except when 256 // a default value is set: Default value means empty string is always allowed! 257 options := []fs.Option{{ 258 Name: "string_required", 259 Required: true, 260 }, { 261 Name: "string_default", 262 Default: "AAA", 263 }, { 264 Name: "string_required_default", 265 Default: "BBB", 266 Required: true, 267 }} 268 269 defer testConfigFile(t, options, "crud.conf")() 270 ctx := context.Background() 271 272 // script for creating remote 273 config.ReadLine = makeReadLine([]string{ 274 "config_test_remote", // type 275 "111", // string_required 276 "222", // string_default 277 "333", // string_required_default 278 "y", // looks good, save 279 }) 280 require.NoError(t, config.NewRemote(ctx, "test")) 281 282 assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) 283 assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) 284 assert.Equal(t, "111", config.FileGet("test", "string_required")) 285 assert.Equal(t, "222", config.FileGet("test", "string_default")) 286 assert.Equal(t, "333", config.FileGet("test", "string_required_default")) 287 288 // delete remote 289 config.DeleteRemote("test") 290 assert.Equal(t, []string{}, config.Data().GetSectionList()) 291 292 // script for creating remote 293 config.ReadLine = makeReadLine([]string{ 294 "config_test_remote", // type 295 "", // string_required - invalid (empty string not allowed) 296 "111", // string_required - valid 297 "", // string_default (empty string allowed, means use default) 298 "", // string_required_default (empty string allowed, means use default) 299 "y", // looks good, save 300 }) 301 require.NoError(t, config.NewRemote(ctx, "test")) 302 303 assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) 304 assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) 305 assert.Equal(t, "111", config.FileGet("test", "string_required")) 306 assert.Equal(t, "", config.FileGet("test", "string_default")) 307 assert.Equal(t, "", config.FileGet("test", "string_required_default")) 308 } 309 310 func TestMultipleChoice(t *testing.T) { 311 // Multiple-choice options can be set to the number of a predefined choice, or 312 // its text. Unless Exclusive=true, tested later, any free text input is accepted. 313 // 314 // By default options are optional, regardless if a default value is defined. 315 // Setting Required=true means empty string is no longer allowed, except when 316 // a default value is set: Default value means empty string is always allowed! 317 options := []fs.Option{{ 318 Name: "multiple_choice", 319 Examples: []fs.OptionExample{{ 320 Value: "AAA", 321 Help: "This is value AAA", 322 }, { 323 Value: "BBB", 324 Help: "This is value BBB", 325 }, { 326 Value: "CCC", 327 Help: "This is value CCC", 328 }}, 329 }, { 330 Name: "multiple_choice_required", 331 Required: true, 332 Examples: []fs.OptionExample{{ 333 Value: "AAA", 334 Help: "This is value AAA", 335 }, { 336 Value: "BBB", 337 Help: "This is value BBB", 338 }, { 339 Value: "CCC", 340 Help: "This is value CCC", 341 }}, 342 }, { 343 Name: "multiple_choice_default", 344 Default: "BBB", 345 Examples: []fs.OptionExample{{ 346 Value: "AAA", 347 Help: "This is value AAA", 348 }, { 349 Value: "BBB", 350 Help: "This is value BBB", 351 }, { 352 Value: "CCC", 353 Help: "This is value CCC", 354 }}, 355 }, { 356 Name: "multiple_choice_required_default", 357 Required: true, 358 Default: "BBB", 359 Examples: []fs.OptionExample{{ 360 Value: "AAA", 361 Help: "This is value AAA", 362 }, { 363 Value: "BBB", 364 Help: "This is value BBB", 365 }, { 366 Value: "CCC", 367 Help: "This is value CCC", 368 }}, 369 }} 370 371 defer testConfigFile(t, options, "crud.conf")() 372 ctx := context.Background() 373 374 // script for creating remote 375 config.ReadLine = makeReadLine([]string{ 376 "config_test_remote", // type 377 "3", // multiple_choice 378 "3", // multiple_choice_required 379 "3", // multiple_choice_default 380 "3", // multiple_choice_required_default 381 "y", // looks good, save 382 }) 383 require.NoError(t, config.NewRemote(ctx, "test")) 384 385 assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) 386 assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) 387 assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice")) 388 assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice_required")) 389 assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice_default")) 390 assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice_required_default")) 391 392 // delete remote 393 config.DeleteRemote("test") 394 assert.Equal(t, []string{}, config.Data().GetSectionList()) 395 396 // script for creating remote 397 config.ReadLine = makeReadLine([]string{ 398 "config_test_remote", // type 399 "XXX", // multiple_choice 400 "XXX", // multiple_choice_required 401 "XXX", // multiple_choice_default 402 "XXX", // multiple_choice_required_default 403 "y", // looks good, save 404 }) 405 require.NoError(t, config.NewRemote(ctx, "test")) 406 407 assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) 408 assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) 409 assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice")) 410 assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice_required")) 411 assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice_default")) 412 assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice_required_default")) 413 414 // delete remote 415 config.DeleteRemote("test") 416 assert.Equal(t, []string{}, config.Data().GetSectionList()) 417 418 // script for creating remote 419 config.ReadLine = makeReadLine([]string{ 420 "config_test_remote", // type 421 "", // multiple_choice (empty string allowed) 422 "", // multiple_choice_required - invalid (empty string not allowed) 423 "XXX", // multiple_choice_required - valid (value not restricted to examples) 424 "", // multiple_choice_default (empty string allowed) 425 "", // multiple_choice_required_default (required does nothing when default is set) 426 "y", // looks good, save 427 }) 428 require.NoError(t, config.NewRemote(ctx, "test")) 429 430 assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) 431 assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) 432 assert.Equal(t, "", config.FileGet("test", "multiple_choice")) 433 assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice_required")) 434 assert.Equal(t, "", config.FileGet("test", "multiple_choice_default")) 435 assert.Equal(t, "", config.FileGet("test", "multiple_choice_required_default")) 436 } 437 438 func TestMultipleChoiceExclusive(t *testing.T) { 439 // Setting Exclusive=true on multiple-choice option means any input 440 // value must be from the predefined list, but empty string is allowed. 441 // Setting a default value makes no difference. 442 options := []fs.Option{{ 443 Name: "multiple_choice_exclusive", 444 Exclusive: true, 445 Examples: []fs.OptionExample{{ 446 Value: "AAA", 447 Help: "This is value AAA", 448 }, { 449 Value: "BBB", 450 Help: "This is value BBB", 451 }, { 452 Value: "CCC", 453 Help: "This is value CCC", 454 }}, 455 }, { 456 Name: "multiple_choice_exclusive_default", 457 Exclusive: true, 458 Default: "CCC", 459 Examples: []fs.OptionExample{{ 460 Value: "AAA", 461 Help: "This is value AAA", 462 }, { 463 Value: "BBB", 464 Help: "This is value BBB", 465 }, { 466 Value: "CCC", 467 Help: "This is value CCC", 468 }}, 469 }} 470 471 defer testConfigFile(t, options, "crud.conf")() 472 ctx := context.Background() 473 474 // script for creating remote 475 config.ReadLine = makeReadLine([]string{ 476 "config_test_remote", // type 477 "XXX", // multiple_choice_exclusive - invalid (not a value from examples) 478 "", // multiple_choice_exclusive - valid (empty string allowed) 479 "YYY", // multiple_choice_exclusive_default - invalid (not a value from examples) 480 "", // multiple_choice_exclusive_default - valid (empty string allowed) 481 "y", // looks good, save 482 }) 483 require.NoError(t, config.NewRemote(ctx, "test")) 484 485 assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) 486 assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) 487 assert.Equal(t, "", config.FileGet("test", "multiple_choice_exclusive")) 488 assert.Equal(t, "", config.FileGet("test", "multiple_choice_exclusive_default")) 489 } 490 491 func TestMultipleChoiceExclusiveRequired(t *testing.T) { 492 // Setting Required=true together with Exclusive=true on multiple-choice option 493 // means empty string is no longer allowed, except when a default value is set 494 // (default value means empty string is always allowed). 495 options := []fs.Option{{ 496 Name: "multiple_choice_exclusive_required", 497 Exclusive: true, 498 Required: true, 499 Examples: []fs.OptionExample{{ 500 Value: "AAA", 501 Help: "This is value AAA", 502 }, { 503 Value: "BBB", 504 Help: "This is value BBB", 505 }, { 506 Value: "CCC", 507 Help: "This is value CCC", 508 }}, 509 }, { 510 Name: "multiple_choice_exclusive_required_default", 511 Exclusive: true, 512 Required: true, 513 Default: "CCC", 514 Examples: []fs.OptionExample{{ 515 Value: "AAA", 516 Help: "This is value AAA", 517 }, { 518 Value: "BBB", 519 Help: "This is value BBB", 520 }, { 521 Value: "CCC", 522 Help: "This is value CCC", 523 }}, 524 }} 525 526 defer testConfigFile(t, options, "crud.conf")() 527 ctx := context.Background() 528 529 // script for creating remote 530 config.ReadLine = makeReadLine([]string{ 531 "config_test_remote", // type 532 "XXX", // multiple_choice_exclusive_required - invalid (not a value from examples) 533 "", // multiple_choice_exclusive_required - invalid (empty string not allowed) 534 "CCC", // multiple_choice_exclusive_required - valid 535 "XXX", // multiple_choice_exclusive_required_default - invalid (not a value from examples) 536 "", // multiple_choice_exclusive_required_default - valid (empty string allowed) 537 "y", // looks good, save 538 }) 539 require.NoError(t, config.NewRemote(ctx, "test")) 540 541 assert.Equal(t, []string{"test"}, config.Data().GetSectionList()) 542 assert.Equal(t, "config_test_remote", config.FileGet("test", "type")) 543 assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice_exclusive_required")) 544 assert.Equal(t, "", config.FileGet("test", "multiple_choice_exclusive_required_default")) 545 }