Download billing/worker/subscriptionsync/service/targetstate/phaseiterator_test.go from Leon4gr45/openmeter: direct link, hf CLI and curl.
- Browser
- Download file 43.8 kB
-
https://huggingface.co/spaces/Leon4gr45/openmeter/resolve/main/billing/worker/subscriptionsync/service/targetstate/phaseiterator_test.go
- Command line
-
hf download hf://spaces/Leon4gr45/openmeter/billing/worker/subscriptionsync/service/targetstate/phaseiterator_test.go
-
curl -L -o phaseiterator_test.go https://huggingface.co/spaces/Leon4gr45/openmeter/resolve/main/billing/worker/subscriptionsync/service/targetstate/phaseiterator_test.go
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) | |
| }) | |
| } | |
| } | |