Files
pulse/pkg/licensing/commercial_migration_load_test.go
T
rcourtman f9b10316b8 fix(licensing): surface unreadable persisted v5 licenses and retry failed startup exchanges
Two silent-downgrade paths in the v5→v6 migration are now visible and
self-healing:

License load/decrypt failure (was: one log line, no UI state): when
license.enc exists but cannot be read, getTenantComponents now persists
a terminal commercial_migration state with reason
persisted_license_unreadable, so the licence panel and global banner
tell the customer to re-enter their v5 key instead of leaving them to
discover missing Pro features.

Failed startup exchange (was: once per process, Community until manual
restart): exchange failures classified as pending — license-server
blips, DNS failures, rate limits — now schedule a background retry loop
with backoff (30s → 30m cap) that re-attempts the exchange until it
succeeds, hits a terminal classification, or the org's service activates
through another path. The loop stops cleanly on manual activation and
StopAllBackgroundLoops.

Registers the new files in the subsystem registry (cloud-paid
commercial-migration verification policy + shared ownership) and pins
both behaviors in the cloud-paid and api-contracts contracts.
2026-06-11 08:44:35 +01:00

44 lines
1.6 KiB
Go

package licensing
import (
"errors"
"testing"
)
func TestClassifyPersistedLicenseLoadError(t *testing.T) {
t.Run("nil error yields no status", func(t *testing.T) {
if status := ClassifyPersistedLicenseLoadError(nil); status != nil {
t.Fatalf("expected nil status, got %+v", status)
}
})
t.Run("load failure is terminal with re-enter-key action", func(t *testing.T) {
status := ClassifyPersistedLicenseLoadError(errors.New("failed to decrypt license: cipher: message authentication failed"))
if status == nil {
t.Fatal("expected status, got nil")
}
if status.Source != CommercialMigrationSourceV5License {
t.Errorf("Source = %q, want %q", status.Source, CommercialMigrationSourceV5License)
}
if status.State != CommercialMigrationStateFailed {
t.Errorf("State = %q, want %q", status.State, CommercialMigrationStateFailed)
}
if status.Reason != CommercialMigrationReasonPersistedUnreadable {
t.Errorf("Reason = %q, want %q", status.Reason, CommercialMigrationReasonPersistedUnreadable)
}
if status.RecommendedAction != CommercialMigrationActionEnterSupportedV5 {
t.Errorf("RecommendedAction = %q, want %q", status.RecommendedAction, CommercialMigrationActionEnterSupportedV5)
}
})
t.Run("survives contract normalization", func(t *testing.T) {
status := NormalizeCommercialMigrationStatus(ClassifyPersistedLicenseLoadError(errors.New("unreadable")))
if status == nil {
t.Fatal("normalization dropped the load-failure status")
}
if status.Reason != CommercialMigrationReasonPersistedUnreadable {
t.Errorf("Reason = %q, want %q", status.Reason, CommercialMigrationReasonPersistedUnreadable)
}
})
}