This repository has been archived by the owner on Oct 11, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add export support to teamschats service layer
- Loading branch information
1 parent
7ab1276
commit b9f7128
Showing
5 changed files
with
348 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package teamschats | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/alcionai/clues" | ||
|
||
"github.com/alcionai/corso/src/internal/common/idname" | ||
"github.com/alcionai/corso/src/internal/data" | ||
"github.com/alcionai/corso/src/internal/m365/collection/teamschats" | ||
"github.com/alcionai/corso/src/internal/m365/resource" | ||
"github.com/alcionai/corso/src/internal/operations/inject" | ||
"github.com/alcionai/corso/src/pkg/backup/details" | ||
"github.com/alcionai/corso/src/pkg/control" | ||
"github.com/alcionai/corso/src/pkg/export" | ||
"github.com/alcionai/corso/src/pkg/fault" | ||
"github.com/alcionai/corso/src/pkg/metrics" | ||
"github.com/alcionai/corso/src/pkg/path" | ||
"github.com/alcionai/corso/src/pkg/services/m365/api" | ||
) | ||
|
||
var _ inject.ServiceHandler = &teamsChatsHandler{} | ||
Check failure on line 22 in src/internal/m365/service/teamschats/export.go GitHub Actions / Source-Code-Linting
|
||
|
||
func NewTeamsChatsHandler( | ||
apiClient api.Client, | ||
resourceGetter idname.GetResourceIDAndNamer, | ||
) *teamsChatsHandler { | ||
return &teamsChatsHandler{ | ||
baseTeamsChatsHandler: baseTeamsChatsHandler{}, | ||
apiClient: apiClient, | ||
resourceGetter: resourceGetter, | ||
} | ||
} | ||
|
||
// ========================================================================== // | ||
// baseTeamsChatsHandler | ||
// ========================================================================== // | ||
|
||
// baseTeamsChatsHandler contains logic for tracking data and doing operations | ||
// (e.x. export) that don't require contact with external M356 services. | ||
type baseTeamsChatsHandler struct{} | ||
|
||
func (h *baseTeamsChatsHandler) CacheItemInfo(v details.ItemInfo) {} | ||
|
||
// ProduceExportCollections will create the export collections for the | ||
// given restore collections. | ||
func (h *baseTeamsChatsHandler) ProduceExportCollections( | ||
ctx context.Context, | ||
backupVersion int, | ||
exportCfg control.ExportConfig, | ||
dcs []data.RestoreCollection, | ||
stats *metrics.ExportStats, | ||
errs *fault.Bus, | ||
) ([]export.Collectioner, error) { | ||
var ( | ||
el = errs.Local() | ||
ec = make([]export.Collectioner, 0, len(dcs)) | ||
) | ||
|
||
for _, dc := range dcs { | ||
category := dc.FullPath().Category() | ||
|
||
switch category { | ||
case path.ChatsCategory: | ||
folders := dc.FullPath().Folders() | ||
pth := path.Builder{}.Append(category.HumanString()).Append(folders...) | ||
|
||
ec = append( | ||
ec, | ||
teamschats.NewExportCollection( | ||
pth.String(), | ||
[]data.RestoreCollection{dc}, | ||
backupVersion, | ||
exportCfg, | ||
stats)) | ||
default: | ||
return nil, clues.NewWC(ctx, "data category not supported"). | ||
With("category", category) | ||
} | ||
} | ||
|
||
return ec, el.Failure() | ||
} | ||
|
||
// ========================================================================== // | ||
// teamschatsHandler | ||
// ========================================================================== // | ||
|
||
// teamsChatsHandler contains logic for handling data and performing operations | ||
// (e.x. restore) regardless of whether they require contact with external M365 | ||
// services or not. | ||
type teamsChatsHandler struct { | ||
baseTeamsChatsHandler | ||
apiClient api.Client | ||
resourceGetter idname.GetResourceIDAndNamer | ||
} | ||
|
||
func (h *teamsChatsHandler) IsServiceEnabled( | ||
ctx context.Context, | ||
resourceID string, | ||
) (bool, error) { | ||
// TODO(ashmrtn): Move free function implementation to this function. | ||
res, err := IsServiceEnabled(ctx, h.apiClient.Users(), resourceID) | ||
return res, clues.Stack(err).OrNil() | ||
} | ||
|
||
func (h *teamsChatsHandler) PopulateProtectedResourceIDAndName( | ||
ctx context.Context, | ||
resourceID string, // Can be either ID or name. | ||
ins idname.Cacher, | ||
) (idname.Provider, error) { | ||
if h.resourceGetter == nil { | ||
return nil, clues.StackWC(ctx, resource.ErrNoResourceLookup) | ||
} | ||
|
||
pr, err := h.resourceGetter.GetResourceIDAndNameFrom(ctx, resourceID, ins) | ||
|
||
return pr, clues.Wrap(err, "identifying resource owner").OrNil() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package teamschats | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"io" | ||
"testing" | ||
|
||
"github.com/alcionai/clues" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/stretchr/testify/suite" | ||
|
||
"github.com/alcionai/corso/src/internal/data" | ||
dataMock "github.com/alcionai/corso/src/internal/data/mock" | ||
teamschatMock "github.com/alcionai/corso/src/internal/m365/service/teamschats/mock" | ||
"github.com/alcionai/corso/src/internal/tester" | ||
"github.com/alcionai/corso/src/internal/version" | ||
"github.com/alcionai/corso/src/pkg/control" | ||
"github.com/alcionai/corso/src/pkg/export" | ||
"github.com/alcionai/corso/src/pkg/fault" | ||
"github.com/alcionai/corso/src/pkg/metrics" | ||
"github.com/alcionai/corso/src/pkg/path" | ||
"github.com/alcionai/corso/src/pkg/services/m365/api" | ||
) | ||
|
||
type ExportUnitSuite struct { | ||
tester.Suite | ||
} | ||
|
||
func TestExportUnitSuite(t *testing.T) { | ||
suite.Run(t, &ExportUnitSuite{Suite: tester.NewUnitSuite(t)}) | ||
} | ||
|
||
type finD struct { | ||
id string | ||
key string | ||
name string | ||
err error | ||
} | ||
|
||
func (fd finD) FetchItemByName(ctx context.Context, name string) (data.Item, error) { | ||
if fd.err != nil { | ||
return nil, fd.err | ||
} | ||
|
||
if name == fd.id { | ||
return &dataMock.Item{ | ||
ItemID: fd.id, | ||
Reader: io.NopCloser(bytes.NewBufferString(`{"` + fd.key + `": "` + fd.name + `"}`)), | ||
}, nil | ||
} | ||
|
||
return nil, assert.AnError | ||
} | ||
|
||
func (suite *ExportUnitSuite) TestExportRestoreCollections_chats() { | ||
t := suite.T() | ||
|
||
ctx, flush := tester.NewContext(t) | ||
defer flush() | ||
|
||
var ( | ||
category = path.ChatsCategory | ||
itemID = "itemID" | ||
dii = teamschatMock.ItemInfo() | ||
content = `{"topic": "` + dii.TeamsChats.Chat.Topic + `"}` | ||
body = io.NopCloser(bytes.NewBufferString(content)) | ||
exportCfg = control.ExportConfig{} | ||
expectedPath = category.HumanString() | ||
expectedItems = []export.Item{ | ||
{ | ||
ID: itemID, | ||
Name: itemID + ".json", | ||
// Body: body, not checked | ||
}, | ||
} | ||
) | ||
|
||
p, err := path.BuildPrefix("t", "pr", path.TeamsChatsService, category) | ||
require.NoError(t, err, clues.ToCore(err)) | ||
|
||
dcs := []data.RestoreCollection{ | ||
data.FetchRestoreCollection{ | ||
Collection: dataMock.Collection{ | ||
Path: p, | ||
ItemData: []data.Item{ | ||
&dataMock.Item{ | ||
ItemID: itemID, | ||
Reader: body, | ||
}, | ||
}, | ||
}, | ||
FetchItemByNamer: finD{ | ||
id: itemID, | ||
key: "id", | ||
name: itemID, | ||
}, | ||
}, | ||
} | ||
|
||
stats := metrics.NewExportStats() | ||
|
||
ecs, err := NewTeamsChatsHandler(api.Client{}, nil). | ||
ProduceExportCollections( | ||
ctx, | ||
int(version.Backup), | ||
exportCfg, | ||
dcs, | ||
stats, | ||
fault.New(true)) | ||
require.NoError(t, err, clues.ToCore(err)) | ||
assert.Len(t, ecs, 1, "num of collections") | ||
|
||
assert.Equal(t, expectedPath, ecs[0].BasePath(), "base dir") | ||
|
||
fitems := []export.Item{} | ||
|
||
size := 0 | ||
|
||
for item := range ecs[0].Items(ctx) { | ||
b, err := io.ReadAll(item.Body) | ||
require.NoError(t, err, clues.ToCore(err)) | ||
|
||
// count up size for tests | ||
size += len(b) | ||
|
||
// have to nil out body, otherwise assert fails due to | ||
// pointer memory location differences | ||
item.Body = nil | ||
fitems = append(fitems, item) | ||
} | ||
|
||
assert.Equal(t, expectedItems, fitems, "items") | ||
|
||
expectedStats := metrics.NewExportStats() | ||
expectedStats.UpdateBytes(category, int64(size)) | ||
expectedStats.UpdateResourceCount(category) | ||
assert.Equal(t, expectedStats.GetStats(), stats.GetStats(), "stats") | ||
} |
Oops, something went wrong.