openmeter / subscription /addon /extend_test.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 6)
d6f631f verified
Raw
History Blame Contribute Delete
30.7 kB
package subscriptionaddon_test
import (
"fmt"
"testing"
"github.com/alpacahq/alpacadecimal"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
"github.com/openmeterio/openmeter/openmeter/ent/db/entitlement"
"github.com/openmeterio/openmeter/openmeter/productcatalog"
"github.com/openmeterio/openmeter/openmeter/productcatalog/addon"
"github.com/openmeterio/openmeter/openmeter/subscription"
subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon"
subscriptiontestutils "github.com/openmeterio/openmeter/openmeter/subscription/testutils"
"github.com/openmeterio/openmeter/pkg/datetime"
"github.com/openmeterio/openmeter/pkg/models"
)
func TestValidations(t *testing.T) {
someMeta := productcatalog.RateCardMeta{
Name: "Test Addon Rate Card 4",
Description: lo.ToPtr("Test Addon Rate Card 4 Description"),
Key: subscriptiontestutils.ExampleFeatureKey,
FeatureKey: lo.ToPtr(subscriptiontestutils.ExampleFeatureKey),
Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(100),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
}),
}
t.Run("Should error if provided RateCard is nil", func(t *testing.T) {
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
})
t.Run("Apply", func(t *testing.T) {
err := rc.Apply(nil, nil)
require.Error(t, err)
require.ErrorContains(t, err, "target must not be nil")
})
t.Run("Restore", func(t *testing.T) {
err := rc.Restore(nil, nil, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "target must not be nil")
})
})
t.Run("Should error if provided RateCard is not a pointer", func(t *testing.T) {
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
})
t.Run("Apply", func(t *testing.T) {
err := rc.Apply(nonPointerRateCard{}, nil)
require.Error(t, err)
require.ErrorContains(t, err, "target must be a pointer")
})
t.Run("Restore", func(t *testing.T) {
err := rc.Restore(nonPointerRateCard{}, nil, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "target must be a pointer")
})
})
t.Run("Should error if provided annotations are nil", func(t *testing.T) {
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
})
t.Run("Apply", func(t *testing.T) {
err := rc.Apply(&productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
}, nil)
require.Error(t, err)
require.ErrorContains(t, err, "annotations must not be nil")
})
t.Run("Restore", func(t *testing.T) {
err := rc.Restore(&productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
}, nil, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "annotations must not be nil")
})
})
t.Run("Should error if target RateCard has different price type", func(t *testing.T) {
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
})
meta := someMeta.Clone()
meta.Price = productcatalog.NewPriceFrom(productcatalog.UnitPrice{
Amount: alpacadecimal.NewFromInt(100),
})
t.Run("Apply", func(t *testing.T) {
err := rc.Apply(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta,
}, models.Annotations{})
require.Error(t, err)
require.ErrorContains(t, err, "price type must match")
})
t.Run("Restore", func(t *testing.T) {
err := rc.Restore(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta,
BillingCadence: datetime.MustParseDuration(t, "P1M"),
}, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
})
})
t.Run("Should error if target RateCard has different entitlement type", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.BooleanEntitlementTemplate{})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta,
})
meta = someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.StaticEntitlementTemplate{})
t.Run("Apply", func(t *testing.T) {
err := rc.Apply(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta,
}, models.Annotations{})
require.Error(t, err)
require.ErrorContains(t, err, "entitlement template type must match")
})
t.Run("Restore", func(t *testing.T) {
err := rc.Restore(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta,
}, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "entitlement template type must match")
})
})
t.Run("Should error if target RateCard has a different key", func(t *testing.T) {
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
})
meta := someMeta.Clone()
meta.Key = "different-key"
t.Run("Apply", func(t *testing.T) {
err := rc.Apply(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta,
}, models.Annotations{})
require.Error(t, err)
require.ErrorContains(t, err, "key must match")
})
t.Run("Restore", func(t *testing.T) {
err := rc.Restore(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta,
}, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "key must match")
})
})
t.Run("Should error if target Price has different payment term", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(100),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(50),
PaymentTerm: productcatalog.InArrearsPaymentTerm,
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
t.Run("Apply", func(t *testing.T) {
err := rc.Apply(target, models.Annotations{})
require.Error(t, err)
require.ErrorContains(t, err, "price payment term must match")
})
t.Run("Restore", func(t *testing.T) {
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "flat price would yield a negative amount")
})
})
t.Run("Should error on UsageBasedRateCards with different BillingCadence", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = nil
// The addon would do nothing (which is valid)
rc := getTestAddonRateCard(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P2M"),
})
target := &productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
}
err := rc.Apply(target, models.Annotations{})
require.Error(t, err)
require.ErrorContains(t, err, "billing cadence must match")
})
t.Run("Should error on UsageBasedRateCards with divergent UnitConfig", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = nil
addonMeta := meta.Clone()
addonMeta.UnitConfig = &productcatalog.UnitConfig{
Operation: productcatalog.UnitConfigOperationDivide,
ConversionFactor: alpacadecimal.NewFromInt(500),
}
rc := getTestAddonRateCard(&productcatalog.UsageBasedRateCard{
RateCardMeta: addonMeta,
BillingCadence: datetime.MustParseDuration(t, "P1M"),
})
targetMeta := meta.Clone()
targetMeta.UnitConfig = &productcatalog.UnitConfig{
Operation: productcatalog.UnitConfigOperationDivide,
ConversionFactor: alpacadecimal.NewFromInt(1000),
}
target := &productcatalog.UsageBasedRateCard{
RateCardMeta: targetMeta,
BillingCadence: datetime.MustParseDuration(t, "P1M"),
}
err := rc.Apply(target, models.Annotations{})
require.Error(t, err)
require.ErrorContains(t, err, "unit config must match")
})
}
func TestExtendApply(t *testing.T) {
someMeta := productcatalog.RateCardMeta{
Name: "Test Addon Rate Card 4",
Description: lo.ToPtr("Test Addon Rate Card 4 Description"),
Key: subscriptiontestutils.ExampleFeatureKey,
FeatureKey: lo.ToPtr(subscriptiontestutils.ExampleFeatureKey),
Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(100),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
}),
}
t.Run("Should not change target RateCard if addon RateCard has no entitlement & no price", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = nil
meta.Price = nil
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta,
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
}
targetClone := target.Clone()
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
require.Equal(t, targetClone, target)
})
t.Run("Should keep FlatPrice of target if addon has no price", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = nil
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(100),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
fp, err := target.AsMeta().Price.AsFlat()
require.NoError(t, err)
require.Equal(t, alpacadecimal.NewFromInt(100), fp.Amount)
})
t.Run("Should update target Price when FlatPrice", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(100),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(50),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
fp, err := target.AsMeta().Price.AsFlat()
require.NoError(t, err)
require.Equal(t, alpacadecimal.NewFromInt(150), fp.Amount)
})
t.Run("Should add addon price to target", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(100),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.Price = nil
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
fp, err := target.AsMeta().Price.AsFlat()
require.NoError(t, err)
require.Equal(t, fp.Amount, alpacadecimal.NewFromInt(100))
})
t.Run("Should add addon entitlement to target", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.BooleanEntitlementTemplate{})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.EntitlementTemplate = nil
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
require.Equal(t, string(entitlement.EntitlementTypeBoolean), string(target.AsMeta().EntitlementTemplate.Type()))
})
t.Run("Should keep BooleanEntitlement of target if addon has no entitlement", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = nil
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.BooleanEntitlementTemplate{})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
require.Equal(t, string(entitlement.EntitlementTypeBoolean), string(target.AsMeta().EntitlementTemplate.Type()))
})
t.Run("Should set MeteredEntitlement of addon to target", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{
IssueAfterReset: lo.ToPtr(100.0),
UsagePeriod: datetime.MustParseDuration(t, "P1M"),
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.EntitlementTemplate = nil
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
me, err := target.AsMeta().EntitlementTemplate.AsMetered()
require.NoError(t, err)
require.NotNil(t, me.IsSoftLimit)
require.Equal(t, 100.0, *me.IssueAfterReset)
})
t.Run("Should combine issueAfterReset for metered entitlements treating nil as 0", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{
IssueAfterReset: lo.ToPtr(100.0),
UsagePeriod: datetime.MustParseDuration(t, "P1M"),
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.EntitlementTemplate = nil
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
me, err := target.AsMeta().EntitlementTemplate.AsMetered()
require.NoError(t, err)
require.NotNil(t, me.IssueAfterReset)
require.Equal(t, 100.0, *me.IssueAfterReset)
err = rc.Apply(target, models.Annotations{})
require.NoError(t, err)
me, err = target.AsMeta().EntitlementTemplate.AsMetered()
require.NoError(t, err)
require.NotNil(t, me.IssueAfterReset)
require.Equal(t, 200.0, *me.IssueAfterReset)
})
t.Run("Should add Usage Discounts from UsageBasedRateCard to target with UnitPrice", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = nil
meta.Discounts = productcatalog.Discounts{
Usage: &productcatalog.UsageDiscount{
Quantity: alpacadecimal.NewFromInt(100),
},
}
rc := getTestAddonRateCard(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
})
meta.Discounts = productcatalog.Discounts{}
meta.Price = productcatalog.NewPriceFrom(productcatalog.UnitPrice{
Amount: alpacadecimal.NewFromInt(1),
})
target := &productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
}
err := rc.Apply(target, models.Annotations{})
require.NoError(t, err)
require.Equal(t, alpacadecimal.NewFromInt(100).InexactFloat64(), target.AsMeta().Discounts.Usage.Quantity.InexactFloat64())
})
}
func TestExtendRestore(t *testing.T) {
someMeta := productcatalog.RateCardMeta{
Name: "Test Addon Rate Card 4",
Description: lo.ToPtr("Test Addon Rate Card 4 Description"),
Key: subscriptiontestutils.ExampleFeatureKey,
FeatureKey: lo.ToPtr(subscriptiontestutils.ExampleFeatureKey),
Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(100),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
}),
}
t.Run("Should not change target RateCard if addon RateCard has no entitlement & no price", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = nil
meta.Price = nil
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta,
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
}
targetClone := target.Clone()
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
require.Equal(t, targetClone, target)
})
t.Run("Should error if target Price is nil but addon has a flat price", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = nil
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.Price = nil
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "target price is nil, cannot restore price without addon")
})
t.Run("Should deduct flat price of addon from target", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = nil
meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(10),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: someMeta.Clone(),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
fp, err := target.AsMeta().Price.AsFlat()
require.NoError(t, err)
require.Equal(t, alpacadecimal.NewFromInt(90), fp.Amount)
})
t.Run("Should allow 0 resulting flat price", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = nil
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
fp, err := target.AsMeta().Price.AsFlat()
require.NoError(t, err)
require.Equal(t, alpacadecimal.NewFromInt(0), fp.Amount)
})
t.Run("Should error if target has no entitlement but addon has a metered entitlement", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{
IssueAfterReset: lo.ToPtr(100.0),
UsagePeriod: datetime.MustParseDuration(t, "P1M"),
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.EntitlementTemplate = nil
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "target entitlement template is nil, cannot restore entitlement template without addon")
})
t.Run("Should error if target has boolean entitlement but no annotation", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.BooleanEntitlementTemplate{})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "target doesn't have boolean entitlement count annotation while has a boolean entitlement template")
})
t.Run("Should error if target has boolean entitlement but annotation is invalid (negative count)", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.BooleanEntitlementTemplate{})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
ann := models.Annotations{}
if _, err := subscription.AnnotationParser.SetBooleanEntitlementCount(ann, -1); err != nil {
t.Fatalf("failed to set boolean entitlement count: %s", err)
}
err := rc.Restore(target, ann, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "received invalid entitlement count annotation value: -1")
})
t.Run("Should error if target has boolean entitlement with annotation value of 0", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.BooleanEntitlementTemplate{})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
ann := models.Annotations{}
if _, err := subscription.AnnotationParser.SetBooleanEntitlementCount(ann, 0); err != nil {
t.Fatalf("failed to set boolean entitlement count: %s", err)
}
err := rc.Restore(target, ann, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "target doesn't have boolean entitlement count annotation while has a boolean entitlement template")
})
t.Run("Should remove boolean entitlement if annotation value falls to 0", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.BooleanEntitlementTemplate{})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
ann := models.Annotations{}
if _, err := subscription.AnnotationParser.SetBooleanEntitlementCount(ann, 1); err != nil {
t.Fatalf("failed to set boolean entitlement count: %s", err)
}
err := rc.Restore(target, ann, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
require.Nil(t, target.EntitlementTemplate)
})
t.Run("Should decrement boolean entitlement count", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.BooleanEntitlementTemplate{})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
ann := models.Annotations{}
if _, err := subscription.AnnotationParser.SetBooleanEntitlementCount(ann, 2); err != nil {
t.Fatalf("failed to set boolean entitlement count: %s", err)
}
err := rc.Restore(target, ann, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
require.NotNil(t, target.EntitlementTemplate)
require.Equal(t, 1, subscription.AnnotationParser.GetBooleanEntitlementCount(ann))
})
t.Run("Should deduct issueAfterReset of addon from target", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{
IssueAfterReset: lo.ToPtr(10.0),
UsagePeriod: datetime.MustParseDuration(t, "P1M"),
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{
IssueAfterReset: lo.ToPtr(100.0),
UsagePeriod: datetime.MustParseDuration(t, "P1M"),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
me, err := target.AsMeta().EntitlementTemplate.AsMetered()
require.NoError(t, err)
require.Equal(t, 90.0, *me.IssueAfterReset)
})
t.Run("Should not allow negative issueAfterReset", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{
IssueAfterReset: lo.ToPtr(100.0),
UsagePeriod: datetime.MustParseDuration(t, "P1M"),
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{
IssueAfterReset: lo.ToPtr(50.0),
UsagePeriod: datetime.MustParseDuration(t, "P1M"),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "restoring entitlement template would yield a negative issue after reset: 50 - 100 = -50")
})
t.Run("Should allow 0 resulting issueAfterReset", func(t *testing.T) {
meta := someMeta.Clone()
meta.EntitlementTemplate = productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{
IssueAfterReset: lo.ToPtr(100.0),
UsagePeriod: datetime.MustParseDuration(t, "P1M"),
})
rc := getTestAddonRateCard(&productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
})
target := &productcatalog.FlatFeeRateCard{
RateCardMeta: meta.Clone(),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
me, err := target.AsMeta().EntitlementTemplate.AsMetered()
require.NoError(t, err)
require.Equal(t, 0.0, *me.IssueAfterReset)
})
t.Run("Should error if trying to restore Usage Discount when one is not present on target", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = nil
meta.Discounts = productcatalog.Discounts{
Usage: &productcatalog.UsageDiscount{
Quantity: alpacadecimal.NewFromInt(100),
},
}
rc := getTestAddonRateCard(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
})
meta.Discounts = productcatalog.Discounts{}
meta.Price = productcatalog.NewPriceFrom(productcatalog.UnitPrice{
Amount: alpacadecimal.NewFromInt(1),
})
target := &productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, "target doesn't have usage discount while addon has a usage discount")
})
t.Run("Should error if trying to restore Usage Discount greater than target's discount", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = nil
meta.Discounts = productcatalog.Discounts{
Usage: &productcatalog.UsageDiscount{
Quantity: alpacadecimal.NewFromInt(100),
},
}
rc := getTestAddonRateCard(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
})
meta.Discounts = productcatalog.Discounts{
Usage: &productcatalog.UsageDiscount{
Quantity: alpacadecimal.NewFromInt(50),
},
}
meta.Price = productcatalog.NewPriceFrom(productcatalog.UnitPrice{
Amount: alpacadecimal.NewFromInt(1),
})
target := &productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.Error(t, err)
require.ErrorContains(t, err, fmt.Sprintf("target has %.0f usage discount which is less than addon's %.0f", 50.0, 100.0))
})
t.Run("Should restore Usage Discounts from UsageBasedRateCard to target with UnitPrice", func(t *testing.T) {
meta := someMeta.Clone()
meta.Price = nil
meta.Discounts = productcatalog.Discounts{
Usage: &productcatalog.UsageDiscount{
Quantity: alpacadecimal.NewFromInt(100),
},
}
rc := getTestAddonRateCard(&productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
})
meta.Discounts = productcatalog.Discounts{
Usage: &productcatalog.UsageDiscount{
Quantity: alpacadecimal.NewFromInt(150),
},
}
meta.Price = productcatalog.NewPriceFrom(productcatalog.UnitPrice{
Amount: alpacadecimal.NewFromInt(1),
})
target := &productcatalog.UsageBasedRateCard{
RateCardMeta: meta.Clone(),
BillingCadence: datetime.MustParseDuration(t, "P1M"),
}
err := rc.Restore(target, models.Annotations{}, productcatalog.AddonInstanceTypeSingle)
require.NoError(t, err)
require.Equal(t, alpacadecimal.NewFromInt(50).InexactFloat64(), target.AsMeta().Discounts.Usage.Quantity.InexactFloat64())
})
}
var _ productcatalog.RateCard = nonPointerRateCard{}
type nonPointerRateCard struct{}
func (n nonPointerRateCard) HasFeature() bool {
return false
}
func (n nonPointerRateCard) GetFeatureID() *string {
return nil
}
func (n nonPointerRateCard) GetFeatureKey() *string {
return nil
}
func (n nonPointerRateCard) SetFeature(_, _ *string) {
}
func (n nonPointerRateCard) IsBillable() bool {
return true
}
func (n nonPointerRateCard) AsMeta() productcatalog.RateCardMeta {
return productcatalog.RateCardMeta{}
}
func (n nonPointerRateCard) ChangeMeta(func(m productcatalog.RateCardMeta) (productcatalog.RateCardMeta, error)) error {
return nil
}
func (n nonPointerRateCard) Clone() productcatalog.RateCard {
return n
}
func (n nonPointerRateCard) Compatible(productcatalog.RateCard) error {
return nil
}
func (n nonPointerRateCard) Equal(productcatalog.RateCard) bool {
return false
}
func (n nonPointerRateCard) Validate() error {
return nil
}
func (n nonPointerRateCard) GetBillingCadence() *datetime.ISODuration {
return nil
}
func (n nonPointerRateCard) Key() string {
return subscriptiontestutils.ExampleFeatureKey
}
func (n nonPointerRateCard) Merge(productcatalog.RateCard) error {
return nil
}
func (n nonPointerRateCard) Type() productcatalog.RateCardType {
return productcatalog.FlatFeeRateCardType
}
func getTestAddonRateCard(rc productcatalog.RateCard) subscriptionaddon.SubscriptionAddonRateCard {
return subscriptionaddon.SubscriptionAddonRateCard{
AddonRateCard: addon.RateCard{
RateCard: rc,
},
}
}