ProduceBackupCollections for SharePoint

This commit is contained in:
Abin 2023-08-18 17:39:47 +05:30
parent 2ba349797f
commit 203f7bd18d
18 changed files with 441 additions and 59 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/internal/diagnostics"
"github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/internal/m365/service/exchange"
"github.com/alcionai/corso/src/internal/m365/service/groups"
"github.com/alcionai/corso/src/internal/m365/service/onedrive"
"github.com/alcionai/corso/src/internal/m365/service/sharepoint"
"github.com/alcionai/corso/src/internal/operations/inject"
@ -116,6 +117,18 @@ func (ctrl *Controller) ProduceBackupCollections(
return nil, nil, false, err
}
case path.GroupsService:
colls, ssmb, canUsePreviousBackup, err = groups.ProduceBackupCollections(
ctx,
bpc,
ctrl.AC,
ctrl.credentials,
ctrl.UpdateStatus,
errs)
if err != nil {
return nil, nil, false, err
}
default:
return nil, nil, false, clues.Wrap(clues.New(service.String()), "service not supported").WithClues(ctx)
}
@ -176,6 +189,10 @@ func verifyBackupInputs(sels selectors.Selector, siteIDs []string) error {
// Exchange and OneDrive user existence now checked in checkServiceEnabled.
return nil
case selectors.ServiceGroups:
// TODO(meain): can use a proper check in checkServiceEnabled.
return nil
case selectors.ServiceSharePoint:
ids = siteIDs
}
@ -197,7 +214,8 @@ func checkServiceEnabled(
service path.ServiceType,
resource string,
) (bool, bool, error) {
if service == path.SharePointService {
if service == path.SharePointService || service == path.GroupsService {
// TODO(meain): use proper check for groups
// No "enabled" check required for sharepoint
return true, true, nil
}

View File

@ -465,3 +465,86 @@ func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Lists() {
}
}
}
// ---------------------------------------------------------------------------
// CreateGroupsCollection tests
// ---------------------------------------------------------------------------
type GroupsCollectionIntgSuite struct {
tester.Suite
connector *Controller
user string
}
func TestGroupsCollectionIntgSuite(t *testing.T) {
suite.Run(t, &GroupsCollectionIntgSuite{
Suite: tester.NewIntegrationSuite(
t,
[][]string{tconfig.M365AcctCredEnvs},
),
})
}
func (suite *GroupsCollectionIntgSuite) SetupSuite() {
ctx, flush := tester.NewContext(suite.T())
defer flush()
suite.connector = newController(ctx, suite.T(), resource.Sites, path.GroupsService)
suite.user = tconfig.M365UserID(suite.T())
tester.LogTimeOfTest(suite.T())
}
func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
var (
groupID = tconfig.M365GroupID(t)
ctrl = newController(ctx, t, resource.Groups, path.GroupsService)
groupIDs = []string{groupID}
)
id, name, err := ctrl.PopulateProtectedResourceIDAndName(ctx, groupID, nil)
require.NoError(t, err, clues.ToCore(err))
sel := selectors.NewGroupsBackup(groupIDs)
// TODO(meain): make use of selectors
sel.Include(sel.LibraryFolders([]string{"test"}, selectors.PrefixMatch()))
sel.SetDiscreteOwnerIDName(id, name)
bpc := inject.BackupProducerConfig{
LastBackupVersion: version.NoBackup,
Options: control.DefaultOptions(),
ProtectedResource: inMock.NewProvider(id, name),
Selector: sel.Selector,
}
collections, excludes, canUsePreviousBackup, err := ctrl.ProduceBackupCollections(
ctx,
bpc,
fault.New(true))
require.NoError(t, err, clues.ToCore(err))
assert.True(t, canUsePreviousBackup, "can use previous backup")
// No excludes yet as this isn't an incremental backup.
assert.True(t, excludes.Empty())
// we don't know an exact count of drives this will produce,
// but it should be more than one.
assert.Greater(t, len(collections), 1)
for _, coll := range collections {
for object := range coll.Items(ctx, fault.New(true)) {
buf := &bytes.Buffer{}
_, err := buf.ReadFrom(object.ToReader())
assert.NoError(t, err, "reading item", clues.ToCore(err))
}
}
status := ctrl.Wait()
assert.NotZero(t, status.Successes)
t.Log(status.String())
}

View File

@ -439,7 +439,7 @@ func (c *Collections) Get(
service, category := c.handler.ServiceCat()
md, err := graph.MakeMetadataCollection(
c.tenantID,
c.resourceOwner,
c.resourceOwner, // TODO(meain): path fixes: group id
service,
category,
[]graph.MetadataCollectionEntry{

View File

@ -0,0 +1,192 @@
package drive
import (
"context"
"net/http"
"strings"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/alcionai/corso/src/internal/common/ptr"
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/services/m365/api"
)
var _ BackupHandler = &groupBackupHandler{}
type groupBackupHandler struct {
groupID string
ac api.Drives
scope selectors.GroupsScope
}
func NewGroupBackupHandler(groupID string, ac api.Drives, scope selectors.GroupsScope) groupBackupHandler {
return groupBackupHandler{groupID, ac, scope}
}
func (h groupBackupHandler) Get(
ctx context.Context,
url string,
headers map[string]string,
) (*http.Response, error) {
return h.ac.Get(ctx, url, headers)
}
func (h groupBackupHandler) PathPrefix(
tenantID, resourceOwner, driveID string,
) (path.Path, error) {
return path.Build(
tenantID,
resourceOwner,
path.GroupsService,
path.LibrariesCategory, // TODO(meain)
false,
odConsts.DrivesPathDir,
driveID,
odConsts.RootPathDir)
}
func (h groupBackupHandler) CanonicalPath(
folders *path.Builder,
tenantID, resourceOwner string,
) (path.Path, error) {
// TODO(meain): resourceOwner
return folders.ToDataLayerGroupPath(tenantID, h.groupID, path.LibrariesCategory, false)
}
func (h groupBackupHandler) ServiceCat() (path.ServiceType, path.CategoryType) {
return path.GroupsService, path.LibrariesCategory
}
func (h groupBackupHandler) NewDrivePager(
resourceOwner string,
fields []string,
) api.DrivePager {
return h.ac.NewSiteDrivePager(resourceOwner, fields)
}
func (h groupBackupHandler) NewItemPager(
driveID, link string,
fields []string,
) api.DriveItemDeltaEnumerator {
return h.ac.NewDriveItemDeltaPager(driveID, link, fields)
}
func (h groupBackupHandler) AugmentItemInfo(
dii details.ItemInfo,
item models.DriveItemable,
size int64,
parentPath *path.Builder,
) details.ItemInfo {
return augmentGroupItemInfo(dii, item, size, parentPath)
}
func (h groupBackupHandler) FormatDisplayPath(
driveName string,
pb *path.Builder,
) string {
return "/" + driveName + "/" + pb.String()
}
func (h groupBackupHandler) NewLocationIDer(
driveID string,
elems ...string,
) details.LocationIDer {
return details.NewSharePointLocationIDer(driveID, elems...)
}
func (h groupBackupHandler) GetItemPermission(
ctx context.Context,
driveID, itemID string,
) (models.PermissionCollectionResponseable, error) {
return h.ac.GetItemPermission(ctx, driveID, itemID)
}
func (h groupBackupHandler) GetItem(
ctx context.Context,
driveID, itemID string,
) (models.DriveItemable, error) {
return h.ac.GetItem(ctx, driveID, itemID)
}
func (h groupBackupHandler) IsAllPass() bool {
// TODO(meain)
return true
}
func (h groupBackupHandler) IncludesDir(dir string) bool {
// TODO(meain)
// return h.scope.Matches(selectors.SharePointGroupFolder, dir)
return true
}
// ---------------------------------------------------------------------------
// Common
// ---------------------------------------------------------------------------
func augmentGroupItemInfo(
dii details.ItemInfo,
item models.DriveItemable,
size int64,
parentPath *path.Builder,
) details.ItemInfo {
var driveName, driveID, creatorEmail string
// TODO: we rely on this info for details/restore lookups,
// so if it's nil we have an issue, and will need an alternative
// way to source the data.
if item.GetCreatedBy() != nil && item.GetCreatedBy().GetUser() != nil {
// User is sometimes not available when created via some
// external applications (like backup/restore solutions)
additionalData := item.GetCreatedBy().GetUser().GetAdditionalData()
ed, ok := additionalData["email"]
if !ok {
ed = additionalData["displayName"]
}
if ed != nil {
creatorEmail = *ed.(*string)
}
}
// gsi := item.GetSharepointIds()
// if gsi != nil {
// siteID = ptr.Val(gsi.GetSiteId())
// weburl = ptr.Val(gsi.GetSiteUrl())
// if len(weburl) == 0 {
// weburl = constructWebURL(item.GetAdditionalData())
// }
// }
if item.GetParentReference() != nil {
driveID = ptr.Val(item.GetParentReference().GetDriveId())
driveName = strings.TrimSpace(ptr.Val(item.GetParentReference().GetName()))
}
var pps string
if parentPath != nil {
pps = parentPath.String()
}
dii.Groups = &details.GroupsInfo{
Created: ptr.Val(item.GetCreatedDateTime()),
DriveID: driveID,
DriveName: driveName,
ItemName: ptr.Val(item.GetName()),
ItemType: details.SharePointLibrary,
Modified: ptr.Val(item.GetLastModifiedDateTime()),
Owner: creatorEmail,
ParentPath: pps,
Size: size,
}
dii.Extension = &details.ExtensionData{}
return dii
}

View File

@ -25,10 +25,9 @@ import (
func CollectLibraries(
ctx context.Context,
bpc inject.BackupProducerConfig,
ad api.Drives,
bh drive.BackupHandler,
tenantID string,
ssmb *prefixmatcher.StringSetMatchBuilder,
scope selectors.SharePointScope,
su support.StatusUpdater,
errs *fault.Bus,
) ([]data.BackupCollection, bool, error) {
@ -37,7 +36,7 @@ func CollectLibraries(
var (
collections = []data.BackupCollection{}
colls = drive.NewCollections(
drive.NewLibraryBackupHandler(ad, scope),
bh,
tenantID,
bpc.ProtectedResource.ID(),
su,

View File

@ -170,6 +170,8 @@ func getResourceClient(rc resource.Category, ac api.Client) (*resourceClient, er
return &resourceClient{enum: rc, getter: ac.Users()}, nil
case resource.Sites:
return &resourceClient{enum: rc, getter: ac.Sites()}, nil
case resource.Groups:
return &resourceClient{enum: rc, getter: ac.Groups()}, nil
default:
return nil, clues.New("unrecognized owner resource enum").With("resource_enum", rc)
}

View File

@ -6,4 +6,5 @@ const (
UnknownResource Category = ""
Users Category = "users"
Sites Category = "sites"
Groups Category = "groups"
)

View File

@ -5,8 +5,12 @@ import (
"github.com/alcionai/clues"
"github.com/alcionai/corso/src/internal/common/idname"
"github.com/alcionai/corso/src/internal/common/prefixmatcher"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/m365/collection/drive"
"github.com/alcionai/corso/src/internal/m365/collection/site"
"github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/internal/m365/support"
"github.com/alcionai/corso/src/internal/observe"
@ -56,7 +60,41 @@ func ProduceBackupCollections(
var dbcs []data.BackupCollection
switch scope.Category().PathType() {
case path.LibrariesCategory: // TODO
case path.LibrariesCategory:
// TODO(meain): Private channels get a separate SharePoint
// site. We should also back those up and not just the
// default one.
resp, err := ac.Stable.
Client().
Groups().
ByGroupId(bpc.ProtectedResource.ID()).
Sites().
BySiteId("root").
Get(ctx, nil)
if err != nil {
return nil, nil, false, clues.Wrap(err, "getting root site for group")
}
pr := idname.NewProvider(ptr.Val(resp.GetId()), ptr.Val(resp.GetName()))
sbpc := inject.BackupProducerConfig{
LastBackupVersion: bpc.LastBackupVersion,
Options: bpc.Options,
ProtectedResource: pr,
Selector: bpc.Selector,
}
dbcs, canUsePreviousBackup, err = site.CollectLibraries(
ctx,
sbpc,
drive.NewGroupBackupHandler(bpc.ProtectedResource.ID(), ac.Drives(), scope),
creds.AzureTenantID,
ssmb,
su,
errs)
if err != nil {
el.AddRecoverable(ctx, err)
continue
}
}
collections = append(collections, dbcs...)
@ -70,7 +108,7 @@ func ProduceBackupCollections(
collections,
creds.AzureTenantID,
bpc.ProtectedResource.ID(),
path.UnknownService, // path.GroupsService
path.GroupsService,
categories,
su,
errs)

View File

@ -7,6 +7,7 @@ import (
"github.com/alcionai/corso/src/internal/common/prefixmatcher"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/m365/collection/drive"
"github.com/alcionai/corso/src/internal/m365/collection/site"
"github.com/alcionai/corso/src/internal/m365/graph"
"github.com/alcionai/corso/src/internal/m365/support"
@ -79,10 +80,9 @@ func ProduceBackupCollections(
spcs, canUsePreviousBackup, err = site.CollectLibraries(
ctx,
bpc,
ac.Drives(),
drive.NewLibraryBackupHandler(ac.Drives(), scope),
creds.AzureTenantID,
ssmb,
scope,
su,
errs)
if err != nil {

View File

@ -30,6 +30,7 @@ const (
TestCfgGroupID = "m365groupid"
TestCfgUserID = "m365userid"
TestCfgSecondaryUserID = "secondarym365userid"
TestCfgSecondaryGroupID = "secondarym365groupid"
TestCfgTertiaryUserID = "tertiarym365userid"
TestCfgLoadTestUserID = "loadtestm365userid"
TestCfgLoadTestOrgUsers = "loadtestm365orgusers"

View File

@ -241,6 +241,8 @@ func (pb Builder) ToStreamStorePath(
metadataService = OneDriveMetadataService
case SharePointService:
metadataService = SharePointMetadataService
case GroupsService:
metadataService = GroupsMetadataService
}
return &dataLayerResourcePath{
@ -282,6 +284,8 @@ func (pb Builder) ToServiceCategoryMetadataPath(
metadataService = OneDriveMetadataService
case SharePointService:
metadataService = SharePointMetadataService
case GroupsService:
metadataService = GroupsMetadataService
}
return &dataLayerResourcePath{
@ -346,6 +350,14 @@ func (pb Builder) ToDataLayerSharePointPath(
return pb.ToDataLayerPath(tenant, site, SharePointService, category, isItem)
}
func (pb Builder) ToDataLayerGroupPath(
tenant, group string,
category CategoryType,
isItem bool,
) (Path, error) {
return pb.ToDataLayerPath(tenant, group, GroupsService, category, isItem)
}
// ---------------------------------------------------------------------------
// Stringers and PII Concealer Compliance
// ---------------------------------------------------------------------------

View File

@ -73,6 +73,9 @@ var serviceCategories = map[ServiceType]map[CategoryType]struct{}{
ListsCategory: {},
PagesCategory: {},
},
GroupsService: {
LibrariesCategory: {}, // TODO(meain)
},
}
func validateServiceAndCategoryStrings(s, c string) (ServiceType, CategoryType, error) {

View File

@ -13,10 +13,12 @@ var piiSafePathElems = pii.MapWithPlurals(
UnknownService.String(),
ExchangeService.String(),
OneDriveService.String(),
GroupsService.String(),
SharePointService.String(),
ExchangeMetadataService.String(),
OneDriveMetadataService.String(),
SharePointMetadataService.String(),
GroupsMetadataService.String(),
// categories
UnknownCategory.String(),

View File

@ -328,6 +328,13 @@ func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() {
expectedService: path.SharePointMetadataService,
check: assert.NoError,
},
{
name: "Passes",
service: path.GroupsService,
category: path.LibrariesCategory,
expectedService: path.GroupsMetadataService,
check: assert.NoError,
},
}
for _, test := range table {

View File

@ -9,6 +9,7 @@ import (
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/backup/identity"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path"
)
@ -204,8 +205,8 @@ func (s *groups) Scopes() []GroupsScope {
// -------------------
// Scope Factories
// Produces one or more Groups site scopes.
// One scope is created per site entry.
// Produces one or more Groups scopes.
// One scope is created per group entry.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
@ -214,38 +215,57 @@ func (s *groups) AllData() []GroupsScope {
scopes = append(
scopes,
makeScope[GroupsScope](GroupsTODOContainer, Any()))
makeScope[GroupsScope](GroupsLibraryFolder, Any()))
return scopes
}
// TODO produces one or more Groups TODO scopes.
// Library produces one or more Group library scopes, where the library
// matches upon a given drive by ID or Name. In order to ensure library selection
// this should always be embedded within the Filter() set; include(Library()) will
// select all items in the library without further filtering.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// Any empty slice defaults to [selectors.None]
func (s *groups) TODO(lists []string, opts ...option) []GroupsScope {
// If any slice is empty, it defaults to [selectors.None]
func (s *groups) Library(library string) []GroupsScope {
return []GroupsScope{
makeInfoScope[GroupsScope](
GroupsLibraryItem,
SharePointInfoLibraryDrive, // TODO(meain)
[]string{library},
filters.Equal),
}
}
// LibraryFolders produces one or more SharePoint libraryFolder scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
func (s *groups) LibraryFolders(libraryFolders []string, opts ...option) []GroupsScope {
var (
scopes = []GroupsScope{}
os = append([]option{pathComparator()}, opts...)
)
scopes = append(scopes, makeScope[GroupsScope](GroupsTODOContainer, lists, os...))
scopes = append(
scopes,
makeScope[GroupsScope](GroupsLibraryFolder, libraryFolders, os...))
return scopes
}
// ListTODOItemsItems produces one or more Groups TODO item scopes.
// LibraryItems produces one or more Groups library item scopes.
// If any slice contains selectors.Any, that slice is reduced to [selectors.Any]
// If any slice contains selectors.None, that slice is reduced to [selectors.None]
// If any slice is empty, it defaults to [selectors.None]
// options are only applied to the list scopes.
func (s *groups) TODOItems(lists, items []string, opts ...option) []GroupsScope {
// options are only applied to the library scopes.
func (s *groups) LibraryItems(libraries, items []string, opts ...option) []GroupsScope {
scopes := []GroupsScope{}
scopes = append(
scopes,
makeScope[GroupsScope](GroupsTODOItem, items, defaultItemOptions(s.Cfg)...).
set(GroupsTODOContainer, lists, opts...))
makeScope[GroupsScope](GroupsLibraryItem, items, defaultItemOptions(s.Cfg)...).
set(GroupsLibraryFolder, libraries, opts...))
return scopes
}
@ -270,21 +290,27 @@ const (
GroupsCategoryUnknown groupsCategory = ""
// types of data in Groups
GroupsGroup groupsCategory = "GroupsGroup"
GroupsTODOContainer groupsCategory = "GroupsTODOContainer"
GroupsTODOItem groupsCategory = "GroupsTODOItem"
GroupsGroup groupsCategory = "GroupsGroup"
// sharepoint
GroupsLibraryFolder groupsCategory = "GroupsLibraryFolder"
GroupsLibraryItem groupsCategory = "GroupsLibraryItem"
// messages
// GroupsTeamChannel groupsCategory = "GroupsTeamChannel"
// GroupsTeamChannelMessages groupsCategory = "GroupsTeamChannelMessages"
// details.itemInfo comparables
// library drive selection
GroupsInfoSiteLibraryDrive groupsCategory = "GroupsInfoSiteLibraryDrive"
GroupsInfoSiteLibraryDrive groupsCategory = "GroupsInfoSiteLibraryDrive" // TODO(meain)
)
// groupsLeafProperties describes common metadata of the leaf categories
var groupsLeafProperties = map[categorizer]leafProperty{
GroupsTODOItem: { // the root category must be represented, even though it isn't a leaf
pathKeys: []categorizer{GroupsTODOContainer, GroupsTODOItem},
pathType: path.UnknownCategory,
GroupsLibraryItem: {
pathKeys: []categorizer{GroupsLibraryFolder, GroupsLibraryItem},
pathType: path.LibrariesCategory,
},
GroupsGroup: { // the root category must be represented, even though it isn't a leaf
pathKeys: []categorizer{GroupsGroup},
@ -303,8 +329,8 @@ func (c groupsCategory) String() string {
// Ex: ServiceUser.leafCat() => ServiceUser
func (c groupsCategory) leafCat() categorizer {
switch c {
case GroupsTODOContainer, GroupsInfoSiteLibraryDrive:
return GroupsTODOItem
case GroupsLibraryFolder, GroupsLibraryItem, GroupsInfoSiteLibraryDrive:
return GroupsLibraryItem
}
return c
@ -334,7 +360,7 @@ func (c groupsCategory) isLeaf() bool {
// pathValues transforms the two paths to maps of identified properties.
//
// Example:
// [tenantID, service, siteID, category, folder, itemID]
// [tenantID, service, groupID, site, siteID, category, folder, itemID]
// => {spFolder: folder, spItemID: itemID}
func (c groupsCategory) pathValues(
repo path.Path,
@ -348,13 +374,13 @@ func (c groupsCategory) pathValues(
)
switch c {
case GroupsTODOContainer, GroupsTODOItem:
case GroupsLibraryFolder, GroupsLibraryItem:
if ent.Groups == nil {
return nil, clues.New("no Groups ItemInfo in details")
}
folderCat, itemCat = GroupsTODOContainer, GroupsTODOItem
rFld = ent.Groups.ParentPath
folderCat, itemCat = GroupsLibraryFolder, GroupsLibraryItem
rFld = ent.Groups.ParentPath // TODO(meain)
default:
return nil, clues.New("unrecognized groupsCategory").With("category", c)
@ -451,7 +477,7 @@ func (s GroupsScope) set(cat groupsCategory, v []string, opts ...option) GroupsS
os := []option{}
switch cat {
case GroupsTODOContainer:
case GroupsLibraryFolder:
os = append(os, pathComparator())
}
@ -462,10 +488,10 @@ func (s GroupsScope) set(cat groupsCategory, v []string, opts ...option) GroupsS
func (s GroupsScope) setDefaults() {
switch s.Category() {
case GroupsGroup:
s[GroupsTODOContainer.String()] = passAny
s[GroupsTODOItem.String()] = passAny
case GroupsTODOContainer:
s[GroupsTODOItem.String()] = passAny
s[GroupsLibraryFolder.String()] = passAny
s[GroupsLibraryItem.String()] = passAny
case GroupsLibraryFolder:
s[GroupsLibraryItem.String()] = passAny
}
}
@ -485,7 +511,7 @@ func (s groups) Reduce(
deets,
s.Selector,
map[path.CategoryType]groupsCategory{
path.UnknownCategory: GroupsTODOItem,
path.LibrariesCategory: GroupsLibraryItem,
},
errs)
}

View File

@ -32,6 +32,7 @@ var serviceToPathType = map[service]path.ServiceType{
ServiceExchange: path.ExchangeService,
ServiceOneDrive: path.OneDriveService,
ServiceSharePoint: path.SharePointService,
ServiceGroups: path.GroupsService,
}
var (

View File

@ -7,6 +7,7 @@ import (
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/common/str"
"github.com/alcionai/corso/src/internal/common/tform"
"github.com/alcionai/corso/src/internal/m365/graph"
@ -27,7 +28,7 @@ func (c Client) Groups() Groups {
return Groups{c}
}
// On creation of each Teams team a corrsponding group gets created.
// On creation of each Teams team a corresponding group gets created.
// The group acts as the protected resource, and all teams data like events,
// drive and mail messages are owned by that group.
@ -167,3 +168,14 @@ func IsTeam(ctx context.Context, mg models.Groupable) bool {
return false
}
// GetIDAndName looks up the group matching the given ID, and returns
// its canonical ID and the name.
func (c Groups) GetIDAndName(ctx context.Context, groupID string) (string, string, error) {
s, err := c.GetByID(ctx, groupID)
if err != nil {
return "", "", err
}
return ptr.Val(s.GetId()), ptr.Val(s.GetDisplayName()), nil
}

View File

@ -107,7 +107,7 @@ func (suite *GroupsIntgSuite) TestGetAll() {
Groups().
GetAll(ctx, fault.New(true))
require.NoError(t, err)
require.NotZero(t, len(groups), "must find at least one group")
require.NotZero(t, len(groups), "must have at least one group")
}
func (suite *GroupsIntgSuite) TestGroups_GetByID() {
@ -122,34 +122,19 @@ func (suite *GroupsIntgSuite) TestGroups_GetByID() {
expectErr func(*testing.T, error)
}{
{
name: "3 part id",
name: "valid id",
id: groupID,
expectErr: func(t *testing.T, err error) {
assert.NoError(t, err, clues.ToCore(err))
},
},
{
name: "malformed id",
name: "invalid id",
id: uuid.NewString(),
expectErr: func(t *testing.T, err error) {
assert.Error(t, err, clues.ToCore(err))
},
},
{
name: "random id",
id: uuid.NewString() + "," + uuid.NewString(),
expectErr: func(t *testing.T, err error) {
assert.Error(t, err, clues.ToCore(err))
},
},
{
name: "malformed url",
id: "barunihlda",
expectErr: func(t *testing.T, err error) {
assert.Error(t, err, clues.ToCore(err))
},
},
}
for _, test := range table {
suite.Run(test.name, func() {