Complete infrastructure platform CLI: - Container runtime (systemd-nspawn) - VoltVisor VMs (Neutron Stardust / QEMU) - Stellarium CAS (content-addressed storage) - ORAS Registry - GitOps integration - Landlock LSM security - Compose orchestration - Mesh networking Copyright (c) Armored Gates LLC. All rights reserved. Licensed under AGPSL v5.0
328 lines
9.4 KiB
Go
328 lines
9.4 KiB
Go
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)
|
|
}
|
|
}
|