ducky/subscriptions
ducky/subscriptions/subscription_store_test.go
Export all subscriptionStore methods. We're not going to wrap all our subscriptionStore interactions in a Context type, so we need to expose all the functions so other packages can call them. Also, it's now SubscriptionStore. Also creates a SubscriptionRequest type that is used to create a new Subscription instance, along with a Validate method on it, to detect errors when creating a SubscriptionRequest. Update our CreateSubscription function to be New, and have it take a SubscriptionRequest, a Stripe instance, and a SubscriptionStore as arguments. It'll create the Subscription in the SubscriptionStore, create a customer in Stripe, and create the subscription in Stripe, then associate the Stripe subscription with the created Subscription. Also, fix our StripeSubscriptionChange function to correctly equate a missing/zero Unix timestamp from Stripe with a missing/zero time.Time.
| paddy@0 | 1 package subscriptions |
| paddy@0 | 2 |
| paddy@0 | 3 import ( |
| paddy@1 | 4 "os" |
| paddy@0 | 5 "strconv" |
| paddy@0 | 6 "testing" |
| paddy@0 | 7 "time" |
| paddy@0 | 8 |
| paddy@0 | 9 "code.secondbit.org/uuid.hg" |
| paddy@0 | 10 ) |
| paddy@0 | 11 |
| paddy@0 | 12 const ( |
| paddy@2 | 13 subscriptionChangeStripeSubscription = 1 << iota |
| paddy@2 | 14 subscriptionChangePlan |
| paddy@2 | 15 subscriptionChangeStatus |
| paddy@2 | 16 subscriptionChangeCanceling |
| paddy@2 | 17 subscriptionChangeTrialStart |
| paddy@2 | 18 subscriptionChangeTrialEnd |
| paddy@2 | 19 subscriptionChangePeriodStart |
| paddy@2 | 20 subscriptionChangePeriodEnd |
| paddy@2 | 21 subscriptionChangeCanceledAt |
| paddy@2 | 22 subscriptionChangeFailedChargeAttempts |
| paddy@2 | 23 subscriptionChangeLastFailedCharge |
| paddy@0 | 24 subscriptionChangeLastNotified |
| paddy@0 | 25 ) |
| paddy@0 | 26 |
| paddy@1 | 27 func init() { |
| paddy@1 | 28 if os.Getenv("PG_TEST_DB") != "" { |
| paddy@1 | 29 p, err := NewPostgres(os.Getenv("PG_TEST_DB")) |
| paddy@1 | 30 if err != nil { |
| paddy@1 | 31 panic(err) |
| paddy@1 | 32 } |
| paddy@1 | 33 testSubscriptionStores = append(testSubscriptionStores, p) |
| paddy@1 | 34 } |
| paddy@1 | 35 } |
| paddy@1 | 36 |
| paddy@3 | 37 var testSubscriptionStores = []SubscriptionStore{ |
| paddy@0 | 38 NewMemstore(), |
| paddy@0 | 39 } |
| paddy@0 | 40 |
| paddy@0 | 41 func compareSubscriptions(sub1, sub2 Subscription) (bool, string, interface{}, interface{}) { |
| paddy@0 | 42 if !sub1.UserID.Equal(sub2.UserID) { |
| paddy@0 | 43 return false, "UserID", sub1.UserID, sub2.UserID |
| paddy@0 | 44 } |
| paddy@2 | 45 if sub1.StripeSubscription != sub2.StripeSubscription { |
| paddy@2 | 46 return false, "StripeSubscription", sub1.StripeSubscription, sub2.StripeSubscription |
| paddy@0 | 47 } |
| paddy@2 | 48 if sub1.Plan != sub2.Plan { |
| paddy@2 | 49 return false, "Plan", sub1.Plan, sub2.Plan |
| paddy@0 | 50 } |
| paddy@2 | 51 if sub1.Status != sub2.Status { |
| paddy@2 | 52 return false, "Status", sub1.Status, sub2.Status |
| paddy@2 | 53 } |
| paddy@2 | 54 if sub1.Canceling != sub2.Canceling { |
| paddy@2 | 55 return false, "Canceling", sub1.Canceling, sub2.Canceling |
| paddy@0 | 56 } |
| paddy@0 | 57 if !sub1.Created.Equal(sub2.Created) { |
| paddy@0 | 58 return false, "Created", sub1.Created, sub2.Created |
| paddy@0 | 59 } |
| paddy@2 | 60 if !sub1.TrialStart.Equal(sub2.TrialStart) { |
| paddy@2 | 61 return false, "TrialStart", sub1.TrialStart, sub2.TrialStart |
| paddy@0 | 62 } |
| paddy@2 | 63 if !sub1.TrialEnd.Equal(sub2.TrialEnd) { |
| paddy@2 | 64 return false, "TrialEnd", sub1.TrialEnd, sub2.TrialEnd |
| paddy@2 | 65 } |
| paddy@2 | 66 if !sub1.PeriodStart.Equal(sub2.PeriodStart) { |
| paddy@2 | 67 return false, "PeriodStart", sub1.PeriodStart, sub2.PeriodStart |
| paddy@2 | 68 } |
| paddy@2 | 69 if !sub1.PeriodEnd.Equal(sub2.PeriodEnd) { |
| paddy@2 | 70 return false, "PeriodEnd", sub1.PeriodEnd, sub2.PeriodEnd |
| paddy@2 | 71 } |
| paddy@2 | 72 if !sub1.CanceledAt.Equal(sub2.CanceledAt) { |
| paddy@2 | 73 return false, "CanceledAt", sub1.CanceledAt, sub2.CanceledAt |
| paddy@2 | 74 } |
| paddy@2 | 75 if sub1.FailedChargeAttempts != sub2.FailedChargeAttempts { |
| paddy@2 | 76 return false, "FailedChargeAttempts", sub1.FailedChargeAttempts, sub2.FailedChargeAttempts |
| paddy@2 | 77 } |
| paddy@2 | 78 if !sub1.LastFailedCharge.Equal(sub2.LastFailedCharge) { |
| paddy@2 | 79 return false, "LastFailedCharge", sub1.LastFailedCharge, sub2.LastFailedCharge |
| paddy@0 | 80 } |
| paddy@0 | 81 if !sub1.LastNotified.Equal(sub2.LastNotified) { |
| paddy@0 | 82 return false, "LastNotified", sub1.LastNotified, sub2.LastNotified |
| paddy@0 | 83 } |
| paddy@0 | 84 return true, "", nil, nil |
| paddy@0 | 85 } |
| paddy@0 | 86 |
| paddy@0 | 87 func subscriptionMapContains(subscriptionMap map[string]Subscription, subscriptions ...Subscription) (bool, []Subscription) { |
| paddy@0 | 88 var missing []Subscription |
| paddy@0 | 89 for _, sub := range subscriptions { |
| paddy@1 | 90 if _, ok := subscriptionMap[sub.UserID.String()]; !ok { |
| paddy@0 | 91 missing = append(missing, sub) |
| paddy@0 | 92 } |
| paddy@0 | 93 } |
| paddy@0 | 94 if len(missing) > 0 { |
| paddy@0 | 95 return false, missing |
| paddy@0 | 96 } |
| paddy@0 | 97 return true, missing |
| paddy@0 | 98 } |
| paddy@0 | 99 |
| paddy@2 | 100 func compareSubscriptionStats(stat1, stat2 SubscriptionStats) (bool, string, interface{}, interface{}) { |
| paddy@2 | 101 if stat1.Number != stat2.Number { |
| paddy@2 | 102 return false, "Number", stat1.Number, stat2.Number |
| paddy@2 | 103 } |
| paddy@2 | 104 if stat1.Canceling != stat2.Canceling { |
| paddy@2 | 105 return false, "Canceling", stat1.Canceling, stat2.Canceling |
| paddy@2 | 106 } |
| paddy@2 | 107 if stat1.Failing != stat2.Failing { |
| paddy@2 | 108 return false, "Failing", stat1.Failing, stat2.Failing |
| paddy@2 | 109 } |
| paddy@2 | 110 if len(stat1.Plans) != len(stat2.Plans) { |
| paddy@2 | 111 return false, "Plans", stat1.Plans, stat2.Plans |
| paddy@2 | 112 } |
| paddy@2 | 113 for key, count := range stat1.Plans { |
| paddy@2 | 114 count2, ok := stat2.Plans[key] |
| paddy@2 | 115 if !ok { |
| paddy@2 | 116 return false, "Plans", stat1.Plans, stat2.Plans |
| paddy@2 | 117 } |
| paddy@2 | 118 if count != count2 { |
| paddy@2 | 119 return false, "Plans", stat1.Plans, stat2.Plans |
| paddy@2 | 120 } |
| paddy@2 | 121 } |
| paddy@2 | 122 return true, "", nil, nil |
| paddy@2 | 123 } |
| paddy@2 | 124 |
| paddy@0 | 125 func TestCreateSubscription(t *testing.T) { |
| paddy@0 | 126 for _, store := range testSubscriptionStores { |
| paddy@3 | 127 err := store.Reset() |
| paddy@0 | 128 if err != nil { |
| paddy@0 | 129 t.Fatalf("Error resetting %T: %+v\n", store, err) |
| paddy@0 | 130 } |
| paddy@0 | 131 customerID := uuid.NewID() |
| paddy@0 | 132 sub := Subscription{ |
| paddy@2 | 133 UserID: customerID, |
| paddy@2 | 134 StripeSubscription: "stripeSubscription1", |
| paddy@2 | 135 Created: time.Now().Round(time.Millisecond), |
| paddy@2 | 136 TrialStart: time.Now().Round(time.Millisecond), |
| paddy@2 | 137 TrialEnd: time.Now().Round(time.Millisecond).Add(time.Hour * 24 * 31), |
| paddy@0 | 138 } |
| paddy@3 | 139 err = store.CreateSubscription(sub) |
| paddy@0 | 140 if err != nil { |
| paddy@0 | 141 t.Errorf("Error creating subscription in %T: %+v\n", store, err) |
| paddy@0 | 142 } |
| paddy@3 | 143 retrieved, err := store.GetSubscriptions([]uuid.ID{sub.UserID}) |
| paddy@0 | 144 if err != nil { |
| paddy@0 | 145 t.Errorf("Error retrieving subscription from %T: %+v\n", store, err) |
| paddy@0 | 146 } |
| paddy@1 | 147 if _, returned := retrieved[sub.UserID.String()]; !returned { |
| paddy@1 | 148 t.Errorf("Error retrieving subscription from %T: %s wasn't in the results.", store, sub.UserID) |
| paddy@0 | 149 } |
| paddy@1 | 150 ok, field, expected, result := compareSubscriptions(sub, retrieved[sub.UserID.String()]) |
| paddy@0 | 151 if !ok { |
| paddy@0 | 152 t.Errorf("Expected %s to be %v, got %v from %T\n", field, expected, result, store) |
| paddy@0 | 153 } |
| paddy@3 | 154 err = store.CreateSubscription(sub) |
| paddy@0 | 155 if err != ErrSubscriptionAlreadyExists { |
| paddy@0 | 156 t.Errorf("Unexpected error creating subscription in %T (wanted %+v): %+v\n", store, ErrSubscriptionAlreadyExists, err) |
| paddy@0 | 157 } |
| paddy@1 | 158 sub.UserID = uuid.NewID() |
| paddy@3 | 159 err = store.CreateSubscription(sub) |
| paddy@2 | 160 if err != ErrStripeSubscriptionAlreadyExists { |
| paddy@2 | 161 t.Errorf("Unexpected error creating subscription in %T (wanted %+v): %#+v\n", store, ErrStripeSubscriptionAlreadyExists, err) |
| paddy@0 | 162 } |
| paddy@2 | 163 sub.StripeSubscription = "stripeSubscription2" |
| paddy@3 | 164 err = store.CreateSubscription(sub) |
| paddy@0 | 165 if err != nil { |
| paddy@0 | 166 t.Errorf("Error creating subscription in %T: %+v\n", store, err) |
| paddy@0 | 167 } |
| paddy@0 | 168 } |
| paddy@0 | 169 } |
| paddy@0 | 170 |
| paddy@0 | 171 func TestUpdateSubscription(t *testing.T) { |
| paddy@2 | 172 variations := 1 << 12 |
| paddy@0 | 173 sub := Subscription{ |
| paddy@2 | 174 UserID: uuid.NewID(), |
| paddy@2 | 175 StripeSubscription: "default", |
| paddy@2 | 176 Created: time.Now().Round(time.Millisecond).Add(time.Hour * -24 * -32), |
| paddy@2 | 177 TrialStart: time.Now().Round(time.Millisecond).Add(time.Hour * -24 * -32), |
| paddy@2 | 178 TrialEnd: time.Now().Round(time.Millisecond).Add(time.Hour * -24), |
| paddy@2 | 179 LastNotified: time.Now().Round(time.Millisecond).Add(time.Hour * -24), |
| paddy@0 | 180 } |
| paddy@0 | 181 sub2 := Subscription{ |
| paddy@2 | 182 UserID: uuid.NewID(), |
| paddy@2 | 183 StripeSubscription: "stripeSubscription2", |
| paddy@2 | 184 Created: time.Now().Round(time.Millisecond), |
| paddy@2 | 185 TrialStart: time.Now().Round(time.Millisecond), |
| paddy@2 | 186 TrialEnd: time.Now().Round(time.Millisecond), |
| paddy@2 | 187 LastNotified: time.Now().Round(time.Millisecond), |
| paddy@0 | 188 } |
| paddy@0 | 189 |
| paddy@2 | 190 for _, store := range testSubscriptionStores { |
| paddy@3 | 191 err := store.Reset() |
| paddy@2 | 192 if err != nil { |
| paddy@2 | 193 t.Fatalf("Error resetting %T: %+v\n", store, err) |
| paddy@2 | 194 } |
| paddy@3 | 195 err = store.CreateSubscription(sub) |
| paddy@2 | 196 if err != nil { |
| paddy@2 | 197 t.Fatalf("Error saving subscription in %T: %s\n", store, err) |
| paddy@2 | 198 } |
| paddy@2 | 199 for i := 1; i < variations; i++ { |
| paddy@2 | 200 var stripeSubscription, plan, status string |
| paddy@2 | 201 var canceling bool |
| paddy@2 | 202 var failedChargeAttempts int |
| paddy@2 | 203 var trialStart, trialEnd, periodStart, periodEnd, canceledAt, lastFailedCharge, lastNotified time.Time |
| paddy@0 | 204 |
| paddy@2 | 205 change := SubscriptionChange{} |
| paddy@2 | 206 empty := change.IsEmpty() |
| paddy@2 | 207 if !empty { |
| paddy@2 | 208 t.Errorf("Expected empty to be %t, was %t\n", true, empty) |
| paddy@2 | 209 } |
| paddy@2 | 210 result := sub |
| paddy@2 | 211 strI := strconv.Itoa(i) |
| paddy@0 | 212 |
| paddy@2 | 213 if i&subscriptionChangeStripeSubscription != 0 { |
| paddy@2 | 214 stripeSubscription = "stripeSubscription-" + strI |
| paddy@2 | 215 change.StripeSubscription = &stripeSubscription |
| paddy@2 | 216 sub.StripeSubscription = stripeSubscription |
| paddy@2 | 217 } |
| paddy@0 | 218 |
| paddy@2 | 219 if i&subscriptionChangePlan != 0 { |
| paddy@2 | 220 plan = "plan-" + strI |
| paddy@2 | 221 change.Plan = &plan |
| paddy@2 | 222 sub.Plan = plan |
| paddy@2 | 223 } |
| paddy@0 | 224 |
| paddy@2 | 225 if i&subscriptionChangeStatus != 0 { |
| paddy@2 | 226 status = "status-" + strI |
| paddy@2 | 227 change.Status = &status |
| paddy@2 | 228 sub.Status = status |
| paddy@2 | 229 } |
| paddy@0 | 230 |
| paddy@2 | 231 if i&subscriptionChangeCanceling != 0 { |
| paddy@2 | 232 canceling = i%2 == 0 |
| paddy@2 | 233 change.Canceling = &canceling |
| paddy@2 | 234 sub.Canceling = canceling |
| paddy@2 | 235 } |
| paddy@0 | 236 |
| paddy@2 | 237 if i&subscriptionChangeTrialStart != 0 { |
| paddy@2 | 238 trialStart = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) |
| paddy@2 | 239 change.TrialStart = &trialStart |
| paddy@2 | 240 sub.TrialStart = trialStart |
| paddy@2 | 241 } |
| paddy@0 | 242 |
| paddy@2 | 243 if i&subscriptionChangeTrialEnd != 0 { |
| paddy@2 | 244 trialEnd = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) |
| paddy@2 | 245 change.TrialEnd = &trialEnd |
| paddy@2 | 246 sub.TrialEnd = trialEnd |
| paddy@2 | 247 } |
| paddy@0 | 248 |
| paddy@2 | 249 if i&subscriptionChangePeriodStart != 0 { |
| paddy@2 | 250 periodStart = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) |
| paddy@2 | 251 change.PeriodStart = &periodStart |
| paddy@2 | 252 sub.PeriodStart = periodStart |
| paddy@2 | 253 } |
| paddy@0 | 254 |
| paddy@2 | 255 if i&subscriptionChangePeriodEnd != 0 { |
| paddy@2 | 256 periodEnd = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) |
| paddy@2 | 257 change.PeriodEnd = &periodEnd |
| paddy@2 | 258 sub.PeriodEnd = periodEnd |
| paddy@2 | 259 } |
| paddy@0 | 260 |
| paddy@2 | 261 if i&subscriptionChangeCanceledAt != 0 { |
| paddy@2 | 262 canceledAt = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) |
| paddy@2 | 263 change.CanceledAt = &canceledAt |
| paddy@2 | 264 sub.CanceledAt = canceledAt |
| paddy@0 | 265 } |
| paddy@2 | 266 |
| paddy@2 | 267 if i&subscriptionChangeFailedChargeAttempts != 0 { |
| paddy@2 | 268 failedChargeAttempts = i |
| paddy@2 | 269 change.FailedChargeAttempts = &failedChargeAttempts |
| paddy@2 | 270 sub.FailedChargeAttempts = failedChargeAttempts |
| paddy@2 | 271 } |
| paddy@2 | 272 |
| paddy@2 | 273 if i&subscriptionChangeLastFailedCharge != 0 { |
| paddy@2 | 274 lastFailedCharge = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) |
| paddy@2 | 275 change.LastFailedCharge = &lastFailedCharge |
| paddy@2 | 276 sub.LastFailedCharge = lastFailedCharge |
| paddy@2 | 277 } |
| paddy@2 | 278 |
| paddy@2 | 279 if i&subscriptionChangeLastNotified != 0 { |
| paddy@2 | 280 lastNotified = time.Now().Round(time.Millisecond).Add(time.Hour * time.Duration(i)) |
| paddy@2 | 281 change.LastNotified = &lastNotified |
| paddy@2 | 282 sub.LastNotified = lastNotified |
| paddy@2 | 283 } |
| paddy@2 | 284 |
| paddy@2 | 285 empty = change.IsEmpty() |
| paddy@2 | 286 if empty { |
| paddy@2 | 287 t.Errorf("Expected empty to be %t, was %t\n", false, empty) |
| paddy@2 | 288 } |
| paddy@2 | 289 |
| paddy@2 | 290 result.ApplyChange(change) |
| paddy@2 | 291 match, field, expected, got := compareSubscriptions(sub, result) |
| paddy@2 | 292 if !match { |
| paddy@2 | 293 t.Errorf("Expected field `%s` to be `%v`, got `%v`\n", field, expected, got) |
| paddy@0 | 294 } |
| paddy@3 | 295 err = store.UpdateSubscription(sub.UserID, change) |
| paddy@0 | 296 if err != nil { |
| paddy@0 | 297 t.Errorf("Error updating subscription in %T: %s\n", store, err) |
| paddy@0 | 298 } |
| paddy@3 | 299 retrieved, err := store.GetSubscriptions([]uuid.ID{sub.UserID}) |
| paddy@0 | 300 if err != nil { |
| paddy@0 | 301 t.Errorf("Error getting subscription from %T: %s\n", store, err) |
| paddy@0 | 302 } |
| paddy@0 | 303 ok, missing := subscriptionMapContains(retrieved, sub) |
| paddy@0 | 304 if !ok { |
| paddy@1 | 305 t.Errorf("Expected to retrieve %s from %T, but missing was %+v\n", sub.UserID.String(), store, missing) |
| paddy@0 | 306 } |
| paddy@2 | 307 match, field, expected, got = compareSubscriptions(sub, retrieved[sub.UserID.String()]) |
| paddy@0 | 308 if !match { |
| paddy@0 | 309 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T\n", field, expected, got, store) |
| paddy@0 | 310 } |
| paddy@2 | 311 sub = result |
| paddy@0 | 312 } |
| paddy@2 | 313 |
| paddy@3 | 314 err = store.CreateSubscription(sub2) |
| paddy@0 | 315 if err != nil { |
| paddy@0 | 316 t.Fatalf("Error saving subscription in %T: %+v\n", store, err) |
| paddy@0 | 317 } |
| paddy@0 | 318 change := SubscriptionChange{} |
| paddy@3 | 319 err = store.UpdateSubscription(sub.UserID, change) |
| paddy@0 | 320 if err != ErrSubscriptionChangeEmpty { |
| paddy@0 | 321 t.Errorf("Expected err to be %+v, but got %+v from %T\n", ErrSubscriptionChangeEmpty, err, store) |
| paddy@0 | 322 } |
| paddy@2 | 323 stripeSubscription := sub2.StripeSubscription |
| paddy@2 | 324 change.StripeSubscription = &stripeSubscription |
| paddy@3 | 325 err = store.UpdateSubscription(uuid.NewID(), change) |
| paddy@0 | 326 if err != ErrSubscriptionNotFound { |
| paddy@0 | 327 t.Errorf("Expected err to be %+v, but got %+v from %T\n", ErrSubscriptionNotFound, err, store) |
| paddy@0 | 328 } |
| paddy@3 | 329 err = store.UpdateSubscription(sub.UserID, change) |
| paddy@2 | 330 if err != ErrStripeSubscriptionAlreadyExists { |
| paddy@2 | 331 t.Errorf("Expected err to be %+v, but got %+v from %T\n", ErrStripeSubscriptionAlreadyExists, err, store) |
| paddy@0 | 332 } |
| paddy@0 | 333 } |
| paddy@0 | 334 } |
| paddy@0 | 335 |
| paddy@0 | 336 func TestDeleteSubscription(t *testing.T) { |
| paddy@0 | 337 for _, store := range testSubscriptionStores { |
| paddy@3 | 338 err := store.Reset() |
| paddy@0 | 339 if err != nil { |
| paddy@0 | 340 t.Fatalf("Error resetting %T: %+v\n", store, err) |
| paddy@0 | 341 } |
| paddy@0 | 342 sub1 := Subscription{ |
| paddy@2 | 343 UserID: uuid.NewID(), |
| paddy@2 | 344 StripeSubscription: "stripeSubscription1", |
| paddy@0 | 345 } |
| paddy@0 | 346 sub2 := Subscription{ |
| paddy@2 | 347 UserID: uuid.NewID(), |
| paddy@2 | 348 StripeSubscription: "stripeSubscription2", |
| paddy@0 | 349 } |
| paddy@3 | 350 err = store.CreateSubscription(sub1) |
| paddy@0 | 351 if err != nil { |
| paddy@0 | 352 t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) |
| paddy@0 | 353 } |
| paddy@3 | 354 err = store.CreateSubscription(sub2) |
| paddy@0 | 355 if err != nil { |
| paddy@2 | 356 t.Fatalf("Error creating %+v in %T: %+v\n", sub2, store, err) |
| paddy@0 | 357 } |
| paddy@3 | 358 err = store.DeleteSubscription(sub1.UserID) |
| paddy@0 | 359 if err != nil { |
| paddy@0 | 360 t.Fatalf("Error deleting %+v in %T: %+v\n", sub1, store, err) |
| paddy@0 | 361 } |
| paddy@3 | 362 retrieved, err := store.GetSubscriptions([]uuid.ID{sub1.UserID, sub2.UserID}) |
| paddy@0 | 363 if err != nil { |
| paddy@0 | 364 t.Errorf("Error retrieving subscriptions from %T: %+v\n", store, err) |
| paddy@0 | 365 } |
| paddy@0 | 366 ok, missing := subscriptionMapContains(retrieved, sub1) |
| paddy@0 | 367 if ok { |
| paddy@1 | 368 t.Errorf("Expected not to retrieve %s from %T, but missing was %+v\n", sub1.UserID.String(), store, missing) |
| paddy@0 | 369 } |
| paddy@0 | 370 ok, missing = subscriptionMapContains(retrieved, sub2) |
| paddy@0 | 371 if !ok { |
| paddy@1 | 372 t.Errorf("Expected to retrieve %s from %T, but missing was %+v\n", sub2.UserID.String(), store, missing) |
| paddy@0 | 373 } |
| paddy@3 | 374 err = store.DeleteSubscription(sub1.UserID) |
| paddy@0 | 375 if err != ErrSubscriptionNotFound { |
| paddy@0 | 376 t.Errorf("Expected err to be %+v, but got %+v from %T\n", ErrSubscriptionNotFound, err, store) |
| paddy@0 | 377 } |
| paddy@0 | 378 } |
| paddy@0 | 379 } |
| paddy@0 | 380 |
| paddy@0 | 381 func TestGetSubscriptions(t *testing.T) { |
| paddy@0 | 382 for _, store := range testSubscriptionStores { |
| paddy@3 | 383 err := store.Reset() |
| paddy@0 | 384 if err != nil { |
| paddy@0 | 385 t.Fatalf("Error resetting %T: %+v\n", store, err) |
| paddy@0 | 386 } |
| paddy@0 | 387 sub1 := Subscription{ |
| paddy@2 | 388 UserID: uuid.NewID(), |
| paddy@2 | 389 StripeSubscription: "stripeSubscription1", |
| paddy@2 | 390 Plan: "plan1", |
| paddy@2 | 391 Created: time.Now().Round(time.Millisecond), |
| paddy@2 | 392 TrialStart: time.Now().Round(time.Millisecond), |
| paddy@2 | 393 TrialEnd: time.Now().Round(time.Millisecond).Add(time.Hour * 24 * 32), |
| paddy@0 | 394 } |
| paddy@0 | 395 sub2 := Subscription{ |
| paddy@2 | 396 UserID: uuid.NewID(), |
| paddy@2 | 397 StripeSubscription: "stripeSubscription2", |
| paddy@2 | 398 Plan: "plan2", |
| paddy@2 | 399 Created: time.Now().Round(time.Millisecond).Add(time.Hour * -720), |
| paddy@2 | 400 TrialStart: time.Now().Round(time.Millisecond).Add(time.Hour * -720), |
| paddy@2 | 401 TrialEnd: time.Now().Round(time.Millisecond), |
| paddy@0 | 402 } |
| paddy@0 | 403 sub3 := Subscription{ |
| paddy@2 | 404 UserID: uuid.NewID(), |
| paddy@2 | 405 StripeSubscription: "stripeSubscription3", |
| paddy@2 | 406 Plan: "plan3", |
| paddy@2 | 407 Created: time.Now().Round(time.Millisecond).Add(time.Hour * -1440), |
| paddy@2 | 408 TrialStart: time.Now().Round(time.Millisecond).Add(time.Hour * -1440), |
| paddy@2 | 409 TrialEnd: time.Now().Round(time.Millisecond).Add(time.Hour * -720), |
| paddy@2 | 410 PeriodStart: time.Now().Round(time.Millisecond).Add(time.Hour * -720), |
| paddy@2 | 411 PeriodEnd: time.Now().Round(time.Millisecond), |
| paddy@2 | 412 Status: "unpaid", |
| paddy@0 | 413 } |
| paddy@3 | 414 err = store.CreateSubscription(sub1) |
| paddy@0 | 415 if err != nil { |
| paddy@0 | 416 t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) |
| paddy@0 | 417 } |
| paddy@3 | 418 err = store.CreateSubscription(sub2) |
| paddy@0 | 419 if err != nil { |
| paddy@0 | 420 t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) |
| paddy@0 | 421 } |
| paddy@3 | 422 err = store.CreateSubscription(sub3) |
| paddy@0 | 423 if err != nil { |
| paddy@0 | 424 t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) |
| paddy@0 | 425 } |
| paddy@3 | 426 retrieved, err := store.GetSubscriptions([]uuid.ID{}) |
| paddy@0 | 427 if err != ErrNoSubscriptionID { |
| paddy@0 | 428 t.Errorf("Error retrieving no subscriptions from %T. Expected %+v, got %+v\n", store, ErrNoSubscriptionID, err) |
| paddy@0 | 429 } |
| paddy@3 | 430 retrieved, err = store.GetSubscriptions([]uuid.ID{sub1.UserID}) |
| paddy@0 | 431 if err != nil { |
| paddy@1 | 432 t.Errorf("Error retrieving %s from %T: %+v\n", sub1.UserID, store, err) |
| paddy@0 | 433 } |
| paddy@0 | 434 ok, missing := subscriptionMapContains(retrieved, sub1) |
| paddy@0 | 435 if !ok { |
| paddy@0 | 436 t.Logf("Results: %+v\n", retrieved) |
| paddy@0 | 437 t.Errorf("Expected %+v to be in the results, was not for %T.\n", missing, store) |
| paddy@0 | 438 } |
| paddy@3 | 439 retrieved, err = store.GetSubscriptions([]uuid.ID{sub1.UserID, sub2.UserID}) |
| paddy@0 | 440 if err != nil { |
| paddy@1 | 441 t.Errorf("Error retrieving %s and %s from %T: %+v\n", sub1.UserID, sub2.UserID, store, err) |
| paddy@0 | 442 } |
| paddy@0 | 443 ok, missing = subscriptionMapContains(retrieved, sub1, sub2) |
| paddy@0 | 444 if !ok { |
| paddy@0 | 445 t.Logf("Results: %+v\n", retrieved) |
| paddy@0 | 446 t.Errorf("Expected %+v to be in the results, was not for %T.\n", missing, store) |
| paddy@0 | 447 } |
| paddy@3 | 448 retrieved, err = store.GetSubscriptions([]uuid.ID{sub1.UserID, sub3.UserID}) |
| paddy@0 | 449 if err != nil { |
| paddy@1 | 450 t.Errorf("Error retrieving %s and %s from %T: %+v\n", sub1.UserID, sub3.UserID, store, err) |
| paddy@0 | 451 } |
| paddy@0 | 452 ok, missing = subscriptionMapContains(retrieved, sub1, sub3) |
| paddy@0 | 453 if !ok { |
| paddy@0 | 454 t.Logf("Results: %+v\n", retrieved) |
| paddy@0 | 455 t.Errorf("Expected %+v to be in the results, was not for %T.\n", missing, store) |
| paddy@0 | 456 } |
| paddy@3 | 457 retrieved, err = store.GetSubscriptions([]uuid.ID{sub1.UserID, sub2.UserID, sub3.UserID}) |
| paddy@0 | 458 if err != nil { |
| paddy@1 | 459 t.Errorf("Error retrieving %s, %s, and %s from %T: %+v\n", sub1.UserID, sub2.UserID, sub3.UserID, store, err) |
| paddy@0 | 460 } |
| paddy@0 | 461 ok, missing = subscriptionMapContains(retrieved, sub1, sub2, sub3) |
| paddy@0 | 462 if !ok { |
| paddy@0 | 463 t.Logf("Results: %+v\n", retrieved) |
| paddy@0 | 464 t.Errorf("Expected %+v to be in the results, was not for %T.\n", missing, store) |
| paddy@0 | 465 } |
| paddy@3 | 466 retrieved, err = store.GetSubscriptions([]uuid.ID{sub2.UserID}) |
| paddy@0 | 467 if err != nil { |
| paddy@1 | 468 t.Errorf("Error retrieving %s from %T: %+v\n", sub2.UserID, store, err) |
| paddy@0 | 469 } |
| paddy@0 | 470 ok, missing = subscriptionMapContains(retrieved, sub2) |
| paddy@0 | 471 if !ok { |
| paddy@0 | 472 t.Logf("Results: %+v\n", retrieved) |
| paddy@0 | 473 t.Errorf("Expected %+v to be in the results, was not for %T.\n", missing, store) |
| paddy@0 | 474 } |
| paddy@3 | 475 retrieved, err = store.GetSubscriptions([]uuid.ID{sub2.UserID, sub3.UserID}) |
| paddy@0 | 476 if err != nil { |
| paddy@1 | 477 t.Errorf("Error retrieving %s and %s from %T: %+v\n", sub2.UserID, sub3.UserID, store, err) |
| paddy@0 | 478 } |
| paddy@0 | 479 ok, missing = subscriptionMapContains(retrieved, sub2, sub3) |
| paddy@0 | 480 if !ok { |
| paddy@0 | 481 t.Logf("Results: %+v\n", retrieved) |
| paddy@0 | 482 t.Errorf("Expected %+v to be in the results, was not for %T.\n", missing, store) |
| paddy@0 | 483 } |
| paddy@3 | 484 retrieved, err = store.GetSubscriptions([]uuid.ID{sub3.UserID}) |
| paddy@0 | 485 if err != nil { |
| paddy@1 | 486 t.Errorf("Error retrieving %s from %T: %+v\n", sub3.UserID, store, err) |
| paddy@0 | 487 } |
| paddy@0 | 488 ok, missing = subscriptionMapContains(retrieved, sub3) |
| paddy@0 | 489 if !ok { |
| paddy@0 | 490 t.Logf("Results: %+v\n", retrieved) |
| paddy@0 | 491 t.Errorf("Expected %+v to be in the results, was not for %T.\n", missing, store) |
| paddy@0 | 492 } |
| paddy@3 | 493 retrieved, err = store.GetSubscriptions([]uuid.ID{uuid.NewID()}) |
| paddy@0 | 494 if err != nil { |
| paddy@0 | 495 t.Errorf("Error retrieving non-existent ID from %T: %+v\n", store, err) |
| paddy@0 | 496 } |
| paddy@0 | 497 if len(retrieved) != 0 { |
| paddy@0 | 498 t.Errorf("Expected no results, %T returned %+v\n", store, retrieved) |
| paddy@0 | 499 } |
| paddy@3 | 500 retrieved, err = store.GetSubscriptions([]uuid.ID{sub1.UserID, sub2.UserID, uuid.NewID(), sub3.UserID}) |
| paddy@0 | 501 if err != nil { |
| paddy@0 | 502 t.Errorf("Error retrieving non-existent ID from %T: %+v\n", store, err) |
| paddy@0 | 503 } |
| paddy@0 | 504 if len(retrieved) != 3 { |
| paddy@0 | 505 t.Errorf("Expected 3 results, %T returned %+v\n", store, retrieved) |
| paddy@0 | 506 } |
| paddy@0 | 507 ok, missing = subscriptionMapContains(retrieved, sub1, sub2, sub3) |
| paddy@0 | 508 if !ok { |
| paddy@0 | 509 t.Logf("Results: %+v\n", retrieved) |
| paddy@0 | 510 t.Errorf("Expected %+v to be in the results, was not for %T.\n", missing, store) |
| paddy@0 | 511 } |
| paddy@0 | 512 } |
| paddy@0 | 513 } |
| paddy@2 | 514 |
| paddy@2 | 515 func TestGetSubscriptionStats(t *testing.T) { |
| paddy@2 | 516 for _, store := range testSubscriptionStores { |
| paddy@3 | 517 err := store.Reset() |
| paddy@2 | 518 if err != nil { |
| paddy@2 | 519 t.Fatalf("Error resetting %T: %+v\n", store, err) |
| paddy@2 | 520 } |
| paddy@2 | 521 sub1 := Subscription{ |
| paddy@2 | 522 UserID: uuid.NewID(), |
| paddy@2 | 523 StripeSubscription: "stripeSubscription1", |
| paddy@2 | 524 Plan: "plan1", |
| paddy@2 | 525 Canceling: true, |
| paddy@2 | 526 } |
| paddy@2 | 527 sub2 := Subscription{ |
| paddy@2 | 528 UserID: uuid.NewID(), |
| paddy@2 | 529 StripeSubscription: "stripeSubscription2", |
| paddy@2 | 530 Plan: "plan2", |
| paddy@2 | 531 Status: "past_due", |
| paddy@2 | 532 } |
| paddy@3 | 533 err = store.CreateSubscription(sub1) |
| paddy@2 | 534 if err != nil { |
| paddy@2 | 535 t.Fatalf("Error creating %+v in %T: %+v\n", sub1, store, err) |
| paddy@2 | 536 } |
| paddy@3 | 537 stats, err := store.GetSubscriptionStats() |
| paddy@2 | 538 if err != nil { |
| paddy@2 | 539 t.Errorf("Error getting stats from %T: %+v\n", store, err) |
| paddy@2 | 540 } |
| paddy@2 | 541 ok, field, expected, results := compareSubscriptionStats(SubscriptionStats{ |
| paddy@2 | 542 Number: 1, |
| paddy@2 | 543 Canceling: 1, |
| paddy@2 | 544 Failing: 0, |
| paddy@2 | 545 Plans: map[string]int64{ |
| paddy@2 | 546 "plan1": 1, |
| paddy@2 | 547 }, |
| paddy@2 | 548 }, stats) |
| paddy@2 | 549 if !ok { |
| paddy@2 | 550 t.Errorf("Expected %s to be %+v, got %+v from %T\n", field, expected, results, store) |
| paddy@2 | 551 } |
| paddy@3 | 552 err = store.CreateSubscription(sub2) |
| paddy@2 | 553 if err != nil { |
| paddy@2 | 554 t.Fatalf("Error creating %+v in %T: %+v\n", sub2, store, err) |
| paddy@2 | 555 } |
| paddy@3 | 556 stats, err = store.GetSubscriptionStats() |
| paddy@2 | 557 if err != nil { |
| paddy@2 | 558 t.Errorf("Error getting status from %T: %+v\n", store, err) |
| paddy@2 | 559 } |
| paddy@2 | 560 ok, field, expected, results = compareSubscriptionStats(SubscriptionStats{ |
| paddy@2 | 561 Number: 2, |
| paddy@2 | 562 Canceling: 1, |
| paddy@2 | 563 Failing: 1, |
| paddy@2 | 564 Plans: map[string]int64{ |
| paddy@2 | 565 "plan1": 1, |
| paddy@2 | 566 "plan2": 1, |
| paddy@2 | 567 }, |
| paddy@2 | 568 }, stats) |
| paddy@2 | 569 if !ok { |
| paddy@2 | 570 t.Errorf("Expected %s to be %+v, got %+v from %T\n", field, expected, results, store) |
| paddy@2 | 571 } |
| paddy@3 | 572 err = store.DeleteSubscription(sub1.UserID) |
| paddy@2 | 573 if err != nil { |
| paddy@2 | 574 t.Errorf("Error deleting subscription from %T: %+v\n", store, err) |
| paddy@2 | 575 } |
| paddy@3 | 576 stats, err = store.GetSubscriptionStats() |
| paddy@2 | 577 if err != nil { |
| paddy@2 | 578 t.Errorf("Error getting status from %T: %+v\n", store, err) |
| paddy@2 | 579 } |
| paddy@2 | 580 ok, field, expected, results = compareSubscriptionStats(SubscriptionStats{ |
| paddy@2 | 581 Number: 1, |
| paddy@2 | 582 Canceling: 0, |
| paddy@2 | 583 Failing: 1, |
| paddy@2 | 584 Plans: map[string]int64{ |
| paddy@2 | 585 "plan2": 1, |
| paddy@2 | 586 }, |
| paddy@2 | 587 }, stats) |
| paddy@2 | 588 if !ok { |
| paddy@2 | 589 t.Errorf("Expected %s to be %+v, got %+v from %T\n", field, expected, results, store) |
| paddy@2 | 590 } |
| paddy@2 | 591 } |
| paddy@2 | 592 } |