vault/api/sys_billing_test.go
Vault Automation be244e8702
VAULT-42829: Create a new billing/config endpoint to set retention months (#14785) (#14945)
* spike

* seperate overview and config into 2 paths, enable overview in admin too, restrict config to root

* warn on error, do not log msg

* rename default const

* change storage approach to string-based pattern

* rename method

* fmt

* linters

* add all response types for the path

* use get from sdk

* add api client methods and tests

* update unit test

* add cleanup and deletion tests

* fix comments, add custom retention test wwith start and end months

* add namespace tests

* consolidate validation into one check

* fix comments

* add changelog

* fix test

* fix subpath for config

* add and use a new lock for get and update methods

* add a warning for when retention is increased

* Update vault/consumption_billing_test.go



* remove redundant TestSystemBackend_BillingConfig_Persistence test

* integrate warnings testing into another test and remove redundant test

---------

Co-authored-by: Amir Aslamov <amir.aslamov@hashicorp.com>
Co-authored-by: Jenny Deng <jenny.deng@hashicorp.com>
2026-05-21 19:14:50 +00:00

295 lines
7.8 KiB
Go

// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: MPL-2.0
package api
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
// TestSys_BillingOverview tests the BillingOverview API client method and structure of the response
func TestSys_BillingOverview(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultBillingHandler))
defer mockVaultServer.Close()
// Create API client pointing to mock server
cfg := DefaultConfig()
cfg.Address = mockVaultServer.URL
client, err := NewClient(cfg)
require.NoError(t, err)
resp, err := client.Sys().BillingOverview(false)
require.NoError(t, err)
require.NotNil(t, resp)
// Verify we have 2 months (current and previous)
require.Len(t, resp.Months, 2)
// Verify current month structure
currentMonth := resp.Months[0]
require.Equal(t, "2026-01", currentMonth.Month)
require.Equal(t, "2026-01-14T10:49:00Z", currentMonth.UpdatedAt)
require.Len(t, currentMonth.UsageMetrics, 9, "should have all 9 metrics")
// Create a map to verify all expected metrics are present
metricsMap := make(map[string]UsageMetric)
for _, metric := range currentMonth.UsageMetrics {
metricsMap[metric.MetricName] = metric
}
// Verify all expected metrics are present
expectedMetrics := []string{
"static_secrets",
"dynamic_roles",
"auto_rotated_roles",
"kmip",
"external_plugins",
"data_protection_calls",
"pki_units",
"managed_keys",
"ssh_units",
}
for _, metricName := range expectedMetrics {
metric, exists := metricsMap[metricName]
require.True(t, exists, "metric %s should be present", metricName)
require.NotNil(t, metric.MetricData, "metric_data should not be nil for %s", metricName)
}
// Verify specific metric structures
staticSecretsMetric := metricsMap["static_secrets"]
require.Contains(t, staticSecretsMetric.MetricData, "total")
require.Contains(t, staticSecretsMetric.MetricData, "metric_details")
kmipMetric := metricsMap["kmip"]
require.Contains(t, kmipMetric.MetricData, "used_in_month")
require.Equal(t, true, kmipMetric.MetricData["used_in_month"])
pkiMetric := metricsMap["pki_units"]
require.Contains(t, pkiMetric.MetricData, "total")
managedKeysMetric := metricsMap["managed_keys"]
require.Contains(t, managedKeysMetric.MetricData, "total")
require.Contains(t, managedKeysMetric.MetricData, "metric_details")
// Verify previous month structure
previousMonth := resp.Months[1]
require.Equal(t, "2025-12", previousMonth.Month)
require.Equal(t, "2025-12-31T23:59:59Z", previousMonth.UpdatedAt)
require.Len(t, previousMonth.UsageMetrics, 1)
// Verify external_plugins metric in previous month
externalPluginsMetric := previousMonth.UsageMetrics[0]
require.Equal(t, "external_plugins", externalPluginsMetric.MetricName)
require.NotNil(t, externalPluginsMetric.MetricData)
require.Contains(t, externalPluginsMetric.MetricData, "total")
sshMetric := metricsMap["ssh_units"]
require.Contains(t, sshMetric.MetricData, "total")
require.Contains(t, sshMetric.MetricData, "metric_details")
}
func mockVaultBillingHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(billingOverviewResponse))
}
const billingOverviewResponse = `{
"request_id": "d8d3e6e1-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"months": [
{
"month": "2026-01",
"updated_at": "2026-01-14T10:49:00Z",
"usage_metrics": [
{
"metric_name": "static_secrets",
"metric_data": {
"total": 10,
"metric_details": [
{
"type": "kv",
"count": 10
}
]
}
},
{
"metric_name": "dynamic_roles",
"metric_data": {
"total": 15,
"metric_details": [
{
"type": "aws_dynamic",
"count": 5
},
{
"type": "azure_dynamic",
"count": 5
},
{
"type": "database_dynamic",
"count": 5
}
]
}
},
{
"metric_name": "auto_rotated_roles",
"metric_data": {
"total": 15,
"metric_details": [
{
"type": "aws_static",
"count": 5
},
{
"type": "azure_static",
"count": 5
},
{
"type": "os_local_account_static",
"count": 5
}
]
}
},
{
"metric_name": "kmip",
"metric_data": {
"used_in_month": true
}
},
{
"metric_name": "external_plugins",
"metric_data": {
"total": 3
}
},
{
"metric_name": "data_protection_calls",
"metric_data": {
"total": 100,
"metric_details": [
{
"type": "transit",
"count": 50
},
{
"type": "transform",
"count": 50
},
{
"type": "gcpkms",
"count": 50
}
]
}
},
{
"metric_name": "pki_units",
"metric_data": {
"total": 100.5
}
},
{
"metric_name": "managed_keys",
"metric_data": {
"total": 10,
"metric_details": [
{
"type": "totp",
"count": 5
},
{
"type": "kmse",
"count": 5
}
]
}
},
{
"metric_name": "ssh_units",
"metric_data": {
"total": 8.4,
"metric_details": [
{
"type": "otp_units",
"count": 5
},
{
"type": "certificate_units",
"count": 3.4
}
]
}
}
]
},
{
"month": "2025-12",
"updated_at": "2025-12-31T23:59:59Z",
"usage_metrics": [
{
"metric_name": "external_plugins",
"metric_data": {
"total": 5
}
}
]
}
]
},
"wrap_info": null,
"warnings": null,
"auth": null
}`
// TestSys_BillingConfig tests the GetBillingConfig and SetBillingConfig API client methods
func TestSys_BillingConfig(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultBillingConfigHandler))
defer mockVaultServer.Close()
// Create API client pointing to mock server
cfg := DefaultConfig()
cfg.Address = mockVaultServer.URL
client, err := NewClient(cfg)
require.NoError(t, err)
// Test GetBillingConfig
resp, err := client.Sys().GetBillingConfig()
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, 37, resp.RetentionMonths)
// Test SetBillingConfig
err = client.Sys().SetBillingConfig(48)
require.NoError(t, err)
}
func mockVaultBillingConfigHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
_, _ = w.Write([]byte(billingConfigResponse))
} else if r.Method == http.MethodPost {
w.WriteHeader(http.StatusNoContent)
}
}
const billingConfigResponse = `{
"request_id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"retention_months": 37
},
"wrap_info": null,
"warnings": null,
"auth": null
}`