diff --git a/internal/api/contract_test.go b/internal/api/contract_test.go index 33e587bce..c126e6a5c 100644 --- a/internal/api/contract_test.go +++ b/internal/api/contract_test.go @@ -7078,6 +7078,32 @@ func TestContract_EntitlementUsageSnapshotWaitsForSettledSupplementalInventory(t } } +func TestContract_MissingLicensePublicKeyActivationErrorGuidesLocalBuilds(t *testing.T) { + msg := userFriendlyActivationError(fmt.Errorf("validate license: %w: signature verification required", pkglicensing.ErrNoPublicKey)) + lower := strings.ToLower(msg) + + for _, want := range []string{ + "official pulse release tarball", + "published docker image", + "same license key", + } { + if !strings.Contains(lower, want) { + t.Fatalf("activation error %q does not contain %q", msg, want) + } + } + + for _, forbidden := range []string{ + "temporarily unavailable", + "try again later", + "validate license:", + "signature verification", + } { + if strings.Contains(lower, forbidden) { + t.Fatalf("activation error %q contains misleading/internal text %q", msg, forbidden) + } + } +} + func TestContract_LegacyMigrationFallbackStaysUncappedJSONSnapshot(t *testing.T) { t.Setenv("PULSE_LICENSE_DEV_MODE", "false") const expectedClientVersion = "6.0.0-rc.1" diff --git a/internal/api/license_handlers_test.go b/internal/api/license_handlers_test.go index 7f1c6550f..58687a15b 100644 --- a/internal/api/license_handlers_test.go +++ b/internal/api/license_handlers_test.go @@ -1313,6 +1313,25 @@ func TestUserFriendlyActivationError_NoGoErrorChainSyntax(t *testing.T) { } }, }, + { + name: "missing license public key", + err: fmt.Errorf("validate license: %w: signature verification required", license.ErrNoPublicKey), + check: func(t *testing.T, msg string) { + lower := strings.ToLower(msg) + if strings.Contains(lower, "temporarily unavailable") || strings.Contains(lower, "try again later") { + t.Errorf("message misclassifies local build configuration as transient outage: %q", msg) + } + if !strings.Contains(lower, "official pulse release tarball") { + t.Errorf("message should direct tarball installs back to official release artifacts: %q", msg) + } + if !strings.Contains(lower, "published docker image") { + t.Errorf("message should direct container installs back to published Docker images: %q", msg) + } + if !strings.Contains(lower, "same license key") { + t.Errorf("message should reassure the customer that the key can be reused: %q", msg) + } + }, + }, { name: "invalid license", err: license.ErrInvalidLicense, diff --git a/internal/api/licensing_handlers.go b/internal/api/licensing_handlers.go index c30bd648f..ed31af7ad 100644 --- a/internal/api/licensing_handlers.go +++ b/internal/api/licensing_handlers.go @@ -924,7 +924,7 @@ func userFriendlyActivationError(err error) string { case errors.Is(err, errExpiredLicenseSentinel): return "This license has expired. Contact support for renewal options." case errors.Is(err, errNoPublicKeySentinel): - return "License verification is temporarily unavailable. Please try again later." + return "This Pulse build cannot verify Pro licenses because it was built without the license validation key. Use the official Pulse release tarball or published Docker image, then activate the same license key again." } // License server errors from the activation key flow.