Leon4gr45's picture
Upload folder using huggingface_hub (part 7)
16cdcb7 verified
Raw
History Blame Contribute Delete
13.9 kB
package subscriptiontestutils
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/openmeterio/openmeter/openmeter/entitlement"
"github.com/openmeterio/openmeter/openmeter/subscription"
subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon"
"github.com/openmeterio/openmeter/pkg/datetime"
"github.com/openmeterio/openmeter/pkg/models"
)
// Ensures the created view matches the input spec
func ValidateSpecAndView(t *testing.T, expected subscription.SubscriptionSpec, found subscription.SubscriptionView) {
t.Helper()
// Let's validate the Subscription itself
assert.Equal(t, expected.Name, found.Subscription.Name)
assert.Equal(t, expected.Description, found.Subscription.Description)
assert.Equal(t, expected.Plan, found.Subscription.PlanRef)
assert.Equal(t, expected.Currency, found.Subscription.Currency)
assert.Equal(t, expected.CustomerId, found.Subscription.CustomerId)
assert.Equal(t, expected.ActiveFrom, found.Subscription.ActiveFrom)
assert.Equal(t, expected.ActiveTo, found.Subscription.ActiveTo)
assert.Equal(t, expected.Metadata, found.Subscription.Metadata)
assert.Equal(t, expected.BillingAnchor, found.Subscription.BillingAnchor)
// Let's validate the phases
foundPhases := found.Phases
specPhases := expected.GetSortedPhases()
require.Equal(t, len(specPhases), len(foundPhases), "phase count mismatch")
for i := range specPhases {
specPhase := specPhases[i]
foundPhase := foundPhases[i]
// Let's validate the phase properties
assert.Equal(t, specPhase.PhaseKey, foundPhase.SubscriptionPhase.Key)
assert.Equal(t, specPhase.Name, foundPhase.SubscriptionPhase.Name)
assert.Equal(t, specPhase.Description, foundPhase.SubscriptionPhase.Description)
assert.Equal(t, specPhase.Metadata, foundPhase.SubscriptionPhase.Metadata)
expectedStart, _ := specPhases[i].StartAfter.AddTo(found.Subscription.ActiveFrom)
assert.Equal(t, expectedStart.UTC(), foundPhases[i].SubscriptionPhase.ActiveFrom.UTC())
// Test Rate Cards of Phase
specItemsByKey := specPhase.ItemsByKey
foundItemsByKey := foundPhase.ItemsByKey
require.Equal(t, len(specItemsByKey), len(foundItemsByKey), "item count mismatch for phase %s", specPhase.PhaseKey)
for specItemsKey := range specItemsByKey {
specItemsByKey, ok := specItemsByKey[specItemsKey]
require.True(t, ok, "item %s not found in spec phase %s", specItemsKey, specPhase.PhaseKey)
foundItemsByKey, ok := foundItemsByKey[specItemsKey]
require.True(t, ok, "item %s not found in found phase %s", specItemsKey, specPhase.PhaseKey)
require.Equal(t, len(specItemsByKey), len(foundItemsByKey), "item count mismatch for item %s in phase %s", specItemsKey, specPhase.PhaseKey)
for idx, specItem := range specItemsByKey {
foundItem := foundItemsByKey[idx]
// Let's validate the item properties
assert.Equal(t, specItem.ItemKey, foundItem.SubscriptionItem.Key)
// Validate phase linking both ways
assert.Equal(t, foundPhase.SubscriptionPhase.Key, specItem.PhaseKey)
assert.Equal(t, foundPhase.SubscriptionPhase.ID, foundItem.SubscriptionItem.PhaseId)
// Let's validate the RateCard is equal
rc1JSON, _ := json.Marshal(specItem.RateCard)
rc2JSON, _ := json.Marshal(foundItem.SubscriptionItem.RateCard)
assert.True(t, specItem.RateCard.Equal(foundItem.SubscriptionItem.RateCard), "rate card mismatch for item %s in phase %s: \nspec: %s \n\nview: %s", specItem.ItemKey, specPhase.PhaseKey, rc1JSON, rc2JSON)
// Let's validate the TaxCodeID
specMeta := specItem.RateCard.AsMeta()
foundMeta := foundItem.SubscriptionItem.RateCard.AsMeta()
if specMeta.TaxConfig != nil && specMeta.TaxConfig.TaxCodeID != nil && *specMeta.TaxConfig.TaxCodeID != "" {
require.NotNil(t, foundMeta.TaxConfig, "TaxConfig must be set for item %s (phase %s) with TaxCodeID %s", specItem.ItemKey, specPhase.PhaseKey, *specMeta.TaxConfig.TaxCodeID)
assert.Equal(t, specMeta.TaxConfig.TaxCodeID, foundMeta.TaxConfig.TaxCodeID, "TaxCodeID mismatch for item %s in phase %s", specItem.ItemKey, specPhase.PhaseKey)
} else if specMeta.TaxConfig != nil && specMeta.TaxConfig.Stripe != nil && specMeta.TaxConfig.Stripe.Code != "" {
require.NotNil(t, foundMeta.TaxConfig, "TaxConfig must be set for item %s (phase %s) with Stripe tax code %s", specItem.ItemKey, specPhase.PhaseKey, specMeta.TaxConfig.Stripe.Code)
require.NotNil(t, foundMeta.TaxConfig.TaxCodeID, "TaxCodeID must be backfilled for item %s (phase %s) with Stripe tax code %s", specItem.ItemKey, specPhase.PhaseKey, specMeta.TaxConfig.Stripe.Code)
assert.Equal(t, specMeta.TaxConfig.TaxCodeID, foundMeta.TaxConfig.TaxCodeID, "TaxCodeID mismatch for item %s in phase %s", specItem.ItemKey, specPhase.PhaseKey)
} else if foundMeta.TaxConfig != nil {
assert.Nil(t, foundMeta.TaxConfig.TaxCodeID, "TaxCodeID must be nil for item %s (phase %s) without a tax code", specItem.ItemKey, specPhase.PhaseKey)
}
// Let's validate the Feature linking
pFeatureKey := specItem.RateCard.AsMeta().FeatureKey
if foundItem.SubscriptionItem.RateCard.AsMeta().FeatureKey != nil {
require.NotNil(t, pFeatureKey)
assert.Equal(t, pFeatureKey, foundItem.SubscriptionItem.RateCard.AsMeta().FeatureKey)
} else {
assert.Empty(t, pFeatureKey)
}
rcInp := specItem.CreateSubscriptionItemPlanInput
// Let's validate the Entitlement
if rcEnt := rcInp.RateCard.AsMeta().EntitlementTemplate; rcEnt != nil {
ent := foundItem.Entitlement
exists := ent != nil
require.True(t, exists)
entInp := ent.ToScheduleSubscriptionEntitlementInput()
assert.Equal(t, rcEnt.Type(), entInp.CreateEntitlementInputs.GetType())
// Let's validate that subscriptionID annotation is present
assert.Equal(t, foundItem.Entitlement.Entitlement.Annotations[subscription.AnnotationSubscriptionID], found.Subscription.NamespacedID.ID)
// Let's validate that the UsagePeriod is aligned
period := GetEntitlementTemplateUsagePeriod(t, *specItem.RateCard.AsMeta().EntitlementTemplate)
require.NotNil(t, period)
// Entitlement UsagePeriod should be aligned to the subscription billing anchor, which means
truncatedBillingAnchor := found.Subscription.BillingAnchor.Truncate(time.Minute) // Due to minute precision
// - its duration should be identical
entPeriod := ent.Entitlement.UsagePeriod.GetOriginalValueAsUsagePeriodInput().GetValue().Interval.ISODuration
assert.True(t, entPeriod.Equal(period), "usage period interval mismatch, expected %s, got %s", period, entPeriod)
// - its anchor should be "aligned" with the subscription's billingAnchor
require.NotNil(t, ent.Entitlement.UsagePeriod)
// billinganchor would be in the past compared to entitlement start, so usageperiod would normalize to a later iteration
// to avoid that, lets test with recurrence instead
recAtAnchor, _, err := ent.Entitlement.UsagePeriod.GetUsagePeriodInputAt(truncatedBillingAnchor)
require.NoError(t, err)
entPerAtAnchor, err := recAtAnchor.GetValue().GetPeriodAt(truncatedBillingAnchor)
require.NoError(t, err)
require.Equal(t, truncatedBillingAnchor, entPerAtAnchor.From, "entitlement usage period anchor should be aligned with the subscription billing anchor, subscription billing anchor: %s, entitlement usage period: %+v", truncatedBillingAnchor, *ent.Entitlement.UsagePeriod)
switch rcInp.RateCard.AsMeta().EntitlementTemplate.Type() {
case entitlement.EntitlementTypeMetered:
// Validate measureUsageFrom, it should measure usage form the start of the current phase
require.NotNil(t, ent.Entitlement.MeasureUsageFrom)
assert.Equal(t, foundPhase.SubscriptionPhase.ActiveFrom.UTC().Truncate(time.Minute), ent.Entitlement.MeasureUsageFrom.UTC().Truncate(time.Minute), "measureUsageFrom should equal the truncated phase start, expected %s, got %s", foundPhase.SubscriptionPhase.ActiveFrom.UTC().Truncate(time.Minute), ent.Entitlement.MeasureUsageFrom.UTC().Truncate(time.Minute))
}
// Validate that entitlement activeFrom is the same as the item activeFrom
require.NotNil(t, ent.Entitlement.ActiveFrom)
assert.Equal(t, foundItem.SubscriptionItem.ActiveFrom, *ent.Entitlement.ActiveFrom)
// Validate that the entitlement is only active until the item is scheduled to be
assert.Equal(t, foundItem.SubscriptionItem.ActiveTo, ent.Entitlement.ActiveTo)
} else {
// If an entitlement wasn't defined then there shouldn't be an entitlement
assert.Nil(t, foundItem.Entitlement)
}
}
}
}
}
func SpecsEqual(t *testing.T, s1, s2 subscription.SubscriptionSpec) {
t.Helper()
// Let's validate the Subscription itself
assert.Equal(t, s1.Name, s2.Name)
assert.Equal(t, s1.Description, s2.Description)
assert.Equal(t, s1.Plan, s2.Plan)
assert.Equal(t, s1.Currency, s2.Currency)
assert.Equal(t, s1.CustomerId, s2.CustomerId)
assert.Equal(t, s1.ActiveFrom, s2.ActiveFrom)
assert.Equal(t, s1.ActiveTo, s2.ActiveTo)
assert.Equal(t, s1.Metadata, s2.Metadata)
// Let's validate the phases
require.Equal(t, len(s1.Phases), len(s2.Phases), "phase count mismatch")
for key := range s1.Phases {
p1 := s1.Phases[key]
p1Cad, err := s1.GetPhaseCadence(key)
require.NoError(t, err)
p2, ok := s2.Phases[key]
p2Cad, err := s2.GetPhaseCadence(key)
require.NoError(t, err)
require.True(t, ok, "phase %s not found in second spec", key)
// Let's validate the phase properties
assert.Equal(t, p1.Name, p2.Name, "mismatch for phase %s", key)
assert.Equal(t, p1.Description, p2.Description, "mismatch for phase %s", key)
assert.Equal(t, p1.Metadata, p2.Metadata, "mismatch for phase %s", key)
assert.Equal(t, p1.PhaseKey, p2.PhaseKey, "mismatch for phase %s", key)
assert.Equal(t, p1.StartAfter, p2.StartAfter, "mismatch for phase %s", key)
// Let's validate the items
require.Equal(t, len(p1.ItemsByKey), len(p2.ItemsByKey), "item count mismatch for phase %s, expected %+v and got %+v", key, lo.Keys(p1.ItemsByKey), lo.Keys(p2.ItemsByKey))
for itemKey := range p1.ItemsByKey {
p1Items := p1.ItemsByKey[itemKey]
p2Items, ok := p2.ItemsByKey[itemKey]
require.True(t, ok, "item %s not found in phase %s", itemKey, key)
require.Equal(
t,
len(p1Items),
len(p2Items),
"item count mismatch for item %s in phase %s\n\nexpected: %+v\n\nfound: %+v",
itemKey,
key,
lo.Map(p1Items, func(item *subscription.SubscriptionItemSpec, _ int) models.CadencedModel {
return item.GetCadence(p1Cad)
}),
lo.Map(p2Items, func(item *subscription.SubscriptionItemSpec, _ int) models.CadencedModel {
return item.GetCadence(p2Cad)
}),
)
for i := range p1Items {
i1 := p1Items[i]
i2 := p2Items[i]
// Let's validate the item properties
assert.Equal(t, i1.ItemKey, i2.ItemKey)
assert.True(t, i1.RateCard.Equal(i2.RateCard), "rate card mismatch for item %s in phase %s: \nspec: %+v\n\nview: %+v", itemKey, key, i1.RateCard, i2.RateCard)
assert.Equal(t, i1.CreateSubscriptionItemPlanInput, i2.CreateSubscriptionItemPlanInput, "create subscription item plan input mismatch for item %s in phase %s", itemKey, key)
// We'll compare the time offsets separately
i1af := i1.ActiveFromOverrideRelativeToPhaseStart
i2af := i2.ActiveFromOverrideRelativeToPhaseStart
equalNilableTime(
t,
tsPlusNillableISO(p1Cad.ActiveFrom, i1af),
tsPlusNillableISO(p2Cad.ActiveFrom, i2af),
"active from override relative to phase start mismatch for item %s in phase %s",
itemKey,
key,
)
i1at := i1.ActiveToOverrideRelativeToPhaseStart
i2at := i2.ActiveToOverrideRelativeToPhaseStart
equalNilableTime(
t,
tsPlusNillableISO(p1Cad.ActiveFrom, i1at),
tsPlusNillableISO(p2Cad.ActiveFrom, i2at),
"active to override relative to phase start mismatch for item %s in phase %s",
itemKey,
key,
)
// Then we compare the rest without the offsets
c1 := subscription.CreateSubscriptionItemCustomerInput{
BillingBehaviorOverride: i1.CreateSubscriptionItemCustomerInput.BillingBehaviorOverride,
}
c2 := subscription.CreateSubscriptionItemCustomerInput{
BillingBehaviorOverride: i2.CreateSubscriptionItemCustomerInput.BillingBehaviorOverride,
}
assert.Equal(t, c1, c2, "create subscription item customer input mismatch for item %s in phase %s", itemKey, key)
}
}
}
}
func tsPlusNillableISO(ts time.Time, iso *datetime.ISODuration) *time.Time {
if iso == nil {
return nil
}
out, _ := iso.AddTo(ts)
return &out
}
func equalNilableTime(t *testing.T, t1, t2 *time.Time, msgAndArgs ...interface{}) {
getTpl := func() error {
if len(msgAndArgs) == 0 {
return nil
}
msg, ok := msgAndArgs[0].(string)
if !ok {
return fmt.Errorf("expected string message, got %T", msgAndArgs[0])
}
return fmt.Errorf(msg, msgAndArgs[1:]...)
}
if t1 == nil != (t2 == nil) {
t.Fatalf("%s: mismatch for time %v and %v", getTpl(), t1, t2)
}
if t1 != nil {
assert.Equal(t, *t1, *t2, "%s: mismatch for time %v and %v", getTpl(), t1, t2)
}
}
func SubscriptionAddonsEqual(t *testing.T, a1, a2 subscriptionaddon.SubscriptionAddon) {
t.Helper()
assert.Equal(t, a1.Addon.ID, a2.Addon.ID) // TODO: check all fields?
assert.Equal(t, a1.SubscriptionID, a2.SubscriptionID)
assert.Equal(t, a1.Metadata, a2.Metadata)
assert.Equal(t, a1.RateCards, a2.RateCards)
require.Equal(t, len(a1.Quantities.GetTimes()), len(a2.Quantities.GetTimes()))
for i := 0; i < len(a1.Quantities.GetTimes()); i++ {
require.Equal(t, a1.Quantities.GetAt(i).GetValue().Quantity, a2.Quantities.GetAt(i).GetValue().Quantity)
require.Equal(t, a1.Quantities.GetAt(i).GetTime(), a2.Quantities.GetAt(i).GetTime())
}
}