浏览代码

Merge pull request #210 from Cqoicebordel/unit-tests

unit tests
Adam Tauber 10 年前
父节点
当前提交
f6db77d81e

+ 3
- 6
searx/engines/currency_convert.py 查看文件

13
     if not m:
13
     if not m:
14
         # wrong query
14
         # wrong query
15
         return params
15
         return params
16
-    try:
17
-        ammount, from_currency, to_currency = m.groups()
18
-        ammount = float(ammount)
19
-    except:
20
-        # wrong params
21
-        return params
16
+
17
+    ammount, from_currency, to_currency = m.groups()
18
+    ammount = float(ammount)
22
 
19
 
23
     q = (from_currency + to_currency).upper()
20
     q = (from_currency + to_currency).upper()
24
 
21
 

+ 5
- 5
searx/engines/duckduckgo.py 查看文件

15
 
15
 
16
 from urllib import urlencode
16
 from urllib import urlencode
17
 from lxml.html import fromstring
17
 from lxml.html import fromstring
18
-from searx.utils import html_to_text
18
+from searx.engines.xpath import extract_text
19
 
19
 
20
 # engine dependent config
20
 # engine dependent config
21
 categories = ['general']
21
 categories = ['general']
28
 # specific xpath variables
28
 # specific xpath variables
29
 result_xpath = '//div[@class="results_links results_links_deep web-result"]'  # noqa
29
 result_xpath = '//div[@class="results_links results_links_deep web-result"]'  # noqa
30
 url_xpath = './/a[@class="large"]/@href'
30
 url_xpath = './/a[@class="large"]/@href'
31
-title_xpath = './/a[@class="large"]//text()'
32
-content_xpath = './/div[@class="snippet"]//text()'
31
+title_xpath = './/a[@class="large"]'
32
+content_xpath = './/div[@class="snippet"]'
33
 
33
 
34
 
34
 
35
 # do search-request
35
 # do search-request
64
         if not res_url:
64
         if not res_url:
65
             continue
65
             continue
66
 
66
 
67
-        title = html_to_text(''.join(r.xpath(title_xpath)))
68
-        content = html_to_text(''.join(r.xpath(content_xpath)))
67
+        title = extract_text(r.xpath(title_xpath))
68
+        content = extract_text(r.xpath(content_xpath))
69
 
69
 
70
         # append result
70
         # append result
71
         results.append({'title': title,
71
         results.append({'title': title,

+ 3
- 2
searx/engines/duckduckgo_definitions.py 查看文件

25
 
25
 
26
 
26
 
27
 def response(resp):
27
 def response(resp):
28
-    search_res = json.loads(resp.text)
29
     results = []
28
     results = []
30
 
29
 
30
+    search_res = json.loads(resp.text)
31
+
31
     content = ''
32
     content = ''
32
     heading = search_res.get('Heading', '')
33
     heading = search_res.get('Heading', '')
33
     attributes = []
34
     attributes = []
68
             results.append({'title': heading, 'url': firstURL})
69
             results.append({'title': heading, 'url': firstURL})
69
 
70
 
70
     # related topics
71
     # related topics
71
-    for ddg_result in search_res.get('RelatedTopics', None):
72
+    for ddg_result in search_res.get('RelatedTopics', []):
72
         if 'FirstURL' in ddg_result:
73
         if 'FirstURL' in ddg_result:
73
             suggestion = result_to_text(ddg_result.get('FirstURL', None),
74
             suggestion = result_to_text(ddg_result.get('FirstURL', None),
74
                                         ddg_result.get('Text', None),
75
                                         ddg_result.get('Text', None),

+ 3
- 5
searx/engines/faroo.py 查看文件

37
 
37
 
38
 # do search-request
38
 # do search-request
39
 def request(query, params):
39
 def request(query, params):
40
-    offset = (params['pageno']-1) * number_of_results + 1
40
+    offset = (params['pageno'] - 1) * number_of_results + 1
41
     categorie = search_category.get(params['category'], 'web')
41
     categorie = search_category.get(params['category'], 'web')
42
 
42
 
43
     if params['language'] == 'all':
43
     if params['language'] == 'all':
45
     else:
45
     else:
46
         language = params['language'].split('_')[0]
46
         language = params['language'].split('_')[0]
47
 
47
 
48
-    # skip, if language is not supported
48
+    # if language is not supported, put it in english
49
     if language != 'en' and\
49
     if language != 'en' and\
50
        language != 'de' and\
50
        language != 'de' and\
51
        language != 'zh':
51
        language != 'zh':
52
-        return params
52
+        language = 'en'
53
 
53
 
54
     params['url'] = search_url.format(offset=offset,
54
     params['url'] = search_url.format(offset=offset,
55
                                       number_of_results=number_of_results,
55
                                       number_of_results=number_of_results,
69
     # HTTP-Code 401: api-key is not valide
69
     # HTTP-Code 401: api-key is not valide
70
     if resp.status_code == 401:
70
     if resp.status_code == 401:
71
         raise Exception("API key is not valide")
71
         raise Exception("API key is not valide")
72
-        return []
73
 
72
 
74
     # HTTP-Code 429: rate limit exceeded
73
     # HTTP-Code 429: rate limit exceeded
75
     if resp.status_code == 429:
74
     if resp.status_code == 429:
76
         raise Exception("rate limit has been exceeded!")
75
         raise Exception("rate limit has been exceeded!")
77
-        return []
78
 
76
 
79
     results = []
77
     results = []
80
 
78
 

+ 5
- 4
searx/engines/openstreetmap.py 查看文件

38
 
38
 
39
     # parse results
39
     # parse results
40
     for r in json:
40
     for r in json:
41
+        if 'display_name' not in r:
42
+            continue
43
+
41
         title = r['display_name']
44
         title = r['display_name']
42
         osm_type = r.get('osm_type', r.get('type'))
45
         osm_type = r.get('osm_type', r.get('type'))
43
         url = result_base_url.format(osm_type=osm_type,
46
         url = result_base_url.format(osm_type=osm_type,
49
         geojson = r.get('geojson')
52
         geojson = r.get('geojson')
50
 
53
 
51
         # if no geojson is found and osm_type is a node, add geojson Point
54
         # if no geojson is found and osm_type is a node, add geojson Point
52
-        if not geojson and\
53
-           osm_type == 'node':
54
-            geojson = {u'type': u'Point',
55
-                       u'coordinates': [r['lon'], r['lat']]}
55
+        if not geojson and osm_type == 'node':
56
+            geojson = {u'type': u'Point', u'coordinates': [r['lon'], r['lat']]}
56
 
57
 
57
         address_raw = r.get('address')
58
         address_raw = r.get('address')
58
         address = {}
59
         address = {}

+ 1
- 1
searx/engines/photon.py 查看文件

61
             continue
61
             continue
62
 
62
 
63
         # get title
63
         # get title
64
-        title = properties['name']
64
+        title = properties.get('name')
65
 
65
 
66
         # get osm-type
66
         # get osm-type
67
         if properties.get('osm_type') == 'N':
67
         if properties.get('osm_type') == 'N':

+ 5
- 8
searx/engines/startpage.py 查看文件

13
 from lxml import html
13
 from lxml import html
14
 from cgi import escape
14
 from cgi import escape
15
 import re
15
 import re
16
+from searx.engines.xpath import extract_text
16
 
17
 
17
 # engine dependent config
18
 # engine dependent config
18
 categories = ['general']
19
 categories = ['general']
45
 
46
 
46
     # set language if specified
47
     # set language if specified
47
     if params['language'] != 'all':
48
     if params['language'] != 'all':
48
-        params['data']['with_language'] = ('lang_' +
49
-                                           params['language'].split('_')[0])
49
+        params['data']['with_language'] = ('lang_' + params['language'].split('_')[0])
50
 
50
 
51
     return params
51
     return params
52
 
52
 
64
             continue
64
             continue
65
         link = links[0]
65
         link = links[0]
66
         url = link.attrib.get('href')
66
         url = link.attrib.get('href')
67
-        try:
68
-            title = escape(link.text_content())
69
-        except UnicodeDecodeError:
70
-            continue
71
 
67
 
72
         # block google-ad url's
68
         # block google-ad url's
73
         if re.match("^http(s|)://www.google.[a-z]+/aclk.*$", url):
69
         if re.match("^http(s|)://www.google.[a-z]+/aclk.*$", url):
74
             continue
70
             continue
75
 
71
 
72
+        title = escape(extract_text(link))
73
+
76
         if result.xpath('./p[@class="desc"]'):
74
         if result.xpath('./p[@class="desc"]'):
77
-            content = escape(result.xpath('./p[@class="desc"]')[0]
78
-                             .text_content())
75
+            content = escape(extract_text(result.xpath('./p[@class="desc"]')))
79
         else:
76
         else:
80
             content = ''
77
             content = ''
81
 
78
 

+ 8
- 7
searx/engines/subtitleseeker.py 查看文件

12
 from urllib import quote_plus
12
 from urllib import quote_plus
13
 from lxml import html
13
 from lxml import html
14
 from searx.languages import language_codes
14
 from searx.languages import language_codes
15
+from searx.engines.xpath import extract_text
15
 
16
 
16
 # engine dependent config
17
 # engine dependent config
17
 categories = ['videos']
18
 categories = ['videos']
20
 
21
 
21
 # search-url
22
 # search-url
22
 url = 'http://www.subtitleseeker.com/'
23
 url = 'http://www.subtitleseeker.com/'
23
-search_url = url+'search/TITLES/{query}&p={pageno}'
24
+search_url = url + 'search/TITLES/{query}&p={pageno}'
24
 
25
 
25
 # specific xpath variables
26
 # specific xpath variables
26
 results_xpath = '//div[@class="boxRows"]'
27
 results_xpath = '//div[@class="boxRows"]'
44
     if resp.search_params['language'] != 'all':
45
     if resp.search_params['language'] != 'all':
45
         search_lang = [lc[1]
46
         search_lang = [lc[1]
46
                        for lc in language_codes
47
                        for lc in language_codes
47
-                       if lc[0][:2] == resp.search_params['language']][0]
48
+                       if lc[0][:2] == resp.search_params['language'].split('_')[0]][0]
48
 
49
 
49
     # parse results
50
     # parse results
50
     for result in dom.xpath(results_xpath):
51
     for result in dom.xpath(results_xpath):
56
         elif search_lang:
57
         elif search_lang:
57
             href = href + search_lang + '/'
58
             href = href + search_lang + '/'
58
 
59
 
59
-        title = escape(link.xpath(".//text()")[0])
60
+        title = escape(extract_text(link))
60
 
61
 
61
-        content = result.xpath('.//div[contains(@class,"red")]//text()')[0]
62
+        content = extract_text(result.xpath('.//div[contains(@class,"red")]'))
62
         content = content + " - "
63
         content = content + " - "
63
-        text = result.xpath('.//div[contains(@class,"grey-web")]')[0]
64
-        content = content + html.tostring(text, method='text')
64
+        text = extract_text(result.xpath('.//div[contains(@class,"grey-web")]')[0])
65
+        content = content + text
65
 
66
 
66
         if result.xpath(".//span") != []:
67
         if result.xpath(".//span") != []:
67
             content = content +\
68
             content = content +\
68
                 " - (" +\
69
                 " - (" +\
69
-                result.xpath(".//span//text()")[0].strip() +\
70
+                extract_text(result.xpath(".//span")) +\
70
                 ")"
71
                 ")"
71
 
72
 
72
         # append result
73
         # append result

+ 8
- 5
searx/engines/twitter.py 查看文件

13
 from urlparse import urljoin
13
 from urlparse import urljoin
14
 from urllib import urlencode
14
 from urllib import urlencode
15
 from lxml import html
15
 from lxml import html
16
-from cgi import escape
17
 from datetime import datetime
16
 from datetime import datetime
17
+from searx.engines.xpath import extract_text
18
 
18
 
19
 # engine dependent config
19
 # engine dependent config
20
 categories = ['social media']
20
 categories = ['social media']
22
 
22
 
23
 # search-url
23
 # search-url
24
 base_url = 'https://twitter.com/'
24
 base_url = 'https://twitter.com/'
25
-search_url = base_url+'search?'
25
+search_url = base_url + 'search?'
26
 
26
 
27
 # specific xpath variables
27
 # specific xpath variables
28
 results_xpath = '//li[@data-item-type="tweet"]'
28
 results_xpath = '//li[@data-item-type="tweet"]'
29
 link_xpath = './/small[@class="time"]//a'
29
 link_xpath = './/small[@class="time"]//a'
30
-title_xpath = './/span[@class="username js-action-profile-name"]//text()'
30
+title_xpath = './/span[@class="username js-action-profile-name"]'
31
 content_xpath = './/p[@class="js-tweet-text tweet-text"]'
31
 content_xpath = './/p[@class="js-tweet-text tweet-text"]'
32
 timestamp_xpath = './/span[contains(@class,"_timestamp")]'
32
 timestamp_xpath = './/span[contains(@class,"_timestamp")]'
33
 
33
 
39
     # set language if specified
39
     # set language if specified
40
     if params['language'] != 'all':
40
     if params['language'] != 'all':
41
         params['cookies']['lang'] = params['language'].split('_')[0]
41
         params['cookies']['lang'] = params['language'].split('_')[0]
42
+    else:
43
+        params['cookies']['lang'] = 'en'
42
 
44
 
43
     return params
45
     return params
44
 
46
 
53
     for tweet in dom.xpath(results_xpath):
55
     for tweet in dom.xpath(results_xpath):
54
         link = tweet.xpath(link_xpath)[0]
56
         link = tweet.xpath(link_xpath)[0]
55
         url = urljoin(base_url, link.attrib.get('href'))
57
         url = urljoin(base_url, link.attrib.get('href'))
56
-        title = ''.join(tweet.xpath(title_xpath))
57
-        content = escape(html.tostring(tweet.xpath(content_xpath)[0], method='text', encoding='UTF-8').decode("utf-8"))
58
+        title = extract_text(tweet.xpath(title_xpath))
59
+        content = extract_text(tweet.xpath(content_xpath)[0])
60
+
58
         pubdate = tweet.xpath(timestamp_xpath)
61
         pubdate = tweet.xpath(timestamp_xpath)
59
         if len(pubdate) > 0:
62
         if len(pubdate) > 0:
60
             timestamp = float(pubdate[0].attrib.get('data-time'))
63
             timestamp = float(pubdate[0].attrib.get('data-time'))

+ 11
- 8
searx/engines/yacy.py 查看文件

25
 # search-url
25
 # search-url
26
 base_url = 'http://localhost:8090'
26
 base_url = 'http://localhost:8090'
27
 search_url = '/yacysearch.json?{query}'\
27
 search_url = '/yacysearch.json?{query}'\
28
-                             '&startRecord={offset}'\
29
-                             '&maximumRecords={limit}'\
30
-                             '&contentdom={search_type}'\
31
-                             '&resource=global'             # noqa
28
+             '&startRecord={offset}'\
29
+             '&maximumRecords={limit}'\
30
+             '&contentdom={search_type}'\
31
+             '&resource=global'
32
 
32
 
33
 # yacy specific type-definitions
33
 # yacy specific type-definitions
34
 search_types = {'general': 'text',
34
 search_types = {'general': 'text',
41
 # do search-request
41
 # do search-request
42
 def request(query, params):
42
 def request(query, params):
43
     offset = (params['pageno'] - 1) * number_of_results
43
     offset = (params['pageno'] - 1) * number_of_results
44
-    search_type = search_types.get(params['category'], '0')
44
+    search_type = search_types.get(params.get('category'), '0')
45
 
45
 
46
     params['url'] = base_url +\
46
     params['url'] = base_url +\
47
         search_url.format(query=urlencode({'query': query}),
47
         search_url.format(query=urlencode({'query': query}),
66
     if not raw_search_results:
66
     if not raw_search_results:
67
         return []
67
         return []
68
 
68
 
69
-    search_results = raw_search_results.get('channels', {})[0].get('items', [])
69
+    search_results = raw_search_results.get('channels', [])
70
 
70
 
71
-    for result in search_results:
71
+    if len(search_results) == 0:
72
+        return []
73
+
74
+    for result in search_results[0].get('items', []):
72
         # parse image results
75
         # parse image results
73
         if result.get('image'):
76
         if result.get('image'):
74
             # append result
77
             # append result
88
                             'content': result['description'],
91
                             'content': result['description'],
89
                             'publishedDate': publishedDate})
92
                             'publishedDate': publishedDate})
90
 
93
 
91
-        #TODO parse video, audio and file results
94
+        # TODO parse video, audio and file results
92
 
95
 
93
     # return results
96
     # return results
94
     return results
97
     return results

+ 2
- 2
searx/engines/yahoo.py 查看文件

35
 def parse_url(url_string):
35
 def parse_url(url_string):
36
     endings = ['/RS', '/RK']
36
     endings = ['/RS', '/RK']
37
     endpositions = []
37
     endpositions = []
38
-    start = url_string.find('http', url_string.find('/RU=')+1)
38
+    start = url_string.find('http', url_string.find('/RU=') + 1)
39
 
39
 
40
     for ending in endings:
40
     for ending in endings:
41
         endpos = url_string.rfind(ending)
41
         endpos = url_string.rfind(ending)
91
                         'content': content})
91
                         'content': content})
92
 
92
 
93
     # if no suggestion found, return results
93
     # if no suggestion found, return results
94
-    if not suggestion_xpath:
94
+    if not dom.xpath(suggestion_xpath):
95
         return results
95
         return results
96
 
96
 
97
     # parse suggestion
97
     # parse suggestion

+ 44
- 0
searx/tests/engines/test_currency_convert.py 查看文件

1
+from collections import defaultdict
2
+from datetime import datetime
3
+import mock
4
+from searx.engines import currency_convert
5
+from searx.testing import SearxTestCase
6
+
7
+
8
+class TestCurrencyConvertEngine(SearxTestCase):
9
+
10
+    def test_request(self):
11
+        query = 'test_query'
12
+        dicto = defaultdict(dict)
13
+        dicto['pageno'] = 1
14
+        params = currency_convert.request(query, dicto)
15
+        self.assertNotIn('url', params)
16
+
17
+        query = '1.1.1 EUR in USD'
18
+        params = currency_convert.request(query, dicto)
19
+        self.assertNotIn('url', params)
20
+
21
+        query = '10 eur in usd'
22
+        params = currency_convert.request(query, dicto)
23
+        self.assertIn('url', params)
24
+        self.assertIn('finance.yahoo.com', params['url'])
25
+        self.assertIn('EUR', params['url'])
26
+        self.assertIn('USD', params['url'])
27
+
28
+    def test_response(self):
29
+        dicto = defaultdict(dict)
30
+        dicto['ammount'] = 10
31
+        dicto['from'] = "EUR"
32
+        dicto['to'] = "USD"
33
+        response = mock.Mock(text='a,b,c,d', search_params=dicto)
34
+        self.assertEqual(currency_convert.response(response), [])
35
+
36
+        csv = "2,0.5,1"
37
+        response = mock.Mock(text=csv, search_params=dicto)
38
+        results = currency_convert.response(response)
39
+        self.assertEqual(type(results), list)
40
+        self.assertEqual(len(results), 1)
41
+        self.assertEqual(results[0]['answer'], '10 EUR = 5.0 USD (1 EUR = 0.5 USD)')
42
+        now_date = datetime.now().strftime('%Y%m%d')
43
+        self.assertEqual(results[0]['url'], 'http://finance.yahoo.com/currency/converter-results/' +
44
+                                            now_date + '/10-eur-to-usd.html')

+ 90
- 0
searx/tests/engines/test_duckduckgo.py 查看文件

1
+from collections import defaultdict
2
+import mock
3
+from searx.engines import duckduckgo
4
+from searx.testing import SearxTestCase
5
+
6
+
7
+class TestDuckduckgoEngine(SearxTestCase):
8
+
9
+    def test_request(self):
10
+        query = 'test_query'
11
+        dicto = defaultdict(dict)
12
+        dicto['pageno'] = 1
13
+        dicto['language'] = 'fr_FR'
14
+        params = duckduckgo.request(query, dicto)
15
+        self.assertIn('url', params)
16
+        self.assertIn(query, params['url'])
17
+        self.assertIn('duckduckgo.com', params['url'])
18
+        self.assertIn('fr-fr', params['url'])
19
+
20
+        dicto['language'] = 'all'
21
+        params = duckduckgo.request(query, dicto)
22
+        self.assertIn('en-us', params['url'])
23
+
24
+    def test_response(self):
25
+        self.assertRaises(AttributeError, duckduckgo.response, None)
26
+        self.assertRaises(AttributeError, duckduckgo.response, [])
27
+        self.assertRaises(AttributeError, duckduckgo.response, '')
28
+        self.assertRaises(AttributeError, duckduckgo.response, '[]')
29
+
30
+        response = mock.Mock(text='<html></html>')
31
+        self.assertEqual(duckduckgo.response(response), [])
32
+
33
+        html = """
34
+        <div class="results_links results_links_deep web-result">
35
+            <div class="icon_fav" style="display: block;">
36
+                <a rel="nofollow" href="https://www.test.com/">
37
+                    <img width="16" height="16" alt=""
38
+                    src="/i/www.test.com.ico" style="visibility: visible;" name="i15" />
39
+                </a>
40
+            </div>
41
+            <div class="links_main links_deep"> <!-- This is the visible part -->
42
+                <a rel="nofollow" class="large" href="http://this.should.be.the.link/">
43
+                    This <b>is</b> <b>the</b> title
44
+                </a>
45
+                <div class="snippet"><b>This</b> should be the content.</div>
46
+                <div class="url">
47
+                    http://this.should.be.the.link/
48
+                </div>
49
+            </div>
50
+        </div>
51
+        """
52
+        response = mock.Mock(text=html)
53
+        results = duckduckgo.response(response)
54
+        self.assertEqual(type(results), list)
55
+        self.assertEqual(len(results), 1)
56
+        self.assertEqual(results[0]['title'], 'This is the title')
57
+        self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
58
+        self.assertEqual(results[0]['content'], 'This should be the content.')
59
+
60
+        html = """
61
+        <div class="results_links results_links_deep web-result">
62
+            <div class="icon_fav" style="display: block;">
63
+            </div>
64
+            <div class="links_main links_deep"> <!-- This is the visible part -->
65
+                <div class="snippet"><b>This</b> should be the content.</div>
66
+                <div class="url">
67
+                    http://this.should.be.the.link/
68
+                </div>
69
+            </div>
70
+        </div>
71
+        <div class="results_links results_links_deep web-result">
72
+            <div class="icon_fav" style="display: block;">
73
+                <img width="16" height="16" alt=""
74
+                src="/i/www.test.com.ico" style="visibility: visible;" name="i15" />
75
+            </div>
76
+            <div class="links_main links_deep"> <!-- This is the visible part -->
77
+                <a rel="nofollow" class="large" href="">
78
+                    This <b>is</b> <b>the</b> title
79
+                </a>
80
+                <div class="snippet"><b>This</b> should be the content.</div>
81
+                <div class="url">
82
+                    http://this.should.be.the.link/
83
+                </div>
84
+            </div>
85
+        </div>
86
+        """
87
+        response = mock.Mock(text=html)
88
+        results = duckduckgo.response(response)
89
+        self.assertEqual(type(results), list)
90
+        self.assertEqual(len(results), 0)

+ 250
- 0
searx/tests/engines/test_duckduckgo_definitions.py 查看文件

1
+from collections import defaultdict
2
+import mock
3
+from searx.engines import duckduckgo_definitions
4
+from searx.testing import SearxTestCase
5
+
6
+
7
+class TestDDGDefinitionsEngine(SearxTestCase):
8
+
9
+    def test_result_to_text(self):
10
+        url = ''
11
+        text = 'Text'
12
+        html_result = 'Html'
13
+        result = duckduckgo_definitions.result_to_text(url, text, html_result)
14
+        self.assertEqual(result, text)
15
+
16
+        html_result = '<a href="url">Text in link</a>'
17
+        result = duckduckgo_definitions.result_to_text(url, text, html_result)
18
+        self.assertEqual(result, 'Text in link')
19
+
20
+    def test_request(self):
21
+        query = 'test_query'
22
+        dicto = defaultdict(dict)
23
+        dicto['pageno'] = 1
24
+        params = duckduckgo_definitions.request(query, dicto)
25
+        self.assertIn('url', params)
26
+        self.assertIn(query, params['url'])
27
+        self.assertIn('duckduckgo.com', params['url'])
28
+
29
+    def test_response(self):
30
+        self.assertRaises(AttributeError, duckduckgo_definitions.response, None)
31
+        self.assertRaises(AttributeError, duckduckgo_definitions.response, [])
32
+        self.assertRaises(AttributeError, duckduckgo_definitions.response, '')
33
+        self.assertRaises(AttributeError, duckduckgo_definitions.response, '[]')
34
+
35
+        response = mock.Mock(text='{}')
36
+        self.assertEqual(duckduckgo_definitions.response(response), [])
37
+
38
+        response = mock.Mock(text='{"data": []}')
39
+        self.assertEqual(duckduckgo_definitions.response(response), [])
40
+
41
+        json = """
42
+        {
43
+          "DefinitionSource": "definition source",
44
+          "Heading": "heading",
45
+          "ImageWidth": 0,
46
+          "RelatedTopics": [
47
+            {
48
+              "Result": "Top-level domains",
49
+              "Icon": {
50
+                "URL": "",
51
+                "Height": "",
52
+                "Width": ""
53
+              },
54
+              "FirstURL": "https://first.url",
55
+              "Text": "text"
56
+            },
57
+            {
58
+              "Topics": [
59
+                {
60
+                  "Result": "result topic",
61
+                  "Icon": {
62
+                    "URL": "",
63
+                    "Height": "",
64
+                    "Width": ""
65
+                  },
66
+                  "FirstURL": "https://duckduckgo.com/?q=2%2F2",
67
+                  "Text": "result topic text"
68
+                }
69
+              ],
70
+              "Name": "name"
71
+            }
72
+          ],
73
+          "Entity": "Entity",
74
+          "Type": "A",
75
+          "Redirect": "",
76
+          "DefinitionURL": "http://definition.url",
77
+          "AbstractURL": "https://abstract.url",
78
+          "Definition": "this is the definition",
79
+          "AbstractSource": "abstract source",
80
+          "Infobox": {
81
+            "content": [
82
+              {
83
+                "data_type": "string",
84
+                "value": "1999",
85
+                "label": "Introduced",
86
+                "wiki_order": 0
87
+              }
88
+            ],
89
+            "meta": [
90
+              {
91
+                "data_type": "string",
92
+                "value": ".test",
93
+                "label": "article_title"
94
+              }
95
+            ]
96
+          },
97
+          "Image": "image.png",
98
+          "ImageIsLogo": 0,
99
+          "Abstract": "abstract",
100
+          "AbstractText": "abstract text",
101
+          "AnswerType": "",
102
+          "ImageHeight": 0,
103
+          "Results": [{
104
+                 "Result" : "result title",
105
+                 "Icon" : {
106
+                    "URL" : "result url",
107
+                    "Height" : 16,
108
+                    "Width" : 16
109
+                 },
110
+                 "FirstURL" : "result first url",
111
+                 "Text" : "result text"
112
+              }
113
+          ],
114
+          "Answer": "answer"
115
+        }
116
+        """
117
+        response = mock.Mock(text=json)
118
+        results = duckduckgo_definitions.response(response)
119
+        self.assertEqual(type(results), list)
120
+        self.assertEqual(len(results), 4)
121
+        self.assertEqual(results[0]['answer'], 'answer')
122
+        self.assertEqual(results[1]['title'], 'heading')
123
+        self.assertEqual(results[1]['url'], 'result first url')
124
+        self.assertEqual(results[2]['suggestion'], 'text')
125
+        self.assertEqual(results[3]['infobox'], 'heading')
126
+        self.assertEqual(results[3]['id'], 'http://definition.url')
127
+        self.assertEqual(results[3]['entity'], 'Entity')
128
+        self.assertIn('abstract', results[3]['content'])
129
+        self.assertIn('this is the definition', results[3]['content'])
130
+        self.assertEqual(results[3]['img_src'], 'image.png')
131
+        self.assertIn('Introduced', results[3]['attributes'][0]['label'])
132
+        self.assertIn('1999', results[3]['attributes'][0]['value'])
133
+        self.assertIn({'url': 'https://abstract.url', 'title': 'abstract source'}, results[3]['urls'])
134
+        self.assertIn({'url': 'http://definition.url', 'title': 'definition source'}, results[3]['urls'])
135
+        self.assertIn({'name': 'name', 'suggestions': ['result topic text']}, results[3]['relatedTopics'])
136
+
137
+        json = """
138
+        {
139
+          "DefinitionSource": "definition source",
140
+          "Heading": "heading",
141
+          "ImageWidth": 0,
142
+          "RelatedTopics": [],
143
+          "Entity": "Entity",
144
+          "Type": "A",
145
+          "Redirect": "",
146
+          "DefinitionURL": "",
147
+          "AbstractURL": "https://abstract.url",
148
+          "Definition": "",
149
+          "AbstractSource": "abstract source",
150
+          "Image": "",
151
+          "ImageIsLogo": 0,
152
+          "Abstract": "",
153
+          "AbstractText": "abstract text",
154
+          "AnswerType": "",
155
+          "ImageHeight": 0,
156
+          "Results": [],
157
+          "Answer": ""
158
+        }
159
+        """
160
+        response = mock.Mock(text=json)
161
+        results = duckduckgo_definitions.response(response)
162
+        self.assertEqual(type(results), list)
163
+        self.assertEqual(len(results), 1)
164
+        self.assertEqual(results[0]['url'], 'https://abstract.url')
165
+        self.assertEqual(results[0]['title'], 'heading')
166
+        self.assertEqual(results[0]['content'], '')
167
+
168
+        json = """
169
+        {
170
+          "DefinitionSource": "definition source",
171
+          "Heading": "heading",
172
+          "ImageWidth": 0,
173
+          "RelatedTopics": [
174
+            {
175
+              "Result": "Top-level domains",
176
+              "Icon": {
177
+                "URL": "",
178
+                "Height": "",
179
+                "Width": ""
180
+              },
181
+              "FirstURL": "https://first.url",
182
+              "Text": "heading"
183
+            },
184
+            {
185
+              "Name": "name"
186
+            },
187
+            {
188
+              "Topics": [
189
+                {
190
+                  "Result": "result topic",
191
+                  "Icon": {
192
+                    "URL": "",
193
+                    "Height": "",
194
+                    "Width": ""
195
+                  },
196
+                  "FirstURL": "https://duckduckgo.com/?q=2%2F2",
197
+                  "Text": "heading"
198
+                }
199
+              ],
200
+              "Name": "name"
201
+            }
202
+          ],
203
+          "Entity": "Entity",
204
+          "Type": "A",
205
+          "Redirect": "",
206
+          "DefinitionURL": "http://definition.url",
207
+          "AbstractURL": "https://abstract.url",
208
+          "Definition": "this is the definition",
209
+          "AbstractSource": "abstract source",
210
+          "Infobox": {
211
+            "meta": [
212
+              {
213
+                "data_type": "string",
214
+                "value": ".test",
215
+                "label": "article_title"
216
+              }
217
+            ]
218
+          },
219
+          "Image": "image.png",
220
+          "ImageIsLogo": 0,
221
+          "Abstract": "abstract",
222
+          "AbstractText": "abstract text",
223
+          "AnswerType": "",
224
+          "ImageHeight": 0,
225
+          "Results": [{
226
+                 "Result" : "result title",
227
+                 "Icon" : {
228
+                    "URL" : "result url",
229
+                    "Height" : 16,
230
+                    "Width" : 16
231
+                 },
232
+                 "Text" : "result text"
233
+              }
234
+          ],
235
+          "Answer": ""
236
+        }
237
+        """
238
+        response = mock.Mock(text=json)
239
+        results = duckduckgo_definitions.response(response)
240
+        self.assertEqual(type(results), list)
241
+        self.assertEqual(len(results), 1)
242
+        self.assertEqual(results[0]['infobox'], 'heading')
243
+        self.assertEqual(results[0]['id'], 'http://definition.url')
244
+        self.assertEqual(results[0]['entity'], 'Entity')
245
+        self.assertIn('abstract', results[0]['content'])
246
+        self.assertIn('this is the definition', results[0]['content'])
247
+        self.assertEqual(results[0]['img_src'], 'image.png')
248
+        self.assertIn({'url': 'https://abstract.url', 'title': 'abstract source'}, results[0]['urls'])
249
+        self.assertIn({'url': 'http://definition.url', 'title': 'definition source'}, results[0]['urls'])
250
+        self.assertIn({'name': 'name', 'suggestions': []}, results[0]['relatedTopics'])

+ 116
- 0
searx/tests/engines/test_faroo.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+import mock
4
+from searx.engines import faroo
5
+from searx.testing import SearxTestCase
6
+
7
+
8
+class TestFarooEngine(SearxTestCase):
9
+
10
+    def test_request(self):
11
+        query = 'test_query'
12
+        dicto = defaultdict(dict)
13
+        dicto['pageno'] = 1
14
+        dicto['language'] = 'fr_FR'
15
+        dicto['category'] = 'general'
16
+        params = faroo.request(query, dicto)
17
+        self.assertIn('url', params)
18
+        self.assertIn(query, params['url'])
19
+        self.assertIn('faroo.com', params['url'])
20
+        self.assertIn('en', params['url'])
21
+        self.assertIn('web', params['url'])
22
+
23
+        dicto['language'] = 'all'
24
+        params = faroo.request(query, dicto)
25
+        self.assertIn('en', params['url'])
26
+
27
+        dicto['language'] = 'de_DE'
28
+        params = faroo.request(query, dicto)
29
+        self.assertIn('de', params['url'])
30
+
31
+    def test_response(self):
32
+        self.assertRaises(AttributeError, faroo.response, None)
33
+        self.assertRaises(AttributeError, faroo.response, [])
34
+        self.assertRaises(AttributeError, faroo.response, '')
35
+        self.assertRaises(AttributeError, faroo.response, '[]')
36
+
37
+        response = mock.Mock(text='{}')
38
+        self.assertEqual(faroo.response(response), [])
39
+
40
+        response = mock.Mock(text='{"data": []}')
41
+        self.assertEqual(faroo.response(response), [])
42
+
43
+        response = mock.Mock(text='{"data": []}', status_code=401)
44
+        self.assertRaises(Exception, faroo.response, response)
45
+
46
+        response = mock.Mock(text='{"data": []}', status_code=429)
47
+        self.assertRaises(Exception, faroo.response, response)
48
+
49
+        json = """
50
+        {
51
+          "results": [
52
+            {
53
+              "title": "This is the title",
54
+              "kwic": "This is the content",
55
+              "content": "",
56
+              "url": "http://this.is.the.url/",
57
+              "iurl": "",
58
+              "domain": "css3test.com",
59
+              "author": "Jim Dalrymple",
60
+              "news": true,
61
+              "votes": "10",
62
+              "date": 1360622563000,
63
+              "related": []
64
+            },
65
+            {
66
+              "title": "This is the title2",
67
+              "kwic": "This is the content2",
68
+              "content": "",
69
+              "url": "http://this.is.the.url2/",
70
+              "iurl": "",
71
+              "domain": "css3test.com",
72
+              "author": "Jim Dalrymple",
73
+              "news": false,
74
+              "votes": "10",
75
+              "related": []
76
+            },
77
+            {
78
+              "title": "This is the title3",
79
+              "kwic": "This is the content3",
80
+              "content": "",
81
+              "url": "http://this.is.the.url3/",
82
+              "iurl": "http://upload.wikimedia.org/optimized.jpg",
83
+              "domain": "css3test.com",
84
+              "author": "Jim Dalrymple",
85
+              "news": false,
86
+              "votes": "10",
87
+              "related": []
88
+            }
89
+          ],
90
+          "query": "test",
91
+          "suggestions": [],
92
+          "count": 100,
93
+          "start": 1,
94
+          "length": 10,
95
+          "time": "15"
96
+        }
97
+        """
98
+        response = mock.Mock(text=json)
99
+        results = faroo.response(response)
100
+        self.assertEqual(type(results), list)
101
+        self.assertEqual(len(results), 4)
102
+        self.assertEqual(results[0]['title'], 'This is the title')
103
+        self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
104
+        self.assertEqual(results[0]['content'], 'This is the content')
105
+        self.assertEqual(results[1]['title'], 'This is the title2')
106
+        self.assertEqual(results[1]['url'], 'http://this.is.the.url2/')
107
+        self.assertEqual(results[1]['content'], 'This is the content2')
108
+        self.assertEqual(results[3]['img_src'], 'http://upload.wikimedia.org/optimized.jpg')
109
+
110
+        json = """
111
+        {}
112
+        """
113
+        response = mock.Mock(text=json)
114
+        results = faroo.response(response)
115
+        self.assertEqual(type(results), list)
116
+        self.assertEqual(len(results), 0)

+ 162
- 0
searx/tests/engines/test_google.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+import mock
4
+import lxml
5
+from searx.engines import google
6
+from searx.testing import SearxTestCase
7
+
8
+
9
+class TestGoogleEngine(SearxTestCase):
10
+
11
+    def test_request(self):
12
+        query = 'test_query'
13
+        dicto = defaultdict(dict)
14
+        dicto['pageno'] = 1
15
+        dicto['language'] = 'fr_FR'
16
+        params = google.request(query, dicto)
17
+        self.assertIn('url', params)
18
+        self.assertIn(query, params['url'])
19
+        self.assertIn('google.com', params['url'])
20
+        self.assertIn('PREF', params['cookies'])
21
+        self.assertIn('fr', params['headers']['Accept-Language'])
22
+
23
+        dicto['language'] = 'all'
24
+        params = google.request(query, dicto)
25
+        self.assertIn('en', params['headers']['Accept-Language'])
26
+
27
+    def test_response(self):
28
+        self.assertRaises(AttributeError, google.response, None)
29
+        self.assertRaises(AttributeError, google.response, [])
30
+        self.assertRaises(AttributeError, google.response, '')
31
+        self.assertRaises(AttributeError, google.response, '[]')
32
+
33
+        response = mock.Mock(text='<html></html>')
34
+        self.assertEqual(google.response(response), [])
35
+
36
+        html = """
37
+        <li class="g">
38
+            <h3 class="r">
39
+                <a href="http://this.should.be.the.link/">
40
+                    <b>This</b> is <b>the</b> title
41
+                </a>
42
+            </h3>
43
+            <div class="s">
44
+                <div class="kv" style="margin-bottom:2px">
45
+                    <cite>
46
+                        <b>test</b>.psychologies.com/
47
+                    </cite>
48
+                    <div class="_nBb">‎
49
+                        <div style="display:inline" onclick="google.sham(this);" aria-expanded="false"
50
+                            aria-haspopup="true" tabindex="0" data-ved="0CBUQ7B0wAA">
51
+                            <span class="_O0">
52
+                            </span>
53
+                        </div>
54
+                        <div style="display:none" class="am-dropdown-menu" role="menu" tabindex="-1">
55
+                            <ul>
56
+                                <li class="_Ykb">
57
+                                    <a class="_Zkb" href="http://www.google.fr/url?url=http://webcache.googleusercontent
58
+                                        .com/search%3Fcache:R1Z_4pGXjuIJ:http://test.psychologies.com/">
59
+                                        En cache
60
+                                    </a>
61
+                                </li>
62
+                                <li class="_Ykb">
63
+                                    <a class="_Zkb" href="/search?safe=off&amp;q=related:test.psy.com/">
64
+                                        Pages similaires
65
+                                    </a>
66
+                                </li>
67
+                            </ul>
68
+                        </div>
69
+                    </div>
70
+                </div>
71
+                <span class="st">
72
+                    This should be the content.
73
+                </span>
74
+                <br>
75
+                <div class="osl">‎
76
+                    <a href="http://www.google.fr/url?url=http://test.psychologies.com/tests/">
77
+                        Test Personnalité
78
+                    </a> - ‎
79
+                    <a href="http://www.google.fr/url?url=http://test.psychologies.com/test/">
80
+                        Tests - Moi
81
+                    </a> - ‎
82
+                    <a href="http://www.google.fr/url?url=http://test.psychologies.com/test/tests-couple">
83
+                        Test Couple
84
+                    </a>
85
+                    - ‎
86
+                    <a href="http://www.google.fr/url?url=http://test.psychologies.com/tests/tests-amour">
87
+                        Test Amour
88
+                    </a>
89
+                </div>
90
+            </div>
91
+        </li>
92
+        <li class="g">
93
+            <h3 class="r">
94
+                <a href="http://www.google.com/images?q=toto">
95
+                    <b>This</b>
96
+                </a>
97
+            </h3>
98
+        </li>
99
+        <li class="g">
100
+            <h3 class="r">
101
+                <a href="http://www.google.com/search?q=toto">
102
+                    <b>This</b> is
103
+                </a>
104
+            </h3>
105
+        </li>
106
+        <li class="g">
107
+            <h3 class="r">
108
+                <a href="€">
109
+                    <b>This</b> is <b>the</b>
110
+                </a>
111
+            </h3>
112
+        </li>
113
+        <li class="g">
114
+            <h3 class="r">
115
+                <a href="/url?q=url">
116
+                    <b>This</b> is <b>the</b>
117
+                </a>
118
+            </h3>
119
+        </li>
120
+        <p class="_Bmc" style="margin:3px 8px">
121
+            <a href="/search?num=20&amp;safe=off&amp;q=t&amp;revid=1754833769&amp;sa=X&amp;ei=-&amp;ved=">
122
+                suggestion <b>title</b>
123
+            </a>
124
+        </p>
125
+        """
126
+        response = mock.Mock(text=html)
127
+        results = google.response(response)
128
+        self.assertEqual(type(results), list)
129
+        self.assertEqual(len(results), 2)
130
+        self.assertEqual(results[0]['title'], 'This is the title')
131
+        self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
132
+        self.assertEqual(results[0]['content'], 'This should be the content.')
133
+        self.assertEqual(results[1]['suggestion'], 'suggestion title')
134
+
135
+        html = """
136
+        <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
137
+        </li>
138
+        """
139
+        response = mock.Mock(text=html)
140
+        results = google.response(response)
141
+        self.assertEqual(type(results), list)
142
+        self.assertEqual(len(results), 0)
143
+
144
+    def test_parse_images(self):
145
+        html = """
146
+        <li>
147
+            <div>
148
+                <a href="http://www.google.com/url?q=http://this.is.the.url/">
149
+                    <img style="margin:3px 0;margin-right:6px;padding:0" height="90"
150
+                        src="https://this.is.the.image/image.jpg" width="60" align="middle" alt="" border="0">
151
+                </a>
152
+            </div>
153
+        </li>
154
+        """
155
+        dom = lxml.html.fromstring(html)
156
+        results = google.parse_images(dom)
157
+        self.assertEqual(type(results), list)
158
+        self.assertEqual(len(results), 1)
159
+        self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
160
+        self.assertEqual(results[0]['title'], '')
161
+        self.assertEqual(results[0]['content'], '')
162
+        self.assertEqual(results[0]['img_src'], 'https://this.is.the.image/image.jpg')

+ 130
- 0
searx/tests/engines/test_mediawiki.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+import mock
4
+from searx.engines import mediawiki
5
+from searx.testing import SearxTestCase
6
+
7
+
8
+class TestMediawikiEngine(SearxTestCase):
9
+
10
+    def test_request(self):
11
+        query = 'test_query'
12
+        dicto = defaultdict(dict)
13
+        dicto['pageno'] = 1
14
+        dicto['language'] = 'fr_FR'
15
+        params = mediawiki.request(query, dicto)
16
+        self.assertIn('url', params)
17
+        self.assertIn(query, params['url'])
18
+        self.assertIn('wikipedia.org', params['url'])
19
+        self.assertIn('fr', params['url'])
20
+
21
+        dicto['language'] = 'all'
22
+        params = mediawiki.request(query, dicto)
23
+        self.assertIn('en', params['url'])
24
+
25
+        mediawiki.base_url = "http://test.url/"
26
+        mediawiki.search_url = mediawiki.base_url +\
27
+                                 'w/api.php?action=query'\
28
+                                 '&list=search'\
29
+                                 '&{query}'\
30
+                                 '&srprop=timestamp'\
31
+                                 '&format=json'\
32
+                                 '&sroffset={offset}'\
33
+                                 '&srlimit={limit}'     # noqa
34
+        params = mediawiki.request(query, dicto)
35
+        self.assertIn('test.url', params['url'])
36
+
37
+    def test_response(self):
38
+        dicto = defaultdict(dict)
39
+        dicto['language'] = 'fr'
40
+        mediawiki.base_url = "https://{language}.wikipedia.org/"
41
+
42
+        self.assertRaises(AttributeError, mediawiki.response, None)
43
+        self.assertRaises(AttributeError, mediawiki.response, [])
44
+        self.assertRaises(AttributeError, mediawiki.response, '')
45
+        self.assertRaises(AttributeError, mediawiki.response, '[]')
46
+
47
+        response = mock.Mock(text='{}', search_params=dicto)
48
+        self.assertEqual(mediawiki.response(response), [])
49
+
50
+        response = mock.Mock(text='{"data": []}', search_params=dicto)
51
+        self.assertEqual(mediawiki.response(response), [])
52
+
53
+        json = """
54
+        {
55
+            "query-continue": {
56
+                "search": {
57
+                    "sroffset": 1
58
+                }
59
+            },
60
+            "query": {
61
+                "searchinfo": {
62
+                    "totalhits": 29721
63
+                },
64
+                "search": [
65
+                    {
66
+                        "ns": 0,
67
+                        "title": "This is the title étude",
68
+                        "timestamp": "2014-12-19T17:42:52Z"
69
+                    }
70
+                ]
71
+            }
72
+        }
73
+        """
74
+        response = mock.Mock(text=json, search_params=dicto)
75
+        results = mediawiki.response(response)
76
+        self.assertEqual(type(results), list)
77
+        self.assertEqual(len(results), 1)
78
+        self.assertEqual(results[0]['title'], u'This is the title étude')
79
+        self.assertIn('fr.wikipedia.org', results[0]['url'])
80
+        self.assertIn('This_is_the_title', results[0]['url'])
81
+        self.assertIn('%C3%A9tude', results[0]['url'])
82
+        self.assertEqual(results[0]['content'], '')
83
+
84
+        json = """
85
+        {
86
+            "query-continue": {
87
+                "search": {
88
+                    "sroffset": 1
89
+                }
90
+            },
91
+            "query": {
92
+                "searchinfo": {
93
+                    "totalhits": 29721
94
+                },
95
+                "search": [
96
+                ]
97
+            }
98
+        }
99
+        """
100
+        response = mock.Mock(text=json, search_params=dicto)
101
+        results = mediawiki.response(response)
102
+        self.assertEqual(type(results), list)
103
+        self.assertEqual(len(results), 0)
104
+
105
+        json = """
106
+        {
107
+            "query-continue": {
108
+                "search": {
109
+                    "sroffset": 1
110
+                }
111
+            },
112
+            "query": {
113
+            }
114
+        }
115
+        """
116
+        response = mock.Mock(text=json, search_params=dicto)
117
+        results = mediawiki.response(response)
118
+        self.assertEqual(type(results), list)
119
+        self.assertEqual(len(results), 0)
120
+
121
+        json = """
122
+        {"toto":[
123
+            {"id":200,"name":"Artist Name",
124
+            "link":"http:\/\/www.mediawiki.com\/artist\/1217","type":"artist"}
125
+        ]}
126
+        """
127
+        response = mock.Mock(text=json, search_params=dicto)
128
+        results = mediawiki.response(response)
129
+        self.assertEqual(type(results), list)
130
+        self.assertEqual(len(results), 0)

+ 199
- 0
searx/tests/engines/test_openstreetmap.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+import mock
4
+from searx.engines import openstreetmap
5
+from searx.testing import SearxTestCase
6
+
7
+
8
+class TestOpenstreetmapEngine(SearxTestCase):
9
+
10
+    def test_request(self):
11
+        query = 'test_query'
12
+        dicto = defaultdict(dict)
13
+        dicto['pageno'] = 1
14
+        params = openstreetmap.request(query, dicto)
15
+        self.assertIn('url', params)
16
+        self.assertIn(query, params['url'])
17
+        self.assertIn('openstreetmap.org', params['url'])
18
+
19
+    def test_response(self):
20
+        self.assertRaises(AttributeError, openstreetmap.response, None)
21
+        self.assertRaises(AttributeError, openstreetmap.response, [])
22
+        self.assertRaises(AttributeError, openstreetmap.response, '')
23
+        self.assertRaises(AttributeError, openstreetmap.response, '[]')
24
+
25
+        response = mock.Mock(text='{}')
26
+        self.assertEqual(openstreetmap.response(response), [])
27
+
28
+        response = mock.Mock(text='{"data": []}')
29
+        self.assertEqual(openstreetmap.response(response), [])
30
+
31
+        json = """
32
+        [
33
+          {
34
+            "place_id": "127732055",
35
+            "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
36
+            "osm_type": "relation",
37
+            "osm_id": "7444",
38
+            "boundingbox": [
39
+              "48.8155755",
40
+              "48.902156",
41
+              "2.224122",
42
+              "2.4697602"
43
+            ],
44
+            "lat": "48.8565056",
45
+            "lon": "2.3521334",
46
+            "display_name": "This is the title",
47
+            "class": "place",
48
+            "type": "city",
49
+            "importance": 0.96893459932191,
50
+            "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
51
+            "address": {
52
+              "city": "Paris",
53
+              "county": "Paris",
54
+              "state": "Île-de-France",
55
+              "country": "France",
56
+              "country_code": "fr"
57
+            },
58
+            "geojson": {
59
+              "type": "Polygon",
60
+              "coordinates": [
61
+                [
62
+                  [
63
+                    2.224122,
64
+                    48.854199
65
+                  ]
66
+                ]
67
+              ]
68
+            }
69
+          }
70
+        ]
71
+        """
72
+        response = mock.Mock(text=json)
73
+        results = openstreetmap.response(response)
74
+        self.assertEqual(type(results), list)
75
+        self.assertEqual(len(results), 1)
76
+        self.assertEqual(results[0]['title'], 'This is the title')
77
+        self.assertEqual(results[0]['url'], 'https://openstreetmap.org/relation/7444')
78
+        self.assertIn('coordinates', results[0]['geojson'])
79
+        self.assertEqual(results[0]['geojson']['coordinates'][0][0][0], 2.224122)
80
+        self.assertEqual(results[0]['geojson']['coordinates'][0][0][1], 48.854199)
81
+        self.assertEqual(results[0]['address'], None)
82
+        self.assertIn('48.8155755', results[0]['boundingbox'])
83
+        self.assertIn('48.902156', results[0]['boundingbox'])
84
+        self.assertIn('2.224122', results[0]['boundingbox'])
85
+        self.assertIn('2.4697602', results[0]['boundingbox'])
86
+
87
+        json = """
88
+        [
89
+          {
90
+            "place_id": "127732055",
91
+            "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
92
+            "osm_type": "relation",
93
+            "osm_id": "7444",
94
+            "boundingbox": [
95
+              "48.8155755",
96
+              "48.902156",
97
+              "2.224122",
98
+              "2.4697602"
99
+            ],
100
+            "lat": "48.8565056",
101
+            "lon": "2.3521334",
102
+            "display_name": "This is the title",
103
+            "class": "tourism",
104
+            "type": "city",
105
+            "importance": 0.96893459932191,
106
+            "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
107
+            "address": {
108
+              "city": "Paris",
109
+              "county": "Paris",
110
+              "state": "Île-de-France",
111
+              "country": "France",
112
+              "country_code": "fr",
113
+              "address29": "Address"
114
+            },
115
+            "geojson": {
116
+              "type": "Polygon",
117
+              "coordinates": [
118
+                [
119
+                  [
120
+                    2.224122,
121
+                    48.854199
122
+                  ]
123
+                ]
124
+              ]
125
+            }
126
+          },
127
+          {
128
+            "place_id": "127732055",
129
+            "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
130
+            "osm_type": "relation",
131
+            "osm_id": "7444",
132
+            "boundingbox": [
133
+              "48.8155755",
134
+              "48.902156",
135
+              "2.224122",
136
+              "2.4697602"
137
+            ],
138
+            "lat": "48.8565056",
139
+            "lon": "2.3521334",
140
+            "display_name": "This is the title",
141
+            "class": "tourism",
142
+            "type": "city",
143
+            "importance": 0.96893459932191,
144
+            "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
145
+            "address": {
146
+              "city": "Paris",
147
+              "county": "Paris",
148
+              "state": "Île-de-France",
149
+              "country": "France",
150
+              "postcode": 75000,
151
+              "country_code": "fr"
152
+            },
153
+            "geojson": {
154
+              "type": "Polygon",
155
+              "coordinates": [
156
+                [
157
+                  [
158
+                    2.224122,
159
+                    48.854199
160
+                  ]
161
+                ]
162
+              ]
163
+            }
164
+          },
165
+          {
166
+            "place_id": "127732055",
167
+            "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
168
+            "osm_type": "node",
169
+            "osm_id": "7444",
170
+            "boundingbox": [
171
+              "48.8155755",
172
+              "48.902156",
173
+              "2.224122",
174
+              "2.4697602"
175
+            ],
176
+            "lat": "48.8565056",
177
+            "lon": "2.3521334",
178
+            "display_name": "This is the title",
179
+            "class": "tourism",
180
+            "type": "city",
181
+            "importance": 0.96893459932191,
182
+            "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
183
+            "address": {
184
+              "city": "Paris",
185
+              "county": "Paris",
186
+              "state": "Île-de-France",
187
+              "country": "France",
188
+              "country_code": "fr",
189
+              "address29": "Address"
190
+            }
191
+          }
192
+        ]
193
+        """
194
+        response = mock.Mock(text=json)
195
+        results = openstreetmap.response(response)
196
+        self.assertEqual(type(results), list)
197
+        self.assertEqual(len(results), 3)
198
+        self.assertIn('48.8565056', results[2]['geojson']['coordinates'])
199
+        self.assertIn('2.3521334', results[2]['geojson']['coordinates'])

+ 166
- 0
searx/tests/engines/test_photon.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+import mock
4
+from searx.engines import photon
5
+from searx.testing import SearxTestCase
6
+
7
+
8
+class TestPhotonEngine(SearxTestCase):
9
+
10
+    def test_request(self):
11
+        query = 'test_query'
12
+        dicto = defaultdict(dict)
13
+        dicto['pageno'] = 1
14
+        dicto['language'] = 'all'
15
+        params = photon.request(query, dicto)
16
+        self.assertIn('url', params)
17
+        self.assertIn(query, params['url'])
18
+        self.assertIn('photon.komoot.de', params['url'])
19
+
20
+        dicto['language'] = 'all'
21
+        params = photon.request(query, dicto)
22
+        self.assertNotIn('lang', params['url'])
23
+
24
+        dicto['language'] = 'al'
25
+        params = photon.request(query, dicto)
26
+        self.assertNotIn('lang', params['url'])
27
+
28
+        dicto['language'] = 'fr'
29
+        params = photon.request(query, dicto)
30
+        self.assertIn('fr', params['url'])
31
+
32
+    def test_response(self):
33
+        self.assertRaises(AttributeError, photon.response, None)
34
+        self.assertRaises(AttributeError, photon.response, [])
35
+        self.assertRaises(AttributeError, photon.response, '')
36
+        self.assertRaises(AttributeError, photon.response, '[]')
37
+
38
+        response = mock.Mock(text='{}')
39
+        self.assertEqual(photon.response(response), [])
40
+
41
+        response = mock.Mock(text='{"data": []}')
42
+        self.assertEqual(photon.response(response), [])
43
+
44
+        json = """
45
+        {
46
+          "features": [
47
+            {
48
+              "properties": {
49
+                "osm_key": "waterway",
50
+                "extent": [
51
+                  -1.4508446,
52
+                  51.1614997,
53
+                  -1.4408036,
54
+                  51.1525635
55
+                ],
56
+                "name": "This is the title",
57
+                "state": "England",
58
+                "osm_id": 114823817,
59
+                "osm_type": "W",
60
+                "osm_value": "river",
61
+                "city": "Test Valley",
62
+                "country": "United Kingdom"
63
+              },
64
+              "type": "Feature",
65
+              "geometry": {
66
+                "type": "Point",
67
+                "coordinates": [
68
+                  -1.4458571,
69
+                  51.1576661
70
+                ]
71
+              }
72
+            },
73
+            {
74
+              "properties": {
75
+                "osm_key": "place",
76
+                "street": "Rue",
77
+                "state": "Ile-de-France",
78
+                "osm_id": 129211377,
79
+                "osm_type": "R",
80
+                "housenumber": "10",
81
+                "postcode": "75011",
82
+                "osm_value": "house",
83
+                "city": "Paris",
84
+                "country": "France"
85
+              },
86
+              "type": "Feature",
87
+              "geometry": {
88
+                "type": "Point",
89
+                "coordinates": [
90
+                  2.3725025,
91
+                  48.8654481
92
+                ]
93
+              }
94
+            },
95
+            {
96
+              "properties": {
97
+                "osm_key": "amenity",
98
+                "street": "Allée",
99
+                "name": "Bibliothèque",
100
+                "state": "Ile-de-France",
101
+                "osm_id": 1028573132,
102
+                "osm_type": "N",
103
+                "postcode": "75001",
104
+                "osm_value": "library",
105
+                "city": "Paris",
106
+                "country": "France"
107
+              },
108
+              "type": "Feature",
109
+              "geometry": {
110
+                "type": "Point",
111
+                "coordinates": [
112
+                  2.3445634,
113
+                  48.862494
114
+                ]
115
+              }
116
+            },
117
+            {
118
+              "properties": {
119
+                "osm_key": "amenity",
120
+                "osm_id": 1028573132,
121
+                "osm_type": "Y",
122
+                "postcode": "75001",
123
+                "osm_value": "library",
124
+                "city": "Paris",
125
+                "country": "France"
126
+              },
127
+              "type": "Feature",
128
+              "geometry": {
129
+                "type": "Point",
130
+                "coordinates": [
131
+                  2.3445634,
132
+                  48.862494
133
+                ]
134
+              }
135
+            },
136
+            {
137
+            }
138
+        ],
139
+          "type": "FeatureCollection"
140
+        }
141
+        """
142
+        response = mock.Mock(text=json)
143
+        results = photon.response(response)
144
+        self.assertEqual(type(results), list)
145
+        self.assertEqual(len(results), 3)
146
+        self.assertEqual(results[0]['title'], 'This is the title')
147
+        self.assertEqual(results[0]['content'], '')
148
+        self.assertEqual(results[0]['longitude'], -1.4458571)
149
+        self.assertEqual(results[0]['latitude'], 51.1576661)
150
+        self.assertIn(-1.4508446, results[0]['boundingbox'])
151
+        self.assertIn(51.1614997, results[0]['boundingbox'])
152
+        self.assertIn(-1.4408036, results[0]['boundingbox'])
153
+        self.assertIn(51.1525635, results[0]['boundingbox'])
154
+        self.assertIn('type', results[0]['geojson'])
155
+        self.assertEqual(results[0]['geojson']['type'], 'Point')
156
+        self.assertEqual(results[0]['address'], None)
157
+        self.assertEqual(results[0]['osm']['type'], 'way')
158
+        self.assertEqual(results[0]['osm']['id'], 114823817)
159
+        self.assertEqual(results[0]['url'], 'https://openstreetmap.org/way/114823817')
160
+        self.assertEqual(results[1]['osm']['type'], 'relation')
161
+        self.assertEqual(results[2]['address']['name'], u'Bibliothèque')
162
+        self.assertEqual(results[2]['address']['house_number'], None)
163
+        self.assertEqual(results[2]['address']['locality'], 'Paris')
164
+        self.assertEqual(results[2]['address']['postcode'], '75001')
165
+        self.assertEqual(results[2]['address']['country'], 'France')
166
+        self.assertEqual(results[2]['osm']['type'], 'node')

+ 140
- 0
searx/tests/engines/test_startpage.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+import mock
4
+from searx.engines import startpage
5
+from searx.testing import SearxTestCase
6
+
7
+
8
+class TestStartpageEngine(SearxTestCase):
9
+
10
+    def test_request(self):
11
+        query = 'test_query'
12
+        dicto = defaultdict(dict)
13
+        dicto['pageno'] = 1
14
+        dicto['language'] = 'fr_FR'
15
+        params = startpage.request(query, dicto)
16
+        self.assertIn('url', params)
17
+        self.assertIn('startpage.com', params['url'])
18
+        self.assertIn('data', params)
19
+        self.assertIn('query', params['data'])
20
+        self.assertIn(query, params['data']['query'])
21
+        self.assertIn('with_language', params['data'])
22
+        self.assertIn('lang_fr', params['data']['with_language'])
23
+
24
+        dicto['language'] = 'all'
25
+        params = startpage.request(query, dicto)
26
+        self.assertNotIn('with_language', params['data'])
27
+
28
+    def test_response(self):
29
+        self.assertRaises(AttributeError, startpage.response, None)
30
+        self.assertRaises(AttributeError, startpage.response, [])
31
+        self.assertRaises(AttributeError, startpage.response, '')
32
+        self.assertRaises(AttributeError, startpage.response, '[]')
33
+
34
+        response = mock.Mock(content='<html></html>')
35
+        self.assertEqual(startpage.response(response), [])
36
+
37
+        html = """
38
+        <div class='result' style=' *width : auto; *margin-right : 10%;'>
39
+            <h3>
40
+                <a href='http://this.should.be.the.link/' id='title_2' name='title_2' >
41
+                    This should be the title
42
+                </a>
43
+                <span id='title_stars_2' name='title_stars_2'>  </span>
44
+            </h3>
45
+            <p class='desc'>
46
+                This should be the content.
47
+            </p>
48
+            <p>
49
+                <span class='url'>www.speed<b>test</b>.net/fr/
50
+                </span>
51
+                  -
52
+                <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
53
+                    class='proxy'>
54
+                    Navigation avec Ixquick Proxy
55
+                </A>
56
+                    -
57
+                <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
58
+                    &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
59
+                    &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
60
+                    Mis en surbrillance
61
+                </A>
62
+            </p>
63
+        </div>
64
+        """
65
+        response = mock.Mock(content=html)
66
+        results = startpage.response(response)
67
+        self.assertEqual(type(results), list)
68
+        self.assertEqual(len(results), 1)
69
+        self.assertEqual(results[0]['title'], 'This should be the title')
70
+        self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
71
+        self.assertEqual(results[0]['content'], 'This should be the content.')
72
+
73
+        html = """
74
+        <div class='result' style=' *width : auto; *margin-right : 10%;'>
75
+            <h3>
76
+                <a href='http://www.google.com/aclk?sa=l&ai=C' id='title_2' name='title_2' >
77
+                    This should be the title
78
+                </a>
79
+                <span id='title_stars_2' name='title_stars_2'>  </span>
80
+            </h3>
81
+            <p class='desc'>
82
+                This should be the content.
83
+            </p>
84
+            <p>
85
+                <span class='url'>www.speed<b>test</b>.net/fr/
86
+                </span>
87
+                  -
88
+                <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
89
+                    class='proxy'>
90
+                    Navigation avec Ixquick Proxy
91
+                </A>
92
+                    -
93
+                <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
94
+                    &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
95
+                    &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
96
+                    Mis en surbrillance
97
+                </A>
98
+            </p>
99
+        </div>
100
+        <div class='result' style=' *width : auto; *margin-right : 10%;'>
101
+            <h3>
102
+                <span id='title_stars_2' name='title_stars_2'>  </span>
103
+            </h3>
104
+            <p class='desc'>
105
+                This should be the content.
106
+            </p>
107
+            <p>
108
+                <span class='url'>www.speed<b>test</b>.net/fr/
109
+                </span>
110
+            </p>
111
+        </div>
112
+        <div class='result' style=' *width : auto; *margin-right : 10%;'>
113
+            <h3>
114
+                <a href='http://this.should.be.the.link/' id='title_2' name='title_2' >
115
+                    This should be the title
116
+                </a>
117
+                <span id='title_stars_2' name='title_stars_2'>  </span>
118
+            </h3>
119
+            <p>
120
+                <span class='url'>www.speed<b>test</b>.net/fr/
121
+                </span>
122
+                  -
123
+                <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
124
+                    class='proxy'>
125
+                    Navigation avec Ixquick Proxy
126
+                </A>
127
+                    -
128
+                <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
129
+                    &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
130
+                    &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
131
+                    Mis en surbrillance
132
+                </A>
133
+            </p>
134
+        </div>
135
+        """
136
+        response = mock.Mock(content=html)
137
+        results = startpage.response(response)
138
+        self.assertEqual(type(results), list)
139
+        self.assertEqual(len(results), 1)
140
+        self.assertEqual(results[0]['content'], '')

+ 169
- 0
searx/tests/engines/test_subtitleseeker.py 查看文件

1
+from collections import defaultdict
2
+import mock
3
+from searx.engines import subtitleseeker
4
+from searx.testing import SearxTestCase
5
+
6
+
7
+class TestSubtitleseekerEngine(SearxTestCase):
8
+
9
+    def test_request(self):
10
+        query = 'test_query'
11
+        dicto = defaultdict(dict)
12
+        dicto['pageno'] = 1
13
+        params = subtitleseeker.request(query, dicto)
14
+        self.assertTrue('url' in params)
15
+        self.assertTrue(query in params['url'])
16
+        self.assertTrue('subtitleseeker.com' in params['url'])
17
+
18
+    def test_response(self):
19
+        dicto = defaultdict(dict)
20
+        dicto['language'] = 'fr_FR'
21
+        response = mock.Mock(search_params=dicto)
22
+
23
+        self.assertRaises(AttributeError, subtitleseeker.response, None)
24
+        self.assertRaises(AttributeError, subtitleseeker.response, [])
25
+        self.assertRaises(AttributeError, subtitleseeker.response, '')
26
+        self.assertRaises(AttributeError, subtitleseeker.response, '[]')
27
+
28
+        response = mock.Mock(text='<html></html>', search_params=dicto)
29
+        self.assertEqual(subtitleseeker.response(response), [])
30
+
31
+        html = """
32
+        <div class="boxRows">
33
+            <div class="boxRowsInner" style="width:600px;">
34
+                <img src="http://static.subtitleseeker.com/images/movie.gif"
35
+                    style="width:16px; height:16px;" class="icon">
36
+                <a href="http://this.is.the.url/"
37
+                    class="blue" title="Title subtitle" >
38
+                    This is the Title
39
+                </a>
40
+                <br><br>
41
+                <span class="f10b grey-dark arial" style="padding:0px 0px 5px 20px">
42
+                    "Alternative Title"
43
+                </span>
44
+            </div>
45
+            <div class="boxRowsInner f12b red" style="width:70px;">
46
+                1998
47
+            </div>
48
+            <div class="boxRowsInner grey-web f12" style="width:120px;">
49
+                <img src="http://static.subtitleseeker.com/images/basket_put.png"
50
+                    style="width:16px; height:16px;" class="icon">
51
+                1039 Subs
52
+            </div>
53
+            <div class="boxRowsInner grey-web f10" style="width:130px;">
54
+                <img src="http://static.subtitleseeker.com/images/arrow_refresh_small.png"
55
+                    style="width:16px; height:16px;" class="icon">
56
+                1 hours ago
57
+            </div>
58
+            <div class="clear"></div>
59
+        </div>
60
+        """
61
+        response = mock.Mock(text=html, search_params=dicto)
62
+        results = subtitleseeker.response(response)
63
+        self.assertEqual(type(results), list)
64
+        self.assertEqual(len(results), 1)
65
+        self.assertEqual(results[0]['title'], 'This is the Title')
66
+        self.assertEqual(results[0]['url'], 'http://this.is.the.url/French/')
67
+        self.assertIn('1998', results[0]['content'])
68
+        self.assertIn('1039 Subs', results[0]['content'])
69
+        self.assertIn('Alternative Title', results[0]['content'])
70
+
71
+        html = """
72
+        <div class="boxRows">
73
+            <div class="boxRowsInner" style="width:600px;">
74
+                <img src="http://static.subtitleseeker.com/images/movie.gif"
75
+                    style="width:16px; height:16px;" class="icon">
76
+                <a href="http://this.is.the.url/"
77
+                    class="blue" title="Title subtitle" >
78
+                    This is the Title
79
+                </a>
80
+            </div>
81
+            <div class="boxRowsInner f12b red" style="width:70px;">
82
+                1998
83
+            </div>
84
+            <div class="boxRowsInner grey-web f12" style="width:120px;">
85
+                <img src="http://static.subtitleseeker.com/images/basket_put.png"
86
+                    style="width:16px; height:16px;" class="icon">
87
+                1039 Subs
88
+            </div>
89
+            <div class="boxRowsInner grey-web f10" style="width:130px;">
90
+                <img src="http://static.subtitleseeker.com/images/arrow_refresh_small.png"
91
+                    style="width:16px; height:16px;" class="icon">
92
+                1 hours ago
93
+            </div>
94
+            <div class="clear"></div>
95
+        </div>
96
+        """
97
+        dicto['language'] = 'all'
98
+        response = mock.Mock(text=html, search_params=dicto)
99
+        results = subtitleseeker.response(response)
100
+        self.assertEqual(type(results), list)
101
+        self.assertEqual(len(results), 1)
102
+        self.assertEqual(results[0]['title'], 'This is the Title')
103
+        self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
104
+        self.assertIn('1998', results[0]['content'])
105
+        self.assertIn('1039 Subs', results[0]['content'])
106
+
107
+        html = """
108
+        <div class="boxRows">
109
+            <div class="boxRowsInner" style="width:600px;">
110
+                <img src="http://static.subtitleseeker.com/images/movie.gif"
111
+                    style="width:16px; height:16px;" class="icon">
112
+                <a href="http://this.is.the.url/"
113
+                    class="blue" title="Title subtitle" >
114
+                    This is the Title
115
+                </a>
116
+            </div>
117
+            <div class="boxRowsInner f12b red" style="width:70px;">
118
+                1998
119
+            </div>
120
+            <div class="boxRowsInner grey-web f12" style="width:120px;">
121
+                <img src="http://static.subtitleseeker.com/images/basket_put.png"
122
+                    style="width:16px; height:16px;" class="icon">
123
+                1039 Subs
124
+            </div>
125
+            <div class="boxRowsInner grey-web f10" style="width:130px;">
126
+                <img src="http://static.subtitleseeker.com/images/arrow_refresh_small.png"
127
+                    style="width:16px; height:16px;" class="icon">
128
+                1 hours ago
129
+            </div>
130
+            <div class="clear"></div>
131
+        </div>
132
+        """
133
+        subtitleseeker.language = 'English'
134
+        response = mock.Mock(text=html, search_params=dicto)
135
+        results = subtitleseeker.response(response)
136
+        self.assertEqual(type(results), list)
137
+        self.assertEqual(len(results), 1)
138
+        self.assertEqual(results[0]['title'], 'This is the Title')
139
+        self.assertEqual(results[0]['url'], 'http://this.is.the.url/English/')
140
+        self.assertIn('1998', results[0]['content'])
141
+        self.assertIn('1039 Subs', results[0]['content'])
142
+
143
+        html = """
144
+        <div class="boxRowsInner" style="width:600px;">
145
+            <img src="http://static.subtitleseeker.com/images/movie.gif"
146
+                style="width:16px; height:16px;" class="icon">
147
+            <a href="http://this.is.the.url/"
148
+                class="blue" title="Title subtitle" >
149
+                This is the Title
150
+            </a>
151
+        </div>
152
+        <div class="boxRowsInner f12b red" style="width:70px;">
153
+            1998
154
+        </div>
155
+        <div class="boxRowsInner grey-web f12" style="width:120px;">
156
+            <img src="http://static.subtitleseeker.com/images/basket_put.png"
157
+                style="width:16px; height:16px;" class="icon">
158
+            1039 Subs
159
+        </div>
160
+        <div class="boxRowsInner grey-web f10" style="width:130px;">
161
+            <img src="http://static.subtitleseeker.com/images/arrow_refresh_small.png"
162
+                style="width:16px; height:16px;" class="icon">
163
+            1 hours ago
164
+        </div>
165
+        """
166
+        response = mock.Mock(text=html, search_params=dicto)
167
+        results = subtitleseeker.response(response)
168
+        self.assertEqual(type(results), list)
169
+        self.assertEqual(len(results), 0)

+ 502
- 0
searx/tests/engines/test_twitter.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+import mock
4
+from searx.engines import twitter
5
+from searx.testing import SearxTestCase
6
+
7
+
8
+class TestTwitterEngine(SearxTestCase):
9
+
10
+    def test_request(self):
11
+        query = 'test_query'
12
+        dicto = defaultdict(dict)
13
+        dicto['pageno'] = 0
14
+        dicto['language'] = 'fr_FR'
15
+        params = twitter.request(query, dicto)
16
+        self.assertIn('url', params)
17
+        self.assertIn(query, params['url'])
18
+        self.assertIn('twitter.com', params['url'])
19
+        self.assertIn('cookies', params)
20
+        self.assertIn('lang', params['cookies'])
21
+        self.assertIn('fr', params['cookies']['lang'])
22
+
23
+        dicto['language'] = 'all'
24
+        params = twitter.request(query, dicto)
25
+        self.assertIn('cookies', params)
26
+        self.assertIn('lang', params['cookies'])
27
+        self.assertIn('en', params['cookies']['lang'])
28
+
29
+    def test_response(self):
30
+        self.assertRaises(AttributeError, twitter.response, None)
31
+        self.assertRaises(AttributeError, twitter.response, [])
32
+        self.assertRaises(AttributeError, twitter.response, '')
33
+        self.assertRaises(AttributeError, twitter.response, '[]')
34
+
35
+        response = mock.Mock(text='<html></html>')
36
+        self.assertEqual(twitter.response(response), [])
37
+
38
+        html = """
39
+        <li class="js-stream-item stream-item stream-item expanding-stream-item" data-item-id="563005573290287105"
40
+            id="stream-item-tweet-563005573290287105" data-item-type="tweet">
41
+            <div class="tweet original-tweet js-stream-tweet js-actionable-tweet js-profile-popup-actionable
42
+                js-original-tweet has-cards has-native-media" data-tweet-id="563005573290287105" data-disclosure-type=""
43
+                data-item-id="563005573290287105" data-screen-name="Jalopnik" data-name="Jalopnik"
44
+                data-user-id="3060631" data-has-native-media="true" data-has-cards="true" data-card-type="photo"
45
+                data-expanded-footer="&lt;div class=&quot;js-tweet-details-fixer
46
+                tweet-details-fixer&quot;&gt;&#10;&#10;&#10;
47
+                &lt;div class=&quot;cards-media-container js-media-container&quot;&gt;&lt;div
48
+                data-card-url=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot; data-card-type=&quot;
49
+                photo&quot; class=&quot;cards-base cards-multimedia&quot; data-element-context=&quot;platform_photo_card
50
+                &quot;&gt;&#10;&#10;&#10;  &lt;a class=&quot;media media-thumbnail twitter-timeline-link is-preview
51
+                &quot; data-url=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
52
+                data-resolved-url-large=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
53
+                href=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot;&gt;&#10;
54
+                &lt;div class=&quot;&quot;&gt;&#10; &lt;img src=&quot;
55
+                https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg&quot;
56
+                alt=&quot;Embedded image permalink&quot; width=&quot;636&quot; height=&quot;309&quot;&gt;&#10;
57
+                &lt;/div&gt;&#10;&#10;  &lt;/a&gt;&#10;&#10;  &lt;div class=&quot;cards-content&quot;&gt;&#10;
58
+                &lt;div class=&quot;byline&quot;&gt;&#10;      &#10;    &lt;/div&gt;&#10;    &#10;  &lt;/div&gt;&#10;
59
+                &#10;&lt;/div&gt;&#10;&#10;&#10;&#10;&#10;&lt;/div&gt;&#10;&#10;&#10;&#10;  &lt;div
60
+                class=&quot;js-machine-translated-tweet-container&quot;&gt;&lt;/div&gt;&#10;    &lt;div
61
+                class=&quot;js-tweet-stats-container tweet-stats-container &quot;&gt;&#10;    &lt;/div&gt;&#10;&#10;
62
+                &lt;div class=&quot;client-and-actions&quot;&gt;&#10;  &lt;span class=&quot;metadata&quot;&gt;&#10;
63
+                &lt;span&gt;5:06 PM - 4 Feb 2015&lt;/span&gt;&#10;&#10;       &amp;middot; &lt;a
64
+                class=&quot;permalink-link js-permalink js-nav&quot; href=&quot;/Jalopnik/status/563005573290287105
65
+                &quot;tabindex=&quot;-1&quot;&gt;Details&lt;/a&gt;&#10;    &#10;&#10;        &#10;        &#10;
66
+                &#10;&#10;  &lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;/div&gt;&#10;" data-you-follow="false"
67
+                data-you-block="false">
68
+                <div class="context">
69
+                </div>
70
+                <div class="content">
71
+                    <div class="stream-item-header">
72
+                        <a class="account-group js-account-group js-action-profile js-user-profile-link js-nav"
73
+                            href="/Jalopnik" data-user-id="3060631">
74
+                            <img class="avatar js-action-profile-avatar"
75
+                                src="https://pbs.twimg.com/profile_images/2976430168/5cd4a59_bigger.jpeg" alt="">
76
+                            <strong class="fullname js-action-profile-name show-popup-with-id" data-aria-label-part>
77
+                                Jalopnik
78
+                            </strong>
79
+                            <span>&rlm;</span>
80
+                            <span class="username js-action-profile-name" data-aria-label-part>
81
+                            <s>@</s><b>TitleName</b>
82
+                            </span>
83
+                        </a>
84
+                        <small class="time">
85
+                        <a href="/this.is.the.url"
86
+                            class="tweet-timestamp js-permalink js-nav js-tooltip" title="5:06 PM - 4 Feb 2015" >
87
+                            <span class="u-hiddenVisually" data-aria-label-part="last">17 minutes ago</span>
88
+                        </a>
89
+                        </small>
90
+                    </div>
91
+                    <p class="js-tweet-text tweet-text" lang="en" data-aria-label-part="0">
92
+                        This is the content étude à€
93
+                        <a href="http://t.co/nRWsqQAwBL" rel="nofollow" dir="ltr"
94
+                            data-expanded-url="http://jalo.ps/ReMENu4" class="twitter-timeline-link"
95
+                            target="_blank" title="http://jalo.ps/ReMENu4" >
96
+                        <span class="tco-ellipsis">
97
+                        </span>
98
+                        <span class="invisible">http://</span><span class="js-display-url">link.in.tweet</span>
99
+                        <span class="invisible"></span>
100
+                        <span class="tco-ellipsis">
101
+                            <span class="invisible">&nbsp;</span>
102
+                        </span>
103
+                    </a>
104
+                    <a href="http://t.co/rbFsfeE0l3" class="twitter-timeline-link u-hidden"
105
+                        data-pre-embedded="true" dir="ltr">
106
+                        pic.twitter.com/rbFsfeE0l3
107
+                    </a>
108
+                    </p>
109
+                    <div class="expanded-content js-tweet-details-dropdown">
110
+                    </div>
111
+                    <div class="stream-item-footer">
112
+                        <a class="details with-icn js-details" href="/Jalopnik/status/563005573290287105">
113
+                            <span class="Icon Icon--photo">
114
+                            </span>
115
+                            <b>
116
+                                <span class="expand-stream-item js-view-details">
117
+                                    View photo
118
+                                </span>
119
+                                <span class="collapse-stream-item  js-hide-details">
120
+                                    Hide photo
121
+                                </span>
122
+                            </b>
123
+                        </a>
124
+                        <span class="ProfileTweet-action--reply u-hiddenVisually">
125
+                            <span class="ProfileTweet-actionCount" aria-hidden="true" data-tweet-stat-count="0">
126
+                                <span class="ProfileTweet-actionCountForAria" >0 replies</span>
127
+                            </span>
128
+                        </span>
129
+                        <span class="ProfileTweet-action--retweet u-hiddenVisually">
130
+                            <span class="ProfileTweet-actionCount"  data-tweet-stat-count="8">
131
+                                <span class="ProfileTweet-actionCountForAria" data-aria-label-part>8 retweets</span>
132
+                            </span>
133
+                        </span>
134
+                        <span class="ProfileTweet-action--favorite u-hiddenVisually">
135
+                            <span class="ProfileTweet-actionCount"  data-tweet-stat-count="14">
136
+                                <span class="ProfileTweet-actionCountForAria" data-aria-label-part>14 favorites</span>
137
+                            </span>
138
+                        </span>
139
+                        <div role="group" aria-label="Tweet actions" class="ProfileTweet-actionList u-cf js-actions">
140
+                            <div class="ProfileTweet-action ProfileTweet-action--reply">
141
+                                <button class="ProfileTweet-actionButton u-textUserColorHover js-actionButton
142
+                                    js-actionReply" data-modal="ProfileTweet-reply" type="button" title="Reply">
143
+                                    <span class="Icon Icon--reply">
144
+                                    </span>
145
+                                    <span class="u-hiddenVisually">Reply</span>
146
+                                    <span class="ProfileTweet-actionCount u-textUserColorHover
147
+                                        ProfileTweet-actionCount--isZero">
148
+                                        <span class="ProfileTweet-actionCountForPresentation" aria-hidden="true">
149
+                                        </span>
150
+                                    </span>
151
+                                </button>
152
+                            </div>
153
+                            <div class="ProfileTweet-action ProfileTweet-action--retweet js-toggleState js-toggleRt">
154
+                                <button class="ProfileTweet-actionButton  js-actionButton js-actionRetweet js-tooltip"
155
+                                    title="Retweet" data-modal="ProfileTweet-retweet" type="button">
156
+                                    <span class="Icon Icon--retweet">
157
+                                    </span>
158
+                                    <span class="u-hiddenVisually">Retweet</span>
159
+                                    <span class="ProfileTweet-actionCount">
160
+                                        <span class="ProfileTweet-actionCountForPresentation">8</span>
161
+                                    </span>
162
+                                </button>
163
+                                <button class="ProfileTweet-actionButtonUndo js-actionButton js-actionRetweet"
164
+                                    data-modal="ProfileTweet-retweet" title="Undo retweet" type="button">
165
+                                    <span class="Icon Icon--retweet">
166
+                                    </span>
167
+                                    <span class="u-hiddenVisually">Retweeted</span>
168
+                                    <span class="ProfileTweet-actionCount">
169
+                                        <span class="ProfileTweet-actionCountForPresentation">8</span>
170
+                                    </span>
171
+                                </button>
172
+                            </div>
173
+                            <div class="ProfileTweet-action ProfileTweet-action--favorite js-toggleState">
174
+                                <button class="ProfileTweet-actionButton js-actionButton js-actionFavorite js-tooltip"
175
+                                    title="Favorite" type="button">
176
+                                    <span class="Icon Icon--favorite">
177
+                                    </span>
178
+                                    <span class="u-hiddenVisually">Favorite</span>
179
+                                    <span class="ProfileTweet-actionCount">
180
+                                        <span class="ProfileTweet-actionCountForPresentation">14</span>
181
+                                    </span>
182
+                                </button>
183
+                                <button class="ProfileTweet-actionButtonUndo u-linkClean js-actionButton
184
+                                    js-actionFavorite" title="Undo favorite" type="button">
185
+                                    <span class="Icon Icon--favorite">
186
+                                    </span>
187
+                                    <span class="u-hiddenVisually">Favorited</span>
188
+                                    <span class="ProfileTweet-actionCount">
189
+                                        <span class="ProfileTweet-actionCountForPresentation">
190
+                                            14
191
+                                        </span>
192
+                                    </span>
193
+                                </button>
194
+                            </div>
195
+                            <div class="ProfileTweet-action ProfileTweet-action--more js-more-ProfileTweet-actions">
196
+                                <div class="dropdown">
197
+                                    <button class="ProfileTweet-actionButton u-textUserColorHover dropdown-toggle
198
+                                        js-tooltip js-dropdown-toggle" type="button" title="More">
199
+                                        <span class="Icon Icon--dots">
200
+                                        </span>
201
+                                        <span class="u-hiddenVisually">More</span>
202
+                                    </button>
203
+                                    <div class="dropdown-menu">
204
+                                        <div class="dropdown-caret">
205
+                                            <div class="caret-outer">
206
+                                            </div>
207
+                                            <div class="caret-inner">
208
+                                            </div>
209
+                                        </div>
210
+                                        <ul>
211
+                                            <li class="share-via-dm js-actionShareViaDM" data-nav="share_tweet_dm">
212
+                                                <button type="button" class="dropdown-link">
213
+                                                    Share via Direct Message
214
+                                                </button>
215
+                                            </li>
216
+                                            <li class="embed-link js-actionEmbedTweet" data-nav="embed_tweet">
217
+                                                <button type="button" class="dropdown-link">
218
+                                                    Embed Tweet
219
+                                                </button>
220
+                                            </li>
221
+                                            <li class="mute-user-item pretty-link">
222
+                                                <button type="button" class="dropdown-link">
223
+                                                    Mute
224
+                                                </button>
225
+                                            </li>
226
+                                            <li class="unmute-user-item pretty-link">
227
+                                                <button type="button" class="dropdown-link">
228
+                                                    Unmute
229
+                                                </button>
230
+                                            </li>
231
+                                            <li class="block-or-report-link js-actionBlockOrReport"
232
+                                                data-nav="block_or_report">
233
+                                                <button type="button" class="dropdown-link">
234
+                                                    Block or report
235
+                                                </button>
236
+                                            </li>
237
+                                        </ul>
238
+                                    </div>
239
+                                </div>
240
+                            </div>
241
+                        </div>
242
+                    </div>
243
+                </div>
244
+            </div>
245
+        </li>
246
+        """
247
+        response = mock.Mock(text=html)
248
+        results = twitter.response(response)
249
+        self.assertEqual(type(results), list)
250
+        self.assertEqual(len(results), 1)
251
+        self.assertEqual(results[0]['title'], '@TitleName')
252
+        self.assertEqual(results[0]['url'], 'https://twitter.com/this.is.the.url')
253
+        self.assertIn(u'This is the content', results[0]['content'])
254
+        # self.assertIn(u'This is the content étude à€', results[0]['content'])
255
+
256
+        html = """
257
+        <li class="js-stream-item stream-item stream-item expanding-stream-item" data-item-id="563005573290287105"
258
+            id="stream-item-tweet-563005573290287105" data-item-type="tweet">
259
+            <div class="tweet original-tweet js-stream-tweet js-actionable-tweet js-profile-popup-actionable
260
+                js-original-tweet has-cards has-native-media" data-tweet-id="563005573290287105" data-disclosure-type=""
261
+                data-item-id="563005573290287105" data-screen-name="Jalopnik" data-name="Jalopnik"
262
+                data-user-id="3060631" data-has-native-media="true" data-has-cards="true" data-card-type="photo"
263
+                data-expanded-footer="&lt;div class=&quot;js-tweet-details-fixer
264
+                tweet-details-fixer&quot;&gt;&#10;&#10;&#10;
265
+                &lt;div class=&quot;cards-media-container js-media-container&quot;&gt;&lt;div
266
+                data-card-url=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot; data-card-type=&quot;
267
+                photo&quot; class=&quot;cards-base cards-multimedia&quot; data-element-context=&quot;platform_photo_card
268
+                &quot;&gt;&#10;&#10;&#10;  &lt;a class=&quot;media media-thumbnail twitter-timeline-link is-preview
269
+                &quot; data-url=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
270
+                data-resolved-url-large=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
271
+                href=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot;&gt;&#10;
272
+                &lt;div class=&quot;&quot;&gt;&#10; &lt;img src=&quot;
273
+                https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg&quot;
274
+                alt=&quot;Embedded image permalink&quot; width=&quot;636&quot; height=&quot;309&quot;&gt;&#10;
275
+                &lt;/div&gt;&#10;&#10;  &lt;/a&gt;&#10;&#10;  &lt;div class=&quot;cards-content&quot;&gt;&#10;
276
+                &lt;div class=&quot;byline&quot;&gt;&#10;      &#10;    &lt;/div&gt;&#10;    &#10;  &lt;/div&gt;&#10;
277
+                &#10;&lt;/div&gt;&#10;&#10;&#10;&#10;&#10;&lt;/div&gt;&#10;&#10;&#10;&#10;  &lt;div
278
+                class=&quot;js-machine-translated-tweet-container&quot;&gt;&lt;/div&gt;&#10;    &lt;div
279
+                class=&quot;js-tweet-stats-container tweet-stats-container &quot;&gt;&#10;    &lt;/div&gt;&#10;&#10;
280
+                &lt;div class=&quot;client-and-actions&quot;&gt;&#10;  &lt;span class=&quot;metadata&quot;&gt;&#10;
281
+                &lt;span&gt;5:06 PM - 4 Feb 2015&lt;/span&gt;&#10;&#10;       &amp;middot; &lt;a
282
+                class=&quot;permalink-link js-permalink js-nav&quot; href=&quot;/Jalopnik/status/563005573290287105
283
+                &quot;tabindex=&quot;-1&quot;&gt;Details&lt;/a&gt;&#10;    &#10;&#10;        &#10;        &#10;
284
+                &#10;&#10;  &lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;/div&gt;&#10;" data-you-follow="false"
285
+                data-you-block="false">
286
+                <div class="context">
287
+                </div>
288
+                <div class="content">
289
+                    <div class="stream-item-header">
290
+                        <a class="account-group js-account-group js-action-profile js-user-profile-link js-nav"
291
+                            href="/Jalopnik" data-user-id="3060631">
292
+                            <img class="avatar js-action-profile-avatar"
293
+                                src="https://pbs.twimg.com/profile_images/2976430168/5cd4a59_bigger.jpeg" alt="">
294
+                            <strong class="fullname js-action-profile-name show-popup-with-id" data-aria-label-part>
295
+                                Jalopnik
296
+                            </strong>
297
+                            <span>&rlm;</span>
298
+                            <span class="username js-action-profile-name" data-aria-label-part>
299
+                            <s>@</s><b>TitleName</b>
300
+                            </span>
301
+                        </a>
302
+                        <small class="time">
303
+                        <a href="/this.is.the.url"
304
+                            class="tweet-timestamp js-permalink js-nav js-tooltip" title="5:06 PM - 4 Feb 2015" >
305
+                            <span class="_timestamp js-short-timestamp js-relative-timestamp"  data-time="1423065963"
306
+                                data-time-ms="1423065963000" data-long-form="true" aria-hidden="true">
307
+                                17m
308
+                            </span>
309
+                            <span class="u-hiddenVisually" data-aria-label-part="last">17 minutes ago</span>
310
+                        </a>
311
+                        </small>
312
+                    </div>
313
+                    <p class="js-tweet-text tweet-text" lang="en" data-aria-label-part="0">
314
+                        This is the content étude à€
315
+                        <a href="http://t.co/nRWsqQAwBL" rel="nofollow" dir="ltr"
316
+                            data-expanded-url="http://jalo.ps/ReMENu4" class="twitter-timeline-link"
317
+                            target="_blank" title="http://jalo.ps/ReMENu4" >
318
+                        <span class="tco-ellipsis">
319
+                        </span>
320
+                        <span class="invisible">http://</span><span class="js-display-url">link.in.tweet</span>
321
+                        <span class="invisible"></span>
322
+                        <span class="tco-ellipsis">
323
+                            <span class="invisible">&nbsp;</span>
324
+                        </span>
325
+                    </a>
326
+                    <a href="http://t.co/rbFsfeE0l3" class="twitter-timeline-link u-hidden"
327
+                        data-pre-embedded="true" dir="ltr">
328
+                        pic.twitter.com/rbFsfeE0l3
329
+                    </a>
330
+                    </p>
331
+                    <div class="expanded-content js-tweet-details-dropdown">
332
+                    </div>
333
+                    <div class="stream-item-footer">
334
+                        <a class="details with-icn js-details" href="/Jalopnik/status/563005573290287105">
335
+                            <span class="Icon Icon--photo">
336
+                            </span>
337
+                            <b>
338
+                                <span class="expand-stream-item js-view-details">
339
+                                    View photo
340
+                                </span>
341
+                                <span class="collapse-stream-item  js-hide-details">
342
+                                    Hide photo
343
+                                </span>
344
+                            </b>
345
+                        </a>
346
+                        <span class="ProfileTweet-action--reply u-hiddenVisually">
347
+                            <span class="ProfileTweet-actionCount" aria-hidden="true" data-tweet-stat-count="0">
348
+                                <span class="ProfileTweet-actionCountForAria" >0 replies</span>
349
+                            </span>
350
+                        </span>
351
+                        <span class="ProfileTweet-action--retweet u-hiddenVisually">
352
+                            <span class="ProfileTweet-actionCount"  data-tweet-stat-count="8">
353
+                                <span class="ProfileTweet-actionCountForAria" data-aria-label-part>8 retweets</span>
354
+                            </span>
355
+                        </span>
356
+                        <span class="ProfileTweet-action--favorite u-hiddenVisually">
357
+                            <span class="ProfileTweet-actionCount"  data-tweet-stat-count="14">
358
+                                <span class="ProfileTweet-actionCountForAria" data-aria-label-part>14 favorites</span>
359
+                            </span>
360
+                        </span>
361
+                        <div role="group" aria-label="Tweet actions" class="ProfileTweet-actionList u-cf js-actions">
362
+                            <div class="ProfileTweet-action ProfileTweet-action--reply">
363
+                                <button class="ProfileTweet-actionButton u-textUserColorHover js-actionButton
364
+                                    js-actionReply" data-modal="ProfileTweet-reply" type="button" title="Reply">
365
+                                    <span class="Icon Icon--reply">
366
+                                    </span>
367
+                                    <span class="u-hiddenVisually">Reply</span>
368
+                                    <span class="ProfileTweet-actionCount u-textUserColorHover
369
+                                        ProfileTweet-actionCount--isZero">
370
+                                        <span class="ProfileTweet-actionCountForPresentation" aria-hidden="true">
371
+                                        </span>
372
+                                    </span>
373
+                                </button>
374
+                            </div>
375
+                            <div class="ProfileTweet-action ProfileTweet-action--retweet js-toggleState js-toggleRt">
376
+                                <button class="ProfileTweet-actionButton  js-actionButton js-actionRetweet js-tooltip"
377
+                                    title="Retweet" data-modal="ProfileTweet-retweet" type="button">
378
+                                    <span class="Icon Icon--retweet">
379
+                                    </span>
380
+                                    <span class="u-hiddenVisually">Retweet</span>
381
+                                    <span class="ProfileTweet-actionCount">
382
+                                        <span class="ProfileTweet-actionCountForPresentation">8</span>
383
+                                    </span>
384
+                                </button>
385
+                                <button class="ProfileTweet-actionButtonUndo js-actionButton js-actionRetweet"
386
+                                    data-modal="ProfileTweet-retweet" title="Undo retweet" type="button">
387
+                                    <span class="Icon Icon--retweet">
388
+                                    </span>
389
+                                    <span class="u-hiddenVisually">Retweeted</span>
390
+                                    <span class="ProfileTweet-actionCount">
391
+                                        <span class="ProfileTweet-actionCountForPresentation">8</span>
392
+                                    </span>
393
+                                </button>
394
+                            </div>
395
+                            <div class="ProfileTweet-action ProfileTweet-action--favorite js-toggleState">
396
+                                <button class="ProfileTweet-actionButton js-actionButton js-actionFavorite js-tooltip"
397
+                                    title="Favorite" type="button">
398
+                                    <span class="Icon Icon--favorite">
399
+                                    </span>
400
+                                    <span class="u-hiddenVisually">Favorite</span>
401
+                                    <span class="ProfileTweet-actionCount">
402
+                                        <span class="ProfileTweet-actionCountForPresentation">14</span>
403
+                                    </span>
404
+                                </button>
405
+                                <button class="ProfileTweet-actionButtonUndo u-linkClean js-actionButton
406
+                                    js-actionFavorite" title="Undo favorite" type="button">
407
+                                    <span class="Icon Icon--favorite">
408
+                                    </span>
409
+                                    <span class="u-hiddenVisually">Favorited</span>
410
+                                    <span class="ProfileTweet-actionCount">
411
+                                        <span class="ProfileTweet-actionCountForPresentation">
412
+                                            14
413
+                                        </span>
414
+                                    </span>
415
+                                </button>
416
+                            </div>
417
+                            <div class="ProfileTweet-action ProfileTweet-action--more js-more-ProfileTweet-actions">
418
+                                <div class="dropdown">
419
+                                    <button class="ProfileTweet-actionButton u-textUserColorHover dropdown-toggle
420
+                                        js-tooltip js-dropdown-toggle" type="button" title="More">
421
+                                        <span class="Icon Icon--dots">
422
+                                        </span>
423
+                                        <span class="u-hiddenVisually">More</span>
424
+                                    </button>
425
+                                    <div class="dropdown-menu">
426
+                                        <div class="dropdown-caret">
427
+                                            <div class="caret-outer">
428
+                                            </div>
429
+                                            <div class="caret-inner">
430
+                                            </div>
431
+                                        </div>
432
+                                        <ul>
433
+                                            <li class="share-via-dm js-actionShareViaDM" data-nav="share_tweet_dm">
434
+                                                <button type="button" class="dropdown-link">
435
+                                                    Share via Direct Message
436
+                                                </button>
437
+                                            </li>
438
+                                            <li class="embed-link js-actionEmbedTweet" data-nav="embed_tweet">
439
+                                                <button type="button" class="dropdown-link">
440
+                                                    Embed Tweet
441
+                                                </button>
442
+                                            </li>
443
+                                            <li class="mute-user-item pretty-link">
444
+                                                <button type="button" class="dropdown-link">
445
+                                                    Mute
446
+                                                </button>
447
+                                            </li>
448
+                                            <li class="unmute-user-item pretty-link">
449
+                                                <button type="button" class="dropdown-link">
450
+                                                    Unmute
451
+                                                </button>
452
+                                            </li>
453
+                                            <li class="block-or-report-link js-actionBlockOrReport"
454
+                                                data-nav="block_or_report">
455
+                                                <button type="button" class="dropdown-link">
456
+                                                    Block or report
457
+                                                </button>
458
+                                            </li>
459
+                                        </ul>
460
+                                    </div>
461
+                                </div>
462
+                            </div>
463
+                        </div>
464
+                    </div>
465
+                </div>
466
+            </div>
467
+        </li>
468
+        """
469
+        response = mock.Mock(text=html)
470
+        results = twitter.response(response)
471
+        self.assertEqual(type(results), list)
472
+        self.assertEqual(len(results), 1)
473
+        self.assertEqual(results[0]['title'], '@TitleName')
474
+        self.assertEqual(results[0]['url'], 'https://twitter.com/this.is.the.url')
475
+        self.assertIn(u'This is the content', results[0]['content'])
476
+
477
+        html = """
478
+        <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
479
+            <div Class="sa_mc">
480
+                <div class="sb_tlst">
481
+                    <h2>
482
+                        <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
483
+                        <strong>This</strong> should be the title</a>
484
+                    </h2>
485
+                </div>
486
+                <div class="sb_meta">
487
+                <cite>
488
+                <strong>this</strong>.meta.com</cite>
489
+                    <span class="c_tlbxTrg">
490
+                        <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
491
+                        </span>
492
+                    </span>
493
+                </div>
494
+                <p>
495
+                <strong>This</strong> should be the content.</p>
496
+            </div>
497
+        </li>
498
+        """
499
+        response = mock.Mock(text=html)
500
+        results = twitter.response(response)
501
+        self.assertEqual(type(results), list)
502
+        self.assertEqual(len(results), 0)

+ 96
- 0
searx/tests/engines/test_yacy.py 查看文件

1
+from collections import defaultdict
2
+import mock
3
+from searx.engines import yacy
4
+from searx.testing import SearxTestCase
5
+
6
+
7
+class TestYacyEngine(SearxTestCase):
8
+
9
+    def test_request(self):
10
+        query = 'test_query'
11
+        dicto = defaultdict(dict)
12
+        dicto['pageno'] = 1
13
+        dicto['language'] = 'fr_FR'
14
+        params = yacy.request(query, dicto)
15
+        self.assertIn('url', params)
16
+        self.assertIn(query, params['url'])
17
+        self.assertIn('localhost', params['url'])
18
+        self.assertIn('fr', params['url'])
19
+
20
+        dicto['language'] = 'all'
21
+        params = yacy.request(query, dicto)
22
+        self.assertIn('url', params)
23
+        self.assertNotIn('lr=lang_', params['url'])
24
+
25
+    def test_response(self):
26
+        self.assertRaises(AttributeError, yacy.response, None)
27
+        self.assertRaises(AttributeError, yacy.response, [])
28
+        self.assertRaises(AttributeError, yacy.response, '')
29
+        self.assertRaises(AttributeError, yacy.response, '[]')
30
+
31
+        response = mock.Mock(text='{}')
32
+        self.assertEqual(yacy.response(response), [])
33
+
34
+        response = mock.Mock(text='{"data": []}')
35
+        self.assertEqual(yacy.response(response), [])
36
+
37
+        json = """
38
+        {
39
+          "channels": [
40
+            {
41
+              "title": "YaCy P2P-Search for test",
42
+              "description": "Search for test",
43
+              "link": "http://search.yacy.de:7001/yacysearch.html?query=test&amp;resource=global&amp;contentdom=0",
44
+              "image": {
45
+                "url": "http://search.yacy.de:7001/env/grafics/yacy.png",
46
+                "title": "Search for test",
47
+                "link": "http://search.yacy.de:7001/yacysearch.html?query=test&amp;resource=global&amp;contentdom=0"
48
+              },
49
+              "totalResults": "249",
50
+              "startIndex": "0",
51
+              "itemsPerPage": "5",
52
+              "searchTerms": "test",
53
+              "items": [
54
+                {
55
+                  "title": "This is the title",
56
+                  "link": "http://this.is.the.url",
57
+                  "code": "",
58
+                  "description": "This should be the content",
59
+                  "pubDate": "Sat, 08 Jun 2013 02:00:00 +0200",
60
+                  "size": "44213",
61
+                  "sizename": "43 kbyte",
62
+                  "guid": "lzh_1T_5FP-A",
63
+                  "faviconCode": "XTS4uQ_5FP-A",
64
+                  "host": "www.gamestar.de",
65
+                  "path": "/spiele/city-of-heroes-freedom/47019.html",
66
+                  "file": "47019.html",
67
+                  "urlhash": "lzh_1T_5FP-A",
68
+                  "ranking": "0.20106804"
69
+                },
70
+                {
71
+                  "title": "This is the title2",
72
+                  "icon": "/ViewImage.png?maxwidth=96&amp;maxheight=96&amp;code=7EbAbW6BpPOA",
73
+                  "image": "http://image.url/image.png",
74
+                  "cache": "/ViewImage.png?quadratic=&amp;url=http://golem.ivwbox.de/cgi-bin/ivw/CP/G_INET?d=14071378",
75
+                  "url": "http://this.is.the.url",
76
+                  "urlhash": "7EbAbW6BpPOA",
77
+                  "host": "www.golem.de",
78
+                  "width": "-1",
79
+                  "height": "-1"
80
+                }
81
+              ]
82
+            }
83
+          ]
84
+        }
85
+        """
86
+        response = mock.Mock(text=json)
87
+        results = yacy.response(response)
88
+        self.assertEqual(type(results), list)
89
+        self.assertEqual(len(results), 2)
90
+        self.assertEqual(results[0]['title'], 'This is the title')
91
+        self.assertEqual(results[0]['url'], 'http://this.is.the.url')
92
+        self.assertEqual(results[0]['content'], 'This should be the content')
93
+        self.assertEqual(results[1]['img_src'], 'http://image.url/image.png')
94
+        self.assertEqual(results[1]['content'], '')
95
+        self.assertEqual(results[1]['url'], 'http://this.is.the.url')
96
+        self.assertEqual(results[1]['title'], 'This is the title2')

+ 154
- 0
searx/tests/engines/test_yahoo.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+import mock
4
+from searx.engines import yahoo
5
+from searx.testing import SearxTestCase
6
+
7
+
8
+class TestYahooEngine(SearxTestCase):
9
+
10
+    def test_parse_url(self):
11
+        test_url = 'http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA;_ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb' +\
12
+                   '2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10/RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=' +\
13
+                   'dtcJsfP4mEeBOjnVfUQ-'
14
+        url = yahoo.parse_url(test_url)
15
+        self.assertEqual('https://this.is.the.url/', url)
16
+
17
+        test_url = 'http://r.search.yahoo.com/_ylt=A0LElb9JUSKcAEGRXNyoA;_ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb' +\
18
+                   '2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10/RU=https%3a%2f%2fthis.is.the.url%2f/RS=' +\
19
+                   'dtcJsfP4mEeBOjnVfUQ-'
20
+        url = yahoo.parse_url(test_url)
21
+        self.assertEqual('https://this.is.the.url/', url)
22
+
23
+        test_url = 'https://this.is.the.url/'
24
+        url = yahoo.parse_url(test_url)
25
+        self.assertEqual('https://this.is.the.url/', url)
26
+
27
+    def test_request(self):
28
+        query = 'test_query'
29
+        dicto = defaultdict(dict)
30
+        dicto['pageno'] = 1
31
+        dicto['language'] = 'fr_FR'
32
+        params = yahoo.request(query, dicto)
33
+        self.assertIn('url', params)
34
+        self.assertIn(query, params['url'])
35
+        self.assertIn('search.yahoo.com', params['url'])
36
+        self.assertIn('fr', params['url'])
37
+        self.assertIn('cookies', params)
38
+        self.assertIn('sB', params['cookies'])
39
+        self.assertIn('fr', params['cookies']['sB'])
40
+
41
+        dicto['language'] = 'all'
42
+        params = yahoo.request(query, dicto)
43
+        self.assertIn('cookies', params)
44
+        self.assertIn('sB', params['cookies'])
45
+        self.assertIn('en', params['cookies']['sB'])
46
+        self.assertIn('en', params['url'])
47
+
48
+    def test_response(self):
49
+        self.assertRaises(AttributeError, yahoo.response, None)
50
+        self.assertRaises(AttributeError, yahoo.response, [])
51
+        self.assertRaises(AttributeError, yahoo.response, '')
52
+        self.assertRaises(AttributeError, yahoo.response, '[]')
53
+
54
+        response = mock.Mock(text='<html></html>')
55
+        self.assertEqual(yahoo.response(response), [])
56
+
57
+        html = """
58
+        <div class="res">
59
+            <div>
60
+                <h3>
61
+                <a id="link-1" class="yschttl spt" href="http://r.search.yahoo.com/_ylt=A0LEVzClb9JUSKcAEGRXNyoA;
62
+                    _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2JmMQR2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10
63
+                    /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-"target="_blank" data-bk="5063.1">
64
+                    <b>This</b> is the title
65
+                </a>
66
+                </h3>
67
+            </div>
68
+            <span class="url" dir="ltr">www.<b>test</b>.com</span>
69
+            <div class="abstr">
70
+                <b>This</b> is the content
71
+            </div>
72
+        </div>
73
+        <div id="satat"  data-bns="Yahoo" data-bk="124.1">
74
+            <h2>Also Try</h2>
75
+            <table>
76
+                <tbody>
77
+                    <tr>
78
+                        <td>
79
+                            <a id="srpnat0" class="" href="https://search.yahoo.com/search=rs-bottom" >
80
+                                <span>
81
+                                    <b></b>This is <b>the suggestion</b>
82
+                                </span>
83
+                            </a>
84
+                        </td>
85
+                    </tr>
86
+                </tbody>
87
+            </table>
88
+        </div>
89
+        """
90
+        response = mock.Mock(text=html)
91
+        results = yahoo.response(response)
92
+        self.assertEqual(type(results), list)
93
+        self.assertEqual(len(results), 2)
94
+        self.assertEqual(results[0]['title'], 'This is the title')
95
+        self.assertEqual(results[0]['url'], 'https://this.is.the.url/')
96
+        self.assertEqual(results[0]['content'], 'This is the content')
97
+        self.assertEqual(results[1]['suggestion'], 'This is the suggestion')
98
+
99
+        html = """
100
+        <div class="res">
101
+            <div>
102
+                <h3>
103
+                <a id="link-1" class="yschttl spt" href="http://r.search.yahoo.com/_ylt=A0LEVzClb9JUSKcAEGRXNyoA;
104
+                    _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2JmMQR2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10
105
+                    /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-"target="_blank" data-bk="5063.1">
106
+                    <b>This</b> is the title
107
+                </a>
108
+                </h3>
109
+            </div>
110
+            <span class="url" dir="ltr">www.<b>test</b>.com</span>
111
+            <div class="abstr">
112
+                <b>This</b> is the content
113
+            </div>
114
+        </div>
115
+        <div class="res">
116
+            <div>
117
+                <h3>
118
+                <a id="link-1" class="yschttl spt">
119
+                    <b>This</b> is the title
120
+                </a>
121
+                </h3>
122
+            </div>
123
+            <span class="url" dir="ltr">www.<b>test</b>.com</span>
124
+            <div class="abstr">
125
+                <b>This</b> is the content
126
+            </div>
127
+        </div>
128
+        <div class="res">
129
+            <div>
130
+                <h3>
131
+                </h3>
132
+            </div>
133
+            <span class="url" dir="ltr">www.<b>test</b>.com</span>
134
+            <div class="abstr">
135
+                <b>This</b> is the content
136
+            </div>
137
+        </div>
138
+        """
139
+        response = mock.Mock(text=html)
140
+        results = yahoo.response(response)
141
+        self.assertEqual(type(results), list)
142
+        self.assertEqual(len(results), 1)
143
+        self.assertEqual(results[0]['title'], 'This is the title')
144
+        self.assertEqual(results[0]['url'], 'https://this.is.the.url/')
145
+        self.assertEqual(results[0]['content'], 'This is the content')
146
+
147
+        html = """
148
+        <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
149
+        </li>
150
+        """
151
+        response = mock.Mock(text=html)
152
+        results = yahoo.response(response)
153
+        self.assertEqual(type(results), list)
154
+        self.assertEqual(len(results), 0)

+ 143
- 0
searx/tests/engines/test_yahoo_news.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+from collections import defaultdict
3
+from datetime import datetime
4
+import mock
5
+from searx.engines import yahoo_news
6
+from searx.testing import SearxTestCase
7
+
8
+
9
+class TestYahooNewsEngine(SearxTestCase):
10
+
11
+    def test_request(self):
12
+        query = 'test_query'
13
+        dicto = defaultdict(dict)
14
+        dicto['pageno'] = 1
15
+        dicto['language'] = 'fr_FR'
16
+        params = yahoo_news.request(query, dicto)
17
+        self.assertIn('url', params)
18
+        self.assertIn(query, params['url'])
19
+        self.assertIn('news.search.yahoo.com', params['url'])
20
+        self.assertIn('fr', params['url'])
21
+        self.assertIn('cookies', params)
22
+        self.assertIn('sB', params['cookies'])
23
+        self.assertIn('fr', params['cookies']['sB'])
24
+
25
+        dicto['language'] = 'all'
26
+        params = yahoo_news.request(query, dicto)
27
+        self.assertIn('cookies', params)
28
+        self.assertIn('sB', params['cookies'])
29
+        self.assertIn('en', params['cookies']['sB'])
30
+        self.assertIn('en', params['url'])
31
+
32
+    def test_response(self):
33
+        self.assertRaises(AttributeError, yahoo_news.response, None)
34
+        self.assertRaises(AttributeError, yahoo_news.response, [])
35
+        self.assertRaises(AttributeError, yahoo_news.response, '')
36
+        self.assertRaises(AttributeError, yahoo_news.response, '[]')
37
+
38
+        response = mock.Mock(text='<html></html>')
39
+        self.assertEqual(yahoo_news.response(response), [])
40
+
41
+        html = """
42
+        <div class="res">
43
+            <div>
44
+                <h3>
45
+                    <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
46
+                        This is
47
+                        the <b>title</b>...
48
+                    </a>
49
+                </h3>
50
+            </div>
51
+            <span class="url">Business via Yahoo! Finance</span> &nbsp; <span class="timestamp">Feb 03 09:45am</span>
52
+            <div class="abstr">
53
+                This is the content
54
+            </div>
55
+        </div>
56
+        """
57
+        response = mock.Mock(text=html)
58
+        results = yahoo_news.response(response)
59
+        self.assertEqual(type(results), list)
60
+        self.assertEqual(len(results), 1)
61
+        self.assertEqual(results[0]['title'], 'This is the title...')
62
+        self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
63
+        self.assertEqual(results[0]['content'], 'This is the content')
64
+
65
+        html = """
66
+        <div class="res">
67
+            <div>
68
+                <h3>
69
+                    <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
70
+                        This is
71
+                        the <b>title</b>...
72
+                    </a>
73
+                </h3>
74
+            </div>
75
+            <span class="url">Business via Yahoo!</span> &nbsp; <span class="timestamp">2 hours, 22 minutes ago</span>
76
+            <div class="abstr">
77
+                This is the content
78
+            </div>
79
+        </div>
80
+        <div class="res">
81
+            <div>
82
+                <h3>
83
+                    <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
84
+                        This is
85
+                        the <b>title</b>...
86
+                    </a>
87
+                </h3>
88
+            </div>
89
+            <span class="url">Business via Yahoo!</span> &nbsp; <span class="timestamp">22 minutes ago</span>
90
+            <div class="abstr">
91
+                This is the content
92
+            </div>
93
+        </div>
94
+        <div class="res">
95
+            <div>
96
+                <h3>
97
+                    <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
98
+                        This is
99
+                        the <b>title</b>...
100
+                    </a>
101
+                </h3>
102
+            </div>
103
+            <span class="url">Business via Yahoo!</span> &nbsp; <span class="timestamp">Feb 03 09:45am 1900</span>
104
+            <div class="abstr">
105
+                This is the content
106
+            </div>
107
+        </div>
108
+        """
109
+        response = mock.Mock(text=html)
110
+        results = yahoo_news.response(response)
111
+        self.assertEqual(type(results), list)
112
+        self.assertEqual(len(results), 3)
113
+        self.assertEqual(results[0]['title'], 'This is the title...')
114
+        self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
115
+        self.assertEqual(results[0]['content'], 'This is the content')
116
+        self.assertEqual(results[2]['publishedDate'].year, datetime.now().year)
117
+
118
+        html = """
119
+        <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
120
+            <div Class="sa_mc">
121
+                <div class="sb_tlst">
122
+                    <h2>
123
+                        <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
124
+                        <strong>This</strong> should be the title</a>
125
+                    </h2>
126
+                </div>
127
+                <div class="sb_meta">
128
+                <cite>
129
+                <strong>this</strong>.meta.com</cite>
130
+                    <span class="c_tlbxTrg">
131
+                        <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
132
+                        </span>
133
+                    </span>
134
+                </div>
135
+                <p>
136
+                <strong>This</strong> should be the content.</p>
137
+            </div>
138
+        </li>
139
+        """
140
+        response = mock.Mock(text=html)
141
+        results = yahoo_news.response(response)
142
+        self.assertEqual(type(results), list)
143
+        self.assertEqual(len(results), 0)

+ 15
- 1
searx/tests/test_engines.py 查看文件

3
 from searx.tests.engines.test_bing_news import *  # noqa
3
 from searx.tests.engines.test_bing_news import *  # noqa
4
 from searx.tests.engines.test_blekko_images import *  # noqa
4
 from searx.tests.engines.test_blekko_images import *  # noqa
5
 from searx.tests.engines.test_btdigg import *  # noqa
5
 from searx.tests.engines.test_btdigg import *  # noqa
6
+from searx.tests.engines.test_currency_convert import *  # noqa
6
 from searx.tests.engines.test_dailymotion import *  # noqa
7
 from searx.tests.engines.test_dailymotion import *  # noqa
7
 from searx.tests.engines.test_deezer import *  # noqa
8
 from searx.tests.engines.test_deezer import *  # noqa
8
 from searx.tests.engines.test_deviantart import *  # noqa
9
 from searx.tests.engines.test_deviantart import *  # noqa
9
 from searx.tests.engines.test_digg import *  # noqa
10
 from searx.tests.engines.test_digg import *  # noqa
11
+from searx.tests.engines.test_duckduckgo import *  # noqa
12
+from searx.tests.engines.test_duckduckgo_definitions import *  # noqa
10
 from searx.tests.engines.test_dummy import *  # noqa
13
 from searx.tests.engines.test_dummy import *  # noqa
14
+from searx.tests.engines.test_faroo import *  # noqa
11
 from searx.tests.engines.test_flickr import *  # noqa
15
 from searx.tests.engines.test_flickr import *  # noqa
12
 from searx.tests.engines.test_flickr_noapi import *  # noqa
16
 from searx.tests.engines.test_flickr_noapi import *  # noqa
13
 from searx.tests.engines.test_gigablast import *  # noqa
17
 from searx.tests.engines.test_gigablast import *  # noqa
14
 from searx.tests.engines.test_github import *  # noqa
18
 from searx.tests.engines.test_github import *  # noqa
15
-from searx.tests.engines.test_www1x import *  # noqa
19
+from searx.tests.engines.test_google import *  # noqa
16
 from searx.tests.engines.test_google_images import *  # noqa
20
 from searx.tests.engines.test_google_images import *  # noqa
17
 from searx.tests.engines.test_google_news import *  # noqa
21
 from searx.tests.engines.test_google_news import *  # noqa
18
 from searx.tests.engines.test_kickass import *  # noqa
22
 from searx.tests.engines.test_kickass import *  # noqa
23
+from searx.tests.engines.test_mediawiki import *  # noqa
19
 from searx.tests.engines.test_mixcloud import *  # noqa
24
 from searx.tests.engines.test_mixcloud import *  # noqa
25
+from searx.tests.engines.test_openstreetmap import *  # noqa
26
+from searx.tests.engines.test_photon import *  # noqa
20
 from searx.tests.engines.test_piratebay import *  # noqa
27
 from searx.tests.engines.test_piratebay import *  # noqa
21
 from searx.tests.engines.test_searchcode_code import *  # noqa
28
 from searx.tests.engines.test_searchcode_code import *  # noqa
22
 from searx.tests.engines.test_searchcode_doc import *  # noqa
29
 from searx.tests.engines.test_searchcode_doc import *  # noqa
23
 from searx.tests.engines.test_soundcloud import *  # noqa
30
 from searx.tests.engines.test_soundcloud import *  # noqa
24
 from searx.tests.engines.test_stackoverflow import *  # noqa
31
 from searx.tests.engines.test_stackoverflow import *  # noqa
32
+from searx.tests.engines.test_startpage import *  # noqa
33
+from searx.tests.engines.test_subtitleseeker import *  # noqa
34
+from searx.tests.engines.test_twitter import *  # noqa
25
 from searx.tests.engines.test_vimeo import *  # noqa
35
 from searx.tests.engines.test_vimeo import *  # noqa
36
+from searx.tests.engines.test_www1x import *  # noqa
26
 from searx.tests.engines.test_www500px import *  # noqa
37
 from searx.tests.engines.test_www500px import *  # noqa
38
+from searx.tests.engines.test_yacy import *  # noqa
39
+from searx.tests.engines.test_yahoo import *  # noqa
27
 from searx.tests.engines.test_youtube import *  # noqa
40
 from searx.tests.engines.test_youtube import *  # noqa
41
+from searx.tests.engines.test_yahoo_news import *  # noqa