Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mail check #437

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The following health checks are bundled with this project:
- Celery ping
- RabbitMQ
- Migrations
- Mail

Writing your own custom health checks is also very quick and easy.

Expand Down Expand Up @@ -74,6 +75,7 @@ Add the `health_check` applications to your `INSTALLED_APPS`:
'health_check.contrib.s3boto3_storage', # requires boto3 and S3BotoStorage backend
'health_check.contrib.rabbitmq', # requires RabbitMQ broker
'health_check.contrib.redis', # requires Redis broker
'health_check.contrib.mail',
]
```

Expand All @@ -90,6 +92,15 @@ one of these checks, set its value to `None`.
}
```

(Optional) If using the `mail` app, you can configure timeout
threshold settings; otherwise below defaults are assumed.

```python
HEALTH_CHECK = {
'MAIL_TIMEOUT': 15, # seconds
}
```

To use Health Check Subsets, Specify a subset name and associate it with the relevant health check services to utilize Health Check Subsets.
```python
HEALTH_CHECK = {
Expand Down
4 changes: 4 additions & 0 deletions health_check/contrib/mail/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import django

if django.VERSION < (3, 2):
default_app_config = "health_check.contrib.mail.apps.HealthCheckConfig"
12 changes: 12 additions & 0 deletions health_check/contrib/mail/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.apps import AppConfig

from health_check.plugins import plugin_dir


class HealthCheckConfig(AppConfig):
name = "health_check.contrib.mail"

def ready(self):
from .backends import MailHealthCheck

plugin_dir.register(MailHealthCheck)
28 changes: 28 additions & 0 deletions health_check/contrib/mail/backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import logging

from django.core.mail import get_connection

from health_check.backends import BaseHealthCheckBackend
from health_check.conf import HEALTH_CHECK
from health_check.exceptions import ServiceUnavailable

logger = logging.getLogger(__name__)


class MailHealthCheck(BaseHealthCheckBackend):
"""Check that mail backend is working."""

def check_status(self) -> None:
"""Open and close connection email server."""
try:
connection = get_connection(fail_silently=False)
connection.timeout = HEALTH_CHECK.get("MAIL_TIMEOUT", 15)
logger.debug("Trying to open connection to mail backend.")
connection.open()
connection.close()
logger.debug("Connection established. Mail backend is healthy.")
except Exception as error:
self.add_error(
error=ServiceUnavailable(error),
cause=error,
)
44 changes: 44 additions & 0 deletions tests/test_mail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from unittest import mock

from health_check.contrib.mail.backends import MailHealthCheck


class TestMailHealthCheck:
"""Test mail health check."""

@mock.patch("health_check.contrib.mail.backends.get_connection")
def test_mail_conn_ok(self, mocked_backend):
"""Test everything is OK."""

# instantiates the class
mail_health_checker = MailHealthCheck()

# invokes the method check_status()
mail_health_checker.check_status()
assert len(mail_health_checker.errors) == 0, mail_health_checker.errors

# mock assertions
assert mocked_backend.return_value.timeout == 15
mocked_backend.return_value.open.assert_called_once()
mocked_backend.return_value.close.assert_called_once()

@mock.patch("health_check.contrib.mail.backends.get_connection")
def test_mail_conn_refused(self, mocked_backend):
"""Test case then connection refused."""

mocked_backend.return_value.open.side_effect = ConnectionRefusedError(
"Refused connection"
)
# instantiates the class
mail_health_checker = MailHealthCheck()

# invokes the method check_status()
mail_health_checker.check_status()
assert len(mail_health_checker.errors) == 1, mail_health_checker.errors
assert (
mail_health_checker.errors[0].message
== mocked_backend.return_value.open.side_effect
)

# mock assertions
mocked_backend.return_value.open.assert_called_once()
1 change: 1 addition & 0 deletions tests/testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"health_check.contrib.migrations",
"health_check.contrib.celery_ping",
"health_check.contrib.s3boto_storage",
"health_check.contrib.mail",
"tests",
)

Expand Down