From 39c85e1a840510d1a26b6346e08d9de98d9b8314 Mon Sep 17 00:00:00 2001 From: Keepers <104464746+ryanfkeepers@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:15:08 -0600 Subject: [PATCH] add output formatting control to cli (#329) * add output formatting control to cli Adds the capacity for the CLI to output either a text table or a json blob to the terminal. Table is the default behavior, json is toggled with the --json flag. --- src/cli/backup/exchange.go | 23 ++---- src/cli/cli.go | 2 + src/cli/print/print.go | 87 +++++++++++++++++++++++ src/go.mod | 9 ++- src/go.sum | 21 +++++- src/internal/operations/backup.go | 5 +- src/pkg/backup/backup.go | 82 +++++++++++++++++++++- src/pkg/backup/backup_test.go | 108 +++++++++++++++++++++++++++++ src/pkg/selectors/exchange_test.go | 4 +- 9 files changed, 313 insertions(+), 28 deletions(-) create mode 100644 src/cli/print/print.go create mode 100644 src/pkg/backup/backup_test.go diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index de3e37f3c..d944ea23f 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -1,14 +1,12 @@ package backup import ( - "os" - "github.com/pkg/errors" - "github.com/segmentio/cli" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/alcionai/corso/cli/config" + "github.com/alcionai/corso/cli/print" "github.com/alcionai/corso/cli/utils" "github.com/alcionai/corso/pkg/logger" "github.com/alcionai/corso/pkg/repository" @@ -136,15 +134,8 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to list backups in the repository") } - // TODO: Can be used to print in alternative forms (e.g. json) - p, err := cli.Format("text", os.Stdout) - if err != nil { - return err - } - defer p.Flush() - for _, rp := range rps { - p.Print(*rp) - } + print.Backups(rps) + return nil } @@ -185,13 +176,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to get backup details in the repository") } - // TODO: Can be used to print in alternative forms - p, err := cli.Format("json", os.Stdout) - if err != nil { - return err - } - defer p.Flush() - p.Print(*rpd) + print.Entries(rpd.Entries) return nil } diff --git a/src/cli/cli.go b/src/cli/cli.go index 79a11ce84..b61f1a377 100644 --- a/src/cli/cli.go +++ b/src/cli/cli.go @@ -10,6 +10,7 @@ import ( "github.com/alcionai/corso/cli/backup" "github.com/alcionai/corso/cli/config" + "github.com/alcionai/corso/cli/print" "github.com/alcionai/corso/cli/repo" "github.com/alcionai/corso/cli/restore" "github.com/alcionai/corso/pkg/logger" @@ -57,6 +58,7 @@ func handleCorsoCmd(cmd *cobra.Command, args []string) error { func Handle() { corsoCmd.Flags().BoolP("version", "v", version, "current version info") corsoCmd.PersistentFlags().StringVar(&cfgFile, "config-file", "", "config file (default is $HOME/.corso)") + print.AddOutputFlag(corsoCmd) corsoCmd.CompletionOptions.DisableDefaultCmd = true diff --git a/src/cli/print/print.go b/src/cli/print/print.go new file mode 100644 index 000000000..b9dca5996 --- /dev/null +++ b/src/cli/print/print.go @@ -0,0 +1,87 @@ +package print + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/alcionai/corso/pkg/backup" + "github.com/spf13/cobra" + "github.com/tidwall/pretty" + "github.com/tomlazar/table" +) + +var outputAsJSON bool + +// adds the --output flag to the provided command. +func AddOutputFlag(parent *cobra.Command) { + parent.PersistentFlags().BoolVar(&outputAsJSON, "json", false, "output data in JSON format") +} + +type Printable interface { + // should list the property names of the values surfaced in Values() + Headers() []string + // list of values for tabular or csv formatting + // if the backing data is nil or otherwise missing, + // values should provide an empty string as opposed to skipping entries + Values() []string +} + +// Prints the backups to the terminal with stdout. +func Backups(bs []*backup.Backup) { + ps := []Printable{} + for _, b := range bs { + ps = append(ps, b) + } + printAll(ps) +} + +// Prints the entries to the terminal with stdout. +func Entries(des []backup.DetailsEntry) { + ps := []Printable{} + for _, de := range des { + ps = append(ps, de) + } + printAll(ps) +} + +// printAll prints the slice of printable items, +// according to the caller's requested format. +func printAll(ps []Printable) { + if len(ps) == 0 { + return + } + if outputAsJSON { + outputJSON(ps) + return + } + outputTable(ps) +} + +// output to stdout the list of printable structs as json +func outputJSON(ps []Printable) { + bs, err := json.Marshal(ps) + if err != nil { + fmt.Fprintf(os.Stderr, "error formatting results to json: %v\n", err) + return + } + fmt.Println(string(pretty.Pretty(bs))) +} + +// output to stdout the list of printable structs in a table +func outputTable(ps []Printable) { + t := table.Table{ + Headers: ps[0].Headers(), + Rows: [][]string{}, + } + for _, p := range ps { + t.Rows = append(t.Rows, p.Values()) + } + _ = t.WriteTable( + os.Stdout, + &table.Config{ + ShowIndex: false, + Color: false, + AlternateColors: false, + }) +} diff --git a/src/go.mod b/src/go.mod index 77eba05ee..912960bda 100644 --- a/src/go.mod +++ b/src/go.mod @@ -13,11 +13,13 @@ require ( github.com/microsoftgraph/msgraph-sdk-go v0.28.0 github.com/microsoftgraph/msgraph-sdk-go-core v0.26.1 github.com/pkg/errors v0.9.1 - github.com/segmentio/cli v0.5.0 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.0 + github.com/tidwall/pretty v1.2.0 + github.com/tomlazar/table v0.1.2 + github.com/zeebo/assert v1.1.0 go.uber.org/zap v1.21.0 golang.org/x/tools v0.1.11 ) @@ -60,7 +62,11 @@ require ( github.com/klauspost/cpuid/v2 v2.0.14 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/microsoft/kiota-http-go v0.5.2 // indirect github.com/microsoft/kiota-serialization-text-go v0.4.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect @@ -76,6 +82,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.35.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/rs/xid v1.4.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect diff --git a/src/go.sum b/src/go.sum index 1075d9c5d..5ca88f6ef 100644 --- a/src/go.sum +++ b/src/go.sum @@ -238,8 +238,18 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microsoft/kiota-abstractions-go v0.8.1 h1:ACCwRwddJYOx+SRqfgcR8Wo8PZTd4g+JMa8lY8ABy+4= github.com/microsoft/kiota-abstractions-go v0.8.1/go.mod h1:05aCidCKhzer+yfhGeePaMUY3MH+wrAkQztBVEreTtc= github.com/microsoft/kiota-authentication-azure-go v0.3.0 h1:iLyy5qldAjBiYMGMk1r/rJkcmARA8cKboiN7/XbRxv4= @@ -313,14 +323,14 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/segmentio/cli v0.5.0 h1:AssNAdZV728i8u6LWfq9pqoeQGxiyXmTt0jrCfnjcx0= -github.com/segmentio/cli v0.5.0/go.mod h1:rktB/5TnLUnEBYdRG+jlAii0bkHWpnrb+jpXiFkoPxs= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -351,6 +361,10 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tomlazar/table v0.1.2 h1:DP8f62FzZAZk8oavepm1v/oyf4ni3/LMHWNlOinmleg= +github.com/tomlazar/table v0.1.2/go.mod h1:IecZnpep9f/BatHacfh+++ftE+lFONN8BVPi9nx5U1w= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -512,6 +526,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -540,6 +555,8 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/src/internal/operations/backup.go b/src/internal/operations/backup.go index ac4f60257..7ea973a85 100644 --- a/src/internal/operations/backup.go +++ b/src/internal/operations/backup.go @@ -108,13 +108,12 @@ func (op *BackupOperation) Run(ctx context.Context) error { } func (op *BackupOperation) createBackupModels(ctx context.Context, snapID string, details *backup.Details) error { - err := op.modelStore.Put(ctx, kopia.BackupDetailsModel, details) + err := op.modelStore.Put(ctx, kopia.BackupDetailsModel, &details.DetailsModel) if err != nil { return errors.Wrap(err, "creating backupdetails model") } - err = op.modelStore.Put(ctx, kopia.BackupModel, - backup.New(snapID, string(details.ModelStoreID))) + err = op.modelStore.Put(ctx, kopia.BackupModel, backup.New(snapID, string(details.ModelStoreID))) if err != nil { return errors.Wrap(err, "creating backup model") } diff --git a/src/pkg/backup/backup.go b/src/pkg/backup/backup.go index 7639178fc..d85ae9c23 100644 --- a/src/pkg/backup/backup.go +++ b/src/pkg/backup/backup.go @@ -23,6 +23,28 @@ type Backup struct { // - Backup "Specification" } +// Headers returns the human-readable names of properties in a Backup +// for printing out to a terminal in a columnar display. +func (b Backup) Headers() []string { + return []string{ + "Creation Time", + "Stable ID", + "Snapshot ID", + "Details ID", + } +} + +// Values returns the values matching the Headers list for printing +// out to a terminal in a columnar display. +func (b Backup) Values() []string { + return []string{ + b.CreationTime.Format(time.RFC3339Nano), + string(b.StableID), + b.SnapshotID, + b.DetailsID, + } +} + func New(snapshotID, detailsID string) *Backup { return &Backup{ CreationTime: time.Now(), @@ -31,10 +53,17 @@ func New(snapshotID, detailsID string) *Backup { } } -// Details describes what was stored in a Backup -type Details struct { +// DetailsModel describes what was stored in a Backup +type DetailsModel struct { model.BaseModel Entries []DetailsEntry `json:"entries"` +} + +// Details augments the core with a mutex for processing. +// Should be sliced back to d.DetailsModel for storage and +// printing. +type Details struct { + DetailsModel // internal mu sync.Mutex `json:"-"` @@ -48,6 +77,31 @@ type DetailsEntry struct { ItemInfo } +// Headers returns the human-readable names of properties in a DetailsEntry +// for printing out to a terminal in a columnar display. +func (de DetailsEntry) Headers() []string { + hs := []string{"Repo Ref"} + if de.ItemInfo.Exchange != nil { + hs = append(hs, de.ItemInfo.Exchange.Headers()...) + } + if de.ItemInfo.Sharepoint != nil { + hs = append(hs, de.ItemInfo.Sharepoint.Headers()...) + } + return hs +} + +// Values returns the values matching the Headers list. +func (de DetailsEntry) Values() []string { + vs := []string{de.RepoRef} + if de.ItemInfo.Exchange != nil { + vs = append(vs, de.ItemInfo.Exchange.Values()...) + } + if de.ItemInfo.Sharepoint != nil { + vs = append(vs, de.ItemInfo.Sharepoint.Values()...) + } + return vs +} + // ItemInfo is a oneOf that contains service specific // information about the item it tracks type ItemInfo struct { @@ -62,6 +116,18 @@ type ExchangeInfo struct { Received time.Time `json:"received"` } +// Headers returns the human-readable names of properties in an ExchangeInfo +// for printing out to a terminal in a columnar display. +func (e ExchangeInfo) Headers() []string { + return []string{"Sender", "Subject", "Received"} +} + +// Values returns the values matching the Headers list for printing +// out to a terminal in a columnar display. +func (e ExchangeInfo) Values() []string { + return []string{e.Sender, e.Subject, e.Received.Format(time.RFC3339Nano)} +} + // SharepointInfo describes a sharepoint item // TODO: Implement this. This is currently here // just to illustrate usage @@ -72,3 +138,15 @@ func (d *Details) Add(repoRef string, info ItemInfo) { defer d.mu.Unlock() d.Entries = append(d.Entries, DetailsEntry{RepoRef: repoRef, ItemInfo: info}) } + +// Headers returns the human-readable names of properties in a SharepointInfo +// for printing out to a terminal in a columnar display. +func (s SharepointInfo) Headers() []string { + return []string{} +} + +// Values returns the values matching the Headers list for printing +// out to a terminal in a columnar display. +func (s SharepointInfo) Values() []string { + return []string{} +} diff --git a/src/pkg/backup/backup_test.go b/src/pkg/backup/backup_test.go new file mode 100644 index 000000000..4cdeb32b1 --- /dev/null +++ b/src/pkg/backup/backup_test.go @@ -0,0 +1,108 @@ +package backup_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/zeebo/assert" + + "github.com/alcionai/corso/internal/model" + "github.com/alcionai/corso/pkg/backup" +) + +type BackupSuite struct { + suite.Suite +} + +func TestBackupSuite(t *testing.T) { + suite.Run(t, new(BackupSuite)) +} + +func (suite *BackupSuite) TestBackup_HeadersValues() { + t := suite.T() + now := time.Now() + + b := backup.Backup{ + BaseModel: model.BaseModel{ + StableID: model.ID("stable"), + }, + CreationTime: now, + SnapshotID: "snapshot", + DetailsID: "details", + } + + expectHs := []string{ + "Creation Time", + "Stable ID", + "Snapshot ID", + "Details ID", + } + hs := b.Headers() + assert.DeepEqual(t, expectHs, hs) + + expectVs := []string{ + now.Format(time.RFC3339Nano), + "stable", + "snapshot", + "details", + } + vs := b.Values() + assert.DeepEqual(t, expectVs, vs) +} + +func (suite *BackupSuite) TestDetailsEntry_HeadersValues() { + now := time.Now() + nowStr := now.Format(time.RFC3339Nano) + + table := []struct { + name string + entry backup.DetailsEntry + expectHs []string + expectVs []string + }{ + { + name: "no info", + entry: backup.DetailsEntry{ + RepoRef: "reporef", + }, + expectHs: []string{"Repo Ref"}, + expectVs: []string{"reporef"}, + }, + { + name: "exhange info", + entry: backup.DetailsEntry{ + RepoRef: "reporef", + ItemInfo: backup.ItemInfo{ + Exchange: &backup.ExchangeInfo{ + Sender: "sender", + Subject: "subject", + Received: now, + }, + }, + }, + expectHs: []string{"Repo Ref", "Sender", "Subject", "Received"}, + expectVs: []string{"reporef", "sender", "subject", nowStr}, + }, + { + name: "sharepoint info", + entry: backup.DetailsEntry{ + RepoRef: "reporef", + ItemInfo: backup.ItemInfo{ + Sharepoint: &backup.SharepointInfo{}, + }, + }, + expectHs: []string{"Repo Ref"}, + expectVs: []string{"reporef"}, + }, + } + + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + hs := test.entry.Headers() + assert.DeepEqual(t, test.expectHs, hs) + vs := test.entry.Values() + assert.DeepEqual(t, test.expectVs, vs) + }) + } +} diff --git a/src/pkg/selectors/exchange_test.go b/src/pkg/selectors/exchange_test.go index bc1dc3c73..8acbd6545 100644 --- a/src/pkg/selectors/exchange_test.go +++ b/src/pkg/selectors/exchange_test.go @@ -606,7 +606,9 @@ func (suite *ExchangeSourceSuite) TestIdPath() { func (suite *ExchangeSourceSuite) TestExchangeRestore_FilterDetails() { makeDeets := func(refs ...string) *backup.Details { deets := &backup.Details{ - Entries: []backup.DetailsEntry{}, + DetailsModel: backup.DetailsModel{ + Entries: []backup.DetailsEntry{}, + }, } for _, r := range refs { deets.Entries = append(deets.Entries, backup.DetailsEntry{