diff --git a/.github/workflows/sanity-test.yaml b/.github/workflows/sanity-test.yaml index 70b0b7a1bc..a96682f5f0 100644 --- a/.github/workflows/sanity-test.yaml +++ b/.github/workflows/sanity-test.yaml @@ -3,6 +3,7 @@ on: push: branches: - main + - 5063-chats-backup-v0-integration-tests workflow_dispatch: inputs: user: @@ -423,6 +424,41 @@ jobs: ########################################################################################################################################## +# Teams Chats + + # generate a folder name + - name: TeamsChats - Create export foldername + id: export-folder-teamschats + run: | + suffix=$(date +"%Y-%m-%d_%H-%M-%S") + echo name="${{ env.RESTORE_DEST_PFX }}$suffix" >> $GITHUB_OUTPUT + + - name: TeamsChats - Backup + id: teamschats-backup + timeout-minutes: 30 + uses: ./.github/actions/backup-restore-test + with: + service: teamschats + kind: first-backup + backup-args: '--user "${{ vars.CORSO_M365_TEST_USER_ID }}"' + restore-container: '${{ jobs.export-folder-teamschats.outputs.name }}' + log-dir: ${{ env.CORSO_LOG_DIR }} + with-export: true + + - name: TeamsChats - Incremental backup + id: teamschats-incremental + timeout-minutes: 30 + uses: ./.github/actions/backup-restore-test + with: + service: teamschats + kind: incremental + backup-args: '--group "${{ vars.CORSO_M365_TEST_USER_ID }}"' + restore-container: '${{ jobs.export-folder-teamschats.outputs.name }}' + log-dir: ${{ env.CORSO_LOG_DIR }} + with-export: true + +########################################################################################################################################## + # Logging & Notifications # Upload the original go test output as an artifact for later review. diff --git a/.gitignore b/.gitignore index bdda0724b1..389834af67 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ *.test test_results/ testlog/ +sanity.test # Output of the go coverage tool, specifically when used with LiteIDE *.out diff --git a/src/cmd/sanity_test/common/filepath.go b/src/cmd/sanity_test/common/filepath.go index 1b2c01a861..970b90e4d6 100644 --- a/src/cmd/sanity_test/common/filepath.go +++ b/src/cmd/sanity_test/common/filepath.go @@ -2,6 +2,7 @@ package common import ( "context" + "fmt" "io/fs" "os" "path/filepath" @@ -21,7 +22,7 @@ func BuildFilepathSanitree( err error, ) error { if err != nil { - Fatal(ctx, "error passed to filepath walker", err) + Fatal(ctx, fmt.Sprintf("error passed to filepath walker for dir %q", p), err) } relPath, err := filepath.Rel(rootDir, p) diff --git a/src/cmd/sanity_test/export/teamschats.go b/src/cmd/sanity_test/export/teamschats.go new file mode 100644 index 0000000000..02513a1cb7 --- /dev/null +++ b/src/cmd/sanity_test/export/teamschats.go @@ -0,0 +1,93 @@ +package export + +import ( + "context" + "io/fs" + "strings" + + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/alcionai/corso/src/cmd/sanity_test/common" + "github.com/alcionai/corso/src/internal/common/ptr" + "github.com/alcionai/corso/src/pkg/path" + "github.com/alcionai/corso/src/pkg/services/m365/api" +) + +func CheckTeamsChatsExport( + ctx context.Context, + ac api.Client, + envs common.Envs, +) { + sourceTree := populateChatsSanitree( + ctx, + ac, + envs.UserID) + + fpTree := common.BuildFilepathSanitree(ctx, envs.RestoreContainer) + + comparator := func( + ctx context.Context, + expect *common.Sanitree[models.Chatable, models.Chatable], + result *common.Sanitree[fs.FileInfo, fs.FileInfo], + ) { + for key := range expect.Leaves { + expect.Leaves[key].Size = 0 // chat sizes cannot be compared + } + + updatedResultLeaves := map[string]*common.Sanileaf[fs.FileInfo, fs.FileInfo]{} + + for key, leaf := range result.Leaves { + key = strings.TrimSuffix(key, ".json") + leaf.Size = 0 // we cannot compare sizes + updatedResultLeaves[key] = leaf + } + + common.CompareLeaves(ctx, expect.Leaves, updatedResultLeaves, nil) + } + + common.CompareDiffTrees( + ctx, + sourceTree, + fpTree.Children[path.ChatsCategory.HumanString()], + comparator) + + common.Infof(ctx, "Success") +} + +func populateChatsSanitree( + ctx context.Context, + ac api.Client, + userID string, +) *common.Sanitree[models.Chatable, models.Chatable] { + root := &common.Sanitree[models.Chatable, models.Chatable]{ + ID: userID, + Name: path.ChatsCategory.HumanString(), + Leaves: map[string]*common.Sanileaf[models.Chatable, models.Chatable]{}, + // teamschat should not have child containers + } + + cc := api.CallConfig{ + Expand: []string{"lastMessagePreview"}, + } + + chats, err := ac.Chats().GetChats(ctx, userID, cc) + if err != nil { + common.Fatal(ctx, "getting channels", err) + } + + for _, chat := range chats { + leaf := &common.Sanileaf[ + models.Chatable, models.Chatable, + ]{ + Parent: root, + ID: ptr.Val(chat.GetId()), + Name: ptr.Val(chat.GetId()), + } + + root.Leaves[ptr.Val(chat.GetId())] = leaf + } + + root.CountLeaves = len(root.Leaves) + + return root +} diff --git a/src/cmd/sanity_test/sanity_tests.go b/src/cmd/sanity_test/sanity_tests.go index 45c5f5b535..d29021e079 100644 --- a/src/cmd/sanity_test/sanity_tests.go +++ b/src/cmd/sanity_test/sanity_tests.go @@ -67,6 +67,7 @@ func main() { expCMD.AddCommand(exportSharePointCMD()) expCMD.AddCommand(exportExchangeCMD()) expCMD.AddCommand(exportGroupsCMD()) + expCMD.AddCommand(exportTeamsChatsCMD()) root.AddCommand(expCMD) if err := root.Execute(); err != nil { @@ -200,6 +201,29 @@ func sanityTestExportExchange(cmd *cobra.Command, args []string) error { return nil } +func exportTeamsChatsCMD() *cobra.Command { + return &cobra.Command{ + Use: "teamschats", + Short: "run the teamschats export sanity tests", + DisableAutoGenTag: true, + RunE: sanityTestExportTeamsChats, + } +} + +func sanityTestExportTeamsChats(cmd *cobra.Command, args []string) error { + ctx := common.SetDebug(cmd.Context()) + envs := common.EnvVars(ctx) + + ac, err := common.GetAC() + if err != nil { + return print.Only(ctx, err) + } + + export.CheckTeamsChatsExport(ctx, ac, envs) + + return nil +} + // --------------------------------------------------------------------------- // service commands - restore // ---------------------------------------------------------------------------