pricesheetclient_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package azure
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "github.com/Azure/azure-sdk-for-go/sdk/azcore"
  7. "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. )
  11. // mockCredential implements azcore.TokenCredential for testing
  12. type mockCredential struct{}
  13. func (m *mockCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
  14. return azcore.AccessToken{
  15. Token: "mock-token",
  16. ExpiresOn: time.Now().Add(time.Hour),
  17. }, nil
  18. }
  19. // TestPriceSheetClient_CreateRequest tests the core HTTP method fix
  20. // This test directly validates the createRequest method to ensure POST is used
  21. func TestPriceSheetClient_CreateRequest_HTTPMethod(t *testing.T) {
  22. tests := []struct {
  23. name string
  24. billingAccountID string
  25. billingPeriodName string
  26. expectedMethod string
  27. expectedPath string
  28. }{
  29. {
  30. name: "POST method for valid billing account",
  31. billingAccountID: "test-billing-account",
  32. billingPeriodName: "202308",
  33. expectedMethod: "POST",
  34. expectedPath: "/providers/Microsoft.Billing/billingAccounts/test-billing-account/billingPeriods/202308/providers/Microsoft.Consumption/pricesheets/download",
  35. },
  36. {
  37. name: "POST method for different billing period",
  38. billingAccountID: "another-account",
  39. billingPeriodName: "202401",
  40. expectedMethod: "POST",
  41. expectedPath: "/providers/Microsoft.Billing/billingAccounts/another-account/billingPeriods/202401/providers/Microsoft.Consumption/pricesheets/download",
  42. },
  43. }
  44. for _, tt := range tests {
  45. t.Run(tt.name, func(t *testing.T) {
  46. cred := &mockCredential{}
  47. client, err := NewPriceSheetClient(tt.billingAccountID, cred, nil)
  48. require.NoError(t, err)
  49. // Test the createRequest method directly
  50. req, err := client.downloadByBillingPeriodCreateRequest(context.Background(), tt.billingPeriodName)
  51. require.NoError(t, err)
  52. // Validate HTTP method - this is the core fix validation
  53. assert.Equal(t, tt.expectedMethod, req.Raw().Method, "HTTP method must be POST, not GET (fix for Azure billing API issue #3326)")
  54. // Validate URL path construction
  55. assert.Contains(t, req.Raw().URL.Path, tt.expectedPath)
  56. // Validate query parameters
  57. assert.Equal(t, "2022-06-01", req.Raw().URL.Query().Get("api-version"))
  58. assert.Equal(t, "en", req.Raw().URL.Query().Get("ln"))
  59. // Validate Accept header
  60. assert.Equal(t, []string{"*/*"}, req.Raw().Header["Accept"])
  61. })
  62. }
  63. }
  64. // TestPriceSheetClient_Validation tests parameter validation
  65. func TestPriceSheetClient_Validation(t *testing.T) {
  66. cred := &mockCredential{}
  67. tests := []struct {
  68. name string
  69. billingAccountID string
  70. billingPeriodName string
  71. expectError bool
  72. expectedError string
  73. }{
  74. {
  75. name: "empty billing account ID",
  76. billingAccountID: "",
  77. billingPeriodName: "202308",
  78. expectError: true,
  79. expectedError: "parameter client.billingAccountID cannot be empty",
  80. },
  81. {
  82. name: "empty billing period name",
  83. billingAccountID: "test-account",
  84. billingPeriodName: "",
  85. expectError: true,
  86. expectedError: "parameter billingPeriodName cannot be empty",
  87. },
  88. {
  89. name: "valid parameters",
  90. billingAccountID: "test-account",
  91. billingPeriodName: "202308",
  92. expectError: false,
  93. },
  94. }
  95. for _, tt := range tests {
  96. t.Run(tt.name, func(t *testing.T) {
  97. client, err := NewPriceSheetClient(tt.billingAccountID, cred, nil)
  98. require.NoError(t, err)
  99. // Test the createRequest method directly for validation
  100. _, err = client.downloadByBillingPeriodCreateRequest(context.Background(), tt.billingPeriodName)
  101. if tt.expectError {
  102. assert.Error(t, err)
  103. assert.Contains(t, err.Error(), tt.expectedError)
  104. } else {
  105. assert.NoError(t, err)
  106. }
  107. })
  108. }
  109. }
  110. // TestPriceSheetClient_URLConstruction tests URL path construction
  111. func TestPriceSheetClient_URLConstruction(t *testing.T) {
  112. tests := []struct {
  113. name string
  114. billingAccountID string
  115. billingPeriodName string
  116. expectedPath string
  117. }{
  118. {
  119. name: "standard billing account and period",
  120. billingAccountID: "123456789",
  121. billingPeriodName: "202308",
  122. expectedPath: "/providers/Microsoft.Billing/billingAccounts/123456789/billingPeriods/202308/providers/Microsoft.Consumption/pricesheets/download",
  123. },
  124. {
  125. name: "billing account with dashes",
  126. billingAccountID: "account-with-dashes",
  127. billingPeriodName: "202308",
  128. expectedPath: "/providers/Microsoft.Billing/billingAccounts/account-with-dashes/billingPeriods/202308/providers/Microsoft.Consumption/pricesheets/download",
  129. },
  130. }
  131. for _, tt := range tests {
  132. t.Run(tt.name, func(t *testing.T) {
  133. cred := &mockCredential{}
  134. client, err := NewPriceSheetClient(tt.billingAccountID, cred, nil)
  135. require.NoError(t, err)
  136. req, err := client.downloadByBillingPeriodCreateRequest(context.Background(), tt.billingPeriodName)
  137. require.NoError(t, err)
  138. // Verify URL path construction
  139. assert.Contains(t, req.Raw().URL.Path, tt.expectedPath)
  140. })
  141. }
  142. }
  143. // TestPriceSheetClient_MethodRegression ensures the HTTP method fix doesn't regress
  144. // This test would fail if someone accidentally changed POST back to GET
  145. func TestPriceSheetClient_MethodRegression(t *testing.T) {
  146. // This test specifically prevents regression of issue #3326
  147. // where Azure billing API was incorrectly using GET instead of POST
  148. cred := &mockCredential{}
  149. client, err := NewPriceSheetClient("test-billing-account", cred, nil)
  150. require.NoError(t, err)
  151. req, err := client.downloadByBillingPeriodCreateRequest(context.Background(), "202308")
  152. require.NoError(t, err)
  153. // Critical assertion: must be POST, never GET
  154. // This is the core fix for Azure billing account pricing API
  155. assert.Equal(t, "POST", req.Raw().Method, "REGRESSION: HTTP method changed back to GET - this will cause 404 errors with Azure billing API")
  156. assert.NotEqual(t, "GET", req.Raw().Method, "HTTP method must not be GET for Azure pricesheet download endpoint")
  157. }