Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit d9f91e4

Browse files
Merge branch 'main' into rvinnakota/indirect-misses
2 parents 9d03cb1 + 15d2415 commit d9f91e4

37 files changed

+570
-156
lines changed

api/internal/commit/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def get_report(self, commit: Commit):
4141
for filename in report.files:
4242
file_report = report.get(filename)
4343
file_totals = CommitTotalsSerializer(
44-
{key: val for key, val in zip(TOTALS_MAP, file_report.totals)}
44+
dict(zip(TOTALS_MAP, file_report.totals))
4545
)
4646
files.append(
4747
{

api/internal/owner/serializers.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
from dataclasses import asdict
32
from datetime import datetime
43

54
from dateutil.relativedelta import relativedelta
@@ -127,10 +126,9 @@ def validate_value(self, value):
127126
current_owner = self.context["request"].current_owner
128127

129128
plan_service = PlanService(current_org=current_org)
130-
available_plans = [
131-
asdict(plan) for plan in plan_service.available_plans(current_owner)
129+
plan_values = [
130+
plan["value"] for plan in plan_service.available_plans(current_owner)
132131
]
133-
plan_values = [plan["value"] for plan in available_plans]
134132
if value not in plan_values:
135133
if value in SENTRY_PAID_USER_PLAN_REPRESENTATIONS:
136134
log.warning(

api/internal/tests/views/test_account_viewset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ def test_update_team_plan_must_fail_if_too_many_activated_users_during_trial(sel
960960
self.current_owner.plan = PlanName.BASIC_PLAN_NAME.value
961961
self.current_owner.plan_user_count = 1
962962
self.current_owner.trial_status = TrialStatus.ONGOING.value
963-
self.current_owner.plan_activated_users = [i for i in range(11)]
963+
self.current_owner.plan_activated_users = list(range(11))
964964
self.current_owner.save()
965965

966966
desired_plans = [

api/internal/tests/views/test_self_hosted_user_viewset.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from unittest.mock import patch
22

3-
from django.test import TransactionTestCase, override_settings
3+
from django.test import TestCase, override_settings
44
from rest_framework.reverse import reverse
55
from shared.django_apps.core.tests.factories import OwnerFactory
66

@@ -10,15 +10,15 @@
1010

1111

1212
@override_settings(IS_ENTERPRISE=True, ROOT_URLCONF="api.internal.enterprise_urls")
13-
class UserViewsetUnauthenticatedTestCase(TransactionTestCase):
13+
class UserViewsetUnauthenticatedTestCase(TestCase):
1414
def test_list_users(self):
1515
res = self.client.get(reverse("selfhosted-users-list"))
1616
# not authenticated
1717
assert res.status_code == 401
1818

1919

2020
@override_settings(IS_ENTERPRISE=True, ROOT_URLCONF="api.internal.enterprise_urls")
21-
class UserViewsetTestCase(TransactionTestCase):
21+
class UserViewsetTestCase(TestCase):
2222
def setUp(self):
2323
self.owner = OwnerFactory()
2424
self.current_owner = OwnerFactory(organizations=[self.owner.ownerid])

api/internal/tests/views/test_user_viewset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from rest_framework import status
55
from rest_framework.reverse import reverse
6-
from rest_framework.test import APITransactionTestCase
6+
from rest_framework.test import APITestCase
77
from shared.django_apps.core.tests.factories import (
88
OwnerFactory,
99
PullFactory,
@@ -14,7 +14,7 @@
1414
from utils.test_utils import APIClient
1515

1616

17-
class UserViewSetTests(APITransactionTestCase):
17+
class UserViewSetTests(APITestCase):
1818
def setUp(self):
1919
non_org_active_user = OwnerFactory()
2020
self.current_owner = OwnerFactory(

billing/tests/test_views.py

Lines changed: 160 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,35 @@ def __getitem__(self, key):
3939
return getattr(self, key)
4040

4141

42+
class MockCard(object):
43+
def __init__(self):
44+
self.brand = "visa"
45+
self.last4 = "1234"
46+
47+
def __getitem__(self, key):
48+
return getattr(self, key)
49+
50+
51+
class MockPaymentMethod(object):
52+
def __init__(self, noCard=False):
53+
if noCard:
54+
self.card = None
55+
return
56+
57+
self.card = MockCard()
58+
59+
def __getitem__(self, key):
60+
return getattr(self, key)
61+
62+
63+
class MockPaymentIntent(object):
64+
def __init__(self, noCard=False):
65+
self.payment_method = MockPaymentMethod(noCard)
66+
67+
def __getitem__(self, key):
68+
return getattr(self, key)
69+
70+
4271
class StripeWebhookHandlerTests(APITestCase):
4372
def setUp(self):
4473
self.owner = OwnerFactory(
@@ -97,6 +126,8 @@ def test_invoice_payment_succeeded_sets_owner_delinquent_false(self):
97126
"object": {
98127
"customer": self.owner.stripe_customer_id,
99128
"subscription": self.owner.stripe_subscription_id,
129+
"total": 24000,
130+
"hosted_invoice_url": "https://stripe.com",
100131
}
101132
},
102133
}
@@ -120,6 +151,72 @@ def test_invoice_payment_succeeded_sets_multiple_owners_delinquent_false(self):
120151
"object": {
121152
"customer": self.owner.stripe_customer_id,
122153
"subscription": self.owner.stripe_subscription_id,
154+
"total": 24000,
155+
"hosted_invoice_url": "https://stripe.com",
156+
}
157+
},
158+
}
159+
)
160+
161+
self.owner.refresh_from_db()
162+
self.other_owner.refresh_from_db()
163+
assert response.status_code == status.HTTP_204_NO_CONTENT
164+
assert self.owner.delinquent is False
165+
assert self.other_owner.delinquent is False
166+
167+
@patch("services.task.TaskService.send_email")
168+
def test_invoice_payment_succeeded_emails_only_emails_delinquents(
169+
self,
170+
mocked_send_email,
171+
):
172+
self.add_second_owner()
173+
self.owner.delinquent = False
174+
self.owner.save()
175+
176+
response = self._send_event(
177+
payload={
178+
"type": "invoice.payment_succeeded",
179+
"data": {
180+
"object": {
181+
"customer": self.owner.stripe_customer_id,
182+
"subscription": self.owner.stripe_subscription_id,
183+
"total": 24000,
184+
"hosted_invoice_url": "https://stripe.com",
185+
}
186+
},
187+
}
188+
)
189+
190+
self.owner.refresh_from_db()
191+
self.other_owner.refresh_from_db()
192+
assert response.status_code == status.HTTP_204_NO_CONTENT
193+
assert self.owner.delinquent is False
194+
195+
mocked_send_email.assert_not_called()
196+
197+
@patch("services.task.TaskService.send_email")
198+
def test_invoice_payment_succeeded_emails_delinquents(self, mocked_send_email):
199+
non_admin = OwnerFactory(email="[email protected]")
200+
admin_1 = OwnerFactory(email="[email protected]")
201+
admin_2 = OwnerFactory(email="[email protected]")
202+
self.owner.admins = [admin_1.ownerid, admin_2.ownerid]
203+
self.owner.plan_activated_users = [non_admin.ownerid]
204+
self.owner.email = "[email protected]"
205+
self.owner.delinquent = True
206+
self.owner.save()
207+
self.add_second_owner()
208+
self.other_owner.delinquent = False
209+
self.other_owner.save()
210+
211+
response = self._send_event(
212+
payload={
213+
"type": "invoice.payment_succeeded",
214+
"data": {
215+
"object": {
216+
"customer": self.owner.stripe_customer_id,
217+
"subscription": self.owner.stripe_subscription_id,
218+
"total": 24000,
219+
"hosted_invoice_url": "https://stripe.com",
123220
}
124221
},
125222
}
@@ -131,22 +228,54 @@ def test_invoice_payment_succeeded_sets_multiple_owners_delinquent_false(self):
131228
assert self.owner.delinquent is False
132229
assert self.other_owner.delinquent is False
133230

134-
def test_invoice_payment_failed_sets_owner_delinquent_true(self):
231+
expected_calls = [
232+
call(
233+
to_addr=self.owner.email,
234+
subject="You're all set",
235+
template_name="success-after-failed-payment",
236+
amount=240,
237+
cta_link="https://stripe.com",
238+
date=datetime.now().strftime("%B %-d, %Y"),
239+
),
240+
call(
241+
to_addr=admin_1.email,
242+
subject="You're all set",
243+
template_name="success-after-failed-payment",
244+
amount=240,
245+
cta_link="https://stripe.com",
246+
date=datetime.now().strftime("%B %-d, %Y"),
247+
),
248+
call(
249+
to_addr=admin_2.email,
250+
subject="You're all set",
251+
template_name="success-after-failed-payment",
252+
amount=240,
253+
cta_link="https://stripe.com",
254+
date=datetime.now().strftime("%B %-d, %Y"),
255+
),
256+
]
257+
258+
mocked_send_email.assert_has_calls(expected_calls)
259+
260+
@patch("services.billing.stripe.PaymentIntent.retrieve")
261+
def test_invoice_payment_failed_sets_owner_delinquent_true(
262+
self, retrieve_paymentintent_mock
263+
):
135264
self.owner.delinquent = False
136265
self.owner.save()
137266

267+
retrieve_paymentintent_mock.return_value = MockPaymentIntent()
268+
138269
response = self._send_event(
139270
payload={
140271
"type": "invoice.payment_failed",
141272
"data": {
142273
"object": {
143274
"customer": self.owner.stripe_customer_id,
144275
"subscription": self.owner.stripe_subscription_id,
145-
"default_payment_method": {
146-
"card": {"brand": "visa", "last4": 1234}
147-
},
148276
"total": 24000,
149277
"hosted_invoice_url": "https://stripe.com",
278+
"payment_intent": "payment_intent_asdf",
150279
}
151280
},
152281
}
@@ -156,25 +285,28 @@ def test_invoice_payment_failed_sets_owner_delinquent_true(self):
156285
assert response.status_code == status.HTTP_204_NO_CONTENT
157286
assert self.owner.delinquent is True
158287

159-
def test_invoice_payment_failed_sets_multiple_owners_delinquent_true(self):
288+
@patch("services.billing.stripe.PaymentIntent.retrieve")
289+
def test_invoice_payment_failed_sets_multiple_owners_delinquent_true(
290+
self, retrieve_paymentintent_mock
291+
):
160292
self.add_second_owner()
161293
self.owner.delinquent = False
162294
self.owner.save()
163295
self.other_owner.delinquent = False
164296
self.other_owner.save()
165297

298+
retrieve_paymentintent_mock.return_value = MockPaymentIntent()
299+
166300
response = self._send_event(
167301
payload={
168302
"type": "invoice.payment_failed",
169303
"data": {
170304
"object": {
171305
"customer": self.owner.stripe_customer_id,
172306
"subscription": self.owner.stripe_subscription_id,
173-
"default_payment_method": {
174-
"card": {"brand": "visa", "last4": 1234}
175-
},
176307
"total": 24000,
177308
"hosted_invoice_url": "https://stripe.com",
309+
"payment_intent": "payment_intent_asdf",
178310
}
179311
},
180312
}
@@ -187,7 +319,12 @@ def test_invoice_payment_failed_sets_multiple_owners_delinquent_true(self):
187319
assert self.other_owner.delinquent is True
188320

189321
@patch("services.task.TaskService.send_email")
190-
def test_invoice_payment_failed_sends_email_to_admins(self, mocked_send_email):
322+
@patch("services.billing.stripe.PaymentIntent.retrieve")
323+
def test_invoice_payment_failed_sends_email_to_admins(
324+
self,
325+
retrieve_paymentintent_mock,
326+
mocked_send_email,
327+
):
191328
non_admin = OwnerFactory(email="[email protected]")
192329
admin_1 = OwnerFactory(email="[email protected]")
193330
admin_2 = OwnerFactory(email="[email protected]")
@@ -196,18 +333,18 @@ def test_invoice_payment_failed_sends_email_to_admins(self, mocked_send_email):
196333
self.owner.email = "[email protected]"
197334
self.owner.save()
198335

336+
retrieve_paymentintent_mock.return_value = MockPaymentIntent()
337+
199338
response = self._send_event(
200339
payload={
201340
"type": "invoice.payment_failed",
202341
"data": {
203342
"object": {
204343
"customer": self.owner.stripe_customer_id,
205344
"subscription": self.owner.stripe_subscription_id,
206-
"default_payment_method": {
207-
"card": {"brand": "visa", "last4": 1234}
208-
},
209345
"total": 24000,
210346
"hosted_invoice_url": "https://stripe.com",
347+
"payment_intent": "payment_intent_asdf",
211348
}
212349
},
213350
}
@@ -225,7 +362,7 @@ def test_invoice_payment_failed_sends_email_to_admins(self, mocked_send_email):
225362
name=self.owner.username,
226363
amount=240,
227364
card_type="visa",
228-
last_four=1234,
365+
last_four="1234",
229366
cta_link="https://stripe.com",
230367
date=datetime.now().strftime("%B %-d, %Y"),
231368
),
@@ -236,7 +373,7 @@ def test_invoice_payment_failed_sends_email_to_admins(self, mocked_send_email):
236373
name=admin_1.username,
237374
amount=240,
238375
card_type="visa",
239-
last_four=1234,
376+
last_four="1234",
240377
cta_link="https://stripe.com",
241378
date=datetime.now().strftime("%B %-d, %Y"),
242379
),
@@ -247,16 +384,20 @@ def test_invoice_payment_failed_sends_email_to_admins(self, mocked_send_email):
247384
name=admin_2.username,
248385
amount=240,
249386
card_type="visa",
250-
last_four=1234,
387+
last_four="1234",
251388
cta_link="https://stripe.com",
252389
date=datetime.now().strftime("%B %-d, %Y"),
253390
),
254391
]
392+
255393
mocked_send_email.assert_has_calls(expected_calls)
256394

257395
@patch("services.task.TaskService.send_email")
396+
@patch("services.billing.stripe.PaymentIntent.retrieve")
258397
def test_invoice_payment_failed_sends_email_to_admins_no_card(
259-
self, mocked_send_email
398+
self,
399+
retrieve_paymentintent_mock,
400+
mocked_send_email,
260401
):
261402
non_admin = OwnerFactory(email="[email protected]")
262403
admin_1 = OwnerFactory(email="[email protected]")
@@ -266,6 +407,8 @@ def test_invoice_payment_failed_sends_email_to_admins_no_card(
266407
self.owner.email = "[email protected]"
267408
self.owner.save()
268409

410+
retrieve_paymentintent_mock.return_value = MockPaymentIntent(noCard=True)
411+
269412
response = self._send_event(
270413
payload={
271414
"type": "invoice.payment_failed",
@@ -276,6 +419,7 @@ def test_invoice_payment_failed_sends_email_to_admins_no_card(
276419
"default_payment_method": None,
277420
"total": 24000,
278421
"hosted_invoice_url": "https://stripe.com",
422+
"payment_intent": "payment_intent_asdf",
279423
}
280424
},
281425
}

0 commit comments

Comments
 (0)