Files
volt/pkg/license/enforce_test.go
Karl Clinger 0ebe75b2ca Volt CLI: source-available under AGPSL v5.0
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
2026-03-21 02:08:15 -05:00

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)
}
}