Skip to content

Commit

Permalink
improve wording, test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
khvn26 committed Jan 26, 2025
1 parent 2c3f3f4 commit beb26cc
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 3 deletions.
2 changes: 1 addition & 1 deletion api/features/feature_health/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FEATURE_HEALTH_PROVIDER_CREATED_MESSAGE = "Health provider %s set up for project %s."
FEATURE_HEALTH_PROVIDER_DELETED_MESSAGE = "Health provider %s removed for project %s."
FEATURE_HEALTH_PROVIDER_DELETED_MESSAGE = "Health provider %s removed from project %s."

FEATURE_HEALTH_EVENT_CREATED_MESSAGE = "Health status changed to %s for feature %s."
FEATURE_HEALTH_EVENT_CREATED_FOR_ENVIRONMENT_MESSAGE = (
Expand Down
17 changes: 17 additions & 0 deletions api/tests/integration/features/feature_health/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

import pytest
from django.urls import reverse
from rest_framework.test import APIClient
Expand All @@ -11,3 +13,18 @@ def sample_feature_health_provider_webhook_url(
url = reverse("api-v1:projects:feature-health-providers-list", args=[project])
response = admin_client.post(url, data=feature_health_provider_data)
return response.json()["webhook_url"]


@pytest.fixture
def unhealthy_feature(
sample_feature_health_provider_webhook_url: str,
feature_name: str,
feature: int,
api_client: APIClient,
) -> int:
api_client.post(
sample_feature_health_provider_webhook_url,
data=json.dumps({"feature": feature_name, "status": "unhealthy"}),
content_type="application/json",
)
return feature
110 changes: 109 additions & 1 deletion api/tests/integration/features/feature_health/test_views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import json
from datetime import datetime, timedelta

import pytest
from django.urls import reverse
from freezegun import freeze_time
from rest_framework.test import APIClient


Expand Down Expand Up @@ -75,14 +77,120 @@ def test_webhook__sample_provider__post__expected_feature_health_event_created__
assert tag_data["id"] in feature_data["tags"]


@pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00")
def test_webhook__sample_provider__post_with_environment_expected_feature_health_event_created(
feature: int,
project: int,
environment: int,
feature_name: str,
environment_name: str,
sample_feature_health_provider_webhook_url: str,
api_client: APIClient,
admin_client: APIClient,
) -> None:
# Given
feature_health_events_url = reverse(
"api-v1:projects:feature-health-events-list", args=[project]
)

# When
webhook_data = {
"feature": feature_name,
"environment": environment_name,
"status": "unhealthy",
}
response = api_client.post(
sample_feature_health_provider_webhook_url,
data=json.dumps(webhook_data),
content_type="application/json",
)

# Then
assert response.status_code == 200
response = admin_client.get(feature_health_events_url)
assert response.json() == [
{
"created_at": "2023-01-19T09:09:47.325132Z",
"environment": environment,
"feature": feature,
"provider_name": "Sample",
"reason": "",
"type": "UNHEALTHY",
}
]


@pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00")
def test_webhook__unhealthy_feature__post__expected_feature_health_event_created__expected_tag_removed(
unhealthy_feature: int,
project: int,
feature_name: str,
sample_feature_health_provider_webhook_url: str,
api_client: APIClient,
admin_client: APIClient,
) -> None:
# Given
feature_health_events_url = reverse(
"api-v1:projects:feature-health-events-list", args=[project]
)
tags_url = reverse("api-v1:projects:tags-list", args=[project])
features_url = reverse("api-v1:projects:project-features-list", args=[project])

# When
webhook_data = {
"feature": feature_name,
"status": "healthy",
}
with freeze_time(datetime.now() + timedelta(seconds=1)):
response = api_client.post(
sample_feature_health_provider_webhook_url,
data=json.dumps(webhook_data),
content_type="application/json",
)

# Then
assert response.status_code == 200
response = admin_client.get(feature_health_events_url)
assert response.json() == [
{
"created_at": "2023-01-19T09:09:48.325132Z",
"environment": None,
"feature": unhealthy_feature,
"provider_name": "Sample",
"reason": "",
"type": "HEALTHY",
}
]
response = admin_client.get(tags_url)
assert (
tag_data := next(
tag_data
for tag_data in response.json()["results"]
if tag_data.get("label") == "Unhealthy"
)
)
response = admin_client.get(features_url)
feature_data = next(
feature_data
for feature_data in response.json()["results"]
if feature_data.get("id") == unhealthy_feature
)
assert tag_data["id"] not in feature_data["tags"]


@pytest.mark.parametrize(
"body", ["invalid", json.dumps({"status": "unhealthy", "feature": "non_existent"})]
)
def test_webhook__sample_provider__post__invalid_payload__expected_response(
sample_feature_health_provider_webhook_url: str,
api_client: APIClient,
body: str,
) -> None:
# When
response = api_client.post(
sample_feature_health_provider_webhook_url,
body="invalid",
data=body,
content_type="application/json",
)

# Then
Expand Down
17 changes: 17 additions & 0 deletions api/tests/unit/features/feature_health/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest

from features.feature_health.models import FeatureHealthProvider
from projects.models import Project
from users.models import FFAdminUser


@pytest.fixture
def feature_health_provider(
project: Project,
staff_user: FFAdminUser,
) -> FeatureHealthProvider:
return FeatureHealthProvider.objects.create(
created_by=staff_user,
project=project,
name="Sample",
)
86 changes: 85 additions & 1 deletion api/tests/unit/features/feature_health/test_models.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,55 @@
import datetime

from freezegun import freeze_time
from pytest_mock import MockerFixture

from features.feature_health.models import FeatureHealthEvent
from environments.models import Environment
from features.feature_health.models import (
FeatureHealthEvent,
FeatureHealthProvider,
)
from features.models import Feature
from organisations.models import Organisation
from projects.models import Project
from users.models import FFAdminUser

now = datetime.datetime.now()


def test_feature_health_provider__get_create_log_message__return_expected(
feature_health_provider: FeatureHealthProvider,
mocker: MockerFixture,
) -> None:
# When
log_message = feature_health_provider.get_create_log_message(mocker.Mock())

# Then
assert log_message == "Health provider Sample set up for project Test Project."


def test_feature_health_provider__get_delete_log_message__return_expected(
feature_health_provider: FeatureHealthProvider,
mocker: MockerFixture,
) -> None:
# When
log_message = feature_health_provider.get_delete_log_message(mocker.Mock())

# Then
assert log_message == "Health provider Sample removed from project Test Project."


def test_feature_health_provider__get_audit_log_author__return_expected(
feature_health_provider: FeatureHealthProvider,
mocker: MockerFixture,
staff_user: FFAdminUser,
) -> None:
# When
audit_log_author = feature_health_provider.get_audit_log_author(mocker.Mock())

# Then
assert audit_log_author == staff_user


def test_feature_health_event__get_latest_by_feature__return_expected(
project: Project,
feature: Feature,
Expand Down Expand Up @@ -105,3 +145,47 @@ def test_feature_health_event__get_latest_by_project__return_expected(
]
assert older_provider1_event not in feature_health_events
assert unrelated_feature_event not in feature_health_events


def test_feature_health_event__get_create_log_message__return_expected(
feature: Feature,
mocker: MockerFixture,
) -> None:
# Given
feature_health_event = FeatureHealthEvent.objects.create(
feature=feature,
type="UNHEALTHY",
provider_name="provider1",
reason="Test reason",
)

# When
log_message = feature_health_event.get_create_log_message(mocker.Mock())

# Then
assert (
log_message == "Health status changed to UNHEALTHY for feature Test Feature1."
"\n\nProvided by provider1\n\nReason:\nTest reason"
)


def test_feature_health_event__get_create_log_message__environment__return_expected(
feature: Feature,
environment: Environment,
mocker: MockerFixture,
) -> None:
# Given
feature_health_event = FeatureHealthEvent.objects.create(
feature=feature,
environment=environment,
type="UNHEALTHY",
)

# When
log_message = feature_health_event.get_create_log_message(mocker.Mock())

# Then
assert (
log_message
== "Health status changed to UNHEALTHY for feature Test Feature1 in environment Test Environment."
)
33 changes: 33 additions & 0 deletions api/tests/unit/features/feature_health/test_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import uuid

from pytest_mock import MockerFixture
from pytest_structlog import StructuredLogCapture

from features.feature_health.services import get_provider_response


def test_get_provider_response__invalid_provider__return_none__log_expected(
mocker: MockerFixture,
log: "StructuredLogCapture",
) -> None:
# Given
expected_provider_name = "invalid_provider"
expected_provider_uuid = uuid.uuid4()

invalid_provider_mock = mocker.MagicMock()
invalid_provider_mock.name = expected_provider_name
invalid_provider_mock.uuid = expected_provider_uuid

# When
response = get_provider_response(invalid_provider_mock, "payload")

# Then
assert response is None
assert log.events == [
{
"event": "invalid-feature-health-provider-requested",
"level": "error",
"provider_id": expected_provider_uuid,
"provider_name": expected_provider_name,
},
]

0 comments on commit beb26cc

Please sign in to comment.