From 61c8082e980d4848f44f0e12095ecb2d21f22814 Mon Sep 17 00:00:00 2001 From: Andrii Date: Thu, 30 May 2024 15:43:51 +0300 Subject: [PATCH] test: [ACI-994] increase test coverage --- .../apps/badges/tests/test_admin_forms.py | 193 +++++++++++++++++- .../apps/badges/tests/test_api_client.py | 136 ++++++++++++ credentials/apps/badges/tests/test_issuers.py | 63 +++++- .../apps/badges/tests/test_services.py | 94 ++++++++- credentials/apps/badges/tests/test_utils.py | 9 + .../apps/badges/tests/test_webhooks.py | 128 ++++++++++++ 6 files changed, 616 insertions(+), 7 deletions(-) create mode 100644 credentials/apps/badges/tests/test_api_client.py create mode 100644 credentials/apps/badges/tests/test_webhooks.py diff --git a/credentials/apps/badges/tests/test_admin_forms.py b/credentials/apps/badges/tests/test_admin_forms.py index db5152331e..2c5fede194 100644 --- a/credentials/apps/badges/tests/test_admin_forms.py +++ b/credentials/apps/badges/tests/test_admin_forms.py @@ -1,11 +1,17 @@ import uuid +from unittest.mock import MagicMock, patch from django import forms from django.contrib.sites.models import Site -from django.test import TestCase -from django.utils.translation import gettext as _ +from django.test import TestCase, override_settings -from credentials.apps.badges.admin_forms import BadgePenaltyForm +from credentials.apps.badges.admin_forms import ( + BadgePenaltyForm, + CredlyOrganizationAdminForm, + DataRuleExtensionsMixin, + ParentMixin, +) +from credentials.apps.badges.credly.exceptions import CredlyAPIError from credentials.apps.badges.models import BadgeRequirement, BadgeTemplate @@ -70,4 +76,183 @@ def test_clean_requirements_different_template(self): with self.assertRaises(forms.ValidationError) as cm: form.clean() - self.assertEqual(str(cm.exception), "['All requirements must belong to the same template.']") + self.assertEqual( + str(cm.exception), "['All requirements must belong to the same template.']" + ) + + @override_settings(BADGES_CONFIG={"credly": {"ORGANIZATIONS": {}}}) + def test_clean(self): + form = CredlyOrganizationAdminForm() + form.cleaned_data = { + "uuid": "test_uuid", + "api_key": "test_api_key", + } + + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_orgs: + mock_get_orgs.return_value = {} + + with patch( + "credentials.apps.badges.admin_forms.CredlyAPIClient" + ) as mock_client: + mock_client.return_value = MagicMock() + + form.clean() + + mock_get_orgs.assert_called_once() + mock_client.assert_called_once_with("test_uuid", "test_api_key") + + @override_settings( + BADGES_CONFIG={"credly": {"ORGANIZATIONS": {"test_uuid": "test_api_key"}}} + ) + def test_clean_with_configured_organization(self): + form = CredlyOrganizationAdminForm() + form.cleaned_data = { + "uuid": "test_uuid", + "api_key": None, + } + + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_orgs: + mock_get_orgs.return_value = {"test_uuid": "test_org"} + + with patch( + "credentials.apps.badges.admin_forms.CredlyAPIClient" + ) as mock_client: + mock_client.return_value = MagicMock() + + form.clean() + + mock_get_orgs.assert_called_once() + mock_client.assert_called_once_with("test_uuid", "test_api_key") + + def test_clean_with_invalid_organization(self): + form = CredlyOrganizationAdminForm() + form.cleaned_data = { + "uuid": "invalid_uuid", + "api_key": "test_api_key", + } + + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_orgs: + mock_get_orgs.return_value = {"test_uuid": "test_org"} + + with self.assertRaises(forms.ValidationError) as cm: + form.clean() + + self.assertIn( + "You specified an invalid authorization token.", str(cm.exception) + ) + + def test_clean_cannot_provide_api_key_for_configured_organization(self): + form = CredlyOrganizationAdminForm() + form.cleaned_data = { + "uuid": "test_uuid", + "api_key": "test_api_key", + } + + with patch( + "credentials.apps.badges.models.CredlyOrganization.get_preconfigured_organizations" + ) as mock_get_orgs: + mock_get_orgs.return_value = {"test_uuid": "test_org"} + + with self.assertRaises(forms.ValidationError) as cm: + form.clean() + + self.assertEqual( + str(cm.exception), + '["You can\'t provide an API key for a configured organization."]', + ) + + def test_ensure_organization_exists(self): + form = CredlyOrganizationAdminForm() + api_client = MagicMock() + api_client.fetch_organization.return_value = {"data": {"org_id": "test_org_id"}} + + form._ensure_organization_exists(api_client) + + api_client.fetch_organization.assert_called_once() + self.assertEqual(form.api_data, {"org_id": "test_org_id"}) + + def test_ensure_organization_exists_with_error(self): + form = CredlyOrganizationAdminForm() + api_client = MagicMock() + api_client.fetch_organization.side_effect = CredlyAPIError("API Error") + + with self.assertRaises(forms.ValidationError) as cm: + form._ensure_organization_exists(api_client) + + api_client.fetch_organization.assert_called_once() + self.assertEqual(str(cm.exception), "['API Error']") + + +class TestParentMixin(ParentMixin): + def get_form_kwargs(self, index): + return super().get_form_kwargs(index) + + +class ParentMixinTestCase(TestCase): + def setUp(self): + self.instance = MagicMock() + self.instance.some_attribute = "some_value" + + self.mixin = TestParentMixin() + self.mixin.instance = self.instance + + def test_get_form_kwargs_passes_parent_instance(self): + with patch.object( + TestParentMixin, + "get_form_kwargs", + return_value={"parent_instance": self.instance}, + ) as super_method: + result = self.mixin.get_form_kwargs(0) + + super_method.assert_called_once_with(0) + + self.assertIn("parent_instance", result) + self.assertEqual(result["parent_instance"], self.instance) + + +class TestForm(DataRuleExtensionsMixin, forms.Form): + data_path = forms.ChoiceField(choices=[]) + value = forms.CharField() + + +class DataRuleExtensionsMixinTestCase(TestCase): + def setUp(self): + self.parent_instance = MagicMock() + self.parent_instance.event_type = COURSE_PASSING_EVENT + + def test_init_sets_choices_based_on_event_type(self): + form = TestForm(parent_instance=self.parent_instance) + self.assertEqual( + form.fields["data_path"].choices, + [("is_passing", "is_passing"), ("course.course_key", "course.course_key")], + ) + + def test_clean_with_valid_boolean_value(self): + form = TestForm( + data={"data_path": "is_passing", "value": "True"}, + parent_instance=self.parent_instance, + ) + form.is_valid() + self.assertRaises(KeyError, lambda: form.errors["__all__"]) + + def test_clean_with_invalid_boolean_value(self): + form = TestForm( + data={"data_path": "is_passing", "value": "invalid"}, + parent_instance=self.parent_instance, + ) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors["__all__"], ["Value must be a boolean."]) + + def test_clean_with_non_boolean_data_path(self): + form = TestForm( + data={"data_path": "course.course_key", "value": "some_value"}, + parent_instance=self.parent_instance, + ) + form.is_valid() + self.assertRaises(KeyError, lambda: form.errors["__all__"]) diff --git a/credentials/apps/badges/tests/test_api_client.py b/credentials/apps/badges/tests/test_api_client.py new file mode 100644 index 0000000000..3da2806ea5 --- /dev/null +++ b/credentials/apps/badges/tests/test_api_client.py @@ -0,0 +1,136 @@ +from unittest import mock + +from attrs import asdict +from django.test import TestCase +from faker import Faker +from openedx_events.learning.data import BadgeData, BadgeTemplateData, UserData, UserPersonalData +from requests.models import Response + +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.credly.exceptions import CredlyAPIError, CredlyError +from credentials.apps.badges.models import CredlyOrganization + + +class CredlyApiClientTestCase(TestCase): + def setUp(self): + fake = Faker() + self.api_client = CredlyAPIClient("test_organization_id", "test_api_key") + self.badge_data = BadgeData( + uuid=fake.uuid4(), + user=UserData( + id=1, + is_active=True, + pii=UserPersonalData( + username="test_user", email="test_email@mail.com", name="Test User" + ), + ), + template=BadgeTemplateData( + uuid=fake.uuid4(), + name="Test Badge", + origin="Credly", + description="Test Badge Description", + image_url="https://test.com/image.png", + ), + ) + + def test_get_organization_nonexistent(self): + with mock.patch( + "credentials.apps.badges.credly.api_client.CredlyOrganization.objects.get" + ) as mock_get: + mock_get.side_effect = CredlyOrganization.DoesNotExist + with self.assertRaises(CredlyError) as cm: + self.api_client._get_organization("nonexistent_organization_id") + self.assertEqual( + str(cm.exception), + "CredlyOrganization with the uuid nonexistent_organization_id does not exist!", + ) + + def test_perform_request(self): + with mock.patch( + "credentials.apps.badges.credly.api_client.requests.request" + ) as mock_request: + mock_response = mock.Mock() + mock_response.json.return_value = {"key": "value"} + mock_request.return_value = mock_response + result = self.api_client.perform_request("GET", "/api/endpoint") + mock_request.assert_called_once_with( + "GET", + "https://sandbox-api.credly.com/api/endpoint", + headers=self.api_client._get_headers(), + json=None, + ) + self.assertEqual(result, {"key": "value"}) + + def test_raise_for_error_success(self): + response = mock.Mock(spec=Response) + response.status_code = 200 + self.api_client._raise_for_error(response) + + def test_raise_for_error_error(self): + response = mock.Mock(spec=Response) + response.status_code = 404 + response.text = "Not Found" + response.raise_for_status.side_effect = CredlyAPIError( + f"Credly API: {response.text} ({response.status_code})" + ) + + with self.assertRaises(CredlyAPIError) as cm: + self.api_client._raise_for_error(response) + self.assertEqual(str(cm.exception), "Credly API: Not Found (404)") + + def test_fetch_organization(self): + with mock.patch.object( + CredlyAPIClient, "perform_request" + ) as mock_perform_request: + mock_perform_request.return_value = {"organization": "data"} + result = self.api_client.fetch_organization() + mock_perform_request.assert_called_once_with("get", "") + self.assertEqual(result, {"organization": "data"}) + + def test_fetch_badge_templates(self): + with mock.patch.object( + CredlyAPIClient, "perform_request" + ) as mock_perform_request: + mock_perform_request.return_value = { + "badge_templates": ["template1", "template2"] + } + result = self.api_client.fetch_badge_templates() + mock_perform_request.assert_called_once_with( + "get", "badge_templates/?filter=state::active" + ) + self.assertEqual(result, {"badge_templates": ["template1", "template2"]}) + + def test_fetch_event_information(self): + event_id = "event123" + with mock.patch.object( + CredlyAPIClient, "perform_request" + ) as mock_perform_request: + mock_perform_request.return_value = {"event": "data"} + result = self.api_client.fetch_event_information(event_id) + mock_perform_request.assert_called_once_with("get", f"events/{event_id}/") + self.assertEqual(result, {"event": "data"}) + + def test_issue_badge(self): + issue_badge_data = self.badge_data + with mock.patch.object( + CredlyAPIClient, "perform_request" + ) as mock_perform_request: + mock_perform_request.return_value = {"badge": "issued"} + result = self.api_client.issue_badge(issue_badge_data) + mock_perform_request.assert_called_once_with( + "post", "badges/", asdict(issue_badge_data) + ) + self.assertEqual(result, {"badge": "issued"}) + + def test_revoke_badge(self): + badge_id = "badge123" + data = {"data": "value"} + with mock.patch.object( + CredlyAPIClient, "perform_request" + ) as mock_perform_request: + mock_perform_request.return_value = {"badge": "revoked"} + result = self.api_client.revoke_badge(badge_id, data) + mock_perform_request.assert_called_once_with( + "put", f"badges/{badge_id}/revoke/", data=data + ) + self.assertEqual(result, {"badge": "revoked"}) diff --git a/credentials/apps/badges/tests/test_issuers.py b/credentials/apps/badges/tests/test_issuers.py index 07a7dae208..77c583869e 100644 --- a/credentials/apps/badges/tests/test_issuers.py +++ b/credentials/apps/badges/tests/test_issuers.py @@ -1,13 +1,19 @@ from unittest import mock +from unittest.mock import patch import faker +from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.test import TestCase +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.credly.exceptions import CredlyAPIError +from credentials.apps.badges.issuers import CredlyBadgeTemplateIssuer +from credentials.apps.badges.models import CredlyBadge, CredlyBadgeTemplate, CredlyOrganization from credentials.apps.credentials.constants import UserCredentialStatus -from ..issuers import CredlyBadgeTemplateIssuer -from ..models import CredlyBadge, CredlyBadgeTemplate, CredlyOrganization + +User = get_user_model() class CredlyBadgeTemplateIssuer(TestCase): @@ -29,7 +35,12 @@ def setUp(self): state="active", organization=credly_organization, ) + User.objects.create_user(username="test_user", email="test_email@fff.com", password="test_password") + def _perform_request(self, method, endpoint, data=None): + fake = faker.Faker() + return {"data": {"id": fake.uuid4(), "state": "issued"}} + def test_create_user_credential_with_status_awared(self): # Call create_user_credential with valid arguments with mock.patch("credentials.apps.badges.issuers.notify_badge_awarded") as mock_notify_badge_awarded: @@ -76,3 +87,51 @@ def test_create_user_credential_with_status_revoked(self): status=UserCredentialStatus.REVOKED, ).exists() ) + + @patch.object(CredlyAPIClient, 'perform_request', _perform_request) + def test_issue_credly_badge(self): + # Create a test user credential + user_credential = self.issued_user_credential_type.objects.create( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + state=CredlyBadge.STATES.pending, + uuid=self.fake.uuid4(), + external_uuid=self.fake.uuid4(), + ) + + # Call the issue_credly_badge method + self.issuer().issue_credly_badge(user_credential=user_credential) + + # Check if the user credential is updated with the external UUID and state + self.assertIsNotNone(user_credential.external_uuid) + self.assertEqual(user_credential.state, "issued") + + # Check if the user credential is saved + user_credential.refresh_from_db() + self.assertIsNotNone(user_credential.external_uuid) + self.assertEqual(user_credential.state, "issued") + + def test_issue_credly_badge_with_error(self): + # Create a test user credential + user_credential = self.issued_user_credential_type.objects.create( + username="test_user", + credential_content_type=ContentType.objects.get_for_model(self.badge_template), + credential_id=self.badge_template.id, + state=CredlyBadge.STATES.pending, + uuid=self.fake.uuid4(), + external_uuid=self.fake.uuid4(), + ) + + # Mock the CredlyAPIClient and its issue_badge method to raise CredlyAPIError + with mock.patch("credentials.apps.badges.credly.api_client.CredlyAPIClient") as mock_credly_api_client: + mock_issue_badge = mock_credly_api_client.return_value.issue_badge + mock_issue_badge.side_effect = CredlyAPIError + + # Call the issue_credly_badge method and expect CredlyAPIError to be raised + with self.assertRaises(CredlyAPIError): + self.issuer().issue_credly_badge(user_credential=user_credential) + + # Check if the user credential state is updated to "error" + user_credential.refresh_from_db() + self.assertEqual(user_credential.state, "error") \ No newline at end of file diff --git a/credentials/apps/badges/tests/test_services.py b/credentials/apps/badges/tests/test_services.py index ae5e9dfbc1..2c3271e9b1 100644 --- a/credentials/apps/badges/tests/test_services.py +++ b/credentials/apps/badges/tests/test_services.py @@ -1,4 +1,5 @@ import uuid +from unittest.mock import MagicMock, patch from django.contrib.auth import get_user_model from django.contrib.sites.models import Site @@ -6,6 +7,7 @@ from opaque_keys.edx.keys import CourseKey from openedx_events.learning.data import CourseData, CoursePassingStatusData, UserData, UserPersonalData +from credentials.apps.badges.exceptions import BadgesProcessingError from credentials.apps.badges.models import ( BadgePenalty, BadgeProgress, @@ -17,7 +19,7 @@ Fulfillment, PenaltyDataRule, ) -from credentials.apps.badges.processing.generic import identify_user +from credentials.apps.badges.processing.generic import identify_user, process_event from credentials.apps.badges.processing.progression import discover_requirements, process_requirements from credentials.apps.badges.processing.regression import discover_penalties, process_penalties from credentials.apps.badges.signals import BADGE_PROGRESS_COMPLETE @@ -631,3 +633,93 @@ class TestIdentifyUser(TestCase): def test_identify_user(self): username = identify_user(event_type=COURSE_PASSING_EVENT, event_payload=COURSE_PASSING_DATA) self.assertEqual(username, "test_username") + + def test_identify_user_not_found(self): + event_type = "unknown_event_type" + event_payload = None + + with self.assertRaises(BadgesProcessingError) as cm: + identify_user(event_type="unknown_event_type", event_payload=event_payload) + + self.assertEqual( + str(cm.exception), + f"User data cannot be found (got: None): {event_payload}. Does event {event_type} include user data at all?" + ) + + +def mock_progress_regress(*args, **kwargs): + return None + + +class TestProcessEvent(TestCase): + def setUp(self): + self.organization = CredlyOrganization.objects.create( + uuid=uuid.uuid4(), api_key="test_api_key", name="test_organization" + ) + self.site = Site.objects.create(domain="test_domain", name="test_name") + self.badge_template = CredlyBadgeTemplate.objects.create( + uuid=uuid.uuid4(), + name="test_template", + state="draft", + site=self.site, + organization=self.organization, + is_active=True, + ) + DataRule.objects.create( + requirement=BadgeRequirement.objects.create( + template=self.badge_template, event_type=COURSE_PASSING_EVENT + ), + data_path="is_passing", + operator="eq", + value="True" + ) + PenaltyDataRule.objects.create( + penalty=BadgePenalty.objects.create(template=self.badge_template, event_type=COURSE_PASSING_EVENT), + data_path="is_passing", + operator="eq", + value="False" + ) + self.sender = MagicMock() + self.sender.event_type = COURSE_PASSING_EVENT + + @patch.object(BadgeProgress, "progress", mock_progress_regress) + def test_process_event_passing(self): + event_payload = COURSE_PASSING_DATA + process_event(sender=self.sender, kwargs=event_payload) + self.assertTrue(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + def test_process_event_not_passing(self): + event_payload = CoursePassingStatusData( + is_passing=False, + course=CourseData(course_key=CourseKey.from_string("course-v1:edX+DemoX.1+2014"), display_name="A"), + user=UserData( + id=1, + is_active=True, + pii=UserPersonalData(username="test_username", email="test_email", name="John Doe"), + ), + ) + process_event(sender=self.sender, kwargs=event_payload) + self.assertFalse(BadgeProgress.for_user(username="test_username", template_id=self.badge_template.id).completed) + + @patch.object(BadgeProgress, "regress", mock_progress_regress) + def test_process_event_not_found(self): + sender = MagicMock() + sender.event_type = "unknown_event_type" + event_payload = None + + with patch("credentials.apps.badges.processing.generic.logger.error") as mock_event_not_found: + process_event(sender=sender, kwargs=event_payload) + mock_event_not_found.assert_called_once() + + + def test_process_event_no_user_data(self): + event_payload = CoursePassingStatusData( + is_passing=True, + course=CourseData(course_key=CourseKey.from_string("course-v1:edX+DemoX.1+2014"), display_name="A"), + user=None, + ) + + with patch("credentials.apps.badges.processing.generic.logger.error") as mock_no_user_data: + process_event(sender=self.sender, kwargs=event_payload) + mock_no_user_data.assert_called_once() + diff --git a/credentials/apps/badges/tests/test_utils.py b/credentials/apps/badges/tests/test_utils.py index f793b2373e..658a06d5f5 100644 --- a/credentials/apps/badges/tests/test_utils.py +++ b/credentials/apps/badges/tests/test_utils.py @@ -134,6 +134,15 @@ def test_badges_checks_non_empty_events(self, mock_get_badging_event_types): mock_get_badging_event_types.return_value = ["event1", "event2"] errors = badges_checks() self.assertEqual(len(errors), 0) + + @patch("credentials.apps.badges.checks.credly_check") + def test_badges_checks_credly_not_configured(self, mock_credly_check): + mock_credly_check.return_value = False + errors = badges_checks() + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].msg, "Credly settings are not properly configured.") + self.assertEqual(errors[0].hint, "Make sure all required settings are present in BADGES_CONFIG['credly'].") + self.assertEqual(errors[0].id, "badges.E002") class TestCredlyCheck(unittest.TestCase): diff --git a/credentials/apps/badges/tests/test_webhooks.py b/credentials/apps/badges/tests/test_webhooks.py new file mode 100644 index 0000000000..51f6e84cb2 --- /dev/null +++ b/credentials/apps/badges/tests/test_webhooks.py @@ -0,0 +1,128 @@ +from unittest.mock import MagicMock, patch + +from django.test import TestCase +from django.test.client import RequestFactory +from faker import Faker + +from credentials.apps.badges.credly.api_client import CredlyAPIClient +from credentials.apps.badges.credly.webhooks import CredlyWebhook +from credentials.apps.badges.models import CredlyBadgeTemplate, CredlyOrganization + + +def mocked_handle_event(**kwargs): + return "test" + + +def get_organization(self, organization_id): + organization = MagicMock(spec=CredlyOrganization) # Create a mocked instance of CredlyOrganization + organization.uuid = organization_id + organization.api_key = "test_api_key" + return organization + + +def perform_request(self, method, endpoint, data=None): + return {"key": "value"} + + +class CredlyWebhookTestCase(TestCase): + def setUp(self): + self.rf = RequestFactory() + self.fake = Faker() + self.organization = CredlyOrganization.objects.create(uuid=self.fake.uuid4(), api_key="test_api_key") + + @patch.object(CredlyAPIClient, "_get_organization", get_organization) + @patch.object(CredlyAPIClient, "perform_request", perform_request) + def test_webhook_created_event(self): + with patch( + "credentials.apps.badges.credly.webhooks.CredlyWebhook.handle_badge_template_created_event" + ) as mock_handle: + req = self.rf.post( + "/credly/webhook/", + data={ + "id": self.fake.uuid4(), + "organization_id": self.organization.uuid, + "event_type": "badge_template.created", + "occurred_at": "2021-01-01T00:00:00Z", + }, + ) + res = CredlyWebhook.as_view()(req) + self.assertEqual(res.status_code, 204) + mock_handle.assert_called_once() + + @patch.object(CredlyAPIClient, "_get_organization", get_organization) + @patch.object(CredlyAPIClient, "perform_request", perform_request) + def test_webhook_changed_event(self): + with patch( + "credentials.apps.badges.credly.webhooks.CredlyWebhook.handle_badge_template_changed_event" + ) as mock_handle: + req = self.rf.post( + "/credly/webhook/", + data={ + "id": self.fake.uuid4(), + "organization_id": self.organization.uuid, + "event_type": "badge_template.changed", + "occurred_at": "2021-01-01T00:00:00Z", + }, + ) + res = CredlyWebhook.as_view()(req) + self.assertEqual(res.status_code, 204) + mock_handle.assert_called_once() + + @patch.object(CredlyAPIClient, "_get_organization", get_organization) + @patch.object(CredlyAPIClient, "perform_request", perform_request) + def test_webhook_deleted_event(self): + with patch( + "credentials.apps.badges.credly.webhooks.CredlyWebhook.handle_badge_template_deleted_event" + ) as mock_handle: + req = self.rf.post( + "/credly/webhook/", + data={ + "id": self.fake.uuid4(), + "organization_id": self.fake.uuid4(), + "event_type": "badge_template.deleted", + "occurred_at": "2021-01-01T00:00:00Z", + }, + ) + res = CredlyWebhook.as_view()(req) + self.assertEqual(res.status_code, 204) + mock_handle.assert_called_once() + + @patch.object(CredlyAPIClient, "_get_organization", get_organization) + @patch.object(CredlyAPIClient, "perform_request", perform_request) + def test_webhook_nonexistent_event(self): + with patch( + "credentials.apps.badges.credly.webhooks.logger.error" + ) as mock_handle: + req = self.rf.post( + "/credly/webhookd/", + data={ + "id": self.fake.uuid4(), + "organization_id": self.fake.uuid4(), + "event_type": "unknown_event", + "occurred_at": "2021-01-01T00:00:00Z", + }, + ) + CredlyWebhook.as_view()(req) + mock_handle.assert_called_once() + + def test_handle_badge_template_deleted_event(self): + request_data = { + "organization_id": "test_organization_id", + "id": "test_event_id", + "event_type": "badge_template.deleted", + "data": { + "badge_template": { + "id": self.fake.uuid4(), + "owner": {"id": self.fake.uuid4()}, + "name": "Test Template", + "state": "active", + "description": "Test Description", + "image_url": "http://example.com/image.png", + } + }, + } + request = self.rf.post("/credly/webhook/", data=request_data) + + CredlyWebhook.handle_badge_template_deleted_event(request, request_data) + + self.assertEqual(CredlyBadgeTemplate.objects.count(), 0)