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

Commit 3b105c0

Browse files
Merge branch 'main' into 3341-add-private-and-author-to-shelter-sync
2 parents 4da22ae + 0feeb80 commit 3b105c0

File tree

7 files changed

+178
-23
lines changed

7 files changed

+178
-23
lines changed

api/public/v1/serializers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ class Meta:
1616
"state",
1717
)
1818
fields = read_only_fields + ("user_provided_base_sha",)
19+
20+
21+
class PullIdSerializer(serializers.Serializer):
22+
pullid = serializers.IntegerField()

api/public/v1/tests/views/test_pull_viewset.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,10 @@ def test_post_pull_user_provided_base(self, pulls_sync_mock):
133133
)
134134
self.assertEqual(response.status_code, 405)
135135
assert not pulls_sync_mock.called
136+
137+
def test_get_pull_no_pullid_provided(self):
138+
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.repo.upload_token)
139+
response = self.client.get("/api/github/codecov/testRepoName/pulls/abc")
140+
self.assertEqual(response.status_code, 400)
141+
content = json.loads(response.content.decode())
142+
self.assertEqual(content["pullid"], ["A valid integer is required."])

api/public/v1/views.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import logging
22

3-
from django.db.models import OuterRef, Subquery
3+
from django.db.models import OuterRef, QuerySet, Subquery
44
from django.shortcuts import get_object_or_404
55
from django_filters.rest_framework import DjangoFilterBackend
66
from rest_framework import filters, mixins, viewsets
77

88
from api.shared.mixins import RepoPropertyMixin
99
from codecov_auth.authentication.repo_auth import RepositoryLegacyTokenAuthentication
10-
from core.models import Commit
10+
from core.models import Commit, Pull
1111
from services.task import TaskService
1212

1313
from .permissions import PullUpdatePermission
14-
from .serializers import PullSerializer
14+
from .serializers import PullIdSerializer, PullSerializer
1515

1616
log = logging.getLogger(__name__)
1717

@@ -30,8 +30,11 @@ class PullViewSet(
3030
authentication_classes = [RepositoryLegacyTokenAuthentication]
3131
permission_classes = [PullUpdatePermission]
3232

33-
def get_object(self):
34-
pullid = self.kwargs.get("pk")
33+
def get_object(self) -> Pull:
34+
serializer = PullIdSerializer(data={"pullid": self.kwargs.get("pk")})
35+
serializer.is_valid(raise_exception=True)
36+
pullid = serializer.validated_data["pullid"]
37+
3538
if self.request.method == "PUT":
3639
# Note: We create a new pull if needed to make sure that they can be updated
3740
# with a base before the upload has finished processing.
@@ -41,7 +44,7 @@ def get_object(self):
4144
return obj
4245
return get_object_or_404(self.get_queryset(), pullid=pullid)
4346

44-
def get_queryset(self):
47+
def get_queryset(self) -> QuerySet:
4548
return self.repo.pull_requests.annotate(
4649
ci_passed=Subquery(
4750
Commit.objects.filter(
@@ -50,7 +53,7 @@ def get_queryset(self):
5053
)
5154
)
5255

53-
def perform_update(self, serializer):
56+
def perform_update(self, serializer: PullSerializer) -> Pull:
5457
result = super().perform_update(serializer)
5558
TaskService().pulls_sync(repoid=self.repo.repoid, pullid=self.kwargs.get("pk"))
5659
return result

api/public/v2/compare/serializers.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ class ComparisonSerializer(BaseComparisonSerializer):
1616

1717
def get_files(self, comparison: Comparison) -> List[dict]:
1818
data = []
19-
for filename in comparison.head_report.files:
20-
file = comparison.get_file_comparison(filename, bypass_max_diff=True)
21-
if self._should_include_file(file):
22-
data.append(FileComparisonSerializer(file).data)
19+
if comparison.head_report is not None:
20+
for filename in comparison.head_report.files:
21+
file = comparison.get_file_comparison(filename, bypass_max_diff=True)
22+
if self._should_include_file(file):
23+
data.append(FileComparisonSerializer(file).data)
2324
return data
2425

2526

billing/tests/test_views.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,132 @@ class MockPaymentMethod:
16431643
"sub_123", default_payment_method=payment_method_retrieve_mock.return_value
16441644
)
16451645

1646+
@patch("logging.Logger.error")
1647+
@patch("services.billing.stripe.PaymentMethod.attach")
1648+
@patch("services.billing.stripe.Customer.modify")
1649+
@patch("services.billing.stripe.Subscription.modify")
1650+
@patch("services.billing.stripe.PaymentMethod.retrieve")
1651+
def test_check_and_handle_delayed_notification_payment_methods_no_subscription(
1652+
self,
1653+
payment_method_retrieve_mock,
1654+
subscription_modify_mock,
1655+
customer_modify_mock,
1656+
payment_method_attach_mock,
1657+
log_error_mock,
1658+
):
1659+
class MockPaymentMethod:
1660+
type = "us_bank_account"
1661+
us_bank_account = {}
1662+
id = "pm_123"
1663+
1664+
payment_method_retrieve_mock.return_value = MockPaymentMethod()
1665+
1666+
self.owner.stripe_subscription_id = None
1667+
self.owner.stripe_customer_id = "cus_123"
1668+
self.owner.save()
1669+
1670+
handler = StripeWebhookHandler()
1671+
handler._check_and_handle_delayed_notification_payment_methods(
1672+
"cus_123", "pm_123"
1673+
)
1674+
1675+
payment_method_retrieve_mock.assert_called_once_with("pm_123")
1676+
payment_method_attach_mock.assert_not_called()
1677+
customer_modify_mock.assert_not_called()
1678+
subscription_modify_mock.assert_not_called()
1679+
1680+
log_error_mock.assert_called_once_with(
1681+
"No owners found with that customer_id, something went wrong",
1682+
extra=dict(customer_id="cus_123"),
1683+
)
1684+
1685+
@patch("logging.Logger.error")
1686+
@patch("services.billing.stripe.PaymentMethod.attach")
1687+
@patch("services.billing.stripe.Customer.modify")
1688+
@patch("services.billing.stripe.Subscription.modify")
1689+
@patch("services.billing.stripe.PaymentMethod.retrieve")
1690+
def test_check_and_handle_delayed_notification_payment_methods_no_customer(
1691+
self,
1692+
payment_method_retrieve_mock,
1693+
subscription_modify_mock,
1694+
customer_modify_mock,
1695+
payment_method_attach_mock,
1696+
log_error_mock,
1697+
):
1698+
class MockPaymentMethod:
1699+
type = "us_bank_account"
1700+
us_bank_account = {}
1701+
id = "pm_123"
1702+
1703+
payment_method_retrieve_mock.return_value = MockPaymentMethod()
1704+
1705+
handler = StripeWebhookHandler()
1706+
handler._check_and_handle_delayed_notification_payment_methods(
1707+
"cus_1", "pm_123"
1708+
)
1709+
1710+
payment_method_retrieve_mock.assert_called_once_with("pm_123")
1711+
payment_method_attach_mock.assert_not_called()
1712+
customer_modify_mock.assert_not_called()
1713+
subscription_modify_mock.assert_not_called()
1714+
1715+
log_error_mock.assert_called_once_with(
1716+
"No owners found with that customer_id, something went wrong",
1717+
extra=dict(customer_id="cus_1"),
1718+
)
1719+
1720+
@patch("services.billing.stripe.PaymentMethod.attach")
1721+
@patch("services.billing.stripe.Customer.modify")
1722+
@patch("services.billing.stripe.Subscription.modify")
1723+
@patch("services.billing.stripe.PaymentMethod.retrieve")
1724+
def test_check_and_handle_delayed_notification_payment_methods_multiple_subscriptions(
1725+
self,
1726+
payment_method_retrieve_mock,
1727+
subscription_modify_mock,
1728+
customer_modify_mock,
1729+
payment_method_attach_mock,
1730+
):
1731+
class MockPaymentMethod:
1732+
type = "us_bank_account"
1733+
us_bank_account = {}
1734+
id = "pm_123"
1735+
1736+
payment_method_retrieve_mock.return_value = MockPaymentMethod()
1737+
1738+
self.owner.stripe_subscription_id = "sub_123"
1739+
self.owner.stripe_customer_id = "cus_123"
1740+
self.owner.save()
1741+
1742+
OwnerFactory(stripe_subscription_id="sub_124", stripe_customer_id="cus_123")
1743+
1744+
handler = StripeWebhookHandler()
1745+
handler._check_and_handle_delayed_notification_payment_methods(
1746+
"cus_123", "pm_123"
1747+
)
1748+
1749+
payment_method_retrieve_mock.assert_called_once_with("pm_123")
1750+
payment_method_attach_mock.assert_called_once_with(
1751+
payment_method_retrieve_mock.return_value, customer="cus_123"
1752+
)
1753+
customer_modify_mock.assert_called_once_with(
1754+
"cus_123",
1755+
invoice_settings={
1756+
"default_payment_method": payment_method_retrieve_mock.return_value
1757+
},
1758+
)
1759+
subscription_modify_mock.assert_has_calls(
1760+
[
1761+
call(
1762+
"sub_123",
1763+
default_payment_method=payment_method_retrieve_mock.return_value,
1764+
),
1765+
call(
1766+
"sub_124",
1767+
default_payment_method=payment_method_retrieve_mock.return_value,
1768+
),
1769+
]
1770+
)
1771+
16461772
@patch(
16471773
"billing.views.StripeWebhookHandler._check_and_handle_delayed_notification_payment_methods"
16481774
)

billing/views.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,6 @@ def _check_and_handle_delayed_notification_payment_methods(
539539
When verification succeeds, this attaches the payment method to the customer and sets
540540
it as the default payment method for both the customer and subscription.
541541
"""
542-
owner = Owner.objects.get(stripe_customer_id=customer_id)
543542
payment_method = stripe.PaymentMethod.retrieve(payment_method_id)
544543

545544
is_us_bank_account = payment_method.type == "us_bank_account" and hasattr(
@@ -548,19 +547,34 @@ def _check_and_handle_delayed_notification_payment_methods(
548547

549548
should_set_as_default = is_us_bank_account
550549

550+
# attach the payment method + set as default on the invoice and subscription
551551
if should_set_as_default:
552-
# attach the payment method + set as default on the invoice and subscription
553-
stripe.PaymentMethod.attach(
554-
payment_method, customer=owner.stripe_customer_id
555-
)
556-
stripe.Customer.modify(
557-
owner.stripe_customer_id,
558-
invoice_settings={"default_payment_method": payment_method},
559-
)
560-
stripe.Subscription.modify(
561-
owner.stripe_subscription_id, default_payment_method=payment_method
552+
# retrieve the number of owners to update
553+
owners = Owner.objects.filter(
554+
stripe_customer_id=customer_id, stripe_subscription_id__isnull=False
562555
)
563556

557+
if owners.exists():
558+
# Even if multiple results are returned, these two stripe calls are
559+
# just for a single customer
560+
stripe.PaymentMethod.attach(payment_method, customer=customer_id)
561+
stripe.Customer.modify(
562+
customer_id,
563+
invoice_settings={"default_payment_method": payment_method},
564+
)
565+
566+
# But this one is for each subscription an owner may have
567+
for owner in owners:
568+
stripe.Subscription.modify(
569+
owner.stripe_subscription_id,
570+
default_payment_method=payment_method,
571+
)
572+
else:
573+
log.error(
574+
"No owners found with that customer_id, something went wrong",
575+
extra=dict(customer_id=customer_id),
576+
)
577+
564578
def payment_intent_succeeded(self, payment_intent: stripe.PaymentIntent) -> None:
565579
"""
566580
Stripe payment_intent.succeeded webhook event is emitted when a

graphql_api/types/plan/plan.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def resolve_trial_end_date(plan_service: PlanService, info) -> Optional[datetime
3030

3131
@plan_bindable.field("trialStatus")
3232
def resolve_trial_status(plan_service: PlanService, info) -> TrialStatus:
33-
if plan_service.trial_status is None:
33+
if not plan_service.trial_status:
3434
return TrialStatus.NOT_STARTED
3535
return TrialStatus(plan_service.trial_status)
3636

0 commit comments

Comments
 (0)