Skip to content

Commit

Permalink
bypass consumption
Browse files Browse the repository at this point in the history
  • Loading branch information
jefer94 committed Oct 4, 2024
1 parent 3802784 commit ea3f00a
Show file tree
Hide file tree
Showing 13 changed files with 732 additions and 335 deletions.
1 change: 1 addition & 0 deletions .flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BYPASS_CONSUMPTION=0
638 changes: 328 additions & 310 deletions Pipfile.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def setUp(self):
# the behavior of permissions is not exact, this changes every time you add a model
self.latest_content_type_id = content_type.id
self.latest_permission_id = permission.id
self.job_content_type_id = self.latest_content_type_id - 63
self.can_delete_job_permission_id = self.latest_permission_id - 253
self.job_content_type_id = self.latest_content_type_id - 64
self.can_delete_job_permission_id = self.latest_permission_id - 257

"""
🔽🔽🔽 format of PERMISSIONS
Expand Down
6 changes: 2 additions & 4 deletions breathecode/media/tests/urls/v1/tests_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -791,9 +791,7 @@ def test_upload_invalid_format(self):
model = self.generate_models(authenticate=True, profile_academy=True, capability="crud_media", role="potato")
url = reverse_lazy("media:upload")

file = tempfile.NamedTemporaryFile(suffix=".txt", delete=False)
text = self.bc.fake.text()
file.write(text.encode("utf-8"))
file = open("breathecode/settings.py", "r")
file.close()

with open(file.name, "rb") as data:
Expand All @@ -807,7 +805,7 @@ def test_upload_invalid_format(self):
self.assertHash(hash)

expected = {
"detail": f'You can upload only files on the following formats: {",".join(MIME_ALLOWED)}, got text/plain',
"detail": f'You can upload only files on the following formats: {",".join(MIME_ALLOWED)}, got text/x-python',
"status_code": 400,
}

Expand Down
25 changes: 14 additions & 11 deletions breathecode/mentorship/permissions/consumers.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import logging

from capyc.core.managers import feature
from capyc.rest_framework.exceptions import PaymentException, ValidationException

from breathecode.admissions.actions import is_no_saas_student_up_to_date_in_any_cohort
from breathecode.authenticate.actions import get_user_language
from breathecode.authenticate.models import User
from breathecode.mentorship.models import MentorProfile, MentorshipService
from breathecode.payments.models import Consumable, ConsumptionSession
from breathecode.utils.decorators import ServiceContext
from breathecode.utils.i18n import translation
from capyc.rest_framework.exceptions import PaymentException, ValidationException

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -111,16 +113,17 @@ def mentorship_service_by_url_param(context: ServiceContext, args: tuple, kwargs
)
)
):

raise ValidationException(
translation(
lang,
en=f'Mentee do not have enough credits to access this service: {context["service"]}',
es="El mentee no tiene suficientes créditos para acceder a este servicio: " f'{context["service"]}',
),
slug="mentee-not-enough-consumables",
code=402,
)
c = feature.context(context=context, user=mentee)
if feature.is_enabled("payments.bypass_consumption", c, False) is False:
raise ValidationException(
translation(
lang,
en=f'Mentee do not have enough credits to access this service: {context["service"]}',
es="El mentee no tiene suficientes créditos para acceder a este servicio: " f'{context["service"]}',
),
slug="mentee-not-enough-consumables",
code=402,
)

if consumable:
session = ConsumptionSession.build_session(request, consumable, mentorship_service.max_duration, mentee)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import capyc.pytest as capy
import pytest
import timeago
from capyc.core.managers import feature
from django.core.handlers.wsgi import WSGIRequest
from django.template import loader
from django.test.client import FakePayload
Expand Down Expand Up @@ -3241,7 +3242,8 @@ def test_with_mentor_profile__redirect_to_session__no_saas(self):
),
)
@patch("django.utils.timezone.now", MagicMock(return_value=UTC_NOW))
def test_with_mentor_profile__redirect_to_session__saas(self):
@patch("capyc.core.managers.feature.is_enabled", MagicMock(return_value=False))
def test_with_mentor_profile__redirect_to_session__saas__bypass_consumption_false(self):
mentor_profile_cases = [
{
"status": x,
Expand Down Expand Up @@ -3322,12 +3324,231 @@ def test_with_mentor_profile__redirect_to_session__saas(self):
)
self.assertEqual(self.bc.database.list_of("payments.Consumable"), [])
self.assertEqual(self.bc.database.list_of("payments.ConsumptionSession"), [])
calls = [
call(
args[0],
{
**args[1],
"context": {
**args[1]["context"],
"request": type(args[1]["context"]["request"]),
"consumer": callable(args[1]["context"]["consumer"]),
"consumables": [x for x in args[1]["context"]["consumables"]],
},
},
*args[2:],
**kwargs,
)
for args, kwargs in feature.is_enabled.call_args_list
]
context1 = feature.context(
context={
"utc_now": UTC_NOW,
"consumer": True,
"service": "join_mentorship",
"request": WSGIRequest,
"consumables": [],
"lifetime": None,
"price": 0,
"is_consumption_session": False,
"flags": {"bypass_consumption": False},
},
)
context2 = feature.context(
context={
"utc_now": UTC_NOW,
"consumer": True,
"service": "join_mentorship",
"request": WSGIRequest,
"consumables": [],
"lifetime": None,
"price": 0,
"is_consumption_session": False,
"flags": {"bypass_consumption": False},
},
user=base.user,
)
assert calls == [
call("payments.bypass_consumption", context1, False),
call("payments.bypass_consumption", context2, False),
]

# teardown
self.bc.database.delete("mentorship.MentorProfile")

self.bc.database.delete("auth.Permission")
self.bc.database.delete("payments.Service")
feature.is_enabled.call_args_list = []

@patch("breathecode.mentorship.actions.mentor_is_ready", MagicMock())
@patch(
"os.getenv",
MagicMock(
side_effect=apply_get_env(
{
"DAILY_API_URL": URL,
"DAILY_API_KEY": API_KEY,
}
)
),
)
@patch(
"requests.request",
apply_requests_request_mock(
[
(
201,
f"{URL}/v1/rooms",
{
"name": ROOM_NAME,
"url": ROOM_URL,
},
)
]
),
)
@patch("django.utils.timezone.now", MagicMock(return_value=UTC_NOW))
@patch("capyc.core.managers.feature.is_enabled", MagicMock(side_effect=[False, True, False, True]))
def test_with_mentor_profile__redirect_to_session__saas__bypass_consumption_true(self):
mentor_profile_cases = [
{
"status": x,
"online_meeting_url": self.bc.fake.url(),
"booking_url": self.bc.fake.url(),
}
for x in ["ACTIVE", "UNLISTED"]
]

id = 0
for mentor_profile in mentor_profile_cases:
id += 1

user = {"first_name": "", "last_name": ""}
service = {"consumer": "JOIN_MENTORSHIP"}
base = self.bc.database.create(user=user, token=1, service=service)

ends_at = UTC_NOW - timedelta(seconds=3600 / 2 + 1)

academy = {"available_as_saas": True}
mentorship_session = {
"mentee_id": base.user.id,
"ends_at": ends_at,
"allow_mentee_to_extend": True,
}
token = 1

model = self.bc.database.create(
mentor_profile=mentor_profile,
mentorship_session=mentorship_session,
user=user,
token=token,
mentorship_service={"language": "en", "video_provider": "DAILY"},
service=base.service,
academy=academy,
)

model.mentorship_session.mentee = None
model.mentorship_session.save()

token = model.token if "token" in model else base.token

querystring = self.bc.format.to_querystring(
{
"token": token.key,
"extend": "true",
"mentee": base.user.id,
"session": model.mentorship_session.id,
}
)
url = (
reverse_lazy(
"mentorship_shortner:meet_slug_service_slug",
kwargs={"mentor_slug": model.mentor_profile.slug, "service_slug": model.mentorship_service.slug},
)
+ f"?{querystring}"
)
response = self.client.get(url)

content = self.bc.format.from_bytes(response.content)
expected = ""

# dump error in external files
if content != expected:
with open("content.html", "w") as f:
f.write(content)

with open("expected.html", "w") as f:
f.write(expected)

self.assertEqual(content, expected)
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
assert (
response.url
== f"/mentor/session/{model.mentorship_session.id}?token={token.key}&message=You%20have%20a%20session%20that%20expired%2030%20minutes%20ago.%20Only%20sessions%20with%20less%20than%2030min%20from%20expiration%20can%20be%20extended%20(if%20allowed%20by%20the%20academy)"
)
self.assertEqual(
self.bc.database.list_of("mentorship.MentorProfile"),
[
self.bc.format.to_dict(model.mentor_profile),
],
)
self.assertEqual(self.bc.database.list_of("payments.Consumable"), [])
self.assertEqual(self.bc.database.list_of("payments.ConsumptionSession"), [])
calls = [
call(
args[0],
{
**args[1],
"context": {
**args[1]["context"],
"request": type(args[1]["context"]["request"]),
"consumer": callable(args[1]["context"]["consumer"]),
"consumables": [x for x in args[1]["context"]["consumables"]],
},
},
*args[2:],
**kwargs,
)
for args, kwargs in feature.is_enabled.call_args_list
]
context1 = feature.context(
context={
"utc_now": UTC_NOW,
"consumer": True,
"service": "join_mentorship",
"request": WSGIRequest,
"consumables": [],
"lifetime": None,
"price": 0,
"is_consumption_session": False,
"flags": {"bypass_consumption": False},
},
)
context2 = feature.context(
context={
"utc_now": UTC_NOW,
"consumer": True,
"service": "join_mentorship",
"request": WSGIRequest,
"consumables": [],
"lifetime": None,
"price": 0,
"is_consumption_session": False,
"flags": {"bypass_consumption": False},
},
user=base.user,
)
assert calls == [
call("payments.bypass_consumption", context1, False),
call("payments.bypass_consumption", context2, False),
]

# teardown
self.bc.database.delete("mentorship.MentorProfile")

self.bc.database.delete("auth.Permission")
self.bc.database.delete("payments.Service")
feature.is_enabled.call_args_list = []

@patch("breathecode.mentorship.actions.mentor_is_ready", MagicMock())
@patch(
Expand Down
1 change: 1 addition & 0 deletions breathecode/payments/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ class PaymentsConfig(AppConfig):
name = "breathecode.payments"

def ready(self):
from . import flags # noqa: F401
from . import receivers # noqa: F401
from . import supervisors # noqa: F401
29 changes: 29 additions & 0 deletions breathecode/payments/flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import Optional

from capyc.core.managers import feature

from breathecode.authenticate.models import User
from breathecode.utils.decorators.consume import ServiceContext

flags = feature.flags


@feature.availability("payments.bypass_consumption")
def bypass_consumption(context: ServiceContext, user: Optional[User] = None) -> bool:
"""
This flag is used to bypass the consumption of a service.
Arguments:
context: ServiceContext - The context of the service.
user: Optional[User] - The user to bypass the consumption for, if none it will use request.user.
"""

if flags.get("BYPASS_CONSUMPTION") not in feature.TRUE:
return False

# write logic here

return False


feature.add(bypass_consumption)
3 changes: 3 additions & 0 deletions breathecode/registry/tests/urls/v1/tests_academy_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
def database_item(academy, category, data={}):
return {
"academy_id": academy.id,
"learnpack_deploy_url": None,
"agent": None,
"assessment_id": None,
"asset_type": "PROJECT",
"author_id": None,
Expand Down Expand Up @@ -88,6 +90,7 @@ def post_serializer(academy, category, data={}):
"slug": category.slug,
"title": category.title,
},
"agent": None,
"delivery_formats": "url",
"delivery_instructions": None,
"delivery_regex_url": None,
Expand Down
2 changes: 1 addition & 1 deletion breathecode/registry/tests/urls/v1/tests_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def get_serializer_technology(technology, data={}):
def get_mid_serializer(asset, data={}):
return {
**get_serializer(asset),
"agent": None,
"with_solutions": asset.with_solutions,
"with_video": asset.with_solutions,
"updated_at": asset.updated_at,
Expand Down Expand Up @@ -260,7 +261,6 @@ def test_assets_expand_readme_ipynb(bc: Breathecode, client):
json = response.json()

asset_readme = model.asset.get_readme()
print(asset_readme)

expected = [
get_mid_serializer(
Expand Down
Loading

0 comments on commit ea3f00a

Please sign in to comment.