Compare commits
5 Commits
main
...
issue-1996
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
549db4a66d | ||
|
|
a8f008d057 | ||
|
|
e17d9e89a1 | ||
|
|
b25df2ce39 | ||
|
|
b3665291cf |
214
design/cli.md
214
design/cli.md
@ -1,214 +0,0 @@
|
||||
# CLI Commands
|
||||
## Status
|
||||
|
||||
Revision: v0.0.1
|
||||
|
||||
-----
|
||||
|
||||
|
||||
This is a proposal for Corso cli commands extrapolated from the Functional Requirements product documentation. Open questions are listed in the `Details & Discussion` section. The command set includes some p1/p2 actions for completeness. This proposal only intends to describe the available commands themselves and does not evaluate functionality or feature design beyond that goal.
|
||||
|
||||
# CLI Goals
|
||||
|
||||
- Ease (and enjoyment) of Use, more than minimal functionality.
|
||||
- Intended for use by Humans, not Computers.
|
||||
- Outputs should be either interactive/progressive (for ongoing work) or easily greppable/parseable.
|
||||
|
||||
## Todo/Undefined:
|
||||
|
||||
- Interactivity and sub-selection/helpful action completion within command operation.
|
||||
- Quality-of-life and niceties such as interactive/output display, formatting and presentation, or maximum minimization of user effort to run Corso.
|
||||
|
||||
-----
|
||||
## Commands
|
||||
|
||||
Standard format:
|
||||
`corso {command} [{subcommand}] [{service|repository}] [{flag}...]`
|
||||
|
||||
| Cmd | | | Flags | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| version | | | | Same as `corso --version` |
|
||||
| | | | —version | Outputs Corso version details. |
|
||||
| help | | | | Same as `corso —-help` |
|
||||
| * | * | help | | Same as `{command} -—help` |
|
||||
| * | * | | —help | Same as `{command} help` |
|
||||
|
||||
| Cmd | | | Flags | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| repo | * | | | Same as `repo [*] --help`. |
|
||||
| repo | init | {repository} | | Initialize a Corso repository. |
|
||||
| repo | init | {repository} | —tenant {azure_tenant_id} | Provides the account’s tenant ID. |
|
||||
| repo | init | {repository} | —client {azure_client_id} | Provides the account’s client ID. |
|
||||
| repo | connect | {repository} | | Connects to the specified repo. |
|
||||
| repo | configure | {repository} | | Sets mutable config properties to the provided values. |
|
||||
| repo | * | * | —config {cfg_file_path} | Specify a repo configuration file. Values may also be provided via individual flags and env vars. |
|
||||
| repo | * | * | —{config-prop} | Blanket commitment to support config via flags. |
|
||||
| repo | * | * | —credentials {creds_file_path} | Specify a file containing credentials or secrets. Values may also be provided via env vars. |
|
||||
|
||||
| Cmd | | | Flags | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| backup | * | | | Same as backup [*] -—help |
|
||||
| backup | list | {service} | | List all backups in the repository for the specified service. |
|
||||
| backup | create | {service} | | Backup the specified service. |
|
||||
| backup | * | {service} | —token {token} | Provides a security key for permission to perform backup. |
|
||||
| backup | * | {service} | —{entity} {entity_id}... | Only involve the target entity(s). Entities are things like users, groups, sites, etc. Entity flag support is service-specific. |
|
||||
|
||||
| Cmd | | | Flags | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| restore | | | | Same as `restore -—help` |
|
||||
| restore | {service} | | | Complete service restoration using the latest versioned backup. |
|
||||
| restore | {service} | | —backup {backup_id} | Restore data from only the targeted backup(s). |
|
||||
| restore | {service} | | —{entity} {entity_id}... | Only involve the target entity(s). Entities are things like users, groups, sites, etc. Entity flag support is service-specific. |
|
||||
---
|
||||
|
||||
|
||||
## Examples
|
||||
### Basic Usage
|
||||
|
||||
**First Run**
|
||||
|
||||
```bash
|
||||
$ export AZURE_CLIENT_SECRET=my_azure_secret
|
||||
$ export AWS_SECRET_ACCESS_KEY=my_s3_secret
|
||||
$ corso repo init s3 --bucket my_s3_bucket --access-key my_s3_key \
|
||||
--tenant my_azure_tenant_id --clientid my_azure_client_id
|
||||
$ corso backup express
|
||||
```
|
||||
|
||||
**Follow-up Actions**
|
||||
|
||||
```bash
|
||||
$ corso repo connect s3 --bucket my_s3_bucket --access-key my_s3_key
|
||||
$ corso backup express
|
||||
$ corso backup list express
|
||||
```
|
||||
-----
|
||||
|
||||
# Details & Discussion
|
||||
|
||||
## UC0 - CLI User Interface
|
||||
|
||||
Base command: `corso`
|
||||
|
||||
Standard format: `corso {command} [{subcommand}] [{service}] [{flag}...]`
|
||||
|
||||
Examples:
|
||||
|
||||
- `corso help`
|
||||
- `corso repo init --repository s3 --tenant t_1`
|
||||
- `corso backup create teams`
|
||||
- `corso restore teams --backup b_1`
|
||||
|
||||
## UC1 - Initialization and Connection
|
||||
|
||||
**Account Handling**
|
||||
|
||||
M365 accounts are paired with repo initialization, resulting in a single-tenancy storage. Any `repo` action applies the same behavior to the account as well. That is, `init` will handle all initialization steps for both the repository and the account, and both must succeed for the command to complete successfully, including all necessary validation checks. Likewise, `connect` will validate and establish a connection (or, at least, the ability to communicate) with both the account and the repository.
|
||||
|
||||
**Init**
|
||||
|
||||
`corso repo init {repository} --config {cfg} --credentials {creds}`
|
||||
|
||||
Initializes a repository, bootstrapping resources as necessary and storing configuration details within Corso. Repo is the name of the repository provider, eg: ‘s3’. Cfg and creds, in this example, point to json (or alternatively yaml?) files containing the details required to establish the connection. Configuration options, when known, will get support for flag-based declaration. Similarly, env vars will be supported as needed.
|
||||
|
||||
**Connection**
|
||||
|
||||
`corso repo connect {repository} --credentials {creds}`
|
||||
|
||||
[https://docs.flexera.com/flexera/EN/SaaSManager/M365CCIntegration.htm#integrations_3059193938_1840275](https://docs.flexera.com/flexera/EN/SaaSManager/M365CCIntegration.htm#integrations_3059193938_1840275)
|
||||
|
||||
Connects to an existing (ie, initialized) repository.
|
||||
|
||||
Corso is expected to gracefully handle transient disconnections during backup/restore runtimes (and otherwise, as needed).
|
||||
|
||||
**Deletion**
|
||||
|
||||
`corso repo delete {repository}`
|
||||
|
||||
(Included here for discussion, but not being added to the CLI command set at this time.)
|
||||
|
||||
Removes a repository from Corso. More exploration is needed here to explore cascading effects (or lack thereof) from the command. At minimum, expect additional user involvement to confirm that the deletion is wanted, and not erroneous.
|
||||
|
||||
## UC1.1 - Version
|
||||
|
||||
`corso --version` outputs the current version details such as: commit id and datetime, maybe semver (complete release version details to be decided).
|
||||
Further versioning controls are not currently covered in this proposal.
|
||||
|
||||
## UC2 - Configuration
|
||||
|
||||
`corso repo configure --reposiory {repo} --config {cfg}`
|
||||
|
||||
Updates the configuration details for an existing repository.
|
||||
|
||||
Configuration is divided between mutable and immutable properties. Generally, initialization-specific configurations (those that identify the storage repository, it’s connection, and its fundamental behavior), among other properties, are considered immutable and cannot be reconfigured. As a result, `repo configure` will not be able to rectify a misconfigured init; some other user flow will be needed to resolve that issue.
|
||||
|
||||
Configure allows mutation of config properties that can be safely and transiently applied. For example: backup retention and expiration policies. A complete list of how each property is classified is forthcoming as we build that list of properties.
|
||||
|
||||
## UC3 - On-Demand Backup
|
||||
|
||||
`corso backup` is reserved as a non-actionable command, rather than have it kick off a backup action. This is to ensure users don’t accidentally kick off a migration in the process of exploring the api. `corso backup` produces the same output as `corso backup --help`.
|
||||
|
||||
**Full Service Backup**
|
||||
|
||||
- `corso backup create {service}`
|
||||
|
||||
**Selective Backup**
|
||||
|
||||
- `corso backup create {service} --{entity} {entity_id}...`
|
||||
|
||||
Entities are service-applicable objects that match up to m365 objects. Users, groups, sites, mailboxes, etc. Entity flags are available on a per-service basis. For example, —site is available for the sharepoint service, and —mailbox for express, but not the reverse. A full list of system-entity mappings is coming in the future.
|
||||
|
||||
**Examples**
|
||||
|
||||
- `corso backup` → displays the help output.
|
||||
- `corso backup create teams` → generates a full backup of the teams service.
|
||||
- `corso backup create express --group g_1` → backs up the g_1 group within express.
|
||||
|
||||
## UC3.2 - Security Token
|
||||
|
||||
(This section is incomplete: further design details are needed about security expression.) Some commands, such as Backup/Restore require a security key declaration to verify that the caller has permission to perform the command.
|
||||
|
||||
`corso * * --token {token}`
|
||||
|
||||
## UC5 - Backup Ops
|
||||
|
||||
`corso backup list {service}`
|
||||
|
||||
Produces a list of the backups which currently exist in the repository.
|
||||
|
||||
`corso backup list {service} --{entity} {entity_id}...`
|
||||
|
||||
The list can be filtered to contain backups relevant to the specified entities. A possible user flow for restoration is for the user to use this to discover which backups match their needs, and then apply those backups in a restore operation.
|
||||
|
||||
**Expiration Control**
|
||||
|
||||
Will appear in a future revision.
|
||||
|
||||
## UC6 - Restore
|
||||
|
||||
Similar to backup, `corso restore` is reserved as a non-actionable command to serve up the same output as `corso restore —help`.
|
||||
|
||||
### UC6.1
|
||||
|
||||
**Full Service Restore**
|
||||
|
||||
- `corso restore {service} [--backup {backup_id}...]`
|
||||
|
||||
If no backups are specified, this defaults to the most recent backup of the specified service.
|
||||
|
||||
**Selective Restore**
|
||||
|
||||
- `corso restore {service} [--backup {backup_id}...] [--{entity} {entity_id}...]`
|
||||
|
||||
Entities are service-applicable objects that match up to m365 objects. Users, groups, sites, mailboxes, etc. Entity flags are available on a per-service basis. For example, —site is available for the sharepoint service, and —mailbox for express, but not the reverse. A full list of system-entity mappings is coming in the future.
|
||||
|
||||
**Examples**
|
||||
|
||||
- `corso restore` → displays the help output.
|
||||
- `corso restore teams` → restores all data in the teams service.
|
||||
- `corso restore sharepoint --backup b_1` → restores the sharepoint data in the b_1 backup.
|
||||
- `corso restore express --group g_1` → restores the g_1 group within sharepoint.
|
||||
|
||||
## UC6.2 - disaster recovery
|
||||
|
||||
Multi-service backup/restoration is still under review.
|
||||
@ -175,7 +175,6 @@ func DataCollections(
|
||||
|
||||
var (
|
||||
user = selector.DiscreteOwner
|
||||
scopes = eb.DiscreteScopes([]string{user})
|
||||
collections = []data.Collection{}
|
||||
errs error
|
||||
)
|
||||
@ -185,7 +184,7 @@ func DataCollections(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, scope := range scopes {
|
||||
for _, scope := range eb.Scopes() {
|
||||
dps := cdps[scope.Category().PathType()]
|
||||
|
||||
dcs, err := createCollections(
|
||||
|
||||
@ -207,8 +207,8 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
|
||||
return opStats.writeErr
|
||||
}
|
||||
|
||||
// TODO: should always be 1, since backups are 1:1 with resourceOwners now.
|
||||
opStats.resourceCount = len(data.ResourceOwnerSet(cs))
|
||||
// should always be 1, since backups are 1:1 with resourceOwners.
|
||||
opStats.resourceCount = 1
|
||||
opStats.started = true
|
||||
opStats.gc = gc.AwaitStatus()
|
||||
|
||||
|
||||
@ -150,7 +150,6 @@ func (op *RestoreOperation) Run(ctx context.Context) (restoreDetails *details.De
|
||||
events.BackupID: op.BackupID,
|
||||
events.BackupCreateTime: bup.CreationTime,
|
||||
events.RestoreID: opStats.restoreID,
|
||||
// TODO: restore options,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@ -229,8 +229,7 @@ func (d *Details) addFolder(folder folderEntry) {
|
||||
|
||||
// DetailsEntry describes a single item stored in a Backup
|
||||
type DetailsEntry struct {
|
||||
// TODO: `RepoRef` is currently the full path to the item in Kopia
|
||||
// This can be optimized.
|
||||
// RepoRef is the full storage path of the item in Kopia
|
||||
RepoRef string `json:"repoRef"`
|
||||
ShortRef string `json:"shortRef"`
|
||||
ParentRef string `json:"parentRef,omitempty"`
|
||||
|
||||
@ -188,21 +188,6 @@ func (s *exchange) Scopes() []ExchangeScope {
|
||||
return scopes[ExchangeScope](s.Selector)
|
||||
}
|
||||
|
||||
// DiscreteScopes retrieves the list of exchangeScopes in the selector.
|
||||
// If any Include scope's User category is set to Any, replaces that
|
||||
// scope's value with the list of userPNs instead.
|
||||
func (s *exchange) DiscreteScopes(userPNs []string) []ExchangeScope {
|
||||
scopes := discreteScopes[ExchangeScope](s.Includes, ExchangeUser, userPNs)
|
||||
|
||||
ss := make([]ExchangeScope, 0, len(scopes))
|
||||
|
||||
for _, scope := range scopes {
|
||||
ss = append(ss, ExchangeScope(scope))
|
||||
}
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
type ExchangeItemScopeConstructor func([]string, []string, ...option) []ExchangeScope
|
||||
|
||||
// -------------------
|
||||
|
||||
@ -479,49 +479,6 @@ func (suite *ExchangeSelectorSuite) TestExchangeBackup_Scopes() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestExchangeBackup_DiscreteScopes() {
|
||||
usrs := []string{"u1", "u2"}
|
||||
table := []struct {
|
||||
name string
|
||||
include []string
|
||||
discrete []string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
name: "any user",
|
||||
include: Any(),
|
||||
discrete: usrs,
|
||||
expect: usrs,
|
||||
},
|
||||
{
|
||||
name: "discrete user",
|
||||
include: []string{"u3"},
|
||||
discrete: usrs,
|
||||
expect: []string{"u3"},
|
||||
},
|
||||
{
|
||||
name: "nil discrete slice",
|
||||
include: Any(),
|
||||
discrete: nil,
|
||||
expect: Any(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
// todo: remove discreteScopes
|
||||
// eb := NewExchangeBackup(test.include)
|
||||
// eb.Include(eb.AllData())
|
||||
|
||||
// scopes := eb.DiscreteScopes(test.discrete)
|
||||
// for _, sc := range scopes {
|
||||
// users := sc.Get(ExchangeUser)
|
||||
// assert.Equal(t, test.expect, users)
|
||||
// }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestExchangeScope_Category() {
|
||||
table := []struct {
|
||||
is exchangeCategory
|
||||
@ -1144,7 +1101,7 @@ func (suite *ExchangeSelectorSuite) TestPasses() {
|
||||
)
|
||||
|
||||
var (
|
||||
es = NewExchangeRestore(Any()) // TODO: move into test and compose with each test value
|
||||
es = NewExchangeRestore(Any())
|
||||
otherMail = setScopesToDefault(es.Mails(Any(), []string{"smarf"}))
|
||||
mail = setScopesToDefault(es.Mails(Any(), []string{mid}))
|
||||
noMail = setScopesToDefault(es.Mails(Any(), None()))
|
||||
@ -1186,7 +1143,7 @@ func (suite *ExchangeSelectorSuite) TestContains() {
|
||||
target := "fnords"
|
||||
|
||||
var (
|
||||
es = NewExchangeRestore(Any()) // TODO: move into test and compose with each test value
|
||||
es = NewExchangeRestore(Any())
|
||||
noMail = setScopesToDefault(es.Mails(None(), None()))
|
||||
does = setScopesToDefault(es.Mails(Any(), []string{target}))
|
||||
doesNot = setScopesToDefault(es.Mails(Any(), []string{"smarf"}))
|
||||
@ -1221,7 +1178,7 @@ func (suite *ExchangeSelectorSuite) TestContains() {
|
||||
|
||||
func (suite *ExchangeSelectorSuite) TestIsAny() {
|
||||
var (
|
||||
es = NewExchangeRestore(Any()) // TODO: move into test and compose with each test value
|
||||
es = NewExchangeRestore(Any())
|
||||
specificMail = setScopesToDefault(es.Mails(Any(), []string{"email"}))
|
||||
anyMail = setScopesToDefault(es.Mails(Any(), Any()))
|
||||
)
|
||||
|
||||
@ -181,21 +181,6 @@ func (s *oneDrive) Scopes() []OneDriveScope {
|
||||
return scopes[OneDriveScope](s.Selector)
|
||||
}
|
||||
|
||||
// DiscreteScopes retrieves the list of oneDriveScopes in the selector.
|
||||
// If any Include scope's User category is set to Any, replaces that
|
||||
// scope's value with the list of userPNs instead.
|
||||
func (s *oneDrive) DiscreteScopes(userPNs []string) []OneDriveScope {
|
||||
scopes := discreteScopes[OneDriveScope](s.Includes, OneDriveUser, userPNs)
|
||||
|
||||
ss := make([]OneDriveScope, 0, len(scopes))
|
||||
|
||||
for _, scope := range scopes {
|
||||
ss = append(ss, OneDriveScope(scope))
|
||||
}
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
// -------------------
|
||||
// Scope Factories
|
||||
|
||||
|
||||
@ -39,49 +39,6 @@ func (suite *OneDriveSelectorSuite) TestToOneDriveBackup() {
|
||||
assert.NotZero(t, ob.Scopes())
|
||||
}
|
||||
|
||||
func (suite *OneDriveSelectorSuite) TestOneDriveBackup_DiscreteScopes() {
|
||||
usrs := []string{"u1", "u2"}
|
||||
table := []struct {
|
||||
name string
|
||||
include []string
|
||||
discrete []string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
name: "any user",
|
||||
include: Any(),
|
||||
discrete: usrs,
|
||||
expect: usrs,
|
||||
},
|
||||
{
|
||||
name: "discrete user",
|
||||
include: []string{"u3"},
|
||||
discrete: usrs,
|
||||
expect: []string{"u3"},
|
||||
},
|
||||
{
|
||||
name: "nil discrete slice",
|
||||
include: Any(),
|
||||
discrete: nil,
|
||||
expect: Any(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
// todo: remove discreteScopes
|
||||
// eb := NewOneDriveBackup(test.include)
|
||||
// eb.Include(eb.AllData())
|
||||
|
||||
// scopes := eb.DiscreteScopes(test.discrete)
|
||||
// for _, sc := range scopes {
|
||||
// users := sc.Get(OneDriveUser)
|
||||
// assert.Equal(t, test.expect, users)
|
||||
// }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *OneDriveSelectorSuite) TestOneDriveSelector_AllData() {
|
||||
t := suite.T()
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||
"github.com/alcionai/corso/src/pkg/filters"
|
||||
@ -182,11 +181,6 @@ func splitByResourceOwner[T scopeT, C categoryT](s Selector, allOwners []string,
|
||||
for _, ro := range targets {
|
||||
c := s
|
||||
c.DiscreteOwner = ro
|
||||
|
||||
// TODO: when the rootCat gets removed from the scopes, we can remove this
|
||||
c.Includes = discreteScopes[T](s.Includes, rootCat, []string{ro})
|
||||
c.Filters = discreteScopes[T](s.Filters, rootCat, []string{ro})
|
||||
|
||||
ss = append(ss, c)
|
||||
}
|
||||
|
||||
@ -221,7 +215,6 @@ func appendScopes[T scopeT](to []scope, scopes ...[]T) []scope {
|
||||
}
|
||||
|
||||
// scopes retrieves the list of scopes in the selector.
|
||||
// future TODO: if Inclues is nil, return filters.
|
||||
func scopes[T scopeT](s Selector) []T {
|
||||
scopes := []T{}
|
||||
|
||||
@ -232,38 +225,6 @@ func scopes[T scopeT](s Selector) []T {
|
||||
return scopes
|
||||
}
|
||||
|
||||
// discreteScopes retrieves the list of scopes in the selector.
|
||||
// for any scope in the `Includes` set, if scope.IsAny(rootCat),
|
||||
// then that category's value is replaced with the provided set of
|
||||
// discrete identifiers.
|
||||
// If discreteIDs is an empty slice, returns the normal scopes(s).
|
||||
// future TODO: if Includes is nil, return filters.
|
||||
func discreteScopes[T scopeT, C categoryT](
|
||||
scopes []scope,
|
||||
rootCat C,
|
||||
discreteIDs []string,
|
||||
) []scope {
|
||||
sl := []scope{}
|
||||
|
||||
if len(discreteIDs) == 0 {
|
||||
return scopes
|
||||
}
|
||||
|
||||
for _, v := range scopes {
|
||||
t := T(v)
|
||||
|
||||
if isAnyTarget(t, rootCat) {
|
||||
w := maps.Clone(t)
|
||||
set(w, rootCat, discreteIDs)
|
||||
t = w
|
||||
}
|
||||
|
||||
sl = append(sl, scope(t))
|
||||
}
|
||||
|
||||
return sl
|
||||
}
|
||||
|
||||
// Returns the path.ServiceType matching the selector service.
|
||||
func (s Selector) PathService() path.ServiceType {
|
||||
return serviceToPathType[s.Service]
|
||||
|
||||
@ -179,21 +179,6 @@ func (s *sharePoint) Scopes() []SharePointScope {
|
||||
return scopes[SharePointScope](s.Selector)
|
||||
}
|
||||
|
||||
// DiscreteScopes retrieves the list of sharePointScopes in the selector.
|
||||
// If any Include scope's Site category is set to Any, replaces that
|
||||
// scope's value with the list of siteIDs instead.
|
||||
func (s *sharePoint) DiscreteScopes(siteIDs []string) []SharePointScope {
|
||||
scopes := discreteScopes[SharePointScope](s.Includes, SharePointSite, siteIDs)
|
||||
|
||||
ss := make([]SharePointScope, 0, len(scopes))
|
||||
|
||||
for _, scope := range scopes {
|
||||
ss = append(ss, SharePointScope(scope))
|
||||
}
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
// -------------------
|
||||
// Scope Factories
|
||||
|
||||
|
||||
@ -37,49 +37,6 @@ func (suite *SharePointSelectorSuite) TestToSharePointBackup() {
|
||||
assert.NotZero(t, ob.Scopes())
|
||||
}
|
||||
|
||||
func (suite *SharePointSelectorSuite) TestSharePointBackup_DiscreteScopes() {
|
||||
sites := []string{"s1", "s2"}
|
||||
table := []struct {
|
||||
name string
|
||||
include []string
|
||||
discrete []string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
name: "any site",
|
||||
include: Any(),
|
||||
discrete: sites,
|
||||
expect: sites,
|
||||
},
|
||||
{
|
||||
name: "discrete sitet",
|
||||
include: []string{"s3"},
|
||||
discrete: sites,
|
||||
expect: []string{"s3"},
|
||||
},
|
||||
{
|
||||
name: "nil discrete slice",
|
||||
include: Any(),
|
||||
discrete: nil,
|
||||
expect: Any(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
// todo: remove discreteScopes
|
||||
// eb := NewSharePointBackup(test.include)
|
||||
// eb.Include(eb.AllData())
|
||||
|
||||
// scopes := eb.DiscreteScopes(test.discrete)
|
||||
// for _, sc := range scopes {
|
||||
// sites := sc.Get(SharePointSite)
|
||||
// assert.Equal(t, test.expect, sites)
|
||||
// }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SharePointSelectorSuite) TestSharePointSelector_AllData() {
|
||||
t := suite.T()
|
||||
|
||||
@ -368,7 +325,7 @@ func (suite *SharePointSelectorSuite) TestSharePointCategory_PathValues() {
|
||||
|
||||
func (suite *SharePointSelectorSuite) TestSharePointScope_MatchesInfo() {
|
||||
var (
|
||||
ods = NewSharePointRestore(nil) // TODO: move into test
|
||||
ods = NewSharePointRestore(nil)
|
||||
host = "www.website.com"
|
||||
pth = "/foo"
|
||||
url = host + pth
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user