mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-27 05:57:27 +00:00
97f894b009
Relay left public checkout on 2026-09-29: it only ever connected the Pulse Mobile app, which retires on 31 March 2027, and existing Relay subscribers now carry Pro entitlements. The app still sold it. Every Community install saw a "Get alerts on your phone ... Available with Relay and Pro plans" upgrade panel on Alerts destinations, the plan screen offered a Relay card with "Remote web access via Relay", and the settings section was called Remote Access although Relay never reached the web UI. The Alerts push panel now renders only on instances that have the relay feature, with no upsell. The settings section, nav, header and locale catalogs say Pulse Mobile and carry the retirement date. Community sees only the Pro comparison card and Relay-tier licenses see none; gated mobile features name Pro as their minimum plan. The relay feature is labelled "Pulse Relay (Mobile Connection)" in the catalog, plan copy no longer claims remote web access, the backend pairing diagnostics point at Settings > Pulse Mobile, and the user docs, including PRIVACY.md, state that Relay does not provide remote web UI access.
128 lines
4.5 KiB
Go
128 lines
4.5 KiB
Go
package license
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/license/entitlements"
|
|
)
|
|
|
|
func TestClaimsEntitlementMetadataHelpers(t *testing.T) {
|
|
t.Run("nil receiver defaults", func(t *testing.T) {
|
|
var claims *Claims
|
|
|
|
if got := claims.EntitlementMetersEnabled(); got != nil {
|
|
t.Fatalf("EntitlementMetersEnabled() = %v, want nil", got)
|
|
}
|
|
if got := claims.EntitlementPlanVersion(); got != "" {
|
|
t.Fatalf("EntitlementPlanVersion() = %q, want empty", got)
|
|
}
|
|
if got := claims.EntitlementSubscriptionState(); got != SubStateActive {
|
|
t.Fatalf("EntitlementSubscriptionState() = %q, want %q", got, SubStateActive)
|
|
}
|
|
})
|
|
|
|
t.Run("explicit values", func(t *testing.T) {
|
|
claims := &Claims{
|
|
MetersEnabled: []string{"events_ingested", "alerts_sent"},
|
|
PlanVersion: "2026-02",
|
|
SubState: SubStateSuspended,
|
|
}
|
|
|
|
if got := claims.EntitlementPlanVersion(); got != "2026-02" {
|
|
t.Fatalf("EntitlementPlanVersion() = %q, want %q", got, "2026-02")
|
|
}
|
|
if got := claims.EntitlementSubscriptionState(); got != SubStateSuspended {
|
|
t.Fatalf("EntitlementSubscriptionState() = %q, want %q", got, SubStateSuspended)
|
|
}
|
|
if got := claims.EntitlementMetersEnabled(); !reflect.DeepEqual(got, claims.MetersEnabled) {
|
|
t.Fatalf("EntitlementMetersEnabled() = %v, want %v", got, claims.MetersEnabled)
|
|
}
|
|
})
|
|
|
|
t.Run("empty subscription state defaults to active", func(t *testing.T) {
|
|
claims := &Claims{SubState: ""}
|
|
if got := claims.EntitlementSubscriptionState(); got != SubStateActive {
|
|
t.Fatalf("EntitlementSubscriptionState() = %q, want %q", got, SubStateActive)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetTierDisplayNameCoversAllKnownTiers(t *testing.T) {
|
|
tests := []struct {
|
|
tier Tier
|
|
want string
|
|
}{
|
|
{tier: TierFree, want: "Community"},
|
|
{tier: TierRelay, want: "Relay"},
|
|
{tier: TierPro, want: "Pro"},
|
|
{tier: TierProPlus, want: "Pro+"},
|
|
{tier: TierProAnnual, want: "Pro (Annual)"},
|
|
{tier: TierLifetime, want: "Pro (Lifetime)"},
|
|
{tier: TierCloud, want: "Cloud"},
|
|
{tier: TierMSP, want: "MSP"},
|
|
{tier: TierEnterprise, want: "Enterprise"},
|
|
{tier: Tier("unknown"), want: "Unknown"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := GetTierDisplayName(tt.tier); got != tt.want {
|
|
t.Fatalf("GetTierDisplayName(%q) = %q, want %q", tt.tier, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetFeatureDisplayNameCoversKnownFeaturesAndFallback(t *testing.T) {
|
|
tests := []struct {
|
|
feature string
|
|
want string
|
|
}{
|
|
{feature: FeatureAIPatrol, want: "Pulse Patrol (Background Health Checks)"},
|
|
{feature: FeatureAIAlerts, want: "Patrol Investigates Issues and Explains the Root Cause"},
|
|
{feature: FeatureAIAutoFix, want: "Patrol Applies Safe Fixes and Verifies the Result"},
|
|
{feature: FeatureKubernetesAI, want: "Kubernetes AI Analysis (Compatibility)"},
|
|
{feature: FeatureUpdateAlerts, want: "Update Alerts (Container/Package Updates)"},
|
|
{feature: FeatureRBAC, want: "Role-Based Access Control (RBAC)"},
|
|
{feature: FeatureMultiUser, want: "Multi-User Mode"},
|
|
{feature: FeatureWhiteLabel, want: "White-Label Branding"},
|
|
{feature: FeatureMultiTenant, want: "Multi-Tenant Mode"},
|
|
{feature: FeatureUnlimited, want: "Hosted Capacity Policy"},
|
|
{feature: FeatureAgentProfiles, want: "Centralized Agent Profiles"},
|
|
{feature: FeatureAuditLogging, want: "Audit Logging"},
|
|
{feature: FeatureSSO, want: "Core SSO (OIDC/SAML)"},
|
|
{feature: FeatureAdvancedSSO, want: "Multi-Provider SSO"},
|
|
{feature: FeatureRelay, want: "Pulse Relay (Mobile Connection)"},
|
|
{feature: FeatureMobileApp, want: "Pulse Mobile Pairing"},
|
|
{feature: FeaturePushNotifications, want: "Push Notifications"},
|
|
{feature: FeatureAdvancedReporting, want: "PDF/CSV Reporting"},
|
|
{feature: FeatureLongTermMetrics, want: "Extended Metric History"},
|
|
{feature: "custom_feature", want: "custom_feature"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if got := GetFeatureDisplayName(tt.feature); got != tt.want {
|
|
t.Fatalf("GetFeatureDisplayName(%q) = %q, want %q", tt.feature, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStatusHostedEvaluatorClampsNegativeLimits(t *testing.T) {
|
|
t.Setenv("PULSE_DEV", "false")
|
|
t.Setenv("PULSE_MOCK_MODE", "false")
|
|
|
|
svc := NewService()
|
|
svc.SetEvaluator(entitlements.NewEvaluator(staticEntitlementSource{
|
|
capabilities: []string{FeatureAIPatrol},
|
|
limits: map[string]int64{"max_monitored_systems": -5, "max_guests": -10},
|
|
subscriptionState: entitlements.SubStateActive,
|
|
}))
|
|
|
|
status := svc.Status()
|
|
if !status.Valid {
|
|
t.Fatalf("Status().Valid = %v, want true", status.Valid)
|
|
}
|
|
if status.MaxGuests != 0 {
|
|
t.Fatalf("Status().MaxGuests = %d, want 0", status.MaxGuests)
|
|
}
|
|
}
|