Changeset - b2d842037a63
[Not reviewed]
default
0 3 0
Branko Majic (branko) - 10 years ago 2013-10-26 23:14:24
branko@majic.rs
CONNT-23: Limit the as-you-type search results to 4 entities and 4 projects maximum. Fixed permission handling in APISearchView to raise exception. Search term is now defauled to empty string (easier to test). Implemented test for APISearchView.
3 files changed with 172 insertions and 3 deletions:
0 comments (0 inline, 0 general)
conntrackt/static/conntrackt.js
Show inline comments
 
@@ -37,7 +37,7 @@
 
        // Show the search suggestions only if the user has provided 2 or more
 
        // characters.
 
        if (searchField.val().length >= 2) {
 
            request = $.getJSON(conntrackt_api_url + "/search/" + searchField.val())
 
            request = $.getJSON(conntrackt_api_url + "/search/" + searchField.val(), {"limit": 4})
 
                .done(function(data) {
 

	
 
                    // Process the retrieved JSON response, setting-up list of
conntrackt/tests/test_views.py
Show inline comments
 
@@ -20,6 +20,7 @@
 

	
 

	
 
# Standard library imports.
 
import json
 
from StringIO import StringIO
 
from zipfile import ZipFile, ZIP_DEFLATED
 

	
 
@@ -27,6 +28,7 @@ from zipfile import ZipFile, ZIP_DEFLATE
 
import mock
 

	
 
# Django imports.
 
from django.core.exceptions import ValidationError
 
from django.core.urlresolvers import reverse
 
from django.http import Http404
 
from django.test import RequestFactory
 
@@ -36,7 +38,7 @@ from django.utils.http import urlquote
 
# Application imports
 
from conntrackt.models import Project, Location, Entity, Interface, Communication
 

	
 
from conntrackt.views import IndexView, SearchView
 
from conntrackt.views import IndexView, SearchView, APISearchView
 
from conntrackt.views import entity_iptables, project_iptables, project_diagram
 

	
 
from conntrackt.views import ProjectView, ProjectCreateView, ProjectUpdateView, ProjectDeleteView
 
@@ -1604,3 +1606,156 @@ class SearchViewTest(PermissionTestMixin
 
        self.assertNotIn("search_term", response.context_data)
 
        # Only the "view" context variable should be present.
 
        self.assertEqual(1, len(response.context_data))
 

	
 

	
 
class APISearchViewTest(PermissionTestMixin, TestCase):
 

	
 
    sufficient_permissions = ("view",)
 
    view_class = APISearchView
 

	
 
    def setUp(self):
 
        """
 
        Set-up some test data.
 
        """
 

	
 
        setup_test_data()
 

	
 
    def test_limit_negative(self):
 
        """
 
        Test if an exception is raised in case a negative limit is requested.
 
        """
 

	
 
        # Get the view.
 
        view = APISearchView.as_view()
 

	
 
        # Generate the request.
 
        request = RequestFactory().get("/fake-path?limit=-1")
 
        request.user = mock.Mock()
 
        request._dont_enforce_csrf_checks = True
 

	
 
        # Validate the response.
 
        self.assertRaisesRegexp(ValidationError, "Limit may not be a negative value.", view, request, search_term="test")
 

	
 
    def test_empty_query(self):
 
        """
 
        Test that the response is empty if empty query was sent.
 
        """
 

	
 
        # Get the view.
 
        view = APISearchView.as_view()
 

	
 
        # Get the response.
 
        response = generate_get_response(view, search_term="")
 

	
 
        self.assertEqual(response['Content-Type'], "application/json")
 
        self.assertEqual(response.content, "[]")
 

	
 
    def test_strip_search_term(self):
 
        """
 
        Verifies that the search term is stripped when search is performed.
 
        """
 

	
 
        # Get the view.
 
        view = APISearchView.as_view()
 

	
 
        # Get the response.
 
        response = generate_get_response(view, search_term="Test Entity 1")
 

	
 
        # Validate the response.
 
        expected_content = """[{"project": "Test Project 1", "url": "/conntrackt/entity/1/", "type": "entity", "name": "Test Entity 1"}]"""
 
        self.assertEqual(response.content, expected_content)
 

	
 
    def test_no_items(self):
 
        """
 
        Test the response if no items are found.
 
        """
 

	
 
        # Get the view.
 
        view = APISearchView.as_view()
 

	
 
        # Get the response.
 
        response = generate_get_response(view, search_term="string that does not exist")
 

	
 
        self.assertEqual(response['Content-Type'], "application/json")
 
        self.assertEqual(response.content, "[]")
 

	
 
    def test_entity_found(self):
 
        """
 
        Test the response if a single entity is found.
 
        """
 

	
 
        # Get the view.
 
        view = APISearchView.as_view()
 

	
 
        # Get the response.
 
        response = generate_get_response(view, search_term="Test Entity 1")
 

	
 
        expected_content = """[{"project": "Test Project 1", "url": "/conntrackt/entity/1/", "type": "entity", "name": "Test Entity 1"}]"""
 

	
 
        self.assertEqual(response['Content-Type'], "application/json")
 
        self.assertEqual(response.content, expected_content)
 

	
 
    def test_project_found(self):
 
        """
 
        Test the response if a single project is found.
 
        """
 

	
 
        # Get the view.
 
        view = APISearchView.as_view()
 

	
 
        # Get the response.
 
        response = generate_get_response(view, search_term="Test Project 1")
 

	
 
        expected_content = """[{"project": "Test Project 1", "url": "/conntrackt/project/1/", "type": "project", "name": "Test Project 1"}]"""
 

	
 
        self.assertEqual(response['Content-Type'], "application/json")
 
        self.assertEqual(response.content, expected_content)
 

	
 
    def test_multiple_items_found(self):
 
        """
 
        Test the response if multiple items are found.
 
        """
 

	
 
        # Get the view.
 
        view = APISearchView.as_view()
 

	
 
        # Get the response.
 
        response = generate_get_response(view, search_term="Test")
 

	
 
        # Verify that the JSON reply is valid.
 
        try:
 
            items = json.loads(response.content)
 
        except ValueError:
 
            self.fail("Parsing of resulting JSON has failed")
 

	
 
        # Verify that a list of items was returned.
 
        self.assertTrue(isinstance(items, list))
 

	
 
        # Verify each item.
 
        for item in items:
 
            # Every item must be a dictionary.
 
            self.assertTrue(isinstance(item, dict))
 
            keys = item.keys()
 
            # Verify that 4 specific keys are present in dictionary (project,
 
            #  url, name, type).
 
            self.assertEqual(len(keys), 4)
 
            self.assertIn("project", keys)
 
            self.assertIn("name", keys)
 
            self.assertIn("url", keys)
 
            self.assertIn("type", keys)
 
            # Verify the type associated with item.
 
            self.assertIn(item["type"], ["project", "entity"])
 

	
 
    def test_content_type(self):
 
        """
 
        Test if correct content type is being returned by the response.
 
        """
 

	
 
        # Get the view.
 
        view = APISearchView.as_view()
 

	
 
        # Get the response.
 
        response = generate_get_response(view, search_term="test")
 

	
 
        self.assertEqual(response['Content-Type'], "application/json")
 

	
conntrackt/views.py
Show inline comments
 
@@ -27,6 +27,7 @@ import json
 
# Django imports.
 
from django.contrib.auth.decorators import permission_required
 
from django.contrib import messages
 
from django.core.exceptions import ValidationError
 
from django.core.urlresolvers import reverse, reverse_lazy
 
from django.db.models import Q
 
from django.http import HttpResponse
 
@@ -1039,7 +1040,10 @@ class APISearchView(MultiplePermissionsR
 
        "all": ("conntrackt.view",),
 
        }
 

	
 
    def get(self, request, search_term):
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def get(self, request, search_term=""):
 
        """
 
        Implements response handling for a GET request.
 
        """
 
@@ -1051,6 +1055,11 @@ class APISearchView(MultiplePermissionsR
 
        # Set-up a list that will contain found items.
 
        items = []
 

	
 
        # Fetch the maximum number of items that should be returned.
 
        limit = int(request.GET.get("limit", 0))
 
        if limit < 0:
 
            raise ValidationError("Limit may not be a negative value.")
 

	
 
        # Don't perform search with empty search term.
 
        if search_term != "":
 

	
 
@@ -1058,6 +1067,11 @@ class APISearchView(MultiplePermissionsR
 
            entities = Entity.objects.search(search_term).select_related("project")
 
            projects = Project.objects.search(search_term)
 

	
 
            # If maximum number of items was provided, narrow-down the results.
 
            if limit > 0:
 
                entities = entities[:limit]
 
                projects = projects[:limit]
 

	
 
            # Add found entities.
 
            for entity in entities:
 
                items.append({"name": entity.name,
0 comments (0 inline, 0 general)