sanity test refactor (#4370)

refactoring the sanity tests with three goals:
1. move from env vars to cli commands so that unsupported commands fail loudly.
2. set up support for groups restore and export testing.
3. introduce some code re-use throughout.

---

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

- [x]  No

#### Type of change

- [x] 🤖 Supportability/Tests

#### Issue(s)

* #3988

#### Test Plan

- [x] 💚 E2E
This commit is contained in:
Keepers 2023-09-28 17:53:15 -06:00 committed by GitHub
parent a5f93f7a10
commit 9e0d464854
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 716 additions and 377 deletions

View File

@ -45,6 +45,9 @@ runs:
shell: bash shell: bash
working-directory: src working-directory: src
run: | run: |
echo "---------------------------"
echo Backup ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
set -euo pipefail set -euo pipefail
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-backup-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-backup-${{ inputs.service }}-${{inputs.kind }}.log
./corso backup create '${{ inputs.service }}' \ ./corso backup create '${{ inputs.service }}' \
@ -61,6 +64,9 @@ runs:
shell: bash shell: bash
working-directory: src working-directory: src
run: | run: |
echo "---------------------------"
echo Restore ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
set -euo pipefail set -euo pipefail
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-restore-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-restore-${{ inputs.service }}-${{inputs.kind }}.log
./corso restore '${{ inputs.service }}' \ ./corso restore '${{ inputs.service }}' \
@ -85,11 +91,14 @@ runs:
SANITY_TEST_KIND: restore SANITY_TEST_KIND: restore
SANITY_TEST_FOLDER: ${{ steps.restore.outputs.result }} SANITY_TEST_FOLDER: ${{ steps.restore.outputs.result }}
SANITY_TEST_SERVICE: ${{ inputs.service }} SANITY_TEST_SERVICE: ${{ inputs.service }}
TEST_DATA: ${{ inputs.test-folder }} SANITY_TEST_DATA: ${{ inputs.test-folder }}
BASE_BACKUP: ${{ inputs.base-backup }} SANITY_BASE_BACKUP: ${{ inputs.base-backup }}
run: | run: |
echo "---------------------------"
echo Sanity Test Restore ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-validate-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-validate-${{ inputs.service }}-${{inputs.kind }}.log
./sanity-test ./sanity-test restore ${{ inputs.service }}
- name: Export ${{ inputs.service }} ${{ inputs.kind }} - name: Export ${{ inputs.service }} ${{ inputs.kind }}
if: inputs.with-export == true if: inputs.with-export == true
@ -97,6 +106,9 @@ runs:
shell: bash shell: bash
working-directory: src working-directory: src
run: | run: |
echo "---------------------------"
echo Export ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
set -euo pipefail set -euo pipefail
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-restore-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-restore-${{ inputs.service }}-${{inputs.kind }}.log
./corso export '${{ inputs.service }}' \ ./corso export '${{ inputs.service }}' \
@ -116,11 +128,14 @@ runs:
SANITY_TEST_KIND: export SANITY_TEST_KIND: export
SANITY_TEST_FOLDER: /tmp/export-${{ inputs.service }}-${{inputs.kind }} SANITY_TEST_FOLDER: /tmp/export-${{ inputs.service }}-${{inputs.kind }}
SANITY_TEST_SERVICE: ${{ inputs.service }} SANITY_TEST_SERVICE: ${{ inputs.service }}
TEST_DATA: ${{ inputs.test-folder }} SANITY_TEST_DATA: ${{ inputs.test-folder }}
BASE_BACKUP: ${{ inputs.base-backup }} SANITY_BASE_BACKUP: ${{ inputs.base-backup }}
run: | run: |
echo "---------------------------"
echo Sanity-Test Export ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-validate-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-validate-${{ inputs.service }}-${{inputs.kind }}.log
./sanity-test ./sanity-test export ${{ inputs.service }}
- name: Export archive ${{ inputs.service }} ${{ inputs.kind }} - name: Export archive ${{ inputs.service }} ${{ inputs.kind }}
if: inputs.with-export == true if: inputs.with-export == true
@ -128,6 +143,9 @@ runs:
shell: bash shell: bash
working-directory: src working-directory: src
run: | run: |
echo "---------------------------"
echo Export Archive ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
set -euo pipefail set -euo pipefail
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-restore-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-restore-${{ inputs.service }}-${{inputs.kind }}.log
./corso export '${{ inputs.service }}' \ ./corso export '${{ inputs.service }}' \
@ -150,16 +168,22 @@ runs:
SANITY_TEST_KIND: export SANITY_TEST_KIND: export
SANITY_TEST_FOLDER: /tmp/export-${{ inputs.service }}-${{inputs.kind }}-unzipped SANITY_TEST_FOLDER: /tmp/export-${{ inputs.service }}-${{inputs.kind }}-unzipped
SANITY_TEST_SERVICE: ${{ inputs.service }} SANITY_TEST_SERVICE: ${{ inputs.service }}
TEST_DATA: ${{ inputs.test-folder }} SANITY_TEST_DATA: ${{ inputs.test-folder }}
BASE_BACKUP: ${{ inputs.base-backup }} SANITY_BASE_BACKUP: ${{ inputs.base-backup }}
run: | run: |
echo "---------------------------"
echo Sanity-Test Export Archive ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-validate-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-validate-${{ inputs.service }}-${{inputs.kind }}.log
./sanity-test ./sanity-test export ${{ inputs.service }}
- name: List ${{ inputs.service }} ${{ inputs.kind }} - name: List ${{ inputs.service }} ${{ inputs.kind }}
shell: bash shell: bash
working-directory: src working-directory: src
run: | run: |
echo "---------------------------"
echo Backup list ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
set -euo pipefail set -euo pipefail
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-backup-list-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-backup-list-${{ inputs.service }}-${{inputs.kind }}.log
./corso backup list ${{ inputs.service }} \ ./corso backup list ${{ inputs.service }} \
@ -178,6 +202,9 @@ runs:
shell: bash shell: bash
working-directory: src working-directory: src
run: | run: |
echo "---------------------------"
echo Backup List w/ Backup ${{ inputs.service }} ${{ inputs.kind }}
echo "---------------------------"
set -euo pipefail set -euo pipefail
CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-backup-list-single-${{ inputs.service }}-${{inputs.kind }}.log CORSO_LOG_FILE=${{ inputs.log-dir }}/gotest-backup-list-single-${{ inputs.service }}-${{inputs.kind }}.log
./corso backup list ${{ inputs.service }} \ ./corso backup list ${{ inputs.service }} \
@ -193,7 +220,13 @@ runs:
exit 1 exit 1
fi fi
# Upload the original go test output as an artifact for later review. - if: always()
shell: bash
run: |
echo "---------------------------"
echo Logging Results
echo "---------------------------"
- name: Upload test log - name: Upload test log
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@ -31,7 +31,7 @@ runs:
- name: use url or blank val - name: use url or blank val
shell: bash shell: bash
run: | run: |
echo "STEP=${{ github.action || '' }}" >> $GITHUB_ENV echo "STEP=${{ env.trimmed_ref || '' }}" >> $GITHUB_ENV
echo "JOB=${{ github.job || '' }}" >> $GITHUB_ENV echo "JOB=${{ github.job || '' }}" >> $GITHUB_ENV
echo "LOGS=${{ github.run_id && env.logurl || '-' }}" >> $GITHUB_ENV echo "LOGS=${{ github.run_id && env.logurl || '-' }}" >> $GITHUB_ENV
echo "COMMIT=${{ github.sha && env.commiturl || '-' }}" >> $GITHUB_ENV echo "COMMIT=${{ github.sha && env.commiturl || '-' }}" >> $GITHUB_ENV
@ -51,7 +51,7 @@ runs:
"type": "section", "type": "section",
"text": { "text": {
"type": "mrkdwn", "type": "mrkdwn",
"text": "${{ inputs.msg }} :: ${{ env.JOB }} - ${{ env.STEP }}\n${{ env.LOGS }} ${{ env.COMMIT }} ${{ env.REF }}" "text": "${{ inputs.msg }}\n${{ env.JOB }} :: ${{ env.STEP }}\n${{ env.LOGS }} ${{ env.COMMIT }} ${{ env.REF }}"
} }
} }
] ]

View File

@ -181,7 +181,7 @@ jobs:
uses: ./.github/actions/backup-restore-test uses: ./.github/actions/backup-restore-test
with: with:
service: exchange service: exchange
kind: initial kind: first-backup
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"' backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}' test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
@ -249,7 +249,7 @@ jobs:
uses: ./.github/actions/backup-restore-test uses: ./.github/actions/backup-restore-test
with: with:
service: onedrive service: onedrive
kind: initial kind: first-backup
backup-args: '--user "${{ env.TEST_USER }}"' backup-args: '--user "${{ env.TEST_USER }}"'
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}' restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}'
test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}' test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}'
@ -305,7 +305,7 @@ jobs:
uses: ./.github/actions/backup-restore-test uses: ./.github/actions/backup-restore-test
with: with:
service: sharepoint service: sharepoint
kind: initial kind: first-backup
backup-args: '--site "${{ secrets.CORSO_M365_TEST_SITE_URL }}"' backup-args: '--site "${{ secrets.CORSO_M365_TEST_SITE_URL }}"'
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}' restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}'
test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}' test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}'
@ -362,12 +362,34 @@ jobs:
uses: ./.github/actions/backup-restore-test uses: ./.github/actions/backup-restore-test
with: with:
service: groups service: groups
kind: initial kind: first-backup
backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}"' backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}"'
test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}' test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}'
log-dir: ${{ env.CORSO_LOG_DIR }} log-dir: ${{ env.CORSO_LOG_DIR }}
# TODO: incrementals # generate some more enteries for incremental check
# - name: Groups - Create new data (for incremental)
# working-directory: ./src/cmd/factory
# run: |
# go run . sharepoint files \
# --site ${{ secrets.CORSO_M365_TEST_GROUPS_SITE_URL }} \
# --user ${{ env.TEST_USER }} \
# --secondaryuser ${{ env.CORSO_SECONDARY_M365_TEST_USER_ID }} \
# --tenant ${{ secrets.TENANT_ID }} \
# --destination ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }} \
# --count 4
# - name: Groups - Incremental backup
# id: groups-incremental
# uses: ./.github/actions/backup-restore-test
# with:
# service: groups
# kind: incremental
# backup-args: '--site "${{ secrets.CORSO_M365_TEST_GROUPS_SITE_URL }}"'
# restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}'
# test-folder: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}'
# log-dir: ${{ env.CORSO_LOG_DIR }}
# with-export: true
########################################################################################################################################## ##########################################################################################################################################

View File

@ -1,6 +1,68 @@
package common package common
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/alcionai/corso/src/internal/tester/tconfig"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/credentials"
"github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/services/m365/api"
)
type PermissionInfo struct { type PermissionInfo struct {
EntityID string EntityID string
Roles []string Roles []string
} }
const (
sanityBaseBackup = "SANITY_BASE_BACKUP"
sanityTestData = "SANITY_TEST_DATA"
sanityTestFolder = "SANITY_TEST_FOLDER"
sanityTestService = "SANITY_TEST_SERVICE"
)
type Envs struct {
BaseBackupFolder string
DataFolder string
FolderName string
Service string
SiteID string
StartTime time.Time
UserID string
}
func EnvVars(ctx context.Context) Envs {
folder := strings.TrimSpace(os.Getenv(sanityTestFolder))
startTime, _ := MustGetTimeFromName(ctx, folder)
e := Envs{
BaseBackupFolder: os.Getenv(sanityBaseBackup),
DataFolder: os.Getenv(sanityTestData),
FolderName: folder,
SiteID: tconfig.GetM365SiteID(ctx),
Service: os.Getenv(sanityTestService),
StartTime: startTime,
UserID: tconfig.GetM365UserID(ctx),
}
fmt.Printf("\n-----\nenvs %+v\n-----\n", e)
logger.Ctx(ctx).Info("envs", e)
return e
}
func GetAC() (api.Client, error) {
creds := account.M365Config{
M365: credentials.GetM365(),
AzureTenantID: os.Getenv(account.AzureTenantID),
}
return api.NewClient(creds, control.DefaultOptions())
}

View File

@ -0,0 +1,38 @@
package common
import (
"os"
"path/filepath"
"time"
"github.com/alcionai/clues"
)
func FilepathWalker(
folderName string,
exportFileSizes map[string]int64,
startTime time.Time,
) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return clues.Stack(err)
}
if info.IsDir() {
return nil
}
relPath, err := filepath.Rel(folderName, path)
if err != nil {
return clues.Stack(err)
}
exportFileSizes[relPath] = info.Size()
if startTime.After(info.ModTime()) {
startTime = info.ModTime()
}
return nil
}
}

View File

@ -0,0 +1,69 @@
package common
import (
"context"
"golang.org/x/exp/maps"
)
// Sanitree is used to build out a hierarchical tree of items
// for comparison against each other. Primarily so that a restore
// can compare two subtrees easily.
type Sanitree[T any] struct {
Container T
ContainerID string
ContainerName string
// non-containers only
ContainsItems int
// name -> node
Children map[string]*Sanitree[T]
}
func AssertEqualTrees[T any](
ctx context.Context,
expect, other *Sanitree[T],
) {
if expect == nil && other == nil {
return
}
Assert(
ctx,
func() bool { return expect != nil && other != nil },
"non nil nodes",
expect,
other)
Assert(
ctx,
func() bool { return expect.ContainerName == other.ContainerName },
"container names match",
expect.ContainerName,
other.ContainerName)
Assert(
ctx,
func() bool { return expect.ContainsItems == other.ContainsItems },
"count of items in container matches",
expect.ContainsItems,
other.ContainsItems)
Assert(
ctx,
func() bool { return len(expect.Children) == len(other.Children) },
"count of child containers matches",
len(expect.Children),
len(other.Children))
for name, s := range expect.Children {
ch, ok := other.Children[name]
Assert(
ctx,
func() bool { return ok },
"found matching child container",
name,
maps.Keys(other.Children))
AssertEqualTrees(ctx, s, ch)
}
}

View File

@ -22,7 +22,7 @@ func Assert(
return return
} }
header = "Error: " + header header = "TEST FAILURE: " + header
expected := fmt.Sprintf("* Expected: %+v", expect) expected := fmt.Sprintf("* Expected: %+v", expect)
got := fmt.Sprintf("* Current: %+v", current) got := fmt.Sprintf("* Current: %+v", current)
@ -37,7 +37,7 @@ func Assert(
func Fatal(ctx context.Context, msg string, err error) { func Fatal(ctx context.Context, msg string, err error) {
logger.CtxErr(ctx, err).Error("test failure: " + msg) logger.CtxErr(ctx, err).Error("test failure: " + msg)
fmt.Println(msg+": ", err) fmt.Println("TEST FAILURE: "+msg+": ", err)
os.Exit(1) os.Exit(1)
} }

View File

@ -0,0 +1,16 @@
package export
import (
"context"
"github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/pkg/services/m365/api"
)
func CheckGroupsExport(
ctx context.Context,
ac api.Client,
envs common.Envs,
) {
// TODO
}

View File

@ -3,28 +3,21 @@ package export
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"time" "time"
"github.com/alcionai/clues"
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
"github.com/alcionai/corso/src/cmd/sanity_test/common" "github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/cmd/sanity_test/restore" "github.com/alcionai/corso/src/cmd/sanity_test/restore"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
func CheckOneDriveExport( func CheckOneDriveExport(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
userID, folderName, dataFolder string, envs common.Envs,
) { ) {
drive, err := client. drive, err := ac.Users().GetDefaultDrive(ctx, envs.UserID)
Users().
ByUserId(userID).
Drive().
Get(ctx, nil)
if err != nil { if err != nil {
common.Fatal(ctx, "getting the drive:", err) common.Fatal(ctx, "getting the drive:", err)
} }
@ -36,37 +29,19 @@ func CheckOneDriveExport(
startTime = time.Now() startTime = time.Now()
) )
err = filepath.Walk(folderName, func(path string, info os.FileInfo, err error) error { err = filepath.Walk(
if err != nil { envs.FolderName,
return clues.Stack(err) common.FilepathWalker(envs.FolderName, exportFileSizes, startTime))
}
if info.IsDir() {
return nil
}
relPath, err := filepath.Rel(folderName, path)
if err != nil {
return clues.Stack(err)
}
exportFileSizes[relPath] = info.Size()
if startTime.After(info.ModTime()) {
startTime = info.ModTime()
}
return nil
})
if err != nil { if err != nil {
fmt.Println("Error walking the path:", err) fmt.Println("Error walking the path:", err)
} }
_ = restore.PopulateDriveDetails( _ = restore.PopulateDriveDetails(
ctx, ctx,
client, ac,
ptr.Val(drive.GetId()), ptr.Val(drive.GetId()),
folderName, envs.FolderName,
dataFolder, envs.DataFolder,
fileSizes, fileSizes,
map[string][]common.PermissionInfo{}, map[string][]common.PermissionInfo{},
startTime) startTime)

View File

@ -3,28 +3,21 @@ package export
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"time" "time"
"github.com/alcionai/clues"
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
"github.com/alcionai/corso/src/cmd/sanity_test/common" "github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/cmd/sanity_test/restore" "github.com/alcionai/corso/src/cmd/sanity_test/restore"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
func CheckSharePointExport( func CheckSharePointExport(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
siteID, folderName, dataFolder string, envs common.Envs,
) { ) {
drive, err := client. drive, err := ac.Sites().GetDefaultDrive(ctx, envs.SiteID)
Sites().
BySiteId(siteID).
Drive().
Get(ctx, nil)
if err != nil { if err != nil {
common.Fatal(ctx, "getting the drive:", err) common.Fatal(ctx, "getting the drive:", err)
} }
@ -36,37 +29,19 @@ func CheckSharePointExport(
startTime = time.Now() startTime = time.Now()
) )
err = filepath.Walk(folderName, func(path string, info os.FileInfo, err error) error { err = filepath.Walk(
if err != nil { envs.FolderName,
return clues.Stack(err) common.FilepathWalker(envs.FolderName, exportFileSizes, startTime))
}
if info.IsDir() {
return nil
}
relPath, err := filepath.Rel(folderName, path)
if err != nil {
return clues.Stack(err)
}
exportFileSizes[relPath] = info.Size()
if startTime.After(info.ModTime()) {
startTime = info.ModTime()
}
return nil
})
if err != nil { if err != nil {
fmt.Println("Error walking the path:", err) fmt.Println("Error walking the path:", err)
} }
_ = restore.PopulateDriveDetails( _ = restore.PopulateDriveDetails(
ctx, ctx,
client, ac,
ptr.Val(drive.GetId()), ptr.Val(drive.GetId()),
folderName, envs.FolderName,
dataFolder, envs.DataFolder,
fileSizes, fileSizes,
map[string][]common.PermissionInfo{}, map[string][]common.PermissionInfo{},
startTime) startTime)

View File

@ -3,99 +3,43 @@ package restore
import ( import (
"context" "context"
"fmt" "fmt"
stdpath "path"
"strings"
"time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/alcionai/corso/src/cmd/sanity_test/common" "github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/pkg/filters" "github.com/alcionai/corso/src/pkg/services/m365/api"
) )
// CheckEmailRestoration verifies that the emails count in restored folder is equivalent to // CheckEmailRestoration verifies that the emails count in restored folder is equivalent to
// emails in actual m365 account // emails in actual m365 account
func CheckEmailRestoration( func CheckEmailRestoration(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
testUser, folderName, dataFolder, baseBackupFolder string, envs common.Envs,
startTime time.Time,
) { ) {
var ( var (
restoreFolder models.MailFolderable folderNameToItemCount = make(map[string]int32)
itemCount = make(map[string]int32) folderNameToRestoreItemCount = make(map[string]int32)
restoreItemCount = make(map[string]int32)
builder = client.Users().ByUserId(testUser).MailFolders()
) )
for { restoredTree := buildSanitree(ctx, ac, envs.UserID, envs.FolderName)
result, err := builder.Get(ctx, nil) dataTree := buildSanitree(ctx, ac, envs.UserID, envs.DataFolder)
if err != nil {
common.Fatal(ctx, "getting mail folders", err)
}
values := result.GetValue()
for _, v := range values {
itemName := ptr.Val(v.GetDisplayName())
if itemName == folderName {
restoreFolder = v
continue
}
if itemName == dataFolder || itemName == baseBackupFolder {
// otherwise, recursively aggregate all child folders.
getAllMailSubFolders(ctx, client, testUser, v, itemName, dataFolder, itemCount)
itemCount[itemName] = ptr.Val(v.GetTotalItemCount())
}
}
link, ok := ptr.ValOK(result.GetOdataNextLink())
if !ok {
break
}
builder = users.NewItemMailFoldersRequestBuilder(link, client.GetAdapter())
}
folderID := ptr.Val(restoreFolder.GetId())
folderName = ptr.Val(restoreFolder.GetDisplayName())
ctx = clues.Add( ctx = clues.Add(
ctx, ctx,
"restore_folder_id", folderID, "restore_folder_id", restoredTree.ContainerID,
"restore_folder_name", folderName) "restore_folder_name", restoredTree.ContainerName,
"original_folder_id", dataTree.ContainerID,
"original_folder_name", dataTree.ContainerName)
childFolder, err := client. verifyEmailData(ctx, folderNameToRestoreItemCount, folderNameToItemCount)
Users().
ByUserId(testUser).
MailFolders().
ByMailFolderId(folderID).
ChildFolders().
Get(ctx, nil)
if err != nil {
common.Fatal(ctx, "getting restore folder child folders", err)
}
for _, fld := range childFolder.GetValue() { common.AssertEqualTrees[models.MailFolderable](
restoreDisplayName := ptr.Val(fld.GetDisplayName()) ctx,
dataTree,
// check if folder is the data folder we loaded or the base backup to verify restoredTree.Children[envs.DataFolder])
// the incremental backup worked fine
if strings.EqualFold(restoreDisplayName, dataFolder) || strings.EqualFold(restoreDisplayName, baseBackupFolder) {
count, _ := ptr.ValOK(fld.GetTotalItemCount())
restoreItemCount[restoreDisplayName] = count
checkAllSubFolder(ctx, client, fld, testUser, restoreDisplayName, dataFolder, restoreItemCount)
}
}
verifyEmailData(ctx, restoreItemCount, itemCount)
} }
func verifyEmailData(ctx context.Context, restoreMessageCount, messageCount map[string]int32) { func verifyEmailData(ctx context.Context, restoreMessageCount, messageCount map[string]int32) {
@ -111,109 +55,71 @@ func verifyEmailData(ctx context.Context, restoreMessageCount, messageCount map[
} }
} }
// getAllSubFolder will recursively check for all subfolders and get the corresponding func buildSanitree(
// email count.
func getAllMailSubFolders(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
testUser string, userID, folderName string,
r models.MailFolderable, ) *common.Sanitree[models.MailFolderable] {
parentFolder, gcc, err := ac.Mail().GetContainerByName(
dataFolder string, ctx,
messageCount map[string]int32, userID,
) { api.MsgFolderRoot,
var ( folderName)
folderID = ptr.Val(r.GetId())
count int32 = 99
options = &users.ItemMailFoldersItemChildFoldersRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemMailFoldersItemChildFoldersRequestBuilderGetQueryParameters{
Top: &count,
},
}
)
ctx = clues.Add(ctx, "parent_folder_id", folderID)
childFolder, err := client.
Users().
ByUserId(testUser).
MailFolders().
ByMailFolderId(folderID).
ChildFolders().
Get(ctx, options)
if err != nil { if err != nil {
common.Fatal(ctx, "getting mail subfolders", err) common.Fatal(
ctx,
fmt.Sprintf("finding folder by name %q", folderName),
err)
} }
for _, child := range childFolder.GetValue() { mmf, ok := gcc.(models.MailFolderable)
var ( if !ok {
childDisplayName = ptr.Val(child.GetDisplayName()) common.Fatal(
childFolderCount = ptr.Val(child.GetChildFolderCount()) ctx,
//nolint:forbidigo "mail folderable required",
fullFolderName = stdpath.Join(parentFolder, childDisplayName) clues.New("casting "+*gcc.GetDisplayName()+" to models.MailFolderable"))
)
if filters.PathContains([]string{dataFolder}).Compare(fullFolderName) {
messageCount[fullFolderName] = ptr.Val(child.GetTotalItemCount())
// recursively check for subfolders
if childFolderCount > 0 {
parentFolder := fullFolderName
getAllMailSubFolders(ctx, client, testUser, child, parentFolder, dataFolder, messageCount)
} }
}
} root := &common.Sanitree[models.MailFolderable]{
} Container: mmf,
ContainerID: ptr.Val(mmf.GetId()),
// checkAllSubFolder will recursively traverse inside the restore folder and ContainerName: ptr.Val(mmf.GetDisplayName()),
// verify that data matched in all subfolders ContainsItems: int(ptr.Val(mmf.GetTotalItemCount())),
func checkAllSubFolder( Children: map[string]*common.Sanitree[models.MailFolderable]{},
ctx context.Context, }
client *msgraphsdk.GraphServiceClient,
r models.MailFolderable, recurseSubfolders(ctx, ac, root, userID)
testUser,
parentFolder, return root
dataFolder string, }
restoreMessageCount map[string]int32,
) { func recurseSubfolders(
var ( ctx context.Context,
folderID = ptr.Val(r.GetId()) ac api.Client,
count int32 = 99 parent *common.Sanitree[models.MailFolderable],
options = &users.ItemMailFoldersItemChildFoldersRequestBuilderGetRequestConfiguration{ userID string,
QueryParameters: &users.ItemMailFoldersItemChildFoldersRequestBuilderGetQueryParameters{ ) {
Top: &count, childFolders, err := ac.Mail().GetContainerChildren(
}, ctx,
} userID,
) parent.ContainerID)
if err != nil {
childFolder, err := client. common.Fatal(ctx, "getting subfolders", err)
Users(). }
ByUserId(testUser).
MailFolders(). for _, child := range childFolders {
ByMailFolderId(folderID). c := &common.Sanitree[models.MailFolderable]{
ChildFolders(). Container: child,
Get(ctx, options) ContainerID: ptr.Val(child.GetId()),
if err != nil { ContainerName: ptr.Val(child.GetDisplayName()),
common.Fatal(ctx, "getting mail subfolders", err) ContainsItems: int(ptr.Val(child.GetTotalItemCount())),
} Children: map[string]*common.Sanitree[models.MailFolderable]{},
}
for _, child := range childFolder.GetValue() {
var ( parent.Children[c.ContainerName] = c
childDisplayName = ptr.Val(child.GetDisplayName())
//nolint:forbidigo if ptr.Val(child.GetChildFolderCount()) > 0 {
fullFolderName = stdpath.Join(parentFolder, childDisplayName) recurseSubfolders(ctx, ac, c, userID)
)
if filters.PathContains([]string{dataFolder}).Compare(fullFolderName) {
childTotalCount, _ := ptr.ValOK(child.GetTotalItemCount())
restoreMessageCount[fullFolderName] = childTotalCount
}
childFolderCount := ptr.Val(child.GetChildFolderCount())
if childFolderCount > 0 {
parentFolder := fullFolderName
checkAllSubFolder(ctx, client, child, testUser, parentFolder, dataFolder, restoreMessageCount)
} }
} }
} }

View File

@ -0,0 +1,16 @@
package restore
import (
"context"
"github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/pkg/services/m365/api"
)
func CheckGroupsRestoration(
ctx context.Context,
ac api.Client,
envs common.Envs,
) {
// TODO
}

View File

@ -7,12 +7,12 @@ import (
"time" "time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"github.com/alcionai/corso/src/cmd/sanity_test/common" "github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
const ( const (
@ -21,34 +21,29 @@ const (
func CheckOneDriveRestoration( func CheckOneDriveRestoration(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
userID, folderName, dataFolder string, envs common.Envs,
startTime time.Time,
) { ) {
drive, err := client. drive, err := ac.Users().GetDefaultDrive(ctx, envs.UserID)
Users().
ByUserId(userID).
Drive().
Get(ctx, nil)
if err != nil { if err != nil {
common.Fatal(ctx, "getting the drive:", err) common.Fatal(ctx, "getting the drive:", err)
} }
checkDriveRestoration( checkDriveRestoration(
ctx, ctx,
client, ac,
path.OneDriveService, path.OneDriveService,
folderName, envs.FolderName,
ptr.Val(drive.GetId()), ptr.Val(drive.GetId()),
ptr.Val(drive.GetName()), ptr.Val(drive.GetName()),
dataFolder, envs.DataFolder,
startTime, envs.StartTime,
false) false)
} }
func checkDriveRestoration( func checkDriveRestoration(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
service path.ServiceType, service path.ServiceType,
folderName, folderName,
driveID, driveID,
@ -70,7 +65,7 @@ func checkDriveRestoration(
restoreFolderID := PopulateDriveDetails( restoreFolderID := PopulateDriveDetails(
ctx, ctx,
client, ac,
driveID, driveID,
folderName, folderName,
dataFolder, dataFolder,
@ -78,7 +73,14 @@ func checkDriveRestoration(
folderPermissions, folderPermissions,
startTime) startTime)
getRestoredDrive(ctx, client, driveID, restoreFolderID, restoreFile, restoredFolderPermissions, startTime) getRestoredDrive(
ctx,
ac,
driveID,
restoreFolderID,
restoreFile,
restoredFolderPermissions,
startTime)
checkRestoredDriveItemPermissions( checkRestoredDriveItemPermissions(
ctx, ctx,
@ -105,7 +107,7 @@ func checkDriveRestoration(
func PopulateDriveDetails( func PopulateDriveDetails(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
driveID, folderName, dataFolder string, driveID, folderName, dataFolder string,
fileSizes map[string]int64, fileSizes map[string]int64,
folderPermissions map[string][]common.PermissionInfo, folderPermissions map[string][]common.PermissionInfo,
@ -113,18 +115,12 @@ func PopulateDriveDetails(
) string { ) string {
var restoreFolderID string var restoreFolderID string
response, err := client. children, err := ac.Drives().GetFolderChildren(ctx, driveID, "root")
Drives().
ByDriveId(driveID).
Items().
ByDriveItemId("root").
Children().
Get(ctx, nil)
if err != nil { if err != nil {
common.Fatal(ctx, "getting drive by id", err) common.Fatal(ctx, "getting drive by id", err)
} }
for _, driveItem := range response.GetValue() { for _, driveItem := range children {
var ( var (
itemID = ptr.Val(driveItem.GetId()) itemID = ptr.Val(driveItem.GetId())
itemName = ptr.Val(driveItem.GetName()) itemName = ptr.Val(driveItem.GetName())
@ -156,8 +152,17 @@ func PopulateDriveDetails(
continue continue
} }
folderPermissions[itemName] = permissionIn(ctx, client, driveID, itemID) folderPermissions[itemName] = permissionIn(ctx, ac, driveID, itemID)
getOneDriveChildFolder(ctx, client, driveID, itemID, itemName, fileSizes, folderPermissions, startTime)
getOneDriveChildFolder(
ctx,
ac,
driveID,
itemID,
itemName,
fileSizes,
folderPermissions,
startTime)
} }
return restoreFolderID return restoreFolderID
@ -228,18 +233,18 @@ func checkRestoredDriveItemPermissions(
func getOneDriveChildFolder( func getOneDriveChildFolder(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
driveID, itemID, parentName string, driveID, itemID, parentName string,
fileSizes map[string]int64, fileSizes map[string]int64,
folderPermission map[string][]common.PermissionInfo, folderPermission map[string][]common.PermissionInfo,
startTime time.Time, startTime time.Time,
) { ) {
response, err := client.Drives().ByDriveId(driveID).Items().ByDriveItemId(itemID).Children().Get(ctx, nil) children, err := ac.Drives().GetFolderChildren(ctx, driveID, itemID)
if err != nil { if err != nil {
common.Fatal(ctx, "getting child folder", err) common.Fatal(ctx, "getting child folder", err)
} }
for _, driveItem := range response.GetValue() { for _, driveItem := range children {
var ( var (
itemID = ptr.Val(driveItem.GetId()) itemID = ptr.Val(driveItem.GetId())
itemName = ptr.Val(driveItem.GetName()) itemName = ptr.Val(driveItem.GetName())
@ -268,31 +273,33 @@ func getOneDriveChildFolder(
continue continue
} }
folderPermission[fullName] = permissionIn(ctx, client, driveID, itemID) folderPermission[fullName] = permissionIn(ctx, ac, driveID, itemID)
getOneDriveChildFolder(ctx, client, driveID, itemID, fullName, fileSizes, folderPermission, startTime) getOneDriveChildFolder(
ctx,
ac,
driveID,
itemID,
fullName,
fileSizes,
folderPermission,
startTime)
} }
} }
func getRestoredDrive( func getRestoredDrive(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
driveID, restoreFolderID string, driveID, restoreFolderID string,
restoreFile map[string]int64, restoreFile map[string]int64,
restoreFolder map[string][]common.PermissionInfo, restoreFolder map[string][]common.PermissionInfo,
startTime time.Time, startTime time.Time,
) { ) {
restored, err := client. children, err := ac.Drives().GetFolderChildren(ctx, driveID, restoreFolderID)
Drives().
ByDriveId(driveID).
Items().
ByDriveItemId(restoreFolderID).
Children().
Get(ctx, nil)
if err != nil { if err != nil {
common.Fatal(ctx, "getting child folder", err) common.Fatal(ctx, "getting child folder", err)
} }
for _, item := range restored.GetValue() { for _, item := range children {
var ( var (
itemID = ptr.Val(item.GetId()) itemID = ptr.Val(item.GetId())
itemName = ptr.Val(item.GetName()) itemName = ptr.Val(item.GetName())
@ -308,8 +315,16 @@ func getRestoredDrive(
continue continue
} }
restoreFolder[itemName] = permissionIn(ctx, client, driveID, itemID) restoreFolder[itemName] = permissionIn(ctx, ac, driveID, itemID)
getOneDriveChildFolder(ctx, client, driveID, itemID, itemName, restoreFile, restoreFolder, startTime) getOneDriveChildFolder(
ctx,
ac,
driveID,
itemID,
itemName,
restoreFile,
restoreFolder,
startTime)
} }
} }
@ -319,18 +334,12 @@ func getRestoredDrive(
func permissionIn( func permissionIn(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
driveID, itemID string, driveID, itemID string,
) []common.PermissionInfo { ) []common.PermissionInfo {
pi := []common.PermissionInfo{} pi := []common.PermissionInfo{}
pcr, err := client. pcr, err := ac.Drives().GetItemPermission(ctx, driveID, itemID)
Drives().
ByDriveId(driveID).
Items().
ByDriveItemId(itemID).
Permissions().
Get(ctx, nil)
if err != nil { if err != nil {
common.Fatal(ctx, "getting permission", err) common.Fatal(ctx, "getting permission", err)
} }

View File

@ -2,38 +2,31 @@ package restore
import ( import (
"context" "context"
"time"
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
"github.com/alcionai/corso/src/cmd/sanity_test/common" "github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/services/m365/api"
) )
func CheckSharePointRestoration( func CheckSharePointRestoration(
ctx context.Context, ctx context.Context,
client *msgraphsdk.GraphServiceClient, ac api.Client,
siteID, userID, folderName, dataFolder string, envs common.Envs,
startTime time.Time,
) { ) {
drive, err := client. drive, err := ac.Sites().GetDefaultDrive(ctx, envs.SiteID)
Sites().
BySiteId(siteID).
Drive().
Get(ctx, nil)
if err != nil { if err != nil {
common.Fatal(ctx, "getting the drive:", err) common.Fatal(ctx, "getting the drive:", err)
} }
checkDriveRestoration( checkDriveRestoration(
ctx, ctx,
client, ac,
path.SharePointService, path.SharePointService,
folderName, envs.FolderName,
ptr.Val(drive.GetId()), ptr.Val(drive.GetId()),
ptr.Val(drive.GetName()), ptr.Val(drive.GetName()),
dataFolder, envs.DataFolder,
startTime, envs.StartTime,
true) true)
} }

View File

@ -2,21 +2,40 @@ package main
import ( import (
"context" "context"
"fmt"
"os" "os"
"strings"
"time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" "github.com/spf13/cobra"
"github.com/alcionai/corso/src/cli/print"
"github.com/alcionai/corso/src/cmd/sanity_test/common" "github.com/alcionai/corso/src/cmd/sanity_test/common"
"github.com/alcionai/corso/src/cmd/sanity_test/export" "github.com/alcionai/corso/src/cmd/sanity_test/export"
"github.com/alcionai/corso/src/cmd/sanity_test/restore" "github.com/alcionai/corso/src/cmd/sanity_test/restore"
"github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/internal/tester/tconfig"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
) )
// ---------------------------------------------------------------------------
// root command
// ---------------------------------------------------------------------------
func rootCMD() *cobra.Command {
return &cobra.Command{
Use: "sanity-test",
Short: "run the sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestRoot,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("running", cmd.UseLine())
},
}
}
func sanityTestRoot(cmd *cobra.Command, args []string) error {
return print.Only(cmd.Context(), clues.New("must specify a kind of test"))
}
func main() { func main() {
ls := logger.Settings{ ls := logger.Settings{
File: logger.GetLogFile(""), File: logger.GetLogFile(""),
@ -29,60 +48,226 @@ func main() {
_ = log.Sync() // flush all logs in the buffer _ = log.Sync() // flush all logs in the buffer
}() }()
// TODO: only needed for exchange
graph.InitializeConcurrencyLimiter(ctx, true, 4) graph.InitializeConcurrencyLimiter(ctx, true, 4)
adapter, err := graph.CreateAdapter( root := rootCMD()
tconfig.GetM365TenantID(ctx),
os.Getenv("AZURE_CLIENT_ID"),
os.Getenv("AZURE_CLIENT_SECRET"))
if err != nil {
common.Fatal(ctx, "creating adapter", err)
}
var ( restCMD := restoreCMD()
client = msgraphsdk.NewGraphServiceClient(adapter)
testUser = tconfig.GetM365UserID(ctx)
testSite = tconfig.GetM365SiteID(ctx)
testKind = os.Getenv("SANITY_TEST_KIND") // restore or export (cli arg?)
testService = os.Getenv("SANITY_TEST_SERVICE")
folder = strings.TrimSpace(os.Getenv("SANITY_TEST_FOLDER"))
dataFolder = os.Getenv("TEST_DATA")
baseBackupFolder = os.Getenv("BASE_BACKUP")
)
ctx = clues.Add( restCMD.AddCommand(restoreExchangeCMD())
ctx, restCMD.AddCommand(restoreOneDriveCMD())
"resource_owner", testUser, restCMD.AddCommand(restoreSharePointCMD())
"service", testService, restCMD.AddCommand(restoreGroupsCMD())
"sanity_restore_folder", folder) root.AddCommand(restCMD)
logger.Ctx(ctx).Info("starting sanity test check") expCMD := exportCMD()
switch testKind { expCMD.AddCommand(exportOneDriveCMD())
case "restore": expCMD.AddCommand(exportSharePointCMD())
startTime, _ := common.MustGetTimeFromName(ctx, folder) expCMD.AddCommand(exportGroupsCMD())
clues.Add(ctx, "sanity_restore_start_time", startTime.Format(time.RFC3339)) root.AddCommand(expCMD)
switch testService { if err := root.Execute(); err != nil {
case "exchange": os.Exit(1)
restore.CheckEmailRestoration(ctx, client, testUser, folder, dataFolder, baseBackupFolder, startTime)
case "onedrive":
restore.CheckOneDriveRestoration(ctx, client, testUser, folder, dataFolder, startTime)
case "sharepoint":
restore.CheckSharePointRestoration(ctx, client, testSite, testUser, folder, dataFolder, startTime)
default:
common.Fatal(ctx, "unknown service for restore sanity tests", nil)
}
case "export":
switch testService {
case "onedrive":
export.CheckOneDriveExport(ctx, client, testUser, folder, dataFolder)
case "sharepoint":
export.CheckSharePointExport(ctx, client, testSite, folder, dataFolder)
default:
common.Fatal(ctx, "unknown service for export sanity tests", nil)
}
default:
common.Fatal(ctx, "unknown test kind (expected restore or export)", nil)
} }
} }
// ---------------------------------------------------------------------------
// restore/export command
// ---------------------------------------------------------------------------
func exportCMD() *cobra.Command {
return &cobra.Command{
Use: "restore",
Short: "run the post-export sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestExport,
}
}
func sanityTestExport(cmd *cobra.Command, args []string) error {
return print.Only(cmd.Context(), clues.New("must specify a service"))
}
func restoreCMD() *cobra.Command {
return &cobra.Command{
Use: "restore",
Short: "run the post-restore sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestRestore,
}
}
func sanityTestRestore(cmd *cobra.Command, args []string) error {
return print.Only(cmd.Context(), clues.New("must specify a service"))
}
// ---------------------------------------------------------------------------
// service commands - export
// ---------------------------------------------------------------------------
func exportGroupsCMD() *cobra.Command {
return &cobra.Command{
Use: "groups",
Short: "run the groups export sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestExportGroups,
}
}
func sanityTestExportGroups(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
envs := common.EnvVars(ctx)
ac, err := common.GetAC()
if err != nil {
return print.Only(ctx, err)
}
export.CheckGroupsExport(ctx, ac, envs)
return nil
}
func exportOneDriveCMD() *cobra.Command {
return &cobra.Command{
Use: "onedrive",
Short: "run the onedrive export sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestExportOneDrive,
}
}
func sanityTestExportOneDrive(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
envs := common.EnvVars(ctx)
ac, err := common.GetAC()
if err != nil {
return print.Only(ctx, err)
}
export.CheckOneDriveExport(ctx, ac, envs)
return nil
}
func exportSharePointCMD() *cobra.Command {
return &cobra.Command{
Use: "sharepoint",
Short: "run the sharepoint export sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestExportSharePoint,
}
}
func sanityTestExportSharePoint(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
envs := common.EnvVars(ctx)
ac, err := common.GetAC()
if err != nil {
return print.Only(ctx, err)
}
export.CheckSharePointExport(ctx, ac, envs)
return nil
}
// ---------------------------------------------------------------------------
// service commands - restore
// ---------------------------------------------------------------------------
func restoreExchangeCMD() *cobra.Command {
return &cobra.Command{
Use: "exchange",
Short: "run the exchange restore sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestRestoreExchange,
}
}
func sanityTestRestoreExchange(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
envs := common.EnvVars(ctx)
ac, err := common.GetAC()
if err != nil {
return print.Only(ctx, err)
}
restore.CheckEmailRestoration(ctx, ac, envs)
return nil
}
func restoreOneDriveCMD() *cobra.Command {
return &cobra.Command{
Use: "onedrive",
Short: "run the onedrive restore sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestRestoreOneDrive,
}
}
func sanityTestRestoreOneDrive(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
envs := common.EnvVars(ctx)
ac, err := common.GetAC()
if err != nil {
return print.Only(ctx, err)
}
restore.CheckOneDriveRestoration(ctx, ac, envs)
return nil
}
func restoreSharePointCMD() *cobra.Command {
return &cobra.Command{
Use: "sharepoint",
Short: "run the sharepoint restore sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestRestoreSharePoint,
}
}
func sanityTestRestoreSharePoint(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
envs := common.EnvVars(ctx)
ac, err := common.GetAC()
if err != nil {
return print.Only(ctx, err)
}
restore.CheckSharePointRestoration(ctx, ac, envs)
return nil
}
func restoreGroupsCMD() *cobra.Command {
return &cobra.Command{
Use: "groups",
Short: "run the groups restore sanity tests",
DisableAutoGenTag: true,
RunE: sanityTestRestoreGroups,
}
}
func sanityTestRestoreGroups(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
envs := common.EnvVars(ctx)
ac, err := common.GetAC()
if err != nil {
return print.Only(ctx, err)
}
restore.CheckGroupsRestoration(ctx, ac, envs)
return nil
}

View File

@ -24,7 +24,7 @@ import (
type Client struct { type Client struct {
Credentials account.M365Config Credentials account.M365Config
// The Stable service is re-usable for any non-paged request. // The Stable service is re-usable for any request.
// This allows us to maintain performance across async requests. // This allows us to maintain performance across async requests.
Stable graph.Servicer Stable graph.Servicer

View File

@ -84,6 +84,26 @@ func (c Drives) GetRootFolder(
return root, nil return root, nil
} }
// TODO: pagination controller needed for completion.
func (c Drives) GetFolderChildren(
ctx context.Context,
driveID, folderID string,
) ([]models.DriveItemable, error) {
response, err := c.Stable.
Client().
Drives().
ByDriveId(driveID).
Items().
ByDriveItemId(folderID).
Children().
Get(ctx, nil)
if err != nil {
return nil, graph.Wrap(ctx, err, "getting folder children")
}
return response.GetValue(), nil
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Items // Items
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -223,6 +223,26 @@ func (c Mail) PatchFolder(
return nil return nil
} }
// TODO: needs pager implementation for completion
func (c Mail) GetContainerChildren(
ctx context.Context,
userID, containerID string,
) ([]models.MailFolderable, error) {
resp, err := c.Stable.
Client().
Users().
ByUserId(userID).
MailFolders().
ByMailFolderId(containerID).
ChildFolders().
Get(ctx, nil)
if err != nil {
return nil, graph.Wrap(ctx, err, "getting container child folders")
}
return resp.GetValue(), nil
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// items // items
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------