Pulse/pkg/licensing/billing_state_normalization_test.go
2026-03-18 16:06:30 +00:00

348 lines
12 KiB
Go

package licensing
import "testing"
func TestDefaultBillingState(t *testing.T) {
state := DefaultBillingState()
if state == nil {
t.Fatal("expected non-nil default billing state")
}
if state.SubscriptionState != SubStateTrial {
t.Fatalf("subscription_state=%q, want %q", state.SubscriptionState, SubStateTrial)
}
if state.PlanVersion != string(SubStateTrial) {
t.Fatalf("plan_version=%q, want %q", state.PlanVersion, string(SubStateTrial))
}
if state.Capabilities == nil || len(state.Capabilities) != 0 {
t.Fatalf("expected empty capabilities slice, got %#v", state.Capabilities)
}
if state.MetersEnabled == nil || len(state.MetersEnabled) != 0 {
t.Fatalf("expected empty meters_enabled slice, got %#v", state.MetersEnabled)
}
if state.Limits == nil || len(state.Limits) != 0 {
t.Fatalf("expected empty limits map, got %#v", state.Limits)
}
}
func TestNormalizeBillingState(t *testing.T) {
trialStarted := int64(100)
trialEnds := int64(200)
input := &BillingState{
Capabilities: []string{" a ", "b"},
Limits: map[string]int64{"max_monitored_systems": 10},
MetersEnabled: []string{"meter_a"},
PlanVersion: " ",
SubscriptionState: SubscriptionState(" ACTIVE "),
TrialStartedAt: &trialStarted,
TrialEndsAt: &trialEnds,
StripeCustomerID: " cus_123 ",
}
normalized := NormalizeBillingState(input)
if normalized.SubscriptionState != SubStateActive {
t.Fatalf("subscription_state=%q, want %q", normalized.SubscriptionState, SubStateActive)
}
if normalized.PlanVersion != "" {
t.Fatalf("plan_version=%q, want empty", normalized.PlanVersion)
}
if normalized.StripeCustomerID != "cus_123" {
t.Fatalf("stripe_customer_id=%q, want %q", normalized.StripeCustomerID, "cus_123")
}
if normalized.Limits["max_monitored_systems"] != 10 {
t.Fatalf("limits[max_nodes]=%d, want 10", normalized.Limits["max_monitored_systems"])
}
input.Capabilities[0] = "changed"
input.MetersEnabled[0] = "changed"
input.Limits["max_monitored_systems"] = 99
if normalized.Capabilities[0] != " a " {
t.Fatalf("expected capabilities to be copied")
}
if normalized.MetersEnabled[0] != "meter_a" {
t.Fatalf("expected meters_enabled to be copied")
}
if normalized.Limits["max_monitored_systems"] != 10 {
t.Fatalf("expected limits map to be copied")
}
}
func TestNormalizeBillingStateNil(t *testing.T) {
normalized := NormalizeBillingState(nil)
if normalized.SubscriptionState != SubStateTrial {
t.Fatalf("subscription_state=%q, want %q", normalized.SubscriptionState, SubStateTrial)
}
}
func TestNormalizeBillingState_PreservesAllFields(t *testing.T) {
trialStarted := int64(100)
trialEnds := int64(200)
trialExtended := int64(150)
input := &BillingState{
Capabilities: []string{"relay", "ai"},
Limits: map[string]int64{"max_monitored_systems": 50},
MetersEnabled: []string{"active_agents"},
PlanVersion: "pro-v2",
SubscriptionState: SubStateActive,
TrialStartedAt: &trialStarted,
TrialEndsAt: &trialEnds,
TrialExtendedAt: &trialExtended,
Integrity: "deadbeef",
StripeCustomerID: "cus_123",
StripeSubscriptionID: "sub_456",
StripePriceID: "price_789",
}
normalized := NormalizeBillingState(input)
// Verify every field survives normalization.
if len(normalized.Capabilities) != 2 {
t.Fatalf("capabilities: got %v", normalized.Capabilities)
}
if normalized.Limits["max_monitored_systems"] != 50 {
t.Fatalf("limits[max_nodes]: got %d", normalized.Limits["max_monitored_systems"])
}
if normalized.MetersEnabled[0] != "active_agents" {
t.Fatalf("meters_enabled: got %v", normalized.MetersEnabled)
}
if normalized.PlanVersion != "pro-v2" {
t.Fatalf("plan_version: got %q", normalized.PlanVersion)
}
if normalized.SubscriptionState != SubStateActive {
t.Fatalf("subscription_state: got %q", normalized.SubscriptionState)
}
if normalized.TrialStartedAt == nil || *normalized.TrialStartedAt != 100 {
t.Fatalf("trial_started_at: got %v", normalized.TrialStartedAt)
}
if normalized.TrialEndsAt == nil || *normalized.TrialEndsAt != 200 {
t.Fatalf("trial_ends_at: got %v", normalized.TrialEndsAt)
}
if normalized.TrialExtendedAt == nil || *normalized.TrialExtendedAt != 150 {
t.Fatalf("trial_extended_at: got %v", normalized.TrialExtendedAt)
}
if normalized.Integrity != "deadbeef" {
t.Fatalf("integrity: got %q", normalized.Integrity)
}
if normalized.StripeCustomerID != "cus_123" {
t.Fatalf("stripe_customer_id: got %q", normalized.StripeCustomerID)
}
if normalized.StripeSubscriptionID != "sub_456" {
t.Fatalf("stripe_subscription_id: got %q", normalized.StripeSubscriptionID)
}
if normalized.StripePriceID != "price_789" {
t.Fatalf("stripe_price_id: got %q", normalized.StripePriceID)
}
// Verify pointer fields are deep-copied (not aliased).
*input.TrialStartedAt = 999
if *normalized.TrialStartedAt != 100 {
t.Fatal("trial_started_at was aliased, not cloned")
}
*input.TrialExtendedAt = 999
if *normalized.TrialExtendedAt != 150 {
t.Fatal("trial_extended_at was aliased, not cloned")
}
}
func TestCloneBillingState_PreservesAllFields(t *testing.T) {
trialStarted := int64(100)
trialEnds := int64(200)
trialExtended := int64(150)
input := BillingState{
Capabilities: []string{"relay", "ai"},
Limits: map[string]int64{"max_monitored_systems": 50},
MetersEnabled: []string{"active_agents"},
PlanVersion: "pro-v2",
SubscriptionState: SubStateActive,
TrialStartedAt: &trialStarted,
TrialEndsAt: &trialEnds,
TrialExtendedAt: &trialExtended,
Integrity: "deadbeef",
StripeCustomerID: "cus_123",
StripeSubscriptionID: "sub_456",
StripePriceID: "price_789",
}
cloned := cloneBillingState(input)
if cloned.Integrity != "deadbeef" {
t.Fatalf("integrity: got %q", cloned.Integrity)
}
if cloned.StripeCustomerID != "cus_123" {
t.Fatalf("stripe_customer_id: got %q", cloned.StripeCustomerID)
}
if cloned.StripeSubscriptionID != "sub_456" {
t.Fatalf("stripe_subscription_id: got %q", cloned.StripeSubscriptionID)
}
if cloned.StripePriceID != "price_789" {
t.Fatalf("stripe_price_id: got %q", cloned.StripePriceID)
}
if cloned.TrialExtendedAt == nil || *cloned.TrialExtendedAt != 150 {
t.Fatalf("trial_extended_at: got %v", cloned.TrialExtendedAt)
}
// Verify deep copy.
*input.TrialExtendedAt = 999
input.Capabilities[0] = "changed"
if *cloned.TrialExtendedAt != 150 {
t.Fatal("trial_extended_at was aliased")
}
if cloned.Capabilities[0] != "relay" {
t.Fatal("capabilities were aliased")
}
}
func TestNormalizeThenClone_RoundTrip(t *testing.T) {
trialStarted := int64(100)
trialEnds := int64(200)
trialExtended := int64(150)
original := &BillingState{
Capabilities: []string{"relay"},
Limits: map[string]int64{"max_monitored_systems": 50},
MetersEnabled: []string{"active_agents"},
PlanVersion: "pro-v2",
SubscriptionState: SubStateActive,
TrialStartedAt: &trialStarted,
TrialEndsAt: &trialEnds,
TrialExtendedAt: &trialExtended,
Integrity: "abc123",
StripeCustomerID: "cus_1",
StripeSubscriptionID: "sub_2",
StripePriceID: "price_3",
}
normalized := NormalizeBillingState(original)
cloned := cloneBillingState(*normalized)
// Every field must survive the full normalize → clone chain.
if cloned.Integrity != "abc123" {
t.Fatalf("integrity lost in round-trip: got %q", cloned.Integrity)
}
if cloned.StripeCustomerID != "cus_1" {
t.Fatalf("stripe_customer_id lost: got %q", cloned.StripeCustomerID)
}
if cloned.TrialExtendedAt == nil || *cloned.TrialExtendedAt != 150 {
t.Fatalf("trial_extended_at lost: got %v", cloned.TrialExtendedAt)
}
}
func TestIsValidBillingSubscriptionState(t *testing.T) {
valid := []SubscriptionState{
SubStateTrial,
SubStateActive,
SubStateGrace,
SubStateExpired,
SubStateSuspended,
SubStateCanceled,
}
for _, state := range valid {
if !IsValidBillingSubscriptionState(state) {
t.Fatalf("expected %q to be valid", state)
}
}
if IsValidBillingSubscriptionState(SubscriptionState("invalid")) {
t.Fatal("expected invalid state to be rejected")
}
}
func TestNormalizeBillingState_MaxNodesToMaxMonitoredSystemsMigration(t *testing.T) {
t.Run("legacy_key_migrated", func(t *testing.T) {
state := &BillingState{
Limits: map[string]int64{"max_nodes": 10},
}
normalized := NormalizeBillingState(state)
if normalized.Limits["max_monitored_systems"] != 10 {
t.Fatalf("expected max_monitored_systems=10, got %d", normalized.Limits["max_monitored_systems"])
}
if _, hasOld := normalized.Limits["max_nodes"]; hasOld {
t.Fatal("expected max_nodes to be deleted after migration")
}
})
t.Run("new_key_preserved_legacy_deleted", func(t *testing.T) {
state := &BillingState{
Limits: map[string]int64{"max_monitored_systems": 15, "max_nodes": 5},
}
normalized := NormalizeBillingState(state)
if normalized.Limits["max_monitored_systems"] != 15 {
t.Fatalf("expected max_monitored_systems=15, got %d", normalized.Limits["max_monitored_systems"])
}
if _, hasOld := normalized.Limits["max_nodes"]; hasOld {
t.Fatal("expected max_nodes to be deleted when max_monitored_systems exists")
}
})
t.Run("no_legacy_key_no_change", func(t *testing.T) {
state := &BillingState{
Limits: map[string]int64{"max_monitored_systems": 20},
}
normalized := NormalizeBillingState(state)
if normalized.Limits["max_monitored_systems"] != 20 {
t.Fatalf("expected max_monitored_systems=20, got %d", normalized.Limits["max_monitored_systems"])
}
})
}
func TestNormalizeBillingState_CanonicalizesCloudPlanVersionAndLimits(t *testing.T) {
state := &BillingState{
PlanVersion: " cloud-v1 ",
Limits: map[string]int64{
"max_monitored_systems": 999,
"max_nodes": 5,
},
}
normalized := NormalizeBillingState(state)
if normalized.PlanVersion != "cloud_starter" {
t.Fatalf("plan_version=%q, want %q", normalized.PlanVersion, "cloud_starter")
}
if got := normalized.Limits["max_monitored_systems"]; got != 10 {
t.Fatalf("limits[max_monitored_systems]=%d, want %d", got, 10)
}
if _, hasOld := normalized.Limits["max_nodes"]; hasOld {
t.Fatal("expected max_nodes to be deleted during normalization")
}
}
func TestNormalizeBillingState_PreservesNonCloudPlanLimits(t *testing.T) {
state := &BillingState{
PlanVersion: "pro-v2",
Limits: map[string]int64{
"max_monitored_systems": 50,
},
}
normalized := NormalizeBillingState(state)
if normalized.PlanVersion != "pro-v2" {
t.Fatalf("plan_version=%q, want %q", normalized.PlanVersion, "pro-v2")
}
if got := normalized.Limits["max_monitored_systems"]; got != 50 {
t.Fatalf("limits[max_monitored_systems]=%d, want %d", got, 50)
}
}
func TestNormalizeBillingState_StripsEntitlementsForRevokedSubscriptions(t *testing.T) {
state := &BillingState{
Capabilities: []string{"relay"},
Limits: map[string]int64{"max_monitored_systems": 999},
MetersEnabled: []string{"api_requests"},
PlanVersion: "cloud_starter",
SubscriptionState: SubStateCanceled,
}
normalized := NormalizeBillingState(state)
if len(normalized.Capabilities) != 0 {
t.Fatalf("capabilities=%v, want empty", normalized.Capabilities)
}
if len(normalized.Limits) != 0 {
t.Fatalf("limits=%v, want empty", normalized.Limits)
}
if len(normalized.MetersEnabled) != 0 {
t.Fatalf("meters_enabled=%v, want empty", normalized.MetersEnabled)
}
if normalized.PlanVersion != "cloud_starter" {
t.Fatalf("plan_version=%q, want %q", normalized.PlanVersion, "cloud_starter")
}
}