mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-06 16:16:26 +00:00
626 lines
14 KiB
Go
626 lines
14 KiB
Go
package licensing
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestClaims_EffectiveCapabilities(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
claims Claims
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "explicit_capabilities_returns_them",
|
|
claims: Claims{
|
|
Capabilities: []string{"feature_a", "feature_b"},
|
|
Tier: TierPro,
|
|
},
|
|
expected: []string{"feature_a", "feature_b"},
|
|
},
|
|
{
|
|
name: "nil_capabilities_derives_from_tier",
|
|
claims: Claims{
|
|
Capabilities: nil,
|
|
Tier: TierPro,
|
|
Features: []string{"extra_feature"},
|
|
},
|
|
expected: DeriveCapabilitiesFromTier(TierPro, []string{"extra_feature"}),
|
|
},
|
|
{
|
|
name: "empty_capabilities_derives_from_tier",
|
|
claims: Claims{
|
|
Capabilities: []string{},
|
|
Tier: TierProAnnual,
|
|
},
|
|
expected: DeriveCapabilitiesFromTier(TierProAnnual, nil),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.claims.EffectiveCapabilities()
|
|
if len(got) != len(tt.expected) {
|
|
t.Fatalf("EffectiveCapabilities() returned %d capabilities, want %d", len(got), len(tt.expected))
|
|
}
|
|
for i, cap := range got {
|
|
if i >= len(tt.expected) {
|
|
break
|
|
}
|
|
if cap != tt.expected[i] {
|
|
t.Fatalf("EffectiveCapabilities()[%d] = %q, want %q", i, cap, tt.expected[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClaims_EffectiveLimits(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
claims Claims
|
|
expected map[string]int64
|
|
}{
|
|
{
|
|
name: "explicit_limits_returns_them",
|
|
claims: Claims{
|
|
Limits: map[string]int64{"max_monitored_systems": 100, "max_guests": 500},
|
|
MaxMonitoredSystems: 50,
|
|
MaxGuests: 200,
|
|
},
|
|
expected: map[string]int64{"max_monitored_systems": 100, "max_guests": 500},
|
|
},
|
|
{
|
|
name: "nil_limits_derives_from_legacy_fields",
|
|
claims: Claims{
|
|
Limits: nil,
|
|
MaxMonitoredSystems: 25,
|
|
MaxGuests: 100,
|
|
},
|
|
expected: map[string]int64{"max_monitored_systems": 25, "max_guests": 100},
|
|
},
|
|
{
|
|
name: "zero_max_monitored_systems_ignored",
|
|
claims: Claims{
|
|
Limits: nil,
|
|
MaxMonitoredSystems: 0,
|
|
MaxGuests: 100,
|
|
},
|
|
expected: map[string]int64{"max_guests": 100},
|
|
},
|
|
{
|
|
name: "no_limits_returns_empty",
|
|
claims: Claims{},
|
|
expected: map[string]int64{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.claims.EffectiveLimits()
|
|
if len(got) != len(tt.expected) {
|
|
t.Fatalf("EffectiveLimits() returned %d limits, want %d", len(got), len(tt.expected))
|
|
}
|
|
for key, val := range tt.expected {
|
|
if got[key] != val {
|
|
t.Fatalf("EffectiveLimits()[%q] = %d, want %d", key, got[key], val)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClaims_EntitlementMetersEnabled(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
claims *Claims
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "nil_claims_returns_nil",
|
|
claims: nil,
|
|
expected: nil,
|
|
},
|
|
{
|
|
name: "returns_meters_enabled",
|
|
claims: &Claims{
|
|
MetersEnabled: []string{"meter_a", "meter_b"},
|
|
},
|
|
expected: []string{"meter_a", "meter_b"},
|
|
},
|
|
{
|
|
name: "empty_meters_returns_empty",
|
|
claims: &Claims{
|
|
MetersEnabled: []string{},
|
|
},
|
|
expected: []string{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.claims.EntitlementMetersEnabled()
|
|
if len(got) != len(tt.expected) {
|
|
t.Fatalf("EntitlementMetersEnabled() returned %d, want %d", len(got), len(tt.expected))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClaims_EntitlementPlanVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
claims *Claims
|
|
expected string
|
|
}{
|
|
{
|
|
name: "nil_claims_returns_empty",
|
|
claims: nil,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "returns_plan_version",
|
|
claims: &Claims{
|
|
PlanVersion: "v2",
|
|
},
|
|
expected: "v2",
|
|
},
|
|
{
|
|
name: "canonicalizes_cloud_alias",
|
|
claims: &Claims{
|
|
PlanVersion: " cloud_v1 ",
|
|
},
|
|
expected: "cloud_starter",
|
|
},
|
|
{
|
|
name: "empty_plan_version_returns_empty",
|
|
claims: &Claims{
|
|
PlanVersion: "",
|
|
},
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.claims.EntitlementPlanVersion()
|
|
if got != tt.expected {
|
|
t.Fatalf("EntitlementPlanVersion() = %q, want %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClaims_EffectiveLimitsCanonicalizesCloudPlanLimits(t *testing.T) {
|
|
claims := Claims{
|
|
Tier: TierCloud,
|
|
PlanVersion: "cloud-v1",
|
|
Limits: map[string]int64{
|
|
"max_monitored_systems": 999,
|
|
"max_guests": 5,
|
|
},
|
|
}
|
|
|
|
limits := claims.EffectiveLimits()
|
|
if got := limits["max_monitored_systems"]; got != 10 {
|
|
t.Fatalf("EffectiveLimits()[max_monitored_systems]=%d, want %d", got, 10)
|
|
}
|
|
if got := limits["max_guests"]; got != 5 {
|
|
t.Fatalf("EffectiveLimits()[max_guests]=%d, want %d", got, 5)
|
|
}
|
|
}
|
|
|
|
func TestClaims_EffectiveLimitsPreservesNonCloudPlanLimits(t *testing.T) {
|
|
claims := Claims{
|
|
PlanVersion: "pro-v2",
|
|
Limits: map[string]int64{
|
|
"max_monitored_systems": 42,
|
|
},
|
|
}
|
|
|
|
limits := claims.EffectiveLimits()
|
|
if got := limits["max_monitored_systems"]; got != 42 {
|
|
t.Fatalf("EffectiveLimits()[max_monitored_systems]=%d, want %d", got, 42)
|
|
}
|
|
}
|
|
|
|
func TestLicenseStatusJSON_EncodesMonitoredSystemContinuity(t *testing.T) {
|
|
status := LicenseStatus{
|
|
Valid: true,
|
|
Tier: TierPro,
|
|
MaxMonitoredSystems: 23,
|
|
MonitoredSystemContinuity: &MonitoredSystemContinuityStatus{
|
|
PlanLimit: 10,
|
|
GrandfatheredFloor: 23,
|
|
EffectiveLimit: 23,
|
|
CapturePending: false,
|
|
CapturedAt: 123,
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(status)
|
|
if err != nil {
|
|
t.Fatalf("marshal status: %v", err)
|
|
}
|
|
|
|
var decoded map[string]any
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("decode status json: %v", err)
|
|
}
|
|
|
|
continuity, ok := decoded["monitored_system_continuity"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("expected monitored_system_continuity object, got %#v", decoded["monitored_system_continuity"])
|
|
}
|
|
if got := continuity["plan_limit"]; got != float64(10) {
|
|
t.Fatalf("plan_limit=%v, want %v", got, float64(10))
|
|
}
|
|
if got := continuity["effective_limit"]; got != float64(23) {
|
|
t.Fatalf("effective_limit=%v, want %v", got, float64(23))
|
|
}
|
|
if got := continuity["grandfathered_floor"]; got != float64(23) {
|
|
t.Fatalf("grandfathered_floor=%v, want %v", got, float64(23))
|
|
}
|
|
if got := continuity["capture_pending"]; got != false {
|
|
t.Fatalf("capture_pending=%v, want false", got)
|
|
}
|
|
}
|
|
|
|
func TestClaims_EffectiveLimitsMissingCloudPlanFailsClosed(t *testing.T) {
|
|
claims := Claims{
|
|
Tier: TierCloud,
|
|
PlanVersion: " ",
|
|
}
|
|
|
|
limits := claims.EffectiveLimits()
|
|
if got := limits["max_monitored_systems"]; got != int64(UnknownPlanDefaultMonitoredSystemLimit) {
|
|
t.Fatalf("EffectiveLimits()[max_monitored_systems]=%d, want %d", got, UnknownPlanDefaultMonitoredSystemLimit)
|
|
}
|
|
}
|
|
|
|
func TestClaims_EffectiveLimitsPreservesExplicitCustomCloudLimit(t *testing.T) {
|
|
claims := Claims{
|
|
Tier: TierCloud,
|
|
PlanVersion: "custom_plan",
|
|
Limits: map[string]int64{
|
|
"max_monitored_systems": 42,
|
|
},
|
|
}
|
|
|
|
limits := claims.EffectiveLimits()
|
|
if got := limits["max_monitored_systems"]; got != 42 {
|
|
t.Fatalf("EffectiveLimits()[max_monitored_systems]=%d, want %d", got, 42)
|
|
}
|
|
}
|
|
|
|
func TestClaims_EntitlementSubscriptionState(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
claims *Claims
|
|
expected SubscriptionState
|
|
}{
|
|
{
|
|
name: "nil_claims_returns_active",
|
|
claims: nil,
|
|
expected: SubStateActive,
|
|
},
|
|
{
|
|
name: "returns_subscription_state",
|
|
claims: &Claims{
|
|
SubState: SubStateGrace,
|
|
},
|
|
expected: SubStateGrace,
|
|
},
|
|
{
|
|
name: "empty_substate_returns_active",
|
|
claims: &Claims{
|
|
SubState: "",
|
|
},
|
|
expected: SubStateActive,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.claims.EntitlementSubscriptionState()
|
|
if got != tt.expected {
|
|
t.Fatalf("EntitlementSubscriptionState() = %q, want %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLicense_IsExpired(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
license License
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "lifetime_license_not_expired",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: 0,
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "future_expiry_not_expired",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "past_expiry_is_expired",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: time.Now().Add(-24 * time.Hour).Unix(),
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.license.IsExpired()
|
|
if got != tt.expected {
|
|
t.Fatalf("IsExpired() = %v, want %v", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLicense_IsLifetime(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
license License
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "zero_expires_at_is_lifetime",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: 0,
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "lifetime_tier_is_lifetime",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
|
Tier: TierLifetime,
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "non_lifetime_not_lifetime",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
|
Tier: TierPro,
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.license.IsLifetime()
|
|
if got != tt.expected {
|
|
t.Fatalf("IsLifetime() = %v, want %v", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLicense_DaysRemaining(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
license License
|
|
expected int
|
|
cmp func(got, want int) bool
|
|
}{
|
|
{
|
|
name: "lifetime_returns_minus_one",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: 0,
|
|
},
|
|
},
|
|
expected: -1,
|
|
cmp: func(got, want int) bool { return got == want },
|
|
},
|
|
{
|
|
name: "future_returns_positive_days",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: time.Now().Add(10 * 24 * time.Hour).Unix(),
|
|
},
|
|
},
|
|
expected: 10,
|
|
cmp: func(got, want int) bool { return got >= 9 && got <= 10 },
|
|
},
|
|
{
|
|
name: "past_returns_zero",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: time.Now().Add(-10 * time.Hour).Unix(),
|
|
},
|
|
},
|
|
expected: 0,
|
|
cmp: func(got, want int) bool { return got == want },
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.license.DaysRemaining()
|
|
if !tt.cmp(got, tt.expected) {
|
|
t.Fatalf("DaysRemaining() = %d, want approximately %d", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLicense_ExpiresAt(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
license License
|
|
expectNil bool
|
|
}{
|
|
{
|
|
name: "lifetime_returns_nil",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: 0,
|
|
},
|
|
},
|
|
expectNil: true,
|
|
},
|
|
{
|
|
name: "non_lifetime_returns_time",
|
|
license: License{
|
|
Claims: Claims{
|
|
ExpiresAt: time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
|
},
|
|
},
|
|
expectNil: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.license.ExpiresAt()
|
|
if tt.expectNil && got != nil {
|
|
t.Fatalf("ExpiresAt() = %v, want nil", got)
|
|
}
|
|
if !tt.expectNil && got == nil {
|
|
t.Fatalf("ExpiresAt() = nil, want non-nil")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLicense_HasFeature(t *testing.T) {
|
|
license := License{
|
|
Claims: Claims{
|
|
Capabilities: []string{"feature_a", "feature_b"},
|
|
Tier: TierPro,
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
feature string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "has_feature",
|
|
feature: "feature_a",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "missing_feature",
|
|
feature: "feature_c",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "empty_feature",
|
|
feature: "",
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := license.HasFeature(tt.feature)
|
|
if got != tt.expected {
|
|
t.Fatalf("HasFeature(%q) = %v, want %v", tt.feature, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLicense_AllFeatures(t *testing.T) {
|
|
license := License{
|
|
Claims: Claims{
|
|
Capabilities: []string{"zebra", "alpha", "middle"},
|
|
Tier: TierPro,
|
|
},
|
|
}
|
|
|
|
got := license.AllFeatures()
|
|
if len(got) != 3 {
|
|
t.Fatalf("AllFeatures() returned %d features, want 3", len(got))
|
|
}
|
|
|
|
if got[0] != "alpha" || got[1] != "middle" || got[2] != "zebra" {
|
|
t.Fatalf("AllFeatures() not sorted: %v", got)
|
|
}
|
|
}
|
|
|
|
func TestClaims_UnmarshalJSON_Migration(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
json string
|
|
wantMax int
|
|
}{
|
|
{
|
|
name: "new_key_only",
|
|
json: `{"lid":"x","email":"a@b","tier":"pro","iat":1,"max_monitored_systems":10}`,
|
|
wantMax: 10,
|
|
},
|
|
{
|
|
name: "legacy_key_only",
|
|
json: `{"lid":"x","email":"a@b","tier":"pro","iat":1,"max_nodes":5}`,
|
|
wantMax: 5,
|
|
},
|
|
{
|
|
name: "both_keys_prefer_new",
|
|
json: `{"lid":"x","email":"a@b","tier":"pro","iat":1,"max_monitored_systems":15,"max_nodes":5}`,
|
|
wantMax: 15,
|
|
},
|
|
{
|
|
name: "new_key_zero_ignores_legacy",
|
|
json: `{"lid":"x","email":"a@b","tier":"pro","iat":1,"max_monitored_systems":0,"max_nodes":5}`,
|
|
wantMax: 0,
|
|
},
|
|
{
|
|
name: "neither_key",
|
|
json: `{"lid":"x","email":"a@b","tier":"pro","iat":1}`,
|
|
wantMax: 0,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var c Claims
|
|
if err := json.Unmarshal([]byte(tt.json), &c); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
if c.MaxMonitoredSystems != tt.wantMax {
|
|
t.Fatalf("MaxMonitoredSystems = %d, want %d", c.MaxMonitoredSystems, tt.wantMax)
|
|
}
|
|
})
|
|
}
|
|
}
|