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

Commit a6b4f36

Browse files
Merge remote-tracking branch 'origin/main' into sshin/fix/1859
2 parents b78a3a7 + e8a5caf commit a6b4f36

File tree

87 files changed

+1830
-970
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+1830
-970
lines changed

Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ check-for-migration-conflicts:
3131
python manage.py check_for_migration_conflicts
3232

3333
test:
34-
COVERAGE_CORE=sysmon python -m pytest --cov=./ --junitxml=junit.xml
34+
COVERAGE_CORE=sysmon python -m pytest --cov=./ --junitxml=junit.xml -o junit_family=legacy
3535

3636
test.unit:
37-
COVERAGE_CORE=sysmon python -m pytest --cov=./ -m "not integration" --cov-report=xml:unit.coverage.xml --junitxml=unit.junit.xml
37+
COVERAGE_CORE=sysmon python -m pytest --cov=./ -m "not integration" --cov-report=xml:unit.coverage.xml --junitxml=unit.junit.xml -o junit_family=legacy
3838

3939
test.integration:
40-
COVERAGE_CORE=sysmon python -m pytest --cov=./ -m "integration" --cov-report=xml:integration.coverage.xml --junitxml=integration.junit.xml
40+
COVERAGE_CORE=sysmon python -m pytest --cov=./ -m "integration" --cov-report=xml:integration.coverage.xml --junitxml=integration.junit.xml -o junit_family=legacy
4141

4242
lint:
4343
make lint.install
@@ -178,6 +178,9 @@ push.self-hosted-rolling:
178178
docker push ${DOCKERHUB_REPO}:rolling_no_dependencies
179179
docker push ${DOCKERHUB_REPO}:rolling
180180

181+
shell:
182+
docker-compose exec api bash
183+
181184
test_env.up:
182185
env | grep GITHUB > .testenv; true
183186
TIMESERIES_ENABLED=${TIMESERIES_ENABLED} docker-compose up -d

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
24.10.1
1+
24.11.1

api/public/v2/tests/test_test_results_view.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ def test_list(self):
5353
"outcome": self.test_instances[0].outcome,
5454
"branch": self.test_instances[0].branch,
5555
"repoid": self.test_instances[0].repoid,
56-
"failure_rate": self.test_instances[0].test.failure_rate,
57-
"commits_where_fail": self.test_instances[
58-
0
59-
].test.commits_where_fail,
6056
},
6157
{
6258
"id": self.test_instances[1].id,
@@ -68,10 +64,6 @@ def test_list(self):
6864
"outcome": self.test_instances[1].outcome,
6965
"branch": self.test_instances[1].branch,
7066
"repoid": self.test_instances[1].repoid,
71-
"failure_rate": self.test_instances[1].test.failure_rate,
72-
"commits_where_fail": self.test_instances[
73-
1
74-
].test.commits_where_fail,
7567
},
7668
],
7769
"total_pages": 1,
@@ -103,10 +95,6 @@ def test_list_filters(self):
10395
"outcome": self.test_instances[0].outcome,
10496
"branch": self.test_instances[0].branch,
10597
"repoid": self.test_instances[0].repoid,
106-
"failure_rate": self.test_instances[0].test.failure_rate,
107-
"commits_where_fail": self.test_instances[
108-
0
109-
].test.commits_where_fail,
11098
},
11199
],
112100
"total_pages": 1,
@@ -137,8 +125,6 @@ def test_retrieve(self, get_repo_permissions):
137125
"outcome": self.test_instances[0].outcome,
138126
"branch": self.test_instances[0].branch,
139127
"repoid": self.test_instances[0].repoid,
140-
"failure_rate": self.test_instances[0].test.failure_rate,
141-
"commits_where_fail": self.test_instances[0].test.commits_where_fail,
142128
}
143129

144130
@patch("api.shared.permissions.RepositoryArtifactPermissions.has_permission")
@@ -240,6 +226,4 @@ def test_result_with_valid_super_token(
240226
"outcome": self.test_instances[0].outcome,
241227
"branch": self.test_instances[0].branch,
242228
"repoid": self.test_instances[0].repoid,
243-
"failure_rate": self.test_instances[0].test.failure_rate,
244-
"commits_where_fail": self.test_instances[0].test.commits_where_fail,
245229
}

api/shared/repo/repository_accessors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22

3+
import sentry_sdk
34
from asgiref.sync import async_to_sync
45
from django.core.exceptions import ObjectDoesNotExist
56
from django.utils import timezone
@@ -30,6 +31,7 @@ def get_repo_permissions(self, user, repo):
3031
RepoProviderService().get_adapter(owner=user, repo=repo).get_authenticated
3132
)()
3233

34+
@sentry_sdk.trace
3335
def get_repo_details(
3436
self, user, repo_name, repo_owner_username, repo_owner_service
3537
):

billing/tests/test_views.py

Lines changed: 174 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import time
2-
from unittest.mock import patch
2+
from datetime import datetime
3+
from unittest.mock import call, patch
34

45
import stripe
56
from django.conf import settings
@@ -142,6 +143,11 @@ def test_invoice_payment_failed_sets_owner_delinquent_true(self):
142143
"object": {
143144
"customer": self.owner.stripe_customer_id,
144145
"subscription": self.owner.stripe_subscription_id,
146+
"default_payment_method": {
147+
"card": {"brand": "visa", "last4": 1234}
148+
},
149+
"total": 24000,
150+
"hosted_invoice_url": "https://stripe.com",
145151
}
146152
},
147153
}
@@ -165,6 +171,11 @@ def test_invoice_payment_failed_sets_multiple_owners_delinquent_true(self):
165171
"object": {
166172
"customer": self.owner.stripe_customer_id,
167173
"subscription": self.owner.stripe_subscription_id,
174+
"default_payment_method": {
175+
"card": {"brand": "visa", "last4": 1234}
176+
},
177+
"total": 24000,
178+
"hosted_invoice_url": "https://stripe.com",
168179
}
169180
},
170181
}
@@ -176,6 +187,142 @@ def test_invoice_payment_failed_sets_multiple_owners_delinquent_true(self):
176187
assert self.owner.delinquent is True
177188
assert self.other_owner.delinquent is True
178189

190+
@patch("services.task.TaskService.send_email")
191+
def test_invoice_payment_failed_sends_email_to_admins(self, mocked_send_email):
192+
non_admin = OwnerFactory(email="[email protected]")
193+
admin_1 = OwnerFactory(email="[email protected]")
194+
admin_2 = OwnerFactory(email="[email protected]")
195+
self.owner.admins = [admin_1.ownerid, admin_2.ownerid]
196+
self.owner.plan_activated_users = [non_admin.ownerid]
197+
self.owner.email = "[email protected]"
198+
self.owner.save()
199+
200+
response = self._send_event(
201+
payload={
202+
"type": "invoice.payment_failed",
203+
"data": {
204+
"object": {
205+
"customer": self.owner.stripe_customer_id,
206+
"subscription": self.owner.stripe_subscription_id,
207+
"default_payment_method": {
208+
"card": {"brand": "visa", "last4": 1234}
209+
},
210+
"total": 24000,
211+
"hosted_invoice_url": "https://stripe.com",
212+
}
213+
},
214+
}
215+
)
216+
217+
self.owner.refresh_from_db()
218+
assert response.status_code == status.HTTP_204_NO_CONTENT
219+
assert self.owner.delinquent is True
220+
221+
expected_calls = [
222+
call(
223+
to_addr=self.owner.email,
224+
subject="Your Codecov payment failed",
225+
template_name="failed-payment",
226+
name=self.owner.username,
227+
amount=240,
228+
card_type="visa",
229+
last_four=1234,
230+
cta_link="https://stripe.com",
231+
date=datetime.now().strftime("%B %-d, %Y"),
232+
),
233+
call(
234+
to_addr=admin_1.email,
235+
subject="Your Codecov payment failed",
236+
template_name="failed-payment",
237+
name=admin_1.username,
238+
amount=240,
239+
card_type="visa",
240+
last_four=1234,
241+
cta_link="https://stripe.com",
242+
date=datetime.now().strftime("%B %-d, %Y"),
243+
),
244+
call(
245+
to_addr=admin_2.email,
246+
subject="Your Codecov payment failed",
247+
template_name="failed-payment",
248+
name=admin_2.username,
249+
amount=240,
250+
card_type="visa",
251+
last_four=1234,
252+
cta_link="https://stripe.com",
253+
date=datetime.now().strftime("%B %-d, %Y"),
254+
),
255+
]
256+
mocked_send_email.assert_has_calls(expected_calls)
257+
258+
@patch("services.task.TaskService.send_email")
259+
def test_invoice_payment_failed_sends_email_to_admins_no_card(
260+
self, mocked_send_email
261+
):
262+
non_admin = OwnerFactory(email="[email protected]")
263+
admin_1 = OwnerFactory(email="[email protected]")
264+
admin_2 = OwnerFactory(email="[email protected]")
265+
self.owner.admins = [admin_1.ownerid, admin_2.ownerid]
266+
self.owner.plan_activated_users = [non_admin.ownerid]
267+
self.owner.email = "[email protected]"
268+
self.owner.save()
269+
270+
response = self._send_event(
271+
payload={
272+
"type": "invoice.payment_failed",
273+
"data": {
274+
"object": {
275+
"customer": self.owner.stripe_customer_id,
276+
"subscription": self.owner.stripe_subscription_id,
277+
"default_payment_method": None,
278+
"total": 24000,
279+
"hosted_invoice_url": "https://stripe.com",
280+
}
281+
},
282+
}
283+
)
284+
285+
self.owner.refresh_from_db()
286+
assert response.status_code == status.HTTP_204_NO_CONTENT
287+
assert self.owner.delinquent is True
288+
289+
expected_calls = [
290+
call(
291+
to_addr=self.owner.email,
292+
subject="Your Codecov payment failed",
293+
template_name="failed-payment",
294+
name=self.owner.username,
295+
amount=240,
296+
card_type=None,
297+
last_four=None,
298+
cta_link="https://stripe.com",
299+
date=datetime.now().strftime("%B %-d, %Y"),
300+
),
301+
call(
302+
to_addr=admin_1.email,
303+
subject="Your Codecov payment failed",
304+
template_name="failed-payment",
305+
name=admin_1.username,
306+
amount=240,
307+
card_type=None,
308+
last_four=None,
309+
cta_link="https://stripe.com",
310+
date=datetime.now().strftime("%B %-d, %Y"),
311+
),
312+
call(
313+
to_addr=admin_2.email,
314+
subject="Your Codecov payment failed",
315+
template_name="failed-payment",
316+
name=admin_2.username,
317+
amount=240,
318+
card_type=None,
319+
last_four=None,
320+
cta_link="https://stripe.com",
321+
date=datetime.now().strftime("%B %-d, %Y"),
322+
),
323+
]
324+
mocked_send_email.assert_has_calls(expected_calls)
325+
179326
def test_customer_subscription_deleted_sets_plan_to_free(self):
180327
self.owner.plan = "users-inappy"
181328
self.owner.plan_user_count = 20
@@ -1003,29 +1150,51 @@ def test_subscription_schedule_updated_logs_changes_to_schedule(
10031150
assert self.owner.plan == original_plan
10041151
assert self.owner.plan_user_count == original_quantity
10051152

1006-
def test_checkout_session_completed_sets_stripe_customer_id(self):
1153+
def test_checkout_session_completed_sets_stripe_ids(self):
10071154
self.owner.stripe_customer_id = None
10081155
self.owner.save()
10091156

1010-
expected_id = "fhjtwoo40"
1157+
expected_customer_id = "cus_1234"
1158+
expected_subscription_id = "sub_7890"
10111159

10121160
self._send_event(
10131161
payload={
10141162
"type": "checkout.session.completed",
10151163
"data": {
10161164
"object": {
1017-
"customer": expected_id,
1165+
"customer": expected_customer_id,
10181166
"client_reference_id": str(self.owner.ownerid),
1167+
"subscription": expected_subscription_id,
10191168
}
10201169
},
10211170
}
10221171
)
10231172

10241173
self.owner.refresh_from_db()
1025-
assert self.owner.stripe_customer_id == expected_id
1174+
assert self.owner.stripe_customer_id == expected_customer_id
1175+
assert self.owner.stripe_subscription_id == expected_subscription_id
10261176

10271177
@patch("billing.views.stripe.Subscription.modify")
10281178
def test_customer_update_but_not_payment_method(self, subscription_modify_mock):
1179+
payment_method = "pm_123"
1180+
self._send_event(
1181+
payload={
1182+
"type": "customer.updated",
1183+
"data": {
1184+
"object": {
1185+
"invoice_settings": {"default_payment_method": None},
1186+
"subscriptions": {
1187+
"data": [{"default_payment_method": payment_method}]
1188+
},
1189+
}
1190+
},
1191+
}
1192+
)
1193+
1194+
subscription_modify_mock.assert_not_called()
1195+
1196+
@patch("billing.views.stripe.Subscription.modify")
1197+
def test_customer_update_but_payment_method_is_same(self, subscription_modify_mock):
10291198
payment_method = "pm_123"
10301199
self._send_event(
10311200
payload={

0 commit comments

Comments
 (0)