tenseleyflow/shithub / 735dcd9

Browse files

orgs tests: subscription.deleted for unknown sub is 200; .updated still 5xx

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
735dcd9af12f33cf14bb0886b0652122daf80a74
Parents
575cecb
Tree
7f98b93

1 changed file

StatusFile+-
M internal/web/handlers/orgs/billing_webhook_guard_test.go 78 0
internal/web/handlers/orgs/billing_webhook_guard_test.gomodified
@@ -185,6 +185,84 @@ func TestBillingWebhookGuardRefusesTeamPriceOnUserSubject(t *testing.T) {
185185
 	}
186186
 }
187187
 
188
+// TestBillingWebhookSubscriptionDeletedForUnknownSubIsNoOp locks PRO08
189
+// D5: when Stripe sends customer.subscription.deleted for a subscription
190
+// shithub has never seen (no metadata, no customer-id match, no
191
+// subscription-id match), the handler logs and returns 200 so Stripe
192
+// stops retrying. Other event types still 5xx to surface misconfig.
193
+func TestBillingWebhookSubscriptionDeletedForUnknownSubIsNoOp(t *testing.T) {
194
+	t.Parallel()
195
+	ctx := context.Background()
196
+	pool := dbtest.NewTestDB(t)
197
+	ownerID := insertOrgAvatarUser(t, pool, "owner")
198
+
199
+	// No metadata, no customer-id we've seen, no subscription-id we've
200
+	// seen. resolvePrincipalFromSubscription returns ErrPrincipalNotFound.
201
+	raw, err := json.Marshal(map[string]any{
202
+		"id":       "sub_unknown",
203
+		"customer": "cus_unknown",
204
+		"status":   "canceled",
205
+		"items":    map[string]any{"data": []map[string]any{}},
206
+	})
207
+	if err != nil {
208
+		t.Fatalf("marshal: %v", err)
209
+	}
210
+	fake := &fakeStripeRemote{
211
+		verifyWebhookFn: func(_ []byte, _ string) (stripeapi.Event, error) {
212
+			return stripeapi.Event{
213
+				ID:   "evt_unknown_delete",
214
+				Type: stripeapi.EventType("customer.subscription.deleted"),
215
+				Data: &stripeapi.EventData{Raw: raw},
216
+			}, nil
217
+		},
218
+	}
219
+	mux := newOrgBillingMux(t, pool, ownerID, fake)
220
+	resp := postBillingWebhook(t, mux, "evt_unknown_delete")
221
+	if resp.Code != http.StatusOK {
222
+		t.Fatalf("unknown-sub delete status=%d body=%s (expected 200 no-op)", resp.Code, resp.Body.String())
223
+	}
224
+	receipt, err := billingdb.New().GetWebhookEventReceipt(ctx, pool, "evt_unknown_delete")
225
+	if err != nil {
226
+		t.Fatalf("get receipt: %v", err)
227
+	}
228
+	if !receipt.ProcessedAt.Valid {
229
+		t.Fatalf("receipt should be marked processed (no retries needed), got %+v", receipt)
230
+	}
231
+}
232
+
233
+// TestBillingWebhookSubscriptionUpdatedForUnknownSubReturnsError is
234
+// the contrast: subscription.updated for an unknown sub still 5xx's
235
+// (operator should hear about it).
236
+func TestBillingWebhookSubscriptionUpdatedForUnknownSubReturnsError(t *testing.T) {
237
+	t.Parallel()
238
+	pool := dbtest.NewTestDB(t)
239
+	ownerID := insertOrgAvatarUser(t, pool, "owner")
240
+
241
+	raw, err := json.Marshal(map[string]any{
242
+		"id":       "sub_unknown_update",
243
+		"customer": "cus_unknown_update",
244
+		"status":   "active",
245
+		"items":    map[string]any{"data": []map[string]any{}},
246
+	})
247
+	if err != nil {
248
+		t.Fatalf("marshal: %v", err)
249
+	}
250
+	fake := &fakeStripeRemote{
251
+		verifyWebhookFn: func(_ []byte, _ string) (stripeapi.Event, error) {
252
+			return stripeapi.Event{
253
+				ID:   "evt_unknown_update",
254
+				Type: stripeapi.EventType("customer.subscription.updated"),
255
+				Data: &stripeapi.EventData{Raw: raw},
256
+			}, nil
257
+		},
258
+	}
259
+	mux := newOrgBillingMux(t, pool, ownerID, fake)
260
+	resp := postBillingWebhook(t, mux, "evt_unknown_update")
261
+	if resp.Code != http.StatusInternalServerError {
262
+		t.Fatalf("unknown-sub update status=%d body=%s (expected 5xx for operator visibility)", resp.Code, resp.Body.String())
263
+	}
264
+}
265
+
188266
 // TestBillingWebhookDropsStaleEvent locks PRO08 D4: a Stripe event
189267
 // with `created` older than the persisted last_event_at must NOT
190268
 // regress state. Pre-PRO08 a reverse-ordered retry could re-activate