utilize format validation check (#4288)

adds usage of the export format validation check
to export cli commands.  Also moves the restore
cfg validation check out of the service flags
validation checks and into the generic restore runner for better separation of concerns.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🌻 Feature

#### Issue(s)

* #3988 

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-09-20 18:24:12 -06:00 committed by GitHub
parent c3e63413d4
commit 6159668b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 104 additions and 89 deletions

View File

@ -61,6 +61,10 @@ func runExport(
sel selectors.Selector, sel selectors.Selector,
backupID, serviceName string, backupID, serviceName string,
) error { ) error {
if err := utils.ValidateExportConfigFlags(&ueco); err != nil {
return Only(ctx, err)
}
r, _, _, _, err := utils.GetAccountAndConnectWithOverrides( r, _, _, _, err := utils.GetAccountAndConnectWithOverrides(
ctx, ctx,
cmd, cmd,

View File

@ -1,13 +1,7 @@
package flags package flags
import ( import (
"strings"
"github.com/alcionai/clues"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/filters"
) )
const ( const (
@ -27,20 +21,3 @@ func AddExportConfigFlags(cmd *cobra.Command) {
fs.StringVar(&FormatFV, FormatFN, "", "Specify the export file format") fs.StringVar(&FormatFV, FormatFN, "", "Specify the export file format")
cobra.CheckErr(fs.MarkHidden(FormatFN)) cobra.CheckErr(fs.MarkHidden(FormatFN))
} }
// ValidateExportConfigFlags ensures all export config flags that utilize
// enumerated values match a well-known value.
func ValidateExportConfigFlags() error {
acceptedFormatTypes := []string{
string(control.DefaultFormat),
string(control.JSONFormat),
}
if !filters.Equal(acceptedFormatTypes).Compare(FormatFV) {
return clues.New("unrecognized format type: " + FormatFV)
}
FormatFV = strings.ToLower(FormatFV)
return nil
}

View File

@ -1,43 +0,0 @@
package flags
import (
"testing"
"github.com/alcionai/clues"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/tester"
)
type ExportUnitSuite struct {
tester.Suite
}
func TestExportUnitSuite(t *testing.T) {
suite.Run(t, &ExportUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *ExportUnitSuite) TestValidateExportConfigFlags() {
t := suite.T()
FormatFV = ""
err := ValidateExportConfigFlags()
assert.NoError(t, err, clues.ToCore(err))
FormatFV = "json"
err = ValidateExportConfigFlags()
assert.NoError(t, err, clues.ToCore(err))
FormatFV = "JsoN"
err = ValidateExportConfigFlags()
assert.NoError(t, err, clues.ToCore(err))
FormatFV = "fnerds"
err = ValidateExportConfigFlags()
assert.Error(t, err, clues.ToCore(err))
}

View File

@ -94,6 +94,10 @@ func runRestore(
sel selectors.Selector, sel selectors.Selector,
backupID, serviceName string, backupID, serviceName string,
) error { ) error {
if err := utils.ValidateRestoreConfigFlags(urco); err != nil {
return Only(ctx, err)
}
r, _, _, _, err := utils.GetAccountAndConnectWithOverrides( r, _, _, _, err := utils.GetAccountAndConnectWithOverrides(
ctx, ctx,
cmd, cmd,

View File

@ -139,7 +139,7 @@ func ValidateExchangeRestoreFlags(backupID string, opts ExchangeOpts) error {
return clues.New("invalid format for event-recurs") return clues.New("invalid format for event-recurs")
} }
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg) return nil
} }
// IncludeExchangeRestoreDataSelectors builds the common data-selector // IncludeExchangeRestoreDataSelectors builds the common data-selector

View File

@ -2,11 +2,14 @@ package utils
import ( import (
"context" "context"
"strings"
"github.com/alcionai/clues"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/alcionai/corso/src/cli/flags" "github.com/alcionai/corso/src/cli/flags"
"github.com/alcionai/corso/src/pkg/control" "github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/filters"
) )
type ExportCfgOpts struct { type ExportCfgOpts struct {
@ -39,3 +42,23 @@ func MakeExportConfig(
return exportCfg return exportCfg
} }
// ValidateExportConfigFlags ensures all export config flags that utilize
// enumerated values match a well-known value.
func ValidateExportConfigFlags(opts *ExportCfgOpts) error {
acceptedFormatTypes := []string{
string(control.DefaultFormat),
string(control.JSONFormat),
}
if _, populated := opts.Populated[flags.FormatFN]; !populated {
opts.Format = string(control.DefaultFormat)
} else if !filters.Equal(acceptedFormatTypes).Compare(opts.Format) {
opts.Format = string(control.DefaultFormat)
return clues.New("unrecognized format type: " + opts.Format)
}
opts.Format = strings.ToLower(opts.Format)
return nil
}

View File

@ -3,6 +3,7 @@ package utils
import ( import (
"testing" "testing"
"github.com/alcionai/clues"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -52,3 +53,57 @@ func (suite *ExportCfgUnitSuite) TestMakeExportConfig() {
}) })
} }
} }
func (suite *ExportCfgUnitSuite) TestValidateExportConfigFlags() {
table := []struct {
name string
input ExportCfgOpts
expectErr assert.ErrorAssertionFunc
expectFormat control.FormatType
}{
{
name: "default",
input: ExportCfgOpts{
Format: string(control.DefaultFormat),
Populated: flags.PopulatedFlags{flags.FormatFN: struct{}{}},
},
expectErr: assert.NoError,
expectFormat: control.DefaultFormat,
},
{
name: "json",
input: ExportCfgOpts{
Format: string(control.JSONFormat),
Populated: flags.PopulatedFlags{flags.FormatFN: struct{}{}},
},
expectErr: assert.NoError,
expectFormat: control.JSONFormat,
},
{
name: "bad format",
input: ExportCfgOpts{
Format: "smurfs",
Populated: flags.PopulatedFlags{flags.FormatFN: struct{}{}},
},
expectErr: assert.Error,
expectFormat: control.DefaultFormat,
},
{
name: "bad format unpopulated",
input: ExportCfgOpts{
Format: "smurfs",
},
expectErr: assert.NoError,
expectFormat: control.DefaultFormat,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
err := ValidateExportConfigFlags(&test.input)
test.expectErr(t, err, clues.ToCore(err))
assert.Equal(t, test.expectFormat, control.FormatType(test.input.Format))
})
}
}

View File

@ -138,7 +138,7 @@ func ValidateGroupsRestoreFlags(backupID string, opts GroupsOpts) error {
return clues.New("invalid time format for " + flags.MessageLastReplyBeforeFN) return clues.New("invalid time format for " + flags.MessageLastReplyBeforeFN)
} }
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg) return nil
} }
// AddGroupsFilter adds the scope of the provided values to the selector's // AddGroupsFilter adds the scope of the provided values to the selector's

View File

@ -67,7 +67,7 @@ func ValidateOneDriveRestoreFlags(backupID string, opts OneDriveOpts) error {
return clues.New("invalid time format for " + flags.FileModifiedBeforeFN) return clues.New("invalid time format for " + flags.FileModifiedBeforeFN)
} }
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg) return nil
} }
// AddOneDriveFilter adds the scope of the provided values to the selector's // AddOneDriveFilter adds the scope of the provided values to the selector's

View File

@ -2,6 +2,7 @@ package utils
import ( import (
"context" "context"
"fmt"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -40,14 +41,13 @@ func makeRestoreCfgOpts(cmd *cobra.Command) RestoreCfgOpts {
} }
} }
// validateRestoreConfigFlags checks common restore flags for // ValidateRestoreConfigFlags checks common restore flags for correctness and interdependencies.
// correctness and interdependencies. func ValidateRestoreConfigFlags(opts RestoreCfgOpts) error {
func validateRestoreConfigFlags(fv string, opts RestoreCfgOpts) error {
_, populated := opts.Populated[flags.CollisionsFN] _, populated := opts.Populated[flags.CollisionsFN]
_, foundInValidSet := control.ValidCollisionPolicies()[control.CollisionPolicy(fv)] isValid := control.IsValidCollisionPolicy(control.CollisionPolicy(opts.Collisions))
if populated && !foundInValidSet { if populated && !isValid {
return clues.New("invalid entry for " + flags.CollisionsFN) return clues.New(fmt.Sprintf("invalid collision policy: %s", flags.CollisionsFN))
} }
return nil return nil

View File

@ -23,13 +23,11 @@ func TestRestoreCfgUnitSuite(t *testing.T) {
func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() { func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
table := []struct { table := []struct {
name string name string
fv string
opts RestoreCfgOpts opts RestoreCfgOpts
expect assert.ErrorAssertionFunc expect assert.ErrorAssertionFunc
}{ }{
{ {
name: "no error", name: "no error",
fv: string(control.Skip),
opts: RestoreCfgOpts{ opts: RestoreCfgOpts{
Collisions: string(control.Skip), Collisions: string(control.Skip),
Populated: flags.PopulatedFlags{ Populated: flags.PopulatedFlags{
@ -40,7 +38,6 @@ func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
}, },
{ {
name: "bad but not populated", name: "bad but not populated",
fv: "foo",
opts: RestoreCfgOpts{ opts: RestoreCfgOpts{
Collisions: "foo", Collisions: "foo",
Populated: flags.PopulatedFlags{}, Populated: flags.PopulatedFlags{},
@ -49,7 +46,6 @@ func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
}, },
{ {
name: "error", name: "error",
fv: "foo",
opts: RestoreCfgOpts{ opts: RestoreCfgOpts{
Collisions: "foo", Collisions: "foo",
Populated: flags.PopulatedFlags{ Populated: flags.PopulatedFlags{
@ -61,7 +57,7 @@ func (suite *RestoreCfgUnitSuite) TestValidateRestoreConfigFlags() {
} }
for _, test := range table { for _, test := range table {
suite.Run(test.name, func() { suite.Run(test.name, func() {
err := validateRestoreConfigFlags(test.fv, test.opts) err := ValidateRestoreConfigFlags(test.opts)
test.expect(suite.T(), err, clues.ToCore(err)) test.expect(suite.T(), err, clues.ToCore(err))
}) })
} }

View File

@ -97,7 +97,7 @@ func ValidateSharePointRestoreFlags(backupID string, opts SharePointOpts) error
return clues.New("invalid time format for " + flags.FileModifiedBeforeFN) return clues.New("invalid time format for " + flags.FileModifiedBeforeFN)
} }
return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg) return nil
} }
// AddSharePointInfo adds the scope of the provided values to the selector's // AddSharePointInfo adds the scope of the provided values to the selector's

View File

@ -7,8 +7,6 @@ import (
"strings" "strings"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"github.com/alcionai/corso/src/internal/common/dttm" "github.com/alcionai/corso/src/internal/common/dttm"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
@ -29,12 +27,13 @@ const (
Replace CollisionPolicy = "replace" Replace CollisionPolicy = "replace"
) )
func ValidCollisionPolicies() map[CollisionPolicy]struct{} { func IsValidCollisionPolicy(cp CollisionPolicy) bool {
return map[CollisionPolicy]struct{}{ switch cp {
Skip: {}, case Skip, Copy, Replace:
Copy: {}, return true
Replace: {},
} }
return false
} }
const RootLocation = "/" const RootLocation = "/"
@ -84,7 +83,7 @@ func EnsureRestoreConfigDefaults(
ctx context.Context, ctx context.Context,
rc RestoreConfig, rc RestoreConfig,
) RestoreConfig { ) RestoreConfig {
if !slices.Contains(maps.Keys(ValidCollisionPolicies()), rc.OnCollision) { if !IsValidCollisionPolicy(rc.OnCollision) {
logger.Ctx(ctx). logger.Ctx(ctx).
With( With(
"bad_collision_policy", rc.OnCollision, "bad_collision_policy", rc.OnCollision,