Skip to content

Commit ebc1cab

Browse files
Zaimwa9pre-commit-ci[bot]khvn26
authored
feat: Test webhook from backend (#5354)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kim Gustyr <kim.gustyr@flagsmith.com>
1 parent a112ec8 commit ebc1cab

23 files changed

Lines changed: 837 additions & 461 deletions

api/api/urls/v1.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@
8080
schema_view.with_ui("swagger", cache_timeout=0),
8181
name="schema-swagger-ui",
8282
),
83+
# Test webhook url
84+
re_path(r"^webhooks/", include("webhooks.urls", namespace="webhooks")),
8385
]
8486

8587
if settings.SPLIT_TESTING_INSTALLED:

api/environments/views.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import logging
2+
from typing import Generic, Type, TypeVar
23

34
from common.environments.permissions import (
45
TAG_SUPPORTED_PERMISSIONS,
56
VIEW_ENVIRONMENT,
67
)
7-
from django.db.models import Count, Q
8+
from django.db.models import Count, Q, QuerySet
89
from django.utils.decorators import method_decorator
910
from drf_yasg import openapi # type: ignore[import-untyped]
1011
from drf_yasg.utils import no_body, swagger_auto_schema # type: ignore[import-untyped]
@@ -15,7 +16,9 @@
1516
from rest_framework.permissions import IsAuthenticated
1617
from rest_framework.request import Request
1718
from rest_framework.response import Response
19+
from rest_framework.serializers import BaseSerializer
1820

21+
from core.models import AbstractBaseExportableModel
1922
from environments.permissions.permissions import (
2023
EnvironmentAdminPermission,
2124
EnvironmentPermissions,
@@ -35,7 +38,6 @@
3538
)
3639
from projects.models import Project
3740
from users.models import FFAdminUser
38-
from webhooks.mixins import TriggerSampleWebhookMixin
3941
from webhooks.webhooks import WebhookType
4042

4143
from .identities.traits.models import Trait
@@ -57,6 +59,8 @@
5759
WebhookSerializer,
5860
)
5961

62+
T = TypeVar("T", bound=AbstractBaseExportableModel)
63+
6064
logger = logging.getLogger(__name__)
6165

6266

@@ -307,43 +311,44 @@ def disable_v2_versioning(self, request: Request, api_key: str) -> Response:
307311
return Response(status=status.HTTP_202_ACCEPTED)
308312

309313

310-
class NestedEnvironmentViewSet(viewsets.GenericViewSet): # type: ignore[type-arg]
311-
model_class = None
314+
class NestedEnvironmentViewSet(Generic[T], viewsets.GenericViewSet[T]):
315+
model_class: Type[T]
312316
webhook_type = WebhookType.ENVIRONMENT
313317

314-
def get_queryset(self): # type: ignore[no-untyped-def]
315-
return self.model_class.objects.filter( # type: ignore[attr-defined]
318+
def get_queryset(self) -> QuerySet[T]:
319+
return self.model_class.objects.filter(
316320
environment__api_key=self.kwargs.get("environment_api_key")
317321
)
318322

319-
def perform_create(self, serializer): # type: ignore[no-untyped-def]
320-
serializer.save(environment=self._get_environment()) # type: ignore[no-untyped-call]
323+
def perform_create(self, serializer: BaseSerializer[T]) -> None:
324+
serializer.save(environment=self._get_environment())
321325

322-
def perform_update(self, serializer): # type: ignore[no-untyped-def]
323-
serializer.save(environment=self._get_environment()) # type: ignore[no-untyped-call]
326+
def perform_update(self, serializer: BaseSerializer[T]) -> None:
327+
serializer.save(environment=self._get_environment())
324328

325-
def _get_environment(self): # type: ignore[no-untyped-def]
326-
return Environment.objects.get(api_key=self.kwargs.get("environment_api_key"))
329+
def _get_environment(self) -> Environment:
330+
environment: Environment = Environment.objects.get(
331+
api_key=self.kwargs.get("environment_api_key")
332+
)
333+
return environment
327334

328335

329336
class WebhookViewSet(
330-
NestedEnvironmentViewSet,
337+
NestedEnvironmentViewSet[Webhook],
331338
mixins.ListModelMixin,
332339
mixins.CreateModelMixin,
333340
mixins.UpdateModelMixin,
334341
mixins.DestroyModelMixin,
335-
TriggerSampleWebhookMixin,
336342
):
337343
serializer_class = WebhookSerializer
338344
pagination_class = None
339345
permission_classes = [IsAuthenticated, NestedEnvironmentPermissions]
340-
model_class = Webhook # type: ignore[assignment]
341-
342-
webhook_type = WebhookType.ENVIRONMENT # type: ignore[assignment]
346+
model_class: Type[Webhook] = Webhook
347+
webhook_type: WebhookType = WebhookType.ENVIRONMENT
343348

344349

345350
class EnvironmentAPIKeyViewSet(
346-
NestedEnvironmentViewSet,
351+
NestedEnvironmentViewSet[EnvironmentAPIKey],
347352
mixins.ListModelMixin,
348353
mixins.CreateModelMixin,
349354
mixins.UpdateModelMixin,
@@ -352,4 +357,4 @@ class EnvironmentAPIKeyViewSet(
352357
serializer_class = EnvironmentAPIKeySerializer
353358
pagination_class = None
354359
permission_classes = [IsAuthenticated, EnvironmentAdminPermission]
355-
model_class = EnvironmentAPIKey # type: ignore[assignment]
360+
model_class: Type[EnvironmentAPIKey] = EnvironmentAPIKey

api/organisations/views.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
from projects.serializers import ProjectListSerializer
6060
from users.models import FFAdminUser
6161
from users.serializers import UserIdSerializer
62-
from webhooks.mixins import TriggerSampleWebhookMixin
6362
from webhooks.webhooks import WebhookType
6463

6564
from .serializers import OrganisationAPIUsageNotificationSerializer
@@ -335,11 +334,11 @@ def chargebee_webhook(request: Request) -> Response:
335334
return webhook_handlers.process_subscription(request)
336335

337336

338-
class OrganisationWebhookViewSet(viewsets.ModelViewSet, TriggerSampleWebhookMixin): # type: ignore[type-arg]
337+
class OrganisationWebhookViewSet(viewsets.ModelViewSet): # type: ignore[type-arg]
339338
serializer_class = OrganisationWebhookSerializer
340339
permission_classes = [IsAuthenticated, NestedOrganisationEntityPermission]
341340

342-
webhook_type = WebhookType.ORGANISATION # type: ignore[assignment]
341+
webhook_type = WebhookType.ORGANISATION
343342

344343
def get_queryset(self): # type: ignore[no-untyped-def]
345344
if getattr(self, "swagger_fake_view", False):

api/tests/unit/environments/test_unit_environments_views.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import json
2-
from unittest import mock
32

43
import pytest
54
from common.environments.permissions import (
@@ -512,32 +511,6 @@ def test_cannot_delete_webhooks_for_environment_user_does_not_belong_to(
512511
assert Webhook.objects.filter(id=webhook.id).exists()
513512

514513

515-
@mock.patch("webhooks.mixins.trigger_sample_webhook")
516-
def test_trigger_sample_webhook_calls_trigger_sample_webhook_method_with_correct_arguments(
517-
trigger_sample_webhook_mock: mock.MagicMock,
518-
environment: Environment,
519-
admin_client: APIClient,
520-
) -> None:
521-
# Given
522-
valid_webhook_url = "http://my.webhook.com/webhooks"
523-
mocked_response = mock.MagicMock(status_code=200)
524-
trigger_sample_webhook_mock.return_value = mocked_response
525-
url = reverse(
526-
"api-v1:environments:environment-webhooks-trigger-sample-webhook",
527-
args=[environment.api_key],
528-
)
529-
data = {"url": valid_webhook_url}
530-
531-
# When
532-
response = admin_client.post(url, data)
533-
534-
# Then
535-
assert response.json()["message"] == "Request returned 200"
536-
assert response.status_code == status.HTTP_200_OK
537-
args, _ = trigger_sample_webhook_mock.call_args
538-
assert args[0].url == valid_webhook_url
539-
540-
541514
def test_list_api_keys(
542515
environment: Environment,
543516
admin_client_new: APIClient,

api/tests/unit/organisations/test_unit_organisations_views.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,33 +1006,6 @@ def test_can_update_secret_for_webhook(
10061006
assert webhook.secret == data["secret"]
10071007

10081008

1009-
@mock.patch("webhooks.mixins.trigger_sample_webhook")
1010-
def test_trigger_sample_webhook_calls_trigger_sample_webhook_method_with_correct_arguments(
1011-
trigger_sample_webhook: MagicMock,
1012-
organisation: Organisation,
1013-
admin_client: APIClient,
1014-
) -> None:
1015-
# Given
1016-
mocked_response = mock.MagicMock(status_code=200)
1017-
trigger_sample_webhook.return_value = mocked_response
1018-
1019-
url = reverse(
1020-
"api-v1:organisations:organisation-webhooks-trigger-sample-webhook",
1021-
args=[organisation.id],
1022-
)
1023-
valid_webhook_url = "http://my.webhook.com/webhooks"
1024-
data = {"url": valid_webhook_url}
1025-
1026-
# When
1027-
response = admin_client.post(url, data)
1028-
1029-
# Then
1030-
assert response.json()["message"] == "Request returned 200"
1031-
assert response.status_code == status.HTTP_200_OK
1032-
args, _ = trigger_sample_webhook.call_args
1033-
assert args[0].url == valid_webhook_url
1034-
1035-
10361009
def test_get_subscription_metadata_when_subscription_information_cache_exist(
10371010
organisation: Organisation,
10381011
admin_client: APIClient,

0 commit comments

Comments
 (0)