| package patch_test |
|
|
| import ( |
| "errors" |
| "testing" |
| "time" |
|
|
| "github.com/samber/lo" |
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/require" |
|
|
| "github.com/openmeterio/openmeter/openmeter/productcatalog" |
| psubs "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" |
| "github.com/openmeterio/openmeter/openmeter/subscription" |
| "github.com/openmeterio/openmeter/openmeter/subscription/patch" |
| 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/currencyx" |
| "github.com/openmeterio/openmeter/pkg/datetime" |
| "github.com/openmeterio/openmeter/pkg/models" |
| ) |
|
|
| func TestRemoveAdd(t *testing.T) { |
| now := testutils.GetRFC3339Time(t, "2021-01-01T00:00:01Z") |
| clock.SetTime(now) |
|
|
| t.Run("Can remove then add an item in a future phase", func(t *testing.T) { |
| s, _ := getDefaultSpec(t, now) |
|
|
| |
| require.GreaterOrEqual(t, len(s.Phases), 3) |
|
|
| p3, ok := s.Phases["test_phase_3"] |
| require.True(t, ok) |
|
|
| require.GreaterOrEqual(t, len(p3.ItemsByKey), 1) |
|
|
| v, ok := p3.ItemsByKey[subscriptiontestutils.ExampleFeatureKey] |
| require.True(t, ok) |
| require.GreaterOrEqual(t, len(v), 1) |
|
|
| |
| rmP := &patch.PatchRemoveItem{ |
| PhaseKey: "test_phase_3", |
| ItemKey: subscriptiontestutils.ExampleFeatureKey, |
| } |
|
|
| |
| nSpec := *v[0] |
| nSpec.RateCard = nSpec.RateCard.Clone() |
| require.NoError(t, nSpec.CreateSubscriptionItemPlanInput.RateCard.ChangeMeta(func(m productcatalog.RateCardMeta) (productcatalog.RateCardMeta, error) { |
| m.Name = "new_name" |
| return m, nil |
| })) |
|
|
| assert.NotEqual(t, "new_name", s.Phases["test_phase_3"].ItemsByKey[subscriptiontestutils.ExampleFeatureKey][0].RateCard.AsMeta().Name) |
|
|
| addP := &patch.PatchAddItem{ |
| PhaseKey: "test_phase_3", |
| ItemKey: subscriptiontestutils.ExampleFeatureKey, |
| CreateInput: nSpec, |
| } |
|
|
| err := s.ApplyMany(lo.Map([]subscription.Patch{rmP, addP}, subscription.ToApplies), subscription.ApplyContext{ |
| CurrentTime: now, |
| }) |
| require.NoError(t, err) |
|
|
| |
| found := s.Phases["test_phase_3"].ItemsByKey[subscriptiontestutils.ExampleFeatureKey][0] |
|
|
| assert.Equal(t, "new_name", found.RateCard.AsMeta().Name) |
| }) |
|
|
| t.Run("Can remove then add an item in the current phase", func(t *testing.T) { |
| s, _ := getDefaultSpec(t, now) |
|
|
| now := now.AddDate(0, 1, 1) |
| latestNowWeGoTu := now.Add(time.Hour * 3) |
|
|
| |
| require.GreaterOrEqual(t, len(s.Phases), 3) |
|
|
| |
| p2, ok := s.Phases["test_phase_2"] |
| require.True(t, ok) |
| p3, ok := s.Phases["test_phase_3"] |
| require.True(t, ok) |
|
|
| p2st, _ := p2.StartAfter.AddTo(s.ActiveFrom) |
| require.True(t, now.After(p2st)) |
| require.True(t, latestNowWeGoTu.After(p2st)) |
| p3st, _ := p3.StartAfter.AddTo(s.ActiveFrom) |
| require.True(t, now.Before(p3st)) |
| require.True(t, latestNowWeGoTu.Before(p3st)) |
|
|
| |
| require.GreaterOrEqual(t, len(p2.ItemsByKey), 1) |
| v, ok := p2.ItemsByKey[subscriptiontestutils.ExampleFeatureKey] |
| require.True(t, ok) |
| require.GreaterOrEqual(t, len(v), 1) |
|
|
| |
| rmP := &patch.PatchRemoveItem{ |
| PhaseKey: "test_phase_2", |
| ItemKey: subscriptiontestutils.ExampleFeatureKey, |
| } |
|
|
| |
| nSpec := *v[0] |
| nSpec.RateCard = nSpec.RateCard.Clone() |
| require.NoError(t, nSpec.CreateSubscriptionItemPlanInput.RateCard.ChangeMeta(func(m productcatalog.RateCardMeta) (productcatalog.RateCardMeta, error) { |
| m.Name = "new_name" |
| return m, nil |
| })) |
|
|
| assert.NotEqual(t, "new_name", s.Phases["test_phase_2"].ItemsByKey[subscriptiontestutils.ExampleFeatureKey][0].RateCard.AsMeta().Name) |
|
|
| addP := &patch.PatchAddItem{ |
| PhaseKey: "test_phase_2", |
| ItemKey: subscriptiontestutils.ExampleFeatureKey, |
| CreateInput: nSpec, |
| } |
|
|
| err := s.ApplyMany(lo.Map([]subscription.Patch{rmP, addP}, subscription.ToApplies), subscription.ApplyContext{ |
| CurrentTime: now, |
| }) |
| require.NoError(t, err) |
|
|
| |
| found := s.Phases["test_phase_2"].ItemsByKey[subscriptiontestutils.ExampleFeatureKey] |
|
|
| assert.Len(t, found, 2) |
| assert.Equal(t, lo.ToPtr(datetime.MustParseDuration(t, "PT86400S")), found[0].ActiveToOverrideRelativeToPhaseStart) |
| assert.Equal(t, lo.ToPtr(datetime.MustParseDuration(t, "PT86400S")), found[1].ActiveFromOverrideRelativeToPhaseStart) |
|
|
| |
| now = now.Add(time.Hour * 1) |
| require.True(t, now.Before(latestNowWeGoTu)) |
|
|
| |
| err = s.ApplyMany(lo.Map([]subscription.Patch{rmP, addP}, subscription.ToApplies), subscription.ApplyContext{ |
| CurrentTime: now, |
| }) |
| require.NoError(t, err) |
|
|
| |
| found = s.Phases["test_phase_2"].ItemsByKey[subscriptiontestutils.ExampleFeatureKey] |
|
|
| assert.Len(t, found, 3) |
| assert.Equal(t, lo.ToPtr(datetime.MustParseDuration(t, "PT86400S")), found[0].ActiveToOverrideRelativeToPhaseStart) |
| assert.Equal(t, lo.ToPtr(datetime.MustParseDuration(t, "PT86400S")), found[1].ActiveFromOverrideRelativeToPhaseStart) |
| |
| assert.Equal(t, lo.ToPtr(datetime.MustParseDuration(t, "PT90000S")), found[1].ActiveToOverrideRelativeToPhaseStart) |
| assert.Equal(t, lo.ToPtr(datetime.MustParseDuration(t, "PT90000S")), found[2].ActiveFromOverrideRelativeToPhaseStart) |
| }) |
| } |
|
|
| |
| type testcase[T subscription.AppliesToSpec] struct { |
| Name string |
| Patch T |
| GetSpec func(t *testing.T) *subscription.SubscriptionSpec |
| Ctx subscription.ApplyContext |
| GetExpectedSpec func(t *testing.T) subscription.SubscriptionSpec |
| ExpectedError error |
| } |
|
|
| type testsuite[T subscription.AppliesToSpec] struct { |
| SystemTime time.Time |
| TT []testcase[T] |
| } |
|
|
| func (ts *testsuite[T]) Run(t *testing.T) { |
| for _, tc := range ts.TT { |
| t.Run(tc.Name, func(t *testing.T) { |
| spec := tc.GetSpec(t) |
|
|
| |
| err := tc.Patch.ApplyTo(spec, tc.Ctx) |
|
|
| if tc.ExpectedError == nil { |
| assert.NoError(t, err) |
| } else { |
| assert.True(t, errors.As(err, lo.ToPtr(tc.ExpectedError.(any)))) |
| assert.EqualError(t, err, tc.ExpectedError.Error()) |
| } |
|
|
| if err == nil { |
| require.NotNil(t, tc.GetExpectedSpec) |
| expectedSpec := tc.GetExpectedSpec(t) |
| subscriptiontestutils.SpecsEqual(t, expectedSpec, *spec) |
| } |
| }) |
| } |
| } |
|
|
| func getDefaultSpec(t *testing.T, activeFrom time.Time) (*subscription.SubscriptionSpec, *psubs.Plan) { |
| pInp := subscriptiontestutils.GetExamplePlanInput(t) |
| p := &psubs.Plan{ |
| Plan: pInp.Plan, |
| Ref: &models.NamespacedID{Namespace: pInp.Namespace, ID: pInp.Key}, |
| } |
|
|
| spec, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ |
| Name: "Test Plan", |
| CustomerId: "test_customer", |
| Currency: currencyx.Code("USD"), |
| ActiveFrom: activeFrom, |
| BillingAnchor: activeFrom, |
| }) |
| require.Nil(t, err) |
|
|
| return &spec, p |
| } |
|
|