openmeter / subscription /addon /service /create_test.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 6)
d6f631f verified
Raw
History Blame Contribute Delete
21 kB
package service_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/oklog/ulid/v2"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
"github.com/openmeterio/openmeter/openmeter/productcatalog"
"github.com/openmeterio/openmeter/openmeter/productcatalog/addon"
"github.com/openmeterio/openmeter/openmeter/productcatalog/plan"
"github.com/openmeterio/openmeter/openmeter/productcatalog/planaddon"
plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription"
"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/openmeter/testutils"
"github.com/openmeterio/openmeter/pkg/clock"
"github.com/openmeterio/openmeter/pkg/models"
)
func TestAddonServiceCreate(t *testing.T) {
now := testutils.GetRFC3339Time(t, "2025-04-01T00:00:00Z")
t.Run("Should error if input is formally invalid", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
p, add := createPlanWithAddon(
t,
deps,
subscriptiontestutils.GetExamplePlanInput(t),
subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
}),
)
// Let's create a subscription
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
ActiveFrom: now,
BillingAnchor: now,
Name: "Test Subscription",
})
require.Nil(t, err)
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: add.ID,
SubscriptionID: sub.NamespacedID.ID,
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 0,
},
}
expErr := subAddonInp.Validate()
require.Error(t, expErr)
_, err = deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Error(t, err)
require.ErrorContains(t, err, expErr.Error())
})
})
t.Run("Should error if addon doesn't exist", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
p, _ := createPlanWithAddon(
t,
deps,
subscriptiontestutils.GetExamplePlanInput(t),
subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
}),
)
// Let's create a subscription
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
ActiveFrom: now,
BillingAnchor: now,
Name: "Test Subscription",
})
require.Nil(t, err)
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: ulid.Make().String(),
SubscriptionID: sub.NamespacedID.ID,
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 1,
},
}
_, err = deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Error(t, err)
require.True(t, models.IsGenericNotFoundError(err))
})
})
t.Run("Should error if subscription doesn't exist", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
// Let's NOT create a subscription
_ = deps.FeatureConnector.CreateExampleFeatures(t, deps.ExampleMeterID)
// Let's create an add
add := deps.AddonService.CreateTestAddon(t, subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
}))
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: add.ID,
SubscriptionID: ulid.Make().String(),
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 1,
},
}
_, err := deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Error(t, err)
require.ErrorAs(t, err, lo.ToPtr(&models.GenericNotFoundError{}))
})
})
t.Run("Should error if addon is single instance but quantity is not 1", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
p, add := createPlanWithAddon(
t,
deps,
subscriptiontestutils.GetExamplePlanInput(t),
subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
}),
)
// Let's create a subscription
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
ActiveFrom: now,
BillingAnchor: now,
Name: "Test Subscription",
})
require.Nil(t, err)
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
// Let's assert that its a single instance addon
require.Equal(t, add.InstanceType, productcatalog.AddonInstanceTypeSingle)
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: add.ID,
SubscriptionID: sub.NamespacedID.ID,
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 3,
},
}
_, err = deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Error(t, err)
require.ErrorAs(t, err, lo.ToPtr(&models.GenericValidationError{}))
})
})
t.Run("Should validate Addon can be purchased for plan of subscription", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
p, _ := createPlanWithAddon(
t,
deps,
subscriptiontestutils.GetExamplePlanInput(t),
subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
}),
)
addonInp := subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
})
addonInp.Key = "unlinked"
add := deps.AddonService.CreateTestAddon(t, addonInp)
// Let's create a subscription
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
ActiveFrom: now,
BillingAnchor: now,
Name: "Test Subscription",
})
require.Nil(t, err)
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: add.ID,
SubscriptionID: sub.NamespacedID.ID,
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 1,
},
}
_, err = deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Error(t, err)
require.ErrorAs(t, err, lo.ToPtr(&models.GenericValidationError{}))
require.ErrorContains(t, err, fmt.Sprintf("addon %s@%d is not linked to the plan %s@%d", add.Key, add.Version, p.ToCreateSubscriptionPlanInput().Plan.Key, p.ToCreateSubscriptionPlanInput().Plan.Version))
})
})
t.Run("Should validate addon can be purchased in target phase", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
// Let's create a plan with addon thats only available in a later phase
planInp := subscriptiontestutils.GetExamplePlanInput(t)
addonInp := subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
})
_ = deps.FeatureConnector.CreateExampleFeatures(t, deps.ExampleMeterID)
p, err := deps.PlanService.CreatePlan(context.Background(), planInp)
require.Nil(t, err)
require.NotNil(t, p)
add := deps.AddonService.CreateTestAddon(t, addonInp)
_, err = deps.PlanAddonService.CreatePlanAddon(context.Background(), planaddon.CreatePlanAddonInput{
NamespacedModel: models.NamespacedModel{
Namespace: subscriptiontestutils.ExampleNamespace,
},
PlanID: p.ID,
AddonID: add.ID,
FromPlanPhase: p.Phases[len(p.Phases)-1].Key, // let's start from last phase
})
require.Nil(t, err, "received error: %s", err)
p, err = deps.PlanService.PublishPlan(context.Background(), plan.PublishPlanInput{
NamespacedID: p.NamespacedID,
EffectivePeriod: productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(clock.Now()),
EffectiveTo: lo.ToPtr(testutils.GetRFC3339Time(t, "2099-01-01T00:00:00Z")),
},
})
require.Nil(t, err, "received error: %s", err)
// Let's create a subscription
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
spec1, err := subscription.NewSpecFromPlan(&plansubscription.Plan{
Plan: p.AsProductCatalogPlan(),
Ref: &p.NamespacedID,
}, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
BillingAnchor: now,
ActiveFrom: now,
Name: "Test Subscription",
})
require.Nil(t, err)
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
aRCIDs := lo.Map(add.RateCards, func(rc addon.RateCard, _ int) string {
return rc.ID
})
require.Len(t, aRCIDs, 1)
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: add.ID,
SubscriptionID: sub.ID,
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 1,
},
}
_, err = deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Error(t, err)
require.ErrorAs(t, err, lo.ToPtr(&models.GenericValidationError{}))
require.ErrorContains(t, err, fmt.Sprintf("addon %s@%d can be only added starting with phase %s, current phase is %s", add.Key, add.Version, "test_phase_3", "test_phase_1"))
})
})
t.Run("Should validate addon quantity doesnt exceed maximum quantity", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
// Let's create a plan with addon thats only available in a later phase
planInp := subscriptiontestutils.GetExamplePlanInput(t)
addonInp := subscriptiontestutils.BuildAddonForTesting(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
}, productcatalog.AddonInstanceTypeMultiple, subscriptiontestutils.ExampleAddonRateCard1.Clone())
_ = deps.FeatureConnector.CreateExampleFeatures(t, deps.ExampleMeterID)
p, err := deps.PlanService.CreatePlan(context.Background(), planInp)
require.Nil(t, err)
require.NotNil(t, p)
add := deps.AddonService.CreateTestAddon(t, addonInp)
_, err = deps.PlanAddonService.CreatePlanAddon(context.Background(), planaddon.CreatePlanAddonInput{
NamespacedModel: models.NamespacedModel{
Namespace: subscriptiontestutils.ExampleNamespace,
},
PlanID: p.ID,
AddonID: add.ID,
FromPlanPhase: p.Phases[0].Key,
MaxQuantity: lo.ToPtr(2),
})
require.Nil(t, err, "received error: %s", err)
p, err = deps.PlanService.PublishPlan(context.Background(), plan.PublishPlanInput{
NamespacedID: p.NamespacedID,
EffectivePeriod: productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(clock.Now()),
EffectiveTo: lo.ToPtr(testutils.GetRFC3339Time(t, "2099-01-01T00:00:00Z")),
},
})
require.Nil(t, err, "received error: %s", err)
// Let's create a subscription
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
spec1, err := subscription.NewSpecFromPlan(&plansubscription.Plan{
Plan: p.AsProductCatalogPlan(),
Ref: &p.NamespacedID,
}, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
BillingAnchor: now,
ActiveFrom: now,
Name: "Test Subscription",
})
require.Nil(t, err)
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
aRCIDs := lo.Map(add.RateCards, func(rc addon.RateCard, _ int) string {
return rc.ID
})
require.Len(t, aRCIDs, 1)
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: add.ID,
SubscriptionID: sub.ID,
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 3,
},
}
_, err = deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Error(t, err)
require.ErrorAs(t, err, lo.ToPtr(&models.GenericValidationError{}))
require.ErrorContains(t, err, fmt.Sprintf("addon %s@%d can be added a maximum of %d times", add.Key, add.Version, 2))
})
})
t.Run("Should not allow purchasing Addon for a custom Subscription", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
// Let's create a subscription without a plan reference
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
_ = deps.FeatureConnector.CreateExampleFeatures(t, deps.ExampleMeterID)
plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t))
spec1, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
ActiveFrom: now,
BillingAnchor: now,
Name: "Test Subscription",
})
require.Nil(t, err)
spec1.Plan = nil
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
// Let's create an addon
add := deps.AddonService.CreateTestAddon(t, subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
}))
aRCIDs := lo.Map(add.RateCards, func(rc addon.RateCard, _ int) string {
return rc.ID
})
require.Len(t, aRCIDs, 1)
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: add.ID,
SubscriptionID: sub.NamespacedID.ID,
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 1,
},
}
_, err = deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Error(t, err)
require.ErrorAs(t, err, lo.ToPtr(&models.GenericValidationError{}))
require.ErrorContains(t, err, "cannot add addon to a custom subscription")
})
})
t.Run("Should create and retrieve addon", func(t *testing.T) {
withDeps(t, func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies) {
clock.SetTime(now)
defer clock.ResetTime()
p, add := createPlanWithAddon(
t,
deps,
subscriptiontestutils.GetExamplePlanInput(t),
subscriptiontestutils.GetExampleAddonInput(t, productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(now),
}),
)
// Let's create a subscription
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
BillingAnchor: now,
ActiveFrom: now,
Name: "Test Subscription",
})
require.Nil(t, err)
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
aRCIDs := lo.Map(add.RateCards, func(rc addon.RateCard, _ int) string {
return rc.ID
})
require.Len(t, aRCIDs, 1)
// Now, let's create a SubscriptionAddon
subAddonInp := subscriptionaddon.CreateSubscriptionAddonInput{
AddonID: add.ID,
SubscriptionID: sub.ID,
InitialQuantity: subscriptionaddon.CreateSubscriptionAddonQuantityInput{
ActiveFrom: now,
Quantity: 1,
},
}
subAdd1, err := deps.SubscriptionAddonService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, subAddonInp)
require.Nil(t, err)
// Now, let's fetch the subscription addon
subAdd2, err := deps.SubscriptionAddonService.Get(t.Context(), subscriptionaddon.GetSubscriptionAddonInput{
NamespacedID: subAdd1.NamespacedID,
})
require.Nil(t, err)
t.Run("Should create addon as specified", func(t *testing.T) {
require.Equal(t, subAddonInp.AddonID, subAdd1.Addon.ID)
require.Equal(t, subAddonInp.SubscriptionID, subAdd1.SubscriptionID)
require.Len(t, subAdd1.RateCards, 1)
require.Equal(t, aRCIDs[0], subAdd1.RateCards[0].AddonRateCard.ID)
})
t.Run("Should return same addon on create and then a subsequent get", func(t *testing.T) {
subscriptiontestutils.SubscriptionAddonsEqual(t, *subAdd1, *subAdd2)
})
})
})
}
func createPlanWithAddon(
t *testing.T,
deps subscriptiontestutils.SubscriptionDependencies,
planInp plan.CreatePlanInput,
addonInp addon.CreateAddonInput,
) (subscription.Plan, addon.Addon) {
t.Helper()
_ = deps.FeatureConnector.CreateExampleFeatures(t, deps.ExampleMeterID)
p, err := deps.PlanService.CreatePlan(context.Background(), planInp)
require.Nil(t, err)
require.NotNil(t, p)
add := deps.AddonService.CreateTestAddon(t, addonInp)
_, err = deps.PlanAddonService.CreatePlanAddon(context.Background(), planaddon.CreatePlanAddonInput{
NamespacedModel: models.NamespacedModel{
Namespace: subscriptiontestutils.ExampleNamespace,
},
PlanID: p.ID,
AddonID: add.ID,
FromPlanPhase: p.Phases[0].Key,
})
require.Nil(t, err, "received error: %s", err)
p, err = deps.PlanService.PublishPlan(context.Background(), plan.PublishPlanInput{
NamespacedID: p.NamespacedID,
EffectivePeriod: productcatalog.EffectivePeriod{
EffectiveFrom: lo.ToPtr(clock.Now()),
EffectiveTo: lo.ToPtr(testutils.GetRFC3339Time(t, "2099-01-01T00:00:00Z")),
},
})
require.Nil(t, err, "received error: %s", err)
return &plansubscription.Plan{
Plan: p.AsProductCatalogPlan(),
Ref: &p.NamespacedID,
}, add
}
func createExampleSubscription(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies, currentTime time.Time) subscription.SubscriptionView {
cust := deps.CustomerAdapter.CreateExampleCustomer(t)
_ = deps.FeatureConnector.CreateExampleFeatures(t, deps.ExampleMeterID)
plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t))
spec1, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{
CustomerId: cust.ID,
Currency: "USD",
BillingAnchor: currentTime,
ActiveFrom: currentTime,
Name: "Test Subscription",
})
require.Nil(t, err)
sub, err := deps.SubscriptionService.Create(context.Background(), subscriptiontestutils.ExampleNamespace, spec1)
require.Nil(t, err)
view, err := deps.SubscriptionService.GetView(context.Background(), sub.NamespacedID)
require.Nil(t, err)
return view
}
func withDeps(t *testing.T, fn func(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies)) {
dbDeps := subscriptiontestutils.SetupDBDeps(t)
deps := subscriptiontestutils.NewService(t, dbDeps)
defer dbDeps.Cleanup(t)
fn(t, deps)
}