mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 08:57:12 +00:00
295 lines
9.6 KiB
Go
295 lines
9.6 KiB
Go
package entitlements
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"encoding/base64"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/cloudcp/registry"
|
|
pkglicensing "github.com/rcourtman/pulse-go-rewrite/pkg/licensing"
|
|
)
|
|
|
|
func newTestService(t *testing.T) (*Service, ed25519.PublicKey, *registry.TenantRegistry) {
|
|
return newTestServiceWithBaseURL(t, "https://cloud.example.com")
|
|
}
|
|
|
|
func newTestServiceWithBaseURL(t *testing.T, baseURL string) (*Service, ed25519.PublicKey, *registry.TenantRegistry) {
|
|
t.Helper()
|
|
|
|
pub, priv, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
t.Fatalf("GenerateKey: %v", err)
|
|
}
|
|
reg, err := registry.NewTenantRegistry(t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("NewTenantRegistry: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = reg.Close() })
|
|
|
|
svc := NewService(reg, baseURL, base64.StdEncoding.EncodeToString(priv))
|
|
return svc, pub, reg
|
|
}
|
|
|
|
func TestIssueTenantBillingStateReturnsLeaseOnlyState(t *testing.T) {
|
|
svc, pub, reg := newTestService(t)
|
|
|
|
accountID, err := registry.GenerateAccountID()
|
|
if err != nil {
|
|
t.Fatalf("GenerateAccountID: %v", err)
|
|
}
|
|
if err := reg.CreateAccount(®istry.Account{
|
|
ID: accountID,
|
|
Kind: registry.AccountKindIndividual,
|
|
DisplayName: "Pulse Labs",
|
|
}); err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
if err := reg.CreateStripeAccount(®istry.StripeAccount{
|
|
AccountID: accountID,
|
|
StripeCustomerID: "cus_paid_service",
|
|
PlanVersion: "cloud_starter",
|
|
SubscriptionState: "active",
|
|
}); err != nil {
|
|
t.Fatalf("CreateStripeAccount: %v", err)
|
|
}
|
|
|
|
tenant := ®istry.Tenant{
|
|
ID: "t-SERVICE01",
|
|
AccountID: accountID,
|
|
Email: "owner@example.com",
|
|
State: registry.TenantStateActive,
|
|
StripeCustomerID: "cus_paid_service",
|
|
PlanVersion: "cloud_starter",
|
|
}
|
|
if err := reg.Create(tenant); err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
|
|
now := time.Unix(1710000000, 0).UTC()
|
|
svc.SetNow(func() time.Time { return now })
|
|
|
|
state, err := svc.IssueTenantBillingState(tenant, pkglicensing.SubStateActive, "cloud_starter", "cus_paid_service", "sub_paid_service", "price_paid_service")
|
|
if err != nil {
|
|
t.Fatalf("IssueTenantBillingState: %v", err)
|
|
}
|
|
if state == nil {
|
|
t.Fatal("expected billing state")
|
|
}
|
|
if state.EntitlementJWT == "" || state.EntitlementRefreshToken == "" {
|
|
t.Fatalf("expected signed lease and refresh token, got %#v", state)
|
|
}
|
|
if state.PlanVersion != "" || state.SubscriptionState != "" {
|
|
t.Fatalf("expected lease-only state, got plan=%q subscription=%q", state.PlanVersion, state.SubscriptionState)
|
|
}
|
|
|
|
claims, err := pkglicensing.VerifyEntitlementLeaseToken(state.EntitlementJWT, pub, "t-SERVICE01.cloud.example.com", now)
|
|
if err != nil {
|
|
t.Fatalf("VerifyEntitlementLeaseToken: %v", err)
|
|
}
|
|
if claims.PlanVersion != "cloud_starter" {
|
|
t.Fatalf("claims.PlanVersion=%q, want %q", claims.PlanVersion, "cloud_starter")
|
|
}
|
|
if claims.SubscriptionState != pkglicensing.SubStateActive {
|
|
t.Fatalf("claims.SubscriptionState=%q, want %q", claims.SubscriptionState, pkglicensing.SubStateActive)
|
|
}
|
|
}
|
|
|
|
func TestIssueTenantBillingStateDefaultsIndividualCloudPlanToStarter(t *testing.T) {
|
|
svc, pub, reg := newTestService(t)
|
|
|
|
tenant := ®istry.Tenant{
|
|
ID: "t-SERVICE04",
|
|
Email: "owner@example.com",
|
|
State: registry.TenantStateActive,
|
|
}
|
|
if err := reg.Create(tenant); err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
|
|
now := time.Unix(1710000100, 0).UTC()
|
|
svc.SetNow(func() time.Time { return now })
|
|
|
|
state, err := svc.IssueTenantBillingState(tenant, pkglicensing.SubStateActive, "", "", "", "")
|
|
if err != nil {
|
|
t.Fatalf("IssueTenantBillingState: %v", err)
|
|
}
|
|
if state == nil {
|
|
t.Fatal("expected billing state")
|
|
}
|
|
|
|
claims, err := pkglicensing.VerifyEntitlementLeaseToken(state.EntitlementJWT, pub, "t-SERVICE04.cloud.example.com", now)
|
|
if err != nil {
|
|
t.Fatalf("VerifyEntitlementLeaseToken: %v", err)
|
|
}
|
|
if claims.PlanVersion != "cloud_starter" {
|
|
t.Fatalf("claims.PlanVersion=%q, want %q", claims.PlanVersion, "cloud_starter")
|
|
}
|
|
if got := claims.Limits["max_monitored_systems"]; got != 10 {
|
|
t.Fatalf("claims.Limits[max_monitored_systems]=%d, want 10", got)
|
|
}
|
|
}
|
|
|
|
func TestIssueTenantBillingStateCanonicalizesLegacyStoredPlanVersion(t *testing.T) {
|
|
svc, pub, reg := newTestService(t)
|
|
|
|
accountID, err := registry.GenerateAccountID()
|
|
if err != nil {
|
|
t.Fatalf("GenerateAccountID: %v", err)
|
|
}
|
|
if err := reg.CreateAccount(®istry.Account{
|
|
ID: accountID,
|
|
Kind: registry.AccountKindIndividual,
|
|
DisplayName: "Pulse Labs",
|
|
}); err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
if err := reg.CreateStripeAccount(®istry.StripeAccount{
|
|
AccountID: accountID,
|
|
StripeCustomerID: "cus_legacy_plan",
|
|
PlanVersion: "cloud-v1",
|
|
SubscriptionState: "active",
|
|
}); err != nil {
|
|
t.Fatalf("CreateStripeAccount: %v", err)
|
|
}
|
|
|
|
tenant := ®istry.Tenant{
|
|
ID: "t-SERVICE05",
|
|
AccountID: accountID,
|
|
Email: "owner@example.com",
|
|
State: registry.TenantStateActive,
|
|
StripeCustomerID: "cus_legacy_plan",
|
|
PlanVersion: "cloud-v1",
|
|
}
|
|
if err := reg.Create(tenant); err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
|
|
now := time.Unix(1710000200, 0).UTC()
|
|
svc.SetNow(func() time.Time { return now })
|
|
|
|
state, err := svc.IssueTenantBillingState(tenant, pkglicensing.SubStateActive, "", "", "", "")
|
|
if err != nil {
|
|
t.Fatalf("IssueTenantBillingState: %v", err)
|
|
}
|
|
|
|
claims, err := pkglicensing.VerifyEntitlementLeaseToken(state.EntitlementJWT, pub, "t-SERVICE05.cloud.example.com", now)
|
|
if err != nil {
|
|
t.Fatalf("VerifyEntitlementLeaseToken: %v", err)
|
|
}
|
|
if claims.PlanVersion != "cloud_starter" {
|
|
t.Fatalf("claims.PlanVersion=%q, want %q", claims.PlanVersion, "cloud_starter")
|
|
}
|
|
if got := claims.Limits["max_monitored_systems"]; got != 10 {
|
|
t.Fatalf("claims.Limits[max_monitored_systems]=%d, want 10", got)
|
|
}
|
|
}
|
|
|
|
func TestRefreshPaidEntitlementRejectsTargetMismatch(t *testing.T) {
|
|
svc, _, reg := newTestService(t)
|
|
|
|
tenant := ®istry.Tenant{
|
|
ID: "t-SERVICE02",
|
|
Email: "owner@example.com",
|
|
State: registry.TenantStateActive,
|
|
}
|
|
if err := reg.Create(tenant); err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
if _, _, err := reg.StoreOrIssueHostedEntitlement(tenant.ID, "etr_paid_service", time.Unix(1710000000, 0).UTC()); err != nil {
|
|
t.Fatalf("StoreOrIssueHostedEntitlement: %v", err)
|
|
}
|
|
|
|
_, err := svc.RefreshEntitlement("etr_paid_service", "wrong.cloud.example.com")
|
|
if err == nil {
|
|
t.Fatal("expected target mismatch")
|
|
}
|
|
if err != ErrHostedEntitlementTargetMismatch {
|
|
t.Fatalf("err=%v, want %v", err, ErrHostedEntitlementTargetMismatch)
|
|
}
|
|
}
|
|
|
|
func TestRefreshPaidEntitlementCanonicalizesBaseURLForExpectedHost(t *testing.T) {
|
|
svc, pub, reg := newTestServiceWithBaseURL(t, "https://Cloud.Example.com:8443/admin")
|
|
|
|
tenant := ®istry.Tenant{
|
|
ID: "T-SERVICE03",
|
|
Email: "owner@example.com",
|
|
State: registry.TenantStateActive,
|
|
}
|
|
if err := reg.Create(tenant); err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
|
|
now := time.Unix(1710000000, 0).UTC()
|
|
svc.SetNow(func() time.Time { return now })
|
|
if _, _, err := reg.StoreOrIssueHostedEntitlement(tenant.ID, "etr_paid_canonical", now); err != nil {
|
|
t.Fatalf("StoreOrIssueHostedEntitlement: %v", err)
|
|
}
|
|
|
|
result, err := svc.RefreshEntitlement("etr_paid_canonical", "t-service03.cloud.example.com")
|
|
if err != nil {
|
|
t.Fatalf("RefreshEntitlement: %v", err)
|
|
}
|
|
|
|
claims, err := pkglicensing.VerifyEntitlementLeaseToken(result.EntitlementJWT, pub, "t-service03.cloud.example.com", now)
|
|
if err != nil {
|
|
t.Fatalf("VerifyEntitlementLeaseToken: %v", err)
|
|
}
|
|
if claims.InstanceHost != "t-service03.cloud.example.com" {
|
|
t.Fatalf("claims.InstanceHost=%q, want %q", claims.InstanceHost, "t-service03.cloud.example.com")
|
|
}
|
|
}
|
|
|
|
func TestRedeemTrialEntitlementAndRefresh(t *testing.T) {
|
|
svc, pub, reg := newTestServiceWithBaseURL(t, "https://cloud.example.com")
|
|
|
|
now := time.Unix(1710000000, 0).UTC()
|
|
svc.SetNow(func() time.Time { return now })
|
|
|
|
redemption, err := svc.RedeemTrialEntitlement(TrialEntitlementInput{
|
|
RequestID: "trial_request_1",
|
|
OrgID: "default",
|
|
Email: "owner@example.com",
|
|
ReturnURL: "https://pulse.example.com/auth/trial-activate",
|
|
InstanceToken: "tsi_test",
|
|
InstanceHost: "pulse.example.com",
|
|
TrialStartedAt: now,
|
|
IssuedAt: now,
|
|
RedeemedAt: now,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("RedeemTrialEntitlement: %v", err)
|
|
}
|
|
if redemption.EntitlementJWT == "" || redemption.EntitlementRefreshToken == "" {
|
|
t.Fatalf("expected lease and refresh token, got %#v", redemption)
|
|
}
|
|
|
|
loaded, err := reg.GetHostedEntitlementByRefreshToken(redemption.EntitlementRefreshToken)
|
|
if err != nil {
|
|
t.Fatalf("GetHostedEntitlementByRefreshToken: %v", err)
|
|
}
|
|
if loaded == nil || loaded.Kind != registry.HostedEntitlementKindTrial {
|
|
t.Fatalf("expected trial entitlement record, got %#v", loaded)
|
|
}
|
|
|
|
refreshResult, err := svc.RefreshEntitlement(redemption.EntitlementRefreshToken, "pulse.example.com")
|
|
if err != nil {
|
|
t.Fatalf("RefreshEntitlement: %v", err)
|
|
}
|
|
claims, err := pkglicensing.VerifyEntitlementLeaseToken(refreshResult.EntitlementJWT, pub, "pulse.example.com", now)
|
|
if err != nil {
|
|
t.Fatalf("VerifyEntitlementLeaseToken: %v", err)
|
|
}
|
|
if claims.SubscriptionState != pkglicensing.SubStateTrial {
|
|
t.Fatalf("claims.SubscriptionState=%q, want %q", claims.SubscriptionState, pkglicensing.SubStateTrial)
|
|
}
|
|
if got := claims.Limits[pkglicensing.MaxMonitoredSystemsLicenseGateKey]; got != int64(pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierPro]) {
|
|
t.Fatalf(
|
|
"claims.Limits[%s]=%d, want %d",
|
|
pkglicensing.MaxMonitoredSystemsLicenseGateKey,
|
|
got,
|
|
pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierPro],
|
|
)
|
|
}
|
|
}
|