Skip to content

Commit 154cfc0

Browse files
Paolo Romolinixrmx
Paolo Romolini
authored andcommitted
tests: first stab at unit testing search
You got to start from somwhere
1 parent 3291e41 commit 154cfc0

File tree

2 files changed

+307
-0
lines changed

2 files changed

+307
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
search_project_response = """
2+
{
3+
"took": 17,
4+
"timed_out": false,
5+
"_shards": {
6+
"total": 5,
7+
"successful": 5,
8+
"skipped": 0,
9+
"failed": 0
10+
},
11+
"hits": {
12+
"total": 1,
13+
"max_score": 1.8232156,
14+
"hits": [
15+
{
16+
"_index": "readthedocs",
17+
"_type": "project",
18+
"_id": "6",
19+
"_score": 1.8232156,
20+
"_source": {
21+
"name": "Pip",
22+
"description": "",
23+
"lang": "en",
24+
"url": "/projects/pip/",
25+
"slug": "pip"
26+
},
27+
"highlight": {
28+
"name": [
29+
"<em>Pip</em>"
30+
]
31+
}
32+
}
33+
]
34+
},
35+
"aggregations": {
36+
"language": {
37+
"doc_count_error_upper_bound": 0,
38+
"sum_other_doc_count": 0,
39+
"buckets": [
40+
{
41+
"key": "en",
42+
"doc_count": 1
43+
}
44+
]
45+
}
46+
}
47+
}
48+
"""
49+
50+
search_file_response = """
51+
{
52+
"took": 27,
53+
"timed_out": false,
54+
"_shards": {
55+
"total": 5,
56+
"successful": 5,
57+
"skipped": 0,
58+
"failed": 0
59+
},
60+
"hits": {
61+
"total": 3,
62+
"max_score": 6.989019,
63+
"hits": [
64+
{
65+
"_index": "readthedocs",
66+
"_type": "page",
67+
"_id": "AWKuy4jp-H7vMtbTbHP5",
68+
"_score": 6.989019,
69+
"_routing": "prova",
70+
"_source": {
71+
"path": "_docs/cap2",
72+
"project": "prova",
73+
"title": "Capitolo 2",
74+
"version": "latest"
75+
},
76+
"highlight": {
77+
"headers": [
78+
"<em>Capitolo</em> 2"
79+
],
80+
"title": [
81+
"<em>Capitolo</em> 2"
82+
],
83+
"content": [
84+
"<em>Capitolo</em> 2 In questo <em>capitolo</em>, vengono trattate"
85+
]
86+
}
87+
},
88+
{
89+
"_index": "readthedocs",
90+
"_type": "page",
91+
"_id": "AWKuy4jp-H7vMtbTbHP4",
92+
"_score": 6.973402,
93+
"_routing": "prova",
94+
"_source": {
95+
"path": "_docs/cap1",
96+
"project": "prova",
97+
"title": "Capitolo 1",
98+
"version": "latest"
99+
},
100+
"highlight": {
101+
"headers": [
102+
"<em>Capitolo</em> 1"
103+
],
104+
"title": [
105+
"<em>Capitolo</em> 1"
106+
],
107+
"content": [
108+
"<em>Capitolo</em> 1 In questo <em>capitolo</em>, vengono descritte le funzioni principali"
109+
]
110+
}
111+
},
112+
{
113+
"_index": "readthedocs",
114+
"_type": "page",
115+
"_id": "AWKuy4jp-H7vMtbTbHP3",
116+
"_score": 0.2017303,
117+
"_routing": "prova",
118+
"_source": {
119+
"path": "index",
120+
"project": "prova",
121+
"title": "Titolo del documento",
122+
"version": "latest"
123+
},
124+
"highlight": {
125+
"content": [
126+
"Titolo del documento Questo documento descrive Nel <em>Capitolo</em> 1 vengono presentati Nel <em>Capitolo</em> 2 vengono presentati <em>Capitolo</em> 1 <em>Capitolo</em> 2"
127+
]
128+
}
129+
}
130+
]
131+
},
132+
"aggregations": {
133+
"project": {
134+
"doc_count_error_upper_bound": 0,
135+
"sum_other_doc_count": 0,
136+
"buckets": [
137+
{
138+
"key": "prova",
139+
"doc_count": 3
140+
}
141+
]
142+
},
143+
"taxonomy": {
144+
"doc_count_error_upper_bound": 0,
145+
"sum_other_doc_count": 0,
146+
"buckets": []
147+
},
148+
"version": {
149+
"doc_count_error_upper_bound": 0,
150+
"sum_other_doc_count": 0,
151+
"buckets": [
152+
{
153+
"key": "latest",
154+
"doc_count": 3
155+
}
156+
]
157+
}
158+
}
159+
}
160+
"""
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import (
3+
absolute_import, division, print_function, unicode_literals)
4+
5+
import json
6+
7+
from django.core.urlresolvers import reverse
8+
from django.test import TestCase, RequestFactory
9+
from mock import patch
10+
from urllib3._collections import HTTPHeaderDict
11+
12+
from readthedocs.projects.models import Project
13+
from readthedocs.rtd_tests.mocks.search_mock_responses import (
14+
search_project_response, search_file_response
15+
)
16+
17+
18+
class TestSearch(TestCase):
19+
fixtures = ['eric', 'test_data']
20+
21+
def setUp(self):
22+
self.client.login(username='eric', password='test')
23+
self.pip = Project.objects.get(slug='pip')
24+
self.factory = RequestFactory()
25+
26+
def perform_request_mock(self, method, url, params=None, body=None, timeout=None, ignore=()):
27+
"""
28+
Elastic Search Urllib3HttpConnection mock
29+
the response is taken by a real search on a db after loading
30+
the test fixtures (eric and test_data) for the project search
31+
and by a local custom project for file search
32+
"""
33+
headers = HTTPHeaderDict(
34+
{
35+
'content-length': '893',
36+
'content-type': 'application/json; charset=UTF-8'
37+
}
38+
)
39+
if 'project.keyword' in params or '"project": "pip"' in params:
40+
raw_data = search_file_response
41+
else:
42+
raw_data = search_project_response
43+
return 200, headers, raw_data
44+
45+
@patch(
46+
'elasticsearch.connection.http_urllib3.Urllib3HttpConnection.perform_request',
47+
side_effect=perform_request_mock
48+
)
49+
def test_search_project(self, perform_request_mock):
50+
"""
51+
Tests the search view (by project) by mocking the perform request method
52+
of the elastic search module. Checks the query string provided
53+
to elastic search.
54+
"""
55+
self.client.login(username='eric', password='test')
56+
r = self.client.get(
57+
reverse('search'),
58+
{'q': 'pip', 'type': 'project', 'project': None}
59+
)
60+
self.assertEqual(r.status_code, 200)
61+
response = perform_request_mock.call_args_list[0][0][3]
62+
query_dict = json.loads(response)
63+
self.assertIn('query', query_dict)
64+
self.assertDictEqual({
65+
'bool': {
66+
'should': [
67+
{'match': {'name': {'query': 'pip', 'boost': 10}}},
68+
{'match': {'description': {'query': 'pip'}}}
69+
]
70+
}
71+
}, query_dict['query'])
72+
main_hit = r.context['results']['hits']['hits'][0]
73+
self.assertEqual(r.status_code, 200)
74+
self.assertEqual(main_hit['_type'], 'project')
75+
self.assertEqual(main_hit['_type'], 'project')
76+
self.assertEqual(main_hit['fields']['name'], 'Pip')
77+
self.assertEqual(main_hit['fields']['slug'], 'pip')
78+
79+
@patch(
80+
'elasticsearch.connection.http_urllib3.Urllib3HttpConnection.perform_request',
81+
side_effect=perform_request_mock
82+
)
83+
def test_search_file(self, perform_request_mock):
84+
"""
85+
Tests the search view (by file) by mocking the perform request method
86+
of the elastic search module. Checks the query string provided
87+
to elastic search.
88+
"""
89+
self.client.login(username='eric', password='test')
90+
r = self.client.get(
91+
reverse('search'),
92+
{'q': 'capitolo', 'type': 'file'}
93+
)
94+
response = perform_request_mock.call_args_list[0][0][3]
95+
query_dict = json.loads(response)
96+
self.assertIn('query', query_dict)
97+
self.assertDictEqual({
98+
'bool': {
99+
'filter': [{'term': {'version': 'latest'}}],
100+
'should': [
101+
{'match_phrase': {'title': {'query': 'capitolo', 'boost': 10, 'slop': 2}}},
102+
{'match_phrase': {'headers': {'query': 'capitolo', 'boost': 5, 'slop': 3}}},
103+
{'match_phrase': {'content': {'query': 'capitolo', 'slop': 5}}}
104+
]
105+
}
106+
}, query_dict['query'])
107+
main_hit = r.context['results']['hits']['hits'][0]
108+
self.assertEqual(r.status_code, 200)
109+
self.assertEqual(main_hit['_type'], 'page')
110+
self.assertEqual(main_hit['fields']['project'], 'prova')
111+
self.assertEqual(main_hit['fields']['path'], '_docs/cap2')
112+
113+
@patch(
114+
'elasticsearch.connection.http_urllib3.Urllib3HttpConnection.perform_request',
115+
side_effect=perform_request_mock
116+
)
117+
def test_search_in_project(self, perform_request_mock):
118+
"""
119+
Tests the search view (by file) by mocking the perform request method
120+
of the elastic search module. Checks the query string provided
121+
to elastic search.
122+
"""
123+
self.client.login(username='eric', password='test')
124+
r = self.client.get(
125+
'/projects/pip/search/',
126+
{'q': 'capitolo'}
127+
)
128+
response = perform_request_mock.call_args_list[0][0][3]
129+
query_dict = json.loads(response)
130+
self.assertDictEqual({
131+
'bool': {
132+
'should': [
133+
{'match': {'title': {'boost': 10, 'query': 'capitolo'}}},
134+
{'match': {'headers': {'boost': 5, 'query': 'capitolo'}}},
135+
{'match': {'content': {'query': 'capitolo'}}}
136+
],
137+
'filter': [
138+
{'term': {'project': 'pip'}},
139+
{'term': {'version': 'latest'}}
140+
]
141+
}
142+
}, query_dict['query'])
143+
main_hit = r.context['results']['hits']['hits'][0]
144+
self.assertEqual(r.status_code, 200)
145+
self.assertEqual(main_hit['_type'], 'page')
146+
self.assertEqual(main_hit['fields']['project'], 'prova')
147+
self.assertEqual(main_hit['fields']['path'], '_docs/cap2')

0 commit comments

Comments
 (0)