diff --git a/app/integrations/google_drive.py b/app/integrations/google_drive.py index 901e5a7c..9338eb78 100644 --- a/app/integrations/google_drive.py +++ b/app/integrations/google_drive.py @@ -377,3 +377,15 @@ def update_spreadsheet_close_incident(channel_name): ).execute() return True return False + + +def healthcheck(): + """Check if the bot can interact with Google Drive.""" + healthy = False + try: + metadata = list_metadata(INCIDENT_TEMPLATE) + healthy = "id" in metadata + logging.info(f"Google Drive healthcheck result: {metadata}") + except Exception as error: + logging.error(f"Google Drive healthcheck failed: {error}") + return healthy diff --git a/app/integrations/maxmind.py b/app/integrations/maxmind.py index de4d6b87..92540105 100644 --- a/app/integrations/maxmind.py +++ b/app/integrations/maxmind.py @@ -1,3 +1,4 @@ +import logging import geoip2.database from geoip2.errors import AddressNotFoundError @@ -16,3 +17,15 @@ def geolocate(ip): return "IP address not found" except ValueError: return "Invalid IP address" + + +def healthcheck(): + """Check if the bot can interact with Maxmind.""" + healthy = False + try: + result = geolocate("8.8.8.8") + healthy = isinstance(result, tuple) + logging.info(f"Maxmind healthcheck result: {result}") + except Exception as error: + logging.error(f"Maxmind healthcheck failed: {error}") + return healthy diff --git a/app/jobs/scheduled_tasks.py b/app/jobs/scheduled_tasks.py index 433f4fe0..74e662a3 100644 --- a/app/jobs/scheduled_tasks.py +++ b/app/jobs/scheduled_tasks.py @@ -5,7 +5,7 @@ import schedule import logging -from integrations import opsgenie +from integrations import google_drive, maxmind, opsgenie logging.basicConfig(level=logging.INFO) @@ -29,6 +29,8 @@ def scheduler_heartbeat(): def integration_healthchecks(): logging.info("Running integration healthchecks ...") healthchecks = { + "google_drive": google_drive.healthcheck, + "maxmind": maxmind.healthcheck, "opsgenie": opsgenie.healthcheck, } for key, healthcheck in healthchecks.items(): diff --git a/app/tests/intergrations/test_google_drive.py b/app/tests/intergrations/test_google_drive.py index 61374b49..aed827a7 100644 --- a/app/tests/intergrations/test_google_drive.py +++ b/app/tests/intergrations/test_google_drive.py @@ -226,3 +226,15 @@ def test_update_spreadsheet(get_google_service_mock): # assert that the function returns the correct response assert google_drive.update_spreadsheet_close_incident(channel_name) is True + + +@patch("integrations.google_drive.list_metadata") +def test_healthcheck_healthy(mock_list_metadata): + mock_list_metadata.return_value = {"id": "test_doc"} + assert google_drive.healthcheck() is True + + +@patch("integrations.google_drive.list_metadata") +def test_healthcheck_unhealthy(mock_list_metadata): + mock_list_metadata.return_value = None + assert google_drive.healthcheck() is False diff --git a/app/tests/intergrations/test_maxmind.py b/app/tests/intergrations/test_maxmind.py index d3d7c00b..c76c7b7d 100644 --- a/app/tests/intergrations/test_maxmind.py +++ b/app/tests/intergrations/test_maxmind.py @@ -31,3 +31,18 @@ def test_geolocate_not_found(geiop2_mock): def test_geolocate_invalid_ip(geiop2_mock): geiop2_mock.database.Reader().city.side_effect = ValueError assert maxmind.geolocate("test_ip") == "Invalid IP address" + + +@patch("integrations.maxmind.geoip2") +def test_healthcheck_healthy(geiop2_mock): + geiop2_mock.database.Reader().city.return_value.country.iso_code = "CA" + geiop2_mock.database.Reader().city.return_value.city.name = "test_city" + geiop2_mock.database.Reader().city.return_value.location.latitude = "test_lat" + geiop2_mock.database.Reader().city.return_value.location.longitude = "test_long" + assert maxmind.healthcheck() is True + + +@patch("integrations.maxmind.geoip2") +def test_healthcheck_unhealthy(geiop2_mock): + geiop2_mock.database.Reader().city.side_effect = ValueError + assert maxmind.healthcheck() is False diff --git a/app/tests/jobs/test_scheduled_tasks.py b/app/tests/jobs/test_scheduled_tasks.py index fd51bb59..d62802d7 100644 --- a/app/tests/jobs/test_scheduled_tasks.py +++ b/app/tests/jobs/test_scheduled_tasks.py @@ -24,20 +24,35 @@ def test_run_continuously(time_mock, threading_mock, schedule_mock): assert result == cease_continuous_run +@patch("jobs.scheduled_tasks.google_drive") +@patch("jobs.scheduled_tasks.maxmind") @patch("jobs.scheduled_tasks.opsgenie") @patch("jobs.scheduled_tasks.logging") -def test_integration_healthchecks_healthy(mock_logging, mock_opsgenie): +def test_integration_healthchecks_healthy( + mock_logging, mock_opsgenie, mock_maxmind, mock_google_drive +): + mock_google_drive.healthcheck.return_value = True + mock_maxmind.healthcheck.return_value = True mock_opsgenie.healthcheck.return_value = True scheduled_tasks.integration_healthchecks() + assert mock_google_drive.healthcheck.call_count == 1 + assert mock_maxmind.healthcheck.call_count == 1 assert mock_opsgenie.healthcheck.call_count == 1 assert mock_logging.error.call_count == 0 +@patch("jobs.scheduled_tasks.google_drive") +@patch("jobs.scheduled_tasks.maxmind") @patch("jobs.scheduled_tasks.opsgenie") @patch("jobs.scheduled_tasks.logging") -def test_integration_healthchecks_unhealthy(mock_logging, mock_opsgenie): - mock_opsgenie.healthcheck.return_value = False - mock_opsgenie.healthcheck.__name__ = "test_integration" +def test_integration_healthchecks_unhealthy( + mock_logging, mock_opsgenie, mock_maxmind, mock_google_drive +): + mock_google_drive.healthcheck.return_value = False + mock_maxmind.healthcheck.return_value = False + mock_opsgenie.healthcheck.return_value = True scheduled_tasks.integration_healthchecks() + assert mock_google_drive.healthcheck.call_count == 1 + assert mock_maxmind.healthcheck.call_count == 1 assert mock_opsgenie.healthcheck.call_count == 1 - assert mock_logging.error.call_count == 1 + assert mock_logging.error.call_count == 2