Skip to content

Commit

Permalink
Merge pull request #3684 from freelawproject/3683-fix-handle-missing-…
Browse files Browse the repository at this point in the history
…stateProvince-field-in-address

fix(donate): Tweaks the Webhook ViewSet to store payload before processing it
  • Loading branch information
mlissner authored Jan 29, 2024
2 parents c921dc3 + 46da3c3 commit e3baafc
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 13 deletions.
24 changes: 14 additions & 10 deletions cl/donate/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class MembershipWebhookViewSet(
serializer_class = NeonMembershipWebhookSerializer
queryset = NeonMembership.objects.all()

@transaction.atomic
def create(self, request: Request, *args, **kwargs):
"""
Processes membership webhooks received from Neon CRM.
Expand All @@ -49,14 +48,19 @@ def create(self, request: Request, *args, **kwargs):

webhook_data = serializer.validated_data
self._store_webhook_payload(webhook_data)
match webhook_data["eventTrigger"]:
case "createMembership" | "editMembership" | "updateMembership":
self._handle_membership_creation_or_update(webhook_data)
case "deleteMembership":
self._handle_membership_deletion(webhook_data)
case _:
trigger = webhook_data["eventTrigger"]
raise NotImplementedError(f"Unknown event trigger-{trigger}")
with transaction.atomic():
match webhook_data["eventTrigger"]:
case (
"createMembership" | "editMembership" | "updateMembership"
):
self._handle_membership_creation_or_update(webhook_data)
case "deleteMembership":
self._handle_membership_deletion(webhook_data)
case _:
trigger = webhook_data["eventTrigger"]
raise NotImplementedError(
f"Unknown event trigger-{trigger}"
)

return Response(status=HTTPStatus.CREATED)

Expand All @@ -81,7 +85,7 @@ def _get_address_from_neon_response(self, addresses: list[dict[str, str]]):
"address2": addresses[0]["addressLine2"],
"city": addresses[0]["city"],
"state": addresses[0]["stateProvince"]["code"],
"zip_code": addresses["zipCode"],
"zip_code": addresses[0]["zipCode"],
"wants_newsletter": False,
}

Expand Down
88 changes: 85 additions & 3 deletions cl/donate/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
UserProfileWithParentsFactory,
)
from cl.tests.cases import TestCase
from cl.users.models import UserProfile

stripe_test_numbers = {
"good": {"visa": "4242424242424242"},
Expand Down Expand Up @@ -336,7 +337,12 @@ def setUp(self) -> None:
}

@override_settings(NEON_MAX_WEBHOOK_NUMBER=10)
def test_store_and_truncate_webhook_data(self) -> None:
@patch(
"cl.donate.api_views.MembershipWebhookViewSet._handle_membership_creation_or_update",
)
def test_store_and_truncate_webhook_data(
self, mock_membership_creation
) -> None:
self.data["eventTrigger"] = "createMembership"
client = Client()
r = client.post(
Expand All @@ -347,13 +353,28 @@ def test_store_and_truncate_webhook_data(self) -> None:
self.assertEqual(r.status_code, HTTP_201_CREATED)
self.assertEqual(NeonWebhookEvent.objects.all().count(), 1)

NeonWebhookEventFactory.create_batch(18)
# Make sure to save the webhook payload even if an error occurs.
mock_membership_creation.side_effect = Exception()
self.data["data"]["membership"]["accountId"] = "9999"
with self.assertRaises(Exception):
client.post(
reverse("membership-webhooks-list", kwargs={"version": "v3"}),
data=self.data,
content_type="application/json",
)
failed_log_query = NeonWebhookEvent.objects.filter(account_id="9999")
self.assertEqual(failed_log_query.count(), 1)
self.assertEqual(NeonWebhookEvent.objects.all().count(), 2)
profile_query = UserProfile.objects.filter(neon_account_id="9999")
self.assertEqual(profile_query.count(), 0)

NeonWebhookEventFactory.create_batch(17)

# Update the trigger type and Adds a new webhook to the log. After
# adding this new record the post_save signal should truncate the
# events table and keep the latest NEON_MAX_WEBHOOK_NUMBER records
self.data["eventTrigger"] = "editMembership"
client = Client()
mock_membership_creation.side_effect = None
r = client.post(
reverse("membership-webhooks-list", kwargs={"version": "v3"}),
data=self.data,
Expand Down Expand Up @@ -523,3 +544,64 @@ async def test_create_stub_account_missing_address(

query = NeonMembership.objects.filter(neon_id="12345")
self.assertEqual(await query.acount(), 1)

@patch(
"cl.lib.neon_utils.NeonClient.get_acount_by_id",
)
@patch.object(
MembershipWebhookViewSet, "_store_webhook_payload", return_value=None
)
async def test_can_create_stub_account_properly(
self, mock_store_webhook, mock_get_account
):
self.data["eventTrigger"] = "createMembership"
self.data["data"]["membership"]["accountId"] = "9524"

# mocks the Neon API response
mock_get_account.return_value = {
"accountId": "9524",
"primaryContact": {
"email1": "test@free.law",
"firstName": "test",
"lastName": "test",
"addresses": [
{
"addressId": "91449",
"addressLine1": "Suite 338 886 Hugh Shoal",
"addressLine2": "",
"addressLine3": None,
"addressLine4": None,
"city": "New Louveniamouth",
"stateProvince": {
"code": "WA",
"name": "Washington",
"status": None,
},
"country": {
"id": "1",
"name": "United States of America",
"status": None,
},
"territory": None,
"zipCode": "30716",
}
],
},
}

r = await self.async_client.post(
reverse("membership-webhooks-list", kwargs={"version": "v3"}),
data=self.data,
content_type="application/json",
)

self.assertEqual(r.status_code, HTTP_201_CREATED)

query = NeonMembership.objects.select_related(
"user", "user__profile"
).filter(neon_id="12345")
self.assertEqual(await query.acount(), 1)

membership = await query.afirst()
self.assertEqual(membership.user.email, "test@free.law")
self.assertEqual(membership.user.profile.neon_account_id, "9524")

0 comments on commit e3baafc

Please sign in to comment.