From c562ecc4bbc3d9b350b322c97ce764f7d11ef08f Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sat, 17 Oct 2026 05:02:46 +0200 Subject: [PATCH] test(azure): pin exactly-cap pagination success in the cache, cosmosdb, database and synapse pagers Each capped loop now has a case where the pager serves exactly the cap number of pages, the last one partial with no next link. The case asserts success, the full item count and the exact page-call count, so an off-by-one that rejects the final allowed page fails. Refs #249 (item 1, the cache catalogue degrade-vs-error decision, remains) Co-Authored-By: Claude Opus 5.5 --- .../services/cache/pagination_cap_test.go | 33 ++++++++ .../services/cosmosdb/pagination_cap_test.go | 29 +++++++ .../services/database/pagination_cap_test.go | 54 +++++++++++++ .../services/synapse/pagination_cap_test.go | 78 +++++++++++++++++++ 4 files changed, 194 insertions(+) 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) +}