Merge lp:~maxiberta/launchpad/sitesearch-cleanup-2 into lp:launchpad
- sitesearch-cleanup-2
- Merge into devel
Proposed by
Maximiliano Bertacchini
Status: | Superseded |
---|---|
Proposed branch: | lp:~maxiberta/launchpad/sitesearch-cleanup-2 |
Merge into: | lp:launchpad |
Diff against target: |
594 lines (+471/-22) 3 files modified
lib/lp/services/sitesearch/tests/test_bing.py (+163/-10) lib/lp/services/sitesearch/tests/test_google.py (+234/-4) lib/lp/services/sitesearch/tests/test_pagematch.py (+74/-8) |
To merge this branch: | bzr merge lp:~maxiberta/launchpad/sitesearch-cleanup-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+343235@code.launchpad.net |
Commit message
Assorted sitesearch fixes and improvements (part 2).
Description of the change
Assorted sitesearch fixes and improvements (part 2).
- Add sitesearch unittests based on doctests.
- sitesearch/
- sitesearch/
- Add PageMatch and PageMatches unittests based on doctests.
- sitesearch/
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/services/sitesearch/tests/test_bing.py' |
2 | --- lib/lp/services/sitesearch/tests/test_bing.py 2018-04-12 19:42:23 +0000 |
3 | +++ lib/lp/services/sitesearch/tests/test_bing.py 2018-04-13 19:49:46 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -# Copyright 2011-2018 Canonical Ltd. This software is licensed under the |
6 | +# Copyright 2018 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | |
9 | """Test the bing search service.""" |
10 | @@ -7,31 +7,177 @@ |
11 | |
12 | __metaclass__ = type |
13 | |
14 | +import json |
15 | +from os import path |
16 | + |
17 | from fixtures import MockPatch |
18 | from requests.exceptions import ( |
19 | ConnectionError, |
20 | HTTPError, |
21 | ) |
22 | -from testtools.matchers import MatchesStructure |
23 | +from testtools.matchers import ( |
24 | + HasLength, |
25 | + MatchesStructure, |
26 | +) |
27 | |
28 | +from lp.services.config import config |
29 | from lp.services.sitesearch import BingSearchService |
30 | from lp.services.sitesearch.interfaces import SiteSearchResponseError |
31 | from lp.services.timeout import TimeoutError |
32 | from lp.testing import TestCase |
33 | -from lp.testing.layers import ( |
34 | - BingLaunchpadFunctionalLayer, |
35 | - LaunchpadFunctionalLayer, |
36 | - ) |
37 | +from lp.testing.layers import BingLaunchpadFunctionalLayer |
38 | |
39 | |
40 | class TestBingSearchService(TestCase): |
41 | """Test BingSearchService.""" |
42 | |
43 | - layer = LaunchpadFunctionalLayer |
44 | - |
45 | def setUp(self): |
46 | super(TestBingSearchService, self).setUp() |
47 | self.search_service = BingSearchService() |
48 | + self.base_path = path.normpath( |
49 | + path.join(path.dirname(__file__), 'data')) |
50 | + |
51 | + def test_configuration(self): |
52 | + self.assertEqual(config.bing.site, self.search_service.site) |
53 | + self.assertEqual( |
54 | + config.bing.subscription_key, self.search_service.subscription_key) |
55 | + self.assertEqual( |
56 | + config.bing.custom_config_id, self.search_service.custom_config_id) |
57 | + |
58 | + def test_create_search_url(self): |
59 | + self.assertEndsWith( |
60 | + self.search_service.create_search_url(terms='svg +bugs'), |
61 | + '&offset=0&q=svg+%2Bbugs') |
62 | + |
63 | + def test_create_search_url_escapes_unicode_chars(self): |
64 | + self.assertEndsWith( |
65 | + self.search_service.create_search_url('Carlo Perell\xf3 Mar\xedn'), |
66 | + '&offset=0&q=Carlo+Perell%C3%B3+Mar%C3%ADn') |
67 | + |
68 | + def test_create_search_url_with_offset(self): |
69 | + self.assertEndsWith( |
70 | + self.search_service.create_search_url(terms='svg +bugs', start=20), |
71 | + '&offset=20&q=svg+%2Bbugs') |
72 | + |
73 | + def test_create_search_url_empty_terms(self): |
74 | + e = self.assertRaises( |
75 | + ValueError, self.search_service.create_search_url, '') |
76 | + self.assertEqual("Missing value for parameter 'q'.", str(e)) |
77 | + |
78 | + def test_create_search_url_null_terms(self): |
79 | + e = self.assertRaises( |
80 | + ValueError, self.search_service.create_search_url, None) |
81 | + self.assertEqual("Missing value for parameter 'q'.", str(e)) |
82 | + |
83 | + def test_create_search_url_requires_start(self): |
84 | + e = self.assertRaises( |
85 | + ValueError, self.search_service.create_search_url, 'bugs', 'true') |
86 | + self.assertEqual("Value for parameter 'offset' is not an int.", str(e)) |
87 | + |
88 | + def test_parse_search_response_invalid_total(self): |
89 | + """The PageMatches's total attribute comes from the |
90 | + `webPages.totalEstimatedMatches` JSON element. |
91 | + When it cannot be found and the value cast to an int, |
92 | + an error is raised. If Bing were to redefine the meaning of the |
93 | + element to use a '~' to indicate an approximate total, an error would |
94 | + be raised. |
95 | + """ |
96 | + file_name = path.join( |
97 | + self.base_path, 'bingsearchservice-incompatible-matches.json') |
98 | + with open(file_name, 'r') as response_file: |
99 | + response = response_file.read() |
100 | + assert ( |
101 | + json.loads(response)['webPages']['totalEstimatedMatches'] == '~25') |
102 | + |
103 | + e = self.assertRaises( |
104 | + SiteSearchResponseError, |
105 | + self.search_service._parse_search_response, response) |
106 | + self.assertEqual( |
107 | + "Could not get the total from the Bing JSON response.", str(e)) |
108 | + |
109 | + def test_parse_search_response_negative_total(self): |
110 | + """If the total is ever less than zero (see bug 683115), |
111 | + this is expected: we simply return a total of 0. |
112 | + """ |
113 | + file_name = path.join( |
114 | + self.base_path, 'bingsearchservice-negative-total.json') |
115 | + with open(file_name, 'r') as response_file: |
116 | + response = response_file.read() |
117 | + assert json.loads(response)['webPages']['totalEstimatedMatches'] == -25 |
118 | + |
119 | + matches = self.search_service._parse_search_response(response) |
120 | + self.assertEqual(0, matches.total) |
121 | + |
122 | + def test_parse_search_response_missing_title(self): |
123 | + """A PageMatch requires a title, url, and a summary. If those elements |
124 | + cannot be found, a PageMatch cannot be made. A missing title ('name') |
125 | + indicates a bad page on Launchpad, so it is ignored. In this example, |
126 | + the first match is missing a title, so only the second page is present |
127 | + in the PageMatches. |
128 | + """ |
129 | + file_name = path.join( |
130 | + self.base_path, 'bingsearchservice-missing-title.json') |
131 | + with open(file_name, 'r') as response_file: |
132 | + response = response_file.read() |
133 | + assert len(json.loads(response)['webPages']['value']) == 2 |
134 | + |
135 | + matches = self.search_service._parse_search_response(response) |
136 | + self.assertThat(matches, HasLength(1)) |
137 | + self.assertEqual('GCleaner in Launchpad', matches[0].title) |
138 | + self.assertEqual('http://launchpad.dev/gcleaner', matches[0].url) |
139 | + |
140 | + def test_parse_search_response_missing_summary(self): |
141 | + """When a match is missing a summary ('snippet'), the match is skipped |
142 | + because there is no information about why it matched. This appears to |
143 | + relate to pages that are in the index, but should be removed. In this |
144 | + example taken from real data, the links are to the same page on |
145 | + different vhosts. The edge vhost has no summary, so it is skipped. |
146 | + """ |
147 | + file_name = path.join( |
148 | + self.base_path, 'bingsearchservice-missing-summary.json') |
149 | + with open(file_name, 'r') as response_file: |
150 | + response = response_file.read() |
151 | + assert len(json.loads(response)['webPages']['value']) == 2 |
152 | + |
153 | + matches = self.search_service._parse_search_response(response) |
154 | + self.assertThat(matches, HasLength(1)) |
155 | + self.assertEqual('BugExpiry - Launchpad Help', matches[0].title) |
156 | + self.assertEqual( |
157 | + 'https://help.launchpad.net/BugExpiry', matches[0].url) |
158 | + |
159 | + def test_parse_search_response_missing_url(self): |
160 | + """When the URL ('url') cannot be found the match is skipped. There are |
161 | + no examples of this. We do not want this hypothetical situation to give |
162 | + users a bad experience. |
163 | + """ |
164 | + file_name = path.join( |
165 | + self.base_path, 'bingsearchservice-missing-url.json') |
166 | + with open(file_name, 'r') as response_file: |
167 | + response = response_file.read() |
168 | + assert len(json.loads(response)['webPages']['value']) == 2 |
169 | + |
170 | + matches = self.search_service._parse_search_response(response) |
171 | + self.assertThat(matches, HasLength(1)) |
172 | + self.assertEqual('LongoMatch in Launchpad', matches[0].title) |
173 | + self.assertEqual('http://launchpad.dev/longomatch', matches[0].url) |
174 | + |
175 | + def test_parse_search_response_with_no_meaningful_results(self): |
176 | + """If no matches are found in the response, and there are 20 or fewer |
177 | + results, an Empty PageMatches is returned. This happens when the |
178 | + results are missing titles and summaries. This is not considered to be |
179 | + a problem because the small number implies that Bing did a poor job |
180 | + of indexing pages or indexed the wrong Launchpad server. In this |
181 | + example, there is only one match, but the results is missing a title so |
182 | + there is not enough information to make a PageMatch. |
183 | + """ |
184 | + file_name = path.join( |
185 | + self.base_path, 'bingsearchservice-no-meaningful-results.json') |
186 | + with open(file_name, 'r') as response_file: |
187 | + response = response_file.read() |
188 | + assert len(json.loads(response)['webPages']['value']) == 1 |
189 | + |
190 | + matches = self.search_service._parse_search_response(response) |
191 | + self.assertThat(matches, HasLength(0)) |
192 | |
193 | def test_search_converts_HTTPError(self): |
194 | # The method converts HTTPError to SiteSearchResponseError. |
195 | @@ -76,13 +222,13 @@ |
196 | self.search_service._parse_search_response, '{}') |
197 | |
198 | |
199 | -class FunctionalTestBingSearchService(TestCase): |
200 | +class FunctionalBingSearchServiceTests(TestCase): |
201 | """Test BingSearchService.""" |
202 | |
203 | layer = BingLaunchpadFunctionalLayer |
204 | |
205 | def setUp(self): |
206 | - super(FunctionalTestBingSearchService, self).setUp() |
207 | + super(FunctionalBingSearchServiceTests, self).setUp() |
208 | self.search_service = BingSearchService() |
209 | |
210 | def test_search_with_results(self): |
211 | @@ -96,6 +242,13 @@ |
212 | self.assertEqual(20, matches.start) |
213 | self.assertEqual(25, matches.total) |
214 | self.assertEqual(5, len(matches)) |
215 | + self.assertEqual([ |
216 | + 'https://help.launchpad.net/Bugs', |
217 | + 'http://blog.launchpad.dev/general/of-bugs-and-statuses', |
218 | + 'http://launchpad.dev/mahara/+milestone/1.8.0', |
219 | + 'http://launchpad.dev/mb', |
220 | + 'http://launchpad.dev/bugs'], |
221 | + [match.url for match in matches]) |
222 | |
223 | def test_search_no_results(self): |
224 | matches = self.search_service.search('fnord') |
225 | |
226 | === modified file 'lib/lp/services/sitesearch/tests/test_google.py' |
227 | --- lib/lp/services/sitesearch/tests/test_google.py 2018-04-12 19:46:41 +0000 |
228 | +++ lib/lp/services/sitesearch/tests/test_google.py 2018-04-13 19:49:46 +0000 |
229 | @@ -3,30 +3,214 @@ |
230 | |
231 | """Test the google search service.""" |
232 | |
233 | +from __future__ import absolute_import, print_function, unicode_literals |
234 | + |
235 | __metaclass__ = type |
236 | |
237 | +from os import path |
238 | |
239 | from fixtures import MockPatch |
240 | from requests.exceptions import ( |
241 | ConnectionError, |
242 | HTTPError, |
243 | ) |
244 | +from testtools.matchers import HasLength |
245 | |
246 | +from lp.services.config import config |
247 | from lp.services.sitesearch import GoogleSearchService |
248 | -from lp.services.sitesearch.interfaces import SiteSearchResponseError |
249 | +from lp.services.sitesearch.interfaces import ( |
250 | + GoogleWrongGSPVersion, |
251 | + SiteSearchResponseError, |
252 | + ) |
253 | from lp.services.timeout import TimeoutError |
254 | from lp.testing import TestCase |
255 | -from lp.testing.layers import LaunchpadFunctionalLayer |
256 | +from lp.testing.layers import GoogleLaunchpadFunctionalLayer |
257 | |
258 | |
259 | class TestGoogleSearchService(TestCase): |
260 | """Test GoogleSearchService.""" |
261 | |
262 | - layer = LaunchpadFunctionalLayer |
263 | - |
264 | def setUp(self): |
265 | super(TestGoogleSearchService, self).setUp() |
266 | self.search_service = GoogleSearchService() |
267 | + self.base_path = path.normpath( |
268 | + path.join(path.dirname(__file__), 'data')) |
269 | + |
270 | + def test_configuration(self): |
271 | + self.assertEqual(config.google.site, self.search_service.site) |
272 | + self.assertEqual( |
273 | + config.google.client_id, self.search_service.client_id) |
274 | + |
275 | + def test_create_search_url(self): |
276 | + self.assertEndsWith( |
277 | + self.search_service.create_search_url(terms='svg +bugs'), |
278 | + '&q=svg+%2Bbugs&start=0') |
279 | + |
280 | + def test_create_search_url_escapes_unicode_chars(self): |
281 | + self.assertEndsWith( |
282 | + self.search_service.create_search_url('Carlo Perell\xf3 Mar\xedn'), |
283 | + '&q=Carlo+Perell%C3%B3+Mar%C3%ADn&start=0') |
284 | + |
285 | + def test_create_search_url_with_offset(self): |
286 | + self.assertEndsWith( |
287 | + self.search_service.create_search_url(terms='svg +bugs', start=20), |
288 | + '&q=svg+%2Bbugs&start=20') |
289 | + |
290 | + def test_create_search_url_empty_terms(self): |
291 | + e = self.assertRaises( |
292 | + ValueError, self.search_service.create_search_url, '') |
293 | + self.assertEqual("Missing value for parameter 'q'.", str(e)) |
294 | + |
295 | + def test_create_search_url_null_terms(self): |
296 | + e = self.assertRaises( |
297 | + ValueError, self.search_service.create_search_url, None) |
298 | + self.assertEqual("Missing value for parameter 'q'.", str(e)) |
299 | + |
300 | + def test_create_search_url_requires_start(self): |
301 | + e = self.assertRaises( |
302 | + ValueError, self.search_service.create_search_url, 'bugs', 'true') |
303 | + self.assertEqual("Value for parameter 'start' is not an int.", str(e)) |
304 | + |
305 | + def test_parse_search_response_incompatible_param(self): |
306 | + """The PageMatches's start attribute comes from the GSP XML element |
307 | + '<PARAM name="start" value="0" original_value="0"/>'. When it cannot |
308 | + be found and the value cast to an int, an error is raised. There is |
309 | + nothing in the value attribute in the next test, so an error is raised. |
310 | + """ |
311 | + file_name = path.join( |
312 | + self.base_path, 'googlesearchservice-incompatible-param.xml') |
313 | + with open(file_name, 'r') as response_file: |
314 | + response = response_file.read() |
315 | + assert '<M>' not in response |
316 | + |
317 | + e = self.assertRaises( |
318 | + GoogleWrongGSPVersion, |
319 | + self.search_service._parse_search_response, response) |
320 | + self.assertEqual( |
321 | + "Could not get the 'start' from the GSP XML response.", str(e)) |
322 | + |
323 | + def test_parse_search_response_invalid_total(self): |
324 | + """The PageMatches's total attribute comes from the GSP XML element |
325 | + '<M>5</M>'. When it cannot be found and the value cast to an int, |
326 | + an error is raised. If Google were to redefine the meaning of the M |
327 | + element to use a '~' to indicate an approximate total, an error would |
328 | + be raised. |
329 | + """ |
330 | + file_name = path.join( |
331 | + self.base_path, 'googlesearchservice-incompatible-matches.xml') |
332 | + with open(file_name, 'r') as response_file: |
333 | + response = response_file.read() |
334 | + assert '<M>~1</M>' in response |
335 | + |
336 | + e = self.assertRaises( |
337 | + GoogleWrongGSPVersion, |
338 | + self.search_service._parse_search_response, response) |
339 | + self.assertEqual( |
340 | + "Could not get the 'total' from the GSP XML response.", str(e)) |
341 | + |
342 | + def test_parse_search_response_negative_total(self): |
343 | + """If the total is ever less than zero (see bug 683115), |
344 | + this is expected: we simply return a total of 0. |
345 | + """ |
346 | + file_name = path.join( |
347 | + self.base_path, 'googlesearchservice-negative-total.xml') |
348 | + with open(file_name, 'r') as response_file: |
349 | + response = response_file.read() |
350 | + assert '<M>-1</M>' in response |
351 | + |
352 | + matches = self.search_service._parse_search_response(response) |
353 | + self.assertEqual(0, matches.total) |
354 | + |
355 | + def test_parse_search_response_missing_title(self): |
356 | + """A PageMatch requires a title, url, and a summary. If those elements |
357 | + ('<T>', '<U>', '<S>') cannot be found nested in an '<R>' a PageMatch |
358 | + cannot be made. A missing title (<T>) indicates a bad page on Launchpad |
359 | + so it is ignored. In this example, The first match is missing a title, |
360 | + so only the second page is present in the PageMatches. |
361 | + """ |
362 | + file_name = path.join( |
363 | + self.base_path, 'googlesearchservice-missing-title.xml') |
364 | + with open(file_name, 'r') as response_file: |
365 | + response = response_file.read() |
366 | + |
367 | + matches = self.search_service._parse_search_response(response) |
368 | + self.assertThat(matches, HasLength(1)) |
369 | + self.assertStartsWith(matches[0].title, 'Bug #205991 in Ubuntu:') |
370 | + self.assertEqual( |
371 | + 'http://bugs.launchpad.dev/bugs/205991', matches[0].url) |
372 | + |
373 | + def test_parse_search_response_missing_summary(self): |
374 | + """When a match is missing a summary (<S>), it is skipped because |
375 | + there is no information about why it matched. This appears to relate to |
376 | + pages that are in the index, but should be removed. In this example |
377 | + taken from real data, the links are to the same page on different |
378 | + vhosts. The edge vhost has no summary, so it is skipped. |
379 | + """ |
380 | + file_name = path.join( |
381 | + self.base_path, 'googlesearchservice-missing-summary.xml') |
382 | + with open(file_name, 'r') as response_file: |
383 | + response = response_file.read() |
384 | + |
385 | + matches = self.search_service._parse_search_response(response) |
386 | + self.assertThat(matches, HasLength(1)) |
387 | + self.assertEqual('Blueprint: <b>Gobuntu</b> 8.04', matches[0].title) |
388 | + self.assertEqual( |
389 | + 'http://blueprints.launchpad.dev/ubuntu/+spec/gobuntu-hardy', |
390 | + matches[0].url) |
391 | + |
392 | + def test_parse_search_response_missing_url(self): |
393 | + """When the URL (<U>) cannot be found the match is skipped. There are |
394 | + no examples of this. We do not want this hypothetical situation to give |
395 | + users a bad experience. |
396 | + """ |
397 | + file_name = path.join( |
398 | + self.base_path, 'googlesearchservice-missing-url.xml') |
399 | + with open(file_name, 'r') as response_file: |
400 | + response = response_file.read() |
401 | + |
402 | + matches = self.search_service._parse_search_response(response) |
403 | + self.assertThat(matches, HasLength(1)) |
404 | + self.assertEqual('Blueprint: <b>Gobuntu</b> 8.04', matches[0].title) |
405 | + self.assertEqual( |
406 | + 'http://blueprints.launchpad.dev/ubuntu/+spec/gobuntu-hardy', |
407 | + matches[0].url) |
408 | + |
409 | + def test_parse_search_response_with_no_meaningful_results(self): |
410 | + """If no matches are found in the response, and there are 20 or fewer |
411 | + results, an Empty PageMatches is returned. This happens when the |
412 | + results are missing titles and summaries. This is not considered to be |
413 | + a problem because the small number implies that Google did a poor job |
414 | + of indexing pages or indexed the wrong Launchpad server. In this |
415 | + example, there is only one match, but the results is missing a title so |
416 | + there is not enough information to make a PageMatch. |
417 | + """ |
418 | + file_name = path.join( |
419 | + self.base_path, 'googlesearchservice-no-meaningful-results.xml') |
420 | + with open(file_name, 'r') as response_file: |
421 | + response = response_file.read() |
422 | + assert '<M>1</M>' in response |
423 | + |
424 | + matches = self.search_service._parse_search_response(response) |
425 | + self.assertThat(matches, HasLength(0)) |
426 | + |
427 | + def test_parse_search_response_with_incompatible_result(self): |
428 | + """If no matches are found in the response, and there are more than 20 |
429 | + possible matches, an error is raised. Unlike the previous example there |
430 | + are lots of results; there is a possibility that the GSP version is |
431 | + incompatible. This example says it has 1000 matches, but none of the R |
432 | + tags can be parsed (because the markup was changed to use RESULT). |
433 | + """ |
434 | + file_name = path.join( |
435 | + self.base_path, 'googlesearchservice-incompatible-result.xml') |
436 | + with open(file_name, 'r') as response_file: |
437 | + response = response_file.read() |
438 | + assert '<M>1000</M>' in response |
439 | + |
440 | + e = self.assertRaises( |
441 | + GoogleWrongGSPVersion, |
442 | + self.search_service._parse_search_response, response) |
443 | + self.assertEqual( |
444 | + "Could not get any PageMatches from the GSP XML response.", str(e)) |
445 | |
446 | def test_search_converts_HTTPError(self): |
447 | # The method converts HTTPError to SiteSearchResponseError. |
448 | @@ -71,3 +255,49 @@ |
449 | self.assertRaises( |
450 | SiteSearchResponseError, |
451 | self.search_service._parse_search_response, data) |
452 | + |
453 | + |
454 | +class FunctionalGoogleSearchServiceTests(TestCase): |
455 | + """Test GoogleSearchService.""" |
456 | + |
457 | + layer = GoogleLaunchpadFunctionalLayer |
458 | + |
459 | + def setUp(self): |
460 | + super(FunctionalGoogleSearchServiceTests, self).setUp() |
461 | + self.search_service = GoogleSearchService() |
462 | + |
463 | + def test_search_with_results(self): |
464 | + matches = self.search_service.search('bug') |
465 | + self.assertEqual(0, matches.start) |
466 | + self.assertEqual(25, matches.total) |
467 | + self.assertEqual(20, len(matches)) |
468 | + |
469 | + def test_search_with_results_and_offset(self): |
470 | + matches = self.search_service.search('bug', start=20) |
471 | + self.assertEqual(20, matches.start) |
472 | + self.assertEqual(25, matches.total) |
473 | + self.assertEqual(5, len(matches)) |
474 | + self.assertEqual([ |
475 | + 'http://bugs.launchpad.dev/ubuntu/hoary/+bug/2', |
476 | + 'http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/2', |
477 | + 'http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/3', |
478 | + 'http://bugs.launchpad.dev/bugs/bugtrackers', |
479 | + 'http://bugs.launchpad.dev/bugs/bugtrackers/debbugs'], |
480 | + [match.url for match in matches]) |
481 | + |
482 | + def test_search_no_results(self): |
483 | + matches = self.search_service.search('fnord') |
484 | + self.assertEqual(0, matches.start) |
485 | + self.assertEqual(0, matches.total) |
486 | + self.assertEqual(0, len(matches)) |
487 | + |
488 | + def test_search_no_meaningful_results(self): |
489 | + matches = self.search_service.search('no-meaningful') |
490 | + self.assertEqual(0, matches.start) |
491 | + self.assertEqual(1, matches.total) |
492 | + self.assertEqual(0, len(matches)) |
493 | + |
494 | + def test_search_incomplete_response(self): |
495 | + self.assertRaises( |
496 | + SiteSearchResponseError, |
497 | + self.search_service.search, 'gnomebaker') |
498 | |
499 | === modified file 'lib/lp/services/sitesearch/tests/test_pagematch.py' |
500 | --- lib/lp/services/sitesearch/tests/test_pagematch.py 2018-03-16 14:02:16 +0000 |
501 | +++ lib/lp/services/sitesearch/tests/test_pagematch.py 2018-04-13 19:49:46 +0000 |
502 | @@ -5,14 +5,59 @@ |
503 | |
504 | __metaclass__ = type |
505 | |
506 | -from lp.services.sitesearch import PageMatch |
507 | -from lp.testing import TestCaseWithFactory |
508 | -from lp.testing.layers import DatabaseFunctionalLayer |
509 | - |
510 | - |
511 | -class TestPageMatchURLHandling(TestCaseWithFactory): |
512 | - |
513 | - layer = DatabaseFunctionalLayer |
514 | +from lp.services.sitesearch import ( |
515 | + PageMatch, |
516 | + PageMatches, |
517 | + ) |
518 | +from lp.testing import TestCase |
519 | + |
520 | + |
521 | +class TestPageMatchURLHandling(TestCase): |
522 | + |
523 | + def test_attributes(self): |
524 | + p = PageMatch( |
525 | + u'Unicode Titles in Launchpad', |
526 | + 'http://example.com/unicode-titles', |
527 | + u'Unicode Titles is a modest project dedicated to using Unicode.') |
528 | + self.assertEqual(u'Unicode Titles in Launchpad', p.title) |
529 | + self.assertEqual( |
530 | + u'Unicode Titles is a modest project dedicated to using Unicode.', |
531 | + p.summary) |
532 | + self.assertEqual('http://example.com/unicode-titles', p.url) |
533 | + |
534 | + def test_rewrite_url(self): |
535 | + """The URL scheme used in the rewritten URL is configured via |
536 | + config.vhosts.use_https. The hostname is set in the shared |
537 | + key config.vhost.mainsite.hostname. |
538 | + """ |
539 | + p = PageMatch( |
540 | + u'Bug #456 in Unicode title: "testrunner hates Unicode"', |
541 | + 'https://bugs.launchpad.net/unicode-titles/+bug/456', |
542 | + u'The Zope testrunner likes ASCII more than Unicode.') |
543 | + self.assertEqual( |
544 | + 'http://bugs.launchpad.dev/unicode-titles/+bug/456', p.url) |
545 | + |
546 | + def test_rewrite_url_with_trailing_slash(self): |
547 | + """A URL's trailing slash is removed; Launchpad does not use trailing |
548 | + slashes. |
549 | + """ |
550 | + p = PageMatch( |
551 | + u'Ubuntu in Launchpad', |
552 | + 'https://launchpad.net/ubuntu/', |
553 | + u'Ubuntu also includes more software than any other operating') |
554 | + self.assertEqual('http://launchpad.dev/ubuntu', p.url) |
555 | + |
556 | + def test_rewrite_url_exceptions(self): |
557 | + """There is a list of URLs that are not rewritten configured in |
558 | + config.sitesearch.url_rewrite_exceptions. For example, |
559 | + help.launchpad.net is only run in one environment, so links to |
560 | + that site will be preserved. |
561 | + """ |
562 | + p = PageMatch( |
563 | + u'OpenID', |
564 | + 'https://help.launchpad.net/OpenID', |
565 | + u'Launchpad uses OpenID.') |
566 | + self.assertEqual('https://help.launchpad.net/OpenID', p.url) |
567 | |
568 | def test_rewrite_url_handles_invalid_data(self): |
569 | # Given a bad url, pagematch can get a valid one. |
570 | @@ -37,3 +82,24 @@ |
571 | "field.text=WUSB54GC+%2Bkarmic&" |
572 | "field.actions.search=Search") |
573 | self.assertEqual(expected, p.url) |
574 | + |
575 | + |
576 | +class TestPageMatches(TestCase): |
577 | + |
578 | + def test_initialisation(self): |
579 | + matches = PageMatches([], start=12, total=15) |
580 | + self.assertEqual(12, matches.start) |
581 | + self.assertEqual(15, matches.total) |
582 | + |
583 | + def test_len(self): |
584 | + matches = PageMatches(['match1', 'match2', 'match3'], 12, 15) |
585 | + self.assertEqual(3, len(matches)) |
586 | + |
587 | + def test_getitem(self): |
588 | + matches = PageMatches(['match1', 'match2', 'match3'], 12, 15) |
589 | + self.assertEqual('match2', matches[1]) |
590 | + |
591 | + def test_iter(self): |
592 | + matches = PageMatches(['match1', 'match2', 'match3'], 12, 15) |
593 | + self.assertEqual( |
594 | + ['match1', 'match2', 'match3'], [match for match in matches]) |