Leon4gr45's picture
Upload folder using huggingface_hub (part 3)
1c4c66b verified
Raw
History Blame Contribute Delete
43.8 kB
package targetstate
import (
"context"
"log/slog"
"testing"
"time"
"github.com/alpacahq/alpacadecimal"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"go.opentelemetry.io/otel/trace/noop"
"github.com/openmeterio/openmeter/openmeter/productcatalog"
"github.com/openmeterio/openmeter/openmeter/subscription"
"github.com/openmeterio/openmeter/pkg/datetime"
"github.com/openmeterio/openmeter/pkg/models"
"github.com/openmeterio/openmeter/pkg/timeutil"
)
const NotSet = ""
type PhaseIteratorTestSuite struct {
*require.Assertions
suite.Suite
}
func TestPhaseIterator(t *testing.T) {
suite.Run(t, new(PhaseIteratorTestSuite))
}
func (s *PhaseIteratorTestSuite) SetupSuite() {
s.Assertions = require.New(s.T())
}
type expectedIterations struct {
ServicePeriod timeutil.ClosedPeriod
FullServicePeriod timeutil.ClosedPeriod
BillingPeriod timeutil.ClosedPeriod
Key string
}
type subscriptionItemViewMock struct {
Key string
Cadence string
StartAfter *datetime.ISODuration
EndAfter *datetime.ISODuration
Type productcatalog.PriceType
}
const NoPriceType = productcatalog.PriceType("NoPrice")
func (s *PhaseIteratorTestSuite) mustParseTime(t string) time.Time {
return lo.Must(time.Parse(time.RFC3339, t))
}
func (s *PhaseIteratorTestSuite) TestPhaseIterator() {
tcs := []struct {
name string
items []subscriptionItemViewMock
end time.Time
expected []expectedIterations
expectedErr error
phaseEnd *time.Time
subscriptionEnd *time.Time
alignedBillingCadence datetime.ISODuration
billingAnchorRelativeToSubscriptionStart datetime.ISODuration // remember to use a negative otherwise test will fail
}{
{
name: "unaligned empty",
items: []subscriptionItemViewMock{},
end: s.mustParseTime("2021-01-01T00:00:00Z"),
expected: []expectedIterations{},
},
{
name: "aligned empty",
items: []subscriptionItemViewMock{},
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1M"),
end: s.mustParseTime("2021-01-01T00:00:00Z"),
expected: []expectedIterations{},
},
{
name: "sanity",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Cadence: "P1D",
},
},
end: s.mustParseTime("2021-01-03T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
},
},
{
name: "sanity-non-billable-filtering",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Cadence: "P1D",
},
{
Key: "item-key-no-price",
Type: NoPriceType,
},
},
end: s.mustParseTime("2021-01-03T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
},
},
{
name: "sanity-phase-end",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Cadence: "P1D",
},
},
end: s.mustParseTime("2021-01-03T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-02T15:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-02T15:00:00Z"), // billing period can never reach over phases
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
},
phaseEnd: lo.ToPtr(s.mustParseTime("2021-01-02T15:00:00Z")),
},
{
name: "different cadence",
items: []subscriptionItemViewMock{
{
Key: "item-key-1d",
Cadence: "P1D",
},
{
Key: "item-key-2d",
Cadence: "P2D",
},
},
end: s.mustParseTime("2021-01-04T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
expected: []expectedIterations{
// 1d cadence
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key-1d/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key-1d/v[0]/period[1]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/item-key-1d/v[0]/period[2]",
},
// 2d cadence
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key-2d/v[0]/period[0]",
},
},
},
{
// Note: this happens on subscription updates, but the active to/from is always disjunct
name: "new-version-split-aligned-with-regular-cadence",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Cadence: "P1D",
EndAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1D")),
},
{
Key: "item-key",
Cadence: "P1D",
StartAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1D")),
},
},
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
end: s.mustParseTime("2021-01-03T00:00:00Z"),
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[1]/period[0]",
},
},
},
{
name: "active-from-to-missmatching-period",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Cadence: "P1D",
EndAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H")),
},
{
Key: "item-key",
Cadence: "P1D",
StartAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H")),
},
},
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
end: s.mustParseTime("2021-01-03T00:00:00Z"),
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-02T20:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T20:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[1]/period[0]",
},
},
},
{
name: "ubp-time truncated",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Cadence: "P1D",
EndAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H2S")),
Type: productcatalog.UnitPriceType,
},
{
Key: "item-key",
Cadence: "P1D",
StartAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H2S")), // empty period
EndAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H2S")),
Type: productcatalog.UnitPriceType,
},
{
Key: "item-key",
Cadence: "P1D",
StartAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H2S")),
EndAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H4S")),
Type: productcatalog.UnitPriceType,
},
{
Key: "item-key",
Cadence: "P1D",
StartAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H4S")),
Type: productcatalog.UnitPriceType,
},
},
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
end: s.mustParseTime("2021-01-03T00:00:00Z"),
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-02T20:00:02Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
// billing period will follow the otherwise cadence
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
// 0 length service period v1 is dropped
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T20:00:02Z"),
To: s.mustParseTime("2021-01-02T20:00:04Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
// billing period will follow the otherwise cadence
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[2]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T20:00:04Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[3]/period[0]",
},
},
},
{
name: "flat-fee recurring",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Cadence: "P1D",
Type: productcatalog.FlatPriceType,
},
},
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
end: s.mustParseTime("2021-01-03T00:00:00Z"),
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
{
// Given end is >= invoice_at only at this point
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[2]",
},
},
},
{
name: "flat-fee one-time",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
},
},
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1M"),
end: s.mustParseTime("2021-01-03T00:00:00Z"),
// If PhaseEnd is defined, and before the otherwise end of the billing cadence, that should be the end of the periods for the one-time item
phaseEnd: lo.ToPtr(s.mustParseTime("2021-01-05T00:00:00Z")),
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-05T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-05T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-05T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]",
},
},
},
{
name: "aligned in-advance and in-arrears recurring when billing cadence is same as service cadence",
items: []subscriptionItemViewMock{
// In Advance
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
Cadence: "P1D",
EndAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H")),
},
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
Cadence: "P1D",
StartAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H")),
},
// In Arrears
{
Key: "arrears-key",
Type: productcatalog.UnitPriceType,
Cadence: "P1D",
},
},
end: s.mustParseTime("2021-01-03T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
expected: []expectedIterations{
// In Advance
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-02T20:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T20:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[1]/period[0]",
},
// As we want to generate all in advance items invoicable by end time, we get an extra in advance item
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[1]/period[1]",
},
// In Arrears
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
Key: "subID/phase-test/arrears-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/arrears-key/v[0]/period[1]",
},
},
},
{
name: "aligned one-time no phase end",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
},
},
end: s.mustParseTime("2021-01-03T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1M"),
expected: []expectedIterations{
{
// If there is no phase end, the service period will be an instant
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-01T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-01T00:00:00Z"),
},
// If there is no foreseeable end to the phase, we'll bill after a single iteration
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-02-01T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]",
},
},
},
{
name: "aligned one-time with phase end",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
},
},
end: s.mustParseTime("2021-01-03T00:00:00Z"),
phaseEnd: lo.ToPtr(s.mustParseTime("2021-02-05T00:00:00Z")),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1M"),
expected: []expectedIterations{
{
// If there is a foreseeable phase end, the service period will account for the entire phase
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-02-05T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-02-05T00:00:00Z"),
},
// Billing period still follows the aligned cadence
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-02-01T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]",
},
},
},
{
name: "aligned in-advance and in-arrears recurring with billing cadence different than service cadence",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
Cadence: "P1D",
},
{
Key: "arrears-key",
Type: productcatalog.UnitPriceType,
Cadence: "P1D",
},
},
end: s.mustParseTime("2021-01-04T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P3D"),
expected: []expectedIterations{
// In Advance
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[2]",
},
// We will also generate for the next billing period the in advance items (as their invoice at will be next start = current end = iteration end)
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-04T00:00:00Z"),
To: s.mustParseTime("2021-01-05T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-04T00:00:00Z"),
To: s.mustParseTime("2021-01-05T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-04T00:00:00Z"),
To: s.mustParseTime("2021-01-07T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[3]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-05T00:00:00Z"),
To: s.mustParseTime("2021-01-06T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-05T00:00:00Z"),
To: s.mustParseTime("2021-01-06T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-04T00:00:00Z"),
To: s.mustParseTime("2021-01-07T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[4]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-06T00:00:00Z"),
To: s.mustParseTime("2021-01-07T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-06T00:00:00Z"),
To: s.mustParseTime("2021-01-07T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-04T00:00:00Z"),
To: s.mustParseTime("2021-01-07T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[5]",
},
// In Arrears
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-02T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/arrears-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/arrears-key/v[0]/period[1]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-03T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-04T00:00:00Z"),
},
Key: "subID/phase-test/arrears-key/v[0]/period[2]",
},
},
},
{
name: "aligned subscription item is outside of subscription",
subscriptionEnd: lo.ToPtr(s.mustParseTime("2021-01-03T00:00:00Z")),
items: []subscriptionItemViewMock{
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
Cadence: "P1D",
StartAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P30D")),
},
},
end: s.mustParseTime("2021-01-03T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
expected: []expectedIterations{},
},
{
name: "aligned subscription item crosses subs cancellation date (also phase end date)",
subscriptionEnd: lo.ToPtr(s.mustParseTime("2021-01-03T00:00:00Z")),
end: s.mustParseTime("2021-01-03T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1M"),
items: []subscriptionItemViewMock{
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
Cadence: "P1M",
},
},
expected: []expectedIterations{
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
// The full service period wasn't served, this will still be a month long
To: s.mustParseTime("2021-02-01T00:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
// If the subscription ends, of course we can bill
// Also, as otherwise, cadence cannot reach cross phase boundaries, which the subscription end is
To: s.mustParseTime("2021-01-03T00:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
},
},
//
// Aligned Subscription Tests with Billing Anchor
//
{
name: "aligned flat-fee recurring when billing cadence is same as service cadence",
items: []subscriptionItemViewMock{
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
Cadence: "P1D",
EndAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H")),
},
{
Key: "item-key",
Type: productcatalog.FlatPriceType,
Cadence: "P1D",
StartAfter: lo.ToPtr(datetime.MustParseDuration(s.T(), "P1DT20H")),
},
},
end: s.mustParseTime("2021-01-03T00:00:00Z"),
alignedBillingCadence: datetime.MustParseDuration(s.T(), "P1D"),
billingAnchorRelativeToSubscriptionStart: datetime.MustParseDuration(s.T(), "-PT1H"),
// We expect the full service periods to be shifted
expected: []expectedIterations{
{
// Service Periods will be aligned to the billing anchor
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-01T23:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2020-12-31T23:00:00Z"),
To: s.mustParseTime("2021-01-01T23:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
// BillingPeriod cannot fall outside of subscription active period (and phase active period) so start gets truncated
From: s.mustParseTime("2021-01-01T00:00:00Z"),
To: s.mustParseTime("2021-01-01T23:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[0]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T23:00:00Z"),
// Item ends so does service period
To: s.mustParseTime("2021-01-02T20:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T23:00:00Z"),
To: s.mustParseTime("2021-01-02T23:00:00Z"),
},
// Billing period will be the next whole day
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T23:00:00Z"),
To: s.mustParseTime("2021-01-02T23:00:00Z"),
},
Key: "subID/phase-test/item-key/v[0]/period[1]",
},
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T20:00:00Z"),
To: s.mustParseTime("2021-01-02T23:00:00Z"),
},
// Item was changed during period so the full service period will be the same as that of the previous version
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T23:00:00Z"),
To: s.mustParseTime("2021-01-02T23:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-01T23:00:00Z"),
To: s.mustParseTime("2021-01-02T23:00:00Z"),
},
Key: "subID/phase-test/item-key/v[1]/period[0]",
},
// Given invoiceAt should be >= end, we have an extra in advance item
{
ServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T23:00:00Z"),
To: s.mustParseTime("2021-01-03T23:00:00Z"),
},
FullServicePeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T23:00:00Z"),
To: s.mustParseTime("2021-01-03T23:00:00Z"),
},
BillingPeriod: timeutil.ClosedPeriod{
From: s.mustParseTime("2021-01-02T23:00:00Z"),
To: s.mustParseTime("2021-01-03T23:00:00Z"),
},
Key: "subID/phase-test/item-key/v[1]/period[1]",
},
},
},
}
for _, tc := range tcs {
s.Run(tc.name, func() {
subscriptionStart := s.mustParseTime("2021-01-01T00:00:00.1234Z")
phase := subscription.SubscriptionPhaseView{
SubscriptionPhase: subscription.SubscriptionPhase{
ActiveFrom: subscriptionStart,
Key: "phase-test",
},
ItemsByKey: map[string][]subscription.SubscriptionItemView{},
Spec: subscription.SubscriptionPhaseSpec{
CreateSubscriptionPhasePlanInput: subscription.CreateSubscriptionPhasePlanInput{
PhaseKey: "phase-test",
},
ItemsByKey: map[string][]*subscription.SubscriptionItemSpec{},
},
}
for _, item := range tc.items {
spec := subscription.SubscriptionItemSpec{
CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{
CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{
ItemKey: item.Key,
PhaseKey: "phase-test",
},
},
}
view := subscription.SubscriptionItemView{}
var pp *productcatalog.Price
var rc productcatalog.RateCard
var bc *datetime.ISODuration
if item.Cadence != "" {
bc = lo.ToPtr(datetime.MustParseDuration(s.T(), item.Cadence))
}
switch item.Type {
case productcatalog.UnitPriceType:
pp = productcatalog.NewPriceFrom(productcatalog.UnitPrice{
Amount: alpacadecimal.NewFromInt(1),
})
rc = &productcatalog.UsageBasedRateCard{
RateCardMeta: productcatalog.RateCardMeta{
Price: pp,
},
BillingCadence: *bc,
}
case productcatalog.FlatPriceType:
pp = productcatalog.NewPriceFrom(productcatalog.FlatPrice{
Amount: alpacadecimal.NewFromInt(1),
PaymentTerm: productcatalog.InAdvancePaymentTerm,
})
rc = &productcatalog.FlatFeeRateCard{
RateCardMeta: productcatalog.RateCardMeta{
Price: pp,
},
BillingCadence: bc,
}
case NoPriceType:
pp = nil
rc = &productcatalog.FlatFeeRateCard{
BillingCadence: bc,
}
default:
pp = productcatalog.NewPriceFrom(productcatalog.UnitPrice{
Amount: alpacadecimal.NewFromInt(1),
})
rc = &productcatalog.FlatFeeRateCard{
RateCardMeta: productcatalog.RateCardMeta{
Price: pp,
},
BillingCadence: bc,
}
}
spec.RateCard = rc
view.SubscriptionItem.RateCard = rc
if item.StartAfter != nil {
af, _ := item.StartAfter.AddTo(phase.SubscriptionPhase.ActiveFrom)
view.SubscriptionItem.ActiveFrom = af
spec.ActiveFromOverrideRelativeToPhaseStart = item.StartAfter
}
if item.EndAfter != nil {
at, _ := item.EndAfter.AddTo(phase.SubscriptionPhase.ActiveFrom)
view.SubscriptionItem.ActiveTo = &at
spec.ActiveToOverrideRelativeToPhaseStart = item.EndAfter
}
if tc.phaseEnd != nil {
if view.SubscriptionItem.ActiveTo != nil && tc.phaseEnd.Before(*view.SubscriptionItem.ActiveTo) {
view.SubscriptionItem.ActiveTo = tc.phaseEnd
} else {
view.SubscriptionItem.ActiveTo = tc.phaseEnd
}
}
if tc.subscriptionEnd != nil {
if view.SubscriptionItem.ActiveTo != nil && tc.subscriptionEnd.Before(*view.SubscriptionItem.ActiveTo) {
view.SubscriptionItem.ActiveTo = tc.subscriptionEnd
} else {
view.SubscriptionItem.ActiveTo = tc.subscriptionEnd
}
}
if view.SubscriptionItem.ActiveFrom.IsZero() {
view.SubscriptionItem.ActiveFrom = phase.SubscriptionPhase.ActiveFrom
}
view.Spec = spec
phase.ItemsByKey[view.Spec.ItemKey] = append(phase.ItemsByKey[view.Spec.ItemKey], view)
phase.Spec.ItemsByKey[view.Spec.ItemKey] = append(phase.Spec.ItemsByKey[view.Spec.ItemKey], &spec)
}
subs := subscription.SubscriptionView{
Subscription: subscription.Subscription{
BillingAnchor: subscriptionStart,
NamespacedID: models.NamespacedID{
ID: "subID",
},
},
Spec: subscription.SubscriptionSpec{
CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{
ActiveFrom: phase.SubscriptionPhase.ActiveFrom,
BillingAnchor: subscriptionStart,
},
Phases: map[string]*subscription.SubscriptionPhaseSpec{
phase.SubscriptionPhase.Key: &phase.Spec,
},
},
Phases: []subscription.SubscriptionPhaseView{phase},
}
if tc.subscriptionEnd != nil {
subs.Subscription.ActiveTo = tc.subscriptionEnd
subs.Spec.ActiveTo = tc.subscriptionEnd
}
if tc.billingAnchorRelativeToSubscriptionStart.Sign() == 1 {
require.Fail(s.T(), "billing anchor relative to subscription start must be negative in test-case")
}
if !tc.alignedBillingCadence.IsZero() {
subs.Spec.BillingCadence = tc.alignedBillingCadence
subs.Subscription.BillingCadence = tc.alignedBillingCadence
anchorTime, ok := tc.billingAnchorRelativeToSubscriptionStart.AddTo(subscriptionStart)
s.True(ok)
subs.Subscription.BillingAnchor = anchorTime
subs.Spec.BillingAnchor = anchorTime
}
if tc.phaseEnd != nil {
subs.Spec.ActiveTo = tc.phaseEnd
subs.Subscription.ActiveTo = tc.phaseEnd
}
it, err := NewPhaseIterator(
slog.Default(),
noop.NewTracerProvider().Tracer("test"),
subs,
phase.SubscriptionPhase.Key,
)
s.NoError(err)
out, err := it.Generate(context.Background(), tc.end)
if tc.expectedErr != nil {
s.EqualError(err, tc.expectedErr.Error())
return
}
s.NoError(err)
outAsExpect := make([]expectedIterations, 0, len(out))
for i, item := range out {
outAsExpect = append(outAsExpect, expectedIterations{
Key: item.UniqueID,
ServicePeriod: item.ServicePeriod,
FullServicePeriod: item.FullServicePeriod,
BillingPeriod: item.BillingPeriod,
})
s.T().Logf("out[%d]: %s \nService Period: [%s..%s] \nFull Service Period: [%s..%s] \nBilling Period: [%s..%s]\n", i, item.UniqueID, item.ServicePeriod.From, item.ServicePeriod.To, item.FullServicePeriod.From, item.FullServicePeriod.To, item.BillingPeriod.From, item.BillingPeriod.To)
}
for i, item := range tc.expected {
s.T().Logf("expected[%d]: %s \nService Period: [%s..%s] \nFull Service Period: [%s..%s] \nBilling Period: [%s..%s]\n", i, item.Key, item.ServicePeriod.From, item.ServicePeriod.To, item.FullServicePeriod.From, item.FullServicePeriod.To, item.BillingPeriod.From, item.BillingPeriod.To)
}
s.ElementsMatch(tc.expected, outAsExpect)
})
}
}