add generic item enumeratiton iface to api (#3628)

Adds a generic item enumeration pager to the api's item_pager set.  This compliments the more focused getIdsAndAdditional with something that's oriented towards single-folder, non-delta enumeration.

---

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

- [x]  No

#### Type of change

- [ ] 🌻 Feature

#### Issue(s)

* #3562

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-06-21 10:43:33 -06:00 committed by GitHub
parent d9b5cda8f1
commit 3bcc405327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 363 additions and 107 deletions

View File

@ -90,19 +90,51 @@ func (c Contacts) EnumerateContainers(
// item pager // item pager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var _ itemPager = &contactPager{} var _ itemPager[models.Contactable] = &contactsPager{}
type contactPager struct { type contactsPager struct {
// TODO(rkeeprs)
}
func (c Contacts) NewContactsPager() itemPager[models.Contactable] {
// TODO(rkeepers)
return nil
}
//lint:ignore U1000 False Positive
func (p *contactsPager) getPage(ctx context.Context) (PageLinker, error) {
// TODO(rkeepers)
return nil, nil
}
//lint:ignore U1000 False Positive
func (p *contactsPager) setNext(nextLink string) {
// TODO(rkeepers)
}
//lint:ignore U1000 False Positive
func (p *contactsPager) valuesIn(pl PageLinker) ([]models.Contactable, error) {
// TODO(rkeepers)
return nil, nil
}
// ---------------------------------------------------------------------------
// item ID pager
// ---------------------------------------------------------------------------
var _ itemIDPager = &contactIDPager{}
type contactIDPager struct {
gs graph.Servicer gs graph.Servicer
builder *users.ItemContactFoldersItemContactsRequestBuilder builder *users.ItemContactFoldersItemContactsRequestBuilder
options *users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration options *users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration
} }
func (c Contacts) NewContactPager( func (c Contacts) NewContactIDsPager(
ctx context.Context, ctx context.Context,
userID, containerID string, userID, containerID string,
immutableIDs bool, immutableIDs bool,
) itemPager { ) itemIDPager {
config := &users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration{ config := &users.ItemContactFoldersItemContactsRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemContactFoldersItemContactsRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemContactFoldersItemContactsRequestBuilderGetQueryParameters{
Select: idAnd(parentFolderID), Select: idAnd(parentFolderID),
@ -118,10 +150,10 @@ func (c Contacts) NewContactPager(
ByContactFolderId(containerID). ByContactFolderId(containerID).
Contacts() Contacts()
return &contactPager{c.Stable, builder, config} return &contactIDPager{c.Stable, builder, config}
} }
func (p *contactPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *contactIDPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
resp, err := p.builder.Get(ctx, p.options) resp, err := p.builder.Get(ctx, p.options)
if err != nil { if err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
@ -130,24 +162,24 @@ func (p *contactPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
return EmptyDeltaLinker[models.Contactable]{PageLinkValuer: resp}, nil return EmptyDeltaLinker[models.Contactable]{PageLinkValuer: resp}, nil
} }
func (p *contactPager) setNext(nextLink string) { func (p *contactIDPager) setNext(nextLink string) {
p.builder = users.NewItemContactFoldersItemContactsRequestBuilder(nextLink, p.gs.Adapter()) p.builder = users.NewItemContactFoldersItemContactsRequestBuilder(nextLink, p.gs.Adapter())
} }
// non delta pagers don't need reset // non delta pagers don't need reset
func (p *contactPager) reset(context.Context) {} func (p *contactIDPager) reset(context.Context) {}
func (p *contactPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *contactIDPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
return toValues[models.Contactable](pl) return toValues[models.Contactable](pl)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// delta item pager // delta item ID pager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var _ itemPager = &contactDeltaPager{} var _ itemIDPager = &contactDeltaIDPager{}
type contactDeltaPager struct { type contactDeltaIDPager struct {
gs graph.Servicer gs graph.Servicer
userID string userID string
containerID string containerID string
@ -165,11 +197,11 @@ func getContactDeltaBuilder(
return builder return builder
} }
func (c Contacts) NewContactDeltaPager( func (c Contacts) NewContactDeltaIDsPager(
ctx context.Context, ctx context.Context,
userID, containerID, oldDelta string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
) itemPager { ) itemIDPager {
options := &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration{ options := &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemContactFoldersItemContactsDeltaRequestBuilderGetQueryParameters{
Select: idAnd(parentFolderID), Select: idAnd(parentFolderID),
@ -184,10 +216,10 @@ func (c Contacts) NewContactDeltaPager(
builder = getContactDeltaBuilder(ctx, c.Stable, userID, containerID, options) builder = getContactDeltaBuilder(ctx, c.Stable, userID, containerID, options)
} }
return &contactDeltaPager{c.Stable, userID, containerID, builder, options} return &contactDeltaIDPager{c.Stable, userID, containerID, builder, options}
} }
func (p *contactDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *contactDeltaIDPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
resp, err := p.builder.Get(ctx, p.options) resp, err := p.builder.Get(ctx, p.options)
if err != nil { if err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
@ -196,15 +228,15 @@ func (p *contactDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error
return resp, nil return resp, nil
} }
func (p *contactDeltaPager) setNext(nextLink string) { func (p *contactDeltaIDPager) setNext(nextLink string) {
p.builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(nextLink, p.gs.Adapter()) p.builder = users.NewItemContactFoldersItemContactsDeltaRequestBuilder(nextLink, p.gs.Adapter())
} }
func (p *contactDeltaPager) reset(ctx context.Context) { func (p *contactDeltaIDPager) reset(ctx context.Context) {
p.builder = getContactDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options) p.builder = getContactDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options)
} }
func (p *contactDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *contactDeltaIDPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
return toValues[models.Contactable](pl) return toValues[models.Contactable](pl)
} }
@ -219,8 +251,8 @@ func (c Contacts) GetAddedAndRemovedItemIDs(
"category", selectors.ExchangeContact, "category", selectors.ExchangeContact,
"container_id", containerID) "container_id", containerID)
pager := c.NewContactPager(ctx, userID, containerID, immutableIDs) pager := c.NewContactIDsPager(ctx, userID, containerID, immutableIDs)
deltaPager := c.NewContactDeltaPager(ctx, userID, containerID, oldDelta, immutableIDs) deltaPager := c.NewContactDeltaIDsPager(ctx, userID, containerID, oldDelta, immutableIDs)
return getAddedAndRemovedItemIDs(ctx, c.Stable, pager, deltaPager, oldDelta, canMakeDeltaQueries) return getAddedAndRemovedItemIDs(ctx, c.Stable, pager, deltaPager, oldDelta, canMakeDeltaQueries)
} }

View File

@ -98,19 +98,51 @@ func (c Events) EnumerateContainers(
// item pager // item pager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var _ itemPager = &eventPager{} var _ itemPager[models.Eventable] = &eventsPager{}
type eventPager struct { type eventsPager struct {
// TODO(rkeeprs)
}
func (c Events) NewEventsPager() itemPager[models.Eventable] {
// TODO(rkeepers)
return nil
}
//lint:ignore U1000 False Positive
func (p *eventsPager) getPage(ctx context.Context) (PageLinker, error) {
// TODO(rkeepers)
return nil, nil
}
//lint:ignore U1000 False Positive
func (p *eventsPager) setNext(nextLink string) {
// TODO(rkeepers)
}
//lint:ignore U1000 False Positive
func (p *eventsPager) valuesIn(pl PageLinker) ([]models.Eventable, error) {
// TODO(rkeepers)
return nil, nil
}
// ---------------------------------------------------------------------------
// item ID pager
// ---------------------------------------------------------------------------
var _ itemIDPager = &eventIDPager{}
type eventIDPager struct {
gs graph.Servicer gs graph.Servicer
builder *users.ItemCalendarsItemEventsRequestBuilder builder *users.ItemCalendarsItemEventsRequestBuilder
options *users.ItemCalendarsItemEventsRequestBuilderGetRequestConfiguration options *users.ItemCalendarsItemEventsRequestBuilderGetRequestConfiguration
} }
func (c Events) NewEventPager( func (c Events) NewEventIDsPager(
ctx context.Context, ctx context.Context,
userID, containerID string, userID, containerID string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
options := &users.ItemCalendarsItemEventsRequestBuilderGetRequestConfiguration{ options := &users.ItemCalendarsItemEventsRequestBuilderGetRequestConfiguration{
Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)), Headers: newPreferHeaders(preferPageSize(maxNonDeltaPageSize), preferImmutableIDs(immutableIDs)),
} }
@ -123,10 +155,10 @@ func (c Events) NewEventPager(
ByCalendarId(containerID). ByCalendarId(containerID).
Events() Events()
return &eventPager{c.Stable, builder, options}, nil return &eventIDPager{c.Stable, builder, options}, nil
} }
func (p *eventPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *eventIDPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
resp, err := p.builder.Get(ctx, p.options) resp, err := p.builder.Get(ctx, p.options)
if err != nil { if err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
@ -135,24 +167,24 @@ func (p *eventPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
return EmptyDeltaLinker[models.Eventable]{PageLinkValuer: resp}, nil return EmptyDeltaLinker[models.Eventable]{PageLinkValuer: resp}, nil
} }
func (p *eventPager) setNext(nextLink string) { func (p *eventIDPager) setNext(nextLink string) {
p.builder = users.NewItemCalendarsItemEventsRequestBuilder(nextLink, p.gs.Adapter()) p.builder = users.NewItemCalendarsItemEventsRequestBuilder(nextLink, p.gs.Adapter())
} }
// non delta pagers don't need reset // non delta pagers don't need reset
func (p *eventPager) reset(context.Context) {} func (p *eventIDPager) reset(context.Context) {}
func (p *eventPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *eventIDPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
return toValues[models.Eventable](pl) return toValues[models.Eventable](pl)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// delta item pager // delta item ID pager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var _ itemPager = &eventDeltaPager{} var _ itemIDPager = &eventDeltaIDPager{}
type eventDeltaPager struct { type eventDeltaIDPager struct {
gs graph.Servicer gs graph.Servicer
userID string userID string
containerID string containerID string
@ -160,11 +192,11 @@ type eventDeltaPager struct {
options *users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration options *users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration
} }
func (c Events) NewEventDeltaPager( func (c Events) NewEventDeltaIDsPager(
ctx context.Context, ctx context.Context,
userID, containerID, oldDelta string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
options := &users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration{ options := &users.ItemCalendarsItemEventsDeltaRequestBuilderGetRequestConfiguration{
Headers: newPreferHeaders(preferPageSize(maxDeltaPageSize), preferImmutableIDs(immutableIDs)), Headers: newPreferHeaders(preferPageSize(maxDeltaPageSize), preferImmutableIDs(immutableIDs)),
} }
@ -177,7 +209,7 @@ func (c Events) NewEventDeltaPager(
builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(oldDelta, c.Stable.Adapter()) builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(oldDelta, c.Stable.Adapter())
} }
return &eventDeltaPager{c.Stable, userID, containerID, builder, options}, nil return &eventDeltaIDPager{c.Stable, userID, containerID, builder, options}, nil
} }
func getEventDeltaBuilder( func getEventDeltaBuilder(
@ -200,7 +232,7 @@ func getEventDeltaBuilder(
return builder return builder
} }
func (p *eventDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *eventDeltaIDPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
resp, err := p.builder.Get(ctx, p.options) resp, err := p.builder.Get(ctx, p.options)
if err != nil { if err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
@ -209,15 +241,15 @@ func (p *eventDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error)
return resp, nil return resp, nil
} }
func (p *eventDeltaPager) setNext(nextLink string) { func (p *eventDeltaIDPager) setNext(nextLink string) {
p.builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(nextLink, p.gs.Adapter()) p.builder = users.NewItemCalendarsItemEventsDeltaRequestBuilder(nextLink, p.gs.Adapter())
} }
func (p *eventDeltaPager) reset(ctx context.Context) { func (p *eventDeltaIDPager) reset(ctx context.Context) {
p.builder = getEventDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options) p.builder = getEventDeltaBuilder(ctx, p.gs, p.userID, p.containerID, p.options)
} }
func (p *eventDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *eventDeltaIDPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
return toValues[models.Eventable](pl) return toValues[models.Eventable](pl)
} }
@ -229,12 +261,12 @@ func (c Events) GetAddedAndRemovedItemIDs(
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {
ctx = clues.Add(ctx, "container_id", containerID) ctx = clues.Add(ctx, "container_id", containerID)
pager, err := c.NewEventPager(ctx, userID, containerID, immutableIDs) pager, err := c.NewEventIDsPager(ctx, userID, containerID, immutableIDs)
if err != nil { if err != nil {
return nil, nil, DeltaUpdate{}, graph.Wrap(ctx, err, "creating non-delta pager") return nil, nil, DeltaUpdate{}, graph.Wrap(ctx, err, "creating non-delta pager")
} }
deltaPager, err := c.NewEventDeltaPager(ctx, userID, containerID, oldDelta, immutableIDs) deltaPager, err := c.NewEventDeltaIDsPager(ctx, userID, containerID, oldDelta, immutableIDs)
if err != nil { if err != nil {
return nil, nil, DeltaUpdate{}, graph.Wrap(ctx, err, "creating delta pager") return nil, nil, DeltaUpdate{}, graph.Wrap(ctx, err, "creating delta pager")
} }

View File

@ -61,27 +61,57 @@ func (e EmptyDeltaLinker[T]) GetValue() []T {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// generic handler for paging item ids in a container // generic handler for non-delta item paging in a container
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
type itemPager interface { type itemPager[T any] interface {
// getPage get a page with the specified options from graph // getPage get a page with the specified options from graph
getPage(context.Context) (DeltaPageLinker, error) getPage(context.Context) (PageLinker, error)
// setNext is used to pass in the next url got from graph // setNext is used to pass in the next url got from graph
setNext(string) setNext(string)
// reset is used to clear delta url in delta pagers. When
// reset is called, we reset the state(delta url) that we
// currently have and start a new delta query without the token.
reset(context.Context)
// valuesIn gets us the values in a page // valuesIn gets us the values in a page
valuesIn(PageLinker) ([]getIDAndAddtler, error) valuesIn(PageLinker) ([]T, error)
} }
type getIDAndAddtler interface { func enumerateItems[T any](
GetId() *string ctx context.Context,
GetAdditionalData() map[string]any pager itemPager[T],
) ([]T, error) {
var (
result = make([]T, 0)
// stubbed initial value to ensure we enter the loop.
nextLink = "do-while"
)
for len(nextLink) > 0 {
// get the next page of data, check for standard errors
resp, err := pager.getPage(ctx)
if err != nil {
return nil, graph.Stack(ctx, err)
}
// each category type responds with a different interface, but all
// of them comply with GetValue, which is where we'll get our item data.
items, err := pager.valuesIn(resp)
if err != nil {
return nil, graph.Stack(ctx, err)
}
result = append(result, items...)
nextLink = NextLink(resp)
pager.setNext(nextLink)
}
logger.Ctx(ctx).Infow("completed enumeration", "count", len(result))
return result, nil
} }
// ---------------------------------------------------------------------------
// generic handler for delta-based ittem paging in a container
// ---------------------------------------------------------------------------
// uses a models interface compliant with { GetValues() []T } // uses a models interface compliant with { GetValues() []T }
// to transform its results into a slice of getIDer interfaces. // to transform its results into a slice of getIDer interfaces.
// Generics used here to handle the variation of msoft interfaces // Generics used here to handle the variation of msoft interfaces
@ -110,16 +140,34 @@ func toValues[T any](a any) ([]getIDAndAddtler, error) {
return r, nil return r, nil
} }
type itemIDPager interface {
// getPage get a page with the specified options from graph
getPage(context.Context) (DeltaPageLinker, error)
// setNext is used to pass in the next url got from graph
setNext(string)
// reset is used to clear delta url in delta pagers. When
// reset is called, we reset the state(delta url) that we
// currently have and start a new delta query without the token.
reset(context.Context)
// valuesIn gets us the values in a page
valuesIn(PageLinker) ([]getIDAndAddtler, error)
}
type getIDAndAddtler interface {
GetId() *string
GetAdditionalData() map[string]any
}
func getAddedAndRemovedItemIDs( func getAddedAndRemovedItemIDs(
ctx context.Context, ctx context.Context,
service graph.Servicer, service graph.Servicer,
pager itemPager, pager itemIDPager,
deltaPager itemPager, deltaPager itemIDPager,
oldDelta string, oldDelta string,
canMakeDeltaQueries bool, canMakeDeltaQueries bool,
) ([]string, []string, DeltaUpdate, error) { ) ([]string, []string, DeltaUpdate, error) {
var ( var (
pgr itemPager pgr itemIDPager
resetDelta bool resetDelta bool
) )
@ -161,17 +209,16 @@ func getAddedAndRemovedItemIDs(
// generic controller for retrieving all item ids in a container. // generic controller for retrieving all item ids in a container.
func getItemsAddedAndRemovedFromContainer( func getItemsAddedAndRemovedFromContainer(
ctx context.Context, ctx context.Context,
pager itemPager, pager itemIDPager,
) ([]string, []string, string, error) { ) ([]string, []string, string, error) {
var ( var (
addedIDs = []string{} addedIDs = []string{}
removedIDs = []string{} removedIDs = []string{}
deltaURL string deltaURL string
itemCount int
page int
) )
itemCount := 0
page := 0
for { for {
// get the next page of data, check for standard errors // get the next page of data, check for standard errors
resp, err := pager.getPage(ctx) resp, err := pager.getPage(ctx)

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -19,6 +20,8 @@ import (
// mock impls & stubs // mock impls & stubs
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// next and delta links
type nextLink struct { type nextLink struct {
nextLink *string nextLink *string
} }
@ -36,6 +39,8 @@ func (l deltaNextLink) GetOdataDeltaLink() *string {
return l.deltaLink return l.deltaLink
} }
// mock values
type testPagerValue struct { type testPagerValue struct {
id string id string
removed bool removed bool
@ -50,6 +55,8 @@ func (v testPagerValue) GetAdditionalData() map[string]any {
return map[string]any{} return map[string]any{}
} }
// mock page
type testPage struct{} type testPage struct{}
func (p testPage) GetOdataNextLink() *string { func (p testPage) GetOdataNextLink() *string {
@ -62,9 +69,35 @@ func (p testPage) GetOdataDeltaLink() *string {
return ptr.To("") return ptr.To("")
} }
var _ itemPager = &testPager{} // mock item pager
var _ itemPager[any] = &testPager{}
type testPager struct { type testPager struct {
t *testing.T
items []any
pageErr error
valuesErr error
}
//lint:ignore U1000 False Positive
func (p *testPager) getPage(ctx context.Context) (PageLinker, error) {
return testPage{}, p.pageErr
}
//lint:ignore U1000 False Positive
func (p *testPager) setNext(nextLink string) {}
//lint:ignore U1000 False Positive
func (p *testPager) valuesIn(pl PageLinker) ([]any, error) {
return p.items, p.valuesErr
}
// mock id pager
var _ itemIDPager = &testIDsPager{}
type testIDsPager struct {
t *testing.T t *testing.T
added []string added []string
removed []string removed []string
@ -72,7 +105,7 @@ type testPager struct {
needsReset bool needsReset bool
} }
func (p *testPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *testIDsPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
if p.errorCode != "" { if p.errorCode != "" {
ierr := odataerrors.NewMainError() ierr := odataerrors.NewMainError()
ierr.SetCode(&p.errorCode) ierr.SetCode(&p.errorCode)
@ -85,8 +118,8 @@ func (p *testPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
return testPage{}, nil return testPage{}, nil
} }
func (p *testPager) setNext(string) {} func (p *testIDsPager) setNext(string) {}
func (p *testPager) reset(context.Context) { func (p *testIDsPager) reset(context.Context) {
if !p.needsReset { if !p.needsReset {
require.Fail(p.t, "reset should not be called") require.Fail(p.t, "reset should not be called")
} }
@ -95,7 +128,7 @@ func (p *testPager) reset(context.Context) {
p.errorCode = "" p.errorCode = ""
} }
func (p *testPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *testIDsPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
items := []getIDAndAddtler{} items := []getIDAndAddtler{}
for _, id := range p.added { for _, id := range p.added {
@ -121,11 +154,83 @@ func TestItemPagerUnitSuite(t *testing.T) {
suite.Run(t, &ItemPagerUnitSuite{Suite: tester.NewUnitSuite(t)}) suite.Run(t, &ItemPagerUnitSuite{Suite: tester.NewUnitSuite(t)})
} }
func (suite *ItemPagerUnitSuite) TestEnumerateItems() {
tests := []struct {
name string
getPager func(*testing.T, context.Context) itemPager[any]
expect []any
expectErr require.ErrorAssertionFunc
}{
{
name: "happy path",
getPager: func(
t *testing.T,
ctx context.Context,
) itemPager[any] {
return &testPager{
t: t,
items: []any{"foo", "bar"},
}
},
expect: []any{"foo", "bar"},
expectErr: require.NoError,
},
{
name: "get values err",
getPager: func(
t *testing.T,
ctx context.Context,
) itemPager[any] {
return &testPager{
t: t,
valuesErr: assert.AnError,
}
},
expect: nil,
expectErr: require.Error,
},
{
name: "next page err",
getPager: func(
t *testing.T,
ctx context.Context,
) itemPager[any] {
return &testPager{
t: t,
pageErr: assert.AnError,
}
},
expect: nil,
expectErr: require.Error,
},
}
for _, test := range tests {
suite.Run(test.name, func() {
t := suite.T()
ctx, flush := tester.NewContext(t)
defer flush()
result, err := enumerateItems[any](ctx, test.getPager(t, ctx))
test.expectErr(t, err, clues.ToCore(err))
require.EqualValues(t, test.expect, result)
})
}
}
func (suite *ItemPagerUnitSuite) TestGetAddedAndRemovedItemIDs() { func (suite *ItemPagerUnitSuite) TestGetAddedAndRemovedItemIDs() {
tests := []struct { tests := []struct {
name string name string
pagerGetter func(context.Context, graph.Servicer, string, string, bool) (itemPager, error) pagerGetter func(*testing.T, context.Context, graph.Servicer, string, string, bool) (itemIDPager, error)
deltaPagerGetter func(context.Context, graph.Servicer, string, string, string, bool) (itemPager, error) deltaPagerGetter func(
*testing.T,
context.Context,
graph.Servicer,
string, string, string,
bool,
) (itemIDPager, error)
added []string added []string
removed []string removed []string
deltaUpdate DeltaUpdate deltaUpdate DeltaUpdate
@ -135,25 +240,27 @@ func (suite *ItemPagerUnitSuite) TestGetAddedAndRemovedItemIDs() {
{ {
name: "no prev delta", name: "no prev delta",
pagerGetter: func( pagerGetter: func(
t *testing.T,
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user string,
directory string, directory string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
// this should not be called // this should not be called
return nil, assert.AnError return nil, assert.AnError
}, },
deltaPagerGetter: func( deltaPagerGetter: func(
t *testing.T,
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user string,
directory string, directory string,
delta string, delta string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
return &testPager{ return &testIDsPager{
t: suite.T(), t: t,
added: []string{"uno", "dos"}, added: []string{"uno", "dos"},
removed: []string{"tres", "quatro"}, removed: []string{"tres", "quatro"},
}, nil }, nil
@ -166,25 +273,27 @@ func (suite *ItemPagerUnitSuite) TestGetAddedAndRemovedItemIDs() {
{ {
name: "with prev delta", name: "with prev delta",
pagerGetter: func( pagerGetter: func(
t *testing.T,
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user string,
directory string, directory string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
// this should not be called // this should not be called
return nil, assert.AnError return nil, assert.AnError
}, },
deltaPagerGetter: func( deltaPagerGetter: func(
t *testing.T,
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user string,
directory string, directory string,
delta string, delta string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
return &testPager{ return &testIDsPager{
t: suite.T(), t: t,
added: []string{"uno", "dos"}, added: []string{"uno", "dos"},
removed: []string{"tres", "quatro"}, removed: []string{"tres", "quatro"},
}, nil }, nil
@ -198,25 +307,27 @@ func (suite *ItemPagerUnitSuite) TestGetAddedAndRemovedItemIDs() {
{ {
name: "delta expired", name: "delta expired",
pagerGetter: func( pagerGetter: func(
t *testing.T,
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user string,
directory string, directory string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
// this should not be called // this should not be called
return nil, assert.AnError return nil, assert.AnError
}, },
deltaPagerGetter: func( deltaPagerGetter: func(
t *testing.T,
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user string,
directory string, directory string,
delta string, delta string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
return &testPager{ return &testIDsPager{
t: suite.T(), t: t,
added: []string{"uno", "dos"}, added: []string{"uno", "dos"},
removed: []string{"tres", "quatro"}, removed: []string{"tres", "quatro"},
errorCode: "SyncStateNotFound", errorCode: "SyncStateNotFound",
@ -232,27 +343,29 @@ func (suite *ItemPagerUnitSuite) TestGetAddedAndRemovedItemIDs() {
{ {
name: "quota exceeded", name: "quota exceeded",
pagerGetter: func( pagerGetter: func(
t *testing.T,
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user string,
directory string, directory string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
return &testPager{ return &testIDsPager{
t: suite.T(), t: t,
added: []string{"uno", "dos"}, added: []string{"uno", "dos"},
removed: []string{"tres", "quatro"}, removed: []string{"tres", "quatro"},
}, nil }, nil
}, },
deltaPagerGetter: func( deltaPagerGetter: func(
t *testing.T,
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,
user string, user string,
directory string, directory string,
delta string, delta string,
immutableIDs bool, immutableIDs bool,
) (itemPager, error) { ) (itemIDPager, error) {
return &testPager{errorCode: "ErrorQuotaExceeded"}, nil return &testIDsPager{errorCode: "ErrorQuotaExceeded"}, nil
}, },
added: []string{"uno", "dos"}, added: []string{"uno", "dos"},
removed: []string{"tres", "quatro"}, removed: []string{"tres", "quatro"},
@ -268,8 +381,8 @@ func (suite *ItemPagerUnitSuite) TestGetAddedAndRemovedItemIDs() {
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
pager, _ := tt.pagerGetter(ctx, graph.Service{}, "user", "directory", false) pager, _ := tt.pagerGetter(t, ctx, graph.Service{}, "user", "directory", false)
deltaPager, _ := tt.deltaPagerGetter(ctx, graph.Service{}, "user", "directory", tt.delta, false) deltaPager, _ := tt.deltaPagerGetter(t, ctx, graph.Service{}, "user", "directory", tt.delta, false)
added, removed, deltaUpdate, err := getAddedAndRemovedItemIDs( added, removed, deltaUpdate, err := getAddedAndRemovedItemIDs(
ctx, ctx,

View File

@ -121,19 +121,51 @@ func (c Mail) EnumerateContainers(
// item pager // item pager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var _ itemPager = &mailPager{} var _ itemPager[models.Messageable] = &mailPager{}
type mailPager struct { type mailPager struct {
// TODO(rkeeprs)
}
func (c Mail) NewMailPager() itemPager[models.Messageable] {
// TODO(rkeepers)
return nil
}
//lint:ignore U1000 False Positive
func (p *mailPager) getPage(ctx context.Context) (PageLinker, error) {
// TODO(rkeepers)
return nil, nil
}
//lint:ignore U1000 False Positive
func (p *mailPager) setNext(nextLink string) {
// TODO(rkeepers)
}
//lint:ignore U1000 False Positive
func (p *mailPager) valuesIn(pl PageLinker) ([]models.Messageable, error) {
// TODO(rkeepers)
return nil, nil
}
// ---------------------------------------------------------------------------
// item ID pager
// ---------------------------------------------------------------------------
var _ itemIDPager = &mailIDPager{}
type mailIDPager struct {
gs graph.Servicer gs graph.Servicer
builder *users.ItemMailFoldersItemMessagesRequestBuilder builder *users.ItemMailFoldersItemMessagesRequestBuilder
options *users.ItemMailFoldersItemMessagesRequestBuilderGetRequestConfiguration options *users.ItemMailFoldersItemMessagesRequestBuilderGetRequestConfiguration
} }
func (c Mail) NewMailPager( func (c Mail) NewMailIDsPager(
ctx context.Context, ctx context.Context,
userID, containerID string, userID, containerID string,
immutableIDs bool, immutableIDs bool,
) itemPager { ) itemIDPager {
config := &users.ItemMailFoldersItemMessagesRequestBuilderGetRequestConfiguration{ config := &users.ItemMailFoldersItemMessagesRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemMailFoldersItemMessagesRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemMailFoldersItemMessagesRequestBuilderGetQueryParameters{
Select: idAnd("isRead"), Select: idAnd("isRead"),
@ -149,10 +181,10 @@ func (c Mail) NewMailPager(
ByMailFolderId(containerID). ByMailFolderId(containerID).
Messages() Messages()
return &mailPager{c.Stable, builder, config} return &mailIDPager{c.Stable, builder, config}
} }
func (p *mailPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *mailIDPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
page, err := p.builder.Get(ctx, p.options) page, err := p.builder.Get(ctx, p.options)
if err != nil { if err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
@ -161,24 +193,24 @@ func (p *mailPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
return EmptyDeltaLinker[models.Messageable]{PageLinkValuer: page}, nil return EmptyDeltaLinker[models.Messageable]{PageLinkValuer: page}, nil
} }
func (p *mailPager) setNext(nextLink string) { func (p *mailIDPager) setNext(nextLink string) {
p.builder = users.NewItemMailFoldersItemMessagesRequestBuilder(nextLink, p.gs.Adapter()) p.builder = users.NewItemMailFoldersItemMessagesRequestBuilder(nextLink, p.gs.Adapter())
} }
// non delta pagers don't have reset // non delta pagers don't have reset
func (p *mailPager) reset(context.Context) {} func (p *mailIDPager) reset(context.Context) {}
func (p *mailPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *mailIDPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
return toValues[models.Messageable](pl) return toValues[models.Messageable](pl)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// delta item pager // delta item ID pager
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var _ itemPager = &mailDeltaPager{} var _ itemIDPager = &mailDeltaIDPager{}
type mailDeltaPager struct { type mailDeltaIDPager struct {
gs graph.Servicer gs graph.Servicer
userID string userID string
containerID string containerID string
@ -204,11 +236,11 @@ func getMailDeltaBuilder(
return builder return builder
} }
func (c Mail) NewMailDeltaPager( func (c Mail) NewMailDeltaIDsPager(
ctx context.Context, ctx context.Context,
userID, containerID, oldDelta string, userID, containerID, oldDelta string,
immutableIDs bool, immutableIDs bool,
) itemPager { ) itemIDPager {
config := &users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetRequestConfiguration{ config := &users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetRequestConfiguration{
QueryParameters: &users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetQueryParameters{ QueryParameters: &users.ItemMailFoldersItemMessagesDeltaRequestBuilderGetQueryParameters{
Select: idAnd("isRead"), Select: idAnd("isRead"),
@ -224,10 +256,10 @@ func (c Mail) NewMailDeltaPager(
builder = getMailDeltaBuilder(ctx, c.Stable, userID, containerID, config) builder = getMailDeltaBuilder(ctx, c.Stable, userID, containerID, config)
} }
return &mailDeltaPager{c.Stable, userID, containerID, builder, config} return &mailDeltaIDPager{c.Stable, userID, containerID, builder, config}
} }
func (p *mailDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error) { func (p *mailDeltaIDPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
page, err := p.builder.Get(ctx, p.options) page, err := p.builder.Get(ctx, p.options)
if err != nil { if err != nil {
return nil, graph.Stack(ctx, err) return nil, graph.Stack(ctx, err)
@ -236,11 +268,11 @@ func (p *mailDeltaPager) getPage(ctx context.Context) (DeltaPageLinker, error) {
return page, nil return page, nil
} }
func (p *mailDeltaPager) setNext(nextLink string) { func (p *mailDeltaIDPager) setNext(nextLink string) {
p.builder = users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(nextLink, p.gs.Adapter()) p.builder = users.NewItemMailFoldersItemMessagesDeltaRequestBuilder(nextLink, p.gs.Adapter())
} }
func (p *mailDeltaPager) reset(ctx context.Context) { func (p *mailDeltaIDPager) reset(ctx context.Context) {
p.builder = p.gs. p.builder = p.gs.
Client(). Client().
Users(). Users().
@ -251,7 +283,7 @@ func (p *mailDeltaPager) reset(ctx context.Context) {
Delta() Delta()
} }
func (p *mailDeltaPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) { func (p *mailDeltaIDPager) valuesIn(pl PageLinker) ([]getIDAndAddtler, error) {
return toValues[models.Messageable](pl) return toValues[models.Messageable](pl)
} }
@ -266,8 +298,8 @@ func (c Mail) GetAddedAndRemovedItemIDs(
"category", selectors.ExchangeMail, "category", selectors.ExchangeMail,
"container_id", containerID) "container_id", containerID)
pager := c.NewMailPager(ctx, userID, containerID, immutableIDs) pager := c.NewMailIDsPager(ctx, userID, containerID, immutableIDs)
deltaPager := c.NewMailDeltaPager(ctx, userID, containerID, oldDelta, immutableIDs) deltaPager := c.NewMailDeltaIDsPager(ctx, userID, containerID, oldDelta, immutableIDs)
return getAddedAndRemovedItemIDs(ctx, c.Stable, pager, deltaPager, oldDelta, canMakeDeltaQueries) return getAddedAndRemovedItemIDs(ctx, c.Stable, pager, deltaPager, oldDelta, canMakeDeltaQueries)
} }