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

Commit e2c35cd

Browse files
authored
Merge branch 'main' into export-plans-and-tiers-to-csv
2 parents a131749 + 8ed03b4 commit e2c35cd

File tree

154 files changed

+4774
-3551
lines changed

Some content is hidden

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

154 files changed

+4774
-3551
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,13 @@ jobs:
3636
uses: codecov/gha-workflows/.github/workflows/[email protected]
3737
secrets: inherit
3838

39-
# ats:
40-
# name: ATS
41-
# needs: [build]
42-
# if: ${{ !github.event.pull_request.head.repo.fork && github.repository_owner == 'codecov' }}
43-
# uses: codecov/gha-workflows/.github/workflows/[email protected]
44-
# secrets: inherit
45-
# with:
46-
# repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
47-
# codecov_cli_upload_args: '--plugin pycoverage --plugin compress-pycoverage --flag smart-labels'
48-
# app_container_name: api
4939
test:
5040
name: Test
5141
needs: [build]
52-
uses: codecov/gha-workflows/.github/workflows/[email protected].27
42+
uses: codecov/gha-workflows/.github/workflows/run-tests-split[email protected].30
5343
secrets: inherit
5444
with:
45+
run_integration: false
5546
repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
5647

5748
build-self-hosted:

.test_durations

Lines changed: 2755 additions & 0 deletions
Large diffs are not rendered by default.

Makefile

Lines changed: 21 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,18 @@ test:
3434
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 -o junit_family=legacy
37+
@if [ -n "$(GROUP)" ]; then \
38+
COVERAGE_CORE=sysmon python -m pytest --splits ${SPLIT} --group $(GROUP) --cov=./ -m "not integration" --cov-report=xml:unit.$(GROUP).coverage.xml --junitxml=unit.$(GROUP).junit.xml -o junit_family=legacy; \
39+
else \
40+
COVERAGE_CORE=sysmon python -m pytest --cov=./ -m "not integration" --cov-report=xml:unit.coverage.xml --junitxml=unit.junit.xml -o junit_family=legacy; \
41+
fi
3842

3943
test.integration:
40-
COVERAGE_CORE=sysmon python -m pytest --cov=./ -m "integration" --cov-report=xml:integration.coverage.xml --junitxml=integration.junit.xml -o junit_family=legacy
44+
@if [ -n "$(GROUP)" ]; then \
45+
COVERAGE_CORE=sysmon python -m pytest --splits ${SPLIT} --group $(GROUP) --cov=./ -m "integration" --cov-report=xml:integration.$(GROUP).coverage.xml --junitxml=integration.$(GROUP).junit.xml -o junit_family=legacy; \
46+
else \
47+
COVERAGE_CORE=sysmon python -m pytest --cov=./ -m "integration" --cov-report=xml:integration.coverage.xml --junitxml=integration.junit.xml -o junit_family=legacy; \
48+
fi
4149

4250
lint:
4351
make lint.install
@@ -197,55 +205,30 @@ test_env.install_cli:
197205

198206
test_env.container_prepare:
199207
apt-get -y install git build-essential netcat-traditional
200-
make test_env.install_cli
201-
git config --global --add safe.directory /app
208+
git config --global --add safe.directory /app || true
202209

203210
test_env.container_check_db:
204211
while ! nc -vz postgres 5432; do sleep 1; echo "waiting for postgres"; done
205212
while ! nc -vz timescale 5432; do sleep 1; echo "waiting for timescale"; done
206213

207214
test_env.run_unit:
208-
docker-compose exec api make test.unit
215+
@if [ -n "$(GROUP)" ]; then \
216+
docker-compose exec api make test.unit SPLIT=${SPLIT} GROUP=${GROUP}; \
217+
else \
218+
docker-compose exec api make test.unit; \
219+
fi
209220

210221
test_env.run_integration:
211-
#docker-compose exec api make test.integration
222+
# @if [ -n "$(GROUP)" ]; then \
223+
# docker-compose exec api make test.integration SPLIT=${SPLIT} GROUP=${GROUP}; \
224+
# else \
225+
# docker-compose exec api make test.integration; \
226+
# fi
212227
echo "Skipping. No Tests"
213228

214229
test_env.check-for-migration-conflicts:
215230
docker-compose exec api python manage.py check_for_migration_conflicts
216231

217-
test_env.upload:
218-
docker-compose exec api make test_env.container_upload CODECOV_UPLOAD_TOKEN=${CODECOV_UPLOAD_TOKEN} CODECOV_URL=${CODECOV_URL}
219-
docker-compose exec api make test_env.container_upload_test_results CODECOV_UPLOAD_TOKEN=${CODECOV_UPLOAD_TOKEN} CODECOV_URL=${CODECOV_URL}
220-
221-
test_env.container_upload:
222-
codecovcli -u ${CODECOV_URL} upload-process --flag unit-latest-uploader --flag unit \
223-
--coverage-files-search-exclude-folder=graphql_api/types/** \
224-
--coverage-files-search-exclude-folder=api/internal/tests/unit/views/cassetes/**
225-
226-
test_env.container_upload_test_results:
227-
codecovcli -u ${CODECOV_URL} do-upload --report-type "test_results" \
228-
--files-search-exclude-folder=graphql_api/types/** \
229-
--files-search-exclude-folder=api/internal/tests/unit/views/cassetes/** || true
230-
231-
test_env.static_analysis:
232-
docker-compose exec api make test_env.container_static_analysis CODECOV_STATIC_TOKEN=${CODECOV_STATIC_TOKEN}
233-
234-
test_env.label_analysis:
235-
docker-compose exec api make test_env.container_label_analysis CODECOV_STATIC_TOKEN=${CODECOV_STATIC_TOKEN}
236-
237-
test_env.ats:
238-
docker-compose exec api make test_env.container_ats CODECOV_UPLOAD_TOKEN=${CODECOV_UPLOAD_TOKEN}
239-
240-
test_env.container_static_analysis:
241-
codecovcli -u ${CODECOV_URL} static-analysis --token=${CODECOV_STATIC_TOKEN}
242-
243-
test_env.container_label_analysis:
244-
codecovcli -u ${CODECOV_URL} label-analysis --base-sha=${merge_sha} --token=${CODECOV_STATIC_TOKEN}
245-
246-
test_env.container_ats:
247-
codecovcli -u ${CODECOV_URL} --codecov-yml-path=codecov_cli.yml upload-process --plugin pycoverage --plugin compress-pycoverage --flag smart-labels --fail-on-error
248-
249232
test_env:
250233
make test_env.up
251234
make test_env.prepare

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
25.2.3
1+
25.2.7

api/internal/owner/serializers.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from typing import Any, Dict
44

55
from dateutil.relativedelta import relativedelta
6-
from django.conf import settings
76
from rest_framework import serializers
87
from rest_framework.exceptions import PermissionDenied
98
from shared.plan.constants import (
@@ -132,7 +131,7 @@ def validate_value(self, value: str) -> str:
132131

133132
plan_service = PlanService(current_org=current_org)
134133
plan_values = [
135-
plan["value"] for plan in plan_service.available_plans(current_owner)
134+
plan.name for plan in plan_service.available_plans(current_owner)
136135
]
137136
if value not in plan_values:
138137
raise serializers.ValidationError(
@@ -217,11 +216,7 @@ class StripeScheduledPhaseSerializer(serializers.Serializer):
217216

218217
def get_plan(self, phase: Dict[str, Any]) -> str:
219218
plan_id = phase["items"][0]["plan"]
220-
stripe_plan_dict = settings.STRIPE_PLAN_IDS
221-
plan_name = list(stripe_plan_dict.keys())[
222-
list(stripe_plan_dict.values()).index(plan_id)
223-
]
224-
marketing_plan_name = Plan.objects.get(name=plan_name).marketing_name
219+
marketing_plan_name = Plan.objects.get(stripe_id=plan_id).marketing_name
225220
return marketing_plan_name
226221

227222
def get_quantity(self, phase: Dict[str, Any]) -> int:
@@ -344,11 +339,13 @@ def update(self, instance: Owner, validated_data: Dict[str, Any]) -> object:
344339
instance, desired_plan
345340
)
346341

347-
sentry_plans = Plan.objects.filter(
348-
tier__tier_name=TierName.SENTRY.value, is_active=True
349-
).values_list("name", flat=True)
342+
plan = (
343+
Plan.objects.select_related("tier")
344+
.filter(name=desired_plan["value"])
345+
.first()
346+
)
350347

351-
if desired_plan["value"] in sentry_plans:
348+
if plan and plan.tier.tier_name == TierName.SENTRY.value:
352349
current_owner = self.context["view"].request.current_owner
353350
send_sentry_webhook(current_owner, instance)
354351

api/internal/owner/views.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from rest_framework.exceptions import PermissionDenied, ValidationError
88
from rest_framework.response import Response
99
from shared.django_apps.codecov_auth.models import Owner
10+
from shared.plan.constants import DEFAULT_FREE_PLAN
1011

1112
from api.shared.mixins import OwnerPropertyMixin
1213
from api.shared.owner.mixins import OwnerViewSetMixin, UserViewSetMixin
@@ -45,8 +46,13 @@ def retrieve(self, *args, **kwargs):
4546
return res
4647

4748
@stripe_safe
48-
def update(self, *args, **kwargs):
49-
return super().update(*args, **kwargs)
49+
def update(self, request, *args, **kwargs):
50+
# Temporary fix. Remove once Gazebo uses the new free plan
51+
plan_value = request.data.get("plan", {}).get("value")
52+
if plan_value == "users-basic":
53+
request.data["plan"]["value"] = DEFAULT_FREE_PLAN
54+
55+
return super().update(request, *args, **kwargs)
5056

5157
def destroy(self, request, *args, **kwargs):
5258
if self.owner.ownerid != request.current_owner.ownerid:

api/internal/tests/views/test_account_viewset.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
OwnerFactory,
1515
UserFactory,
1616
)
17-
from shared.plan.constants import PlanName, TrialStatus
17+
from shared.plan.constants import DEFAULT_FREE_PLAN, PlanName, TrialStatus
1818
from stripe import StripeError
1919

2020
from api.internal.tests.test_utils import GetAdminProviderAdapter
@@ -185,7 +185,7 @@ def test_retrieve_account_gets_account_fields(self):
185185
"inactive_user_count": 1,
186186
"plan": {
187187
"marketing_name": "Developer",
188-
"value": PlanName.BASIC_PLAN_NAME.value,
188+
"value": DEFAULT_FREE_PLAN,
189189
"billing_rate": None,
190190
"base_unit_price": 0,
191191
"benefits": [
@@ -234,7 +234,7 @@ def test_retrieve_account_gets_account_fields_when_there_are_scheduled_details(
234234
schedule_params = {
235235
"id": 123,
236236
"start_date": 123689126736,
237-
"stripe_plan_id": "plan_H6P3KZXwmAbqPS",
237+
"stripe_plan_id": "plan_pro_yearly",
238238
"quantity": 6,
239239
}
240240
phases = [
@@ -270,7 +270,7 @@ def test_retrieve_account_gets_account_fields_when_there_are_scheduled_details(
270270
"plan_provider": owner.plan_provider,
271271
"plan": {
272272
"marketing_name": "Developer",
273-
"value": PlanName.BASIC_PLAN_NAME.value,
273+
"value": DEFAULT_FREE_PLAN,
274274
"billing_rate": None,
275275
"base_unit_price": 0,
276276
"benefits": [
@@ -330,7 +330,7 @@ def test_retrieve_account_returns_last_phase_when_more_than_one_scheduled_phases
330330
schedule_params = {
331331
"id": 123,
332332
"start_date": 123689126736,
333-
"stripe_plan_id": "plan_H6P3KZXwmAbqPS",
333+
"stripe_plan_id": "plan_pro_yearly",
334334
"quantity": 6,
335335
}
336336
phases = [
@@ -367,7 +367,7 @@ def test_retrieve_account_returns_last_phase_when_more_than_one_scheduled_phases
367367
"inactive_user_count": 1,
368368
"plan": {
369369
"marketing_name": "Developer",
370-
"value": PlanName.BASIC_PLAN_NAME.value,
370+
"value": DEFAULT_FREE_PLAN,
371371
"billing_rate": None,
372372
"base_unit_price": 0,
373373
"benefits": [
@@ -441,7 +441,7 @@ def test_retrieve_account_gets_none_for_schedule_details_when_schedule_is_nonexi
441441
"inactive_user_count": 1,
442442
"plan": {
443443
"marketing_name": "Developer",
444-
"value": PlanName.BASIC_PLAN_NAME.value,
444+
"value": DEFAULT_FREE_PLAN,
445445
"billing_rate": None,
446446
"base_unit_price": 0,
447447
"benefits": [
@@ -509,13 +509,13 @@ def test_retrieve_account_gets_account_students(self):
509509
}
510510

511511
def test_account_with_free_user_plan(self):
512-
self.current_owner.plan = PlanName.BASIC_PLAN_NAME.value
512+
self.current_owner.plan = DEFAULT_FREE_PLAN
513513
self.current_owner.save()
514514
response = self._retrieve()
515515
assert response.status_code == status.HTTP_200_OK
516516
assert response.data["plan"] == {
517517
"marketing_name": "Developer",
518-
"value": PlanName.BASIC_PLAN_NAME.value,
518+
"value": DEFAULT_FREE_PLAN,
519519
"billing_rate": None,
520520
"base_unit_price": 0,
521521
"benefits": [
@@ -711,7 +711,7 @@ def test_update_can_set_plan_auto_activate_on_org_with_account(self):
711711
assert self.current_owner.plan_auto_activate is False
712712
assert response.data["plan_auto_activate"] is False
713713

714-
def test_update_can_set_plan_to_users_basic(self):
714+
def test_update_can_set_plan_to_users_developer_should_set_to_developer(self):
715715
self.current_owner.plan = PlanName.CODECOV_PRO_YEARLY.value
716716
self.current_owner.save()
717717

@@ -720,14 +720,14 @@ def test_update_can_set_plan_to_users_basic(self):
720720
"service": self.current_owner.service,
721721
"owner_username": self.current_owner.username,
722722
},
723-
data={"plan": {"value": PlanName.BASIC_PLAN_NAME.value}},
723+
data={"plan": {"value": DEFAULT_FREE_PLAN}},
724724
)
725725

726726
assert response.status_code == status.HTTP_200_OK
727727

728728
self.current_owner.refresh_from_db()
729729

730-
assert self.current_owner.plan == PlanName.BASIC_PLAN_NAME.value
730+
assert self.current_owner.plan == DEFAULT_FREE_PLAN
731731
assert self.current_owner.plan_activated_users is None
732732
assert self.current_owner.plan_user_count == 1
733733
assert response.data["plan_auto_activate"] is True
@@ -984,7 +984,7 @@ def test_update_must_fail_if_quantity_and_plan_are_equal_to_the_owners_current_o
984984
)
985985

986986
def test_update_team_plan_must_fail_if_too_many_activated_users_during_trial(self):
987-
self.current_owner.plan = PlanName.BASIC_PLAN_NAME.value
987+
self.current_owner.plan = DEFAULT_FREE_PLAN
988988
self.current_owner.plan_user_count = 1
989989
self.current_owner.trial_status = TrialStatus.ONGOING.value
990990
self.current_owner.plan_activated_users = list(range(11))
@@ -1005,13 +1005,7 @@ def test_update_team_plan_must_fail_if_too_many_activated_users_during_trial(sel
10051005
)
10061006

10071007
assert response.status_code == status.HTTP_400_BAD_REQUEST
1008-
assert response.json() == {
1009-
"plan": {
1010-
"value": [
1011-
f"Invalid value for plan: {desired_plan['value']}; must be one of ['users-basic', 'users-pr-inappm', 'users-pr-inappy']"
1012-
]
1013-
}
1014-
}
1008+
assert "Invalid value for plan:" in response.json()["plan"]["value"][0]
10151009

10161010
def test_update_team_plan_must_fail_if_currently_team_plan_add_too_many_users(self):
10171011
self.current_owner.plan = PlanName.TEAM_MONTHLY.value
@@ -1602,7 +1596,7 @@ def test_update_sentry_plan_non_sentry_user(
16021596
assert res.json() == {
16031597
"plan": {
16041598
"value": [
1605-
"Invalid value for plan: users-sentrym; must be one of ['users-basic', 'users-pr-inappm', 'users-pr-inappy', 'users-teamm', 'users-teamy']"
1599+
f"Invalid value for plan: users-sentrym; must be one of ['users-pr-inappm', 'users-pr-inappy', 'users-teamm', 'users-teamy', '{DEFAULT_FREE_PLAN}']"
16061600
]
16071601
}
16081602
}

api/internal/tests/views/test_user_viewset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
PullFactory,
1111
RepositoryFactory,
1212
)
13-
from shared.plan.constants import PlanName, TierName
13+
from shared.plan.constants import DEFAULT_FREE_PLAN, TierName
1414

1515
from core.models import Pull
1616
from utils.test_utils import APIClient
@@ -20,7 +20,7 @@ class UserViewSetTests(APITestCase):
2020
def setUp(self):
2121
non_org_active_user = OwnerFactory()
2222
tier = TierFactory(tier_name=TierName.BASIC.value)
23-
plan = PlanFactory(name=PlanName.BASIC_PLAN_NAME.value, tier=tier)
23+
plan = PlanFactory(name=DEFAULT_FREE_PLAN, tier=tier)
2424
self.current_owner = OwnerFactory(
2525
plan=plan.name,
2626
plan_user_count=5,

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."])

0 commit comments

Comments
 (0)