mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-21 03:05:44 +00:00
f9b10316b8
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.
44 lines
1.6 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|