diff --git a/providers/azure/services/cache/pagination_cap_test.go b/providers/azure/services/cache/pagination_cap_test.go index 6e1d671f..c09ec02b 100644 --- a/providers/azure/services/cache/pagination_cap_test.go +++ b/providers/azure/services/cache/pagination_cap_test.go @@ -39,3 +39,36 @@ func TestFetchSKUCatalogue_CancelledContextStopsWalk(t *testing.T) { assert.Nil(t, client.fetchSKUCatalogue(ctx)) assert.Zero(t, pager.calls) } + +// finalPartialRedisPager serves exactly pages pages: each full page holds +// perPage distinct SKUs, the last holds a single one with no next link. +type finalPartialRedisPager struct { + calls, pages, perPage int +} + +func (p *finalPartialRedisPager) More() bool { return p.calls < p.pages } +func (p *finalPartialRedisPager) NextPage(_ context.Context) (armredis.ClientListBySubscriptionResponse, error) { + n := p.perPage + if p.calls == p.pages-1 { + n = 1 + } + var caches []*armredis.ResourceInfo + for i := 0; i < n; i++ { + name, family, capacity := armredis.SKUNamePremium, armredis.SKUFamilyP, int32(p.calls*p.perPage+i) + caches = append(caches, &armredis.ResourceInfo{Properties: &armredis.Properties{ + SKU: &armredis.SKU{Name: &name, Family: &family, Capacity: &capacity}, + }}) + } + p.calls++ + return armredis.ClientListBySubscriptionResponse{ListResult: armredis.ListResult{Value: caches}}, nil +} + +func TestFetchSKUCatalogue_ExactlyCapPagesWithPartialLastPageSucceeds(t *testing.T) { + pager := &finalPartialRedisPager{pages: maxCachesPages, perPage: 2} + client := NewClient(nil, "test-subscription", "eastus") + client.SetRedisCachesPager(pager) + + got := client.fetchSKUCatalogue(context.Background()) + assert.Len(t, got, 2*(maxCachesPages-1)+1) + assert.Equal(t, maxCachesPages, pager.calls) +} diff --git a/providers/azure/services/cosmosdb/pagination_cap_test.go b/providers/azure/services/cosmosdb/pagination_cap_test.go index 322a5aba..33bfa278 100644 --- a/providers/azure/services/cosmosdb/pagination_cap_test.go +++ b/providers/azure/services/cosmosdb/pagination_cap_test.go @@ -39,3 +39,32 @@ func TestFetchDominantAPIType_CancelledContextStopsWalk(t *testing.T) { assert.Equal(t, "", client.fetchDominantAPIType(ctx)) assert.Zero(t, pager.calls) } + +// finalPartialAccountsPager serves exactly pages pages and only the last, +// partial one carries an account, so the result proves the final page was read. +type finalPartialAccountsPager struct { + calls, pages int +} + +func (p *finalPartialAccountsPager) More() bool { return p.calls < p.pages } +func (p *finalPartialAccountsPager) NextPage(_ context.Context) (armcosmos.DatabaseAccountsClientListResponse, error) { + p.calls++ + if p.calls < p.pages { + return armcosmos.DatabaseAccountsClientListResponse{}, nil + } + kind := armcosmos.DatabaseAccountKindMongoDB + return armcosmos.DatabaseAccountsClientListResponse{ + DatabaseAccountsListResult: armcosmos.DatabaseAccountsListResult{ + Value: []*armcosmos.DatabaseAccountGetResults{{Kind: &kind}}, + }, + }, nil +} + +func TestFetchDominantAPIType_ExactlyCapPagesWithPartialLastPageSucceeds(t *testing.T) { + pager := &finalPartialAccountsPager{pages: maxAccountsPages} + client := NewClient(nil, "test-subscription", "eastus") + client.SetCosmosAccountsPager(pager) + + assert.Equal(t, "mongodb", client.fetchDominantAPIType(context.Background())) + assert.Equal(t, maxAccountsPages, pager.calls) +} diff --git a/providers/azure/services/database/pagination_cap_test.go b/providers/azure/services/database/pagination_cap_test.go index d1bef43b..1bf1840a 100644 --- a/providers/azure/services/database/pagination_cap_test.go +++ b/providers/azure/services/database/pagination_cap_test.go @@ -114,3 +114,57 @@ func TestFetchServerInfo_CancelAfterManagedWalkLeavesSignalsEmpty(t *testing.T) assert.Empty(t, azConfig) assert.Empty(t, deployment) } + +// finalPartialMIPager serves exactly pages pages: full pages of two +// instances, then a last page of one with no next link. +type finalPartialMIPager struct { + calls, pages int +} + +func (p *finalPartialMIPager) More() bool { return p.calls < p.pages } +func (p *finalPartialMIPager) NextPage(_ context.Context) (armsql.ManagedInstancesClientListResponse, error) { + p.calls++ + if p.calls == p.pages { + return buildMIPage(true), nil + } + return buildMIPage(true, false), nil +} + +// finalPartialServersPager serves exactly pages pages and only the last, +// partial one lists a server. +type finalPartialServersPager struct { + calls, pages int +} + +func (p *finalPartialServersPager) More() bool { return p.calls < p.pages } +func (p *finalPartialServersPager) NextPage(_ context.Context) (armsql.ServersClientListResponse, error) { + p.calls++ + if p.calls == p.pages { + return buildServerPage(1), nil + } + return armsql.ServersClientListResponse{}, nil +} + +func TestWalkManagedInstances_ExactlyCapPagesWithPartialLastPageSucceeds(t *testing.T) { + pager := &finalPartialMIPager{pages: maxSQLListPages} + client := NewClient(nil, "test-subscription", "eastus") + client.SetManagedInstancesPager(pager) + + zr, nzr, total, complete := client.walkManagedInstances(context.Background()) + assert.True(t, complete) + assert.Equal(t, maxSQLListPages, zr) + assert.Equal(t, maxSQLListPages-1, nzr) + assert.Equal(t, 2*(maxSQLListPages-1)+1, total) + assert.Equal(t, maxSQLListPages, pager.calls) +} + +func TestHasRegularServers_ExactlyCapPagesWithPartialLastPageSucceeds(t *testing.T) { + pager := &finalPartialServersPager{pages: maxSQLListPages} + client := NewClient(nil, "test-subscription", "eastus") + client.SetServersPager(pager) + + found, complete := client.hasRegularServers(context.Background()) + assert.True(t, found) + assert.True(t, complete) + assert.Equal(t, maxSQLListPages, pager.calls) +} diff --git a/providers/azure/services/synapse/pagination_cap_test.go b/providers/azure/services/synapse/pagination_cap_test.go index 5c55659f..e6cb8635 100644 --- a/providers/azure/services/synapse/pagination_cap_test.go +++ b/providers/azure/services/synapse/pagination_cap_test.go @@ -2,9 +2,12 @@ package synapse import ( "context" + "fmt" "testing" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/consumption/armconsumption" + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/providers/azure/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -76,3 +79,78 @@ func TestGetExistingCommitments_CancelledContextReturnsContextError(t *testing.T require.ErrorIs(t, err, context.Canceled) assert.Zero(t, pager.calls) } + +// finalPartialRecommendationsPager serves exactly pages pages: full pages of +// two recommendations, then a last page of one with no next link. +type finalPartialRecommendationsPager struct { + calls, pages int +} + +func (p *finalPartialRecommendationsPager) More() bool { return p.calls < p.pages } +func (p *finalPartialRecommendationsPager) NextPage(_ context.Context) (armconsumption.ReservationRecommendationsClientListResponse, error) { + p.calls++ + n := 2 + if p.calls == p.pages { + n = 1 + } + var recs []armconsumption.ReservationRecommendationClassification + for i := 0; i < n; i++ { + recs = append(recs, mocks.BuildLegacyReservationRecommendation( + mocks.WithRegion("eastus"), + mocks.WithNormalizedSize("DW1000c"), + mocks.WithQuantity(1), + mocks.WithCosts(5000.0, 3500.0, 1500.0), + )) + } + return armconsumption.ReservationRecommendationsClientListResponse{ + ReservationRecommendationsListResult: armconsumption.ReservationRecommendationsListResult{Value: recs}, + }, nil +} + +// finalPartialReservationsPager serves exactly pages pages: full pages of two +// reservations, then a last page of one with no next link. +type finalPartialReservationsPager struct { + calls, pages int +} + +func (p *finalPartialReservationsPager) More() bool { return p.calls < p.pages } +func (p *finalPartialReservationsPager) NextPage(_ context.Context) (armconsumption.ReservationsDetailsClientListResponse, error) { + n := 2 + if p.calls == p.pages-1 { + n = 1 + } + var details []*armconsumption.ReservationDetail + for i := 0; i < n; i++ { + sku, id := "DW1000c", fmt.Sprintf("res-%d-%d", p.calls, i) + details = append(details, &armconsumption.ReservationDetail{ + Properties: &armconsumption.ReservationDetailProperties{SKUName: &sku, ReservationID: &id}, + }) + } + p.calls++ + return armconsumption.ReservationsDetailsClientListResponse{ + ReservationDetailsListResult: armconsumption.ReservationDetailsListResult{Value: details}, + }, nil +} + +func TestGetRecommendations_ExactlyCapPagesWithPartialLastPageSucceeds(t *testing.T) { + pager := &finalPartialRecommendationsPager{pages: maxRecsPages} + client := newPricedTestClient() + client.SetRecommendationsPager(pager) + + recs, err := client.GetRecommendations(context.Background(), &common.RecommendationParams{}) + require.NoError(t, err) + // Each recommendation expands to an upfront and a monthly variant. + assert.Len(t, recs, 2*(2*(maxRecsPages-1)+1)) + assert.Equal(t, maxRecsPages, pager.calls) +} + +func TestGetExistingCommitments_ExactlyCapPagesWithPartialLastPageSucceeds(t *testing.T) { + pager := &finalPartialReservationsPager{pages: maxReservationsPages} + client := NewClient(nil, "test-subscription", "eastus") + client.SetReservationsPager(pager) + + got, err := client.GetExistingCommitments(context.Background()) + require.NoError(t, err) + assert.Len(t, got, 2*(maxReservationsPages-1)+1) + assert.Equal(t, maxReservationsPages, pager.calls) +}