Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions api/app/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,19 @@
from rest_framework.views import exception_handler


class ImproperlyConfiguredError(RuntimeError):
pass


def custom_api_exception_handler(exc, context):

Check failure on line 8 in api/app/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.12)

Function is missing a type annotation

Check failure on line 8 in api/app/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.11)

Function is missing a type annotation

Check failure on line 8 in api/app/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.13)

Function is missing a type annotation
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)

if response is not None and hasattr(exc, "get_codes"):
if isinstance(response.data, dict) and "detail" in response.data:
codes = exc.get_codes()
if isinstance(codes, str):
response.data["code"] = codes

return response
1 change: 1 addition & 0 deletions api/app/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@
"api_keys.authentication.MasterAPIKeyAuthentication",
"oauth2_metadata.authentication.OAuth2BearerTokenAuthentication",
),
"EXCEPTION_HANDLER": "app.exceptions.custom_api_exception_handler",
"PAGE_SIZE": 10,
"UNICODE_JSON": False,
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
Expand Down
9 changes: 7 additions & 2 deletions api/organisations/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from rest_framework import status
from rest_framework.exceptions import APIException


class OrganisationHasNoPaidSubscription(APIException):
status_code = 400
default_detail = "Organisation has no subscription"
status_code = status.HTTP_402_PAYMENT_REQUIRED
default_code = "invalid-plan"
default_detail = (
"Organisation has no subscription. "
"Please upgrade your plan: https://www.flagsmith.com/pricing"
)
27 changes: 19 additions & 8 deletions api/organisations/subscriptions/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
from rest_framework import status
from rest_framework.exceptions import APIException


class InvalidSubscriptionPlanError(APIException):
status_code = 403
default_detail = "Organisation does not have a valid plan for this resource."
class BaseInvalidPlanError(APIException):
status_code = status.HTTP_402_PAYMENT_REQUIRED
default_code = "invalid-plan"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
default_detail = (
"Organisation does not have a valid plan for this resource. "
Comment thread
srijantrpth marked this conversation as resolved.
"Please upgrade your plan: https://www.flagsmith.com/pricing"
)


class InvalidSubscriptionPlanError(BaseInvalidPlanError):
pass


class CannotCancelChargebeeSubscription(APIException):
Expand All @@ -15,7 +24,7 @@ class UpgradeSeatsError(APIException):


class UpgradeSeatsPaymentFailure(APIException):
status_code = 400
status_code = status.HTTP_400_BAD_REQUEST
default_detail = (
"Joining the organisation has failed due to a payment issue. "
"Please contact your organisation's admin."
Expand All @@ -27,13 +36,15 @@ class UpgradeAPIUsageError(APIException):


class UpgradeAPIUsagePaymentFailure(APIException):
status_code = 400
status_code = status.HTTP_400_BAD_REQUEST
default_detail = (
"API usage upgrade has failed due to a payment issue. "
"If this persists, contact the organisation admin."
)


class SubscriptionDoesNotSupportSeatUpgrade(APIException):
status_code = 400
default_detail = "Please upgrade your plan to add additional seats/users"
class SubscriptionDoesNotSupportSeatUpgrade(BaseInvalidPlanError):
default_detail = (
"Please upgrade your plan to add additional seats/users: "
"https://www.flagsmith.com/pricing"
)
34 changes: 34 additions & 0 deletions api/projects/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from rest_framework import status
from rest_framework.exceptions import APIException


Expand All @@ -19,3 +20,36 @@
class ProjectTooLargeError(APIException):
status_code = 400
default_detail = "Project is too large; Please contact support"


class SystemLimitError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_code = "system-limit"
default_detail = (
"System limit exceeded. Please contact support to adjust your limits: "
"https://docs.flagsmith.com/support#getting-in-touch"
)


class DynamoNotEnabledError(APIException):

Check failure on line 34 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.12)

Name "DynamoNotEnabledError" already defined on line 5

Check failure on line 34 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.11)

Name "DynamoNotEnabledError" already defined on line 5

Check failure on line 34 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.13)

Name "DynamoNotEnabledError" already defined on line 5
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "Dynamo DB is not enabled for this project"


class ProjectMigrationError(APIException):

Check failure on line 39 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.12)

Name "ProjectMigrationError" already defined on line 10

Check failure on line 39 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.11)

Name "ProjectMigrationError" already defined on line 10

Check failure on line 39 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.13)

Name "ProjectMigrationError" already defined on line 10
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "Migration is either already done or is in progress"


class TooManyIdentitiesError(SystemLimitError):

Check failure on line 44 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.12)

Name "TooManyIdentitiesError" already defined on line 15

Check failure on line 44 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.11)

Name "TooManyIdentitiesError" already defined on line 15

Check failure on line 44 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.13)

Name "TooManyIdentitiesError" already defined on line 15
default_detail = (
"Too many identities. Please contact support to adjust your limits: "
"https://docs.flagsmith.com/support#getting-in-touch"
)


class ProjectTooLargeError(SystemLimitError):

Check failure on line 51 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.12)

Name "ProjectTooLargeError" already defined on line 20

Check failure on line 51 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.11)

Name "ProjectTooLargeError" already defined on line 20

Check failure on line 51 in api/projects/exceptions.py

View workflow job for this annotation

GitHub Actions / API Unit Tests (3.13)

Name "ProjectTooLargeError" already defined on line 20
default_detail = (
"Project is too large. Please contact support to adjust your limits: "
"https://docs.flagsmith.com/support#getting-in-touch"
)
40 changes: 27 additions & 13 deletions api/tests/unit/organisations/invites/test_unit_invites_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_get_invite_links__multiple_roles_exist__returns_all_links(
assert all(attr in invite_link for attr in expected_attributes)


def test_get_invite_links__seats_exceeded__returns_400(
def test_get_invite_links__seats_exceeded__returns_402(
settings: SettingsWrapper,
organisation: Organisation,
admin_client: APIClient,
Expand All @@ -91,7 +91,12 @@ def test_get_invite_links__seats_exceeded__returns_400(
response = admin_client.get(url)

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.status_code == status.HTTP_402_PAYMENT_REQUIRED
Comment thread
srijantrpth marked this conversation as resolved.
assert response.json()["code"] == "invalid-plan"
assert (
response.json()["detail"]
== "Please upgrade your plan to add additional seats/users: https://www.flagsmith.com/pricing"
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def test_delete_invite_link__valid_invite__returns_204(
Expand Down Expand Up @@ -120,7 +125,7 @@ def test_delete_invite_link__valid_invite__returns_204(
assert response.status_code == status.HTTP_204_NO_CONTENT


def test_delete_invite_link__seats_exceeded__returns_400(
def test_delete_invite_link__seats_exceeded__returns_402(
organisation: Organisation,
admin_client: APIClient,
settings: SettingsWrapper,
Expand All @@ -137,7 +142,12 @@ def test_delete_invite_link__seats_exceeded__returns_400(
response = admin_client.delete(url)

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.status_code == status.HTTP_402_PAYMENT_REQUIRED
assert response.json()["code"] == "invalid-plan"
assert (
response.json()["detail"]
== "Please upgrade your plan to add additional seats/users: https://www.flagsmith.com/pricing"
)


def test_update_invite_link__patch_request__returns_405( # type: ignore[no-untyped-def]
Expand Down Expand Up @@ -258,6 +268,7 @@ def test_create_invite__permission_group_from_another_org__returns_400(
)

# Then
# Validation error, stays 400
assert response.status_code == status.HTTP_400_BAD_REQUEST

response_json = response.json()
Expand All @@ -268,7 +279,7 @@ def test_create_invite__permission_group_from_another_org__returns_400(
}


def test_create_invite__seats_exceeded__returns_400(
def test_create_invite__seats_exceeded__returns_402(
admin_client: APIClient,
organisation: Organisation,
user_permission_group: UserPermissionGroup,
Expand All @@ -286,10 +297,11 @@ def test_create_invite__seats_exceeded__returns_400(
url, data=json.dumps(data), content_type="application/json"
)
# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.status_code == status.HTTP_402_PAYMENT_REQUIRED
assert response.json()["code"] == "invalid-plan"
assert (
response.json()["detail"]
== "Please upgrade your plan to add additional seats/users"
== "Please upgrade your plan to add additional seats/users: https://www.flagsmith.com/pricing"
)


Expand Down Expand Up @@ -346,7 +358,7 @@ def test_update_invite__put_request__returns_405( # type: ignore[no-untyped-def
(lazy_fixture("invite_link"), "api-v1:users:user-join-organisation-link"),
],
)
def test_join_organisation__exceeds_plan_limit_saas__returns_400(
def test_join_organisation__exceeds_plan_limit_saas__returns_402(
staff_client: APIClient,
invite_object: Invite | InviteLink,
url: str,
Expand All @@ -359,10 +371,11 @@ def test_join_organisation__exceeds_plan_limit_saas__returns_400(
response = staff_client.post(url)

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.status_code == status.HTTP_402_PAYMENT_REQUIRED
assert response.json()["code"] == "invalid-plan"
assert (
response.json()["detail"]
== "Please upgrade your plan to add additional seats/users"
== "Please upgrade your plan to add additional seats/users: https://www.flagsmith.com/pricing"
)


Expand All @@ -374,7 +387,7 @@ def test_join_organisation__exceeds_plan_limit_saas__returns_400(
(lazy_fixture("invite_link"), "api-v1:users:user-join-organisation-link"),
],
)
def test_join_organisation__exceeds_plan_limit_self_hosted__returns_400(
def test_join_organisation__exceeds_plan_limit_self_hosted__returns_402(
staff_client: APIClient,
invite_object: Invite | InviteLink,
url: str,
Expand All @@ -391,10 +404,11 @@ def test_join_organisation__exceeds_plan_limit_self_hosted__returns_400(
response = staff_client.post(url)

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.status_code == status.HTTP_402_PAYMENT_REQUIRED
assert response.json()["code"] == "invalid-plan"
assert (
response.json()["detail"]
== "Please upgrade your plan to add additional seats/users"
== "Please upgrade your plan to add additional seats/users: https://www.flagsmith.com/pricing"
)


Expand Down
24 changes: 20 additions & 4 deletions api/tests/unit/projects/test_unit_projects_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,11 @@ def test_migrate_to_edge__too_many_identities__returns_400( # type: ignore[no-u

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["detail"] == "Too many identities; Please contact support"
assert response.json()["code"] == "system-limit"
assert (
response.json()["detail"]
== "Too many identities. Please contact support to adjust your limits: https://docs.flagsmith.com/support#getting-in-touch"
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
mocked_identity_migrator.assert_not_called()


Expand All @@ -611,7 +615,11 @@ def test_migrate_to_edge__too_many_features__returns_400( # type: ignore[no-unt

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["detail"] == "Project is too large; Please contact support"
assert response.json()["code"] == "system-limit"
assert (
response.json()["detail"]
== "Project is too large. Please contact support to adjust your limits: https://docs.flagsmith.com/support#getting-in-touch"
)
mocked_identity_migrator.assert_not_called()


Expand Down Expand Up @@ -639,7 +647,11 @@ def test_migrate_to_edge__too_many_segments__returns_400( # type: ignore[no-unt

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["detail"] == "Project is too large; Please contact support"
assert response.json()["code"] == "system-limit"
assert (
response.json()["detail"]
== "Project is too large. Please contact support to adjust your limits: https://docs.flagsmith.com/support#getting-in-touch"
)
mocked_identity_migrator.assert_not_called()


Expand Down Expand Up @@ -672,7 +684,11 @@ def test_migrate_to_edge__too_many_segment_overrides__returns_400( # type: igno

# Then
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["detail"] == "Project is too large; Please contact support"
assert response.json()["code"] == "system-limit"
assert (
response.json()["detail"]
== "Project is too large. Please contact support to adjust your limits: https://docs.flagsmith.com/support#getting-in-touch"
)
mocked_identity_migrator.assert_not_called()


Expand Down
Loading