Skip to content

Commit

Permalink
fix hook manager timedelta serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
jefer94 committed Jul 1, 2024
1 parent b2d2e55 commit 8bd8cef
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 4 deletions.
10 changes: 8 additions & 2 deletions breathecode/notify/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ def parse_payload(payload: dict):

logger.info('Starting async_deliver_hook')

has_response = False

try:
if isinstance(payload, dict):
payload = parse_payload(payload)
Expand All @@ -155,6 +157,7 @@ def parse_payload(payload: dict):
data=encoded_payload,
headers={'Content-Type': 'application/json'},
timeout=2)
has_response = True

if hook_id:
hook_model_cls = HookManager.get_hook_model()
Expand Down Expand Up @@ -182,6 +185,9 @@ def parse_payload(payload: dict):
hook.total_calls = hook.total_calls + 1
hook.save()

except Exception:
except Exception as e:
logger.error(payload)
raise AbortTask(f'Error while trying to save hook call with status code {response.status_code}. {payload}')
if has_response:
raise AbortTask(f'Error while trying to save hook call with status code {response.status_code}. {payload}')

raise e
Empty file.
80 changes: 80 additions & 0 deletions breathecode/notify/tests/utils/tests_hook_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from datetime import timedelta
from unittest.mock import MagicMock, call

import pytest

from breathecode.notify import tasks
from breathecode.notify.utils.hook_manager import HookManager
from breathecode.tests.mixins.breathecode_mixin.breathecode import Breathecode


@pytest.fixture(autouse=True)
def mocks(db, monkeypatch):
m1 = MagicMock()
monkeypatch.setattr(tasks.async_deliver_hook, 'delay', m1)
yield m1


def get_model_label(instance):
if instance is None:
return None
opts = instance._meta.concrete_model._meta
try:
return opts.label
except AttributeError:
return '.'.join([opts.app_label, opts.object_name])


def test_transform_timedeltas(bc: Breathecode, enable_signals, enable_hook_manager, mocks):
enable_hook_manager()
mock = mocks

model = bc.database.create(hook={'event': 'survey.created'},
user={
'username': 'test',
'is_superuser': True
},
academy={
'slug': 'test',
'available_as_saas': True,
},
survey=1)

x = {'feedback.Survey': {'created': ('survey.created', False)}}
HookManager.HOOK_EVENTS = {'survey.created': 'feedback.Survey.created+'}
# HookManager._HOOK_EVENT_ACTIONS_CONFIG = x
HookManager._HOOK_EVENT_ACTIONS_CONFIG = None

HookManager.process_model_event(
model.survey,
get_model_label(model.survey.__class__),
'created',
payload_override={
'delta': timedelta(days=3),
'children': [
{
'delta': timedelta(days=4)
},
{
'delta': timedelta(days=5)
},
]
},
)

assert bc.database.list_of('feedback.Survey') == [bc.format.to_dict(model.survey)]

assert mock.call_args_list == [
call(model.hook.target, {
'delta': 259200.0,
'children': [
{
'delta': 345600.0
},
{
'delta': 432000.0
},
]
},
hook_id=1),
]
26 changes: 26 additions & 0 deletions breathecode/notify/utils/hook_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from datetime import timedelta

from django.apps import apps as django_apps
from django.conf import settings
Expand Down Expand Up @@ -41,6 +42,7 @@ def get_event_actions_config(self):
event_name,
ignore_user_override,
)

return self._HOOK_EVENT_ACTIONS_CONFIG

def get_module(self, path):
Expand Down Expand Up @@ -194,6 +196,27 @@ def process_model_event(
payload_override=payload_override,
academy_override=academy_override)

def serialize(self, payload: dict | list | tuple) -> dict | list | tuple:

if isinstance(payload, (list, tuple)):
for item in payload:
self.serialize(item)

return payload

for key in payload:
if isinstance(payload[key], dict):
self.serialize(payload[key])

elif isinstance(payload[key], (list, tuple)):
for item in payload[key]:
self.serialize(item)

elif isinstance(payload[key], timedelta):
payload[key] = payload[key].total_seconds()

return payload

def deliver_hook(self, hook, instance, payload_override=None, academy_override=None):
"""
Deliver the payload to the target URL.
Expand All @@ -214,6 +237,9 @@ def deliver_hook(self, hook, instance, payload_override=None, academy_override=N
payload = payload(hook, instance)

logger.debug(f'Calling delayed task deliver_hook for hook {hook.id}')

self.serialize(payload)

async_deliver_hook.delay(hook.target, payload, hook_id=hook.id)

return None
Expand Down
81 changes: 81 additions & 0 deletions breathecode/payments/tests/signals/tests_subscription_created.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from datetime import datetime
from unittest.mock import MagicMock, call

import pytest

from breathecode.notify import tasks
from breathecode.tests.mixins.breathecode_mixin.breathecode import Breathecode


@pytest.fixture(autouse=True)
def mocks(db, monkeypatch):
m1 = MagicMock()
monkeypatch.setattr(tasks.async_deliver_hook, 'delay', m1)
yield m1


@pytest.fixture(autouse=True)
def base(db, bc: Breathecode):
model = bc.database.create(hook={'event': 'subscription.subscription_created'},
user={'username': 'test'},
academy={
'slug': 'test',
'available_as_saas': True,
})
yield model


def serializer(subscription, user=None, academy=None):
academy_obj = None
if academy:
academy_obj = {
'id': academy.id,
'name': academy.name,
'slug': academy.slug,
}

user_obj = None
if user:
user_obj = {
'first_name': user.first_name,
'last_name': user.last_name,
'email': user.email,
}

return {
'id': subscription.id,
'status': subscription.status,
'status_message': subscription.status_message,
'user': user_obj,
'academy': academy_obj,
'selected_cohort_set': subscription.selected_cohort_set,
'selected_mentorship_service_set': subscription.selected_mentorship_service_set,
'selected_event_type_set': subscription.selected_event_type_set,
'plans': [],
'invoices': [],
'next_payment_at': subscription.next_payment_at,
'valid_until': subscription.valid_until,
'paid_at': subscription.paid_at,
'is_refundable': subscription.is_refundable,
'pay_every': subscription.pay_every,
'pay_every_unit': subscription.pay_every_unit,
}


def test_nothing_happens(bc: Breathecode, enable_signals, enable_hook_manager, mocks, base):
enable_signals('breathecode.payments.signals.subscription_created')
enable_hook_manager()

mock = mocks
model = bc.database.create(
subscription=2,
user=base.user,
academy=base.academy,
)

assert bc.database.list_of('payments.subscription') == bc.format.to_dict(model.subscription)

assert mock.call_args_list == [
call(base.hook.target, serializer(model.subscription[0], user=model.user, academy=model.academy), hook_id=1),
call(base.hook.target, serializer(model.subscription[1], user=model.user, academy=model.academy), hook_id=1),
]
3 changes: 2 additions & 1 deletion breathecode/tests/mixins/generate_models_mixin/auth_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def generate_credentials(self,
app_user_agreement=False,
first_party_credentials=False,
task_watcher=False,
hook=False,
profile_academy='',
user_kwargs={},
group_kwargs={},
Expand All @@ -58,7 +59,7 @@ def generate_credentials(self,
or is_valid(invoice) or is_valid(subscription) or is_valid(bag)
or is_valid(user_setting) or is_valid(consumption_session)
or is_valid(provisioning_container) or is_valid(app_user_agreement)
or is_valid(first_party_credentials) or is_valid(task_watcher)):
or is_valid(first_party_credentials) or is_valid(task_watcher) or is_valid(hook)):
kargs = {}

if 'group' in models:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
Collections of mixins used to login in authorize microservice
"""
from breathecode.tests.mixins.models_mixin import ModelsMixin
from .utils import is_valid, create_models, just_one

from .utils import create_models, is_valid, just_one


class NotifyModelsMixin(ModelsMixin):
Expand Down Expand Up @@ -82,6 +83,9 @@ def generate_notify_models(self,
if not 'hook' in models and is_valid(hook):
kargs = {}

if 'user' in models:
kargs['user'] = just_one(models['user'])

models['hook'] = create_models(hook, 'notify.Hook', **kargs)

return models

0 comments on commit 8bd8cef

Please sign in to comment.