mirror of https://github.com/portainer/portainer
Unit tests for `enableFeaturesFromFlags` function (#6063)
* - exporting BoolPairs CLI func - added tests for enableFeaturesFromFlags function * Add a test that uses a feature flag to add change the outcome of code - and test persistence, as that's the current implementation Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io> * Minor comment updates Co-authored-by: Sven Dowideit <sven.dowideit@portainer.io> Co-authored-by: Stéphane Busso <stephane.busso@gmail.com>pull/6081/head
parent
9e9a4ca4cc
commit
7d92aa1971
|
@ -36,7 +36,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||||
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
|
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
|
||||||
FeatureFlags: boolPairs(kingpin.Flag("feat", "List of feature flags").Hidden()),
|
FeatureFlags: BoolPairs(kingpin.Flag("feat", "List of feature flags").Hidden()),
|
||||||
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
|
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),
|
||||||
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app (deprecated)").Bool(),
|
NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app (deprecated)").Bool(),
|
||||||
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
|
TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(),
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (l *pairListBool) IsCumulative() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolPairs(s kingpin.Settings) (target *[]portainer.Pair) {
|
func BoolPairs(s kingpin.Settings) (target *[]portainer.Pair) {
|
||||||
target = new([]portainer.Pair)
|
target = new([]portainer.Pair)
|
||||||
s.SetValue((*pairListBool)(target))
|
s.SetValue((*pairListBool)(target))
|
||||||
return
|
return
|
||||||
|
|
|
@ -241,7 +241,7 @@ func updateSettingsFromFlags(dataStore portainer.DataStore, flags *portainer.CLI
|
||||||
|
|
||||||
// enableFeaturesFromFlags turns on or off feature flags
|
// enableFeaturesFromFlags turns on or off feature flags
|
||||||
// e.g. portainer --feat open-amt --feat fdo=true ... (defaults to true)
|
// e.g. portainer --feat open-amt --feat fdo=true ... (defaults to true)
|
||||||
// note, settins persisted to the DB. To turn off --feat open-amt=false
|
// note, settings are persisted to the DB. To turn off `--feat open-amt=false`
|
||||||
func enableFeaturesFromFlags(dataStore portainer.DataStore, flags *portainer.CLIFlags) error {
|
func enableFeaturesFromFlags(dataStore portainer.DataStore, flags *portainer.CLIFlags) error {
|
||||||
settings, err := dataStore.Settings().Settings()
|
settings, err := dataStore.Settings().Settings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/bolt"
|
||||||
|
"github.com/portainer/portainer/api/cli"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockKingpinSetting string
|
||||||
|
|
||||||
|
func (m mockKingpinSetting) SetValue(value kingpin.Value) {
|
||||||
|
value.Set(string(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_enableFeaturesFromFlags(t *testing.T) {
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
store, teardown := bolt.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
featureFlag string
|
||||||
|
isSupported bool
|
||||||
|
}{
|
||||||
|
{"test", false},
|
||||||
|
{"openamt", false},
|
||||||
|
{"open-amt", true},
|
||||||
|
{"oPeN-amT", true},
|
||||||
|
{"fdo", true},
|
||||||
|
{"FDO", true},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%s succeeds:%v", test.featureFlag, test.isSupported), func(t *testing.T) {
|
||||||
|
mockKingpinSetting := mockKingpinSetting(test.featureFlag)
|
||||||
|
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||||
|
err := enableFeaturesFromFlags(store, flags)
|
||||||
|
if test.isSupported {
|
||||||
|
is.NoError(err)
|
||||||
|
} else {
|
||||||
|
is.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("passes for all supported feature flags", func(t *testing.T) {
|
||||||
|
for _, flag := range portainer.SupportedFeatureFlags {
|
||||||
|
mockKingpinSetting := mockKingpinSetting(flag)
|
||||||
|
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||||
|
err := enableFeaturesFromFlags(store, flags)
|
||||||
|
is.NoError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const FeatTest portainer.Feature = "optional-test"
|
||||||
|
|
||||||
|
func optionalFunc(dataStore portainer.DataStore) string {
|
||||||
|
|
||||||
|
// TODO: this is a code smell - finding out if a feature flag is enabled should not require having access to the store, and asking for a settings obj.
|
||||||
|
// ideally, the `if` should look more like:
|
||||||
|
// if featureflags.FlagEnabled(FeatTest) {}
|
||||||
|
settings, err := dataStore.Settings().Settings()
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.FeatureFlagSettings[FeatTest] {
|
||||||
|
return "enabled"
|
||||||
|
}
|
||||||
|
return "disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_optionalFeature(t *testing.T) {
|
||||||
|
portainer.SupportedFeatureFlags = append(portainer.SupportedFeatureFlags, FeatTest)
|
||||||
|
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
store, teardown := bolt.MustNewTestStore(true)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
// Enable the test feature
|
||||||
|
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||||
|
mockKingpinSetting := mockKingpinSetting(FeatTest)
|
||||||
|
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||||
|
err := enableFeaturesFromFlags(store, flags)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal("enabled", optionalFunc(store))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Same store, so the feature flag should still be enabled
|
||||||
|
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||||
|
is.Equal("enabled", optionalFunc(store))
|
||||||
|
})
|
||||||
|
|
||||||
|
// disable the test feature
|
||||||
|
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||||
|
mockKingpinSetting := mockKingpinSetting(FeatTest + "=false")
|
||||||
|
flags := &portainer.CLIFlags{FeatureFlags: cli.BoolPairs(mockKingpinSetting)}
|
||||||
|
err := enableFeaturesFromFlags(store, flags)
|
||||||
|
is.NoError(err)
|
||||||
|
is.Equal("disabled", optionalFunc(store))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Same store, so feature flag should still be disabled
|
||||||
|
t.Run(fmt.Sprintf("%s succeeds:%v", FeatTest, true), func(t *testing.T) {
|
||||||
|
is.Equal("disabled", optionalFunc(store))
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue