diff --git a/.gitignore b/.gitignore index 7216a168..e3ee2e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -159,6 +159,7 @@ cython_debug/ #.idea/ .vscode/launch.json +.vscode/settings.json chirps/erl_crash.dump *.dump diff --git a/chirps/target/providers/redis.py b/chirps/target/providers/redis.py index 9a1f7a47..5f8c9229 100644 --- a/chirps/target/providers/redis.py +++ b/chirps/target/providers/redis.py @@ -2,6 +2,7 @@ from logging import getLogger from django.db import models +from redis import Redis from target.models import BaseTarget logger = getLogger(__name__) @@ -28,5 +29,11 @@ def search(self, query: str, max_results: int) -> str: def test_connection(self) -> bool: """Ensure that the Redis target can be connected to.""" - logger.error('RedisTarget search not implemented') - raise NotImplementedError + client = Redis( + host=self.host, + port=self.port, + db=self.database_name, + password=self.password, + username=self.username, + ) + return client.ping() diff --git a/chirps/target/templates/target/dashboard.html b/chirps/target/templates/target/dashboard.html index 54c2699e..3da2977f 100644 --- a/chirps/target/templates/target/dashboard.html +++ b/chirps/target/templates/target/dashboard.html @@ -9,54 +9,97 @@

Targets

- - + +
-
+ +
+
{% for target in page_obj %} - -
-
- -
{{ target.name }}
-
- - -
-

- - {% if target.html_name == 'Mantium' %} - Application ID: {{ target.app_id }} - {% elif target.html_name == 'Redis' %} - Host: {{ target.host }}
- Port: {{ target.port }}
- Database Name: {{ target.database_name }}
- Username: {{ target.username }} - {% endif %} - {% if target.html_name == 'Pinecone' %} - API Key: {{ target.decrypted_api_key }}
- Environment: {{ target.environment }}
- Index Name: {{ target.index_name }}
- Project Name: {{ target.project_name }} - {% endif %} -

-
- Delete - Edit +
+
+
+ +
{{ target.name }}
+
+
+

+ {% if target.html_name == 'Mantium' %} + Application ID: {{ target.app_id }} + {% elif target.html_name == 'Redis' %} + Host: {{ target.host }}
+ Port: {{ target.port }}
+ Database Name: {{ target.database_name }}
+ Username: {{ target.username }} + {% endif %} + {% if target.html_name == 'Pinecone' %} + API Key: {{ target.decrypted_api_key }}
+ Environment: {{ target.environment }}
+ Index Name: {{ target.index_name }}
+ Project Name: {{ target.project_name }} + {% endif %} +

+
+ Delete + Edit + {% if target.html_name == 'Redis' %} + + {% endif %} +
- {% endfor %}
+ +
+
+
+ Notification + +
+
+ +
+
+
+ + + + {% include 'pagination.html' with page_obj=page_obj %} {% endblock %} diff --git a/chirps/target/tests.py b/chirps/target/tests.py index 25e4df3c..6b5fbfa2 100644 --- a/chirps/target/tests.py +++ b/chirps/target/tests.py @@ -1,8 +1,14 @@ """Test cases for the target application.""" +from unittest import mock + +import fakeredis +import pytest from django.contrib.auth.models import User # noqa: E5142 from django.test import TestCase from django.urls import reverse +from redis import exceptions from target.providers.mantium import MantiumTarget +from target.providers.redis import RedisTarget class TargetTests(TestCase): @@ -107,3 +113,31 @@ def test_scan_dashboard_last_page(self): response = self.client.get(reverse('target_dashboard'), {'item_count': 1, 'page': 100}) self.assertContains(response, 'chirps-pagination-widget', status_code=200) self.assertContains(response, 'chirps-target-3', status_code=200) + + +class RedisTargetTests(TestCase): + """Test the RedisTarget""" + + def setUp(self): + """Set up the RedisTarget tests""" + # Login the user before performing any tests + self.client.post(reverse('login'), {'username': 'admin', 'password': 'admin'}) + self.server = fakeredis.FakeServer() + self.redis = fakeredis.FakeStrictRedis(server=self.server) + + def test_ping__success(self): + """Test that connectivity check works""" + self.server.connected = True + + with mock.patch('target.providers.redis.Redis', return_value=self.redis): + target = RedisTarget(host='localhost', port=12000) + assert target.test_connection() + + def test_ping__failure(self): + """Test that ping raises ConnectionError if the server is not connected""" + self.server.connected = False + + with mock.patch('target.providers.redis.Redis', return_value=self.redis): + target = RedisTarget(host='localhost', port=12000) + with pytest.raises(exceptions.ConnectionError): + assert target.test_connection() diff --git a/chirps/target/urls.py b/chirps/target/urls.py index 3191ed03..294f5324 100644 --- a/chirps/target/urls.py +++ b/chirps/target/urls.py @@ -7,6 +7,7 @@ urlpatterns = [ path('', views.dashboard, name='target_dashboard'), path('create/', views.create, name='target_create'), + path('ping//', views.ping, name='target_ping'), path('delete/', views.delete, name='target_delete'), path('decrypted_keys/', views.decrypted_keys, name='decrypted_keys'), ] diff --git a/chirps/target/views.py b/chirps/target/views.py index 81e4b305..30bcfdbb 100644 --- a/chirps/target/views.py +++ b/chirps/target/views.py @@ -1,12 +1,16 @@ """View handlers for targets.""" +import json + from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator -from django.http import JsonResponse +from django.http import HttpResponseBadRequest, JsonResponse from django.shortcuts import get_object_or_404, redirect, render +from redis import exceptions from .forms import target_from_html_name, targets from .models import BaseTarget from .providers.pinecone import PineconeTarget +from .providers.redis import RedisTarget def decrypted_keys(request): @@ -72,6 +76,21 @@ def create(request, html_name): return render(request, 'target/create.html', {'form': form, 'target': target}) +@login_required +def ping(request, target_id): + """Ping a RedisTarget database using the test_connection() function.""" + target = get_object_or_404(BaseTarget, pk=target_id) + if isinstance(target, RedisTarget): + try: + result = target.test_connection() + return JsonResponse({'success': result}) + except exceptions.ConnectionError: + return HttpResponseBadRequest( + json.dumps({'success': False, 'error': 'Unable to connect to Redis'}), content_type='application/json' + ) + return HttpResponseBadRequest(json.dumps({'success': False, 'error': 'Not a RedisTarget'})) + + @login_required def delete(request, target_id): # pylint: disable=unused-argument """Delete a target from the database.""" diff --git a/requirements.txt b/requirements.txt index 52f41f72..d18ac0e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,6 @@ mantium-client redis==4.5.5 requests==2.31.0 pinecone-client +pytest python-dotenv +fakeredis==2.16.0