package license import ( "os" "path/filepath" "testing" "time" "gopkg.in/yaml.v3" ) // setupTestStore creates a temporary license store for testing. func setupTestStore(t *testing.T) *Store { t.Helper() dir := t.TempDir() return &Store{Dir: dir} } // saveLicense writes a license to the test store. func saveLicense(t *testing.T, store *Store, lic *License) { t.Helper() data, err := yaml.Marshal(lic) if err != nil { t.Fatalf("failed to marshal license: %v", err) } if err := os.MkdirAll(store.Dir, 0700); err != nil { t.Fatalf("failed to create store dir: %v", err) } if err := os.WriteFile(filepath.Join(store.Dir, "license.yaml"), data, 0600); err != nil { t.Fatalf("failed to write license: %v", err) } } // TestRequireFeature_CommunityAllowed verifies that Community-tier features // (like CAS) are allowed without any license. func TestRequireFeature_CommunityAllowed(t *testing.T) { store := setupTestStore(t) // No license file — defaults to Community tier communityFeatures := []string{"cas", "containers", "networking-basic", "security-profiles", "logs", "ps", "cas-pull", "cas-push"} for _, feature := range communityFeatures { err := RequireFeatureWithStore(store, feature) if err != nil { t.Errorf("Community feature %q should be allowed without license, got: %v", feature, err) } } } // TestRequireFeature_ProDeniedWithoutLicense verifies that Pro-tier features // (like VMs) are denied without a license. func TestRequireFeature_ProDeniedWithoutLicense(t *testing.T) { store := setupTestStore(t) // No license file proFeatures := []string{"vms", "cas-distributed", "cluster", "cicada"} for _, feature := range proFeatures { err := RequireFeatureWithStore(store, feature) if err == nil { t.Errorf("Pro feature %q should be DENIED without license", feature) } } } // TestRequireFeature_ProAllowedWithProLicense verifies that Pro features // work with a Pro license. func TestRequireFeature_ProAllowedWithProLicense(t *testing.T) { store := setupTestStore(t) saveLicense(t, store, &License{ Key: "VOLT-PRO-AAAA-BBBB-CCCC-DDDD-EEEE-FFFF", Tier: TierPro, ActivatedAt: time.Now(), }) proFeatures := []string{"vms", "cas-distributed", "cluster", "cicada", "cas", "containers"} for _, feature := range proFeatures { err := RequireFeatureWithStore(store, feature) if err != nil { t.Errorf("Pro feature %q should be allowed with Pro license, got: %v", feature, err) } } } // TestRequireFeature_EnterpriseDeniedWithProLicense verifies that Enterprise // features are denied with only a Pro license. func TestRequireFeature_EnterpriseDeniedWithProLicense(t *testing.T) { store := setupTestStore(t) saveLicense(t, store, &License{ Key: "VOLT-PRO-AAAA-BBBB-CCCC-DDDD-EEEE-FFFF", Tier: TierPro, ActivatedAt: time.Now(), }) enterpriseFeatures := []string{"sso", "rbac", "audit", "live-migration", "cas-cross-region"} for _, feature := range enterpriseFeatures { err := RequireFeatureWithStore(store, feature) if err == nil { t.Errorf("Enterprise feature %q should be DENIED with Pro license", feature) } } } // TestRequireFeature_EnterpriseAllowed verifies Enterprise features with // an Enterprise license. func TestRequireFeature_EnterpriseAllowed(t *testing.T) { store := setupTestStore(t) saveLicense(t, store, &License{ Key: "VOLT-ENT-AAAA-BBBB-CCCC-DDDD-EEEE-FFFF", Tier: TierEnterprise, ActivatedAt: time.Now(), }) features := []string{"sso", "rbac", "vms", "cas", "containers", "live-migration"} for _, feature := range features { err := RequireFeatureWithStore(store, feature) if err != nil { t.Errorf("Feature %q should be allowed with Enterprise license, got: %v", feature, err) } } } // TestRequireContainerLimit verifies container limit enforcement by tier. func TestRequireContainerLimit(t *testing.T) { tests := []struct { name string tier string count int wantError bool }{ {"Community under limit", TierCommunity, 25, false}, {"Community at limit", TierCommunity, 50, true}, {"Community over limit", TierCommunity, 75, true}, {"Pro under limit", TierPro, 250, false}, {"Pro at limit", TierPro, 500, true}, {"Pro over limit", TierPro, 750, true}, {"Enterprise unlimited", TierEnterprise, 99999, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { store := setupTestStore(t) if tt.tier != TierCommunity { saveLicense(t, store, &License{ Key: "VOLT-PRO-AAAA-BBBB-CCCC-DDDD-EEEE-FFFF", Tier: tt.tier, ActivatedAt: time.Now(), }) } err := RequireContainerLimitWithStore(store, tt.count) if tt.wantError && err == nil { t.Errorf("expected error for %d containers on %s tier", tt.count, tt.tier) } if !tt.wantError && err != nil { t.Errorf("expected no error for %d containers on %s tier, got: %v", tt.count, tt.tier, err) } }) } } // TestRequireContainerLimit_NoLicense verifies container limits with no license (Community). func TestRequireContainerLimit_NoLicense(t *testing.T) { store := setupTestStore(t) err := RequireContainerLimitWithStore(store, 25) if err != nil { t.Errorf("25 containers should be within Community limit, got: %v", err) } err = RequireContainerLimitWithStore(store, 50) if err == nil { t.Error("50 containers should exceed Community limit") } } // TestTrialExpiration verifies that expired trials fall back to Community. func TestTrialExpiration(t *testing.T) { store := setupTestStore(t) // Active trial — Pro features should work saveLicense(t, store, &License{ Key: "VOLT-PRO-AAAA-BBBB-CCCC-DDDD-EEEE-FFFF", Tier: TierPro, IsTrial: true, TrialEndsAt: time.Now().Add(24 * time.Hour), // expires tomorrow CouponCode: "TEST2025", ActivatedAt: time.Now(), }) err := RequireFeatureWithStore(store, "vms") if err != nil { t.Errorf("Active trial should allow Pro features, got: %v", err) } // Expired trial — Pro features should be denied saveLicense(t, store, &License{ Key: "VOLT-PRO-AAAA-BBBB-CCCC-DDDD-EEEE-FFFF", Tier: TierPro, IsTrial: true, TrialEndsAt: time.Now().Add(-24 * time.Hour), // expired yesterday CouponCode: "TEST2025", ActivatedAt: time.Now(), }) err = RequireFeatureWithStore(store, "vms") if err == nil { t.Error("Expired trial should DENY Pro features") } // Expired trial — Community features should still work err = RequireFeatureWithStore(store, "cas") if err != nil { t.Errorf("Expired trial should still allow Community features, got: %v", err) } } // TestTrialExpiration_ContainerLimit verifies expired trials use Community container limits. func TestTrialExpiration_ContainerLimit(t *testing.T) { store := setupTestStore(t) // Expired trial saveLicense(t, store, &License{ Key: "VOLT-PRO-AAAA-BBBB-CCCC-DDDD-EEEE-FFFF", Tier: TierPro, IsTrial: true, TrialEndsAt: time.Now().Add(-1 * time.Hour), ActivatedAt: time.Now(), }) // Should use Community limit (50), not Pro limit (500) err := RequireContainerLimitWithStore(store, 50) if err == nil { t.Error("Expired trial should use Community container limit (50)") } err = RequireContainerLimitWithStore(store, 25) if err != nil { t.Errorf("25 containers should be within Community limit even with expired trial, got: %v", err) } } // TestIsTrialExpired verifies the License.IsTrialExpired() method. func TestIsTrialExpired(t *testing.T) { tests := []struct { name string license License expected bool }{ { name: "not a trial", license: License{IsTrial: false}, expected: false, }, { name: "trial with zero expiry", license: License{IsTrial: true}, expected: false, }, { name: "active trial", license: License{IsTrial: true, TrialEndsAt: time.Now().Add(24 * time.Hour)}, expected: false, }, { name: "expired trial", license: License{IsTrial: true, TrialEndsAt: time.Now().Add(-24 * time.Hour)}, expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.license.IsTrialExpired() if got != tt.expected { t.Errorf("IsTrialExpired() = %v, want %v", got, tt.expected) } }) } } // TestRequiredTier verifies the requiredTier helper returns the minimum tier. func TestRequiredTier(t *testing.T) { tests := []struct { feature string expected string }{ {"cas", "Community"}, {"containers", "Community"}, {"vms", "Professional"}, {"cluster", "Professional"}, {"sso", "Enterprise"}, {"rbac", "Enterprise"}, {"nonexistent", "Unknown"}, } for _, tt := range tests { t.Run(tt.feature, func(t *testing.T) { got := requiredTier(tt.feature) if got != tt.expected { t.Errorf("requiredTier(%q) = %q, want %q", tt.feature, got, tt.expected) } }) } } // TestRequireFeature_ExpiredLicense verifies expired non-trial licenses. func TestRequireFeature_ExpiredLicense(t *testing.T) { store := setupTestStore(t) saveLicense(t, store, &License{ Key: "VOLT-PRO-AAAA-BBBB-CCCC-DDDD-EEEE-FFFF", Tier: TierPro, ActivatedAt: time.Now().Add(-365 * 24 * time.Hour), ExpiresAt: time.Now().Add(-24 * time.Hour), // expired yesterday }) // Pro feature should be denied err := RequireFeatureWithStore(store, "vms") if err == nil { t.Error("Expired license should deny Pro features") } // Community feature should still work err = RequireFeatureWithStore(store, "cas") if err != nil { t.Errorf("Expired license should still allow Community features, got: %v", err) } }