| package subscriptiontestutils |
|
|
| import ( |
| "fmt" |
| "testing" |
|
|
| "github.com/invopop/gobl/currency" |
| "github.com/samber/lo" |
| "github.com/stretchr/testify/require" |
|
|
| "github.com/openmeterio/openmeter/openmeter/productcatalog" |
| "github.com/openmeterio/openmeter/openmeter/productcatalog/plan" |
| "github.com/openmeterio/openmeter/openmeter/subscription" |
| "github.com/openmeterio/openmeter/pkg/clock" |
| "github.com/openmeterio/openmeter/pkg/currencyx" |
| "github.com/openmeterio/openmeter/pkg/datetime" |
| "github.com/openmeterio/openmeter/pkg/models" |
| ) |
|
|
| |
| type testPlanbuilder struct { |
| p plan.CreatePlanInput |
| } |
|
|
| func (b *testPlanbuilder) AddPhase(dur *datetime.ISODuration, rcs ...productcatalog.RateCard) *testPlanbuilder { |
| idx := len(b.p.Plan.Phases) + 1 |
|
|
| b.p.Plan.Phases = append(b.p.Plan.Phases, productcatalog.Phase{ |
| PhaseMeta: productcatalog.PhaseMeta{ |
| Key: fmt.Sprintf("test_phase_%d", idx), |
| Name: fmt.Sprintf("Test Phase %d", idx), |
| Description: lo.ToPtr(fmt.Sprintf("Test Phase %d Description", idx)), |
| Duration: dur, |
| }, |
| RateCards: rcs, |
| }) |
|
|
| return b |
| } |
|
|
| func (b *testPlanbuilder) SetMeta(meta productcatalog.PlanMeta) *testPlanbuilder { |
| b.p.Plan.PlanMeta = meta |
| return b |
| } |
|
|
| func (b *testPlanbuilder) Build() plan.CreatePlanInput { |
| return b.p |
| } |
|
|
| func BuildTestPlanInput(t *testing.T) *testPlanbuilder { |
| b := &testPlanbuilder{ |
| p: plan.CreatePlanInput{ |
| NamespacedModel: models.NamespacedModel{ |
| Namespace: ExampleNamespace, |
| }, |
| Plan: productcatalog.Plan{ |
| PlanMeta: productcatalog.PlanMeta{ |
| Name: "Test Plan", |
| Key: "test_plan", |
| Version: 1, |
| Currency: currency.USD, |
| BillingCadence: datetime.MustParseDuration(t, "P1M"), |
| ProRatingConfig: productcatalog.ProRatingConfig{ |
| Enabled: true, |
| Mode: productcatalog.ProRatingModeProratePrices, |
| }, |
| SettlementMode: productcatalog.CreditThenInvoiceSettlementMode, |
| }, |
| Phases: []productcatalog.Phase{}, |
| }, |
| }, |
| } |
|
|
| return b |
| } |
|
|
| type testSubscriptionSpecBuilder struct { |
| s subscription.SubscriptionSpec |
| t *testing.T |
| } |
|
|
| func (b *testSubscriptionSpecBuilder) AddPhase(dur *datetime.ISODuration, rcs ...productcatalog.RateCard) *testSubscriptionSpecBuilder { |
| idx := len(b.s.Phases) + 1 |
| startAfter := datetime.ISODurationBetween(b.s.ActiveFrom, b.s.ActiveFrom) |
|
|
| if idx > 1 { |
| phases := b.s.GetSortedPhases() |
| cad, err := b.s.GetPhaseCadence(phases[idx-1].PhaseKey) |
| require.NoError(b.t, err) |
| if cad.ActiveTo == nil { |
| b.t.Fatalf("phase %s has no active to, cannot add new phase without specifying duration for previous", phases[idx-1].PhaseKey) |
| } |
| startAfter = datetime.ISODurationBetween(b.s.ActiveFrom, *cad.ActiveTo) |
| } |
|
|
| |
| newPhase := subscription.SubscriptionPhaseSpec{ |
| CreateSubscriptionPhasePlanInput: subscription.CreateSubscriptionPhasePlanInput{ |
| PhaseKey: fmt.Sprintf("test_phase_%d", idx), |
| Name: fmt.Sprintf("Test Phase %d", idx), |
| Description: lo.ToPtr(fmt.Sprintf("Test Phase %d Description", idx)), |
| StartAfter: startAfter, |
| SortHint: lo.ToPtr(uint8(idx)), |
| }, |
| CreateSubscriptionPhaseCustomerInput: subscription.CreateSubscriptionPhaseCustomerInput{}, |
| ItemsByKey: make(map[string][]*subscription.SubscriptionItemSpec), |
| } |
|
|
| |
| for _, rc := range rcs { |
| newPhase.ItemsByKey[rc.Key()] = append(newPhase.ItemsByKey[rc.Key()], &subscription.SubscriptionItemSpec{ |
| CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ |
| CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ |
| PhaseKey: newPhase.PhaseKey, |
| ItemKey: rc.Key(), |
| RateCard: rc, |
| }, |
| CreateSubscriptionItemCustomerInput: subscription.CreateSubscriptionItemCustomerInput{}, |
| }, |
| }) |
| } |
|
|
| for key, items := range newPhase.ItemsByKey { |
| if len(items) > 1 { |
| b.t.Fatalf("multiple ratecards specified with same key %s", key) |
| } |
| } |
|
|
| b.s.Phases[newPhase.PhaseKey] = &newPhase |
|
|
| return b |
| } |
|
|
| func (b *testSubscriptionSpecBuilder) Build() (subscription.SubscriptionSpec, error) { |
| spec := b.s |
|
|
| if err := spec.SyncAnnotations(); err != nil { |
| return spec, fmt.Errorf("failed to sync annotations: %w", err) |
| } |
|
|
| if err := spec.Validate(); err != nil { |
| return spec, fmt.Errorf("failed to validate spec: %w", err) |
| } |
|
|
| return spec, nil |
| } |
|
|
| func BuildTestSubscriptionSpec(t *testing.T) *testSubscriptionSpecBuilder { |
| now := clock.Now() |
|
|
| return &testSubscriptionSpecBuilder{ |
| s: subscription.SubscriptionSpec{ |
| CreateSubscriptionPlanInput: subscription.CreateSubscriptionPlanInput{ |
| Plan: &subscription.PlanRef{ |
| Key: "test_plan", |
| Version: 1, |
| }, |
| BillingCadence: datetime.MustParseDuration(t, "P1M"), |
| SettlementMode: productcatalog.CreditThenInvoiceSettlementMode, |
| }, |
| CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{ |
| CustomerId: "01K6JCPG631MH1EKEQB2YMDBJW", |
| ActiveFrom: now, |
| ActiveTo: nil, |
| Name: "test_subscription", |
| BillingAnchor: now, |
| Currency: currencyx.Code(currency.USD), |
| }, |
| Phases: make(map[string]*subscription.SubscriptionPhaseSpec), |
| }, |
| t: t, |
| } |
| } |
|
|