ducky/subscriptions
ducky/subscriptions/subscription_store_test.go
Use stripe's built-in subscriptions. We're going to use Stripe's built-in subscriptions to manage our subscriptions, which required us to change a lot of stuff. We're now tracking stripe_subscription instead of stripe_customer, and we need to track the plan, status, and if the user is canceling after this month. We also don't need to know when to begin charging them (Stripe will do it), but we should track when their trial starts and ends, when the current pay period they're in starts and ends, when they canceled (if they've canceled), the number of failed charge attempts they've had, and the last time we notified them about billing (To avoid spamming users). We get to delete all the stuff about periods, which is nice. We updated our SubscriptionChange type to match. Notably, there are a lot of non-user modifiable things now, but our Stripe webhook will need to use them to update our database records and keep them in sync. We no longer need to deal with sorting stuff, which is also nice. Our SubscriptionStats have been updated to be... useful? Now we can track how many users we have, and how many of them have failing credit cards, how many are canceling at the end of their current payment period, and how many users are on each plan. We also switched around how the TestUpdateSubscription loops were written, to avoid resetting more than we needed to. Before, we had to call store.reset() after every single change iteration. Now we get to call it only when switching stores. This makes a significant difference in the amount of time it takes to run tests. Finally, we added a test case for retrieving subscription stats. It's minimal, but it works.
1.1 --- a/subscription_store_test.go Sun Jun 14 02:48:08 2015 -0400 1.2 +++ b/subscription_store_test.go Tue Jun 16 23:09:59 2015 -0400 1.3 @@ -10,13 +10,18 @@ 1.4 ) 1.5 1.6 const ( 1.7 - subscriptionChangeStripeCustomer = 1 << iota 1.8 - subscriptionChangeAmount 1.9 - subscriptionChangePeriod 1.10 - subscriptionChangeBeginCharging 1.11 - subscriptionChangeLastCharged 1.12 + subscriptionChangeStripeSubscription = 1 << iota 1.13 + subscriptionChangePlan 1.14 + subscriptionChangeStatus 1.15 + subscriptionChangeCanceling 1.16 + subscriptionChangeTrialStart 1.17 + subscriptionChangeTrialEnd 1.18 + subscriptionChangePeriodStart 1.19 + subscriptionChangePeriodEnd 1.20 + subscriptionChangeCanceledAt 1.21 + subscriptionChangeFailedChargeAttempts 1.22 + subscriptionChangeLastFailedCharge 1.23 subscriptionChangeLastNotified 1.24 - subscriptionChangeInLockout 1.25 ) 1.26 1.27 func init() { 1.28 @@ -37,30 +42,45 @@ 1.29 if !sub1.UserID.Equal(sub2.UserID) { 1.30 return false, "UserID", sub1.UserID, sub2.UserID 1.31 } 1.32 - if sub1.StripeCustomer != sub2.StripeCustomer { 1.33 - return false, "StripeCustomer", sub1.StripeCustomer, sub2.StripeCustomer 1.34 + if sub1.StripeSubscription != sub2.StripeSubscription { 1.35 + return false, "StripeSubscription", sub1.StripeSubscription, sub2.StripeSubscription 1.36 } 1.37 - if sub1.Amount != sub2.Amount { 1.38 - return false, "Amount", sub1.Amount, sub2.Amount 1.39 + if sub1.Plan != sub2.Plan { 1.40 + return false, "Plan", sub1.Plan, sub2.Plan 1.41 } 1.42 - if sub1.Period != sub2.Period { 1.43 - return false, "Period", sub1.Period, sub2.Period 1.44 + if sub1.Status != sub2.Status { 1.45 + return false, "Status", sub1.Status, sub2.Status 1.46 + } 1.47 + if sub1.Canceling != sub2.Canceling { 1.48 + return false, "Canceling", sub1.Canceling, sub2.Canceling 1.49 } 1.50 if !sub1.Created.Equal(sub2.Created) { 1.51 return false, "Created", sub1.Created, sub2.Created 1.52 } 1.53 - if !sub1.BeginCharging.Equal(sub2.BeginCharging) { 1.54 - return false, "BeginCharging", sub1.BeginCharging, sub2.BeginCharging 1.55 + if !sub1.TrialStart.Equal(sub2.TrialStart) { 1.56 + return false, "TrialStart", sub1.TrialStart, sub2.TrialStart 1.57 } 1.58 - if !sub1.LastCharged.Equal(sub2.LastCharged) { 1.59 - return false, "LastCharged", sub1.LastCharged, sub2.LastCharged 1.60 + if !sub1.TrialEnd.Equal(sub2.TrialEnd) { 1.61 + return false, "TrialEnd", sub1.TrialEnd, sub2.TrialEnd 1.62 + } 1.63 + if !sub1.PeriodStart.Equal(sub2.PeriodStart) { 1.64 + return false, "PeriodStart", sub1.PeriodStart, sub2.PeriodStart 1.65 + } 1.66 + if !sub1.PeriodEnd.Equal(sub2.PeriodEnd) { 1.67 + return false, "PeriodEnd", sub1.PeriodEnd, sub2.PeriodEnd 1.68 + } 1.69 + if !sub1.CanceledAt.Equal(sub2.CanceledAt) { 1.70 + return false, "CanceledAt", sub1.CanceledAt, sub2.CanceledAt 1.71 + } 1.72 + if sub1.FailedChargeAttempts != sub2.FailedChargeAttempts { 1.73 + return false, "FailedChargeAttempts", sub1.FailedChargeAttempts, sub2.FailedChargeAttempts 1.74 + } 1.75 + if !sub1.LastFailedCharge.Equal(sub2.LastFailedCharge) { 1.76 + return false, "LastFailedCharge", sub1.LastFailedCharge, sub2.LastFailedCharge 1.77 } 1.78 if !sub1.LastNotified.Equal(sub2.LastNotified) { 1.79 return false, "LastNotified", sub1.LastNotified, sub2.LastNotified 1.80 } 1.81 - if sub1.InLockout != sub2.InLockout { 1.82 - return false, "InLockout", sub1.InLockout, sub2.InLockout 1.83 - } 1.84 return true, "", nil, nil 1.85 } 1.86 1.87 @@ -77,6 +97,31 @@ 1.88 return true, missing 1.89 } 1.90 1.91 +func compareSubscriptionStats(stat1, stat2 SubscriptionStats) (bool, string, interface{}, interface{}) { 1.92 + if stat1.Number != stat2.Number { 1.93 + return false, "Number", stat1.Number, stat2.Number 1.94 + } 1.95 + if stat1.Canceling != stat2.Canceling { 1.96 + return false, "Canceling", stat1.Canceling, stat2.Canceling 1.97 + } 1.98 + if stat1.Failing != stat2.Failing { 1.99 + return false, "Failing", stat1.Failing, stat2.Failing 1.100 + } 1.101 + if len(stat1.Plans) != len(stat2.Plans) { 1.102 + return false, "Plans", stat1.Plans, stat2.Plans 1.103 + } 1.104 + for key, count := range stat1.Plans { 1.105 + count2, ok := stat2.Plans[key] 1.106 + if !ok { 1.107 + return false, "Plans", stat1.Plans, stat2.Plans 1.108 + } 1.109 + if count != count2 { 1.110 + return false, "Plans", stat1.Plans, stat2.Plans 1.111 + } 1.112 + } 1.113 + return true, "", nil, nil 1.114 +} 1.115 + 1.116 func TestCreateSubscription(t *testing.T) { 1.117 for _, store := range testSubscriptionStores { 1.118 err := store.reset() 1.119 @@ -85,12 +130,11 @@ 1.120 } 1.121 customerID := uuid.NewID() 1.122 sub := Subscription{ 1.123 - UserID: customerID, 1.124 - StripeCustomer: "stripeCustomer1", 1.125 - Amount: 200, 1.126 - Period: MonthlyPeriod, 1.127 - Created: time.Now().Round(time.Millisecond), 1.128 - BeginCharging: time.Now().Round(time.Millisecond).Add(time.Hour), 1.129 + UserID: customerID, 1.130 + StripeSubscription: "stripeSubscription1", 1.131 + Created: time.Now().Round(time.Millisecond), 1.132 + TrialStart: time.Now().Round(time.Millisecond), 1.133 + TrialEnd: time.Now().Round(time.Millisecond).Add(time.Hour * 24 * 31), 1.134 } 1.135 err = store.createSubscription(sub) 1.136 if err != nil { 1.137 @@ -113,10 +157,10 @@ 1.138 } 1.139 sub.UserID = uuid.NewID() 1.140 err = store.createSubscription(sub) 1.141 - if err != ErrStripeCustomerAlreadyExists { 1.142 - t.Errorf("Unexpected error creating subscription in %T (wanted %+v): %#+v\n", store, ErrStripeCustomerAlreadyExists, err) 1.143 + if err != ErrStripeSubscriptionAlreadyExists { 1.144 + t.Errorf("Unexpected error creating subscription in %T (wanted %+v): %#+v\n", store, ErrStripeSubscriptionAlreadyExists, err) 1.145 } 1.146 - sub.StripeCustomer = "stripeCustomer2" 1.147 + sub.StripeSubscription = "stripeSubscription2" 1.148 err = store.createSubscription(sub) 1.149 if err != nil { 1.150 t.Errorf("Error creating subscription in %T: %+v\n", store, err) 1.151 @@ -125,106 +169,128 @@ 1.152 } 1.153 1.154 func TestUpdateSubscription(t *testing.T) { 1.155 - variations := 1 << 7 1.156 + variations := 1 << 12 1.157 sub := Subscription{ 1.158 - UserID: uuid.NewID(), 1.159 - StripeCustomer: "default", 1.160 - Amount: -1, 1.161 - Period: MonthlyPeriod, 1.162 - Created: time.Now().Round(time.Millisecond).Add(time.Hour * -24), 1.163 - BeginCharging: time.Now().Round(time.Millisecond).Add(time.Hour * -24), 1.164 - LastCharged: time.Now().Round(time.Millisecond).Add(time.Hour * -24), 1.165 - LastNotified: time.Now().Round(time.Millisecond).Add(time.Hour * -24), 1.166 - InLockout: true, 1.167 + UserID: uuid.NewID(), 1.168 + StripeSubscription: "default", 1.169 + Created: time.Now().Round(time.Millisecond).Add(time.Hour * -24 * -32), 1.170 + TrialStart: time.Now().Round(time.Millisecond).Add(time.Hour * -24 * -32), 1.171 + TrialEnd: time.Now().Round(time.Millisecond).Add(time.Hour * -24), 1.172 + LastNotified: time.Now().Round(time.Millisecond).Add(time.Hour * -24), 1.173 } 1.174 sub2 := Subscription{ 1.175 - UserID: uuid.NewID(), 1.176 - StripeCustomer: "stripeCustomer2", 1.177 - Amount: -2, 1.178 - Period: MonthlyPeriod, 1.179 - Created: time.Now().Round(time.Millisecond), 1.180 - BeginCharging: time.Now().Round(time.Millisecond), 1.181 - LastCharged: time.Now().Round(time.Millisecond), 1.182 - LastNotified: time.Now().Round(time.Millisecond), 1.183 - InLockout: false, 1.184 + UserID: uuid.NewID(), 1.185 + StripeSubscription: "stripeSubscription2", 1.186 + Created: time.Now().Round(time.Millisecond), 1.187 + TrialStart: time.Now().Round(time.Millisecond), 1.188 + TrialEnd: time.Now().Round(time.Millisecond), 1.189 + LastNotified: time.Now().Round(time.Millisecond), 1.190 } 1.191 1.192 - for i := 1; i < variations; i++ { 1.193 - var stripeCustomer string 1.194 - var amount int 1.195 - var inLockout bool 1.196 - var per period 1.197 - var beginCharging, lastCharged, lastNotified time.Time 1.198 + for _, store := range testSubscriptionStores { 1.199 + err := store.reset() 1.200 + if err != nil { 1.201 + t.Fatalf("Error resetting %T: %+v\n", store, err) 1.202 + } 1.203 + err = store.createSubscription(sub) 1.204 + if err != nil { 1.205 + t.Fatalf("Error saving subscription in %T: %s\n", store, err) 1.206 + } 1.207 + for i := 1; i < variations; i++ { 1.208 + var stripeSubscription, plan, status string 1.209 + var canceling bool 1.210 + var failedChargeAttempts int 1.211 + var trialStart, trialEnd, periodStart, periodEnd, canceledAt, lastFailedCharge, lastNotified time.Time 1.212 1.213 - change := SubscriptionChange{} 1.214 - empty := change.IsEmpty() 1.215 - if !empty { 1.216 - t.Errorf("Expected empty to be %t, was %t\n", true, empty) 1.217 - } 1.218 - expectation := sub 1.219 - result := sub 1.220 - strI := strconv.Itoa(i) 1.221 + change := SubscriptionChange{} 1.222 + empty := change.IsEmpty() 1.223 + if !empty { 1.224 + t.Errorf("Expected empty to be %t, was %t\n", true, empty) 1.225 + } 1.226 + result := sub 1.227 + strI := strconv.Itoa(i) 1.228 1.229 - if i&subscriptionChangeStripeCustomer != 0 { 1.230 - stripeCustomer = "stripeCustomer-" + strI 1.231 - change.StripeCustomer = &stripeCustomer 1.232 - expectation.StripeCustomer = stripeCustomer 1.233 - } 1.234 + if i&subscriptionChangeStripeSubscription != 0 { 1.235 + stripeSubscription = "stripeSubscription-" + strI 1.236 + change.StripeSubscription = &stripeSubscription 1.237 + sub.StripeSubscription = stripeSubscription 1.238 + } 1.239 1.240 - if i&subscriptionChangeAmount != 0 { 1.241 - amount = i 1.242 - change.Amount = &amount 1.243 - expectation.Amount = amount 1.244 - } 1.245 + if i&subscriptionChangePlan != 0 { 1.246 + plan = "plan-" + strI 1.247 + change.Plan = &plan 1.248 + sub.Plan = plan 1.249 + } 1.250 1.251 - if i&subscriptionChangePeriod != 0 { 1.252 - per = period("period-" + strI) 1.253 - change.Period = &per 1.254 - expectation.Period = per 1.255 - } 1.256 + if i&subscriptionChangeStatus != 0 { 1.257 + status = "status-" + strI 1.258 + change.Status = &status 1.259 + sub.Status = status 1.260 + } 1.261 1.262 - if i&subscriptionChangeBeginCharging != 0 { 1.263 - beginCharging = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.264 - change.BeginCharging = &beginCharging 1.265 - expectation.BeginCharging = beginCharging 1.266 - } 1.267 + if i&subscriptionChangeCanceling != 0 { 1.268 + canceling = i%2 == 0 1.269 + change.Canceling = &canceling 1.270 + sub.Canceling = canceling 1.271 + } 1.272 1.273 - if i&subscriptionChangeLastCharged != 0 { 1.274 - lastCharged = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.275 - change.LastCharged = &lastCharged 1.276 - expectation.LastCharged = lastCharged 1.277 - } 1.278 + if i&subscriptionChangeTrialStart != 0 { 1.279 + trialStart = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.280 + change.TrialStart = &trialStart 1.281 + sub.TrialStart = trialStart 1.282 + } 1.283 1.284 - if i&subscriptionChangeLastNotified != 0 { 1.285 - lastNotified = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.286 - change.LastNotified = &lastNotified 1.287 - expectation.LastNotified = lastNotified 1.288 - } 1.289 + if i&subscriptionChangeTrialEnd != 0 { 1.290 + trialEnd = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.291 + change.TrialEnd = &trialEnd 1.292 + sub.TrialEnd = trialEnd 1.293 + } 1.294 1.295 - if i&subscriptionChangeInLockout != 0 { 1.296 - inLockout = i%2 == 0 1.297 - change.InLockout = &inLockout 1.298 - expectation.InLockout = inLockout 1.299 - } 1.300 + if i&subscriptionChangePeriodStart != 0 { 1.301 + periodStart = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.302 + change.PeriodStart = &periodStart 1.303 + sub.PeriodStart = periodStart 1.304 + } 1.305 1.306 - empty = change.IsEmpty() 1.307 - if empty { 1.308 - t.Errorf("Expected empty to be %t, was %t\n", false, empty) 1.309 - } 1.310 + if i&subscriptionChangePeriodEnd != 0 { 1.311 + periodEnd = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.312 + change.PeriodEnd = &periodEnd 1.313 + sub.PeriodEnd = periodEnd 1.314 + } 1.315 1.316 - result.ApplyChange(change) 1.317 - match, field, expected, got := compareSubscriptions(expectation, result) 1.318 - if !match { 1.319 - t.Errorf("Expected field `%s` to be `%v`, got `%v`\n", field, expected, got) 1.320 - } 1.321 - for _, store := range testSubscriptionStores { 1.322 - err := store.reset() 1.323 - if err != nil { 1.324 - t.Fatalf("Error resetting %T: %+v\n", store, err) 1.325 + if i&subscriptionChangeCanceledAt != 0 { 1.326 + canceledAt = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.327 + change.CanceledAt = &canceledAt 1.328 + sub.CanceledAt = canceledAt 1.329 } 1.330 - err = store.createSubscription(sub) 1.331 - if err != nil { 1.332 - t.Fatalf("Error saving subscription in %T: %s\n", store, err) 1.333 + 1.334 + if i&subscriptionChangeFailedChargeAttempts != 0 { 1.335 + failedChargeAttempts = i 1.336 + change.FailedChargeAttempts = &failedChargeAttempts 1.337 + sub.FailedChargeAttempts = failedChargeAttempts 1.338 + } 1.339 + 1.340 + if i&subscriptionChangeLastFailedCharge != 0 { 1.341 + lastFailedCharge = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.342 + change.LastFailedCharge = &lastFailedCharge 1.343 + sub.LastFailedCharge = lastFailedCharge 1.344 + } 1.345 + 1.346 + if i&subscriptionChangeLastNotified != 0 { 1.347 + lastNotified = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) 1.348 + change.LastNotified = &lastNotified 1.349 + sub.LastNotified = lastNotified 1.350 + } 1.351 + 1.352 + empty = change.IsEmpty() 1.353 + if empty { 1.354 + t.Errorf("Expected empty to be %t, was %t\n", false, empty) 1.355 + } 1.356 + 1.357 + result.ApplyChange(change) 1.358 + match, field, expected, got := compareSubscriptions(sub, result) 1.359 + if !match { 1.360 + t.Errorf("Expected field `%s` to be `%v`, got `%v`\n", field, expected, got) 1.361 } 1.362 err = store.updateSubscription(sub.UserID, change) 1.363 if err != nil { 1.364 @@ -238,21 +304,13 @@ 1.365 if !ok { 1.366 t.Errorf("Expected to retrieve %s from %T, but missing was %+v\n", sub.UserID.String(), store, missing) 1.367 } 1.368 - match, field, expected, got = compareSubscriptions(expectation, retrieved[sub.UserID.String()]) 1.369 + match, field, expected, got = compareSubscriptions(sub, retrieved[sub.UserID.String()]) 1.370 if !match { 1.371 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T\n", field, expected, got, store) 1.372 } 1.373 + sub = result 1.374 } 1.375 - } 1.376 - for _, store := range testSubscriptionStores { 1.377 - err := store.reset() 1.378 - if err != nil { 1.379 - t.Fatalf("Error resetting %T: %+v\n", store, err) 1.380 - } 1.381 - err = store.createSubscription(sub) 1.382 - if err != nil { 1.383 - t.Fatalf("Error saving subscription in %T: %+v\n", store, err) 1.384 - } 1.385 + 1.386 err = store.createSubscription(sub2) 1.387 if err != nil { 1.388 t.Fatalf("Error saving subscription in %T: %+v\n", store, err) 1.389 @@ -262,15 +320,15 @@ 1.390 if err != ErrSubscriptionChangeEmpty { 1.391 t.Errorf("Expected err to be %+v, but got %+v from %T\n", ErrSubscriptionChangeEmpty, err, store) 1.392 } 1.393 - stripeCustomer := sub2.StripeCustomer 1.394 - change.StripeCustomer = &stripeCustomer 1.395 + stripeSubscription := sub2.StripeSubscription 1.396 + change.StripeSubscription = &stripeSubscription 1.397 err = store.updateSubscription(uuid.NewID(), change) 1.398 if err != ErrSubscriptionNotFound { 1.399 t.Errorf("Expected err to be %+v, but got %+v from %T\n", ErrSubscriptionNotFound, err, store) 1.400 } 1.401 err = store.updateSubscription(sub.UserID, change) 1.402 - if err != ErrStripeCustomerAlreadyExists { 1.403 - t.Errorf("Expected err to be %+v, but got %+v from %T\n", ErrStripeCustomerAlreadyExists, err, store) 1.404 + if err != ErrStripeSubscriptionAlreadyExists { 1.405 + t.Errorf("Expected err to be %+v, but got %+v from %T\n", ErrStripeSubscriptionAlreadyExists, err, store) 1.406 } 1.407 } 1.408 } 1.409 @@ -282,12 +340,12 @@ 1.410 t.Fatalf("Error resetting %T: %+v\n", store, err) 1.411 } 1.412 sub1 := Subscription{ 1.413 - UserID: uuid.NewID(), 1.414 - StripeCustomer: "stripeCustomer1", 1.415 + UserID: uuid.NewID(), 1.416 + StripeSubscription: "stripeSubscription1", 1.417 } 1.418 sub2 := Subscription{ 1.419 - UserID: uuid.NewID(), 1.420 - StripeCustomer: "stripeCustomer2", 1.421 + UserID: uuid.NewID(), 1.422 + StripeSubscription: "stripeSubscription2", 1.423 } 1.424 err = store.createSubscription(sub1) 1.425 if err != nil { 1.426 @@ -295,7 +353,7 @@ 1.427 } 1.428 err = store.createSubscription(sub2) 1.429 if err != nil { 1.430 - t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) 1.431 + t.Fatalf("Error creating %+v in %T: %+v\n", sub2, store, err) 1.432 } 1.433 err = store.deleteSubscription(sub1.UserID) 1.434 if err != nil { 1.435 @@ -320,108 +378,6 @@ 1.436 } 1.437 } 1.438 1.439 -func TestListSubscriptionsLastChargedBefore(t *testing.T) { 1.440 - for _, store := range testSubscriptionStores { 1.441 - err := store.reset() 1.442 - if err != nil { 1.443 - t.Fatalf("Error resetting %T: %+v\n", store, err) 1.444 - } 1.445 - sub1 := Subscription{ 1.446 - UserID: uuid.NewID(), 1.447 - StripeCustomer: "stripeCustomer1", 1.448 - Amount: 200, 1.449 - Period: MonthlyPeriod, 1.450 - Created: time.Now().Round(time.Millisecond).Add(time.Hour * -24 * 32), 1.451 - BeginCharging: time.Now().Round(time.Millisecond).Add(time.Hour * -24), 1.452 - LastCharged: time.Now().Round(time.Millisecond).Add(time.Hour * -24), 1.453 - } 1.454 - sub2 := Subscription{ 1.455 - UserID: uuid.NewID(), 1.456 - StripeCustomer: "stripeCustomer2", 1.457 - Amount: 300, 1.458 - Period: MonthlyPeriod, 1.459 - Created: time.Now().Round(time.Millisecond).Add(time.Hour * -24 * 61), 1.460 - BeginCharging: time.Now().Round(time.Millisecond).Add(time.Hour * -24 * 31), 1.461 - LastCharged: time.Now().Round(time.Millisecond).Add(time.Hour * -24 * 31), 1.462 - } 1.463 - sub3 := Subscription{ 1.464 - UserID: uuid.NewID(), 1.465 - StripeCustomer: "stripeCustomer3", 1.466 - Amount: 100, 1.467 - Period: MonthlyPeriod, 1.468 - Created: time.Now().Round(time.Millisecond).Add(time.Hour * -1), 1.469 - BeginCharging: time.Now().Round(time.Millisecond).Add(time.Hour * 31), 1.470 - LastCharged: time.Time{}, 1.471 - } 1.472 - err = store.createSubscription(sub1) 1.473 - if err != nil { 1.474 - t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) 1.475 - } 1.476 - err = store.createSubscription(sub2) 1.477 - if err != nil { 1.478 - t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) 1.479 - } 1.480 - err = store.createSubscription(sub3) 1.481 - if err != nil { 1.482 - t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) 1.483 - } 1.484 - t.Logf("sub1: %+v\n", sub1) 1.485 - t.Logf("sub2: %+v\n", sub2) 1.486 - t.Logf("sub3: %+v\n", sub3) 1.487 - // subscriptions last charged before right now 1.488 - // should be sub1, sub2, and sub3 1.489 - results, err := store.listSubscriptionsLastChargedBefore(time.Now().Round(time.Millisecond)) 1.490 - if err != nil { 1.491 - t.Errorf("Unexpected error listing subscriptions in %T: %+v\n", store, err) 1.492 - } 1.493 - if len(results) != 3 { 1.494 - t.Errorf("Expected three results from %T, got %+v\n", store, results) 1.495 - } 1.496 - ok, field, expected, result := compareSubscriptions(sub3, results[0]) 1.497 - if !ok { 1.498 - t.Errorf("Expected %s in pos 0 to be %+v, got %+v from %T", field, expected, result, store) 1.499 - } 1.500 - ok, field, expected, result = compareSubscriptions(sub2, results[1]) 1.501 - if !ok { 1.502 - t.Errorf("Expected %s in pos 1 to be %+v, got %+v from %T", field, expected, result, store) 1.503 - } 1.504 - ok, field, expected, result = compareSubscriptions(sub1, results[2]) 1.505 - if !ok { 1.506 - t.Errorf("Expected %s in pos 2 to be %+v, got %+v from %T", field, expected, result, store) 1.507 - } 1.508 - // subscriptions last charged before a week ago 1.509 - // should be sub2, sub3 1.510 - results, err = store.listSubscriptionsLastChargedBefore(time.Now().Round(time.Millisecond).Add(time.Hour * -24 * 7)) 1.511 - if err != nil { 1.512 - t.Errorf("Unexpected error listing subscriptions in %T: %+v\n", store, err) 1.513 - } 1.514 - if len(results) != 2 { 1.515 - t.Errorf("Expected two results from %T, got %+v\n", store, results) 1.516 - } 1.517 - ok, field, expected, result = compareSubscriptions(sub3, results[0]) 1.518 - if !ok { 1.519 - t.Errorf("Expected %s in pos 0 to be %+v, got %+v from %T", field, expected, result, store) 1.520 - } 1.521 - ok, field, expected, result = compareSubscriptions(sub2, results[1]) 1.522 - if !ok { 1.523 - t.Errorf("Expected %s in pos 1 to be %+v, got %+v from %T", field, expected, result, store) 1.524 - } 1.525 - // subscriptions last charged before 32 days ago 1.526 - // should be sub3 1.527 - results, err = store.listSubscriptionsLastChargedBefore(time.Now().Round(time.Millisecond).Add(time.Hour * -24 * 32)) 1.528 - if err != nil { 1.529 - t.Errorf("Unexpected error listing subscriptions in %T: %+v\n", store, err) 1.530 - } 1.531 - if len(results) != 1 { 1.532 - t.Errorf("Expected one result from %T, got %+v\n", store, results) 1.533 - } 1.534 - ok, field, expected, result = compareSubscriptions(sub3, results[0]) 1.535 - if !ok { 1.536 - t.Errorf("Expected %s in pos 0 to be %+v, got %+v from %T", field, expected, result, store) 1.537 - } 1.538 - } 1.539 -} 1.540 - 1.541 func TestGetSubscriptions(t *testing.T) { 1.542 for _, store := range testSubscriptionStores { 1.543 err := store.reset() 1.544 @@ -429,31 +385,31 @@ 1.545 t.Fatalf("Error resetting %T: %+v\n", store, err) 1.546 } 1.547 sub1 := Subscription{ 1.548 - UserID: uuid.NewID(), 1.549 - StripeCustomer: "stripeCustomer1", 1.550 - Amount: 200, 1.551 - Period: MonthlyPeriod, 1.552 - Created: time.Now().Round(time.Millisecond), 1.553 - BeginCharging: time.Now().Round(time.Millisecond).Add(time.Hour), 1.554 + UserID: uuid.NewID(), 1.555 + StripeSubscription: "stripeSubscription1", 1.556 + Plan: "plan1", 1.557 + Created: time.Now().Round(time.Millisecond), 1.558 + TrialStart: time.Now().Round(time.Millisecond), 1.559 + TrialEnd: time.Now().Round(time.Millisecond).Add(time.Hour * 24 * 32), 1.560 } 1.561 sub2 := Subscription{ 1.562 - UserID: uuid.NewID(), 1.563 - StripeCustomer: "stripeCustomer2", 1.564 - Amount: 300, 1.565 - Period: MonthlyPeriod, 1.566 - Created: time.Now().Round(time.Millisecond).Add(time.Hour * -720), 1.567 - BeginCharging: time.Now().Round(time.Millisecond).Add(time.Hour*-720 + time.Hour*2), 1.568 - LastCharged: time.Now().Round(time.Millisecond), 1.569 + UserID: uuid.NewID(), 1.570 + StripeSubscription: "stripeSubscription2", 1.571 + Plan: "plan2", 1.572 + Created: time.Now().Round(time.Millisecond).Add(time.Hour * -720), 1.573 + TrialStart: time.Now().Round(time.Millisecond).Add(time.Hour * -720), 1.574 + TrialEnd: time.Now().Round(time.Millisecond), 1.575 } 1.576 sub3 := Subscription{ 1.577 - UserID: uuid.NewID(), 1.578 - StripeCustomer: "stripeCustomer3", 1.579 - Amount: 100, 1.580 - Period: MonthlyPeriod, 1.581 - Created: time.Now().Round(time.Millisecond).Add(time.Hour * -1440), 1.582 - BeginCharging: time.Now().Round(time.Millisecond).Add(time.Hour * -1440), 1.583 - LastNotified: time.Now().Round(time.Millisecond).Add(time.Hour * -720), 1.584 - InLockout: true, 1.585 + UserID: uuid.NewID(), 1.586 + StripeSubscription: "stripeSubscription3", 1.587 + Plan: "plan3", 1.588 + Created: time.Now().Round(time.Millisecond).Add(time.Hour * -1440), 1.589 + TrialStart: time.Now().Round(time.Millisecond).Add(time.Hour * -1440), 1.590 + TrialEnd: time.Now().Round(time.Millisecond).Add(time.Hour * -720), 1.591 + PeriodStart: time.Now().Round(time.Millisecond).Add(time.Hour * -720), 1.592 + PeriodEnd: time.Now().Round(time.Millisecond), 1.593 + Status: "unpaid", 1.594 } 1.595 err = store.createSubscription(sub1) 1.596 if err != nil { 1.597 @@ -555,3 +511,82 @@ 1.598 } 1.599 } 1.600 } 1.601 + 1.602 +func TestGetSubscriptionStats(t *testing.T) { 1.603 + for _, store := range testSubscriptionStores { 1.604 + err := store.reset() 1.605 + if err != nil { 1.606 + t.Fatalf("Error resetting %T: %+v\n", store, err) 1.607 + } 1.608 + sub1 := Subscription{ 1.609 + UserID: uuid.NewID(), 1.610 + StripeSubscription: "stripeSubscription1", 1.611 + Plan: "plan1", 1.612 + Canceling: true, 1.613 + } 1.614 + sub2 := Subscription{ 1.615 + UserID: uuid.NewID(), 1.616 + StripeSubscription: "stripeSubscription2", 1.617 + Plan: "plan2", 1.618 + Status: "past_due", 1.619 + } 1.620 + err = store.createSubscription(sub1) 1.621 + if err != nil { 1.622 + t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) 1.623 + } 1.624 + stats, err := store.getSubscriptionStats() 1.625 + if err != nil { 1.626 + t.Errorf("Error getting stats from %T: %+v\n", store, err) 1.627 + } 1.628 + ok, field, expected, results := compareSubscriptionStats(SubscriptionStats{ 1.629 + Number: 1, 1.630 + Canceling: 1, 1.631 + Failing: 0, 1.632 + Plans: map[string]int64{ 1.633 + "plan1": 1, 1.634 + }, 1.635 + }, stats) 1.636 + if !ok { 1.637 + t.Errorf("Expected %s to be %+v, got %+v from %T\n", field, expected, results, store) 1.638 + } 1.639 + err = store.createSubscription(sub2) 1.640 + if err != nil { 1.641 + t.Fatalf("Error creating %+v in %T: %+v\n", sub2, store, err) 1.642 + } 1.643 + stats, err = store.getSubscriptionStats() 1.644 + if err != nil { 1.645 + t.Errorf("Error getting status from %T: %+v\n", store, err) 1.646 + } 1.647 + ok, field, expected, results = compareSubscriptionStats(SubscriptionStats{ 1.648 + Number: 2, 1.649 + Canceling: 1, 1.650 + Failing: 1, 1.651 + Plans: map[string]int64{ 1.652 + "plan1": 1, 1.653 + "plan2": 1, 1.654 + }, 1.655 + }, stats) 1.656 + if !ok { 1.657 + t.Errorf("Expected %s to be %+v, got %+v from %T\n", field, expected, results, store) 1.658 + } 1.659 + err = store.deleteSubscription(sub1.UserID) 1.660 + if err != nil { 1.661 + t.Errorf("Error deleting subscription from %T: %+v\n", store, err) 1.662 + } 1.663 + stats, err = store.getSubscriptionStats() 1.664 + if err != nil { 1.665 + t.Errorf("Error getting status from %T: %+v\n", store, err) 1.666 + } 1.667 + ok, field, expected, results = compareSubscriptionStats(SubscriptionStats{ 1.668 + Number: 1, 1.669 + Canceling: 0, 1.670 + Failing: 1, 1.671 + Plans: map[string]int64{ 1.672 + "plan2": 1, 1.673 + }, 1.674 + }, stats) 1.675 + if !ok { 1.676 + t.Errorf("Expected %s to be %+v, got %+v from %T\n", field, expected, results, store) 1.677 + } 1.678 + } 1.679 +}