config: fix handling of default, exclusive and required properties of multiple-choice options

Previously an empty input (just pressing enter) was only allowed for multiple-choice
options that did not have the Exclusive property set. With this change the existing
Required property is introduced into the multiple choice handling, so that one can have
Exclusive and Required options where only a value from the list is allowed, and one can
have Exclusive but not Required options where an empty value is accepted but any
non-empty value must still be matching an item from the list.

Fixes #5549

See #5551
This commit is contained in:
albertony
2021-08-22 22:43:51 +02:00
parent bd4bbed592
commit 38e2f835ed
4 changed files with 470 additions and 109 deletions

View File

@@ -20,7 +20,17 @@ import (
"github.com/stretchr/testify/require"
)
func testConfigFile(t *testing.T, configFileName string) func() {
var simpleOptions = []fs.Option{{
Name: "bool",
Default: false,
IsPassword: false,
}, {
Name: "pass",
Default: "",
IsPassword: true,
}}
func testConfigFile(t *testing.T, options []fs.Option, configFileName string) func() {
ctx := context.Background()
ci := fs.GetConfig(ctx)
config.ClearConfigPassword()
@@ -46,24 +56,18 @@ func testConfigFile(t *testing.T, configFileName string) func() {
configfile.Install()
assert.Equal(t, []string{}, config.Data().GetSectionList())
// Fake a remote
fs.Register(&fs.RegInfo{
Name: "config_test_remote",
Options: fs.Options{
{
Name: "bool",
Default: false,
IsPassword: false,
},
{
Name: "pass",
Default: "",
IsPassword: true,
},
},
})
// Fake a filesystem/backend
backendName := "config_test_remote"
if regInfo, _ := fs.Find(backendName); regInfo != nil {
regInfo.Options = options
} else {
fs.Register(&fs.RegInfo{
Name: backendName,
Options: options,
})
}
// Undo the above
// Undo the above (except registered backend, unfortunately)
return func() {
err := os.Remove(path)
assert.NoError(t, err)
@@ -91,7 +95,7 @@ func makeReadLine(answers []string) func() string {
}
func TestCRUD(t *testing.T) {
defer testConfigFile(t, "crud.conf")()
defer testConfigFile(t, simpleOptions, "crud.conf")()
ctx := context.Background()
// script for creating remote
@@ -129,7 +133,7 @@ func TestCRUD(t *testing.T) {
}
func TestChooseOption(t *testing.T) {
defer testConfigFile(t, "crud.conf")()
defer testConfigFile(t, simpleOptions, "crud.conf")()
ctx := context.Background()
// script for creating remote
@@ -165,7 +169,7 @@ func TestChooseOption(t *testing.T) {
}
func TestNewRemoteName(t *testing.T) {
defer testConfigFile(t, "crud.conf")()
defer testConfigFile(t, simpleOptions, "crud.conf")()
ctx := context.Background()
// script for creating remote
@@ -189,7 +193,7 @@ func TestNewRemoteName(t *testing.T) {
func TestCreateUpdatePasswordRemote(t *testing.T) {
ctx := context.Background()
defer testConfigFile(t, "update.conf")()
defer testConfigFile(t, simpleOptions, "update.conf")()
for _, doObscure := range []bool{false, true} {
for _, noObscure := range []bool{false, true} {
@@ -244,5 +248,298 @@ func TestCreateUpdatePasswordRemote(t *testing.T) {
})
}
}
}
func TestDefaultRequired(t *testing.T) {
// By default options are optional (sic), regardless if a default value is defined.
// Setting Required=true means empty string is no longer allowed, except when
// a default value is set: Default value means empty string is always allowed!
options := []fs.Option{{
Name: "string_required",
Required: true,
}, {
Name: "string_default",
Default: "AAA",
}, {
Name: "string_required_default",
Default: "BBB",
Required: true,
}}
defer testConfigFile(t, options, "crud.conf")()
ctx := context.Background()
// script for creating remote
config.ReadLine = makeReadLine([]string{
"config_test_remote", // type
"111", // string_required
"222", // string_default
"333", // string_required_default
"y", // looks good, save
})
require.NoError(t, config.NewRemote(ctx, "test"))
assert.Equal(t, []string{"test"}, config.Data().GetSectionList())
assert.Equal(t, "config_test_remote", config.FileGet("test", "type"))
assert.Equal(t, "111", config.FileGet("test", "string_required"))
assert.Equal(t, "222", config.FileGet("test", "string_default"))
assert.Equal(t, "333", config.FileGet("test", "string_required_default"))
// delete remote
config.DeleteRemote("test")
assert.Equal(t, []string{}, config.Data().GetSectionList())
// script for creating remote
config.ReadLine = makeReadLine([]string{
"config_test_remote", // type
"", // string_required - invalid (empty string not allowed)
"111", // string_required - valid
"", // string_default (empty string allowed, means use default)
"", // string_required_default (empty string allowed, means use default)
"y", // looks good, save
})
require.NoError(t, config.NewRemote(ctx, "test"))
assert.Equal(t, []string{"test"}, config.Data().GetSectionList())
assert.Equal(t, "config_test_remote", config.FileGet("test", "type"))
assert.Equal(t, "111", config.FileGet("test", "string_required"))
assert.Equal(t, "", config.FileGet("test", "string_default"))
assert.Equal(t, "", config.FileGet("test", "string_required_default"))
}
func TestMultipleChoice(t *testing.T) {
// Multiple-choice options can be set to the number of a predefined choice, or
// its text. Unless Exclusive=true, tested later, any free text input is accepted.
//
// By default options are optional, regardless if a default value is defined.
// Setting Required=true means empty string is no longer allowed, except when
// a default value is set: Default value means empty string is always allowed!
options := []fs.Option{{
Name: "multiple_choice",
Examples: []fs.OptionExample{{
Value: "AAA",
Help: "This is value AAA",
}, {
Value: "BBB",
Help: "This is value BBB",
}, {
Value: "CCC",
Help: "This is value CCC",
}},
}, {
Name: "multiple_choice_required",
Required: true,
Examples: []fs.OptionExample{{
Value: "AAA",
Help: "This is value AAA",
}, {
Value: "BBB",
Help: "This is value BBB",
}, {
Value: "CCC",
Help: "This is value CCC",
}},
}, {
Name: "multiple_choice_default",
Default: "BBB",
Examples: []fs.OptionExample{{
Value: "AAA",
Help: "This is value AAA",
}, {
Value: "BBB",
Help: "This is value BBB",
}, {
Value: "CCC",
Help: "This is value CCC",
}},
}, {
Name: "multiple_choice_required_default",
Required: true,
Default: "BBB",
Examples: []fs.OptionExample{{
Value: "AAA",
Help: "This is value AAA",
}, {
Value: "BBB",
Help: "This is value BBB",
}, {
Value: "CCC",
Help: "This is value CCC",
}},
}}
defer testConfigFile(t, options, "crud.conf")()
ctx := context.Background()
// script for creating remote
config.ReadLine = makeReadLine([]string{
"config_test_remote", // type
"3", // multiple_choice
"3", // multiple_choice_required
"3", // multiple_choice_default
"3", // multiple_choice_required_default
"y", // looks good, save
})
require.NoError(t, config.NewRemote(ctx, "test"))
assert.Equal(t, []string{"test"}, config.Data().GetSectionList())
assert.Equal(t, "config_test_remote", config.FileGet("test", "type"))
assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice"))
assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice_required"))
assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice_default"))
assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice_required_default"))
// delete remote
config.DeleteRemote("test")
assert.Equal(t, []string{}, config.Data().GetSectionList())
// script for creating remote
config.ReadLine = makeReadLine([]string{
"config_test_remote", // type
"XXX", // multiple_choice
"XXX", // multiple_choice_required
"XXX", // multiple_choice_default
"XXX", // multiple_choice_required_default
"y", // looks good, save
})
require.NoError(t, config.NewRemote(ctx, "test"))
assert.Equal(t, []string{"test"}, config.Data().GetSectionList())
assert.Equal(t, "config_test_remote", config.FileGet("test", "type"))
assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice"))
assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice_required"))
assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice_default"))
assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice_required_default"))
// delete remote
config.DeleteRemote("test")
assert.Equal(t, []string{}, config.Data().GetSectionList())
// script for creating remote
config.ReadLine = makeReadLine([]string{
"config_test_remote", // type
"", // multiple_choice (empty string allowed)
"", // multiple_choice_required - invalid (empty string not allowed)
"XXX", // multiple_choice_required - valid (value not restricted to examples)
"", // multiple_choice_default (empty string allowed)
"", // multiple_choice_required_default (required does nothing when default is set)
"y", // looks good, save
})
require.NoError(t, config.NewRemote(ctx, "test"))
assert.Equal(t, []string{"test"}, config.Data().GetSectionList())
assert.Equal(t, "config_test_remote", config.FileGet("test", "type"))
assert.Equal(t, "", config.FileGet("test", "multiple_choice"))
assert.Equal(t, "XXX", config.FileGet("test", "multiple_choice_required"))
assert.Equal(t, "", config.FileGet("test", "multiple_choice_default"))
assert.Equal(t, "", config.FileGet("test", "multiple_choice_required_default"))
}
func TestMultipleChoiceExclusive(t *testing.T) {
// Setting Exclusive=true on multiple-choice option means any input
// value must be from the predefined list, but empty string is allowed.
// Setting a default value makes no difference.
options := []fs.Option{{
Name: "multiple_choice_exclusive",
Exclusive: true,
Examples: []fs.OptionExample{{
Value: "AAA",
Help: "This is value AAA",
}, {
Value: "BBB",
Help: "This is value BBB",
}, {
Value: "CCC",
Help: "This is value CCC",
}},
}, {
Name: "multiple_choice_exclusive_default",
Exclusive: true,
Default: "CCC",
Examples: []fs.OptionExample{{
Value: "AAA",
Help: "This is value AAA",
}, {
Value: "BBB",
Help: "This is value BBB",
}, {
Value: "CCC",
Help: "This is value CCC",
}},
}}
defer testConfigFile(t, options, "crud.conf")()
ctx := context.Background()
// script for creating remote
config.ReadLine = makeReadLine([]string{
"config_test_remote", // type
"XXX", // multiple_choice_exclusive - invalid (not a value from examples)
"", // multiple_choice_exclusive - valid (empty string allowed)
"YYY", // multiple_choice_exclusive_default - invalid (not a value from examples)
"", // multiple_choice_exclusive_default - valid (empty string allowed)
"y", // looks good, save
})
require.NoError(t, config.NewRemote(ctx, "test"))
assert.Equal(t, []string{"test"}, config.Data().GetSectionList())
assert.Equal(t, "config_test_remote", config.FileGet("test", "type"))
assert.Equal(t, "", config.FileGet("test", "multiple_choice_exclusive"))
assert.Equal(t, "", config.FileGet("test", "multiple_choice_exclusive_default"))
}
func TestMultipleChoiceExclusiveRequired(t *testing.T) {
// Setting Required=true together with Exclusive=true on multiple-choice option
// means empty string is no longer allowed, except when a default value is set
// (default value means empty string is always allowed).
options := []fs.Option{{
Name: "multiple_choice_exclusive_required",
Exclusive: true,
Required: true,
Examples: []fs.OptionExample{{
Value: "AAA",
Help: "This is value AAA",
}, {
Value: "BBB",
Help: "This is value BBB",
}, {
Value: "CCC",
Help: "This is value CCC",
}},
}, {
Name: "multiple_choice_exclusive_required_default",
Exclusive: true,
Required: true,
Default: "CCC",
Examples: []fs.OptionExample{{
Value: "AAA",
Help: "This is value AAA",
}, {
Value: "BBB",
Help: "This is value BBB",
}, {
Value: "CCC",
Help: "This is value CCC",
}},
}}
defer testConfigFile(t, options, "crud.conf")()
ctx := context.Background()
// script for creating remote
config.ReadLine = makeReadLine([]string{
"config_test_remote", // type
"XXX", // multiple_choice_exclusive_required - invalid (not a value from examples)
"", // multiple_choice_exclusive_required - invalid (empty string not allowed)
"CCC", // multiple_choice_exclusive_required - valid
"XXX", // multiple_choice_exclusive_required_default - invalid (not a value from examples)
"", // multiple_choice_exclusive_required_default - valid (empty string allowed)
"y", // looks good, save
})
require.NoError(t, config.NewRemote(ctx, "test"))
assert.Equal(t, []string{"test"}, config.Data().GetSectionList())
assert.Equal(t, "config_test_remote", config.FileGet("test", "type"))
assert.Equal(t, "CCC", config.FileGet("test", "multiple_choice_exclusive_required"))
assert.Equal(t, "", config.FileGet("test", "multiple_choice_exclusive_required_default"))
}