diff --git a/pkg/licensing/service.go b/pkg/licensing/service.go index 70c453200..7bfcc5380 100644 --- a/pkg/licensing/service.go +++ b/pkg/licensing/service.go @@ -535,7 +535,13 @@ func (s *Service) IsValid() bool { func (s *Service) HasFeature(feature string) bool { // In demo mode or dev mode, grant all Pro features if isDemoMode() || isDevMode() { - return devModeFeatureEnabled(feature) + if devModeFeatureEnabled(feature) { + return true + } + // Features excluded from the implicit dev grant (white_label, + // multi_user, unlimited, env-gated multi_tenant) fall through to + // real entitlement evaluation, so an explicitly activated license + // behaves the same in dev builds as in release builds. } s.mu.Lock() // Need write lock since we may update grace period diff --git a/pkg/licensing/service_activate_test.go b/pkg/licensing/service_activate_test.go index dc2be06d0..e791cb6d8 100644 --- a/pkg/licensing/service_activate_test.go +++ b/pkg/licensing/service_activate_test.go @@ -567,6 +567,38 @@ func TestServiceStatus_DevModeKeepsCustomerFacingStatusCommunityWithoutLicense(t } } +func TestServiceHasFeature_DevModeHonorsActivatedLicenseForExcludedFeatures(t *testing.T) { + t.Setenv("PULSE_DEV", "true") + t.Setenv("PULSE_MOCK_MODE", "true") + t.Setenv("PULSE_LICENSE_DEV_MODE", "true") + t.Setenv("PULSE_MULTI_TENANT_ENABLED", "") + SetPublicKey(nil) + t.Cleanup(func() { SetPublicKey(nil) }) + + svc := NewService() + if svc.HasFeature(FeatureWhiteLabel) { + t.Fatalf("HasFeature(%q)=true without a license, want false in dev mode", FeatureWhiteLabel) + } + + licenseKey, err := GenerateLicenseForTesting("dev-white-label@example.com", TierEnterprise, 24*time.Hour) + if err != nil { + t.Fatalf("GenerateLicenseForTesting: %v", err) + } + if _, err := svc.Activate(licenseKey); err != nil { + t.Fatalf("Activate in dev mode: %v", err) + } + + // The implicit dev grant excludes white_label, but an explicitly + // activated license that carries it must behave like a release build. + if !svc.HasFeature(FeatureWhiteLabel) { + t.Fatalf("HasFeature(%q)=false with an activated enterprise license, want true", FeatureWhiteLabel) + } + // Features the dev grant already enables stay enabled. + if !svc.HasFeature(FeatureAdvancedReporting) { + t.Fatalf("HasFeature(%q)=false, want true for dev-mode backend gate bypass", FeatureAdvancedReporting) + } +} + func TestServiceStatus_DevModeMultiTenantBypassDoesNotChangeCustomerFacingStatus(t *testing.T) { t.Setenv("PULSE_DEV", "true") t.Setenv("PULSE_MULTI_TENANT_ENABLED", "true")