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

Commit a89fb3e

Browse files
fix tests
1 parent d4bab7e commit a89fb3e

File tree

9 files changed

+147
-53
lines changed

9 files changed

+147
-53
lines changed

graphql_api/tests/test_billing.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from unittest.mock import patch
2+
3+
from django.test import TransactionTestCase
4+
from shared.django_apps.codecov_auth.tests.factories import OwnerFactory
5+
from stripe.api_resources import PaymentIntent, SetupIntent
6+
7+
from .helper import GraphQLTestHelper
8+
9+
10+
class BillingTestCase(GraphQLTestHelper, TransactionTestCase):
11+
def setUp(self):
12+
self.owner = OwnerFactory(stripe_customer_id="test-customer-id")
13+
14+
def test_fetch_unverified_payment_methods(self):
15+
query = """
16+
query {
17+
owner(username: "%s") {
18+
billing {
19+
unverifiedPaymentMethods {
20+
paymentMethodId
21+
hostedVerificationUrl
22+
}
23+
}
24+
}
25+
}
26+
""" % (self.owner.username)
27+
28+
payment_intent = PaymentIntent.construct_from(
29+
{
30+
"payment_method": "pm_123",
31+
"next_action": {
32+
"type": "verify_with_microdeposits",
33+
"verify_with_microdeposits": {
34+
"hosted_verification_url": "https://verify.stripe.com/1"
35+
},
36+
},
37+
},
38+
"fake_api_key",
39+
)
40+
41+
setup_intent = SetupIntent.construct_from(
42+
{
43+
"payment_method": "pm_456",
44+
"next_action": {
45+
"type": "verify_with_microdeposits",
46+
"verify_with_microdeposits": {
47+
"hosted_verification_url": "https://verify.stripe.com/2"
48+
},
49+
},
50+
},
51+
"fake_api_key",
52+
)
53+
54+
with (
55+
patch(
56+
"services.billing.stripe.PaymentIntent.list"
57+
) as payment_intent_list_mock,
58+
patch("services.billing.stripe.SetupIntent.list") as setup_intent_list_mock,
59+
):
60+
payment_intent_list_mock.return_value.data = [payment_intent]
61+
setup_intent_list_mock.return_value.data = [setup_intent]
62+
63+
result = self.gql_request(query, owner=self.owner)
64+
65+
assert "errors" not in result
66+
data = result["owner"]["billing"]["unverifiedPaymentMethods"]
67+
assert len(data) == 2
68+
assert data[0]["paymentMethodId"] == "pm_123"
69+
assert data[0]["hostedVerificationUrl"] == "https://verify.stripe.com/1"
70+
assert data[1]["paymentMethodId"] == "pm_456"
71+
assert data[1]["hostedVerificationUrl"] == "https://verify.stripe.com/2"

graphql_api/types/__init__.py

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

44
from ..helpers.ariadne import ariadne_load_local_graphql
55
from .account import account, account_bindable
6-
from .billing import billing_bindable
6+
from .billing import billing, billing_bindable
77
from .branch import branch, branch_bindable
88
from .bundle_analysis import (
99
bundle_analysis,
@@ -30,10 +30,7 @@
3030
from .component import component, component_bindable
3131
from .component_comparison import component_comparison, component_comparison_bindable
3232
from .config import config, config_bindable
33-
from .coverage_analytics import (
34-
coverage_analytics,
35-
coverage_analytics_bindable,
36-
)
33+
from .coverage_analytics import coverage_analytics, coverage_analytics_bindable
3734
from .coverage_totals import coverage_totals, coverage_totals_bindable
3835
from .enums import enum_types
3936
from .file import commit_file, file_bindable

graphql_api/types/billing/billing.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
type Billing {
2-
unVerifiedPaymentMethods: [UnverifiedPaymentMethod!]!
2+
unverifiedPaymentMethods: [UnverifiedPaymentMethod]
33
}
44

55
type UnverifiedPaymentMethod {

graphql_api/types/billing/billing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111
def resolve_unverified_payment_methods(
1212
owner: Owner, info: GraphQLResolveInfo
1313
) -> list[dict]:
14-
return BillingService(requesting_user=owner).get_unverified_payment_methods()
14+
return BillingService(requesting_user=owner).get_unverified_payment_methods(owner)

graphql_api/types/owner/owner.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ type Owner {
22
account: Account
33
availablePlans: [PlanRepresentation!]
44
avatarUrl: String!
5+
billing: Billing
56
defaultOrgUsername: String
67
delinquent: Boolean
78
hashOwnerid: String

graphql_api/types/owner/owner.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
)
2626
from codecov_auth.views.okta_cloud import OKTA_SIGNED_IN_ACCOUNTS_SESSION_KEY
2727
from core.models import Repository
28-
from graphql_api.actions.repository import (
29-
list_repository_for_owner,
30-
)
28+
from graphql_api.actions.repository import list_repository_for_owner
3129
from graphql_api.helpers.ariadne import ariadne_load_local_graphql
3230
from graphql_api.helpers.connection import (
3331
Connection,
@@ -398,8 +396,8 @@ def resolve_activated_user_count(owner: Owner, info: GraphQLResolveInfo) -> int:
398396
return owner.activated_user_count
399397

400398

401-
402399
@owner_bindable.field("billing")
403400
@sync_to_async
404401
@require_part_of_org
405402
def resolve_billing(owner: Owner, info: GraphQLResolveInfo) -> dict | None:
403+
return owner

services/billing.py

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -545,32 +545,6 @@ def create_checkout_session(self, owner: Owner, desired_plan):
545545
)
546546
return session["id"]
547547

548-
def _is_unverified_payment_method(self, payment_method_id: str) -> bool:
549-
payment_method = stripe.PaymentMethod.retrieve(payment_method_id)
550-
551-
is_us_bank_account = payment_method.type == "us_bank_account" and hasattr(
552-
payment_method, "us_bank_account"
553-
)
554-
if is_us_bank_account:
555-
setup_intents = stripe.SetupIntent.list(
556-
payment_method=payment_method_id, limit=1
557-
)
558-
if (
559-
setup_intents
560-
and hasattr(setup_intents, "data")
561-
and isinstance(setup_intents.data, list)
562-
and len(setup_intents.data) > 0
563-
):
564-
latest_intent = setup_intents.data[0]
565-
if (
566-
latest_intent.status == "requires_action"
567-
and latest_intent.next_action
568-
and latest_intent.next_action.type == "verify_with_microdeposits"
569-
):
570-
return True
571-
572-
return False
573-
574548
@_log_stripe_error
575549
def update_payment_method(self, owner: Owner, payment_method):
576550
log.info(
@@ -746,7 +720,8 @@ def create_setup_intent(self, owner: Owner) -> stripe.SetupIntent:
746720
customer=owner.stripe_customer_id,
747721
)
748722

749-
def _get_unverified_payment_methods(self, owner):
723+
@_log_stripe_error
724+
def get_unverified_payment_methods(self, owner):
750725
log.info(
751726
"Getting unverified payment methods",
752727
extra=dict(
@@ -768,11 +743,13 @@ def _get_unverified_payment_methods(self, owner):
768743
and intent.next_action
769744
and intent.next_action.get("type") == "verify_with_microdeposits"
770745
):
771-
unverified_payment_methods.append(
772-
{
773-
"payment_method_id": intent.payment_method,
774-
"hosted_verification_link": intent.next_action.verify_with_microdeposits.hosted_verification_url,
775-
}
746+
unverified_payment_methods.extend(
747+
[
748+
{
749+
"payment_method_id": intent.payment_method,
750+
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
751+
}
752+
]
776753
)
777754

778755
# Check setup intents
@@ -785,11 +762,13 @@ def _get_unverified_payment_methods(self, owner):
785762
and intent.next_action
786763
and intent.next_action.get("type") == "verify_with_microdeposits"
787764
):
788-
unverified_payment_methods.append(
789-
{
790-
"payment_method_id": intent.payment_method,
791-
"hosted_verification_link": intent.next_action.verify_with_microdeposits.hosted_verification_url,
792-
}
765+
unverified_payment_methods.extend(
766+
[
767+
{
768+
"payment_method_id": intent.payment_method,
769+
"hosted_verification_url": intent.next_action.verify_with_microdeposits.hosted_verification_url,
770+
}
771+
]
793772
)
794773

795774
return unverified_payment_methods
@@ -868,7 +847,7 @@ def list_filtered_invoices(self, owner, limit=10):
868847
return self.payment_service.list_filtered_invoices(owner, limit)
869848

870849
def get_unverified_payment_methods(self, owner):
871-
return self.payment_service._get_unverified_payment_methods(owner)
850+
return self.payment_service.get_unverified_payment_methods(owner)
872851

873852
def update_plan(self, owner, desired_plan):
874853
"""

services/tests/test_billing.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from shared.django_apps.core.tests.factories import OwnerFactory
99
from shared.plan.constants import PlanName
1010
from stripe import InvalidRequestError
11+
from stripe.api_resources import PaymentIntent, SetupIntent
1112

1213
from codecov_auth.models import Service
1314
from services.billing import AbstractPaymentService, BillingService, StripeService
@@ -1832,6 +1833,53 @@ def test_create_setup_intent(self, setup_intent_create_mock):
18321833
resp = self.stripe.create_setup_intent(owner)
18331834
assert resp["client_secret"] == "test-client-secret"
18341835

1836+
@patch("services.billing.stripe.PaymentIntent.list")
1837+
@patch("services.billing.stripe.SetupIntent.list")
1838+
def test_get_unverified_payment_methods(
1839+
self, setup_intent_list_mock, payment_intent_list_mock
1840+
):
1841+
owner = OwnerFactory(stripe_customer_id="test-customer-id")
1842+
payment_intent = PaymentIntent.construct_from(
1843+
{
1844+
"payment_method": "pm_123",
1845+
"next_action": {
1846+
"type": "verify_with_microdeposits",
1847+
"verify_with_microdeposits": {
1848+
"hosted_verification_url": "https://verify.stripe.com/1"
1849+
},
1850+
},
1851+
},
1852+
"fake_api_key",
1853+
)
1854+
1855+
setup_intent = SetupIntent.construct_from(
1856+
{
1857+
"payment_method": "pm_456",
1858+
"next_action": {
1859+
"type": "verify_with_microdeposits",
1860+
"verify_with_microdeposits": {
1861+
"hosted_verification_url": "https://verify.stripe.com/2"
1862+
},
1863+
},
1864+
},
1865+
"fake_api_key",
1866+
)
1867+
1868+
payment_intent_list_mock.return_value.data = [payment_intent]
1869+
setup_intent_list_mock.return_value.data = [setup_intent]
1870+
1871+
expected = [
1872+
{
1873+
"payment_method_id": "pm_123",
1874+
"hosted_verification_url": "https://verify.stripe.com/1",
1875+
},
1876+
{
1877+
"payment_method_id": "pm_456",
1878+
"hosted_verification_url": "https://verify.stripe.com/2",
1879+
},
1880+
]
1881+
assert self.stripe.get_unverified_payment_methods(owner) == expected
1882+
18351883

18361884
class MockPaymentService(AbstractPaymentService):
18371885
def list_filtered_invoices(self, owner, limit=10):

utils/test_utils.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ def app(self) -> str:
4545
migrate_to = None
4646

4747
def setUp(self) -> None:
48-
assert self.migrate_from and self.migrate_to, (
49-
"TestCase '{}' must define migrate_from and migrate_to properties".format(
50-
type(self).__name__
51-
)
48+
assert (
49+
self.migrate_from and self.migrate_to
50+
), "TestCase '{}' must define migrate_from and migrate_to properties".format(
51+
type(self).__name__
5252
)
5353
self.migrate_from = [(self.app, self.migrate_from)]
5454
self.migrate_to = [(self.app, self.migrate_to)]

0 commit comments

Comments
 (0)