diff --git a/plan/__init__.py b/plan/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plan/service.py b/plan/service.py deleted file mode 100644 index 5ad8658ba0..0000000000 --- a/plan/service.py +++ /dev/null @@ -1,303 +0,0 @@ -import logging -from datetime import datetime, timedelta -from typing import List, Optional - -from shared.plan.constants import ( - BASIC_PLAN, - FREE_PLAN, - FREE_PLAN_REPRESENTATIONS, - PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS, - SENTRY_PAID_USER_PLAN_REPRESENTATIONS, - TEAM_PLAN_MAX_USERS, - TEAM_PLAN_REPRESENTATIONS, - TRIAL_PLAN_REPRESENTATION, - TRIAL_PLAN_SEATS, - USER_PLAN_REPRESENTATIONS, - PlanData, - PlanName, - TrialDaysAmount, - TrialStatus, -) - -from codecov.commands.exceptions import ValidationError -from codecov_auth.models import Owner -from services import sentry -from services.self_hosted import enterprise_has_seats_left, license_seats -from utils.config import get_config - -log = logging.getLogger(__name__) - - -# TODO: Consider moving some of these methods to the billing directory as they overlap billing functionality -class PlanService: - def __init__(self, current_org: Owner): - """ - Initializes a plan service object with a plan. The plan will be a trial plan - if applicable - - Args: - current_org (Owner): this is selected organization entry. This is not the user that is sending the request. - - Returns: - No value - """ - self.current_org = current_org - if self.current_org.plan not in USER_PLAN_REPRESENTATIONS: - raise ValueError("Unsupported plan") - self._plan_data = None - - def update_plan(self, name, user_count: int | None) -> None: - if name not in USER_PLAN_REPRESENTATIONS: - raise ValueError("Unsupported plan") - if not user_count: - raise ValueError("Quantity Needed") - self.current_org.plan = name - self.current_org.plan_user_count = user_count - self._plan_data = USER_PLAN_REPRESENTATIONS[self.current_org.plan] - self.current_org.delinquent = False - self.current_org.save() - - def current_org(self) -> Owner: - return self.current_org - - def set_default_plan_data(self) -> None: - log.info(f"Setting plan to users-basic for owner {self.current_org.ownerid}") - self.current_org.plan = PlanName.BASIC_PLAN_NAME.value - self.current_org.plan_activated_users = None - self.current_org.plan_user_count = 1 - self.current_org.stripe_subscription_id = None - self.current_org.save() - - @property - def has_account(self) -> bool: - return False if self.current_org.account is None else True - - @property - def plan_data(self) -> PlanData: - if self._plan_data is not None: - return self._plan_data - - if self.has_account: - self._plan_data = USER_PLAN_REPRESENTATIONS[self.current_org.account.plan] - else: - self._plan_data = USER_PLAN_REPRESENTATIONS[self.current_org.plan] - return self._plan_data - - @plan_data.setter - def set_plan_data(self, plan_data: PlanData | None) -> None: - self._plan_data = plan_data - - @property - def plan_name(self) -> str: - return self.plan_data.value - - @property - def plan_user_count(self) -> int: - if get_config("setup", "enterprise_license"): - return license_seats() - if self.has_account: - return self.current_org.account.total_seat_count - return self.current_org.plan_user_count - - @property - def plan_activated_users(self) -> Optional[List[int]]: - return self.current_org.plan_activated_users - - @property - def pretrial_users_count(self) -> int: - return self.current_org.pretrial_users_count or 1 - - @property - def marketing_name(self) -> str: - return self.plan_data.marketing_name - - @property - def billing_rate(self) -> Optional[str]: - return self.plan_data.billing_rate - - @property - def base_unit_price(self) -> int: - return self.plan_data.base_unit_price - - @property - def benefits(self) -> List[str]: - return self.plan_data.benefits - - @property - def monthly_uploads_limit(self) -> Optional[int]: - """ - Property that returns monthly uploads limit based on your trial status - - Returns: - Optional number of monthly uploads - """ - return self.plan_data.monthly_uploads_limit - - @property - def tier_name(self) -> str: - return self.plan_data.tier_name - - def available_plans(self, owner: Owner) -> List[PlanData]: - """ - Returns the available plans for an owner and an organization - - Args: - current_owner (Owner): this is the user that is sending the request. - - Returns: - No value - """ - available_plans = [] - available_plans.append(BASIC_PLAN) - - if self.plan_name == FREE_PLAN.value: - available_plans.append(FREE_PLAN) - - available_plans += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - - if owner and sentry.is_sentry_user(owner=owner): - available_plans += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - - # If number of activated users is less than or equal to TEAM_PLAN_MAX_USERS - if ( - self.plan_activated_users is None - or len(self.plan_activated_users) <= TEAM_PLAN_MAX_USERS - ): - available_plans += TEAM_PLAN_REPRESENTATIONS.values() - - return available_plans - - def _start_trial_helper( - self, - current_owner: Owner, - end_date: Optional[datetime] = None, - is_extension: bool = False, - ) -> None: - start_date = datetime.now() - - # When they are not extending a trial, have to setup all the default values - if not is_extension: - self.current_org.trial_start_date = start_date - self.current_org.trial_status = TrialStatus.ONGOING.value - self.current_org.plan = PlanName.TRIAL_PLAN_NAME.value - self.current_org.pretrial_users_count = self.current_org.plan_user_count - self.current_org.plan_user_count = TRIAL_PLAN_SEATS - self.current_org.plan_auto_activate = True - - if end_date is None: - self.current_org.trial_end_date = start_date + timedelta( - days=TrialDaysAmount.CODECOV_SENTRY.value - ) - else: - self.current_org.trial_end_date = end_date - self.current_org.trial_fired_by = current_owner.ownerid - self.current_org.save() - - # Trial Data - def start_trial(self, current_owner: Owner) -> None: - """ - Method that starts trial on an organization if the trial_start_date - is not empty. - - Returns: - No value - - Raises: - ValidationError: if trial has already started - """ - if self.trial_status != TrialStatus.NOT_STARTED.value: - raise ValidationError("Cannot start an existing trial") - if self.plan_name not in FREE_PLAN_REPRESENTATIONS: - raise ValidationError("Cannot trial from a paid plan") - - self._start_trial_helper(current_owner) - - def start_trial_manually(self, current_owner: Owner, end_date: datetime) -> None: - """ - Method that start trial immediately and ends at a predefined date for an organization - Used by administrators to manually start and extend trials - - Returns: - No value - """ - # Start a new trial plan for free users currently not on trial - if self.plan_name in FREE_PLAN_REPRESENTATIONS: - self._start_trial_helper(current_owner, end_date, is_extension=False) - # Extend an existing trial plan for users currently on trial - elif self.plan_name in TRIAL_PLAN_REPRESENTATION: - self._start_trial_helper(current_owner, end_date, is_extension=True) - # Paying users cannot start a trial - else: - raise ValidationError("Cannot trial from a paid plan") - - def cancel_trial(self) -> None: - if not self.is_org_trialing: - raise ValidationError("Cannot cancel a trial that is not ongoing") - now = datetime.now() - self.current_org.trial_status = TrialStatus.EXPIRED.value - self.current_org.trial_end_date = now - self.set_default_plan_data() - - def expire_trial_when_upgrading(self) -> None: - """ - Method that expires trial on an organization based on it's current trial status. - - - Returns: - No value - """ - if self.trial_status == TrialStatus.EXPIRED.value: - return - if self.trial_status != TrialStatus.CANNOT_TRIAL.value: - # Not adjusting the trial start/end dates here as some customers can - # directly purchase a plan without trialing first - self.current_org.trial_status = TrialStatus.EXPIRED.value - self.current_org.plan_activated_users = None - self.current_org.plan_user_count = ( - self.current_org.pretrial_users_count or 1 - ) - self.current_org.trial_end_date = datetime.now() - - self.current_org.save() - - @property - def trial_status(self) -> TrialStatus: - return self.current_org.trial_status - - @property - def trial_start_date(self) -> Optional[datetime]: - return self.current_org.trial_start_date - - @property - def trial_end_date(self) -> Optional[datetime]: - return self.current_org.trial_end_date - - @property - def trial_total_days(self) -> Optional[int]: - return self.plan_data.trial_days - - @property - def is_org_trialing(self) -> bool: - return ( - self.trial_status == TrialStatus.ONGOING.value - and self.plan_name == PlanName.TRIAL_PLAN_NAME.value - ) - - @property - def has_trial_dates(self) -> bool: - return bool(self.trial_start_date and self.trial_end_date) - - @property - def has_seats_left(self) -> bool: - if get_config("setup", "enterprise_license"): - return enterprise_has_seats_left() - if self.has_account: - # edge case: IF the User is already a plan_activated_user on any of the Orgs in the Account, - # AND their Account is at capacity, - # AND they try to become a plan_activated_user on another Org in the Account, - # has_seats_left will evaluate as False even though the User should be allowed to activate on the Org. - return self.current_org.account.can_activate_user() - return ( - self.plan_activated_users is None - or len(self.plan_activated_users) < self.plan_user_count - ) diff --git a/plan/tests/__init__.py b/plan/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plan/tests/test_plan.py b/plan/tests/test_plan.py deleted file mode 100644 index c0ac76154d..0000000000 --- a/plan/tests/test_plan.py +++ /dev/null @@ -1,891 +0,0 @@ -from datetime import datetime, timedelta -from unittest.mock import patch - -from django.test import TestCase -from freezegun import freeze_time -from pytest import raises -from shared.django_apps.codecov.commands.exceptions import ValidationError -from shared.django_apps.codecov_auth.tests.factories import ( - AccountFactory, - AccountsUsersFactory, - OwnerFactory, -) -from shared.plan.constants import ( - BASIC_PLAN, - FREE_PLAN, - FREE_PLAN_REPRESENTATIONS, - PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS, - SENTRY_PAID_USER_PLAN_REPRESENTATIONS, - TEAM_PLAN_REPRESENTATIONS, - TRIAL_PLAN_REPRESENTATION, - TRIAL_PLAN_SEATS, - PlanName, - TrialDaysAmount, - TrialStatus, -) -from shared.plan.service import PlanService - - -@freeze_time("2023-06-19") -class PlanServiceTests(TestCase): - def test_plan_service_trial_status_not_started(self): - current_org = OwnerFactory(plan=PlanName.BASIC_PLAN_NAME.value) - plan_service = PlanService(current_org=current_org) - - assert plan_service.trial_status == TrialStatus.NOT_STARTED.value - - def test_plan_service_trial_status_expired(self): - trial_start_date = datetime.now() - trial_end_date_expired = trial_start_date - timedelta(days=1) - current_org = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date_expired, - trial_status=TrialStatus.EXPIRED.value, - ) - plan_service = PlanService(current_org=current_org) - - assert plan_service.trial_status == TrialStatus.EXPIRED.value - - def test_plan_service_trial_status_ongoing(self): - trial_start_date = datetime.now() - trial_end_date_ongoing = trial_start_date + timedelta(days=5) - current_org = OwnerFactory( - plan=PlanName.TRIAL_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date_ongoing, - trial_status=TrialStatus.ONGOING.value, - ) - plan_service = PlanService(current_org=current_org) - - assert plan_service.trial_status == TrialStatus.ONGOING.value - assert plan_service.is_org_trialing == True - - def test_plan_service_expire_trial_when_upgrading_successful_if_trial_is_not_started( - self, - ): - current_org_with_ongoing_trial = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=None, - trial_end_date=None, - trial_status=TrialStatus.NOT_STARTED.value, - ) - plan_service = PlanService(current_org=current_org_with_ongoing_trial) - plan_service.expire_trial_when_upgrading() - assert current_org_with_ongoing_trial.trial_status == TrialStatus.EXPIRED.value - assert current_org_with_ongoing_trial.plan_activated_users is None - assert current_org_with_ongoing_trial.plan_user_count == 1 - assert current_org_with_ongoing_trial.trial_end_date == datetime.now() - - def test_plan_service_expire_trial_when_upgrading_successful_if_trial_is_ongoing( - self, - ): - trial_start_date = datetime.now() - trial_end_date_ongoing = trial_start_date + timedelta(days=5) - current_org_with_ongoing_trial = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date_ongoing, - trial_status=TrialStatus.ONGOING.value, - ) - plan_service = PlanService(current_org=current_org_with_ongoing_trial) - plan_service.expire_trial_when_upgrading() - assert current_org_with_ongoing_trial.trial_status == TrialStatus.EXPIRED.value - assert current_org_with_ongoing_trial.plan_activated_users is None - assert current_org_with_ongoing_trial.plan_user_count == 1 - assert current_org_with_ongoing_trial.trial_end_date == datetime.now() - - def test_plan_service_expire_trial_users_pretrial_users_count_if_existing( - self, - ): - trial_start_date = datetime.now() - trial_end_date_ongoing = trial_start_date + timedelta(days=5) - pretrial_users_count = 5 - current_org_with_ongoing_trial = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date_ongoing, - trial_status=TrialStatus.ONGOING.value, - pretrial_users_count=pretrial_users_count, - ) - plan_service = PlanService(current_org=current_org_with_ongoing_trial) - plan_service.expire_trial_when_upgrading() - assert current_org_with_ongoing_trial.trial_status == TrialStatus.EXPIRED.value - assert current_org_with_ongoing_trial.plan_activated_users is None - assert current_org_with_ongoing_trial.plan_user_count == pretrial_users_count - assert current_org_with_ongoing_trial.trial_end_date == datetime.now() - - def test_plan_service_start_trial_errors_if_status_is_ongoing(self): - trial_start_date = datetime.now() - trial_end_date = trial_start_date + timedelta( - days=TrialDaysAmount.CODECOV_SENTRY.value - ) - current_org = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date, - trial_status=TrialStatus.ONGOING.value, - ) - plan_service = PlanService(current_org=current_org) - current_owner = OwnerFactory() - - with self.assertRaises(ValidationError): - plan_service.start_trial(current_owner=current_owner) - - def test_plan_service_start_trial_errors_if_status_is_expired(self): - trial_start_date = datetime.now() - trial_end_date = trial_start_date + timedelta(days=-1) - current_org = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date, - trial_status=TrialStatus.EXPIRED.value, - ) - plan_service = PlanService(current_org=current_org) - current_owner = OwnerFactory() - - with self.assertRaises(ValidationError): - plan_service.start_trial(current_owner=current_owner) - - def test_plan_service_start_trial_errors_if_status_is_cannot_trial(self): - current_org = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=None, - trial_end_date=None, - trial_status=TrialStatus.CANNOT_TRIAL.value, - ) - plan_service = PlanService(current_org=current_org) - current_owner = OwnerFactory() - - with self.assertRaises(ValidationError): - plan_service.start_trial(current_owner=current_owner) - - def test_plan_service_start_trial_errors_owners_plan_is_not_a_free_plan(self): - current_org = OwnerFactory( - plan=PlanName.CODECOV_PRO_MONTHLY.value, - trial_start_date=None, - trial_end_date=None, - trial_status=TrialStatus.CANNOT_TRIAL.value, - ) - plan_service = PlanService(current_org=current_org) - current_owner = OwnerFactory() - - with self.assertRaises(ValidationError): - plan_service.start_trial(current_owner=current_owner) - - def test_plan_service_start_trial_succeeds_if_trial_has_not_started(self): - trial_start_date = None - trial_end_date = None - plan_user_count = 5 - current_org = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date, - trial_status=TrialStatus.NOT_STARTED.value, - plan_user_count=plan_user_count, - ) - plan_service = PlanService(current_org=current_org) - current_owner = OwnerFactory() - - plan_service.start_trial(current_owner=current_owner) - assert current_org.trial_start_date == datetime.now() - assert current_org.trial_end_date == datetime.now() + timedelta( - days=TrialDaysAmount.CODECOV_SENTRY.value - ) - assert current_org.trial_status == TrialStatus.ONGOING.value - assert current_org.plan == PlanName.TRIAL_PLAN_NAME.value - assert current_org.pretrial_users_count == plan_user_count - assert current_org.plan_user_count == TRIAL_PLAN_SEATS - assert current_org.plan_auto_activate == True - assert current_org.trial_fired_by == current_owner.ownerid - - def test_plan_service_start_trial_manually(self): - trial_start_date = None - trial_end_date = None - plan_user_count = 5 - current_org = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date, - trial_status=TrialStatus.NOT_STARTED.value, - plan_user_count=plan_user_count, - ) - plan_service = PlanService(current_org=current_org) - current_owner = OwnerFactory() - - plan_service.start_trial_manually( - current_owner=current_owner, end_date="2024-01-01 00:00:00" - ) - assert current_org.trial_start_date == datetime.now() - assert current_org.trial_end_date == "2024-01-01 00:00:00" - assert current_org.trial_status == TrialStatus.ONGOING.value - assert current_org.plan == PlanName.TRIAL_PLAN_NAME.value - assert current_org.pretrial_users_count == plan_user_count - assert current_org.plan_user_count == TRIAL_PLAN_SEATS - assert current_org.plan_auto_activate == True - assert current_org.trial_fired_by == current_owner.ownerid - - def test_plan_service_start_trial_manually_already_on_paid_plan(self): - current_org = OwnerFactory( - plan=PlanName.CODECOV_PRO_MONTHLY.value, - trial_start_date=None, - trial_end_date=None, - trial_status=TrialStatus.NOT_STARTED.value, - ) - plan_service = PlanService(current_org=current_org) - current_owner = OwnerFactory() - - with self.assertRaises(ValidationError): - plan_service.start_trial_manually( - current_owner=current_owner, end_date="2024-01-01 00:00:00" - ) - - def test_plan_service_returns_plan_data_for_non_trial_basic_plan(self): - trial_start_date = None - trial_end_date = None - current_org = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date, - ) - plan_service = PlanService(current_org=current_org) - - basic_plan = FREE_PLAN_REPRESENTATIONS[PlanName.BASIC_PLAN_NAME.value] - assert plan_service.current_org == current_org - assert plan_service.trial_status == TrialStatus.NOT_STARTED.value - assert plan_service.marketing_name == basic_plan.marketing_name - assert plan_service.plan_name == basic_plan.value - assert plan_service.tier_name == basic_plan.tier_name - assert plan_service.billing_rate == basic_plan.billing_rate - assert plan_service.base_unit_price == basic_plan.base_unit_price - assert plan_service.benefits == basic_plan.benefits - assert ( - plan_service.monthly_uploads_limit == basic_plan.monthly_uploads_limit - ) # should be 250 - assert ( - plan_service.monthly_uploads_limit == 250 - ) # should be 250 since not trialing - assert plan_service.trial_total_days == basic_plan.trial_days - - def test_plan_service_returns_plan_data_for_trialing_user_trial_plan(self): - trial_start_date = datetime.now() - trial_end_date = datetime.now() + timedelta( - days=TrialDaysAmount.CODECOV_SENTRY.value - ) - current_org = OwnerFactory( - plan=PlanName.TRIAL_PLAN_NAME.value, - trial_start_date=trial_start_date, - trial_end_date=trial_end_date, - trial_status=TrialStatus.ONGOING.value, - ) - plan_service = PlanService(current_org=current_org) - - trial_plan = TRIAL_PLAN_REPRESENTATION[PlanName.TRIAL_PLAN_NAME.value] - assert plan_service.trial_status == TrialStatus.ONGOING.value - assert plan_service.marketing_name == trial_plan.marketing_name - assert plan_service.plan_name == trial_plan.value - assert plan_service.tier_name == trial_plan.tier_name - assert plan_service.billing_rate == trial_plan.billing_rate - assert plan_service.base_unit_price == trial_plan.base_unit_price - assert plan_service.benefits == trial_plan.benefits - assert plan_service.monthly_uploads_limit is None # Not 250 since it's trialing - assert plan_service.trial_total_days == trial_plan.trial_days - - def test_plan_service_sets_default_plan_data_values_correctly(self): - current_org = OwnerFactory( - plan=PlanName.CODECOV_PRO_MONTHLY.value, - stripe_subscription_id="test-sub-123", - plan_user_count=20, - plan_activated_users=[44], - plan_auto_activate=False, - ) - current_org.save() - - plan_service = PlanService(current_org=current_org) - plan_service.set_default_plan_data() - - assert current_org.plan == PlanName.BASIC_PLAN_NAME.value - assert current_org.plan_user_count == 1 - assert current_org.plan_activated_users is None - assert current_org.stripe_subscription_id is None - - def test_plan_service_returns_if_owner_has_trial_dates(self): - current_org = OwnerFactory( - plan=PlanName.CODECOV_PRO_MONTHLY.value, - trial_start_date=datetime.now(), - trial_end_date=datetime.now() + timedelta(days=14), - ) - current_org.save() - - plan_service = PlanService(current_org=current_org) - - assert plan_service.has_trial_dates == True - - def test_plan_service_has_seats_left(self): - current_org = OwnerFactory( - plan=PlanName.TRIAL_PLAN_NAME.value, - plan_user_count=6, - plan_activated_users=[i for i in range(5)], - ) - plan_service = PlanService(current_org=current_org) - - assert plan_service.has_seats_left == True - - def test_plan_service_has_no_seats_left(self): - current_org = OwnerFactory( - plan=PlanName.TRIAL_PLAN_NAME.value, - plan_user_count=5, - plan_activated_users=[i for i in range(5)], - ) - plan_service = PlanService(current_org=current_org) - - assert plan_service.has_seats_left == False - - def test_plan_service_update_plan_invalid_name(self): - current_org = OwnerFactory(plan=PlanName.BASIC_PLAN_NAME.value) - plan_service = PlanService(current_org=current_org) - - with raises(ValueError, match="Unsupported plan"): - plan_service.update_plan(name="blah", user_count=1) - - def test_plan_service_update_plan_invalid_user_count(self): - current_org = OwnerFactory(plan=PlanName.BASIC_PLAN_NAME.value) - plan_service = PlanService(current_org=current_org) - - with raises(ValueError, match="Quantity Needed"): - plan_service.update_plan( - name=PlanName.BASIC_PLAN_NAME.value, user_count=None - ) - - def test_plan_service_update_plan_succeeds(self): - current_org = OwnerFactory(plan=PlanName.BASIC_PLAN_NAME.value) - plan_service = PlanService(current_org=current_org) - - plan_service.update_plan(name=PlanName.TEAM_MONTHLY.value, user_count=8) - - assert current_org.plan == PlanName.TEAM_MONTHLY.value - assert current_org.plan_user_count == 8 - - def test_has_account(self): - current_org = OwnerFactory() - plan_service = PlanService(current_org=current_org) - self.assertFalse(plan_service.has_account) - - current_org.account = AccountFactory() - current_org.save() - plan_service = PlanService(current_org=current_org) - self.assertTrue(plan_service.has_account) - - def test_plan_data_has_account(self): - current_org = OwnerFactory(plan=PlanName.BASIC_PLAN_NAME.value) - plan_service = PlanService(current_org=current_org) - self.assertEqual(plan_service.plan_name, PlanName.BASIC_PLAN_NAME.value) - - current_org.account = AccountFactory(plan=PlanName.CODECOV_PRO_YEARLY.value) - current_org.save() - plan_service = PlanService(current_org=current_org) - self.assertEqual(plan_service.plan_name, PlanName.CODECOV_PRO_YEARLY.value) - - def test_plan_user_count_has_account(self): - org = OwnerFactory(plan=PlanName.BASIC_PLAN_NAME.value, plan_user_count=5) - account = AccountFactory( - plan=PlanName.BASIC_PLAN_NAME.value, plan_seat_count=50, free_seat_count=3 - ) - - plan_service = PlanService(current_org=org) - self.assertEqual(plan_service.plan_user_count, 5) - - org.account = account - org.save() - plan_service = PlanService(current_org=org) - self.assertEqual(plan_service.plan_user_count, 53) - - def test_has_seats_left_has_account(self): - org = OwnerFactory( - plan=PlanName.BASIC_PLAN_NAME.value, - plan_user_count=5, - plan_activated_users=[1, 2, 3], - ) - account = AccountFactory( - plan=PlanName.BASIC_PLAN_NAME.value, plan_seat_count=5, free_seat_count=3 - ) - for i in range(8): - AccountsUsersFactory(account=account) - - plan_service = PlanService(current_org=org) - self.assertEqual(plan_service.has_seats_left, True) - - org.account = account - org.save() - plan_service = PlanService(current_org=org) - self.assertEqual(plan_service.has_seats_left, False) - - -class AvailablePlansBeforeTrial(TestCase): - """ - - users-basic, no trial -> users-pr-inappm/y, users-basic - - users-free, no trial -> users-pr-inappm/y, users-basic, users-free - - users-teamm/y, no trial -> users-pr-inappm/y, users-basic, users-teamm/y - - users-pr-inappm/y, no trial -> users-pr-inappm/y, users-basic - - sentry customer, users-basic, no trial -> users-pr-inappm/y, users-sentrym/y, users-basic - - sentry customer, users-teamm/y, no trial -> users-pr-inappm/y, users-sentrym/y, users-basic, users-teamm/y - - sentry customer, users-sentrym/y, no trial -> users-pr-inappm/y, users-sentrym/y, users-basic - """ - - def setUp(self): - self.current_org = OwnerFactory( - trial_start_date=None, - trial_end_date=None, - trial_status=TrialStatus.NOT_STARTED.value, - ) - self.owner = OwnerFactory() - - def test_available_plans_for_basic_plan_non_trial( - self, - ): - self.current_org.plan = PlanName.BASIC_PLAN_NAME.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - def test_available_plans_for_free_plan_non_trial( - self, - ): - self.current_org.plan = PlanName.FREE_PLAN_NAME.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result.append(FREE_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - def test_available_plans_for_team_plan_non_trial( - self, - ): - self.current_org.plan = PlanName.TEAM_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - def test_available_plans_for_pro_plan_non_trial(self): - self.current_org.plan = PlanName.CODECOV_PRO_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_available_plans_for_sentry_customer_basic_plan_non_trial( - self, is_sentry_user - ): - is_sentry_user.return_value = True - self.current_org.plan = PlanName.BASIC_PLAN_NAME.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_available_plans_for_sentry_customer_team_plan_non_trial( - self, is_sentry_user - ): - is_sentry_user.return_value = True - self.current_org.plan = PlanName.TEAM_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_available_plans_for_sentry_plan_non_trial(self, is_sentry_user): - is_sentry_user.return_value = True - self.current_org.plan = PlanName.SENTRY_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - -@freeze_time("2023-06-19") -class AvailablePlansExpiredTrialLessThanTenUsers(TestCase): - """ - - users-basic, has trialed, less than 10 users -> users-pr-inappm/y, users-basic, users-teamm/y - - users-teamm/y, has trialed, less than 10 users -> users-pr-inappm/y, users-basic, users-teamm/y - - users-pr-inappm/y, has trialed, less than 10 users -> users-pr-inappm/y, users-basic, users-teamm/y - - sentry customer, users-basic, has trialed, less than 10 users -> users-pr-inappm/y, users-sentrym/y, users-basic, users-teamm/y - - sentry customer, users-teamm/y, has trialed, less than 10 users -> users-pr-inappm/y, users-sentrym/y, users-basic, users-teamm/y - - sentry customer, users-sentrym/y, has trialed, less than 10 users -> users-pr-inappm/y, users-sentrym/y, users-basic, users-teamm/y - """ - - def setUp(self): - self.current_org = OwnerFactory( - trial_start_date=datetime.now() + timedelta(days=-10), - trial_end_date=datetime.now() + timedelta(days=-3), - trial_status=TrialStatus.EXPIRED.value, - plan_user_count=3, - ) - self.owner = OwnerFactory() - - def test_available_plans_for_basic_plan_expired_trial_less_than_10_users( - self, - ): - self.current_org.plan = PlanName.BASIC_PLAN_NAME.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - def test_available_plans_for_team_plan_expired_trial_less_than_10_users( - self, - ): - self.current_org.plan = PlanName.TEAM_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - def test_available_plans_for_pro_plan_expired_trial_less_than_10_users(self): - self.current_org.plan = PlanName.CODECOV_PRO_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_available_plans_for_sentry_customer_basic_plan_expired_trial_less_than_10_users( - self, is_sentry_user - ): - is_sentry_user.return_value = True - self.current_org.plan = PlanName.BASIC_PLAN_NAME.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_available_plans_for_sentry_customer_team_plan_expired_trial_less_than_10_users( - self, is_sentry_user - ): - is_sentry_user.return_value = True - self.current_org.plan = PlanName.TEAM_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_available_plans_for_sentry_plan_expired_trial_less_than_10_users( - self, is_sentry_user - ): - is_sentry_user.return_value = True - self.current_org.plan = PlanName.SENTRY_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - -@freeze_time("2023-06-19") -class AvailablePlansExpiredTrialMoreThanTenActivatedUsers(TestCase): - """ - - users-pr-inappm/y, has trialed, more than 10 activated users -> users-pr-inappm/y, users-basic - - sentry customer, users-basic, has trialed, more than 10 activated users -> users-pr-inappm/y, users-sentrym/y, users-basic - - sentry customer, users-sentrym/y, has trialed, more than 10 activated users -> users-pr-inappm/y, users-sentrym/y, users-basic - """ - - def setUp(self): - self.current_org = OwnerFactory( - trial_start_date=datetime.now() + timedelta(days=-10), - trial_end_date=datetime.now() + timedelta(days=-3), - trial_status=TrialStatus.EXPIRED.value, - plan_user_count=1, - plan_activated_users=[i for i in range(13)], - ) - self.owner = OwnerFactory() - - def test_available_plans_for_pro_plan_expired_trial_more_than_10_users(self): - self.current_org.plan = PlanName.CODECOV_PRO_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_available_plans_for_sentry_customer_basic_plan_expired_trial_more_than_10_users( - self, is_sentry_user - ): - is_sentry_user.return_value = True - self.current_org.plan = PlanName.BASIC_PLAN_NAME.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_available_plans_for_sentry_plan_expired_trial_more_than_10_users( - self, is_sentry_user - ): - is_sentry_user.return_value = True - self.current_org.plan = PlanName.SENTRY_MONTHLY.value - self.current_org.save() - - plan_service = PlanService(current_org=self.current_org) - - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - - assert plan_service.available_plans(owner=self.owner) == expected_result - - -@freeze_time("2023-06-19") -class AvailablePlansExpiredTrialMoreThanTenSeatsLessThanTenActivatedUsers(TestCase): - """ - Tests that what matters for Team plan is activated users not the total seat count - """ - - def setUp(self): - self.expected_result = [] - self.expected_result.append(BASIC_PLAN) - self.expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - self.expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - def test_currently_team_plan(self): - self.current_org = OwnerFactory( - plan_user_count=100, - plan_activated_users=[i for i in range(10)], - plan=PlanName.TEAM_MONTHLY.value, - ) - self.owner = OwnerFactory() - self.plan_service = PlanService(current_org=self.current_org) - - assert ( - self.plan_service.available_plans(owner=self.owner) == self.expected_result - ) - - def test_trial_expired(self): - self.current_org = OwnerFactory( - plan_user_count=100, - plan_activated_users=[i for i in range(10)], - trial_status=TrialStatus.EXPIRED.value, - trial_start_date=datetime.now() + timedelta(days=-10), - trial_end_date=datetime.now() + timedelta(days=-3), - ) - self.owner = OwnerFactory() - self.plan_service = PlanService(current_org=self.current_org) - - assert ( - self.plan_service.available_plans(owner=self.owner) == self.expected_result - ) - - def test_trial_ongoing(self): - self.current_org = OwnerFactory( - plan_user_count=100, - plan_activated_users=[i for i in range(10)], - trial_status=TrialStatus.ONGOING.value, - trial_start_date=datetime.now() + timedelta(days=-10), - trial_end_date=datetime.now() + timedelta(days=3), - ) - self.owner = OwnerFactory() - self.plan_service = PlanService(current_org=self.current_org) - - assert ( - self.plan_service.available_plans(owner=self.owner) == self.expected_result - ) - - def test_trial_not_started(self): - self.current_org = OwnerFactory( - plan_user_count=100, - plan_activated_users=[i for i in range(10)], - trial_status=TrialStatus.NOT_STARTED.value, - ) - self.owner = OwnerFactory() - self.plan_service = PlanService(current_org=self.current_org) - - self.expected_result = [] - self.expected_result.append(BASIC_PLAN) - self.expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - self.expected_result += TEAM_PLAN_REPRESENTATIONS.values() - assert ( - self.plan_service.available_plans(owner=self.owner) == self.expected_result - ) - - -@freeze_time("2023-06-19") -class AvailablePlansOngoingTrial(TestCase): - """ - Non Sentry User is trialing - when <=10 activated seats -> users-pr-inappm/y, users-basic, users-teamm/y - when > 10 activated seats -> users-pr-inappm/y, users-basic - Sentry User is trialing - when <=10 activated seats -> users-pr-inappm/y, users-sentrym/y, users-basic, users-teamm/y - when > 10 activated seats -> users-pr-inappm/y, users-sentrym/y, users-basic - """ - - def setUp(self): - self.current_org = OwnerFactory( - plan=PlanName.TRIAL_PLAN_NAME.value, - trial_start_date=datetime.now(), - trial_end_date=datetime.now() + timedelta(days=14), - trial_status=TrialStatus.ONGOING.value, - plan_user_count=1000, - plan_activated_users=None, - ) - self.owner = OwnerFactory() - self.plan_service = PlanService(current_org=self.current_org) - - def test_non_sentry_user(self): - # [Basic, Pro Monthly, Pro Yearly, Team Monthly, Team Yearly] - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - # Can do Team plan when plan_activated_users is null - assert self.plan_service.available_plans(owner=self.owner) == expected_result - - self.current_org.plan_activated_users = [i for i in range(10)] - self.current_org.save() - - # Can do Team plan when at 10 activated users - assert self.plan_service.available_plans(owner=self.owner) == expected_result - - self.current_org.plan_activated_users = [i for i in range(11)] - self.current_org.save() - - # [Basic, Pro Monthly, Pro Yearly, Team Monthly, Team Yearly] - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - - # Can not do Team plan when at 11 activated users - assert self.plan_service.available_plans(owner=self.owner) == expected_result - - @patch("shared.plan.service.is_sentry_user") - def test_sentry_user(self, is_sentry_user): - is_sentry_user.return_value = True - - # [Basic, Pro Monthly, Pro Yearly, Sentry Monthly, Sentry Yearly, Team Monthly, Team Yearly] - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += TEAM_PLAN_REPRESENTATIONS.values() - - # Can do Team plan when plan_activated_users is null - assert self.plan_service.available_plans(owner=self.owner) == expected_result - - self.current_org.plan_activated_users = [i for i in range(10)] - self.current_org.save() - - # Can do Team plan when at 10 activated users - assert self.plan_service.available_plans(owner=self.owner) == expected_result - - self.current_org.plan_activated_users = [i for i in range(11)] - self.current_org.save() - - # [Basic, Pro Monthly, Pro Yearly, Sentry Monthly, Sentry Yearly] - expected_result = [] - expected_result.append(BASIC_PLAN) - expected_result += PR_AUTHOR_PAID_USER_PLAN_REPRESENTATIONS.values() - expected_result += SENTRY_PAID_USER_PLAN_REPRESENTATIONS.values() - - # Can not do Team plan when at 11 activated users - assert self.plan_service.available_plans(owner=self.owner) == expected_result