summaryrefslogtreecommitdiff
path: root/tests/unit/engines
diff options
context:
space:
mode:
authorJohannes 'josch' Schauer <josch@mister-muffin.de>2017-06-16 15:18:31 +0200
committerJohannes 'josch' Schauer <josch@mister-muffin.de>2017-06-16 15:18:31 +0200
commit7fe1a5ea5ff4aeecbbc2af673cbdc88fbbea18d5 (patch)
treefecfa8408befea37218807ea487e1f954afb356c /tests/unit/engines
New upstream version 0.12.0+dfsg1
Diffstat (limited to 'tests/unit/engines')
-rw-r--r--tests/unit/engines/__init__.py0
-rw-r--r--tests/unit/engines/seedpeer_fixture.html110
-rw-r--r--tests/unit/engines/test_archlinux.py106
-rw-r--r--tests/unit/engines/test_bing.py120
-rw-r--r--tests/unit/engines/test_bing_images.py84
-rw-r--r--tests/unit/engines/test_bing_news.py146
-rw-r--r--tests/unit/engines/test_blekko_images.py71
-rw-r--r--tests/unit/engines/test_btdigg.py384
-rw-r--r--tests/unit/engines/test_currency_convert.py43
-rw-r--r--tests/unit/engines/test_dailymotion.py111
-rw-r--r--tests/unit/engines/test_deezer.py57
-rw-r--r--tests/unit/engines/test_deviantart.py95
-rw-r--r--tests/unit/engines/test_digbt.py61
-rw-r--r--tests/unit/engines/test_digg.py101
-rw-r--r--tests/unit/engines/test_doku.py79
-rw-r--r--tests/unit/engines/test_duckduckgo.py100
-rw-r--r--tests/unit/engines/test_duckduckgo_definitions.py254
-rw-r--r--tests/unit/engines/test_duckduckgo_images.py72
-rw-r--r--tests/unit/engines/test_dummy.py26
-rw-r--r--tests/unit/engines/test_faroo.py116
-rw-r--r--tests/unit/engines/test_fdroid.py49
-rw-r--r--tests/unit/engines/test_flickr.py142
-rw-r--r--tests/unit/engines/test_flickr_noapi.py329
-rw-r--r--tests/unit/engines/test_framalibre.py103
-rw-r--r--tests/unit/engines/test_frinkiac.py50
-rw-r--r--tests/unit/engines/test_gigablast.py119
-rw-r--r--tests/unit/engines/test_github.py61
-rw-r--r--tests/unit/engines/test_google.py236
-rw-r--r--tests/unit/engines/test_google_images.py42
-rw-r--r--tests/unit/engines/test_google_news.py50
-rw-r--r--tests/unit/engines/test_ina.py64
-rw-r--r--tests/unit/engines/test_kickass.py397
-rw-r--r--tests/unit/engines/test_mediawiki.py130
-rw-r--r--tests/unit/engines/test_mixcloud.py67
-rw-r--r--tests/unit/engines/test_nyaa.py66
-rw-r--r--tests/unit/engines/test_openstreetmap.py199
-rw-r--r--tests/unit/engines/test_pdbe.py109
-rw-r--r--tests/unit/engines/test_photon.py166
-rw-r--r--tests/unit/engines/test_piratebay.py166
-rw-r--r--tests/unit/engines/test_qwant.py338
-rw-r--r--tests/unit/engines/test_reddit.py71
-rw-r--r--tests/unit/engines/test_scanr_structures.py175
-rw-r--r--tests/unit/engines/test_searchcode_code.py75
-rw-r--r--tests/unit/engines/test_searchcode_doc.py70
-rw-r--r--tests/unit/engines/test_seedpeer.py51
-rw-r--r--tests/unit/engines/test_soundcloud.py192
-rw-r--r--tests/unit/engines/test_spotify.py124
-rw-r--r--tests/unit/engines/test_stackoverflow.py106
-rw-r--r--tests/unit/engines/test_startpage.py140
-rw-r--r--tests/unit/engines/test_subtitleseeker.py174
-rw-r--r--tests/unit/engines/test_swisscows.py155
-rw-r--r--tests/unit/engines/test_tokyotoshokan.py110
-rw-r--r--tests/unit/engines/test_torrentz.py91
-rw-r--r--tests/unit/engines/test_twitter.py502
-rw-r--r--tests/unit/engines/test_vimeo.py36
-rw-r--r--tests/unit/engines/test_wikidata.py503
-rw-r--r--tests/unit/engines/test_wikipedia.py259
-rw-r--r--tests/unit/engines/test_wolframalpha_api.py166
-rw-r--r--tests/unit/engines/test_wolframalpha_noapi.py224
-rw-r--r--tests/unit/engines/test_www1x.py57
-rw-r--r--tests/unit/engines/test_www500px.py34
-rw-r--r--tests/unit/engines/test_yacy.py96
-rw-r--r--tests/unit/engines/test_yahoo.py179
-rw-r--r--tests/unit/engines/test_yahoo_news.py149
-rw-r--r--tests/unit/engines/test_youtube_api.py111
-rw-r--r--tests/unit/engines/test_youtube_noapi.py174
66 files changed, 9043 insertions, 0 deletions
diff --git a/tests/unit/engines/__init__.py b/tests/unit/engines/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/unit/engines/__init__.py
diff --git a/tests/unit/engines/seedpeer_fixture.html b/tests/unit/engines/seedpeer_fixture.html
new file mode 100644
index 0000000..28207bf
--- /dev/null
+++ b/tests/unit/engines/seedpeer_fixture.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ </head>
+ <body>
+ <div id="header">
+ <div id="whoIsYou">
+ <a href="/lang.php"><small>SeedPeer in your own language?</small></a>&nbsp;&nbsp;&nbsp;<a href="http://www.seedpeer.eu"><img src="/images/flags/uk.gif" width="16px" alt="Torrents EN" /></a> <a href="http://spanish.seedpeer.eu"><img src="/images/flags/es.gif" width="16px" alt="Torrents ES" /></a> <a href="http://german.seedpeer.eu"><img src="/images/flags/de.gif" width="16px" alt="Torrents DE" /></a> <a href="http://french.seedpeer.eu"><img src="/images/flags/fr.gif" width="16px" alt="Torrents FR" /></a> <a href="http://portuguese.seedpeer.eu"><img src="/images/flags/pt.gif" width="16px" alt="Torrents Portuguese" /></a> <a href="http://swedish.seedpeer.eu"><img src="/images/flags/se.gif" width="16px" alt="Torrents Sweden" /></a>
+ </div>
+
+ <script type="text/javascript">
+ whoIsYou();
+ </script>
+ <div id="search">
+ <form action="/search.php" method="get">
+ <input id="topsearchbar" name="search" value="narcos season 2" />
+ <input type="submit" class="searchbutton" value="Torrents" />
+ <input style="color:#000" type="submit" class="searchbutton" name="usenet" value="Usenet Binaries" />
+ </form>
+ <div id="suggestion"></div>
+ </div>
+ <div id="logo"><a href="/"><img src="/images/logo2.gif" alt="Seedpeer homepage" width="415" height="143" /></a></div>
+ <div id="subtext"><a href="/">Home</a> &gt; <a href="/search.html">Torrent search</a> &gt; Narcos season 2 | page 1</div>
+ </div>
+ <div id="nav">
+ <ul>
+ <!--
+ <li><font style="color:red;font-size:9px;font-weight:bold;">NEW</font><a title="Download TOP Games for FREE" rel="nofollow" href="http://www.bigrebelads.com/affiliate/index?ref=9301" target="_blank">FREE Games</a></li>
+
+ -->
+ <li style="border-left:none" id="categories"><a title="Browse Torrent Categories" href="/browse.html">Categories</a>
+ <ul>
+ <li><a title="Browse Anime Torrents" href="/browse.html#6">Anime</a></li>
+ <li><a title="Browse Game Torrents" href="/browse.html#4">Games</a></li>
+ <li><a title="Browse Movie Torrents" href="/browse.html#1">Movies</a></li>
+ <li><a title="Browse Music Torrents" href="/browse.html#3">Music</a></li>
+ <li><a title="Browse Software Torrents" href="/browse.html#5">Software</a></li>
+ <li><a title="Browse TV Torrents" href="/browse.html#2">TV Shows</a></li>
+ <li><a title="Browse Other Torrents" href="/browse.html#7">Others</a></li>
+ </ul>
+ </li>
+ <li><a title="Upload A Torrents" href="/upload.html">Upload torrent</a></li>
+ <li id="verified"><a title="Verified Torrents" href="/verified.html">Verified</a></li>
+ <li id="searchoptions"><a title="Search Torrents" href="/search.html">Torrent search</a></li>
+ <li id="newsgroups"><a style="color:#212b3e" title="News Groups" href="/usenet.html">Usenet Binaries</a></li>
+ <li id="about" style="border-right:none"><a rel="nofollow" href="/faq.html">About Us</a>
+ <ul>
+ <li><a title="SeedPeer Statistics" href="/stats.html">Statistics</a></li>
+ <li><a title="Contact Us" href="/contact.html">Contact</a></li>
+ <li><a title="Frequently Asked Questions" href="/faq.html">FAQ</a></li>
+ <li><a title="SeedPeer API" href="http://api.seedpeer.eu">Our API</a></li>
+ <li><a title="SeedPeer Blog" href="/blog">Blog</a></li>
+ </ul>
+ </li>
+ <!--<li><a href="/toolbar.php">Our Toolbar</a></li>-->
+ </ul>
+ <div class="clear"></div>
+ </div>
+ <div id="body"><div id="pageTop"></div>
+ <div id="headerbox"><h1>Verified <font class="colored">Narcos season 2</font> torrents</h1></div><table width="100%"><tr><th>
+ <span style="float:right">
+ <a href="/search/narcos-season-2/8/1.html"><img style="vertical-align:middle" src="/images/comments.gif" alt="comments" /></a> |
+ <a href="/search/narcos-season-2/7/1.html"><img style="vertical-align:middle" src="/images/ver.gif" alt="verified" /></a>
+ </span>
+ <a href="/search/narcos-season-2/1/1.html">Torrent name</a></th><th class="right"><a href="/search/narcos-season-2/2/1.html">Age</a></th><th class="right"><a href="/search/narcos-season-2/3/1.html">Size</a></th><th class="right"><a href="/search/narcos-season-2/4/1.html">Seeds</a></th><th class="right"><a href="/search/narcos-season-2/5/1.html">Peers</a></th><th class="center"><a href="/search/narcos-season-2/6/1.html">Health</a></th><td class="tableAd" rowspan="6"><iframe src="http://creative.wwwpromoter.com/13689?d=300x250" width="300" height="250" style="border: none;" frameborder="0" scrolling="no"></iframe></td></tr><tr class=""><td><a class="pblink" id="pblink_table_item_1" href="" data-tad="431726" data-last-search="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Narcos season 2</strong> Full Version</a></td><td class="right">20 hours</td><td class="right">681.3 MB</td><td class="right"><font color="green">28</font> </td><td class="right"><font color="navy">654</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr><tr class="tdark"><td><a class="pblink" id="pblink_table_item_2" href="" data-tad="431727" data-url="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Narcos season 2</strong> Trusted Source</a></td><td class="right">12 hours</td><td class="right">787.1 MB</td><td class="right"><font color="green">64</font> </td><td class="right"><font color="navy">220</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr><tr class=""><td><a class="pblink" id="pblink_table_item_3" href="" data-tad="431729" data-last-search="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Full Narcos season 2 Download</strong></a> <small><a class="pblink" id="pblink_table_item_4" href="" data-tad="431729" data-last-search="narcos+season+2" target="_blank" rel="nofollow">Usenet</a></small></td><td class="right">24 hours</td><td class="right">775.5 MB</td><td class="right"><font color="green">60</font> </td><td class="right"><font color="navy">236</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr><tr class="tdark"><td><a class="pblink" id="pblink_table_item_5" href="" data-tad="431730" data-last-search="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Narcos season 2</strong> 2014 - DIRECT STREAMING</a> <small><a class="pblink" id="pblink_table_item_6" href="" data-tad="431729" data-last-search="narcos+season+2" target="_blank" rel="nofollow">Movies</a></small></td><td class="right">17 hours</td><td class="right">654.1 MB</td><td class="right"><font color="green">2</font> </td><td class="right"><font color="navy">391</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr><tr class=""><td><a class="pblink" id="pblink_table_item_7" href="" data-tad="431731" data-last-search="narcos+season+2" target="_blank" rel="nofollow"><strong class='colored'>Narcos season 2</strong> 2014</a> <small><a class="pblink" id="pblink_table_item_8" href="" data-tad="431729" data-last-search="narcos+season+2" target="_blank" rel="nofollow">Movies</a></small></td><td class="right">20 hours</td><td class="right">754.5 MB</td><td class="right"><font color="green">21</font> </td><td class="right"><font color="navy">919</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" /></td></tr></table><br /><br /><center><iframe src='http://creative.wwwpromoter.com/13689?d=728x90' width='728' height='90' style='border: none;' frameborder='0' scrolling='no'></iframe><center><span style="float:right;margin:1em .2em 0 0"><a title="Download at the speed of your connection" href="/usenet.php?search=narcos+season+2"><img src="/images/dlf.gif" alt="Search Binaries" /></a></span><div style="margin-bottom:1em;margin-right:290px" id="headerbox"><h1><a href="/searchfeed/narcos+season+2.xml" target="_blank" title="SeedPeer RSS Torrent Search Feed fornarcos season 2"><img src="/images/feedIcon.png" border="0" /></a>&nbsp;2 <font class="colored">Narcos season 2</font> Torrents were found</h1></div><table width="100%"><tr><th>
+ <span style="float:right">
+ <a href="/search/narcos-season-2/8/1.html"><img style="vertical-align:middle" src="/images/comments.gif" alt="comments" /></a> |
+ <a href="/search/narcos-season-2/7/1.html"><img style="vertical-align:middle" src="/images/ver.gif" alt="verified" /></a>
+ </span>
+ <a href="/search/narcos-season-2/1/1.html">Torrent name</a></th><th class="right"><a href="/search/narcos-season-2/2/1.html">Age</a></th><th class="right"><a href="/search/narcos-season-2/3/1.html">Size</a></th><th class="right"><a href="/search/narcos-season-2/4/1.html">Seeds</a></th><th class="right"><a href="/search/narcos-season-2/5/1.html">Peers</a></th><th class="center"><a href="/search/narcos-season-2/6/1.html">Health</a></th></tr><tr class=""><td><small class="comments"><a href="http://www.facebook.com/sharer.php?t=Download%20<strong class='colored'>Narcos</strong> <strong class='colored'>Season</strong> <strong class='colored'>2</strong> Complete 7<strong class='colored'>2</strong>0p WebRip EN-SUB x<strong class='colored'>2</strong>64-[MULVAcoded] S0<strong class='colored'>2</strong>%20 torrent&u=http://seedpeer.seedpeer.eu/details/11686840/Narcos-Season-2-Complete-720p-WebRip-EN-SUB-x264-[MULVAcoded]-S02.html"><img src="/images/facebook.png" alt="Add to Facebook" width="14" height="14" /></a></small><a href="/details/11686840/Narcos-Season-2-Complete-720p-WebRip-EN-SUB-x264-[MULVAcoded]-S02.html"><strong class='colored'>Narcos</strong> <strong class='colored'>Season</strong> <strong class='colored'>2</strong> Complete 7<strong class='colored'>2</strong>0p WebRip EN-SUB x<strong class='colored'>2</strong>64-[MULVAcoded] S0<strong class='colored'>2</strong> <small><a href="/browse.html#11686840"></a></small></a></td><td class="right">19 hours</td><td class="right">4.39 GB</td><td class="right"><font color="green">715</font> </td><td class="right"><font color="navy">183</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" width="40" height="11" /></td></tr><tr class="tdark"><td><small class="comments"><a href="http://www.facebook.com/sharer.php?t=Download%20<strong class='colored'>Narcos</strong> - <strong class='colored'>Season</strong> <strong class='colored'>2</strong> - 7<strong class='colored'>2</strong>0p WEBRiP - x<strong class='colored'>2</strong>65 HEVC - ShAaNiG%20 torrent&u=http://seedpeer.seedpeer.eu/details/11685972/Narcos---Season-2---720p-WEBRiP---x265-HEVC---ShAaNiG.html"><img src="/images/facebook.png" alt="Add to Facebook" width="14" height="14" /></a></small><a href="/details/11685972/Narcos---Season-2---720p-WEBRiP---x265-HEVC---ShAaNiG.html"><strong class='colored'>Narcos</strong> - <strong class='colored'>Season</strong> <strong class='colored'>2</strong> - 7<strong class='colored'>2</strong>0p WEBRiP - x<strong class='colored'>2</strong>65 HEVC - ShAaNiG <small><a href="/browse.html#11685972"></a></small></a></td><td class="right">1 day</td><td class="right">2.48 GB</td><td class="right"><font color="green">861</font> </td><td class="right"><font color="navy">332</font> </td><td class="center"><img src="/images/h5.gif" alt="Health" width="40" height="11" /></td></tr></table><div id="headerbox"><h1>Related searches for: <font class="colored">Narcos season 2</font></h1></div><div id="search_suggestions"><br />Other suggested searches: </div><br /><a href="http://torrentz2.eu/search?f=narcos-season-2">Search for "narcos-season-2" on Torrentz2.eu</a><br /><a href="http://torrent-finder.info/show.php?q=narcos-season-2">Search for "narcos-season-2" on Torrent-Finder</a><br /><center><iframe src='http://creative.wwwpromoter.com/13689?d=300x250' width='300' height='250' style='border: none;' frameborder='0' scrolling='no'></iframe>&nbsp;<iframe src='http://creative.wwwpromoter.com/13689?d=300x250' width='300' height='250' style='border: none;' frameborder='0' scrolling='no'></iframe>&nbsp;<iframe src='http://creative.wwwpromoter.com/13689?d=300x250' width='300' height='250' style='border: none;' frameborder='0' scrolling='no'></iframe></center><div id="footer">
+ <table width="100%">
+ <tr>
+ <td width="30%">
+ <h2>Torrents Download</h2>
+ <a href="/">Torrent search</a><br />
+ <a href="/browse.html">Browse categories</a><br />
+ <a href="/verified.html">Verified Torrents</a><br />
+ <a href="/order-date.html">Today's torrents</a><br />
+ <a href="/yesterday.html">Yesterday's torrents</a><br />
+ <a href="/stats.html">Statistics</a><br />
+ <br />
+ <a href="/faq.html#copyright"><strong>Copyright & Removal</strong></a>
+ </td>
+ <td width="30%"><h2>Cool Stuff</h2>
+ <a href="/promotional.php">Promotional</a><br />
+ <a href="/contact.html">Advertising Information</a><br />
+ <strong><a href="/plugins.php" title="Add a search plugin to Firefox or Internet Explorer">Search Plugin <span style="color:red">*</span></a></strong><br />
+ <a href="http://www.utorrent.com">&micro;Torrent Client</a><br />
+ <a href="/blog">Seedpeer Blog</a><br />
+ </td>
+ <td width="30%"><h2>Links</h2>
+ <a href="http://www.sumotorrent.com" target="_blank"><strong>SumoTorrent</strong></a><br />
+ <a href="http://www.torrent-finder.info" target="_blank"><strong>Torrent Finder</strong></a><br />
+ <a href="http://www.torrentpond.com" target="_blank"><strong>TorrentPond</strong></a><br />
+ <a href="https://www.limetorrents.cc" target="_blank">LimeTorrents.cc</a><br />
+ <a href="http://www.torrents.to/" target="_blank">Torrents.to</a><br />
+ <a href="http://www.torrentfunk.com" target="_blank">TorrentFunk</a><br />
+ <a href="https://monova.org" target="_blank">Monova</a><br />
+ <a href="http://www.torrentroom.com" target="_blank">TorrentRoom</a><br />
+ <a href="http://www.katcr.co/" target="_blank">Kickass Torrents Community</a><br />
+ </td>
+ <td width="10%"><div id="bottomlogo"></div></td>
+ </tr>
+ </table>
+ <br />
+ <br />
+ </div>
+ </div>
+ </body>
+ </html> \ No newline at end of file
diff --git a/tests/unit/engines/test_archlinux.py b/tests/unit/engines/test_archlinux.py
new file mode 100644
index 0000000..e4ee033
--- /dev/null
+++ b/tests/unit/engines/test_archlinux.py
@@ -0,0 +1,106 @@
+from collections import defaultdict
+import mock
+from searx.engines import archlinux
+from searx.testing import SearxTestCase
+
+domains = {
+ 'all': 'https://wiki.archlinux.org',
+ 'de': 'https://wiki.archlinux.de',
+ 'fr': 'https://wiki.archlinux.fr',
+ 'ja': 'https://wiki.archlinuxjp.org',
+ 'ro': 'http://wiki.archlinux.ro',
+ 'tr': 'http://archtr.org/wiki'
+}
+
+
+class TestArchLinuxEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dic = defaultdict(dict)
+ dic['pageno'] = 1
+ dic['language'] = 'en_US'
+ params = archlinux.request(query, dic)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('wiki.archlinux.org' in params['url'])
+
+ for lang, domain in domains.items():
+ dic['language'] = lang
+ params = archlinux.request(query, dic)
+ self.assertTrue(domain in params['url'])
+
+ def test_response(self):
+ response = mock.Mock(text='<html></html>',
+ search_params={'language': 'en_US'})
+ self.assertEqual(archlinux.response(response), [])
+
+ html = """
+ <ul class="mw-search-results">
+ <li>
+ <div class="mw-search-result-heading">
+ <a href="/index.php/ATI" title="ATI">ATI</a>
+ </div>
+ <div class="searchresult">
+ Lorem ipsum dolor sit amet
+ </div>
+ <div class="mw-search-result-data">
+ 30 KB (4,630 words) - 19:04, 17 March 2016</div>
+ </li>
+ <li>
+ <div class="mw-search-result-heading">
+ <a href="/index.php/Frequently_asked_questions" title="Frequently asked questions">
+ Frequently asked questions
+ </a>
+ </div>
+ <div class="searchresult">
+ CPUs with AMDs instruction set "AMD64"
+ </div>
+ <div class="mw-search-result-data">
+ 17 KB (2,722 words) - 20:13, 21 March 2016
+ </div>
+ </li>
+ <li>
+ <div class="mw-search-result-heading">
+ <a href="/index.php/CPU_frequency_scaling" title="CPU frequency scaling">CPU frequency scaling</a>
+ </div>
+ <div class="searchresult">
+ ondemand for AMD and older Intel CPU
+ </div>
+ <div class="mw-search-result-data">
+ 15 KB (2,319 words) - 23:46, 16 March 2016
+ </div>
+ </li>
+ </ul>
+ """
+
+ expected = [
+ {
+ 'title': 'ATI',
+ 'url': 'https://wiki.archlinux.org/index.php/ATI'
+ },
+ {
+ 'title': 'Frequently asked questions',
+ 'url': 'https://wiki.archlinux.org/index.php/Frequently_asked_questions'
+ },
+ {
+ 'title': 'CPU frequency scaling',
+ 'url': 'https://wiki.archlinux.org/index.php/CPU_frequency_scaling'
+ }
+ ]
+
+ response = mock.Mock(text=html)
+ response.search_params = {
+ 'language': 'en_US'
+ }
+ results = archlinux.response(response)
+
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), len(expected))
+
+ i = 0
+ for exp in expected:
+ res = results[i]
+ i += 1
+ for key, value in exp.items():
+ self.assertEqual(res[key], value)
diff --git a/tests/unit/engines/test_bing.py b/tests/unit/engines/test_bing.py
new file mode 100644
index 0000000..523ec57
--- /dev/null
+++ b/tests/unit/engines/test_bing.py
@@ -0,0 +1,120 @@
+from collections import defaultdict
+import mock
+from searx.engines import bing
+from searx.testing import SearxTestCase
+
+
+class TestBingEngine(SearxTestCase):
+
+ def test_request(self):
+ query = u'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['language'] = 'fr_FR'
+ params = bing.request(query.encode('utf-8'), dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('language%3AFR' in params['url'])
+ self.assertTrue('bing.com' in params['url'])
+
+ dicto['language'] = 'all'
+ params = bing.request(query.encode('utf-8'), dicto)
+ self.assertTrue('language' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, bing.response, None)
+ self.assertRaises(AttributeError, bing.response, [])
+ self.assertRaises(AttributeError, bing.response, '')
+ self.assertRaises(AttributeError, bing.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(bing.response(response), [])
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(bing.response(response), [])
+
+ html = """
+ <div class="sa_cc" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
+ <div Class="sa_mc">
+ <div class="sb_tlst">
+ <h3>
+ <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
+ <strong>This</strong> should be the title</a>
+ </h3>
+ </div>
+ <div class="sb_meta"><cite><strong>this</strong>.meta.com</cite>
+ <span class="c_tlbxTrg">
+ <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
+ </span>
+ </span>
+ </div>
+ <p><strong>This</strong> should be the content.</p>
+ </div>
+ </div>
+ """
+ response = mock.Mock(text=html)
+ results = bing.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This should be the title')
+ self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
+ self.assertEqual(results[0]['content'], 'This should be the content.')
+
+ html = """
+ <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
+ <div Class="sa_mc">
+ <div class="sb_tlst">
+ <h2>
+ <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
+ <strong>This</strong> should be the title</a>
+ </h2>
+ </div>
+ <div class="sb_meta"><cite><strong>this</strong>.meta.com</cite>
+ <span class="c_tlbxTrg">
+ <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
+ </span>
+ </span>
+ </div>
+ <p><strong>This</strong> should be the content.</p>
+ </div>
+ </li>
+ """
+ response = mock.Mock(text=html)
+ results = bing.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This should be the title')
+ self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
+ self.assertEqual(results[0]['content'], 'This should be the content.')
+
+ def test_fetch_supported_languages(self):
+ html = """<html></html>"""
+ response = mock.Mock(text=html)
+ results = bing._fetch_supported_languages(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ html = """
+ <html>
+ <body>
+ <form>
+ <div id="limit-languages">
+ <div>
+ <div><input id="es" value="es"></input></div>
+ </div>
+ <div>
+ <div><input id="pt_BR" value="pt_BR"></input></div>
+ <div><input id="pt_PT" value="pt_PT"></input></div>
+ </div>
+ </div>
+ </form>
+ </body>
+ </html>
+ """
+ response = mock.Mock(text=html)
+ languages = bing._fetch_supported_languages(response)
+ self.assertEqual(type(languages), list)
+ self.assertEqual(len(languages), 3)
+ self.assertIn('es', languages)
+ self.assertIn('pt-BR', languages)
+ self.assertIn('pt-PT', languages)
diff --git a/tests/unit/engines/test_bing_images.py b/tests/unit/engines/test_bing_images.py
new file mode 100644
index 0000000..287f134
--- /dev/null
+++ b/tests/unit/engines/test_bing_images.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import bing_images
+from searx.testing import SearxTestCase
+
+
+class TestBingImagesEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr_FR'
+ dicto['safesearch'] = 1
+ dicto['time_range'] = ''
+ params = bing_images.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('bing.com' in params['url'])
+ self.assertTrue('SRCHHPGUSR' in params['cookies'])
+ self.assertTrue('fr' in params['cookies']['SRCHHPGUSR'])
+
+ dicto['language'] = 'all'
+ params = bing_images.request(query, dicto)
+ self.assertIn('SRCHHPGUSR', params['cookies'])
+ self.assertIn('en', params['cookies']['SRCHHPGUSR'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, bing_images.response, None)
+ self.assertRaises(AttributeError, bing_images.response, [])
+ self.assertRaises(AttributeError, bing_images.response, '')
+ self.assertRaises(AttributeError, bing_images.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(bing_images.response(response), [])
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(bing_images.response(response), [])
+
+ html = """
+ <div id="mmComponent_images_1">
+ <ul>
+ <li>
+ <div>
+ <div class="imgpt">
+ <a m='{"purl":"page_url","murl":"img_url"}' mad='{"turl":"thumb_url"}'>
+ <img src="" alt="alt text" />
+ </a>
+ </div>
+ <div></div>
+ </div>
+ <div>
+ <div class="imgpt">
+ <a m='{"purl":"page_url2","murl":"img_url2"}' mad='{"turl":"thumb_url2"}'>
+ <img src="" alt="alt text 2" />
+ </a>
+ </div>
+ </div>
+ </li>
+ </ul>
+ <ul>
+ <li>
+ <div>
+ <div class="imgpt">
+ <a m='{"purl":"page_url3","murl":"img_url3"}' mad='{"turl":"thumb_url3"}'>
+ <img src="" alt="alt text 3" />
+ </a>
+ </div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ """
+ html = html.replace('\r\n', '').replace('\n', '').replace('\r', '')
+ response = mock.Mock(text=html)
+ results = bing_images.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 3)
+ self.assertEqual(results[0]['title'], 'alt text')
+ self.assertEqual(results[0]['url'], 'page_url')
+ self.assertEqual(results[0]['content'], '')
+ self.assertEqual(results[0]['thumbnail_src'], 'thumb_url')
+ self.assertEqual(results[0]['img_src'], 'img_url')
diff --git a/tests/unit/engines/test_bing_news.py b/tests/unit/engines/test_bing_news.py
new file mode 100644
index 0000000..1f1aeca
--- /dev/null
+++ b/tests/unit/engines/test_bing_news.py
@@ -0,0 +1,146 @@
+from collections import defaultdict
+import mock
+from searx.engines import bing_news
+from searx.testing import SearxTestCase
+import lxml
+
+
+class TestBingNewsEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr_FR'
+ dicto['time_range'] = ''
+ params = bing_news.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('bing.com', params['url'])
+ self.assertIn('fr', params['url'])
+
+ dicto['language'] = 'all'
+ params = bing_news.request(query, dicto)
+ self.assertIn('en', params['url'])
+
+ def test_no_url_in_request_year_time_range(self):
+ dicto = defaultdict(dict)
+ query = 'test_query'
+ dicto['time_range'] = 'year'
+ params = bing_news.request(query, dicto)
+ self.assertEqual({}, params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, bing_news.response, None)
+ self.assertRaises(AttributeError, bing_news.response, [])
+ self.assertRaises(AttributeError, bing_news.response, '')
+ self.assertRaises(AttributeError, bing_news.response, '[]')
+
+ response = mock.Mock(content='<html></html>')
+ self.assertEqual(bing_news.response(response), [])
+
+ response = mock.Mock(content='<html></html>')
+ self.assertEqual(bing_news.response(response), [])
+
+ html = """<?xml version="1.0" encoding="utf-8" ?>
+<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
+ <channel>
+ <title>python - Bing News</title>
+ <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ <description>Search results</description>
+ <image>
+ <url>http://10.53.64.9/rsslogo.gif</url>
+ <title>test</title>
+ <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ </image>
+ <copyright>Copyright</copyright>
+ <item>
+ <title>Title</title>
+ <link>https://www.bing.com/news/apiclick.aspx?ref=FexRss&amp;aid=&amp;tid=c237eccc50bd4758b106a5e3c94fce09&amp;url=http%3a%2f%2furl.of.article%2f&amp;c=xxxxxxxxx&amp;mkt=en-us</link>
+ <description>Article Content</description>
+ <pubDate>Tue, 02 Jun 2015 13:37:00 GMT</pubDate>
+ <News:Source>Infoworld</News:Source>
+ <News:Image>http://a1.bing4.com/th?id=ON.13371337133713371337133713371337&amp;pid=News</News:Image>
+ <News:ImageSize>w={0}&amp;h={1}&amp;c=7</News:ImageSize>
+ <News:ImageKeepOriginalRatio></News:ImageKeepOriginalRatio>
+ <News:ImageMaxWidth>620</News:ImageMaxWidth>
+ <News:ImageMaxHeight>413</News:ImageMaxHeight>
+ </item>
+ <item>
+ <title>Another Title</title>
+ <link>https://www.bing.com/news/apiclick.aspx?ref=FexRss&amp;aid=&amp;tid=c237eccc50bd4758b106a5e3c94fce09&amp;url=http%3a%2f%2fanother.url.of.article%2f&amp;c=xxxxxxxxx&amp;mkt=en-us</link>
+ <description>Another Article Content</description>
+ <pubDate>Tue, 02 Jun 2015 13:37:00 GMT</pubDate>
+ </item>
+ </channel>
+</rss>""" # noqa
+ response = mock.Mock(content=html.encode('utf-8'))
+ results = bing_news.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://url.of.article/')
+ self.assertEqual(results[0]['content'], 'Article Content')
+ self.assertEqual(results[0]['img_src'], 'https://www.bing.com/th?id=ON.13371337133713371337133713371337')
+ self.assertEqual(results[1]['title'], 'Another Title')
+ self.assertEqual(results[1]['url'], 'http://another.url.of.article/')
+ self.assertEqual(results[1]['content'], 'Another Article Content')
+ self.assertNotIn('img_src', results[1])
+
+ html = """<?xml version="1.0" encoding="utf-8" ?>
+<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
+ <channel>
+ <title>python - Bing News</title>
+ <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ <description>Search results</description>
+ <image>
+ <url>http://10.53.64.9/rsslogo.gif</url>
+ <title>test</title>
+ <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ </image>
+ <copyright>Copyright</copyright>
+ <item>
+ <title>Title</title>
+ <link>http://another.url.of.article/</link>
+ <description>Article Content</description>
+ <pubDate>garbage</pubDate>
+ <News:Source>Infoworld</News:Source>
+ <News:Image>http://another.bing.com/image</News:Image>
+ <News:ImageSize>w={0}&amp;h={1}&amp;c=7</News:ImageSize>
+ <News:ImageKeepOriginalRatio></News:ImageKeepOriginalRatio>
+ <News:ImageMaxWidth>620</News:ImageMaxWidth>
+ <News:ImageMaxHeight>413</News:ImageMaxHeight>
+ </item>
+ </channel>
+</rss>""" # noqa
+ response = mock.Mock(content=html.encode('utf-8'))
+ results = bing_news.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://another.url.of.article/')
+ self.assertEqual(results[0]['content'], 'Article Content')
+ self.assertEqual(results[0]['img_src'], 'http://another.bing.com/image')
+
+ html = """<?xml version="1.0" encoding="utf-8" ?>
+<rss version="2.0" xmlns:News="https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS">
+ <channel>
+ <title>python - Bing News</title>
+ <link>https://www.bing.com:443/news/search?q=python&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ <description>Search results</description>
+ <image>
+ <url>http://10.53.64.9/rsslogo.gif</url>
+ <title>test</title>
+ <link>https://www.bing.com:443/news/search?q=test&amp;setmkt=en-US&amp;first=1&amp;format=RSS</link>
+ </image>
+ </channel>
+</rss>""" # noqa
+
+ response = mock.Mock(content=html.encode('utf-8'))
+ results = bing_news.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ html = """<?xml version="1.0" encoding="utf-8" ?>gabarge"""
+ response = mock.Mock(content=html.encode('utf-8'))
+ self.assertRaises(lxml.etree.XMLSyntaxError, bing_news.response, response)
diff --git a/tests/unit/engines/test_blekko_images.py b/tests/unit/engines/test_blekko_images.py
new file mode 100644
index 0000000..beb0853
--- /dev/null
+++ b/tests/unit/engines/test_blekko_images.py
@@ -0,0 +1,71 @@
+from collections import defaultdict
+import mock
+from searx.engines import blekko_images
+from searx.testing import SearxTestCase
+
+
+class TestBlekkoImagesEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['safesearch'] = 1
+ params = blekko_images.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('blekko.com', params['url'])
+ self.assertIn('page', params['url'])
+
+ dicto['pageno'] = 1
+ params = blekko_images.request(query, dicto)
+ self.assertNotIn('page', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, blekko_images.response, None)
+ self.assertRaises(AttributeError, blekko_images.response, [])
+ self.assertRaises(AttributeError, blekko_images.response, '')
+ self.assertRaises(AttributeError, blekko_images.response, '[]')
+
+ response = mock.Mock(text='[]')
+ self.assertEqual(blekko_images.response(response), [])
+
+ json = """
+ [
+ {
+ "c": 1,
+ "page_url": "http://result_url.html",
+ "title": "Photo title",
+ "tn_url": "http://ts1.mm.bing.net/th?id=HN.608050619474382748&pid=15.1",
+ "url": "http://result_image.jpg"
+ },
+ {
+ "c": 2,
+ "page_url": "http://companyorange.simpsite.nl/OSM",
+ "title": "OSM",
+ "tn_url": "http://ts2.mm.bing.net/th?id=HN.608048068264919461&pid=15.1",
+ "url": "http://simpsite.nl/userdata2/58985/Home/OSM.bmp"
+ },
+ {
+ "c": 3,
+ "page_url": "http://invincible.webklik.nl/page/osm",
+ "title": "OSM",
+ "tn_url": "http://ts1.mm.bing.net/th?id=HN.608024514657649476&pid=15.1",
+ "url": "http://www.webklik.nl/user_files/2009_09/65324/osm.gif"
+ },
+ {
+ "c": 4,
+ "page_url": "http://www.offshorenorway.no/event/companyDetail/id/12492",
+ "title": "Go to OSM Offshore AS homepage",
+ "tn_url": "http://ts2.mm.bing.net/th?id=HN.608054265899847285&pid=15.1",
+ "url": "http://www.offshorenorway.no/firmalogo/OSM-logo.png"
+ }
+ ]
+ """
+ response = mock.Mock(text=json)
+ results = blekko_images.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 4)
+ self.assertEqual(results[0]['title'], 'Photo title')
+ self.assertEqual(results[0]['url'], 'http://result_url.html')
+ self.assertEqual(results[0]['img_src'], 'http://result_image.jpg')
diff --git a/tests/unit/engines/test_btdigg.py b/tests/unit/engines/test_btdigg.py
new file mode 100644
index 0000000..6a88e3f
--- /dev/null
+++ b/tests/unit/engines/test_btdigg.py
@@ -0,0 +1,384 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import btdigg
+from searx.testing import SearxTestCase
+
+
+class TestBtdiggEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = btdigg.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('btdigg.org', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, btdigg.response, None)
+ self.assertRaises(AttributeError, btdigg.response, [])
+ self.assertRaises(AttributeError, btdigg.response, '')
+ self.assertRaises(AttributeError, btdigg.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(btdigg.response(response), [])
+
+ html = u"""
+ <div id="search_res">
+ <table>
+ <tr>
+ <td class="idx">1</td>
+ <td>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="torrent_name">
+ <a href="/url">Should be the title</a>
+ </td>
+ </tr>
+ </table>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="ttth">
+ <a onclick="fclck(this.href)" href="magnet:?xt=urn:btih:magnet&amp;dn=Test"
+ title="Télécharger des liens Magnet">[magnet]</a>
+ </td>
+ <td class="ttth">
+ <a href="https://btcloud.io/manager?cmd=add&amp;info_hash=hash"
+ target="_blank" title="Ajouter à BTCloud">[cloud]</a>
+ </td>
+ <td>
+ <span class="attr_name">Taille:</span>
+ <span class="attr_val">8 B</span>
+ </td>
+ <td>
+ <span class="attr_name">Fichiers:</span>
+ <span class="attr_val">710</span>
+ </td>
+ <td>
+ <span class="attr_name">Téléchargements:</span>
+ <span class="attr_val">5</span>
+ </td>
+ <td>
+ <span class="attr_name">Temps:</span>
+ <span class="attr_val">417.8&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Dernière&nbsp;mise&nbsp;à&nbsp;jour:</span>
+ <span class="attr_val">5.3&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Faux:</span>
+ <span class="attr_val">Aucun</span>
+ </td>
+ </tr>
+ </table>
+ <pre class="snippet">
+ Content
+ </pre>
+ </td>
+ </tr>
+ </table>
+ </div>
+ """
+ response = mock.Mock(text=html.encode('utf-8'))
+ results = btdigg.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Should be the title')
+ self.assertEqual(results[0]['url'], 'https://btdigg.org/url')
+ self.assertEqual(results[0]['content'], 'Content')
+ self.assertEqual(results[0]['seed'], 5)
+ self.assertEqual(results[0]['leech'], 0)
+ self.assertEqual(results[0]['filesize'], 8)
+ self.assertEqual(results[0]['files'], 710)
+ self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:magnet&dn=Test')
+
+ html = """
+ <div id="search_res">
+ <table>
+ </table>
+ </div>
+ """
+ response = mock.Mock(text=html.encode('utf-8'))
+ results = btdigg.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ html = u"""
+ <div id="search_res">
+ <table>
+ <tr>
+ <td class="idx">1</td>
+ <td>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="torrent_name">
+ <a href="/url">Should be the title</a>
+ </td>
+ </tr>
+ </table>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="ttth">
+ <a onclick="fclck(this.href)" href="magnet:?xt=urn:btih:magnet&amp;dn=Test"
+ title="Télécharger des liens Magnet">[magnet]</a>
+ </td>
+ <td class="ttth">
+ <a href="https://btcloud.io/manager?cmd=add&amp;info_hash=hash"
+ target="_blank" title="Ajouter à BTCloud">[cloud]</a>
+ </td>
+ <td>
+ <span class="attr_name">Taille:</span>
+ <span class="attr_val">1 KB</span>
+ </td>
+ <td>
+ <span class="attr_name">Fichiers:</span>
+ <span class="attr_val">710</span>
+ </td>
+ <td>
+ <span class="attr_name">Téléchargements:</span>
+ <span class="attr_val">5</span>
+ </td>
+ <td>
+ <span class="attr_name">Temps:</span>
+ <span class="attr_val">417.8&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Dernière&nbsp;mise&nbsp;à&nbsp;jour:</span>
+ <span class="attr_val">5.3&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Faux:</span>
+ <span class="attr_val">Aucun</span>
+ </td>
+ </tr>
+ </table>
+ <pre class="snippet">
+ Content
+ </pre>
+ </td>
+ </tr>
+ <tr>
+ <td class="idx">1</td>
+ <td>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="torrent_name">
+ <a href="/url">Should be the title</a>
+ </td>
+ </tr>
+ </table>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="ttth">
+ <a onclick="fclck(this.href)" href="magnet:?xt=urn:btih:magnet&amp;dn=Test"
+ title="Télécharger des liens Magnet">[magnet]</a>
+ </td>
+ <td class="ttth">
+ <a href="https://btcloud.io/manager?cmd=add&amp;info_hash=hash"
+ target="_blank" title="Ajouter à BTCloud">[cloud]</a>
+ </td>
+ <td>
+ <span class="attr_name">Taille:</span>
+ <span class="attr_val">1 MB</span>
+ </td>
+ <td>
+ <span class="attr_name">Fichiers:</span>
+ <span class="attr_val">a</span>
+ </td>
+ <td>
+ <span class="attr_name">Téléchargements:</span>
+ <span class="attr_val">4</span>
+ </td>
+ <td>
+ <span class="attr_name">Temps:</span>
+ <span class="attr_val">417.8&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Dernière&nbsp;mise&nbsp;à&nbsp;jour:</span>
+ <span class="attr_val">5.3&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Faux:</span>
+ <span class="attr_val">Aucun</span>
+ </td>
+ </tr>
+ </table>
+ <pre class="snippet">
+ Content
+ </pre>
+ </td>
+ </tr>
+ <tr>
+ <td class="idx">1</td>
+ <td>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="torrent_name">
+ <a href="/url">Should be the title</a>
+ </td>
+ </tr>
+ </table>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="ttth">
+ <a onclick="fclck(this.href)" href="magnet:?xt=urn:btih:magnet&amp;dn=Test"
+ title="Télécharger des liens Magnet">[magnet]</a>
+ </td>
+ <td class="ttth">
+ <a href="https://btcloud.io/manager?cmd=add&amp;info_hash=hash"
+ target="_blank" title="Ajouter à BTCloud">[cloud]</a>
+ </td>
+ <td>
+ <span class="attr_name">Taille:</span>
+ <span class="attr_val">1 GB</span>
+ </td>
+ <td>
+ <span class="attr_name">Fichiers:</span>
+ <span class="attr_val">710</span>
+ </td>
+ <td>
+ <span class="attr_name">Téléchargements:</span>
+ <span class="attr_val">3</span>
+ </td>
+ <td>
+ <span class="attr_name">Temps:</span>
+ <span class="attr_val">417.8&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Dernière&nbsp;mise&nbsp;à&nbsp;jour:</span>
+ <span class="attr_val">5.3&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Faux:</span>
+ <span class="attr_val">Aucun</span>
+ </td>
+ </tr>
+ </table>
+ <pre class="snippet">
+ Content
+ </pre>
+ </td>
+ </tr>
+ <tr>
+ <td class="idx">1</td>
+ <td>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="torrent_name">
+ <a href="/url">Should be the title</a>
+ </td>
+ </tr>
+ </table>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="ttth">
+ <a onclick="fclck(this.href)" href="magnet:?xt=urn:btih:magnet&amp;dn=Test"
+ title="Télécharger des liens Magnet">[magnet]</a>
+ </td>
+ <td class="ttth">
+ <a href="https://btcloud.io/manager?cmd=add&amp;info_hash=hash"
+ target="_blank" title="Ajouter à BTCloud">[cloud]</a>
+ </td>
+ <td>
+ <span class="attr_name">Taille:</span>
+ <span class="attr_val">1 TB</span>
+ </td>
+ <td>
+ <span class="attr_name">Fichiers:</span>
+ <span class="attr_val">710</span>
+ </td>
+ <td>
+ <span class="attr_name">Téléchargements:</span>
+ <span class="attr_val">2</span>
+ </td>
+ <td>
+ <span class="attr_name">Temps:</span>
+ <span class="attr_val">417.8&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Dernière&nbsp;mise&nbsp;à&nbsp;jour:</span>
+ <span class="attr_val">5.3&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Faux:</span>
+ <span class="attr_val">Aucun</span>
+ </td>
+ </tr>
+ </table>
+ <pre class="snippet">
+ Content
+ </pre>
+ </td>
+ </tr>
+ <tr>
+ <td class="idx">1</td>
+ <td>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="torrent_name">
+ <a href="/url">Should be the title</a>
+ </td>
+ </tr>
+ </table>
+ <table class="torrent_name_tbl">
+ <tr>
+ <td class="ttth">
+ <a onclick="fclck(this.href)" href="magnet:?xt=urn:btih:magnet&amp;dn=Test"
+ title="Télécharger des liens Magnet">[magnet]</a>
+ </td>
+ <td class="ttth">
+ <a href="https://btcloud.io/manager?cmd=add&amp;info_hash=hash"
+ target="_blank" title="Ajouter à BTCloud">[cloud]</a>
+ </td>
+ <td>
+ <span class="attr_name">Taille:</span>
+ <span class="attr_val">a TB</span>
+ </td>
+ <td>
+ <span class="attr_name">Fichiers:</span>
+ <span class="attr_val">710</span>
+ </td>
+ <td>
+ <span class="attr_name">Téléchargements:</span>
+ <span class="attr_val">z</span>
+ </td>
+ <td>
+ <span class="attr_name">Temps:</span>
+ <span class="attr_val">417.8&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Dernière&nbsp;mise&nbsp;à&nbsp;jour:</span>
+ <span class="attr_val">5.3&nbsp;jours</span>
+ </td>
+ <td>
+ <span class="attr_name">Faux:</span>
+ <span class="attr_val">Aucun</span>
+ </td>
+ </tr>
+ </table>
+ <pre class="snippet">
+ Content
+ </pre>
+ </td>
+ </tr>
+ </table>
+ </div>
+ """
+ response = mock.Mock(text=html.encode('utf-8'))
+ results = btdigg.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 5)
+ self.assertEqual(results[0]['title'], 'Should be the title')
+ self.assertEqual(results[0]['url'], 'https://btdigg.org/url')
+ self.assertEqual(results[0]['content'], 'Content')
+ self.assertEqual(results[0]['seed'], 5)
+ self.assertEqual(results[0]['leech'], 0)
+ self.assertEqual(results[0]['files'], 710)
+ self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:magnet&dn=Test')
+ self.assertEqual(results[0]['filesize'], 1024)
+ self.assertEqual(results[1]['filesize'], 1048576)
+ self.assertEqual(results[2]['filesize'], 1073741824)
+ self.assertEqual(results[3]['filesize'], 1099511627776)
diff --git a/tests/unit/engines/test_currency_convert.py b/tests/unit/engines/test_currency_convert.py
new file mode 100644
index 0000000..2814d79
--- /dev/null
+++ b/tests/unit/engines/test_currency_convert.py
@@ -0,0 +1,43 @@
+from collections import defaultdict
+from datetime import datetime
+import mock
+from searx.engines import currency_convert
+from searx.testing import SearxTestCase
+
+
+class TestCurrencyConvertEngine(SearxTestCase):
+
+ def test_request(self):
+ query = b'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ params = currency_convert.request(query, dicto)
+ self.assertNotIn('url', params)
+
+ query = b'convert 10 Pound Sterlings to United States Dollars'
+ params = currency_convert.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn('finance.yahoo.com', params['url'])
+ self.assertIn('GBP', params['url'])
+ self.assertIn('USD', params['url'])
+
+ def test_response(self):
+ dicto = defaultdict(dict)
+ dicto['ammount'] = float(10)
+ dicto['from'] = "GBP"
+ dicto['to'] = "USD"
+ dicto['from_name'] = "pound sterling"
+ dicto['to_name'] = "United States dollar"
+ response = mock.Mock(text='a,b,c,d', search_params=dicto)
+ self.assertEqual(currency_convert.response(response), [])
+
+ csv = "2,0.5,1"
+ response = mock.Mock(text=csv, search_params=dicto)
+ results = currency_convert.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['answer'], '10.0 GBP = 5.0 USD, 1 GBP (pound sterling)' +
+ ' = 0.5 USD (United States dollar)')
+ now_date = datetime.now().strftime('%Y%m%d')
+ self.assertEqual(results[0]['url'], 'https://finance.yahoo.com/currency/converter-results/' +
+ now_date + '/10.0-gbp-to-usd.html')
diff --git a/tests/unit/engines/test_dailymotion.py b/tests/unit/engines/test_dailymotion.py
new file mode 100644
index 0000000..72071af
--- /dev/null
+++ b/tests/unit/engines/test_dailymotion.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import dailymotion
+from searx.testing import SearxTestCase
+
+
+class TestDailymotionEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['language'] = 'fr_FR'
+ params = dailymotion.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('dailymotion.com' in params['url'])
+ self.assertTrue('fr' in params['url'])
+
+ dicto['language'] = 'all'
+ params = dailymotion.request(query, dicto)
+ self.assertTrue('en' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, dailymotion.response, None)
+ self.assertRaises(AttributeError, dailymotion.response, [])
+ self.assertRaises(AttributeError, dailymotion.response, '')
+ self.assertRaises(AttributeError, dailymotion.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(dailymotion.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(dailymotion.response(response), [])
+
+ json = """
+ {
+ "page": 1,
+ "limit": 5,
+ "explicit": false,
+ "total": 289487,
+ "has_more": true,
+ "list": [
+ {
+ "created_time": 1422173451,
+ "title": "Title",
+ "description": "Description",
+ "duration": 81,
+ "url": "http://www.url",
+ "thumbnail_360_url": "http://thumbnail",
+ "id": "x2fit7q"
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = dailymotion.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertIn('x2fit7q', results[0]['embedded'])
+
+ json = r"""
+ {"toto":[
+ {"id":200,"name":"Artist Name",
+ "link":"http:\/\/www.dailymotion.com\/artist\/1217","type":"artist"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = dailymotion.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ def test_fetch_supported_languages(self):
+ json = r"""
+ {"list":[{"code":"af","name":"Afrikaans","native_name":"Afrikaans",
+ "localized_name":"Afrikaans","display_name":"Afrikaans"},
+ {"code":"ar","name":"Arabic","native_name":"\u0627\u0644\u0639\u0631\u0628\u064a\u0629",
+ "localized_name":"Arabic","display_name":"Arabic"},
+ {"code":"la","name":"Latin","native_name":null,
+ "localized_name":"Latin","display_name":"Latin"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ languages = dailymotion._fetch_supported_languages(response)
+ self.assertEqual(type(languages), dict)
+ self.assertEqual(len(languages), 3)
+ self.assertIn('af', languages)
+ self.assertIn('ar', languages)
+ self.assertIn('la', languages)
+
+ self.assertEqual(type(languages['af']), dict)
+ self.assertEqual(type(languages['ar']), dict)
+ self.assertEqual(type(languages['la']), dict)
+
+ self.assertIn('name', languages['af'])
+ self.assertIn('name', languages['ar'])
+ self.assertNotIn('name', languages['la'])
+
+ self.assertIn('english_name', languages['af'])
+ self.assertIn('english_name', languages['ar'])
+ self.assertIn('english_name', languages['la'])
+
+ self.assertEqual(languages['af']['name'], 'Afrikaans')
+ self.assertEqual(languages['af']['english_name'], 'Afrikaans')
+ self.assertEqual(languages['ar']['name'], u'العربية')
+ self.assertEqual(languages['ar']['english_name'], 'Arabic')
+ self.assertEqual(languages['la']['english_name'], 'Latin')
diff --git a/tests/unit/engines/test_deezer.py b/tests/unit/engines/test_deezer.py
new file mode 100644
index 0000000..5b9f55c
--- /dev/null
+++ b/tests/unit/engines/test_deezer.py
@@ -0,0 +1,57 @@
+from collections import defaultdict
+import mock
+from searx.engines import deezer
+from searx.testing import SearxTestCase
+
+
+class TestDeezerEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = deezer.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('deezer.com' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, deezer.response, None)
+ self.assertRaises(AttributeError, deezer.response, [])
+ self.assertRaises(AttributeError, deezer.response, '')
+ self.assertRaises(AttributeError, deezer.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(deezer.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(deezer.response(response), [])
+
+ json = r"""
+ {"data":[
+ {"id":100, "title":"Title of track",
+ "link":"https:\/\/www.deezer.com\/track\/1094042","duration":232,
+ "artist":{"id":200,"name":"Artist Name",
+ "link":"https:\/\/www.deezer.com\/artist\/1217","type":"artist"},
+ "album":{"id":118106,"title":"Album Title","type":"album"},"type":"track"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = deezer.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title of track')
+ self.assertEqual(results[0]['url'], 'https://www.deezer.com/track/1094042')
+ self.assertEqual(results[0]['content'], 'Artist Name - Album Title - Title of track')
+ self.assertTrue('100' in results[0]['embedded'])
+
+ json = r"""
+ {"data":[
+ {"id":200,"name":"Artist Name",
+ "link":"https:\/\/www.deezer.com\/artist\/1217","type":"artist"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = deezer.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_deviantart.py b/tests/unit/engines/test_deviantart.py
new file mode 100644
index 0000000..bd2cf18
--- /dev/null
+++ b/tests/unit/engines/test_deviantart.py
@@ -0,0 +1,95 @@
+from collections import defaultdict
+import mock
+from searx.engines import deviantart
+from searx.testing import SearxTestCase
+
+
+class TestDeviantartEngine(SearxTestCase):
+
+ def test_request(self):
+ dicto = defaultdict(dict)
+ query = 'test_query'
+ dicto['pageno'] = 0
+ dicto['time_range'] = ''
+ params = deviantart.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('deviantart.com' in params['url'])
+
+ def test_no_url_in_request_year_time_range(self):
+ dicto = defaultdict(dict)
+ query = 'test_query'
+ dicto['time_range'] = 'year'
+ params = deviantart.request(query, dicto)
+ self.assertEqual({}, params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, deviantart.response, None)
+ self.assertRaises(AttributeError, deviantart.response, [])
+ self.assertRaises(AttributeError, deviantart.response, '')
+ self.assertRaises(AttributeError, deviantart.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(deviantart.response(response), [])
+
+ response = mock.Mock(status_code=302)
+ self.assertEqual(deviantart.response(response), [])
+
+ html = """
+ <div id="page-1-results" class="page-results results-page-thumb torpedo-container">
+ <span class="thumb wide" href="http://amai911.deviantart.com/art/Horse-195212845"
+ data-super-full-width="900" data-super-full-height="600">
+ <a class="torpedo-thumb-link" href="https://url.of.image">
+ <img data-sigil="torpedo-img" src="https://url.of.thumbnail" />
+ </a>
+ <span class="info"><span class="title-wrap"><span class="title">Title of image</span></span>
+ </div>
+ """
+ response = mock.Mock(text=html)
+ results = deviantart.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title of image')
+ self.assertEqual(results[0]['url'], 'https://url.of.image')
+ self.assertNotIn('content', results[0])
+ self.assertEqual(results[0]['thumbnail_src'], 'https://url.of.thumbnail')
+
+ html = """
+ <span class="tt-fh-tc" style="width: 202px;">
+ <span class="tt-bb" style="width: 202px;">
+ </span>
+ <span class="shadow">
+ <a class="thumb" href="http://url.of.result/2nd.part.of.url"
+ title="Behoimi BE Animation Test by test-0, Jan 4,
+ 2010 in Digital Art &gt; Animation"> <i></i>
+ <img width="200" height="200" alt="Test"
+ src="http://url.of.thumbnail" data-src="http://th08.deviantart.net/test.jpg">
+ </a>
+ </span>
+ <!-- ^TTT -->
+ </span>
+ <span class="details">
+ <a href="http://test-0.deviantart.com/art/Test" class="t"
+ title="Behoimi BE Animation Test by test-0, Jan 4, 2010">
+ <span class="tt-fh-oe">Title of image</span> </a>
+ <small>
+ <span class="category">
+ <span class="age">
+ 5 years ago
+ </span>
+ in <a title="Behoimi BE Animation Test by test-0, Jan 4, 2010"
+ href="http://www.deviantart.com/browse/all/digitalart/animation/">Animation</a>
+ </span>
+ <div class="commentcount">
+ <a href="http://test-0.deviantart.com/art/Test#comments">
+ <span class="iconcommentsstats"></span>9 Comments</a>
+ </div>
+ <a class="mlt-link" href="http://www.deviantart.com/morelikethis/149167425">
+ <span class="mlt-icon"></span> <span class="mlt-text">More Like This</span> </a>
+ </span>
+ </small> <!-- TTT$ -->
+ """
+ response = mock.Mock(text=html)
+ results = deviantart.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_digbt.py b/tests/unit/engines/test_digbt.py
new file mode 100644
index 0000000..31c2eca
--- /dev/null
+++ b/tests/unit/engines/test_digbt.py
@@ -0,0 +1,61 @@
+from collections import defaultdict
+import mock
+from searx.engines import digbt
+from searx.testing import SearxTestCase
+
+
+class TestDigBTEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = digbt.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('digbt.org', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, digbt.response, None)
+ self.assertRaises(AttributeError, digbt.response, [])
+ self.assertRaises(AttributeError, digbt.response, '')
+ self.assertRaises(AttributeError, digbt.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(digbt.response(response), [])
+
+ html = """
+ <table class="table">
+ <tr><td class="x-item">
+ <div>
+ <a title="The Big Bang Theory" class="title" href="/The-Big-Bang-Theory-d2.html">
+ The Big <span class="highlight">Bang</span> Theory
+ </a>
+ <span class="ctime"><span style="color:red;">4 hours ago</span></span>
+ </div>
+ <div class="files">
+ <ul>
+ <li>The Big Bang Theory 2.9 GB</li>
+ <li>....</li>
+ </ul>
+ </div>
+ <div class="tail">
+ Files: 1 Size: 2.9 GB Downloads: 1 Updated: <span style="color:red;">4 hours ago</span>
+ &nbsp; &nbsp;
+ <a class="title" href="magnet:?xt=urn:btih:a&amp;dn=The+Big+Bang+Theory">
+ <span class="glyphicon glyphicon-magnet"></span> magnet-link
+ </a>
+ &nbsp; &nbsp;
+ </div>
+ </td></tr>
+ </table>
+ """
+ response = mock.Mock(text=html.encode('utf-8'))
+ results = digbt.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'The Big Bang Theory')
+ self.assertEqual(results[0]['url'], 'https://digbt.org/The-Big-Bang-Theory-d2.html')
+ self.assertEqual(results[0]['content'], 'The Big Bang Theory 2.9 GB ....')
+ self.assertEqual(results[0]['filesize'], 3113851289)
+ self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:a&dn=The+Big+Bang+Theory')
diff --git a/tests/unit/engines/test_digg.py b/tests/unit/engines/test_digg.py
new file mode 100644
index 0000000..6e7c9cc
--- /dev/null
+++ b/tests/unit/engines/test_digg.py
@@ -0,0 +1,101 @@
+from collections import defaultdict
+import mock
+from searx.engines import digg
+from searx.testing import SearxTestCase
+
+
+class TestDiggEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ params = digg.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('digg.com', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, digg.response, None)
+ self.assertRaises(AttributeError, digg.response, [])
+ self.assertRaises(AttributeError, digg.response, '')
+ self.assertRaises(AttributeError, digg.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(digg.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(digg.response(response), [])
+
+ json = """
+ {
+ "status": "ok",
+ "num": 10,
+ "next_position": 20,
+ "html": "<article itemscope itemtype=\\"http://schema.org/Article\\"
+ class=\\"story-container digg-story-el hentry entry story-1sRANah col-1\\"
+ data-content-id=\\"1sRANah\\" data-contenturl=\\"http://url.of.link\\"
+ data-position=\\"0\\" data-diggs=\\"24\\" data-tweets=\\"69\\"
+ data-digg-score=\\"1190\\"> <div class=\\"story-image story-image-thumb\\">
+ <a data-position=\\"0\\" data-content-id=\\"1sRANah\\"
+ class=\\"story-link\\" href=\\"http://www.thedailybeast.com/\\"
+ target=\\"_blank\\"><img class=\\"story-image-img\\"
+ src=\\"http://url.of.image.jpeg\\" width=\\"312\\" height=\\"170\\"
+ alt=\\"\\" /> </a> </div> <div class=\\"story-content\\"><header
+ class=\\"story-header\\"> <div itemprop=\\"alternativeHeadline\\"
+ class=\\"story-kicker\\" >Kicker</div> <h2 itemprop=\\"headline\\"
+ class=\\"story-title entry-title\\"><a class=\\"story-title-link story-link\\"
+ rel=\\"bookmark\\" itemprop=\\"url\\" href=\\"http://www.thedailybeast.com/\\"
+ target=\\"_blank\\">Title of article</h2> <div class=\\"story-meta\\">
+ <div class=\\"story-score \\">
+ <div class=\\"story-score-diggscore diggscore-1sRANah\\">1190</div>
+ <div class=\\"story-score-details\\"> <div class=\\"arrow\\"></div>
+ <ul class=\\"story-score-details-list\\"> <li
+ class=\\"story-score-detail story-score-diggs\\"><span
+ class=\\"label\\">Diggs:</span> <span class=\\"count diggs-1sRANah\\">24</span>
+ </li> <li class=\\"story-score-detail story-score-twitter\\"><span
+ class=\\"label\\">Tweets:</span> <span class=\\"count tweets-1sRANah\\">69</span>
+ </li> <li class=\\"story-score-detail story-score-facebook\\"><span
+ class=\\"label\\">Facebook Shares:</span> <span
+ class=\\"count fb_shares-1sRANah\\">1097</span></li> </ul> </div> </div>
+ <span class=\\"story-meta-item story-source\\"> <a
+ itemprop=\\"publisher copyrightHolder sourceOrganization provider\\"
+ class=\\"story-meta-item-link story-source-link\\"
+ href=\\"/source/thedailybeast.com\\">The Daily Beast </a> </span>
+ <span class=\\"story-meta-item story-tag first-tag\\"> <a
+ itemprop=\\"keywords\\" rel=\\"tag\\"
+ class=\\"story-meta-item-link story-tag-link\\" href=\\"/tag/news\\">News</a>
+ </span> <abbr class=\\"published story-meta-item story-timestamp\\"
+ title=\\"2014-10-18 14:53:45\\"> <time datetime=\\"2014-10-18 14:53:45\\">18 Oct 2014</time>
+ </abbr> </div> </header> </div> <ul class=\\"story-actions\\"> <li
+ class=\\"story-action story-action-digg btn-story-action-container\\">
+ <a class=\\"target digg-1sRANah\\" href=\\"#\\">Digg</a></li> <li
+ class=\\"story-action story-action-save btn-story-action-container\\">
+ <a class=\\"target save-1sRANah\\" href=\\"#\\">Save</a></li> <li
+ class=\\"story-action story-action-share\\"><a
+ class=\\"target share-facebook\\" href=\\"https://www.facebook.com/\\">Facebook</a></li>
+ <li class=\\"story-action story-action-share\\"><a class=\\"target share-twitter\\"
+ href=\\"https://twitter.com/\\">Twitter</a></li> </ul> </article>"
+ }
+ """
+ json = json.replace('\r\n', '').replace('\n', '').replace('\r', '')
+ response = mock.Mock(text=json)
+ results = digg.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title of article')
+ self.assertEqual(results[0]['url'], 'http://url.of.link')
+ self.assertEqual(results[0]['thumbnail'], 'http://url.of.image.jpeg')
+ self.assertEqual(results[0]['content'], '')
+
+ json = """
+ {
+ "status": "error",
+ "num": 10,
+ "next_position": 20
+ }
+ """
+ response = mock.Mock(text=json)
+ results = digg.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_doku.py b/tests/unit/engines/test_doku.py
new file mode 100644
index 0000000..22ddb7a
--- /dev/null
+++ b/tests/unit/engines/test_doku.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import doku
+from searx.testing import SearxTestCase
+
+
+class TestDokuEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ params = doku.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, doku.response, None)
+ self.assertRaises(AttributeError, doku.response, [])
+ self.assertRaises(AttributeError, doku.response, '')
+ self.assertRaises(AttributeError, doku.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(doku.response(response), [])
+
+ html = u"""
+ <div class="search_quickresult">
+ <h3>Pages trouvées :</h3>
+ <ul class="search_quickhits">
+ <li> <a href="/xfconf-query" class="wikilink1" title="xfconf-query">xfconf-query</a></li>
+ </ul>
+ <div class="clearer"></div>
+ </div>
+ """
+ response = mock.Mock(text=html)
+ results = doku.response(response)
+ expected = [{'content': '', 'title': 'xfconf-query', 'url': 'http://localhost:8090/xfconf-query'}]
+ self.assertEqual(doku.response(response), expected)
+
+ html = u"""
+ <dl class="search_results">
+ <dt><a href="/xvnc?s[]=query" class="wikilink1" title="xvnc">xvnc</a>: 40 Occurrences trouvées</dt>
+ <dd>er = /usr/bin/Xvnc
+ server_args = -inetd -<strong class="search_hit">query</strong> localhost -geometry 640x480 ... er = /usr/bin/Xvnc
+ server_args = -inetd -<strong class="search_hit">query</strong> localhost -geometry 800x600 ... er = /usr/bin/Xvnc
+ server_args = -inetd -<strong class="search_hit">query</strong> localhost -geometry 1024x768 ... er = /usr/bin/Xvnc
+ server_args = -inetd -<strong class="search_hit">query</strong> localhost -geometry 1280x1024 -depth 8 -Sec</dd>
+ <dt><a href="/postfix_mysql_tls_sasl_1404?s[]=query"
+ class="wikilink1"
+ title="postfix_mysql_tls_sasl_1404">postfix_mysql_tls_sasl_1404</a>: 14 Occurrences trouvées</dt>
+ <dd>tdepasse
+ hosts = 127.0.0.1
+ dbname = postfix
+ <strong class="search_hit">query</strong> = SELECT goto FROM alias WHERE address='%s' AND a... tdepasse
+ hosts = 127.0.0.1
+ dbname = postfix
+ <strong class="search_hit">query</strong> = SELECT domain FROM domain WHERE domain='%s'
+ #optional <strong class="search_hit">query</strong> to use when relaying for backup MX
+ #<strong class="search_hit">query</strong> = SELECT domain FROM domain WHERE domain='%s' and backupmx =</dd>
+ <dt><a href="/bind9?s[]=query" class="wikilink1" title="bind9">bind9</a>: 12 Occurrences trouvées</dt>
+ <dd> printcmd
+;; Got answer:
+;; -&gt;&gt;HEADER&lt;&lt;- opcode: <strong class="search_hit">QUERY</strong>, status: NOERROR, id: 13427
+;; flags: qr aa rd ra; <strong class="search_hit">QUERY</strong>: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
+
+[...]
+
+;; <strong class="search_hit">Query</strong> time: 1 msec
+;; SERVER: 127.0.0.1#53(127.0.0.1)
+;... par la requête (<strong class="search_hit">Query</strong> time) , entre la première et la deuxième requête.</dd>
+ </dl>
+ """
+ response = mock.Mock(text=html)
+ results = doku.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 3)
+ self.assertEqual(results[0]['title'], 'xvnc')
+# FIXME self.assertEqual(results[0]['url'], u'http://this.should.be.the.link/ű')
+# FIXME self.assertEqual(results[0]['content'], 'This should be the content.')
diff --git a/tests/unit/engines/test_duckduckgo.py b/tests/unit/engines/test_duckduckgo.py
new file mode 100644
index 0000000..eea4789
--- /dev/null
+++ b/tests/unit/engines/test_duckduckgo.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import duckduckgo
+from searx.testing import SearxTestCase
+
+
+class TestDuckduckgoEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'de-CH'
+ dicto['time_range'] = ''
+ params = duckduckgo.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('duckduckgo.com', params['url'])
+ self.assertIn('ch-de', params['url'])
+ self.assertIn('s=0', params['url'])
+
+ # when ddg uses non standard code
+ dicto['language'] = 'en-GB'
+ params = duckduckgo.request(query, dicto)
+ self.assertIn('uk-en', params['url'])
+
+ # no country given
+ duckduckgo.supported_languages = ['de-CH', 'en-US']
+ dicto['language'] = 'de'
+ params = duckduckgo.request(query, dicto)
+ self.assertIn('ch-de', params['url'])
+
+ def test_no_url_in_request_year_time_range(self):
+ dicto = defaultdict(dict)
+ query = 'test_query'
+ dicto['time_range'] = 'year'
+ params = duckduckgo.request(query, dicto)
+ self.assertEqual({}, params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, duckduckgo.response, None)
+ self.assertRaises(AttributeError, duckduckgo.response, [])
+ self.assertRaises(AttributeError, duckduckgo.response, '')
+ self.assertRaises(AttributeError, duckduckgo.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(duckduckgo.response(response), [])
+
+ html = u"""
+ <div class="result results_links results_links_deep web-result result--no-result">
+ <div class="links_main links_deep result__body">
+ <h2 class="result__title">
+ </h2>
+ <div class="no-results">No results</div>
+ <div class="result__extras">
+ </div>
+ </div>
+ </div>
+ """
+ response = mock.Mock(text=html)
+ results = duckduckgo.response(response)
+ self.assertEqual(duckduckgo.response(response), [])
+
+ html = u"""
+ <div class="result results_links results_links_deep web-result ">
+ <div class="links_main links_deep result__body">
+ <h2 class="result__title">
+ <a rel="nofollow" class="result__a" href="http://this.should.be.the.link/ű">
+ This <b>is</b> <b>the</b> title
+ </a>
+ </h2>
+ <a class="result__snippet" href="http://this.should.be.the.link/ű">
+ <b>This</b> should be the content.
+ </a>
+ <div class="result__extras">
+ </div>
+ </div>
+ </div>
+ """
+ response = mock.Mock(text=html)
+ results = duckduckgo.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], u'http://this.should.be.the.link/ű')
+ self.assertEqual(results[0]['content'], 'This should be the content.')
+
+ def test_fetch_supported_languages(self):
+ js = """some code...regions:{
+ "wt-wt":"All Results","ar-es":"Argentina","au-en":"Australia","at-de":"Austria","be-fr":"Belgium (fr)"
+ }some more code..."""
+ response = mock.Mock(text=js)
+ languages = list(duckduckgo._fetch_supported_languages(response))
+ self.assertEqual(len(languages), 5)
+ self.assertIn('wt-WT', languages)
+ self.assertIn('es-AR', languages)
+ self.assertIn('en-AU', languages)
+ self.assertIn('de-AT', languages)
+ self.assertIn('fr-BE', languages)
diff --git a/tests/unit/engines/test_duckduckgo_definitions.py b/tests/unit/engines/test_duckduckgo_definitions.py
new file mode 100644
index 0000000..feafe47
--- /dev/null
+++ b/tests/unit/engines/test_duckduckgo_definitions.py
@@ -0,0 +1,254 @@
+from collections import defaultdict
+import mock
+from searx.engines import duckduckgo_definitions
+from searx.testing import SearxTestCase
+
+
+class TestDDGDefinitionsEngine(SearxTestCase):
+
+ def test_result_to_text(self):
+ url = ''
+ text = 'Text'
+ html_result = 'Html'
+ result = duckduckgo_definitions.result_to_text(url, text, html_result)
+ self.assertEqual(result, text)
+
+ html_result = '<a href="url">Text in link</a>'
+ result = duckduckgo_definitions.result_to_text(url, text, html_result)
+ self.assertEqual(result, 'Text in link')
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'es'
+ params = duckduckgo_definitions.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('duckduckgo.com', params['url'])
+ self.assertIn('headers', params)
+ self.assertIn('Accept-Language', params['headers'])
+ self.assertIn('es', params['headers']['Accept-Language'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, duckduckgo_definitions.response, None)
+ self.assertRaises(AttributeError, duckduckgo_definitions.response, [])
+ self.assertRaises(AttributeError, duckduckgo_definitions.response, '')
+ self.assertRaises(AttributeError, duckduckgo_definitions.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(duckduckgo_definitions.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(duckduckgo_definitions.response(response), [])
+
+ json = """
+ {
+ "DefinitionSource": "definition source",
+ "Heading": "heading",
+ "ImageWidth": 0,
+ "RelatedTopics": [
+ {
+ "Result": "Top-level domains",
+ "Icon": {
+ "URL": "",
+ "Height": "",
+ "Width": ""
+ },
+ "FirstURL": "https://first.url",
+ "Text": "text"
+ },
+ {
+ "Topics": [
+ {
+ "Result": "result topic",
+ "Icon": {
+ "URL": "",
+ "Height": "",
+ "Width": ""
+ },
+ "FirstURL": "https://duckduckgo.com/?q=2%2F2",
+ "Text": "result topic text"
+ }
+ ],
+ "Name": "name"
+ }
+ ],
+ "Entity": "Entity",
+ "Type": "A",
+ "Redirect": "",
+ "DefinitionURL": "http://definition.url",
+ "AbstractURL": "https://abstract.url",
+ "Definition": "this is the definition",
+ "AbstractSource": "abstract source",
+ "Infobox": {
+ "content": [
+ {
+ "data_type": "string",
+ "value": "1999",
+ "label": "Introduced",
+ "wiki_order": 0
+ }
+ ],
+ "meta": [
+ {
+ "data_type": "string",
+ "value": ".test",
+ "label": "article_title"
+ }
+ ]
+ },
+ "Image": "image.png",
+ "ImageIsLogo": 0,
+ "Abstract": "abstract",
+ "AbstractText": "abstract text",
+ "AnswerType": "",
+ "ImageHeight": 0,
+ "Results": [{
+ "Result" : "result title",
+ "Icon" : {
+ "URL" : "result url",
+ "Height" : 16,
+ "Width" : 16
+ },
+ "FirstURL" : "result first url",
+ "Text" : "result text"
+ }
+ ],
+ "Answer": "answer"
+ }
+ """
+ response = mock.Mock(text=json)
+ results = duckduckgo_definitions.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 4)
+ self.assertEqual(results[0]['answer'], 'answer')
+ self.assertEqual(results[1]['title'], 'heading')
+ self.assertEqual(results[1]['url'], 'result first url')
+ self.assertEqual(results[2]['suggestion'], 'text')
+ self.assertEqual(results[3]['infobox'], 'heading')
+ self.assertEqual(results[3]['id'], 'https://definition.url')
+ self.assertEqual(results[3]['entity'], 'Entity')
+ self.assertIn('abstract', results[3]['content'])
+ self.assertIn('this is the definition', results[3]['content'])
+ self.assertEqual(results[3]['img_src'], 'image.png')
+ self.assertIn('Introduced', results[3]['attributes'][0]['label'])
+ self.assertIn('1999', results[3]['attributes'][0]['value'])
+ self.assertIn({'url': 'https://abstract.url', 'title': 'abstract source'}, results[3]['urls'])
+ self.assertIn({'url': 'http://definition.url', 'title': 'definition source'}, results[3]['urls'])
+ self.assertIn({'name': 'name', 'suggestions': ['result topic text']}, results[3]['relatedTopics'])
+
+ json = """
+ {
+ "DefinitionSource": "definition source",
+ "Heading": "heading",
+ "ImageWidth": 0,
+ "RelatedTopics": [],
+ "Entity": "Entity",
+ "Type": "A",
+ "Redirect": "",
+ "DefinitionURL": "",
+ "AbstractURL": "https://abstract.url",
+ "Definition": "",
+ "AbstractSource": "abstract source",
+ "Image": "",
+ "ImageIsLogo": 0,
+ "Abstract": "",
+ "AbstractText": "abstract text",
+ "AnswerType": "",
+ "ImageHeight": 0,
+ "Results": [],
+ "Answer": ""
+ }
+ """
+ response = mock.Mock(text=json)
+ results = duckduckgo_definitions.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['url'], 'https://abstract.url')
+ self.assertEqual(results[0]['title'], 'heading')
+ self.assertEqual(results[0]['content'], '')
+
+ json = """
+ {
+ "DefinitionSource": "definition source",
+ "Heading": "heading",
+ "ImageWidth": 0,
+ "RelatedTopics": [
+ {
+ "Result": "Top-level domains",
+ "Icon": {
+ "URL": "",
+ "Height": "",
+ "Width": ""
+ },
+ "FirstURL": "https://first.url",
+ "Text": "heading"
+ },
+ {
+ "Name": "name"
+ },
+ {
+ "Topics": [
+ {
+ "Result": "result topic",
+ "Icon": {
+ "URL": "",
+ "Height": "",
+ "Width": ""
+ },
+ "FirstURL": "https://duckduckgo.com/?q=2%2F2",
+ "Text": "heading"
+ }
+ ],
+ "Name": "name"
+ }
+ ],
+ "Entity": "Entity",
+ "Type": "A",
+ "Redirect": "",
+ "DefinitionURL": "http://definition.url",
+ "AbstractURL": "https://abstract.url",
+ "Definition": "this is the definition",
+ "AbstractSource": "abstract source",
+ "Infobox": {
+ "meta": [
+ {
+ "data_type": "string",
+ "value": ".test",
+ "label": "article_title"
+ }
+ ]
+ },
+ "Image": "image.png",
+ "ImageIsLogo": 0,
+ "Abstract": "abstract",
+ "AbstractText": "abstract text",
+ "AnswerType": "",
+ "ImageHeight": 0,
+ "Results": [{
+ "Result" : "result title",
+ "Icon" : {
+ "URL" : "result url",
+ "Height" : 16,
+ "Width" : 16
+ },
+ "Text" : "result text"
+ }
+ ],
+ "Answer": ""
+ }
+ """
+ response = mock.Mock(text=json)
+ results = duckduckgo_definitions.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['infobox'], 'heading')
+ self.assertEqual(results[0]['id'], 'https://definition.url')
+ self.assertEqual(results[0]['entity'], 'Entity')
+ self.assertIn('abstract', results[0]['content'])
+ self.assertIn('this is the definition', results[0]['content'])
+ self.assertEqual(results[0]['img_src'], 'image.png')
+ self.assertIn({'url': 'https://abstract.url', 'title': 'abstract source'}, results[0]['urls'])
+ self.assertIn({'url': 'http://definition.url', 'title': 'definition source'}, results[0]['urls'])
+ self.assertIn({'name': 'name', 'suggestions': []}, results[0]['relatedTopics'])
diff --git a/tests/unit/engines/test_duckduckgo_images.py b/tests/unit/engines/test_duckduckgo_images.py
new file mode 100644
index 0000000..5f94e3c
--- /dev/null
+++ b/tests/unit/engines/test_duckduckgo_images.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import duckduckgo_images
+from searx.testing import SearxTestCase
+
+
+class TestDuckduckgoImagesEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['is_test'] = True
+ dicto['pageno'] = 1
+ dicto['safesearch'] = 0
+ dicto['language'] = 'all'
+ params = duckduckgo_images.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('duckduckgo.com', params['url'])
+ self.assertIn('s=0', params['url'])
+ self.assertIn('p=-1', params['url'])
+ self.assertIn('vqd=12345', params['url'])
+
+ # test paging and safe search
+ dicto['pageno'] = 2
+ dicto['safesearch'] = 2
+ params = duckduckgo_images.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('s=50', params['url'])
+ self.assertIn('p=1', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, duckduckgo_images.response, None)
+ self.assertRaises(AttributeError, duckduckgo_images.response, [])
+ self.assertRaises(AttributeError, duckduckgo_images.response, '')
+ self.assertRaises(AttributeError, duckduckgo_images.response, '[]')
+
+ response = mock.Mock(text='If this error persists, please let us know: ops@duckduckgo.com')
+ self.assertEqual(duckduckgo_images.response(response), [])
+
+ json = u"""
+ {
+ "query": "test_query",
+ "results": [
+ {
+ "title": "Result 1",
+ "url": "https://site1.url",
+ "thumbnail": "https://thumb1.nail",
+ "image": "https://image1"
+ },
+ {
+ "title": "Result 2",
+ "url": "https://site2.url",
+ "thumbnail": "https://thumb2.nail",
+ "image": "https://image2"
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = duckduckgo_images.response(response)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], 'Result 1')
+ self.assertEqual(results[0]['url'], 'https://site1.url')
+ self.assertEqual(results[0]['thumbnail_src'], 'https://thumb1.nail')
+ self.assertEqual(results[0]['img_src'], 'https://image1')
+ self.assertEqual(results[1]['title'], 'Result 2')
+ self.assertEqual(results[1]['url'], 'https://site2.url')
+ self.assertEqual(results[1]['thumbnail_src'], 'https://thumb2.nail')
+ self.assertEqual(results[1]['img_src'], 'https://image2')
diff --git a/tests/unit/engines/test_dummy.py b/tests/unit/engines/test_dummy.py
new file mode 100644
index 0000000..9399bea
--- /dev/null
+++ b/tests/unit/engines/test_dummy.py
@@ -0,0 +1,26 @@
+from searx.engines import dummy
+from searx.testing import SearxTestCase
+
+
+class TestDummyEngine(SearxTestCase):
+
+ def test_request(self):
+ test_params = [
+ [1, 2, 3],
+ ['a'],
+ [],
+ 1
+ ]
+ for params in test_params:
+ self.assertEqual(dummy.request(None, params), params)
+
+ def test_response(self):
+ responses = [
+ None,
+ [],
+ True,
+ dict(),
+ tuple()
+ ]
+ for response in responses:
+ self.assertEqual(dummy.response(response), [])
diff --git a/tests/unit/engines/test_faroo.py b/tests/unit/engines/test_faroo.py
new file mode 100644
index 0000000..acebdda
--- /dev/null
+++ b/tests/unit/engines/test_faroo.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import faroo
+from searx.testing import SearxTestCase
+
+
+class TestFarooEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr_FR'
+ dicto['category'] = 'general'
+ params = faroo.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('faroo.com', params['url'])
+ self.assertIn('en', params['url'])
+ self.assertIn('web', params['url'])
+
+ dicto['language'] = 'all'
+ params = faroo.request(query, dicto)
+ self.assertIn('en', params['url'])
+
+ dicto['language'] = 'de_DE'
+ params = faroo.request(query, dicto)
+ self.assertIn('de', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, faroo.response, None)
+ self.assertRaises(AttributeError, faroo.response, [])
+ self.assertRaises(AttributeError, faroo.response, '')
+ self.assertRaises(AttributeError, faroo.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(faroo.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(faroo.response(response), [])
+
+ response = mock.Mock(text='{"data": []}', status_code=401)
+ self.assertRaises(Exception, faroo.response, response)
+
+ response = mock.Mock(text='{"data": []}', status_code=429)
+ self.assertRaises(Exception, faroo.response, response)
+
+ json = """
+ {
+ "results": [
+ {
+ "title": "This is the title",
+ "kwic": "This is the content",
+ "content": "",
+ "url": "http://this.is.the.url/",
+ "iurl": "",
+ "domain": "css3test.com",
+ "author": "Jim Dalrymple",
+ "news": true,
+ "votes": "10",
+ "date": 1360622563000,
+ "related": []
+ },
+ {
+ "title": "This is the title2",
+ "kwic": "This is the content2",
+ "content": "",
+ "url": "http://this.is.the.url2/",
+ "iurl": "",
+ "domain": "css3test.com",
+ "author": "Jim Dalrymple",
+ "news": false,
+ "votes": "10",
+ "related": []
+ },
+ {
+ "title": "This is the title3",
+ "kwic": "This is the content3",
+ "content": "",
+ "url": "http://this.is.the.url3/",
+ "iurl": "http://upload.wikimedia.org/optimized.jpg",
+ "domain": "css3test.com",
+ "author": "Jim Dalrymple",
+ "news": false,
+ "votes": "10",
+ "related": []
+ }
+ ],
+ "query": "test",
+ "suggestions": [],
+ "count": 100,
+ "start": 1,
+ "length": 10,
+ "time": "15"
+ }
+ """
+ response = mock.Mock(text=json)
+ results = faroo.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 4)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
+ self.assertEqual(results[0]['content'], 'This is the content')
+ self.assertEqual(results[1]['title'], 'This is the title2')
+ self.assertEqual(results[1]['url'], 'http://this.is.the.url2/')
+ self.assertEqual(results[1]['content'], 'This is the content2')
+ self.assertEqual(results[3]['img_src'], 'http://upload.wikimedia.org/optimized.jpg')
+
+ json = """
+ {}
+ """
+ response = mock.Mock(text=json)
+ results = faroo.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_fdroid.py b/tests/unit/engines/test_fdroid.py
new file mode 100644
index 0000000..d75f4f0
--- /dev/null
+++ b/tests/unit/engines/test_fdroid.py
@@ -0,0 +1,49 @@
+import mock
+from collections import defaultdict
+from searx.engines import fdroid
+from searx.testing import SearxTestCase
+
+
+class TestFdroidEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dic = defaultdict(dict)
+ dic['pageno'] = 1
+ params = fdroid.request(query, dic)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('f-droid.org' in params['url'])
+
+ def test_response(self):
+ resp = mock.Mock(text='<html></html>')
+ self.assertEqual(fdroid.response(resp), [])
+
+ html = """
+ <a href="https://google.com/qwerty">
+ <div id="appheader">
+ <div style="float:left;padding-right:10px;">
+ <img src="http://example.com/image.png"
+ style="width:48px;border:none;">
+ </div>
+ <div style="float:right;">
+ <p>Details...</p>
+ </div>
+ <p style="color:#000000;">
+ <span style="font-size:20px;">Sample title</span>
+ <br>
+ Sample content
+ </p>
+ </div>
+ </a>
+ """
+
+ resp = mock.Mock(text=html)
+ results = fdroid.response(resp)
+
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['url'], 'https://google.com/qwerty')
+ self.assertEqual(results[0]['title'], 'Sample title')
+ self.assertEqual(results[0]['content'], 'Sample content')
+ self.assertEqual(results[0]['img_src'], 'http://example.com/image.png')
diff --git a/tests/unit/engines/test_flickr.py b/tests/unit/engines/test_flickr.py
new file mode 100644
index 0000000..be97647
--- /dev/null
+++ b/tests/unit/engines/test_flickr.py
@@ -0,0 +1,142 @@
+from collections import defaultdict
+import mock
+from searx.engines import flickr
+from searx.testing import SearxTestCase
+
+
+class TestFlickrEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = flickr.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('flickr.com' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, flickr.response, None)
+ self.assertRaises(AttributeError, flickr.response, [])
+ self.assertRaises(AttributeError, flickr.response, '')
+ self.assertRaises(AttributeError, flickr.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(flickr.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(flickr.response(response), [])
+
+ json = r"""
+ { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
+ "photo": [
+ { "id": "15751017054", "owner": "66847915@N08",
+ "secret": "69c22afc40", "server": "7285", "farm": 8,
+ "title": "Photo title", "ispublic": 1,
+ "isfriend": 0, "isfamily": 0,
+ "description": { "_content": "Description" },
+ "ownername": "Owner",
+ "url_o": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_9178e0f963_o.jpg",
+ "height_o": "2100", "width_o": "2653",
+ "url_n": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_69c22afc40_n.jpg",
+ "height_n": "253", "width_n": "320",
+ "url_z": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_69c22afc40_z.jpg",
+ "height_z": "507", "width_z": "640" }
+ ] }, "stat": "ok" }
+ """
+ response = mock.Mock(text=json)
+ results = flickr.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Photo title')
+ self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/66847915@N08/15751017054')
+ self.assertTrue('o.jpg' in results[0]['img_src'])
+ self.assertTrue('n.jpg' in results[0]['thumbnail_src'])
+ self.assertTrue('Owner' in results[0]['author'])
+ self.assertTrue('Description' in results[0]['content'])
+
+ json = r"""
+ { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
+ "photo": [
+ { "id": "15751017054", "owner": "66847915@N08",
+ "secret": "69c22afc40", "server": "7285", "farm": 8,
+ "title": "Photo title", "ispublic": 1,
+ "isfriend": 0, "isfamily": 0,
+ "description": { "_content": "Description" },
+ "ownername": "Owner",
+ "url_z": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_69c22afc40_z.jpg",
+ "height_z": "507", "width_z": "640" }
+ ] }, "stat": "ok" }
+ """
+ response = mock.Mock(text=json)
+ results = flickr.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Photo title')
+ self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/66847915@N08/15751017054')
+ self.assertTrue('z.jpg' in results[0]['img_src'])
+ self.assertTrue('z.jpg' in results[0]['thumbnail_src'])
+ self.assertTrue('Owner' in results[0]['author'])
+ self.assertTrue('Description' in results[0]['content'])
+
+ json = r"""
+ { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
+ "photo": [
+ { "id": "15751017054", "owner": "66847915@N08",
+ "secret": "69c22afc40", "server": "7285", "farm": 8,
+ "title": "Photo title", "ispublic": 1,
+ "isfriend": 0, "isfamily": 0,
+ "description": { "_content": "Description" },
+ "ownername": "Owner",
+ "url_o": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_9178e0f963_o.jpg",
+ "height_o": "2100", "width_o": "2653" }
+ ] }, "stat": "ok" }
+ """
+ response = mock.Mock(text=json)
+ results = flickr.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Photo title')
+ self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/66847915@N08/15751017054')
+ self.assertTrue('o.jpg' in results[0]['img_src'])
+ self.assertTrue('o.jpg' in results[0]['thumbnail_src'])
+ self.assertTrue('Owner' in results[0]['author'])
+ self.assertTrue('Description' in results[0]['content'])
+
+ json = r"""
+ { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
+ "photo": [
+ { "id": "15751017054", "owner": "66847915@N08",
+ "secret": "69c22afc40", "server": "7285", "farm": 8,
+ "title": "Photo title", "ispublic": 1,
+ "isfriend": 0, "isfamily": 0,
+ "description": { "_content": "Description" },
+ "ownername": "Owner",
+ "url_n": "https:\/\/farm8.staticflickr.com\/7285\/15751017054_69c22afc40_n.jpg",
+ "height_n": "253", "width_n": "320" }
+ ] }, "stat": "ok" }
+ """
+ response = mock.Mock(text=json)
+ results = flickr.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ { "photos": { "page": 1, "pages": "41001", "perpage": 100, "total": "4100032",
+ "toto": [] }, "stat": "ok" }
+ """
+ response = mock.Mock(text=json)
+ results = flickr.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = r"""
+ {"toto":[
+ {"id":200,"name":"Artist Name",
+ "link":"http:\/\/www.flickr.com\/artist\/1217","type":"artist"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = flickr.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_flickr_noapi.py b/tests/unit/engines/test_flickr_noapi.py
new file mode 100644
index 0000000..5f8b069
--- /dev/null
+++ b/tests/unit/engines/test_flickr_noapi.py
@@ -0,0 +1,329 @@
+from collections import defaultdict
+import mock
+from searx.engines import flickr_noapi
+from searx.testing import SearxTestCase
+
+
+class TestFlickrNoapiEngine(SearxTestCase):
+
+ def test_build_flickr_url(self):
+ url = flickr_noapi.build_flickr_url("uid", "pid")
+ self.assertIn("uid", url)
+ self.assertIn("pid", url)
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['time_range'] = ''
+ params = flickr_noapi.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('flickr.com', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, flickr_noapi.response, None)
+ self.assertRaises(AttributeError, flickr_noapi.response, [])
+ self.assertRaises(AttributeError, flickr_noapi.response, '')
+ self.assertRaises(AttributeError, flickr_noapi.response, '[]')
+
+ response = mock.Mock(text='"search-photos-lite-models","photos":{},"totalItems":')
+ self.assertEqual(flickr_noapi.response(response), [])
+
+ response = mock.Mock(text='search-photos-lite-models","photos":{"data": []},"totalItems":')
+ self.assertEqual(flickr_noapi.response(response), [])
+
+ # everthing is ok test
+ json = """
+ "search-photos-lite-models","photos":
+ {
+ "_data": [
+ {
+ "_flickrModelRegistry": "photo-lite-models",
+ "title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "ownerNsid": "59729010@N00",
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
+ "sizes": {
+ "c": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_c.jpg",
+ "width": 541,
+ "height": 800,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_c.jpg",
+ "key": "c"
+ },
+ "h": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_761d32237a_h.jpg",
+ "width": 1081,
+ "height": 1600,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_761d32237a_h.jpg",
+ "key": "h"
+ },
+ "k": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_f145a2c11a_k.jpg",
+ "width": 1383,
+ "height": 2048,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_f145a2c11a_k.jpg",
+ "key": "k"
+ },
+ "l": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_b.jpg",
+ "width": 692,
+ "height": 1024,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_b.jpg",
+ "key": "l"
+ },
+ "m": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777.jpg",
+ "width": 338,
+ "height": 500,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777.jpg",
+ "key": "m"
+ },
+ "n": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_n.jpg",
+ "width": 216,
+ "height": 320,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_n.jpg",
+ "key": "n"
+ },
+ "q": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_q.jpg",
+ "width": 150,
+ "height": 150,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_q.jpg",
+ "key": "q"
+ },
+ "s": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_m.jpg",
+ "width": 162,
+ "height": 240,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_m.jpg",
+ "key": "s"
+ },
+ "sq": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_s.jpg",
+ "width": 75,
+ "height": 75,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_s.jpg",
+ "key": "sq"
+ },
+ "t": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_t.jpg",
+ "width": 68,
+ "height": 100,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_t.jpg",
+ "key": "t"
+ },
+ "z": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_z.jpg",
+ "width": 433,
+ "height": 640,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_z.jpg",
+ "key": "z"
+ }
+ }
+ }
+ ],
+ "fetchedStart": true,
+ "fetchedEnd": false,
+ "totalItems": "4386039"
+ },"totalItems":
+ """
+ json = json.replace('\r\n', '').replace('\n', '').replace('\r', '')
+ response = mock.Mock(text=json)
+ results = flickr_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/59729010@N00/14001294434')
+ self.assertIn('k.jpg', results[0]['img_src'])
+ self.assertIn('n.jpg', results[0]['thumbnail_src'])
+ self.assertIn('Owner', results[0]['author'])
+
+ # no n size, only the z size
+ json = """
+ "search-photos-lite-models","photos":
+ {
+ "_data": [
+ {
+ "_flickrModelRegistry": "photo-lite-models",
+ "title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "ownerNsid": "59729010@N00",
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
+ "sizes": {
+ "z": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_z.jpg",
+ "width": 433,
+ "height": 640,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_z.jpg",
+ "key": "z"
+ }
+ }
+ }
+ ],
+ "fetchedStart": true,
+ "fetchedEnd": false,
+ "totalItems": "4386039"
+ },"totalItems":
+ """
+ response = mock.Mock(text=json)
+ results = flickr_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/59729010@N00/14001294434')
+ self.assertIn('z.jpg', results[0]['img_src'])
+ self.assertIn('z.jpg', results[0]['thumbnail_src'])
+ self.assertIn('Owner', results[0]['author'])
+
+ # no z or n size
+ json = """
+ "search-photos-lite-models","photos":
+ {
+ "_data": [
+ {
+ "_flickrModelRegistry": "photo-lite-models",
+ "title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "ownerNsid": "59729010@N00",
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
+ "sizes": {
+ "o": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_o.jpg",
+ "width": 433,
+ "height": 640,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_o.jpg",
+ "key": "o"
+ }
+ }
+ }
+ ],
+ "fetchedStart": true,
+ "fetchedEnd": false,
+ "totalItems": "4386039"
+ },"totalItems":
+ """
+ response = mock.Mock(text=json)
+ results = flickr_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://www.flickr.com/photos/59729010@N00/14001294434')
+ self.assertIn('o.jpg', results[0]['img_src'])
+ self.assertIn('o.jpg', results[0]['thumbnail_src'])
+ self.assertIn('Owner', results[0]['author'])
+
+ # no image test
+ json = """
+ "search-photos-lite-models","photos":
+ {
+ "_data": [
+ {
+ "_flickrModelRegistry": "photo-lite-models",
+ "title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "ownerNsid": "59729010@N00",
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
+ "sizes": {
+ }
+ }
+ ],
+ "fetchedStart": true,
+ "fetchedEnd": false,
+ "totalItems": "4386039"
+ },"totalItems":
+ """
+ response = mock.Mock(text=json)
+ results = flickr_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ # null test
+ json = """
+ "search-photos-models","photos":
+ {
+ "_data": [null],
+ "fetchedStart": true,
+ "fetchedEnd": false,
+ "totalItems": "4386039"
+ },"totalItems":
+ """
+ response = mock.Mock(text=json)
+ results = flickr_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ # no ownerNsid test
+ json = """
+ "search-photos-lite-models","photos":
+ {
+ "_data": [
+ {
+ "_flickrModelRegistry": "photo-lite-models",
+ "title": "This is the title",
+ "username": "Owner",
+ "pathAlias": "klink692",
+ "realname": "Owner",
+ "license": 0,
+ "canComment": false,
+ "commentCount": 14,
+ "faveCount": 21,
+ "id": "14001294434",
+ "sizes": {
+ "o": {
+ "displayUrl": "//farm8.staticflickr.com/7246/14001294434_410f653777_o.jpg",
+ "width": 433,
+ "height": 640,
+ "url": "//c4.staticflickr.com/8/7246/14001294434_410f653777_o.jpg",
+ "key": "o"
+ }
+ }
+ }
+ ],
+ "fetchedStart": true,
+ "fetchedEnd": false,
+ "totalItems": "4386039"
+ },"totalItems":
+ """
+ response = mock.Mock(text=json)
+ results = flickr_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ # garbage test
+ json = r"""
+ {"toto":[
+ {"id":200,"name":"Artist Name",
+ "link":"http:\/\/www.flickr.com\/artist\/1217","type":"artist"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = flickr_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_framalibre.py b/tests/unit/engines/test_framalibre.py
new file mode 100644
index 0000000..8509963
--- /dev/null
+++ b/tests/unit/engines/test_framalibre.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import framalibre
+from searx.testing import SearxTestCase
+
+
+class TestFramalibreEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = framalibre.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('framalibre.org' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, framalibre.response, None)
+ self.assertRaises(AttributeError, framalibre.response, [])
+ self.assertRaises(AttributeError, framalibre.response, '')
+ self.assertRaises(AttributeError, framalibre.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(framalibre.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(framalibre.response(response), [])
+
+ html = u"""
+ <div class="nodes-list-row">
+ <div id="node-431"
+ class="node node-logiciel-annuaires node-promoted node-teaser node-teaser node-sheet clearfix nodes-list"
+ about="/content/gogs" typeof="sioc:Item foaf:Document">
+ <header class="media">
+ <div class="media-left">
+ <div class="field field-name-field-logo field-type-image field-label-hidden">
+ <div class="field-items">
+ <div class="field-item even">
+ <a href="/content/gogs">
+ <img class="media-object img-responsive" typeof="foaf:Image"
+ src="https://framalibre.org/sites/default/files/styles/teaser_logo/public/leslogos/gogs-lg.png?itok=rrCxKKBy"
+ width="70" height="70" alt="" />
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="media-body">
+ <h3 class="node-title"><a href="/content/gogs">Gogs</a></h3>
+ <span property="dc:title" content="Gogs" class="rdf-meta element-hidden"></span>
+ <div class="field field-name-field-annuaires field-type-taxonomy-term-reference field-label-hidden">
+ <div class="field-items">
+ <div class="field-item even">
+ <a href="/annuaires/cloudwebapps"
+ typeof="skos:Concept" property="rdfs:label skos:prefLabel"
+ datatype="" class="label label-primary">Cloud/webApps</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </header>
+ <div class="content">
+ <div class="field field-name-field-votre-appr-ciation field-type-fivestar field-label-hidden">
+ <div class="field-items">
+ <div class="field-item even">
+ </div>
+ </div>
+ </div>
+ <div class="field field-name-body field-type-text-with-summary field-label-hidden">
+ <div class="field-items">
+ <div class="field-item even" property="content:encoded">
+ <p>Gogs est une interface web basée sur git et une bonne alternative à GitHub.</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ <footer>
+ <a href="/content/gogs" class="read-more btn btn-default btn-sm">Voir la notice</a>
+ <div class="field field-name-field-lien-officiel field-type-link-field field-label-hidden">
+ <div class="field-items">
+ <div class="field-item even">
+ <a href="https://gogs.io/" target="_blank" title="Voir le site officiel">
+ <span class="glyphicon glyphicon-globe"></span>
+ <span class="sr-only">Lien officiel</span>
+ </a>
+ </div>
+ </div>
+ </div>
+ </footer>
+ </div>
+ </div>
+ """
+ response = mock.Mock(text=html)
+ results = framalibre.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Gogs')
+ self.assertEqual(results[0]['url'],
+ 'https://framalibre.org/content/gogs')
+ self.assertEqual(results[0]['content'],
+ u"Gogs est une interface web basée sur git et une bonne alternative à GitHub.")
diff --git a/tests/unit/engines/test_frinkiac.py b/tests/unit/engines/test_frinkiac.py
new file mode 100644
index 0000000..5ea220c
--- /dev/null
+++ b/tests/unit/engines/test_frinkiac.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import frinkiac
+from searx.testing import SearxTestCase
+
+
+class TestFrinkiacEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ request_dict = defaultdict(dict)
+ params = frinkiac.request(query, request_dict)
+ self.assertTrue('url' in params)
+
+ def test_response(self):
+ self.assertRaises(AttributeError, frinkiac.response, None)
+ self.assertRaises(AttributeError, frinkiac.response, [])
+ self.assertRaises(AttributeError, frinkiac.response, '')
+ self.assertRaises(AttributeError, frinkiac.response, '[]')
+
+ text = """
+[{"Id":770931,
+ "Episode":"S06E18",
+ "Timestamp":534616,
+ "Filename":""},
+ {"Id":1657080,
+ "Episode":"S12E14",
+ "Timestamp":910868,
+ "Filename":""},
+ {"Id":1943753,
+ "Episode":"S14E21",
+ "Timestamp":773439,
+ "Filename":""},
+ {"Id":107835,
+ "Episode":"S02E03",
+ "Timestamp":531709,
+ "Filename":""}]
+ """
+
+ response = mock.Mock(text=text)
+ results = frinkiac.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 4)
+ self.assertEqual(results[0]['title'], u'S06E18')
+ self.assertIn('p=caption', results[0]['url'])
+ self.assertIn('e=S06E18', results[0]['url'])
+ self.assertIn('t=534616', results[0]['url'])
+ self.assertEqual(results[0]['thumbnail_src'], 'https://frinkiac.com/img/S06E18/534616/medium.jpg')
+ self.assertEqual(results[0]['img_src'], 'https://frinkiac.com/img/S06E18/534616.jpg')
diff --git a/tests/unit/engines/test_gigablast.py b/tests/unit/engines/test_gigablast.py
new file mode 100644
index 0000000..6b2d264
--- /dev/null
+++ b/tests/unit/engines/test_gigablast.py
@@ -0,0 +1,119 @@
+from collections import defaultdict
+import mock
+from searx.engines import gigablast
+from searx.testing import SearxTestCase
+
+
+class TestGigablastEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['safesearch'] = 0
+ dicto['language'] = 'all'
+ params = gigablast.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('gigablast.com' in params['url'])
+ self.assertTrue('xx' in params['url'])
+
+ dicto['language'] = 'en-US'
+ params = gigablast.request(query, dicto)
+ self.assertTrue('en' in params['url'])
+ self.assertFalse('en-US' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, gigablast.response, None)
+ self.assertRaises(AttributeError, gigablast.response, [])
+ self.assertRaises(AttributeError, gigablast.response, '')
+ self.assertRaises(AttributeError, gigablast.response, '[]')
+
+ response = mock.Mock(text='{"results": []}')
+ self.assertEqual(gigablast.response(response), [])
+
+ json = """{"results": [
+ {
+ "title":"South by Southwest 2016",
+ "dmozEntry":{
+ "dmozCatId":1041152,
+ "directCatId":1,
+ "dmozCatStr":"Top: Regional: North America: United States",
+ "dmozTitle":"South by Southwest (SXSW)",
+ "dmozSum":"Annual music, film, and interactive conference.",
+ "dmozAnchor":""
+ },
+ "dmozEntry":{
+ "dmozCatId":763945,
+ "directCatId":1,
+ "dmozCatStr":"Top: Regional: North America: United States",
+ "dmozTitle":"South by Southwest (SXSW)",
+ "dmozSum":"",
+ "dmozAnchor":"www.sxsw.com"
+ },
+ "dmozEntry":{
+ "dmozCatId":761446,
+ "directCatId":1,
+ "dmozCatStr":"Top: Regional: North America: United States",
+ "dmozTitle":"South by Southwest (SXSW)",
+ "dmozSum":"Music, film, and interactive conference and festival.",
+ "dmozAnchor":""
+ },
+ "indirectDmozCatId":1041152,
+ "indirectDmozCatId":763945,
+ "indirectDmozCatId":761446,
+ "contentType":"html",
+ "sum":"This should be the content.",
+ "url":"www.sxsw.com",
+ "hopCount":0,
+ "size":" 102k",
+ "sizeInBytes":104306,
+ "bytesUsedToComputeSummary":70000,
+ "docId":269411794364,
+ "docScore":586571136.000000,
+ "summaryGenTimeMS":12,
+ "summaryTagdbLookupTimeMS":0,
+ "summaryTitleRecLoadTimeMS":1,
+ "site":"www.sxsw.com",
+ "spidered":1452203608,
+ "firstIndexedDateUTC":1444167123,
+ "contentHash32":2170650347,
+ "language":"English",
+ "langAbbr":"en"
+ }
+]}
+ """
+ response = mock.Mock(text=json)
+ results = gigablast.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'South by Southwest 2016')
+ self.assertEqual(results[0]['url'], 'www.sxsw.com')
+ self.assertEqual(results[0]['content'], 'This should be the content.')
+
+ def test_fetch_supported_languages(self):
+ html = """<html></html>"""
+ response = mock.Mock(text=html)
+ results = gigablast._fetch_supported_languages(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ html = """
+ <html>
+ <body>
+ <span id="menu2">
+ <a href="/search?&rxikd=1&qlang=xx"></a>
+ <a href="/search?&rxikd=1&qlang=en"></a>
+ <a href="/search?&rxikd=1&prepend=gblang%3Aen"></a>
+ <a href="/search?&rxikd=1&qlang=zh_"></a>
+ <a href="/search?&rxikd=1&prepend=gblang%3Azh_tw"></a>
+ </span>
+ </body>
+ </html>
+ """
+ response = mock.Mock(text=html)
+ languages = gigablast._fetch_supported_languages(response)
+ self.assertEqual(type(languages), list)
+ self.assertEqual(len(languages), 2)
+ self.assertIn('en', languages)
+ self.assertIn('zh-TW', languages)
diff --git a/tests/unit/engines/test_github.py b/tests/unit/engines/test_github.py
new file mode 100644
index 0000000..460be8c
--- /dev/null
+++ b/tests/unit/engines/test_github.py
@@ -0,0 +1,61 @@
+from collections import defaultdict
+import mock
+from searx.engines import github
+from searx.testing import SearxTestCase
+
+
+class TestGitHubEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ params = github.request(query, defaultdict(dict))
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('github.com' in params['url'])
+ self.assertEqual(params['headers']['Accept'], github.accept_header)
+
+ def test_response(self):
+ self.assertRaises(AttributeError, github.response, None)
+ self.assertRaises(AttributeError, github.response, [])
+ self.assertRaises(AttributeError, github.response, '')
+ self.assertRaises(AttributeError, github.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(github.response(response), [])
+
+ response = mock.Mock(text='{"items": []}')
+ self.assertEqual(github.response(response), [])
+
+ json = """
+ {
+ "items": [
+ {
+ "name": "title",
+ "html_url": "url",
+ "description": ""
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = github.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'title')
+ self.assertEqual(results[0]['url'], 'url')
+ self.assertEqual(results[0]['content'], '')
+
+ json = """
+ {
+ "items": [
+ {
+ "name": "title",
+ "html_url": "url",
+ "description": "desc"
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = github.response(response)
+ self.assertEqual(results[0]['content'], "desc")
diff --git a/tests/unit/engines/test_google.py b/tests/unit/engines/test_google.py
new file mode 100644
index 0000000..0d56b1e
--- /dev/null
+++ b/tests/unit/engines/test_google.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+import lxml
+from searx.engines import google
+from searx.testing import SearxTestCase
+
+
+class TestGoogleEngine(SearxTestCase):
+
+ def mock_response(self, text):
+ response = mock.Mock(text=text, url='https://www.google.com/search?q=test&start=0&gbv=1&gws_rd=cr')
+ response.search_params = mock.Mock()
+ response.search_params.get = mock.Mock(return_value='www.google.com')
+ return response
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr-FR'
+ dicto['time_range'] = ''
+ params = google.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('google.fr', params['url'])
+ self.assertIn('fr', params['headers']['Accept-Language'])
+
+ dicto['language'] = 'all'
+ params = google.request(query, dicto)
+ self.assertIn('google.com', params['url'])
+ self.assertIn('en', params['headers']['Accept-Language'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, google.response, None)
+ self.assertRaises(AttributeError, google.response, [])
+ self.assertRaises(AttributeError, google.response, '')
+ self.assertRaises(AttributeError, google.response, '[]')
+
+ response = self.mock_response('<html></html>')
+ self.assertEqual(google.response(response), [])
+
+ html = """
+ <div class="g">
+ <h3 class="r">
+ <a href="http://this.should.be.the.link/">
+ <b>This</b> is <b>the</b> title
+ </a>
+ </h3>
+ <div class="s">
+ <div class="kv" style="margin-bottom:2px">
+ <cite>
+ <b>test</b>.psychologies.com/
+ </cite>
+ <div class="_nBb">‎
+ <div style="display:inline" onclick="google.sham(this);" aria-expanded="false"
+ aria-haspopup="true" tabindex="0" data-ved="0CBUQ7B0wAA">
+ <span class="_O0">
+ </span>
+ </div>
+ <div style="display:none" class="am-dropdown-menu" role="menu" tabindex="-1">
+ <ul>
+ <li class="_Ykb">
+ <a class="_Zkb" href="http://www.google.fr/url?url=http://webcache.googleusercontent
+ .com/search%3Fcache:R1Z_4pGXjuIJ:http://test.psychologies.com/">
+ En cache
+ </a>
+ </li>
+ <li class="_Ykb">
+ <a class="_Zkb" href="/search?safe=off&amp;q=related:test.psy.com/">
+ Pages similaires
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <span class="st">
+ This should be the content.
+ </span>
+ <br>
+ <div class="osl">‎
+ <a href="http://www.google.fr/url?url=http://test.psychologies.com/tests/">
+ Test Personnalité
+ </a> - ‎
+ <a href="http://www.google.fr/url?url=http://test.psychologies.com/test/">
+ Tests - Moi
+ </a> - ‎
+ <a href="http://www.google.fr/url?url=http://test.psychologies.com/test/tests-couple">
+ Test Couple
+ </a>
+ - ‎
+ <a href="http://www.google.fr/url?url=http://test.psychologies.com/tests/tests-amour">
+ Test Amour
+ </a>
+ </div>
+ </div>
+ </div>
+ <div class="g">
+ <h3 class="r">
+ <a href="http://www.google.com/images?q=toto">
+ <b>This</b>
+ </a>
+ </h3>
+ </div>
+ <div class="g">
+ <h3 class="r">
+ <a href="http://www.google.com/search?q=toto">
+ <b>This</b> is
+ </a>
+ </h3>
+ </div>
+ <div class="g">
+ <h3 class="r">
+ <a href="€">
+ <b>This</b> is <b>the</b>
+ </a>
+ </h3>
+ </div>
+ <div class="g">
+ <h3 class="r">
+ <a href="/url?q=url">
+ <b>This</b> is <b>the</b>
+ </a>
+ </h3>
+ </div>
+ <p class="_Bmc" style="margin:3px 8px">
+ <a href="/search?num=20&amp;safe=off&amp;q=t&amp;revid=1754833769&amp;sa=X&amp;ei=-&amp;ved=">
+ suggestion <b>title</b>
+ </a>
+ </p>
+ """
+ response = self.mock_response(html)
+ results = google.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
+ self.assertEqual(results[0]['content'], 'This should be the content.')
+ self.assertEqual(results[1]['suggestion'], 'suggestion title')
+
+ html = """
+ <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
+ </li>
+ """
+ response = self.mock_response(html)
+ results = google.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ response = mock.Mock(text='<html></html>', url='https://sorry.google.com')
+ response.search_params = mock.Mock()
+ response.search_params.get = mock.Mock(return_value='www.google.com')
+ self.assertRaises(RuntimeWarning, google.response, response)
+
+ response = mock.Mock(text='<html></html>', url='https://www.google.com/sorry/IndexRedirect')
+ response.search_params = mock.Mock()
+ response.search_params.get = mock.Mock(return_value='www.google.com')
+ self.assertRaises(RuntimeWarning, google.response, response)
+
+ def test_parse_images(self):
+ html = """
+ <li>
+ <div>
+ <a href="http://www.google.com/url?q=http://this.is.the.url/">
+ <img style="margin:3px 0;margin-right:6px;padding:0" height="90"
+ src="https://this.is.the.image/image.jpg" width="60" align="middle" alt="" border="0">
+ </a>
+ </div>
+ </li>
+ """
+ dom = lxml.html.fromstring(html)
+ results = google.parse_images(dom, 'www.google.com')
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
+ self.assertEqual(results[0]['title'], '')
+ self.assertEqual(results[0]['content'], '')
+ self.assertEqual(results[0]['img_src'], 'https://this.is.the.image/image.jpg')
+
+ def test_fetch_supported_languages(self):
+ html = """<html></html>"""
+ response = mock.Mock(text=html)
+ languages = google._fetch_supported_languages(response)
+ self.assertEqual(type(languages), dict)
+ self.assertEqual(len(languages), 0)
+
+ html = u"""
+ <html>
+ <body>
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <font>
+ <label>
+ <span id="ten">English</span>
+ </label>
+ </font>
+ </td>
+ <td>
+ <font>
+ <label>
+ <span id="tzh-CN">中文 (简体)</span>
+ </label>
+ <label>
+ <span id="tzh-TW">中文 (繁體)</span>
+ </label>
+ </font>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </body>
+ </html>
+ """
+ response = mock.Mock(text=html)
+ languages = google._fetch_supported_languages(response)
+ self.assertEqual(type(languages), dict)
+ self.assertEqual(len(languages), 3)
+
+ self.assertIn('en', languages)
+ self.assertIn('zh-CN', languages)
+ self.assertIn('zh-TW', languages)
+
+ self.assertEquals(type(languages['en']), dict)
+ self.assertEquals(type(languages['zh-CN']), dict)
+ self.assertEquals(type(languages['zh-TW']), dict)
+
+ self.assertIn('name', languages['en'])
+ self.assertIn('name', languages['zh-CN'])
+ self.assertIn('name', languages['zh-TW'])
+
+ self.assertEquals(languages['en']['name'], 'English')
+ self.assertEquals(languages['zh-CN']['name'], u'中文 (简体)')
+ self.assertEquals(languages['zh-TW']['name'], u'中文 (繁體)')
diff --git a/tests/unit/engines/test_google_images.py b/tests/unit/engines/test_google_images.py
new file mode 100644
index 0000000..493741c
--- /dev/null
+++ b/tests/unit/engines/test_google_images.py
@@ -0,0 +1,42 @@
+from collections import defaultdict
+import mock
+from searx.engines import google_images
+from searx.testing import SearxTestCase
+
+
+class TestGoogleImagesEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['safesearch'] = 1
+ dicto['time_range'] = ''
+ params = google_images.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+
+ dicto['safesearch'] = 0
+ params = google_images.request(query, dicto)
+ self.assertNotIn('safe', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, google_images.response, None)
+ self.assertRaises(AttributeError, google_images.response, [])
+ self.assertRaises(AttributeError, google_images.response, '')
+ self.assertRaises(AttributeError, google_images.response, '[]')
+
+ html = r"""
+["rg_s",["dom","\u003Cstyle\u003E.rg_kn,.rg_s{}.rg_bx{display:-moz-inline-box;display:inline-block;margin-top:0;margin-right:12px;margin-bottom:12px;margin-left:0;overflow:hidden;position:relative;vertical-align:top;z-index:1}.rg_meta{display:none}.rg_l{display:inline-block;height:100%;position:absolute;text-decoration:none;width:100%}.rg_l:focus{outline:0}.rg_i{border:0;color:rgba(0,0,0,0);display:block;-webkit-touch-callout:none;}.rg_an,.rg_anbg,.rg_ilm,.rg_ilmbg{right:0;bottom:0;box-sizing:border-box;-moz-box-sizing:border-box;color:#fff;font:normal 11px arial,sans-serif;line-height:100%;white-space:nowrap;width:100%}.rg_anbg,.rg_ilmbg{background:rgba(51,51,51,0.8);margin-left:0;padding:2px 4px;position:absolute}.rg_ilmn{bottom:0;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rg_ilm{display:none}#rg_s.rg_kn .rg_l:focus .rg_ilm{display:block}.rg_kn .rg_bx:hover .rg_ilm,.rg_bx:hover .rg_anbg{display:none}.rg_bx:hover .rg_ilm,.rg_anbg,.rg_kn .rg_bx:hover .rg_anbg{display:block}\u003C\/style\u003E\u003Cdiv eid=\"qlKuV-T3BoqksAHMnaroAw\" id=\"isr_scm_0\" style=\"display:none\"\u003E\u003C\/div\u003E\u003Cdiv data-cei=\"qlKuV-T3BoqksAHMnaroAw\" class=\"rg_add_chunk\"\u003E\u003C!--m--\u003E\u003Cdiv class=\"rg_di rg_bx rg_el ivg-i\" data-ved=\"0ahUKEwjk9PCm-7zOAhUKEiwKHcyOCj0QMwgCKAAwAA\"\u003E\u003Ca jsaction=\"fire.ivg_o;mouseover:str.hmov;mouseout:str.hmou\" class=\"rg_l\" style=\"background:rgb(170,205,240)\"\u003E\u003Cimg data-sz=\"f\" name=\"5eykIeMjmCk7xM:\" src=\"https:\/\/encrypted-tbn0.gstatic.com\/images?q=tbn\" class=\"rg_i rg_ic\" alt=\"Image result for south\" jsaction=\"load:str.tbn\" onload=\"google.aft\u0026\u0026google.aft(this)\"\u003E\u003Cdiv class=\"_aOd rg_ilm\"\u003E\u003Cdiv class=\"rg_ilmbg\"\u003E\u003Cspan class=\"rg_ilmn\"\u003E 566\u0026nbsp;\u0026#215;\u0026nbsp;365 - en.wikipedia.org \u003C\/span\u003E\u003C\/div\u003E\u003C\/div\u003E\u003C\/a\u003E\u003Cdiv class=\"rg_meta\"\u003E{\"id\":\"5eykIeMjmCk7xM:\",\"isu\":\"en.wikipedia.org\",\"itg\":false,\"ity\":\"png\",\"oh\":365,\"ou\":\"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/e\/e4\/Us_south_census.png\",\"ow\":566,\"pt\":\"Southern United States - Wikipedia, the free encyclopedia\",\"rid\":\"cErfE02-v-VcAM\",\"ru\":\"https:\/\/en.wikipedia.org\/wiki\/Southern_United_States\",\"s\":\"The Southern United States as defined by the United States Census Bureau.\",\"sc\":1,\"th\":180,\"tu\":\"https:\/\/encrypted-tbn0.gstatic.com\/images?q\\u003dtbn\",\"tw\":280}\u003C\/div\u003E\u003C\/div\u003E\u003C!--n--\u003E\u003C!--m--\u003E\u003Cdiv class=\"rg_di rg_bx rg_el ivg-i\" data-ved=\"0ahUKEwjk9PCm-7zOAhUKEiwKHcyOCj0QMwgDKAEwAQ\"\u003E\u003Ca jsaction=\"fire.ivg_o;mouseover:str.hmov;mouseout:str.hmou\" class=\"rg_l\" style=\"background:rgb(249,252,249)\"\u003E\u003Cimg data-sz=\"f\" name=\"eRjGCc0cFyVkKM:\" src=\"https:\/\/encrypted-tbn2.gstatic.com\/images?q=tbn:ANd9GcSI7SZlbDwdMCgGXzJkpwgdn9uL41xUJ1IiIcKs0qW43_Yp0EhEsg\" class=\"rg_i rg_ic\" alt=\"Image result for south\" jsaction=\"load:str.tbn\" onload=\"google.aft\u0026\u0026google.aft(this)\"\u003E\u003Cdiv class=\"_aOd rg_ilm\"\u003E\u003Cdiv class=\"rg_ilmbg\"\u003E\u003Cspan class=\"rg_ilmn\"\u003E 2000\u0026nbsp;\u0026#215;\u0026nbsp;1002 - commons.wikimedia.org \u003C\/span\u003E\u003C\/div\u003E\u003C\/div\u003E\u003C\/a\u003E\u003Cdiv class=\"rg_meta\"\u003E{\"id\":\"eRjGCc0cFyVkKM:\",\"isu\":\"commons.wikimedia.org\",\"itg\":false,\"ity\":\"png\",\"oh\":1002,\"ou\":\"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/thumb\/8\/84\/South_plate.svg\/2000px-South_plate.svg.png\",\"ow\":2000,\"pt\":\"File:South plate.svg - Wikimedia Commons\",\"rid\":\"F8TVsT2GBLb6RM\",\"ru\":\"https:\/\/commons.wikimedia.org\/wiki\/File:South_plate.svg\",\"s\":\"This image rendered as PNG in other widths: 200px, 500px, 1000px, 2000px.\",\"sc\":1,\"th\":159,\"tu\":\"https:\/\/encrypted-tbn2.gstatic.com\/images?q\\u003dtbn:ANd9GcSI7SZlbDwdMCgGXzJkpwgdn9uL41xUJ1IiIcKs0qW43_Yp0EhEsg\",\"tw\":317}\u003C\/div\u003E\u003C\/div\u003E\u003C!--n--\u003E\u003C\/div\u003E"]]""" # noqa
+ response = mock.Mock(text=html)
+ results = google_images.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], u'Southern United States - Wikipedia, the free encyclopedia')
+ self.assertEqual(results[0]['url'], 'https://en.wikipedia.org/wiki/Southern_United_States')
+ self.assertEqual(results[0]['img_src'],
+ 'https://upload.wikimedia.org/wikipedia/commons/e/e4/Us_south_census.png')
+ self.assertEqual(results[0]['content'],
+ 'The Southern United States as defined by the United States Census Bureau.')
+ self.assertEqual(results[0]['thumbnail_src'],
+ 'https://encrypted-tbn0.gstatic.com/images?q=tbn')
diff --git a/tests/unit/engines/test_google_news.py b/tests/unit/engines/test_google_news.py
new file mode 100644
index 0000000..6454dde
--- /dev/null
+++ b/tests/unit/engines/test_google_news.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+from collections import defaultdict
+import mock
+from searx.engines import google_news
+from searx.testing import SearxTestCase
+
+
+class TestGoogleNewsEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr_FR'
+ dicto['time_range'] = 'w'
+ params = google_news.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('fr', params['url'])
+
+ dicto['language'] = 'all'
+ params = google_news.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertNotIn('fr', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, google_news.response, None)
+ self.assertRaises(AttributeError, google_news.response, [])
+ self.assertRaises(AttributeError, google_news.response, '')
+ self.assertRaises(AttributeError, google_news.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(google_news.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(google_news.response(response), [])
+
+ html = u"""
+<div class="g">
+<div class="ts _V6c _Zmc _XO _knc _d7c"><a class="top _vQb _mnc" href="http://this.is.the.url" onmousedown="return rwt(this,'','','','5','AFQjCNGixEtJGC3qTB9pYFLXlRj8XXwdiA','','0ahUKEwiG7O_M5-rQAhWDtRoKHd0RD5QQvIgBCCwwBA','','',event)"><img class="th _lub" id="news-thumbnail-image-52779299683347" src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAGQAZAMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAAAAAAAAAFBgADBAIBB//EADsQAAIBAwIEAwUGBAUFAAAAAAECAwAEEQUhBhIxURNBYRQiMnGBFSORobHBB1Ji4UKS0fDxFiQzNYL/xAAYAQEBAQEBAAAAAAAAAAAAAAACAwEEAP/EACERAAMBAAICAgMBAAAAAAAAAAABAhEhMQMSE2EEQVEi/9oADAMBAAIRAxEAPwDSkKr8JFWIzwnMTlSepFdiBgMiuxEdid6B7QbrEr8qE4L4wGNCIT7u/WifE0eIosHBz0oWnugCjQ4BfEipJHbpK/JG0gDP/KO9XT6Lw7BGDHrofIBIJQ4NXalAs8IDJnFLepS21pmOOFXl679FoY66ZTcNwWxtGzaaqwOf8JFcXupSSr4cupSOp6gnGaDQahJESQwU/wBIxXbX0l0jl+VgduYjJrfj/ofc9MluueV1z3xV2jlX1AlDkY61TY3MAk5LuJSnm4G4+nnTFb2ltEweFRvuCO1ebw8uTYNnQ9iDTtDKJYY8DflFJgXLp8xT5FbJHBGx/lFFM9a4MrQFjnFSilvPGiYaItv1zUrTnwHljgLViqwUDyNFEskKlmIDCs08JMWHfl32INZ8hnIscWII4oWPmdqCg9KLccsUtLZM596g6HmVT6Um9WlvGeXk/gWruCObGFz3pb0/R59Y1D2WDYk5lmfpGP3PpW7iK4aIxY3CqWA7t5UxcG3SQWxsZI2VI0MxM6hedj1Oeo9Kc5M6PPagQ3DdjpcpWeHx2U7s+9LuvPCsr+yoETIwAOgpq1DV59RmuJIpbS2hhOG5zvmlLVXE4OCpcnBKdDRW7ptZgMEuCCPrTJwteGYS2z78vvpnt5j/AH3pWIxRbhZ+XV4h5Orj8s/tTtcE5fI6xKWmjVepYfrX0trQrbxLICDyDavm0EvgXEU2M8jhsfI033fHiyxxSfZkwj+EtjP6VzpPR0E8ICQR0qUuT8Y2DSExxtj69alVwOINy3Esye7zKfnXdpZzzMjSfAO1KGm8UX99qNvbm1SNZZApYnoPOimr6rf2crBLmQoTsFIwo8htUHDRk+N0Zv4nQi2itMHALUAiP3SH0oxJrUtyqJcKk4GDiZeb9a0LqkOADY2nz8FaSeLCkxgsWUVrecWQRXwDQW8DzMpGckdNvPr0orxZfpa2if8AYywPIfDkY4YDYHbHbcVgucf9YWd3CqR+MxUgDC5C7bDy2FecRzXQsxJd3lmJN8wCPCvgkZBBOc1Vr2kccaA7PkuBOm6qzD3+XZh1/UflWG+isoGAgMjPzb8x2Jz2r37Sm8AouynqKFyOQxkk8unqa9KYKawolI529CRRLhr/ANxb46AOT/lNBgSTTPwTL7PqMlwpHOkXKuR3IzTvonPY2WqCa6hiPR3VT9TTfx3pzaRb2IskUJICpBPUgUvDXphjLR5HQ8oq6bii9uOUXExmC/CJBzY/GoTWFXOiKV1BZpvEjJYyEkgbb1Kdvt19j9zk9fuk/wBKlL5foz0Zk0vTWa0N/hyVYqiKcZ26/jSvdX81pqET+0PJCX8Nw3l6H5b19H0pccPWpxu2WO/9R3pQ4s0yzi1K4gcFGuW8WN+b3STny8t6xr9lU+MRe2AQVPukZGKq9qTxzCGJcLzbHIqm2uILSCO2uiyvF7pdh1HUNjtQO0uootbLSmQ25kIfwyOYrnyztmvT/oL4C91M3NHIh96GRZB9D+/T61xxNf6XqbBrC2LSbkhVyfypk1Cew1SP7F0exNjbW8LXeovKwMx5VJCs3/0vToT6EUhcHTqOIEfConIyLy+QNX9MWElYOmkYHDKVx5EYrFIrSNk7imriCFrm7kt+TM8R5ie696y6Do41G5naUlLa2TnkxjJ7L9TR3BOdeCzgbg82fntRvR1mtbpGYe5IvUdiP7isrWDSTuIwFIyewA+tbraC7js4J7WVpYz92VUZMTdeUr28wenX1rXyg+rTC7TkHrXonPeiGk6TFeWeNQuWsLot7kkihoXHrj3l+e4oZJbSxzTxY5mgcrJyHmAx57eXrXOVc1Pawt8dq8rPv5VKww+jWUgbhGymTO8QIPod6y8cadHfaQsgws8GWUjrjO4/erXxa8M2cETZCW6bEdNq513UlsbSO6mh9oRXXmh5+UPkk4Jx54NdUrQ08Qj2cQ1ezMDkpexA7k/EBQ+C3ubOVXliICts3l1rXqEr3+qPeWASK7n55vZ7bHhxDGeQf1coJP4dc13Y8SMiPDeRBiRjp19DUaiofBs3NLkKahcwcNaLJaQMtzquspz3jhuYRRE5EYPc9W/4oDDYGyvLO4upLdB4YzFFJmRQq5HMvcj9qtvI7SPXpGguxqFvFysZSvKHON167gdM1a/EUb6fLYz2FvI01yJZrphmVlyCVHYfLue9dTWyRisotfVpLm8D272suB0mURtjtkV3p2qwWF5P7VbmETAK6ZyrD0odrtq+nTiy1PT47Oc4mWWPBflYbA4OMenWsdnd3FusqxOkyMpVkYdR9a5nLXBer2tQyxRWcjznT5CpfDBMbtjoPl6VlN+sCLNbwMZjIysoAGPQj8OtBre5dJeaPKN5ods0aGoW2o2rQ3i/eHYSpsw+dTc4dng/L9J9WjSsK3Og3V/JeQWvhL4cUcr5Zj5j0JGw9aG6Jpst3NDFLcmzNzA8ts2ATIyZ265Xz/vtWbVbOzgQCW4eJ4oT4I8MkSHyUYHmepJAHY0PWGSbT11CFFSGKQwM6EhmYjOTv2OM7darCyeDl/J8r8nk1sIwXDSRK7jDHrnY586lNuh6Pouu6Xb3ZvE06ZV8KaFCFUuvVgD0zsa8oNE9CmpA/YtoRkhrdPptWPidZ5dGuIraETSye6V64jQB3IH82QD8gcb1gvuIDJp9vaW8JVkRYyZN8kDyArBbXx1XXLO2vb2SKI8yyGBSFRSu+MZJzhc/6V1QsYLeo1R3Oh3dnby6RYvDcWcQE1wUVRM3KASQCd/n39aUkhN7rltFJ1muI0OP6mA/ejeqX1jYaxLpOmzmfT4IQgmOMu5PMzbAdwPpS+l37Pq8NzGvM0Myui9yrAgfpXrfIZ6CkmjXWpa9qcPD9s3hQvIyoGGBGrcvmfP96OcI8YaTpelw2mp6MJkR2cThVkEj9dww28umdsVm0HSotb1O6tbK5v8ATp1R2dmcOo94Aqccp8/yoHxBbrpt39lLMswtCweRVwGdt2/ABR9KbWAX2aNX1231DiCXVru3M5kkLmGR/cIxhV+QAH4UHvbqC5lMsFvFa7Y5Yc4P4k0X1vS7XS9E0znwb+6BmkXkX3E8t8ZzuOpxsawXEa6Td3MKrHLJ4JhdiPgYgc2PUbrn51Nz/WPfoxpOcY8QEjbB3q2KUswzjbzWt+t3Ftcw2Hh28cKwRLCzqP8AydMtjA32rnUtG9jiS4srhJ7OT4JUYZ+TDqDQpCVFpmW/g9llYArvGxHwmrNX0v2HQdKkjcxx3HOZk5jhnGMk9+woRFKUO/Uedbprye8tGiZs28Eikcx+FmBG3ocfkKKWcG1zyPv8PLi2Xh3wpbOSQxTuodFHvA4PbuTUpG07XtW0qA29lMYoy3MVx5kD07AVKxzyZphmvZnWViR/KMeQrvQ4lutQm8bJ8G0lmUZ/xKuRn61KldDACoHYzu7MSx6sepo9pFtE/vMMloyT9cj8sV7UoPsS6G7+HX3Wm6jdLvM0ioWPYLn9SaQFka91NHuTzme4Bkz58zb/AK17Uq1dImg5rcrXPGcUU2GSIxqq+QGA36ml+bMkaXEjM0kw8R89yd6lSpiCfCcEV7fXdpcxq8TWUjjujAggr2NXcJ3k08F9pcpDWslpJNyEfA6gEFe29SpQZqAgbmAY9TvXAY5YZOCRUqV79ifRthucRhXgglxsGkTmIHbNSpUrAn//2Q==" alt="A(z) south témájának képe a következőből: CBC.ca" data-deferred="1" onload="google.aft&amp;&amp;google.aft(this)"></a><div class="_cnc"><h3 class="r _U6c"><a class="l _HId" href="http://this.is.the.url" onmousedown="return rwt(this,'','','','5','AFQjCNGixEtJGC3qTB9pYFLXlRj8XXwdiA','','0ahUKEwiG7O_M5-rQAhWDtRoKHd0RD5QQqQIILSgAMAQ','','',event)">Meet Thuli Madonsela — <em>South</em> Africa's conscience</a></h3><div class="slp"><span class="_tQb _IId">CBC.ca</span><span class="_v5">-</span><span class="f nsa _uQb">9 órával ezelőtt</span></div><div class="st"><em>South</em> African Public Protector</div></div><div class="_Xmc card-section"><a class="_sQb" href="http://www.news24.com/Columnists/Mpumelelo_Mkhabela/who-really-governs-south-africa-20161209" onmousedown="return rwt(this,'','','','5','AFQjCNHhc2MnYSZ5T4COqInzvgoju5k5bA','','0ahUKEwiG7O_M5-rQAhWDtRoKHd0RD5QQuogBCC4oATAE','','',event)">Who really governs <em>South</em> Africa?</a><br><span class="_Wmc _GId">Vélemény</span><span class="_v5">-</span><span class="_tQb _IId">News24</span><span class="_v5">-</span><span class="f nsa _uQb">2016. dec. 8.</span></div><div class="_Vmc"></div></div>
+</div>
+ """ # noqa
+ response = mock.Mock(text=html)
+ results = google_news.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], u'Meet Thuli Madonsela \u2014 South Africa\'s conscience')
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url')
+ self.assertEqual(results[0]['content'], 'South African Public Protector')
diff --git a/tests/unit/engines/test_ina.py b/tests/unit/engines/test_ina.py
new file mode 100644
index 0000000..109a959
--- /dev/null
+++ b/tests/unit/engines/test_ina.py
@@ -0,0 +1,64 @@
+from collections import defaultdict
+import mock
+from searx.engines import ina
+from searx.testing import SearxTestCase
+
+
+class TestInaEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = ina.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('ina.fr' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, ina.response, None)
+ self.assertRaises(AttributeError, ina.response, [])
+ self.assertRaises(AttributeError, ina.response, '')
+ self.assertRaises(AttributeError, ina.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(ina.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(ina.response(response), [])
+
+ json = """
+ {"content":"\\t<div class=\\"container\\">\\n\\t\\n\
+ <!-- DEBUT CONTENU PRINCIPAL -->\\n<div class=\\"row\\">\\n\
+ <div class=\\"search-results--list\\"><div class=\\"media\\">\\n\
+ \\t\\t\\t\\t<a class=\\"media-left media-video premium xiti_click_action\\" \
+ data-xiti-params=\\"recherche_v4::resultats_conference_de_presse_du_general_de_gaulle::N\\" \
+ href=\\"\\/video\\/CAF89035682\\/conference-de-presse-du-general-de-gaulle-video.html\\">\\n\
+ <img src=\\"https:\\/\\/www.ina.fr\\/images_v2\\/140x105\\/CAF89035682.jpeg\\" \
+ alt=\\"Conf\\u00e9rence de presse du G\\u00e9n\\u00e9ral de Gaulle \\">\\n\
+ \\t\\t\\t\\t\\t<\\/a>\\n\
+ \\t\\t\\t\\t\\t<div class=\\"media-body\\">\\n\\t\\t\\t\\t\\t\\t<h3 class=\\"h3--title media-heading\\">\\n\
+ \\t\\t\\t\\t\\t\\t\\t<a class=\\"xiti_click_action\\" \
+ data-xiti-params=\\"recherche_v4::resultats_conference_de_presse_du_general_de_gaulle::N\\" \
+ href=\\"\\/video\\/CAF89035682\\/conference-de-presse-du-general-de-gaulle-video.html\\">\
+ Conf\\u00e9rence de presse du G\\u00e9n\\u00e9ral de Gaulle <\\/a>\\n\
+ <\\/h3>\\n\
+ <div class=\\"media-body__info\\">\\n<span class=\\"broadcast\\">27\\/11\\/1967<\\/span>\\n\
+ <span class=\\"views\\">29321 vues<\\/span>\\n\
+ <span class=\\"duration\\">01h 33m 07s<\\/span>\\n\
+ <\\/div>\\n\
+ <p class=\\"media-body__summary\\">VERSION INTEGRALE DE LA CONFERENCE DE PRESSE DU GENERAL DE GAULLE . \
+ - PA le Pr\\u00e9sident DE GAULLE : il ouvre les bras et s'assied. DP journalis...<\\/p>\\n\
+ <\\/div>\\n<\\/div><!-- \\/.media -->\\n"
+ }
+ """
+ response = mock.Mock(text=json)
+ results = ina.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], u'Conf\xe9rence de presse du G\xe9n\xe9ral de Gaulle')
+ self.assertEqual(results[0]['url'],
+ 'https://www.ina.fr/video/CAF89035682/conference-de-presse-du-general-de-gaulle-video.html')
+ self.assertEqual(results[0]['content'],
+ u"VERSION INTEGRALE DE LA CONFERENCE DE PRESSE DU GENERAL DE GAULLE ."
+ u" - PA le Pr\u00e9sident DE GAULLE : il ouvre les bras et s'assied. DP journalis...")
diff --git a/tests/unit/engines/test_kickass.py b/tests/unit/engines/test_kickass.py
new file mode 100644
index 0000000..3a75c66
--- /dev/null
+++ b/tests/unit/engines/test_kickass.py
@@ -0,0 +1,397 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import kickass
+from searx.testing import SearxTestCase
+
+
+class TestKickassEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ params = kickass.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('kickass.cd', params['url'])
+ self.assertFalse(params['verify'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, kickass.response, None)
+ self.assertRaises(AttributeError, kickass.response, [])
+ self.assertRaises(AttributeError, kickass.response, '')
+ self.assertRaises(AttributeError, kickass.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(kickass.response(response), [])
+
+ html = """
+ <table cellpadding="0" cellspacing="0" class="data" style="width: 100%">
+ <tr class="firstr">
+ <th class="width100perc nopad">torrent name</th>
+ <th class="center">
+ <a href="/search/test/?field=size&sorder=desc" rel="nofollow">size</a>
+ </th>
+ <th class="center"><span class="files">
+ <a href="/search/test/?field=files_count&sorder=desc" rel="nofollow">files</a></span>
+ </th>
+ <th class="center"><span>
+ <a href="/search/test/?field=time_add&sorder=desc" rel="nofollow">age</a></span>
+ </th>
+ <th class="center"><span class="seed">
+ <a href="/search/test/?field=seeders&sorder=desc" rel="nofollow">seed</a></span>
+ </th>
+ <th class="lasttd nobr center">
+ <a href="/search/test/?field=leechers&sorder=desc" rel="nofollow">leech</a>
+ </th>
+ </tr>
+ <tr class="even" id="torrent_test6478745">
+ <td>
+ <div class="iaconbox center floatright">
+ <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
+ <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
+ <i class="ka ka-comment"></i>
+ </a>
+ <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
+ <i class="ka ka16 ka-verify ka-green"></i>
+ </a>
+ <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
+ <i class="ka ka16 ka-arrow-down partner1Button"></i>
+ </a>
+ <a title="Torrent magnet link"
+ href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
+ <i class="ka ka16 ka-magnet"></i>
+ </a>
+ <a title="Download torrent file"
+ href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
+ <i class="ka ka16 ka-arrow-down"></i>
+ </a>
+ </div>
+ <div class="torrentname">
+ <a href="/test-t6478745.html" class="torType txtType"></a>
+ <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
+ <div class="markeredBlock torType txtType">
+ <a href="/url.html" class="cellMainLink">
+ <strong class="red">This should be the title</strong>
+ </a>
+ <span class="font11px lightgrey block">
+ Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
+ <a class="plain" href="/user/riri/">riri</a> in
+ <span id="cat_6478745">
+ <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
+ </span>
+ </span>
+ </div>
+ </td>
+ <td class="nobr center">449 bytes</td>
+ <td class="center">4</td>
+ <td class="center">2&nbsp;years</td>
+ <td class="green center">10</td>
+ <td class="red lasttd center">1</td>
+ </tr>
+ </table>
+ """
+ response = mock.Mock(text=html)
+ results = kickass.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This should be the title')
+ self.assertEqual(results[0]['url'], 'https://kickass.cd/url.html')
+ self.assertEqual(results[0]['content'], 'Posted by riri in Other > Unsorted')
+ self.assertEqual(results[0]['seed'], 10)
+ self.assertEqual(results[0]['leech'], 1)
+ self.assertEqual(results[0]['filesize'], 449)
+ self.assertEqual(results[0]['files'], 4)
+ self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETURL&dn=test')
+ self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/53917.torrent?title=test')
+
+ html = """
+ <table cellpadding="0" cellspacing="0" class="data" style="width: 100%">
+ <tr class="firstr">
+ <th class="width100perc nopad">torrent name</th>
+ <th class="center">
+ <a href="/search/test/?field=size&sorder=desc" rel="nofollow">size</a>
+ </th>
+ <th class="center"><span class="files">
+ <a href="/search/test/?field=files_count&sorder=desc" rel="nofollow">files</a></span>
+ </th>
+ <th class="center"><span>
+ <a href="/search/test/?field=time_add&sorder=desc" rel="nofollow">age</a></span>
+ </th>
+ <th class="center"><span class="seed">
+ <a href="/search/test/?field=seeders&sorder=desc" rel="nofollow">seed</a></span>
+ </th>
+ <th class="lasttd nobr center">
+ <a href="/search/test/?field=leechers&sorder=desc" rel="nofollow">leech</a>
+ </th>
+ </tr>
+ </table>
+ """
+ response = mock.Mock(text=html)
+ results = kickass.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ html = """
+ <table cellpadding="0" cellspacing="0" class="data" style="width: 100%">
+ <tr class="firstr">
+ <th class="width100perc nopad">torrent name</th>
+ <th class="center">
+ <a href="/search/test/?field=size&sorder=desc" rel="nofollow">size</a>
+ </th>
+ <th class="center"><span class="files">
+ <a href="/search/test/?field=files_count&sorder=desc" rel="nofollow">files</a></span>
+ </th>
+ <th class="center"><span>
+ <a href="/search/test/?field=time_add&sorder=desc" rel="nofollow">age</a></span>
+ </th>
+ <th class="center"><span class="seed">
+ <a href="/search/test/?field=seeders&sorder=desc" rel="nofollow">seed</a></span>
+ </th>
+ <th class="lasttd nobr center">
+ <a href="/search/test/?field=leechers&sorder=desc" rel="nofollow">leech</a>
+ </th>
+ </tr>
+ <tr class="even" id="torrent_test6478745">
+ <td>
+ <div class="iaconbox center floatright">
+ <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
+ <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
+ <i class="ka ka-comment"></i>
+ </a>
+ <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
+ <i class="ka ka16 ka-verify ka-green"></i>
+ </a>
+ <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
+ <i class="ka ka16 ka-arrow-down partner1Button"></i>
+ </a>
+ <a title="Torrent magnet link"
+ href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
+ <i class="ka ka16 ka-magnet"></i>
+ </a>
+ <a title="Download torrent file"
+ href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
+ <i class="ka ka16 ka-arrow-down"></i>
+ </a>
+ </div>
+ <div class="torrentname">
+ <a href="/test-t6478745.html" class="torType txtType"></a>
+ <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
+ <div class="markeredBlock torType txtType">
+ <a href="/url.html" class="cellMainLink">
+ <strong class="red">This should be the title</strong>
+ </a>
+ <span class="font11px lightgrey block">
+ Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
+ <a class="plain" href="/user/riri/">riri</a> in
+ <span id="cat_6478745">
+ <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
+ </span>
+ </span>
+ </div>
+ </td>
+ <td class="nobr center">1 KiB</td>
+ <td class="center">4</td>
+ <td class="center">2&nbsp;years</td>
+ <td class="green center">10</td>
+ <td class="red lasttd center">1</td>
+ </tr>
+ <tr class="even" id="torrent_test6478745">
+ <td>
+ <div class="iaconbox center floatright">
+ <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
+ <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
+ <i class="ka ka-comment"></i>
+ </a>
+ <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
+ <i class="ka ka16 ka-verify ka-green"></i>
+ </a>
+ <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
+ <i class="ka ka16 ka-arrow-down partner1Button"></i>
+ </a>
+ <a title="Torrent magnet link"
+ href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
+ <i class="ka ka16 ka-magnet"></i>
+ </a>
+ <a title="Download torrent file"
+ href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
+ <i class="ka ka16 ka-arrow-down"></i>
+ </a>
+ </div>
+ <div class="torrentname">
+ <a href="/test-t6478745.html" class="torType txtType"></a>
+ <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
+ <div class="markeredBlock torType txtType">
+ <a href="/url.html" class="cellMainLink">
+ <strong class="red">This should be the title</strong>
+ </a>
+ <span class="font11px lightgrey block">
+ Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
+ <a class="plain" href="/user/riri/">riri</a> in
+ <span id="cat_6478745">
+ <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
+ </span>
+ </span>
+ </div>
+ </td>
+ <td class="nobr center">1 MiB</td>
+ <td class="center">4</td>
+ <td class="center">2&nbsp;years</td>
+ <td class="green center">9</td>
+ <td class="red lasttd center">1</td>
+ </tr>
+ <tr class="even" id="torrent_test6478745">
+ <td>
+ <div class="iaconbox center floatright">
+ <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
+ <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
+ <i class="ka ka-comment"></i>
+ </a>
+ <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
+ <i class="ka ka16 ka-verify ka-green"></i>
+ </a>
+ <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
+ <i class="ka ka16 ka-arrow-down partner1Button"></i>
+ </a>
+ <a title="Torrent magnet link"
+ href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
+ <i class="ka ka16 ka-magnet"></i>
+ </a>
+ <a title="Download torrent file"
+ href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
+ <i class="ka ka16 ka-arrow-down"></i>
+ </a>
+ </div>
+ <div class="torrentname">
+ <a href="/test-t6478745.html" class="torType txtType"></a>
+ <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
+ <div class="markeredBlock torType txtType">
+ <a href="/url.html" class="cellMainLink">
+ <strong class="red">This should be the title</strong>
+ </a>
+ <span class="font11px lightgrey block">
+ Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
+ <a class="plain" href="/user/riri/">riri</a> in
+ <span id="cat_6478745">
+ <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
+ </span>
+ </span>
+ </div>
+ </td>
+ <td class="nobr center">1 GiB</td>
+ <td class="center">4</td>
+ <td class="center">2&nbsp;years</td>
+ <td class="green center">8</td>
+ <td class="red lasttd center">1</td>
+ </tr>
+ <tr class="even" id="torrent_test6478745">
+ <td>
+ <div class="iaconbox center floatright">
+ <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
+ <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
+ <i class="ka ka-comment"></i>
+ </a>
+ <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
+ <i class="ka ka16 ka-verify ka-green"></i>
+ </a>
+ <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
+ <i class="ka ka16 ka-arrow-down partner1Button"></i>
+ </a>
+ <a title="Torrent magnet link"
+ href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
+ <i class="ka ka16 ka-magnet"></i>
+ </a>
+ <a title="Download torrent file"
+ href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
+ <i class="ka ka16 ka-arrow-down"></i>
+ </a>
+ </div>
+ <div class="torrentname">
+ <a href="/test-t6478745.html" class="torType txtType"></a>
+ <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
+ <div class="markeredBlock torType txtType">
+ <a href="/url.html" class="cellMainLink">
+ <strong class="red">This should be the title</strong>
+ </a>
+ <span class="font11px lightgrey block">
+ Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
+ <a class="plain" href="/user/riri/">riri</a> in
+ <span id="cat_6478745">
+ <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
+ </span>
+ </span>
+ </div>
+ </td>
+ <td class="nobr center">1 TiB</td>
+ <td class="center">4</td>
+ <td class="center">2&nbsp;years</td>
+ <td class="green center">7</td>
+ <td class="red lasttd center">1</td>
+ </tr>
+ <tr class="even" id="torrent_test6478745">
+ <td>
+ <div class="iaconbox center floatright">
+ <a rel="6478745,0" class="icommentjs icon16" href="/test-t6478745.html#comment">
+ <em style="font-size: 12px; margin: 0 4px 0 4px;" class="iconvalue">3</em>
+ <i class="ka ka-comment"></i>
+ </a>
+ <a class="iverify icon16" href="/test-t6478745.html" title="Verified Torrent">
+ <i class="ka ka16 ka-verify ka-green"></i>
+ </a>
+ <a href="#" onclick="_scq.push([]); return false;" class="partner1Button idownload icon16">
+ <i class="ka ka16 ka-arrow-down partner1Button"></i>
+ </a>
+ <a title="Torrent magnet link"
+ href="magnet:?xt=urn:btih:MAGNETURL&dn=test" class="imagnet icon16">
+ <i class="ka ka16 ka-magnet"></i>
+ </a>
+ <a title="Download torrent file"
+ href="http://torcache.net/torrent/53917.torrent?title=test" class="idownload icon16">
+ <i class="ka ka16 ka-arrow-down"></i>
+ </a>
+ </div>
+ <div class="torrentname">
+ <a href="/test-t6478745.html" class="torType txtType"></a>
+ <a href="/test-t6478745.html" class="normalgrey font12px plain bold"></a>
+ <div class="markeredBlock torType txtType">
+ <a href="/url.html" class="cellMainLink">
+ <strong class="red">This should be the title</strong>
+ </a>
+ <span class="font11px lightgrey block">
+ Posted by <i class="ka ka-verify" style="font-size: 16px;color:orange;"></i>
+ <a class="plain" href="/user/riri/">riri</a> in
+ <span id="cat_6478745">
+ <strong><a href="/other/">Other</a> > <a href="/unsorted/">Unsorted</a></strong>
+ </span>
+ </span>
+ </div>
+ </td>
+ <td class="nobr center">z bytes</td>
+ <td class="center">r</td>
+ <td class="center">2&nbsp;years</td>
+ <td class="green center">a</td>
+ <td class="red lasttd center">t</td>
+ </tr>
+ </table>
+ """
+ response = mock.Mock(text=html)
+ results = kickass.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 5)
+ self.assertEqual(results[0]['title'], 'This should be the title')
+ self.assertEqual(results[0]['url'], 'https://kickass.cd/url.html')
+ self.assertEqual(results[0]['content'], 'Posted by riri in Other > Unsorted')
+ self.assertEqual(results[0]['seed'], 10)
+ self.assertEqual(results[0]['leech'], 1)
+ self.assertEqual(results[0]['files'], 4)
+ self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETURL&dn=test')
+ self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/53917.torrent?title=test')
+ self.assertEqual(results[0]['filesize'], 1000)
+ self.assertEqual(results[1]['filesize'], 1000000)
+ self.assertEqual(results[2]['filesize'], 1000000000)
+ self.assertEqual(results[3]['filesize'], 1000000000000)
+ self.assertEqual(results[4]['seed'], 0)
+ self.assertEqual(results[4]['leech'], 0)
+ self.assertEqual(results[4]['files'], None)
+ self.assertEqual(results[4]['filesize'], None)
diff --git a/tests/unit/engines/test_mediawiki.py b/tests/unit/engines/test_mediawiki.py
new file mode 100644
index 0000000..b863727
--- /dev/null
+++ b/tests/unit/engines/test_mediawiki.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import mediawiki
+from searx.testing import SearxTestCase
+
+
+class TestMediawikiEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr_FR'
+ params = mediawiki.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('wikipedia.org', params['url'])
+ self.assertIn('fr', params['url'])
+
+ dicto['language'] = 'all'
+ params = mediawiki.request(query, dicto)
+ self.assertIn('en', params['url'])
+
+ mediawiki.base_url = "http://test.url/"
+ mediawiki.search_url = mediawiki.base_url +\
+ 'w/api.php?action=query'\
+ '&list=search'\
+ '&{query}'\
+ '&srprop=timestamp'\
+ '&format=json'\
+ '&sroffset={offset}'\
+ '&srlimit={limit}' # noqa
+ params = mediawiki.request(query, dicto)
+ self.assertIn('test.url', params['url'])
+
+ def test_response(self):
+ dicto = defaultdict(dict)
+ dicto['language'] = 'fr'
+ mediawiki.base_url = "https://{language}.wikipedia.org/"
+
+ self.assertRaises(AttributeError, mediawiki.response, None)
+ self.assertRaises(AttributeError, mediawiki.response, [])
+ self.assertRaises(AttributeError, mediawiki.response, '')
+ self.assertRaises(AttributeError, mediawiki.response, '[]')
+
+ response = mock.Mock(text='{}', search_params=dicto)
+ self.assertEqual(mediawiki.response(response), [])
+
+ response = mock.Mock(text='{"data": []}', search_params=dicto)
+ self.assertEqual(mediawiki.response(response), [])
+
+ json = """
+ {
+ "query-continue": {
+ "search": {
+ "sroffset": 1
+ }
+ },
+ "query": {
+ "searchinfo": {
+ "totalhits": 29721
+ },
+ "search": [
+ {
+ "ns": 0,
+ "title": "This is the title étude",
+ "timestamp": "2014-12-19T17:42:52Z"
+ }
+ ]
+ }
+ }
+ """
+ response = mock.Mock(text=json, search_params=dicto)
+ results = mediawiki.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], u'This is the title étude')
+ self.assertIn('fr.wikipedia.org', results[0]['url'])
+ self.assertIn('This_is_the_title', results[0]['url'])
+ self.assertIn('%C3%A9tude', results[0]['url'])
+ self.assertEqual(results[0]['content'], '')
+
+ json = """
+ {
+ "query-continue": {
+ "search": {
+ "sroffset": 1
+ }
+ },
+ "query": {
+ "searchinfo": {
+ "totalhits": 29721
+ },
+ "search": [
+ ]
+ }
+ }
+ """
+ response = mock.Mock(text=json, search_params=dicto)
+ results = mediawiki.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {
+ "query-continue": {
+ "search": {
+ "sroffset": 1
+ }
+ },
+ "query": {
+ }
+ }
+ """
+ response = mock.Mock(text=json, search_params=dicto)
+ results = mediawiki.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = r"""
+ {"toto":[
+ {"id":200,"name":"Artist Name",
+ "link":"http:\/\/www.mediawiki.com\/artist\/1217","type":"artist"}
+ ]}
+ """
+ response = mock.Mock(text=json, search_params=dicto)
+ results = mediawiki.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_mixcloud.py b/tests/unit/engines/test_mixcloud.py
new file mode 100644
index 0000000..9c79a47
--- /dev/null
+++ b/tests/unit/engines/test_mixcloud.py
@@ -0,0 +1,67 @@
+from collections import defaultdict
+import mock
+from searx.engines import mixcloud
+from searx.testing import SearxTestCase
+
+
+class TestMixcloudEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = mixcloud.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('mixcloud.com' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, mixcloud.response, None)
+ self.assertRaises(AttributeError, mixcloud.response, [])
+ self.assertRaises(AttributeError, mixcloud.response, '')
+ self.assertRaises(AttributeError, mixcloud.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(mixcloud.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(mixcloud.response(response), [])
+
+ json = """
+ {"data":[
+ {
+ "user": {
+ "url": "http://www.mixcloud.com/user/",
+ "username": "user",
+ "name": "User",
+ "key": "/user/"
+ },
+ "key": "/user/this-is-the-url/",
+ "created_time": "2014-11-14T13:30:02Z",
+ "audio_length": 3728,
+ "slug": "this-is-the-url",
+ "name": "Title of track",
+ "url": "http://www.mixcloud.com/user/this-is-the-url/",
+ "updated_time": "2014-11-14T13:14:10Z"
+ }
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = mixcloud.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title of track')
+ self.assertEqual(results[0]['url'], 'http://www.mixcloud.com/user/this-is-the-url/')
+ self.assertEqual(results[0]['content'], 'User')
+ self.assertTrue('http://www.mixcloud.com/user/this-is-the-url/' in results[0]['embedded'])
+
+ json = r"""
+ {"toto":[
+ {"id":200,"name":"Artist Name",
+ "link":"http:\/\/www.mixcloud.com\/artist\/1217","type":"artist"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = mixcloud.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_nyaa.py b/tests/unit/engines/test_nyaa.py
new file mode 100644
index 0000000..db412e1
--- /dev/null
+++ b/tests/unit/engines/test_nyaa.py
@@ -0,0 +1,66 @@
+from collections import defaultdict
+import mock
+from searx.engines import nyaa
+from searx.testing import SearxTestCase
+
+
+class TestNyaaEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dic = defaultdict(dict)
+ dic['pageno'] = 1
+ params = nyaa.request(query, dic)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('nyaa.se' in params['url'])
+
+ def test_response(self):
+ resp = mock.Mock(text='<html></html>')
+ self.assertEqual(nyaa.response(resp), [])
+
+ html = """
+ <table class="tlist">
+ <tbody>
+ <tr class="trusted tlistrow">
+ <td class="tlisticon">
+ <a href="//www.nyaa.se" title="English-translated Anime">
+ <img src="//files.nyaa.se" alt="English-translated Anime">
+ </a>
+ </td>
+ <td class="tlistname">
+ <a href="//www.nyaa.se/?page3">
+ Sample torrent title
+ </a>
+ </td>
+ <td class="tlistdownload">
+ <a href="//www.nyaa.se/?page_dl" title="Download">
+ <img src="//files.nyaa.se/www-dl.png" alt="DL">
+ </a>
+ </td>
+ <td class="tlistsize">10 MiB</td>
+ <td class="tlistsn">1</td>
+ <td class="tlistln">3</td>
+ <td class="tlistdn">666</td>
+ <td class="tlistmn">0</td>
+ </tr>
+ </tbody>
+ </table>
+ """
+
+ resp = mock.Mock(text=html)
+ results = nyaa.response(resp)
+
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+
+ r = results[0]
+ self.assertTrue(r['url'].find('www.nyaa.se/?page3') >= 0)
+ self.assertTrue(r['torrentfile'].find('www.nyaa.se/?page_dl') >= 0)
+ self.assertTrue(r['content'].find('English-translated Anime') >= 0)
+ self.assertTrue(r['content'].find('Downloaded 666 times.') >= 0)
+
+ self.assertEqual(r['title'], 'Sample torrent title')
+ self.assertEqual(r['seed'], 1)
+ self.assertEqual(r['leech'], 3)
+ self.assertEqual(r['filesize'], 10 * 1024 * 1024)
diff --git a/tests/unit/engines/test_openstreetmap.py b/tests/unit/engines/test_openstreetmap.py
new file mode 100644
index 0000000..7b7783f
--- /dev/null
+++ b/tests/unit/engines/test_openstreetmap.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import openstreetmap
+from searx.testing import SearxTestCase
+
+
+class TestOpenstreetmapEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ params = openstreetmap.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('openstreetmap.org', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, openstreetmap.response, None)
+ self.assertRaises(AttributeError, openstreetmap.response, [])
+ self.assertRaises(AttributeError, openstreetmap.response, '')
+ self.assertRaises(AttributeError, openstreetmap.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(openstreetmap.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(openstreetmap.response(response), [])
+
+ json = """
+ [
+ {
+ "place_id": "127732055",
+ "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
+ "osm_type": "relation",
+ "osm_id": "7444",
+ "boundingbox": [
+ "48.8155755",
+ "48.902156",
+ "2.224122",
+ "2.4697602"
+ ],
+ "lat": "48.8565056",
+ "lon": "2.3521334",
+ "display_name": "This is the title",
+ "class": "place",
+ "type": "city",
+ "importance": 0.96893459932191,
+ "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
+ "address": {
+ "city": "Paris",
+ "county": "Paris",
+ "state": "Île-de-France",
+ "country": "France",
+ "country_code": "fr"
+ },
+ "geojson": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 2.224122,
+ 48.854199
+ ]
+ ]
+ ]
+ }
+ }
+ ]
+ """
+ response = mock.Mock(text=json)
+ results = openstreetmap.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://openstreetmap.org/relation/7444')
+ self.assertIn('coordinates', results[0]['geojson'])
+ self.assertEqual(results[0]['geojson']['coordinates'][0][0][0], 2.224122)
+ self.assertEqual(results[0]['geojson']['coordinates'][0][0][1], 48.854199)
+ self.assertEqual(results[0]['address'], None)
+ self.assertIn('48.8155755', results[0]['boundingbox'])
+ self.assertIn('48.902156', results[0]['boundingbox'])
+ self.assertIn('2.224122', results[0]['boundingbox'])
+ self.assertIn('2.4697602', results[0]['boundingbox'])
+
+ json = """
+ [
+ {
+ "place_id": "127732055",
+ "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
+ "osm_type": "relation",
+ "osm_id": "7444",
+ "boundingbox": [
+ "48.8155755",
+ "48.902156",
+ "2.224122",
+ "2.4697602"
+ ],
+ "lat": "48.8565056",
+ "lon": "2.3521334",
+ "display_name": "This is the title",
+ "class": "tourism",
+ "type": "city",
+ "importance": 0.96893459932191,
+ "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
+ "address": {
+ "city": "Paris",
+ "county": "Paris",
+ "state": "Île-de-France",
+ "country": "France",
+ "country_code": "fr",
+ "address29": "Address"
+ },
+ "geojson": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 2.224122,
+ 48.854199
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "place_id": "127732055",
+ "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
+ "osm_type": "relation",
+ "osm_id": "7444",
+ "boundingbox": [
+ "48.8155755",
+ "48.902156",
+ "2.224122",
+ "2.4697602"
+ ],
+ "lat": "48.8565056",
+ "lon": "2.3521334",
+ "display_name": "This is the title",
+ "class": "tourism",
+ "type": "city",
+ "importance": 0.96893459932191,
+ "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
+ "address": {
+ "city": "Paris",
+ "county": "Paris",
+ "state": "Île-de-France",
+ "country": "France",
+ "postcode": 75000,
+ "country_code": "fr"
+ },
+ "geojson": {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ 2.224122,
+ 48.854199
+ ]
+ ]
+ ]
+ }
+ },
+ {
+ "place_id": "127732055",
+ "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
+ "osm_type": "node",
+ "osm_id": "7444",
+ "boundingbox": [
+ "48.8155755",
+ "48.902156",
+ "2.224122",
+ "2.4697602"
+ ],
+ "lat": "48.8565056",
+ "lon": "2.3521334",
+ "display_name": "This is the title",
+ "class": "tourism",
+ "type": "city",
+ "importance": 0.96893459932191,
+ "icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png",
+ "address": {
+ "city": "Paris",
+ "county": "Paris",
+ "state": "Île-de-France",
+ "country": "France",
+ "country_code": "fr",
+ "address29": "Address"
+ }
+ }
+ ]
+ """
+ response = mock.Mock(text=json)
+ results = openstreetmap.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 3)
+ self.assertIn('48.8565056', results[2]['geojson']['coordinates'])
+ self.assertIn('2.3521334', results[2]['geojson']['coordinates'])
diff --git a/tests/unit/engines/test_pdbe.py b/tests/unit/engines/test_pdbe.py
new file mode 100644
index 0000000..7aa8e26
--- /dev/null
+++ b/tests/unit/engines/test_pdbe.py
@@ -0,0 +1,109 @@
+import mock
+from collections import defaultdict
+from searx.engines import pdbe
+from searx.testing import SearxTestCase
+
+
+class TestPdbeEngine(SearxTestCase):
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ params = pdbe.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue('ebi.ac.uk' in params['url'])
+ self.assertTrue('data' in params)
+ self.assertTrue('q' in params['data'])
+ self.assertTrue(query in params['data']['q'])
+ self.assertTrue('wt' in params['data'])
+ self.assertTrue('json' in params['data']['wt'])
+ self.assertTrue('method' in params)
+ self.assertTrue(params['method'] == 'POST')
+
+ def test_response(self):
+ self.assertRaises(AttributeError, pdbe.response, None)
+ self.assertRaises(AttributeError, pdbe.response, [])
+ self.assertRaises(AttributeError, pdbe.response, '')
+ self.assertRaises(AttributeError, pdbe.response, '[]')
+
+ json = """
+{
+ "response": {
+ "docs": [
+ {
+ "citation_title": "X-ray crystal structure of ferric Aplysia limacina myoglobin in different liganded states.",
+ "citation_year": 1993,
+ "entry_author_list": [
+ "Conti E, Moser C, Rizzi M, Mattevi A, Lionetti C, Coda A, Ascenzi P, Brunori M, Bolognesi M"
+ ],
+ "journal": "J. Mol. Biol.",
+ "journal_page": "498-508",
+ "journal_volume": "233",
+ "pdb_id": "2fal",
+ "status": "REL",
+ "title": "X-RAY CRYSTAL STRUCTURE OF FERRIC APLYSIA LIMACINA MYOGLOBIN IN DIFFERENT LIGANDED STATES"
+ }
+ ],
+ "numFound": 1,
+ "start": 0
+ },
+ "responseHeader": {
+ "QTime": 0,
+ "params": {
+ "q": "2fal",
+ "wt": "json"
+ },
+ "status": 0
+ }
+}
+"""
+
+ response = mock.Mock(text=json)
+ results = pdbe.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'],
+ 'X-RAY CRYSTAL STRUCTURE OF FERRIC APLYSIA LIMACINA MYOGLOBIN IN DIFFERENT LIGANDED STATES')
+ self.assertEqual(results[0]['url'], pdbe.pdbe_entry_url.format(pdb_id='2fal'))
+ self.assertEqual(results[0]['img_src'], pdbe.pdbe_preview_url.format(pdb_id='2fal'))
+ self.assertTrue('Conti E' in results[0]['content'])
+ self.assertTrue('X-ray crystal structure of ferric Aplysia limacina myoglobin in different liganded states.' in
+ results[0]['content'])
+ self.assertTrue('1993' in results[0]['content'])
+
+ # Testing proper handling of PDB entries marked as obsolete
+ json = """
+{
+ "response": {
+ "docs": [
+ {
+ "citation_title": "Obsolete entry test",
+ "citation_year": 2016,
+ "entry_author_list": ["Doe J"],
+ "journal": "J. Obs.",
+ "journal_page": "1-2",
+ "journal_volume": "1",
+ "pdb_id": "xxxx",
+ "status": "OBS",
+ "title": "OBSOLETE ENTRY TEST",
+ "superseded_by": "yyyy"
+ }
+ ],
+ "numFound": 1,
+ "start": 0
+ },
+ "responseHeader": {
+ "QTime": 0,
+ "params": {
+ "q": "xxxx",
+ "wt": "json"
+ },
+ "status": 0
+ }
+}
+"""
+ response = mock.Mock(text=json)
+ results = pdbe.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'OBSOLETE ENTRY TEST&nbsp;(OBSOLETE)')
+ self.assertTrue(results[0]['content'].startswith('<em>This entry has been superseded by'))
diff --git a/tests/unit/engines/test_photon.py b/tests/unit/engines/test_photon.py
new file mode 100644
index 0000000..7344978
--- /dev/null
+++ b/tests/unit/engines/test_photon.py
@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import photon
+from searx.testing import SearxTestCase
+
+
+class TestPhotonEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'all'
+ params = photon.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('photon.komoot.de', params['url'])
+
+ dicto['language'] = 'all'
+ params = photon.request(query, dicto)
+ self.assertNotIn('lang', params['url'])
+
+ dicto['language'] = 'al'
+ params = photon.request(query, dicto)
+ self.assertNotIn('lang', params['url'])
+
+ dicto['language'] = 'fr'
+ params = photon.request(query, dicto)
+ self.assertIn('fr', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, photon.response, None)
+ self.assertRaises(AttributeError, photon.response, [])
+ self.assertRaises(AttributeError, photon.response, '')
+ self.assertRaises(AttributeError, photon.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(photon.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(photon.response(response), [])
+
+ json = """
+ {
+ "features": [
+ {
+ "properties": {
+ "osm_key": "waterway",
+ "extent": [
+ -1.4508446,
+ 51.1614997,
+ -1.4408036,
+ 51.1525635
+ ],
+ "name": "This is the title",
+ "state": "England",
+ "osm_id": 114823817,
+ "osm_type": "W",
+ "osm_value": "river",
+ "city": "Test Valley",
+ "country": "United Kingdom"
+ },
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ -1.4458571,
+ 51.1576661
+ ]
+ }
+ },
+ {
+ "properties": {
+ "osm_key": "place",
+ "street": "Rue",
+ "state": "Ile-de-France",
+ "osm_id": 129211377,
+ "osm_type": "R",
+ "housenumber": "10",
+ "postcode": "75011",
+ "osm_value": "house",
+ "city": "Paris",
+ "country": "France"
+ },
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 2.3725025,
+ 48.8654481
+ ]
+ }
+ },
+ {
+ "properties": {
+ "osm_key": "amenity",
+ "street": "Allée",
+ "name": "Bibliothèque",
+ "state": "Ile-de-France",
+ "osm_id": 1028573132,
+ "osm_type": "N",
+ "postcode": "75001",
+ "osm_value": "library",
+ "city": "Paris",
+ "country": "France"
+ },
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 2.3445634,
+ 48.862494
+ ]
+ }
+ },
+ {
+ "properties": {
+ "osm_key": "amenity",
+ "osm_id": 1028573132,
+ "osm_type": "Y",
+ "postcode": "75001",
+ "osm_value": "library",
+ "city": "Paris",
+ "country": "France"
+ },
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 2.3445634,
+ 48.862494
+ ]
+ }
+ },
+ {
+ }
+ ],
+ "type": "FeatureCollection"
+ }
+ """
+ response = mock.Mock(text=json)
+ results = photon.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 3)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['content'], '')
+ self.assertEqual(results[0]['longitude'], -1.4458571)
+ self.assertEqual(results[0]['latitude'], 51.1576661)
+ self.assertIn(-1.4508446, results[0]['boundingbox'])
+ self.assertIn(51.1614997, results[0]['boundingbox'])
+ self.assertIn(-1.4408036, results[0]['boundingbox'])
+ self.assertIn(51.1525635, results[0]['boundingbox'])
+ self.assertIn('type', results[0]['geojson'])
+ self.assertEqual(results[0]['geojson']['type'], 'Point')
+ self.assertEqual(results[0]['address'], None)
+ self.assertEqual(results[0]['osm']['type'], 'way')
+ self.assertEqual(results[0]['osm']['id'], 114823817)
+ self.assertEqual(results[0]['url'], 'https://openstreetmap.org/way/114823817')
+ self.assertEqual(results[1]['osm']['type'], 'relation')
+ self.assertEqual(results[2]['address']['name'], u'Bibliothèque')
+ self.assertEqual(results[2]['address']['house_number'], None)
+ self.assertEqual(results[2]['address']['locality'], 'Paris')
+ self.assertEqual(results[2]['address']['postcode'], '75001')
+ self.assertEqual(results[2]['address']['country'], 'France')
+ self.assertEqual(results[2]['osm']['type'], 'node')
diff --git a/tests/unit/engines/test_piratebay.py b/tests/unit/engines/test_piratebay.py
new file mode 100644
index 0000000..5699380
--- /dev/null
+++ b/tests/unit/engines/test_piratebay.py
@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import piratebay
+from searx.testing import SearxTestCase
+
+
+class TestPiratebayEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['category'] = 'Toto'
+ params = piratebay.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('piratebay.se', params['url'])
+ self.assertIn('0', params['url'])
+
+ dicto['category'] = 'music'
+ params = piratebay.request(query, dicto)
+ self.assertIn('100', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, piratebay.response, None)
+ self.assertRaises(AttributeError, piratebay.response, [])
+ self.assertRaises(AttributeError, piratebay.response, '')
+ self.assertRaises(AttributeError, piratebay.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(piratebay.response(response), [])
+
+ html = """
+ <table id="searchResult">
+ <tr>
+ </tr>
+ <tr>
+ <td class="vertTh">
+ <center>
+ <a href="#" title="More from this category">Anime</a><br/>
+ (<a href="#" title="More from this category">Anime</a>)
+ </center>
+ </td>
+ <td>
+ <div class="detName">
+ <a href="/this.is.the.link" class="detLink" title="Title">
+ This is the title
+ </a>
+ </div>
+ <a href="magnet:?xt=urn:btih:MAGNETLINK" title="Download this torrent using magnet">
+ <img src="/static/img/icon-magnet.gif" alt="Magnet link"/>
+ </a>
+ <a href="http://torcache.net/torrent/TORRENTFILE.torrent" title="Download this torrent">
+ <img src="/static/img/dl.gif" class="dl" alt="Download"/>
+ </a>
+ <a href="/user/HorribleSubs">
+ <img src="/static/img/vip.gif" alt="VIP" title="VIP" style="width:11px;" border='0'/>
+ </a>
+ <img src="/static/img/11x11p.png"/>
+ <font class="detDesc">
+ This is the content <span>and should be</span> OK
+ </font>
+ </td>
+ <td align="right">13</td>
+ <td align="right">334</td>
+ </tr>
+ <tr>
+ <td class="vertTh">
+ <center>
+ <a href="#" title="More from this category">Anime</a><br/>
+ (<a href="#" title="More from this category">Anime</a>)
+ </center>
+ </td>
+ <td>
+ <div class="detName">
+ <a href="/this.is.the.link" class="detLink" title="Title">
+ This is the title
+ </a>
+ </div>
+ <a href="magnet:?xt=urn:btih:MAGNETLINK" title="Download this torrent using magnet">
+ <img src="/static/img/icon-magnet.gif" alt="Magnet link"/>
+ </a>
+ <a href="/user/HorribleSubs">
+ <img src="/static/img/vip.gif" alt="VIP" title="VIP" style="width:11px;" border='0'/>
+ </a>
+ <img src="/static/img/11x11p.png"/>
+ <font class="detDesc">
+ This is the content <span>and should be</span> OK
+ </font>
+ </td>
+ <td align="right">13</td>
+ <td align="right">334</td>
+ </tr>
+ </table>
+ """
+ response = mock.Mock(text=html)
+ results = piratebay.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://thepiratebay.se/this.is.the.link')
+ self.assertEqual(results[0]['content'], 'This is the content and should be OK')
+ self.assertEqual(results[0]['seed'], 13)
+ self.assertEqual(results[0]['leech'], 334)
+ self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETLINK')
+ self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/TORRENTFILE.torrent')
+
+ self.assertEqual(results[1]['torrentfile'], None)
+
+ html = """
+ <table id="searchResult">
+ <tr>
+ </tr>
+ <tr>
+ <td class="vertTh">
+ <center>
+ <a href="#" title="More from this category">Anime</a><br/>
+ (<a href="#" title="More from this category">Anime</a>)
+ </center>
+ </td>
+ <td>
+ <div class="detName">
+ <a href="/this.is.the.link" class="detLink" title="Title">
+ This is the title
+ </a>
+ </div>
+ <a href="magnet:?xt=urn:btih:MAGNETLINK" title="Download this torrent using magnet">
+ <img src="/static/img/icon-magnet.gif" alt="Magnet link"/>
+ </a>
+ <a href="http://torcache.net/torrent/TORRENTFILE.torrent" title="Download this torrent">
+ <img src="/static/img/dl.gif" class="dl" alt="Download"/>
+ </a>
+ <a href="/user/HorribleSubs">
+ <img src="/static/img/vip.gif" alt="VIP" title="VIP" style="width:11px;" border='0'/>
+ </a>
+ <img src="/static/img/11x11p.png"/>
+ <font class="detDesc">
+ This is the content <span>and should be</span> OK
+ </font>
+ </td>
+ <td align="right">s</td>
+ <td align="right">d</td>
+ </tr>
+ </table>
+ """
+ response = mock.Mock(text=html)
+ results = piratebay.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://thepiratebay.se/this.is.the.link')
+ self.assertEqual(results[0]['content'], 'This is the content and should be OK')
+ self.assertEqual(results[0]['seed'], 0)
+ self.assertEqual(results[0]['leech'], 0)
+ self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:MAGNETLINK')
+ self.assertEqual(results[0]['torrentfile'], 'http://torcache.net/torrent/TORRENTFILE.torrent')
+
+ html = """
+ <table id="searchResult">
+ </table>
+ """
+ response = mock.Mock(text=html)
+ results = piratebay.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_qwant.py b/tests/unit/engines/test_qwant.py
new file mode 100644
index 0000000..b7133c0
--- /dev/null
+++ b/tests/unit/engines/test_qwant.py
@@ -0,0 +1,338 @@
+from collections import defaultdict
+import mock
+from searx.engines import qwant
+from searx.testing import SearxTestCase
+
+
+class TestQwantEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['language'] = 'fr-FR'
+ qwant.categories = ['']
+ params = qwant.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('web', params['url'])
+ self.assertIn('qwant.com', params['url'])
+ self.assertIn('fr_fr', params['url'])
+
+ dicto['language'] = 'all'
+ qwant.categories = ['news']
+ params = qwant.request(query, dicto)
+ self.assertFalse('fr' in params['url'])
+ self.assertIn('news', params['url'])
+
+ qwant.supported_languages = ['en', 'fr-FR', 'fr-CA']
+ dicto['language'] = 'fr'
+ params = qwant.request(query, dicto)
+ self.assertIn('fr_fr', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, qwant.response, None)
+ self.assertRaises(AttributeError, qwant.response, [])
+ self.assertRaises(AttributeError, qwant.response, '')
+ self.assertRaises(AttributeError, qwant.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(qwant.response(response), [])
+
+ response = mock.Mock(text='{"data": {}}')
+ self.assertEqual(qwant.response(response), [])
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "desc": "Description",
+ "date": "",
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['general']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url.xyz')
+ self.assertEqual(results[0]['content'], 'Description')
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "media": "http://image.jpg",
+ "desc": "",
+ "thumbnail": "http://thumbnail.jpg",
+ "date": "",
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['images']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url.xyz')
+ self.assertEqual(results[0]['content'], '')
+ self.assertEqual(results[0]['thumbnail_src'], 'http://thumbnail.jpg')
+ self.assertEqual(results[0]['img_src'], 'http://image.jpg')
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "desc": "Description",
+ "date": 1433260920,
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['news']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url.xyz')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertIn('publishedDate', results[0])
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "desc": "Description",
+ "date": 1433260920,
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['social media']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'http://www.url.xyz')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertIn('publishedDate', results[0])
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "items": [
+ {
+ "title": "Title",
+ "score": 9999,
+ "url": "http://www.url.xyz",
+ "source": "...",
+ "desc": "Description",
+ "date": 1433260920,
+ "_id": "db0aadd62c2a8565567ffc382f5c61fa",
+ "favicon": "https://s.qwant.com/fav.ico"
+ }
+ ],
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ qwant.categories = ['']
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "result": {
+ "filters": []
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {
+ "status": "success",
+ "data": {
+ "query": {
+ "locale": "en_us",
+ "query": "Test",
+ "offset": 10
+ },
+ "cache": {
+ "key": "e66aa864c00147a0e3a16ff7a5efafde",
+ "created": 1433092754,
+ "expiration": 259200,
+ "status": "miss",
+ "age": 0
+ }
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {
+ "status": "success"
+ }
+ """
+ response = mock.Mock(text=json)
+ results = qwant.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ def test_fetch_supported_languages(self):
+ page = """some code...
+ config_set('project.regionalisation', {"continents":{},"languages":
+ {"de":{"code":"de","name":"Deutsch","countries":["DE","CH","AT"]},
+ "it":{"code":"it","name":"Italiano","countries":["IT","CH"]}}});
+ some more code..."""
+ response = mock.Mock(text=page)
+ languages = qwant._fetch_supported_languages(response)
+ self.assertEqual(type(languages), list)
+ self.assertEqual(len(languages), 5)
+ self.assertIn('de-DE', languages)
+ self.assertIn('de-CH', languages)
+ self.assertIn('de-AT', languages)
+ self.assertIn('it-IT', languages)
+ self.assertIn('it-CH', languages)
diff --git a/tests/unit/engines/test_reddit.py b/tests/unit/engines/test_reddit.py
new file mode 100644
index 0000000..9c94f4e
--- /dev/null
+++ b/tests/unit/engines/test_reddit.py
@@ -0,0 +1,71 @@
+from collections import defaultdict
+import mock
+from searx.engines import reddit
+from searx.testing import SearxTestCase
+from datetime import datetime
+
+
+class TestRedditEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dic = defaultdict(dict)
+ params = reddit.request(query, dic)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('reddit.com' in params['url'])
+
+ def test_response(self):
+ resp = mock.Mock(text='{}')
+ self.assertEqual(reddit.response(resp), [])
+
+ json = """
+ {
+ "kind": "Listing",
+ "data": {
+ "children": [{
+ "data": {
+ "url": "http://google2.com/",
+ "permalink": "http://google.com/",
+ "title": "Title number one",
+ "selftext": "Sample",
+ "created_utc": 1401219957.0,
+ "thumbnail": "http://image.com/picture.jpg"
+ }
+ }, {
+ "data": {
+ "url": "https://reddit2.com/",
+ "permalink": "https://reddit.com/",
+ "title": "Title number two",
+ "selftext": "Dominus vobiscum",
+ "created_utc": 1438792533.0,
+ "thumbnail": "self"
+ }
+ }]
+ }
+ }
+ """
+
+ resp = mock.Mock(text=json)
+ results = reddit.response(resp)
+
+ self.assertEqual(len(results), 2)
+ self.assertEqual(type(results), list)
+
+ # testing first result (picture)
+ r = results[0]
+ self.assertEqual(r['url'], 'http://google.com/')
+ self.assertEqual(r['title'], 'Title number one')
+ self.assertEqual(r['template'], 'images.html')
+ self.assertEqual(r['img_src'], 'http://google2.com/')
+ self.assertEqual(r['thumbnail_src'], 'http://image.com/picture.jpg')
+
+ # testing second result (self-post)
+ r = results[1]
+ self.assertEqual(r['url'], 'https://reddit.com/')
+ self.assertEqual(r['title'], 'Title number two')
+ self.assertEqual(r['content'], 'Dominus vobiscum')
+ created = datetime.fromtimestamp(1438792533.0)
+ self.assertEqual(r['publishedDate'], created)
+ self.assertTrue('thumbnail_src' not in r)
+ self.assertTrue('img_src' not in r)
diff --git a/tests/unit/engines/test_scanr_structures.py b/tests/unit/engines/test_scanr_structures.py
new file mode 100644
index 0000000..a7b9e91
--- /dev/null
+++ b/tests/unit/engines/test_scanr_structures.py
@@ -0,0 +1,175 @@
+from collections import defaultdict
+import mock
+from searx.engines import scanr_structures
+from searx.testing import SearxTestCase
+
+
+class TestScanrStructuresEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ params = scanr_structures.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['data'])
+ self.assertIn('scanr.enseignementsup-recherche.gouv.fr', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, scanr_structures.response, None)
+ self.assertRaises(AttributeError, scanr_structures.response, [])
+ self.assertRaises(AttributeError, scanr_structures.response, '')
+ self.assertRaises(AttributeError, scanr_structures.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(scanr_structures.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(scanr_structures.response(response), [])
+
+ json = u"""
+ {
+ "request":
+ {
+ "query":"test_query",
+ "page":1,
+ "pageSize":20,
+ "sortOrder":"RELEVANCY",
+ "sortDirection":"ASC",
+ "searchField":"ALL",
+ "from":0
+ },
+ "total":2471,
+ "results":[
+ {
+ "id":"200711886U",
+ "label":"Laboratoire d'Informatique de Grenoble",
+ "kind":"RNSR",
+ "publicEntity":true,
+ "address":{"city":"Grenoble","departement":"38"},
+ "logo":"/static/logos/200711886U.png",
+ "acronym":"LIG",
+ "type":{"code":"UR","label":"Unit\xe9 de recherche"},
+ "level":2,
+ "institutions":[
+ {
+ "id":"193819125",
+ "label":"Grenoble INP",
+ "acronym":"IPG",
+ "code":"UMR 5217"
+ },
+ {
+ "id":"130021397",
+ "label":"Universit\xe9 de Grenoble Alpes",
+ "acronym":"UGA",
+ "code":"UMR 5217"
+ },
+ {
+ "id":"180089013",
+ "label":"Centre national de la recherche scientifique",
+ "acronym":"CNRS",
+ "code":"UMR 5217"
+ },
+ {
+ "id":"180089047",
+ "label":"Institut national de recherche en informatique et en automatique",
+ "acronym":"Inria",
+ "code":"UMR 5217"
+ }
+ ],
+ "highlights":[
+ {
+ "type":"projects",
+ "value":"linguicielles d\xe9velopp\xe9s jusqu'ici par le GETALP\
+ du <strong>LIG</strong> en tant que prototypes op\xe9rationnels.\
+\\r\\nDans le contexte"
+ },
+ {
+ "type":"acronym",
+ "value":"<strong>LIG</strong>"
+ },
+ {
+ "type":"websiteContents",
+ "value":"S\xe9lection\\nListe structures\\nD\xe9tail\\n\
+ Accueil\\n200711886U : <strong>LIG</strong>\
+ Laboratoire d'Informatique de Grenoble Unit\xe9 de recherche"},
+ {
+ "type":"publications",
+ "value":"de noms. Nous avons d'abord d\xe9velopp\xe9 LOOV \
+ (pour <strong>Lig</strong> Overlaid OCR in Vid\xe9o), \
+ un outil d'extraction des"
+ }
+ ]
+ },
+ {
+ "id":"199511665F",
+ "label":"Laboratoire Bordelais de Recherche en Informatique",
+ "kind":"RNSR",
+ "publicEntity":true,
+ "address":{"city":"Talence","departement":"33"},
+ "logo":"/static/logos/199511665F.png",
+ "acronym":"LaBRI",
+ "type":{"code":"UR","label":"Unit\xe9 de recherche"},
+ "level":2,
+ "institutions":[
+ {
+ "id":"130006356",
+ "label":"Institut polytechnique de Bordeaux",
+ "acronym":"IPB",
+ "code":"UMR 5800"
+ },
+ {
+ "id":"130018351",
+ "label":"Universit\xe9 de Bordeaux",
+ "acronym":null,
+ "code":"UMR 5800"
+ },
+ {
+ "id":"180089013",
+ "label":"Centre national de la recherche scientifique",
+ "acronym":"CNRS",
+ "code":"UMR 5800"
+ },
+ {
+ "id":"180089047",
+ "label":"Institut national de recherche en informatique et en automatique",
+ "acronym":"Inria",
+ "code":"UMR 5800"
+ }
+ ],
+ "highlights":[
+ {
+ "type":"websiteContents",
+ "value":"Samia Kerdjoudj\\n2016-07-05\\nDouble-exponential\
+ and <strong>triple</strong>-exponential bounds for\
+ choosability problems parameterized"
+ },
+ {
+ "type":"publications",
+ "value":"de cam\xe9ras install\xe9es dans les lieux publiques \
+ a <strong>tripl\xe9</strong> en 2009, passant de 20 000 \
+ \xe0 60 000. Malgr\xe9 le"
+ }
+ ]
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = scanr_structures.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], u"Laboratoire d'Informatique de Grenoble")
+ self.assertEqual(results[0]['url'], 'https://scanr.enseignementsup-recherche.gouv.fr/structure/200711886U')
+ self.assertEqual(results[0]['content'],
+ u"linguicielles d\xe9velopp\xe9s jusqu'ici par le GETALP "
+ u"du LIG en tant que prototypes "
+ u"op\xe9rationnels. Dans le contexte")
+ self.assertEqual(results[1]['img_src'],
+ 'https://scanr.enseignementsup-recherche.gouv.fr//static/logos/199511665F.png')
+ self.assertEqual(results[1]['content'],
+ "Samia Kerdjoudj 2016-07-05 Double-exponential and"
+ " triple-exponential bounds for "
+ "choosability problems parameterized")
+ self.assertEqual(results[1]['url'], 'https://scanr.enseignementsup-recherche.gouv.fr/structure/199511665F')
+ self.assertEqual(results[1]['title'], u"Laboratoire Bordelais de Recherche en Informatique")
diff --git a/tests/unit/engines/test_searchcode_code.py b/tests/unit/engines/test_searchcode_code.py
new file mode 100644
index 0000000..955aea1
--- /dev/null
+++ b/tests/unit/engines/test_searchcode_code.py
@@ -0,0 +1,75 @@
+from collections import defaultdict
+import mock
+from searx.engines import searchcode_code
+from searx.testing import SearxTestCase
+
+
+class TestSearchcodeCodeEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = searchcode_code.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('searchcode.com', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, searchcode_code.response, None)
+ self.assertRaises(AttributeError, searchcode_code.response, [])
+ self.assertRaises(AttributeError, searchcode_code.response, '')
+ self.assertRaises(AttributeError, searchcode_code.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(searchcode_code.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(searchcode_code.response(response), [])
+
+ json = """
+ {
+ "matchterm": "test",
+ "previouspage": null,
+ "searchterm": "test",
+ "query": "test",
+ "total": 1000,
+ "page": 0,
+ "nextpage": 1,
+ "results": [
+ {
+ "repo": "https://repo",
+ "linescount": 1044,
+ "location": "/tests",
+ "name": "Name",
+ "url": "https://url",
+ "md5hash": "ecac6e479edd2b9406c9e08603cec655",
+ "lines": {
+ "1": "// Test 011",
+ "2": "// Source: "
+ },
+ "id": 51223527,
+ "filename": "File.CPP"
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = searchcode_code.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Name - File.CPP')
+ self.assertEqual(results[0]['url'], 'https://url')
+ self.assertEqual(results[0]['repository'], 'https://repo')
+ self.assertEqual(results[0]['code_language'], 'cpp')
+
+ json = r"""
+ {"toto":[
+ {"id":200,"name":"Artist Name",
+ "link":"http:\/\/www.searchcode_code.com\/artist\/1217","type":"artist"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = searchcode_code.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_searchcode_doc.py b/tests/unit/engines/test_searchcode_doc.py
new file mode 100644
index 0000000..d02bb7a
--- /dev/null
+++ b/tests/unit/engines/test_searchcode_doc.py
@@ -0,0 +1,70 @@
+from collections import defaultdict
+import mock
+from searx.engines import searchcode_doc
+from searx.testing import SearxTestCase
+
+
+class TestSearchcodeDocEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = searchcode_doc.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('searchcode.com', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, searchcode_doc.response, None)
+ self.assertRaises(AttributeError, searchcode_doc.response, [])
+ self.assertRaises(AttributeError, searchcode_doc.response, '')
+ self.assertRaises(AttributeError, searchcode_doc.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(searchcode_doc.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(searchcode_doc.response(response), [])
+
+ json = """
+ {
+ "matchterm": "test",
+ "previouspage": null,
+ "searchterm": "test",
+ "query": "test",
+ "total": 60,
+ "page": 0,
+ "nextpage": 1,
+ "results": [
+ {
+ "synopsis": "Synopsis",
+ "displayname": null,
+ "name": "test",
+ "url": "http://url",
+ "type": "Type",
+ "icon": null,
+ "namespace": "Namespace",
+ "description": "Description"
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = searchcode_doc.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], '[Type] Namespace test')
+ self.assertEqual(results[0]['url'], 'http://url')
+ self.assertIn('Description', results[0]['content'])
+
+ json = r"""
+ {"toto":[
+ {"id":200,"name":"Artist Name",
+ "link":"http:\/\/www.searchcode_doc.com\/artist\/1217","type":"artist"}
+ ]}
+ """
+ response = mock.Mock(text=json)
+ results = searchcode_doc.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_seedpeer.py b/tests/unit/engines/test_seedpeer.py
new file mode 100644
index 0000000..37b2de8
--- /dev/null
+++ b/tests/unit/engines/test_seedpeer.py
@@ -0,0 +1,51 @@
+import mock
+from collections import defaultdict
+from searx.engines import seedpeer
+from searx.testing import SearxTestCase
+from datetime import datetime
+
+
+class TestSeedPeerEngine(SearxTestCase):
+
+ html = ''
+ with open('./tests/unit/engines/seedpeer_fixture.html') as fixture:
+ html += fixture.read()
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ params = seedpeer.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('seedpeer.eu', params['url'])
+
+ def test_response_raises_attr_error_on_empty_response(self):
+ self.assertRaises(AttributeError, seedpeer.response, None)
+ self.assertRaises(AttributeError, seedpeer.response, [])
+ self.assertRaises(AttributeError, seedpeer.response, '')
+ self.assertRaises(AttributeError, seedpeer.response, '[]')
+
+ def test_response_returns_empty_list(self):
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(seedpeer.response(response), [])
+
+ def test_response_returns_all_results(self):
+ response = mock.Mock(text=self.html)
+ results = seedpeer.response(response)
+ self.assertTrue(isinstance(results, list))
+ self.assertEqual(len(results), 2)
+
+ def test_response_returns_correct_results(self):
+ response = mock.Mock(text=self.html)
+ results = seedpeer.response(response)
+ self.assertEqual(
+ results[0]['title'], 'Narcos - Season 2 - 720p WEBRiP - x265 HEVC - ShAaNiG '
+ )
+ self.assertEqual(
+ results[0]['url'],
+ 'http://www.seedpeer.eu/details/11685972/Narcos---Season-2---720p-WEBRiP---x265-HEVC---ShAaNiG.html'
+ )
+ self.assertEqual(results[0]['content'], '2.48 GB, 1 day')
+ self.assertEqual(results[0]['seed'], '861')
+ self.assertEqual(results[0]['leech'], '332')
diff --git a/tests/unit/engines/test_soundcloud.py b/tests/unit/engines/test_soundcloud.py
new file mode 100644
index 0000000..3077d3b
--- /dev/null
+++ b/tests/unit/engines/test_soundcloud.py
@@ -0,0 +1,192 @@
+from collections import defaultdict
+import mock
+from searx.engines import soundcloud
+from searx.testing import SearxTestCase
+from searx.url_utils import quote_plus
+
+
+class TestSoundcloudEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ params = soundcloud.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('soundcloud.com', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, soundcloud.response, None)
+ self.assertRaises(AttributeError, soundcloud.response, [])
+ self.assertRaises(AttributeError, soundcloud.response, '')
+ self.assertRaises(AttributeError, soundcloud.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(soundcloud.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(soundcloud.response(response), [])
+
+ json = """
+ {
+ "collection": [
+ {
+ "kind": "track",
+ "id": 159723640,
+ "created_at": "2014/07/22 00:51:21 +0000",
+ "user_id": 2976616,
+ "duration": 303780,
+ "commentable": true,
+ "state": "finished",
+ "original_content_size": 13236349,
+ "last_modified": "2015/01/31 15:14:50 +0000",
+ "sharing": "public",
+ "tag_list": "seekae flume",
+ "permalink": "seekae-test-recognise-flume-re-work",
+ "streamable": true,
+ "embeddable_by": "all",
+ "downloadable": true,
+ "purchase_url": "http://www.facebook.com/seekaemusic",
+ "label_id": null,
+ "purchase_title": "Seekae",
+ "genre": "freedownload",
+ "title": "This is the title",
+ "description": "This is the content",
+ "label_name": "Future Classic",
+ "release": "",
+ "track_type": "remix",
+ "key_signature": "",
+ "isrc": "",
+ "video_url": null,
+ "bpm": null,
+ "release_year": 2014,
+ "release_month": 7,
+ "release_day": 22,
+ "original_format": "mp3",
+ "license": "all-rights-reserved",
+ "uri": "https://api.soundcloud.com/tracks/159723640",
+ "user": {
+ "id": 2976616,
+ "kind": "user",
+ "permalink": "flume",
+ "username": "Flume",
+ "last_modified": "2014/11/24 19:21:29 +0000",
+ "uri": "https://api.soundcloud.com/users/2976616",
+ "permalink_url": "http://soundcloud.com/flume",
+ "avatar_url": "https://i1.sndcdn.com/avatars-000044475439-4zi7ii-large.jpg"
+ },
+ "permalink_url": "http://soundcloud.com/this.is.the.url",
+ "artwork_url": "https://i1.sndcdn.com/artworks-000085857162-xdxy5c-large.jpg",
+ "waveform_url": "https://w1.sndcdn.com/DWrL1lAN8BkP_m.png",
+ "stream_url": "https://api.soundcloud.com/tracks/159723640/stream",
+ "download_url": "https://api.soundcloud.com/tracks/159723640/download",
+ "playback_count": 2190687,
+ "download_count": 54856,
+ "favoritings_count": 49061,
+ "comment_count": 826,
+ "likes_count": 49061,
+ "reposts_count": 15910,
+ "attachments_uri": "https://api.soundcloud.com/tracks/159723640/attachments",
+ "policy": "ALLOW"
+ }
+ ],
+ "total_results": 375750,
+ "next_href": "https://api.soundcloud.com/search?&q=test",
+ "tx_id": ""
+ }
+ """
+ response = mock.Mock(text=json)
+ results = soundcloud.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'http://soundcloud.com/this.is.the.url')
+ self.assertEqual(results[0]['content'], 'This is the content')
+ self.assertIn(quote_plus('https://api.soundcloud.com/tracks/159723640'), results[0]['embedded'])
+
+ json = """
+ {
+ "collection": [
+ {
+ "kind": "user",
+ "id": 159723640,
+ "created_at": "2014/07/22 00:51:21 +0000",
+ "user_id": 2976616,
+ "duration": 303780,
+ "commentable": true,
+ "state": "finished",
+ "original_content_size": 13236349,
+ "last_modified": "2015/01/31 15:14:50 +0000",
+ "sharing": "public",
+ "tag_list": "seekae flume",
+ "permalink": "seekae-test-recognise-flume-re-work",
+ "streamable": true,
+ "embeddable_by": "all",
+ "downloadable": true,
+ "purchase_url": "http://www.facebook.com/seekaemusic",
+ "label_id": null,
+ "purchase_title": "Seekae",
+ "genre": "freedownload",
+ "title": "This is the title",
+ "description": "This is the content",
+ "label_name": "Future Classic",
+ "release": "",
+ "track_type": "remix",
+ "key_signature": "",
+ "isrc": "",
+ "video_url": null,
+ "bpm": null,
+ "release_year": 2014,
+ "release_month": 7,
+ "release_day": 22,
+ "original_format": "mp3",
+ "license": "all-rights-reserved",
+ "uri": "https://api.soundcloud.com/tracks/159723640",
+ "user": {
+ "id": 2976616,
+ "kind": "user",
+ "permalink": "flume",
+ "username": "Flume",
+ "last_modified": "2014/11/24 19:21:29 +0000",
+ "uri": "https://api.soundcloud.com/users/2976616",
+ "permalink_url": "http://soundcloud.com/flume",
+ "avatar_url": "https://i1.sndcdn.com/avatars-000044475439-4zi7ii-large.jpg"
+ },
+ "permalink_url": "http://soundcloud.com/this.is.the.url",
+ "artwork_url": "https://i1.sndcdn.com/artworks-000085857162-xdxy5c-large.jpg",
+ "waveform_url": "https://w1.sndcdn.com/DWrL1lAN8BkP_m.png",
+ "stream_url": "https://api.soundcloud.com/tracks/159723640/stream",
+ "download_url": "https://api.soundcloud.com/tracks/159723640/download",
+ "playback_count": 2190687,
+ "download_count": 54856,
+ "favoritings_count": 49061,
+ "comment_count": 826,
+ "likes_count": 49061,
+ "reposts_count": 15910,
+ "attachments_uri": "https://api.soundcloud.com/tracks/159723640/attachments",
+ "policy": "ALLOW"
+ }
+ ],
+ "total_results": 375750,
+ "next_href": "https://api.soundcloud.com/search?&q=test",
+ "tx_id": ""
+ }
+ """
+ response = mock.Mock(text=json)
+ results = soundcloud.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {
+ "collection": [],
+ "total_results": 375750,
+ "next_href": "https://api.soundcloud.com/search?&q=test",
+ "tx_id": ""
+ }
+ """
+ response = mock.Mock(text=json)
+ results = soundcloud.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_spotify.py b/tests/unit/engines/test_spotify.py
new file mode 100644
index 0000000..e37c344
--- /dev/null
+++ b/tests/unit/engines/test_spotify.py
@@ -0,0 +1,124 @@
+from collections import defaultdict
+import mock
+from searx.engines import spotify
+from searx.testing import SearxTestCase
+
+
+class TestSpotifyEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = spotify.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('spotify.com', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, spotify.response, None)
+ self.assertRaises(AttributeError, spotify.response, [])
+ self.assertRaises(AttributeError, spotify.response, '')
+ self.assertRaises(AttributeError, spotify.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(spotify.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(spotify.response(response), [])
+
+ json = """
+ {
+ "tracks": {
+ "href": "https://api.spotify.com/v1/search?query=nosfell&offset=0&limit=20&type=track",
+ "items": [
+ {
+ "album": {
+ "album_type": "album",
+ "external_urls": {
+ "spotify": "https://open.spotify.com/album/5c9ap1PBkSGLxT3J73toxA"
+ },
+ "href": "https://api.spotify.com/v1/albums/5c9ap1PBkSGLxT3J73toxA",
+ "id": "5c9ap1PBkSGLxT3J73toxA",
+ "name": "Album Title",
+ "type": "album",
+ "uri": "spotify:album:5c9ap1PBkSGLxT3J73toxA"
+ },
+ "artists": [
+ {
+ "external_urls": {
+ "spotify": "https://open.spotify.com/artist/0bMc6b75FfZEpQHG1jifKu"
+ },
+ "href": "https://api.spotify.com/v1/artists/0bMc6b75FfZEpQHG1jifKu",
+ "id": "0bMc6b75FfZEpQHG1jifKu",
+ "name": "Artist Name",
+ "type": "artist",
+ "uri": "spotify:artist:0bMc6b75FfZEpQHG1jifKu"
+ }
+ ],
+ "disc_number": 1,
+ "duration_ms": 202386,
+ "explicit": false,
+ "external_ids": {
+ "isrc": "FRV640600067"
+ },
+ "external_urls": {
+ "spotify": "https://open.spotify.com/track/2GzvFiedqW8hgqUpWcASZa"
+ },
+ "href": "https://api.spotify.com/v1/tracks/2GzvFiedqW8hgqUpWcASZa",
+ "id": "1000",
+ "is_playable": true,
+ "name": "Title of track",
+ "popularity": 6,
+ "preview_url": "https://p.scdn.co/mp3-preview/7b8ecda580965a066b768c2647f877e43f7b1a0a",
+ "track_number": 3,
+ "type": "track",
+ "uri": "spotify:track:2GzvFiedqW8hgqUpWcASZa"
+ }
+ ],
+ "limit": 20,
+ "next": "https://api.spotify.com/v1/search?query=nosfell&offset=20&limit=20&type=track",
+ "offset": 0,
+ "previous": null,
+ "total": 107
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = spotify.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title of track')
+ self.assertEqual(results[0]['url'], 'https://open.spotify.com/track/2GzvFiedqW8hgqUpWcASZa')
+ self.assertEqual(results[0]['content'], 'Artist Name - Album Title - Title of track')
+ self.assertIn('1000', results[0]['embedded'])
+
+ json = """
+ {
+ "tracks": {
+ "href": "https://api.spotify.com/v1/search?query=nosfell&offset=0&limit=20&type=track",
+ "items": [
+ {
+ "href": "https://api.spotify.com/v1/tracks/2GzvFiedqW8hgqUpWcASZa",
+ "id": "1000",
+ "is_playable": true,
+ "name": "Title of track",
+ "popularity": 6,
+ "preview_url": "https://p.scdn.co/mp3-preview/7b8ecda580965a066b768c2647f877e43f7b1a0a",
+ "track_number": 3,
+ "type": "album",
+ "uri": "spotify:track:2GzvFiedqW8hgqUpWcASZa"
+ }
+ ],
+ "limit": 20,
+ "next": "https://api.spotify.com/v1/search?query=nosfell&offset=20&limit=20&type=track",
+ "offset": 0,
+ "previous": null,
+ "total": 107
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = spotify.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_stackoverflow.py b/tests/unit/engines/test_stackoverflow.py
new file mode 100644
index 0000000..18a1ff4
--- /dev/null
+++ b/tests/unit/engines/test_stackoverflow.py
@@ -0,0 +1,106 @@
+from collections import defaultdict
+import mock
+from searx.engines import stackoverflow
+from searx.testing import SearxTestCase
+
+
+class TestStackoverflowEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = stackoverflow.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('stackoverflow.com' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, stackoverflow.response, None)
+ self.assertRaises(AttributeError, stackoverflow.response, [])
+ self.assertRaises(AttributeError, stackoverflow.response, '')
+ self.assertRaises(AttributeError, stackoverflow.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(stackoverflow.response(response), [])
+
+ html = """
+ <div class="question-summary search-result" id="answer-id-1783426">
+ <div class="statscontainer">
+ <div class="statsarrow"></div>
+ <div class="stats">
+ <div class="vote">
+ <div class="votes answered">
+ <span class="vote-count-post "><strong>2583</strong></span>
+ <div class="viewcount">votes</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="summary">
+ <div class="result-link">
+ <span>
+ <a href="/questions/this.is.the.url"
+ data-searchsession="/questions"
+ title="Checkout remote Git branch">
+ This is the title
+ </a>
+ </span>
+ </div>
+ <div class="excerpt">
+ This is the content
+ </div>
+ <div class="tags user-tags t-git t-git-checkout t-remote-branch">
+ </div>
+ <div class="started fr">
+ answered <span title="2009-11-23 14:26:08Z" class="relativetime">nov 23 '09</span> by
+ <a href="/users/214090/hallski">hallski</a>
+ </div>
+ </div>
+ </div>
+ """
+ response = mock.Mock(text=html)
+ results = stackoverflow.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://stackoverflow.com/questions/this.is.the.url')
+ self.assertEqual(results[0]['content'], 'This is the content')
+
+ html = """
+ <div class="statscontainer">
+ <div class="statsarrow"></div>
+ <div class="stats">
+ <div class="vote">
+ <div class="votes answered">
+ <span class="vote-count-post "><strong>2583</strong></span>
+ <div class="viewcount">votes</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="summary">
+ <div class="result-link">
+ <span>
+ <a href="/questions/this.is.the.url"
+ data-searchsession="/questions"
+ title="Checkout remote Git branch">
+ This is the title
+ </a>
+ </span>
+ </div>
+ <div class="excerpt">
+ This is the content
+ </div>
+ <div class="tags user-tags t-git t-git-checkout t-remote-branch">
+ </div>
+ <div class="started fr">
+ answered <span title="2009-11-23 14:26:08Z" class="relativetime">nov 23 '09</span> by
+ <a href="/users/214090/hallski">hallski</a>
+ </div>
+ </div>
+ """
+ response = mock.Mock(text=html)
+ results = stackoverflow.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_startpage.py b/tests/unit/engines/test_startpage.py
new file mode 100644
index 0000000..a7a9778
--- /dev/null
+++ b/tests/unit/engines/test_startpage.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import startpage
+from searx.testing import SearxTestCase
+
+
+class TestStartpageEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr_FR'
+ params = startpage.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn('startpage.com', params['url'])
+ self.assertIn('data', params)
+ self.assertIn('query', params['data'])
+ self.assertIn(query, params['data']['query'])
+ self.assertIn('with_language', params['data'])
+ self.assertIn('lang_fr', params['data']['with_language'])
+
+ dicto['language'] = 'all'
+ params = startpage.request(query, dicto)
+ self.assertNotIn('with_language', params['data'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, startpage.response, None)
+ self.assertRaises(AttributeError, startpage.response, [])
+ self.assertRaises(AttributeError, startpage.response, '')
+ self.assertRaises(AttributeError, startpage.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(startpage.response(response), [])
+
+ html = """
+ <div class='result' style=' *width : auto; *margin-right : 10%;'>
+ <h3>
+ <a href='http://this.should.be.the.link/' id='title_2' name='title_2' >
+ This should be the title
+ </a>
+ <span id='title_stars_2' name='title_stars_2'> </span>
+ </h3>
+ <p class='desc clk'>
+ This should be the content.
+ </p>
+ <p>
+ <span class='url'>www.speed<b>test</b>.net/fr/
+ </span>
+ -
+ <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
+ class='proxy'>
+ Navigation avec Ixquick Proxy
+ </A>
+ -
+ <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
+ &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
+ &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
+ Mis en surbrillance
+ </A>
+ </p>
+ </div>
+ """
+ response = mock.Mock(text=html.encode('utf-8'))
+ results = startpage.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This should be the title')
+ self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
+ self.assertEqual(results[0]['content'], 'This should be the content.')
+
+ html = """
+ <div class='result' style=' *width : auto; *margin-right : 10%;'>
+ <h3>
+ <a href='http://www.google.com/aclk?sa=l&ai=C' id='title_2' name='title_2' >
+ This should be the title
+ </a>
+ <span id='title_stars_2' name='title_stars_2'> </span>
+ </h3>
+ <p class='desc clk'>
+ This should be the content.
+ </p>
+ <p>
+ <span class='url'>www.speed<b>test</b>.net/fr/
+ </span>
+ -
+ <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
+ class='proxy'>
+ Navigation avec Ixquick Proxy
+ </A>
+ -
+ <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
+ &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
+ &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
+ Mis en surbrillance
+ </A>
+ </p>
+ </div>
+ <div class='result' style=' *width : auto; *margin-right : 10%;'>
+ <h3>
+ <span id='title_stars_2' name='title_stars_2'> </span>
+ </h3>
+ <p class='desc clk'>
+ This should be the content.
+ </p>
+ <p>
+ <span class='url'>www.speed<b>test</b>.net/fr/
+ </span>
+ </p>
+ </div>
+ <div class='result' style=' *width : auto; *margin-right : 10%;'>
+ <h3>
+ <a href='http://this.should.be.the.link/' id='title_2' name='title_2' >
+ This should be the title
+ </a>
+ <span id='title_stars_2' name='title_stars_2'> </span>
+ </h3>
+ <p>
+ <span class='url'>www.speed<b>test</b>.net/fr/
+ </span>
+ -
+ <A class="proxy" id="proxy_link" HREF="https://ixquick-proxy.com/do/spg/proxy?ep=&edata=&ek=&ekdata="
+ class='proxy'>
+ Navigation avec Ixquick Proxy
+ </A>
+ -
+ <A HREF="https://ixquick-proxy.com/do/spg/highlight.pl?l=francais&c=hf&cat=web&q=test&rl=NONE&rid=
+ &hlq=https://startpage.com/do/search&mtabp=-1&mtcmd=process_search&mtlanguage=francais&mtengine0=
+ &mtcat=web&u=http:%2F%2Fwww.speedtest.net%2Ffr%2F" class='proxy'>
+ Mis en surbrillance
+ </A>
+ </p>
+ </div>
+ """
+ response = mock.Mock(text=html.encode('utf-8'))
+ results = startpage.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['content'], '')
diff --git a/tests/unit/engines/test_subtitleseeker.py b/tests/unit/engines/test_subtitleseeker.py
new file mode 100644
index 0000000..a22ee74
--- /dev/null
+++ b/tests/unit/engines/test_subtitleseeker.py
@@ -0,0 +1,174 @@
+from collections import defaultdict
+import mock
+from searx.engines import subtitleseeker
+from searx.testing import SearxTestCase
+
+
+class TestSubtitleseekerEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr-FR'
+ params = subtitleseeker.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('subtitleseeker.com' in params['url'])
+
+ def test_response(self):
+ dicto = defaultdict(dict)
+ dicto['language'] = 'fr-FR'
+ response = mock.Mock(search_params=dicto)
+
+ self.assertRaises(AttributeError, subtitleseeker.response, None)
+ self.assertRaises(AttributeError, subtitleseeker.response, [])
+ self.assertRaises(AttributeError, subtitleseeker.response, '')
+ self.assertRaises(AttributeError, subtitleseeker.response, '[]')
+
+ response = mock.Mock(text='<html></html>', search_params=dicto)
+ self.assertEqual(subtitleseeker.response(response), [])
+
+ html = """
+ <div class="boxRows">
+ <div class="boxRowsInner" style="width:600px;">
+ <img src="http://static.subtitleseeker.com/images/movie.gif"
+ style="width:16px; height:16px;" class="icon">
+ <a href="http://this.is.the.url/"
+ class="blue" title="Title subtitle" >
+ This is the Title
+ </a>
+ <br><br>
+ <span class="f10b grey-dark arial" style="padding:0px 0px 5px 20px">
+ "Alternative Title"
+ </span>
+ </div>
+ <div class="boxRowsInner f12b red" style="width:70px;">
+ 1998
+ </div>
+ <div class="boxRowsInner grey-web f12" style="width:120px;">
+ <img src="http://static.subtitleseeker.com/images/basket_put.png"
+ style="width:16px; height:16px;" class="icon">
+ 1039 Subs
+ </div>
+ <div class="boxRowsInner grey-web f10" style="width:130px;">
+ <img src="http://static.subtitleseeker.com/images/arrow_refresh_small.png"
+ style="width:16px; height:16px;" class="icon">
+ 1 hours ago
+ </div>
+ <div class="clear"></div>
+ </div>
+ """
+ response = mock.Mock(text=html, search_params=dicto)
+ results = subtitleseeker.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the Title')
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url/French/')
+ self.assertIn('1998', results[0]['content'])
+ self.assertIn('1039 Subs', results[0]['content'])
+ self.assertIn('Alternative Title', results[0]['content'])
+
+ dicto['language'] = 'pt-BR'
+ results = subtitleseeker.response(response)
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url/Brazilian/')
+
+ html = """
+ <div class="boxRows">
+ <div class="boxRowsInner" style="width:600px;">
+ <img src="http://static.subtitleseeker.com/images/movie.gif"
+ style="width:16px; height:16px;" class="icon">
+ <a href="http://this.is.the.url/"
+ class="blue" title="Title subtitle" >
+ This is the Title
+ </a>
+ </div>
+ <div class="boxRowsInner f12b red" style="width:70px;">
+ 1998
+ </div>
+ <div class="boxRowsInner grey-web f12" style="width:120px;">
+ <img src="http://static.subtitleseeker.com/images/basket_put.png"
+ style="width:16px; height:16px;" class="icon">
+ 1039 Subs
+ </div>
+ <div class="boxRowsInner grey-web f10" style="width:130px;">
+ <img src="http://static.subtitleseeker.com/images/arrow_refresh_small.png"
+ style="width:16px; height:16px;" class="icon">
+ 1 hours ago
+ </div>
+ <div class="clear"></div>
+ </div>
+ """
+ dicto['language'] = 'all'
+ response = mock.Mock(text=html, search_params=dicto)
+ results = subtitleseeker.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the Title')
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
+ self.assertIn('1998', results[0]['content'])
+ self.assertIn('1039 Subs', results[0]['content'])
+
+ html = """
+ <div class="boxRows">
+ <div class="boxRowsInner" style="width:600px;">
+ <img src="http://static.subtitleseeker.com/images/movie.gif"
+ style="width:16px; height:16px;" class="icon">
+ <a href="http://this.is.the.url/"
+ class="blue" title="Title subtitle" >
+ This is the Title
+ </a>
+ </div>
+ <div class="boxRowsInner f12b red" style="width:70px;">
+ 1998
+ </div>
+ <div class="boxRowsInner grey-web f12" style="width:120px;">
+ <img src="http://static.subtitleseeker.com/images/basket_put.png"
+ style="width:16px; height:16px;" class="icon">
+ 1039 Subs
+ </div>
+ <div class="boxRowsInner grey-web f10" style="width:130px;">
+ <img src="http://static.subtitleseeker.com/images/arrow_refresh_small.png"
+ style="width:16px; height:16px;" class="icon">
+ 1 hours ago
+ </div>
+ <div class="clear"></div>
+ </div>
+ """
+ subtitleseeker.language = 'English'
+ response = mock.Mock(text=html, search_params=dicto)
+ results = subtitleseeker.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the Title')
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url/English/')
+ self.assertIn('1998', results[0]['content'])
+ self.assertIn('1039 Subs', results[0]['content'])
+
+ html = """
+ <div class="boxRowsInner" style="width:600px;">
+ <img src="http://static.subtitleseeker.com/images/movie.gif"
+ style="width:16px; height:16px;" class="icon">
+ <a href="http://this.is.the.url/"
+ class="blue" title="Title subtitle" >
+ This is the Title
+ </a>
+ </div>
+ <div class="boxRowsInner f12b red" style="width:70px;">
+ 1998
+ </div>
+ <div class="boxRowsInner grey-web f12" style="width:120px;">
+ <img src="http://static.subtitleseeker.com/images/basket_put.png"
+ style="width:16px; height:16px;" class="icon">
+ 1039 Subs
+ </div>
+ <div class="boxRowsInner grey-web f10" style="width:130px;">
+ <img src="http://static.subtitleseeker.com/images/arrow_refresh_small.png"
+ style="width:16px; height:16px;" class="icon">
+ 1 hours ago
+ </div>
+ """
+ response = mock.Mock(text=html, search_params=dicto)
+ results = subtitleseeker.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_swisscows.py b/tests/unit/engines/test_swisscows.py
new file mode 100644
index 0000000..53890be
--- /dev/null
+++ b/tests/unit/engines/test_swisscows.py
@@ -0,0 +1,155 @@
+from collections import defaultdict
+import mock
+from searx.engines import swisscows
+from searx.testing import SearxTestCase
+
+
+class TestSwisscowsEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'de-DE'
+ params = swisscows.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('swisscows.ch' in params['url'])
+ self.assertTrue('uiLanguage=de' in params['url'])
+ self.assertTrue('region=de-DE' in params['url'])
+
+ dicto['language'] = 'all'
+ params = swisscows.request(query, dicto)
+ self.assertTrue('uiLanguage=browser' in params['url'])
+ self.assertTrue('region=browser' in params['url'])
+
+ dicto['category'] = 'images'
+ params = swisscows.request(query, dicto)
+ self.assertIn('image', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, swisscows.response, None)
+ self.assertRaises(AttributeError, swisscows.response, [])
+ self.assertRaises(AttributeError, swisscows.response, '')
+ self.assertRaises(AttributeError, swisscows.response, '[]')
+
+ response = mock.Mock(text=b'<html></html>')
+ self.assertEqual(swisscows.response(response), [])
+
+ response = mock.Mock(text=b'<html></html>')
+ self.assertEqual(swisscows.response(response), [])
+
+ html = b"""
+ <script>
+ App.Dispatcher.dispatch("initialize", {
+ html5history: true,
+ initialData: {"Request":
+ {"Page":1,
+ "ItemsCount":1,
+ "Query":"This should ",
+ "NormalizedQuery":"This should ",
+ "Region":"de-AT",
+ "UILanguage":"de"},
+ "Results":{"items":[
+ {"Title":"\uE000This should\uE001 be the title",
+ "Description":"\uE000This should\uE001 be the content.",
+ "Url":"http://this.should.be.the.link/",
+ "DisplayUrl":"www.\uE000this.should.be.the\uE001.link",
+ "Id":"782ef287-e439-451c-b380-6ebc14ba033d"},
+ {"Title":"Datei:This should1.svg",
+ "Url":"https://i.swisscows.ch/?link=http%3a%2f%2fts2.mm.This/should1.png",
+ "SourceUrl":"http://de.wikipedia.org/wiki/Datei:This should1.svg",
+ "DisplayUrl":"de.wikipedia.org/wiki/Datei:This should1.svg",
+ "Width":950,
+ "Height":534,
+ "FileSize":92100,
+ "ContentType":"image/jpeg",
+ "Thumbnail":{
+ "Url":"https://i.swisscows.ch/?link=http%3a%2f%2fts2.mm.This/should1.png",
+ "ContentType":"image/jpeg",
+ "Width":300,
+ "Height":168,
+ "FileSize":9134},
+ "Id":"6a97a542-8f65-425f-b7f6-1178c3aba7be"
+ }
+ ],"TotalCount":55300,
+ "Query":"This should "
+ },
+ "Images":[{"Title":"Datei:This should.svg",
+ "Url":"https://i.swisscows.ch/?link=http%3a%2f%2fts2.mm.This/should.png",
+ "SourceUrl":"http://de.wikipedia.org/wiki/Datei:This should.svg",
+ "DisplayUrl":"de.wikipedia.org/wiki/Datei:This should.svg",
+ "Width":1280,
+ "Height":677,
+ "FileSize":50053,
+ "ContentType":"image/png",
+ "Thumbnail":{"Url":"https://i.swisscows.ch/?link=http%3a%2f%2fts2.mm.This/should.png",
+ "ContentType":"image/png",
+ "Width":300,
+ "Height":158,
+ "FileSize":8023},
+ "Id":"ae230fd8-a06a-47d6-99d5-e74766d8143a"}]},
+ environment: "production"
+ }).then(function (options) {
+ $('#Search_Form').on('submit', function (e) {
+ if (!Modernizr.history) return;
+ e.preventDefault();
+
+ var $form = $(this),
+ $query = $('#Query'),
+ query = $.trim($query.val()),
+ path = App.Router.makePath($form.attr('action'), null, $form.serializeObject())
+
+ if (query.length) {
+ options.html5history ?
+ ReactRouter.HistoryLocation.push(path) :
+ ReactRouter.RefreshLocation.push(path);
+ }
+ else $('#Query').trigger('blur');
+ });
+
+ });
+ </script>
+ """
+ response = mock.Mock(text=html)
+ results = swisscows.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 3)
+ self.assertEqual(results[0]['title'], 'This should be the title')
+ self.assertEqual(results[0]['url'], 'http://this.should.be.the.link/')
+ self.assertEqual(results[0]['content'], 'This should be the content.')
+ self.assertEqual(results[1]['title'], 'Datei:This should1.svg')
+ self.assertEqual(results[1]['url'], 'http://de.wikipedia.org/wiki/Datei:This should1.svg')
+ self.assertEqual(results[1]['img_src'], 'http://ts2.mm.This/should1.png')
+ self.assertEqual(results[1]['template'], 'images.html')
+ self.assertEqual(results[2]['title'], 'Datei:This should.svg')
+ self.assertEqual(results[2]['url'], 'http://de.wikipedia.org/wiki/Datei:This should.svg')
+ self.assertEqual(results[2]['img_src'], 'http://ts2.mm.This/should.png')
+ self.assertEqual(results[2]['template'], 'images.html')
+
+ def test_fetch_supported_languages(self):
+ html = """<html></html>"""
+ response = mock.Mock(text=html)
+ languages = swisscows._fetch_supported_languages(response)
+ self.assertEqual(type(languages), list)
+ self.assertEqual(len(languages), 0)
+
+ html = """
+ <html>
+ <div id="regions-popup">
+ <div>
+ <ul>
+ <li><a data-val="browser"></a></li>
+ <li><a data-val="de-CH"></a></li>
+ <li><a data-val="fr-CH"></a></li>
+ </ul>
+ </div>
+ </div>
+ </html>
+ """
+ response = mock.Mock(text=html)
+ languages = swisscows._fetch_supported_languages(response)
+ self.assertEqual(type(languages), list)
+ self.assertEqual(len(languages), 3)
+ self.assertIn('de-CH', languages)
+ self.assertIn('fr-CH', languages)
diff --git a/tests/unit/engines/test_tokyotoshokan.py b/tests/unit/engines/test_tokyotoshokan.py
new file mode 100644
index 0000000..b5c6fad
--- /dev/null
+++ b/tests/unit/engines/test_tokyotoshokan.py
@@ -0,0 +1,110 @@
+import mock
+from collections import defaultdict
+from searx.engines import tokyotoshokan
+from searx.testing import SearxTestCase
+from datetime import datetime
+
+
+class TestTokyotoshokanEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dic = defaultdict(dict)
+ dic['pageno'] = 1
+ params = tokyotoshokan.request(query, dic)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('tokyotosho.info' in params['url'])
+
+ def test_response(self):
+ resp = mock.Mock(text='<html></html>')
+ self.assertEqual(tokyotoshokan.response(resp), [])
+
+ html = """
+ <table class="listing">
+ <tbody>
+ <tr class="shade category_0">
+ <td rowspan="2">
+ <a href="/?cat=7"><span class="sprite_cat-raw"></span></a>
+ </td>
+ <td class="desc-top">
+ <a href="magnet:?xt=urn:btih:4c19eb46b5113685fbd2288ed2531b0b">
+ <span class="sprite_magnet"></span>
+ </a>
+ <a rel="nofollow" type="application/x-bittorrent" href="http://www.nyaa.se/f">
+ Koyomimonogatari
+ </a>
+ </td>
+ <td class="web"><a rel="nofollow" href="details.php?id=975700">Details</a></td>
+ </tr>
+ <tr class="shade category_0">
+ <td class="desc-bot">
+ Authorized: <span class="auth_ok">Yes</span>
+ Submitter: <a href="?username=Ohys">Ohys</a> |
+ Size: 10.5MB |
+ Date: 2016-03-26 16:41 UTC |
+ Comment: sample comment
+ </td>
+ <td style="color: #BBB; font-family: monospace" class="stats" align="right">
+ S: <span style="color: red">53</span>
+ L: <span style="color: red">18</span>
+ C: <span style="color: red">0</span>
+ ID: 975700
+ </td>
+ </tr>
+
+ <tr class="category_0">
+ <td rowspan="2">
+ <a href="/?cat=7"><span class="sprite_cat-raw"></span></a>
+ </td>
+ <td class="desc-top">
+ <a rel="nofollow" type="application/x-bittorrent" href="http://google.com/q">
+ Owarimonogatari
+ </a>
+ </td>
+ <td class="web"><a rel="nofollow" href="details.php?id=975700">Details</a></td>
+ </tr>
+ <tr class="category_0">
+ <td class="desc-bot">
+ Submitter: <a href="?username=Ohys">Ohys</a> |
+ Size: 932.84EB |
+ Date: QWERTY-03-26 16:41 UTC
+ </td>
+ <td style="color: #BBB; font-family: monospace" class="stats" align="right">
+ S: <span style="color: red">0</span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ """
+
+ resp = mock.Mock(text=html)
+ results = tokyotoshokan.response(resp)
+
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+
+ # testing the first result, which has correct format
+ # and should have all information fields filled
+ r = results[0]
+ self.assertEqual(r['url'], 'http://www.nyaa.se/f')
+ self.assertEqual(r['title'], 'Koyomimonogatari')
+ self.assertEqual(r['magnetlink'], 'magnet:?xt=urn:btih:4c19eb46b5113685fbd2288ed2531b0b')
+ self.assertEqual(r['filesize'], int(1024 * 1024 * 10.5))
+ self.assertEqual(r['publishedDate'], datetime(2016, 3, 26, 16, 41))
+ self.assertEqual(r['content'], 'Comment: sample comment')
+ self.assertEqual(r['seed'], 53)
+ self.assertEqual(r['leech'], 18)
+
+ # testing the second result, which does not include magnet link,
+ # seed & leech info, and has incorrect size & creation date
+ r = results[1]
+ self.assertEqual(r['url'], 'http://google.com/q')
+ self.assertEqual(r['title'], 'Owarimonogatari')
+
+ self.assertFalse('magnetlink' in r)
+ self.assertFalse('filesize' in r)
+ self.assertFalse('content' in r)
+ self.assertFalse('publishedDate' in r)
+ self.assertFalse('seed' in r)
+ self.assertFalse('leech' in r)
diff --git a/tests/unit/engines/test_torrentz.py b/tests/unit/engines/test_torrentz.py
new file mode 100644
index 0000000..2f836f7
--- /dev/null
+++ b/tests/unit/engines/test_torrentz.py
@@ -0,0 +1,91 @@
+import mock
+from collections import defaultdict
+from searx.engines import torrentz
+from searx.testing import SearxTestCase
+from datetime import datetime
+
+
+class TestTorrentzEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dic = defaultdict(dict)
+ dic['pageno'] = 1
+ params = torrentz.request(query, dic)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('torrentz.eu' in params['url'])
+
+ def test_response(self):
+ resp = mock.Mock(text='<html></html>')
+ self.assertEqual(torrentz.response(resp), [])
+
+ html = """
+ <div class="results">
+ <dl>
+ <dt>
+ <a href="/4362e08b1d80e1820fb2550b752f9f3126fe76d6">
+ Completely valid info
+ </a>
+ books ebooks
+ </dt>
+ <dd>
+ <span class="v">1</span>
+ <span class="a">
+ <span title="Sun, 22 Nov 2015 03:01:42">4 months</span>
+ </span>
+ <span class="s">30 MB</span>
+ <span class="u">14</span>
+ <span class="d">1</span>
+ </dd>
+ </dl>
+
+ <dl>
+ <dt>
+ <a href="/poaskdpokaspod">
+ Invalid hash and date and filesize
+ </a>
+ books ebooks
+ </dt>
+ <dd>
+ <span class="v">1</span>
+ <span class="a">
+ <span title="Sun, 2124091j0j190gm42">4 months</span>
+ </span>
+ <span class="s">30MB</span>
+ <span class="u">5,555</span>
+ <span class="d">1,234,567</span>
+ </dd>
+ </dl>
+ </div>
+ """
+
+ resp = mock.Mock(text=html)
+ results = torrentz.response(resp)
+
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+
+ # testing against the first result
+ r = results[0]
+ self.assertEqual(r['url'], 'https://torrentz.eu/4362e08b1d80e1820fb2550b752f9f3126fe76d6')
+ self.assertEqual(r['title'], 'Completely valid info books ebooks')
+ # 22 Nov 2015 03:01:42
+ self.assertEqual(r['publishedDate'], datetime(2015, 11, 22, 3, 1, 42))
+ self.assertEqual(r['seed'], 14)
+ self.assertEqual(r['leech'], 1)
+ self.assertEqual(r['filesize'], 30 * 1024 * 1024)
+ self.assertEqual(r['magnetlink'], 'magnet:?xt=urn:btih:4362e08b1d80e1820fb2550b752f9f3126fe76d6')
+
+ # testing against the second result
+ r = results[1]
+ self.assertEqual(r['url'], 'https://torrentz.eu/poaskdpokaspod')
+ self.assertEqual(r['title'], 'Invalid hash and date and filesize books ebooks')
+ self.assertEqual(r['seed'], 5555)
+ self.assertEqual(r['leech'], 1234567)
+
+ # in the second result we have invalid hash, creation date & torrent size,
+ # so these tests should fail
+ self.assertFalse('magnetlink' in r)
+ self.assertFalse('filesize' in r)
+ self.assertFalse('publishedDate' in r)
diff --git a/tests/unit/engines/test_twitter.py b/tests/unit/engines/test_twitter.py
new file mode 100644
index 0000000..b444b48
--- /dev/null
+++ b/tests/unit/engines/test_twitter.py
@@ -0,0 +1,502 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import twitter
+from searx.testing import SearxTestCase
+
+
+class TestTwitterEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['language'] = 'fr_FR'
+ params = twitter.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('twitter.com', params['url'])
+ self.assertIn('cookies', params)
+ self.assertIn('lang', params['cookies'])
+ self.assertIn('fr', params['cookies']['lang'])
+
+ dicto['language'] = 'all'
+ params = twitter.request(query, dicto)
+ self.assertIn('cookies', params)
+ self.assertIn('lang', params['cookies'])
+ self.assertIn('en', params['cookies']['lang'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, twitter.response, None)
+ self.assertRaises(AttributeError, twitter.response, [])
+ self.assertRaises(AttributeError, twitter.response, '')
+ self.assertRaises(AttributeError, twitter.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(twitter.response(response), [])
+
+ html = """
+ <li class="js-stream-item stream-item stream-item expanding-stream-item" data-item-id="563005573290287105"
+ id="stream-item-tweet-563005573290287105" data-item-type="tweet">
+ <div class="tweet original-tweet js-stream-tweet js-actionable-tweet js-profile-popup-actionable
+ js-original-tweet has-cards has-native-media" data-tweet-id="563005573290287105" data-disclosure-type=""
+ data-item-id="563005573290287105" data-screen-name="Jalopnik" data-name="Jalopnik"
+ data-user-id="3060631" data-has-native-media="true" data-has-cards="true" data-card-type="photo"
+ data-expanded-footer="&lt;div class=&quot;js-tweet-details-fixer
+ tweet-details-fixer&quot;&gt;&#10;&#10;&#10;
+ &lt;div class=&quot;cards-media-container js-media-container&quot;&gt;&lt;div
+ data-card-url=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot; data-card-type=&quot;
+ photo&quot; class=&quot;cards-base cards-multimedia&quot; data-element-context=&quot;platform_photo_card
+ &quot;&gt;&#10;&#10;&#10; &lt;a class=&quot;media media-thumbnail twitter-timeline-link is-preview
+ &quot; data-url=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
+ data-resolved-url-large=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
+ href=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot;&gt;&#10;
+ &lt;div class=&quot;&quot;&gt;&#10; &lt;img src=&quot;
+ https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg&quot;
+ alt=&quot;Embedded image permalink&quot; width=&quot;636&quot; height=&quot;309&quot;&gt;&#10;
+ &lt;/div&gt;&#10;&#10; &lt;/a&gt;&#10;&#10; &lt;div class=&quot;cards-content&quot;&gt;&#10;
+ &lt;div class=&quot;byline&quot;&gt;&#10; &#10; &lt;/div&gt;&#10; &#10; &lt;/div&gt;&#10;
+ &#10;&lt;/div&gt;&#10;&#10;&#10;&#10;&#10;&lt;/div&gt;&#10;&#10;&#10;&#10; &lt;div
+ class=&quot;js-machine-translated-tweet-container&quot;&gt;&lt;/div&gt;&#10; &lt;div
+ class=&quot;js-tweet-stats-container tweet-stats-container &quot;&gt;&#10; &lt;/div&gt;&#10;&#10;
+ &lt;div class=&quot;client-and-actions&quot;&gt;&#10; &lt;span class=&quot;metadata&quot;&gt;&#10;
+ &lt;span&gt;5:06 PM - 4 Feb 2015&lt;/span&gt;&#10;&#10; &amp;middot; &lt;a
+ class=&quot;permalink-link js-permalink js-nav&quot; href=&quot;/Jalopnik/status/563005573290287105
+ &quot;tabindex=&quot;-1&quot;&gt;Details&lt;/a&gt;&#10; &#10;&#10; &#10; &#10;
+ &#10;&#10; &lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;/div&gt;&#10;" data-you-follow="false"
+ data-you-block="false">
+ <div class="context">
+ </div>
+ <div class="content">
+ <div class="stream-item-header">
+ <a class="account-group js-account-group js-action-profile js-user-profile-link js-nav"
+ href="/Jalopnik" data-user-id="3060631">
+ <img class="avatar js-action-profile-avatar"
+ src="https://pbs.twimg.com/profile_images/2976430168/5cd4a59_bigger.jpeg" alt="">
+ <strong class="fullname js-action-profile-name show-popup-with-id" data-aria-label-part>
+ Jalopnik
+ </strong>
+ <span>&rlm;</span>
+ <span class="username js-action-profile-name" data-aria-label-part>
+ <s>@</s><b>TitleName</b>
+ </span>
+ </a>
+ <small class="time">
+ <a href="/this.is.the.url"
+ class="tweet-timestamp js-permalink js-nav js-tooltip" title="5:06 PM - 4 Feb 2015" >
+ <span class="u-hiddenVisually" data-aria-label-part="last">17 minutes ago</span>
+ </a>
+ </small>
+ </div>
+ <p class="js-tweet-text tweet-text" lang="en" data-aria-label-part="0">
+ This is the content étude à€
+ <a href="http://t.co/nRWsqQAwBL" rel="nofollow" dir="ltr"
+ data-expanded-url="http://jalo.ps/ReMENu4" class="twitter-timeline-link"
+ target="_blank" title="http://jalo.ps/ReMENu4" >
+ <span class="tco-ellipsis">
+ </span>
+ <span class="invisible">http://</span><span class="js-display-url">link.in.tweet</span>
+ <span class="invisible"></span>
+ <span class="tco-ellipsis">
+ <span class="invisible">&nbsp;</span>
+ </span>
+ </a>
+ <a href="http://t.co/rbFsfeE0l3" class="twitter-timeline-link u-hidden"
+ data-pre-embedded="true" dir="ltr">
+ pic.twitter.com/rbFsfeE0l3
+ </a>
+ </p>
+ <div class="expanded-content js-tweet-details-dropdown">
+ </div>
+ <div class="stream-item-footer">
+ <a class="details with-icn js-details" href="/Jalopnik/status/563005573290287105">
+ <span class="Icon Icon--photo">
+ </span>
+ <b>
+ <span class="expand-stream-item js-view-details">
+ View photo
+ </span>
+ <span class="collapse-stream-item js-hide-details">
+ Hide photo
+ </span>
+ </b>
+ </a>
+ <span class="ProfileTweet-action--reply u-hiddenVisually">
+ <span class="ProfileTweet-actionCount" aria-hidden="true" data-tweet-stat-count="0">
+ <span class="ProfileTweet-actionCountForAria" >0 replies</span>
+ </span>
+ </span>
+ <span class="ProfileTweet-action--retweet u-hiddenVisually">
+ <span class="ProfileTweet-actionCount" data-tweet-stat-count="8">
+ <span class="ProfileTweet-actionCountForAria" data-aria-label-part>8 retweets</span>
+ </span>
+ </span>
+ <span class="ProfileTweet-action--favorite u-hiddenVisually">
+ <span class="ProfileTweet-actionCount" data-tweet-stat-count="14">
+ <span class="ProfileTweet-actionCountForAria" data-aria-label-part>14 favorites</span>
+ </span>
+ </span>
+ <div role="group" aria-label="Tweet actions" class="ProfileTweet-actionList u-cf js-actions">
+ <div class="ProfileTweet-action ProfileTweet-action--reply">
+ <button class="ProfileTweet-actionButton u-textUserColorHover js-actionButton
+ js-actionReply" data-modal="ProfileTweet-reply" type="button" title="Reply">
+ <span class="Icon Icon--reply">
+ </span>
+ <span class="u-hiddenVisually">Reply</span>
+ <span class="ProfileTweet-actionCount u-textUserColorHover
+ ProfileTweet-actionCount--isZero">
+ <span class="ProfileTweet-actionCountForPresentation" aria-hidden="true">
+ </span>
+ </span>
+ </button>
+ </div>
+ <div class="ProfileTweet-action ProfileTweet-action--retweet js-toggleState js-toggleRt">
+ <button class="ProfileTweet-actionButton js-actionButton js-actionRetweet js-tooltip"
+ title="Retweet" data-modal="ProfileTweet-retweet" type="button">
+ <span class="Icon Icon--retweet">
+ </span>
+ <span class="u-hiddenVisually">Retweet</span>
+ <span class="ProfileTweet-actionCount">
+ <span class="ProfileTweet-actionCountForPresentation">8</span>
+ </span>
+ </button>
+ <button class="ProfileTweet-actionButtonUndo js-actionButton js-actionRetweet"
+ data-modal="ProfileTweet-retweet" title="Undo retweet" type="button">
+ <span class="Icon Icon--retweet">
+ </span>
+ <span class="u-hiddenVisually">Retweeted</span>
+ <span class="ProfileTweet-actionCount">
+ <span class="ProfileTweet-actionCountForPresentation">8</span>
+ </span>
+ </button>
+ </div>
+ <div class="ProfileTweet-action ProfileTweet-action--favorite js-toggleState">
+ <button class="ProfileTweet-actionButton js-actionButton js-actionFavorite js-tooltip"
+ title="Favorite" type="button">
+ <span class="Icon Icon--favorite">
+ </span>
+ <span class="u-hiddenVisually">Favorite</span>
+ <span class="ProfileTweet-actionCount">
+ <span class="ProfileTweet-actionCountForPresentation">14</span>
+ </span>
+ </button>
+ <button class="ProfileTweet-actionButtonUndo u-linkClean js-actionButton
+ js-actionFavorite" title="Undo favorite" type="button">
+ <span class="Icon Icon--favorite">
+ </span>
+ <span class="u-hiddenVisually">Favorited</span>
+ <span class="ProfileTweet-actionCount">
+ <span class="ProfileTweet-actionCountForPresentation">
+ 14
+ </span>
+ </span>
+ </button>
+ </div>
+ <div class="ProfileTweet-action ProfileTweet-action--more js-more-ProfileTweet-actions">
+ <div class="dropdown">
+ <button class="ProfileTweet-actionButton u-textUserColorHover dropdown-toggle
+ js-tooltip js-dropdown-toggle" type="button" title="More">
+ <span class="Icon Icon--dots">
+ </span>
+ <span class="u-hiddenVisually">More</span>
+ </button>
+ <div class="dropdown-menu">
+ <div class="dropdown-caret">
+ <div class="caret-outer">
+ </div>
+ <div class="caret-inner">
+ </div>
+ </div>
+ <ul>
+ <li class="share-via-dm js-actionShareViaDM" data-nav="share_tweet_dm">
+ <button type="button" class="dropdown-link">
+ Share via Direct Message
+ </button>
+ </li>
+ <li class="embed-link js-actionEmbedTweet" data-nav="embed_tweet">
+ <button type="button" class="dropdown-link">
+ Embed Tweet
+ </button>
+ </li>
+ <li class="mute-user-item pretty-link">
+ <button type="button" class="dropdown-link">
+ Mute
+ </button>
+ </li>
+ <li class="unmute-user-item pretty-link">
+ <button type="button" class="dropdown-link">
+ Unmute
+ </button>
+ </li>
+ <li class="block-or-report-link js-actionBlockOrReport"
+ data-nav="block_or_report">
+ <button type="button" class="dropdown-link">
+ Block or report
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ """
+ response = mock.Mock(text=html)
+ results = twitter.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], '@TitleName')
+ self.assertEqual(results[0]['url'], 'https://twitter.com/this.is.the.url')
+ self.assertIn(u'This is the content', results[0]['content'])
+ # self.assertIn(u'This is the content étude à€', results[0]['content'])
+
+ html = """
+ <li class="js-stream-item stream-item stream-item expanding-stream-item" data-item-id="563005573290287105"
+ id="stream-item-tweet-563005573290287105" data-item-type="tweet">
+ <div class="tweet original-tweet js-stream-tweet js-actionable-tweet js-profile-popup-actionable
+ js-original-tweet has-cards has-native-media" data-tweet-id="563005573290287105" data-disclosure-type=""
+ data-item-id="563005573290287105" data-screen-name="Jalopnik" data-name="Jalopnik"
+ data-user-id="3060631" data-has-native-media="true" data-has-cards="true" data-card-type="photo"
+ data-expanded-footer="&lt;div class=&quot;js-tweet-details-fixer
+ tweet-details-fixer&quot;&gt;&#10;&#10;&#10;
+ &lt;div class=&quot;cards-media-container js-media-container&quot;&gt;&lt;div
+ data-card-url=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot; data-card-type=&quot;
+ photo&quot; class=&quot;cards-base cards-multimedia&quot; data-element-context=&quot;platform_photo_card
+ &quot;&gt;&#10;&#10;&#10; &lt;a class=&quot;media media-thumbnail twitter-timeline-link is-preview
+ &quot; data-url=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
+ data-resolved-url-large=&quot;https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg:large&quot;
+ href=&quot;//twitter.com/Jalopnik/status/563005573290287105/photo/1&quot;&gt;&#10;
+ &lt;div class=&quot;&quot;&gt;&#10; &lt;img src=&quot;
+ https://pbs.twimg.com/media/B9Aylf5IMAAuziP.jpg&quot;
+ alt=&quot;Embedded image permalink&quot; width=&quot;636&quot; height=&quot;309&quot;&gt;&#10;
+ &lt;/div&gt;&#10;&#10; &lt;/a&gt;&#10;&#10; &lt;div class=&quot;cards-content&quot;&gt;&#10;
+ &lt;div class=&quot;byline&quot;&gt;&#10; &#10; &lt;/div&gt;&#10; &#10; &lt;/div&gt;&#10;
+ &#10;&lt;/div&gt;&#10;&#10;&#10;&#10;&#10;&lt;/div&gt;&#10;&#10;&#10;&#10; &lt;div
+ class=&quot;js-machine-translated-tweet-container&quot;&gt;&lt;/div&gt;&#10; &lt;div
+ class=&quot;js-tweet-stats-container tweet-stats-container &quot;&gt;&#10; &lt;/div&gt;&#10;&#10;
+ &lt;div class=&quot;client-and-actions&quot;&gt;&#10; &lt;span class=&quot;metadata&quot;&gt;&#10;
+ &lt;span&gt;5:06 PM - 4 Feb 2015&lt;/span&gt;&#10;&#10; &amp;middot; &lt;a
+ class=&quot;permalink-link js-permalink js-nav&quot; href=&quot;/Jalopnik/status/563005573290287105
+ &quot;tabindex=&quot;-1&quot;&gt;Details&lt;/a&gt;&#10; &#10;&#10; &#10; &#10;
+ &#10;&#10; &lt;/span&gt;&#10;&lt;/div&gt;&#10;&#10;&#10;&lt;/div&gt;&#10;" data-you-follow="false"
+ data-you-block="false">
+ <div class="context">
+ </div>
+ <div class="content">
+ <div class="stream-item-header">
+ <a class="account-group js-account-group js-action-profile js-user-profile-link js-nav"
+ href="/Jalopnik" data-user-id="3060631">
+ <img class="avatar js-action-profile-avatar"
+ src="https://pbs.twimg.com/profile_images/2976430168/5cd4a59_bigger.jpeg" alt="">
+ <strong class="fullname js-action-profile-name show-popup-with-id" data-aria-label-part>
+ Jalopnik
+ </strong>
+ <span>&rlm;</span>
+ <span class="username js-action-profile-name" data-aria-label-part>
+ <s>@</s><b>TitleName</b>
+ </span>
+ </a>
+ <small class="time">
+ <a href="/this.is.the.url"
+ class="tweet-timestamp js-permalink js-nav js-tooltip" title="5:06 PM - 4 Feb 2015" >
+ <span class="_timestamp js-short-timestamp js-relative-timestamp" data-time="1423065963"
+ data-time-ms="1423065963000" data-long-form="true" aria-hidden="true">
+ 17m
+ </span>
+ <span class="u-hiddenVisually" data-aria-label-part="last">17 minutes ago</span>
+ </a>
+ </small>
+ </div>
+ <p class="js-tweet-text tweet-text" lang="en" data-aria-label-part="0">
+ This is the content étude à€
+ <a href="http://t.co/nRWsqQAwBL" rel="nofollow" dir="ltr"
+ data-expanded-url="http://jalo.ps/ReMENu4" class="twitter-timeline-link"
+ target="_blank" title="http://jalo.ps/ReMENu4" >
+ <span class="tco-ellipsis">
+ </span>
+ <span class="invisible">http://</span><span class="js-display-url">link.in.tweet</span>
+ <span class="invisible"></span>
+ <span class="tco-ellipsis">
+ <span class="invisible">&nbsp;</span>
+ </span>
+ </a>
+ <a href="http://t.co/rbFsfeE0l3" class="twitter-timeline-link u-hidden"
+ data-pre-embedded="true" dir="ltr">
+ pic.twitter.com/rbFsfeE0l3
+ </a>
+ </p>
+ <div class="expanded-content js-tweet-details-dropdown">
+ </div>
+ <div class="stream-item-footer">
+ <a class="details with-icn js-details" href="/Jalopnik/status/563005573290287105">
+ <span class="Icon Icon--photo">
+ </span>
+ <b>
+ <span class="expand-stream-item js-view-details">
+ View photo
+ </span>
+ <span class="collapse-stream-item js-hide-details">
+ Hide photo
+ </span>
+ </b>
+ </a>
+ <span class="ProfileTweet-action--reply u-hiddenVisually">
+ <span class="ProfileTweet-actionCount" aria-hidden="true" data-tweet-stat-count="0">
+ <span class="ProfileTweet-actionCountForAria" >0 replies</span>
+ </span>
+ </span>
+ <span class="ProfileTweet-action--retweet u-hiddenVisually">
+ <span class="ProfileTweet-actionCount" data-tweet-stat-count="8">
+ <span class="ProfileTweet-actionCountForAria" data-aria-label-part>8 retweets</span>
+ </span>
+ </span>
+ <span class="ProfileTweet-action--favorite u-hiddenVisually">
+ <span class="ProfileTweet-actionCount" data-tweet-stat-count="14">
+ <span class="ProfileTweet-actionCountForAria" data-aria-label-part>14 favorites</span>
+ </span>
+ </span>
+ <div role="group" aria-label="Tweet actions" class="ProfileTweet-actionList u-cf js-actions">
+ <div class="ProfileTweet-action ProfileTweet-action--reply">
+ <button class="ProfileTweet-actionButton u-textUserColorHover js-actionButton
+ js-actionReply" data-modal="ProfileTweet-reply" type="button" title="Reply">
+ <span class="Icon Icon--reply">
+ </span>
+ <span class="u-hiddenVisually">Reply</span>
+ <span class="ProfileTweet-actionCount u-textUserColorHover
+ ProfileTweet-actionCount--isZero">
+ <span class="ProfileTweet-actionCountForPresentation" aria-hidden="true">
+ </span>
+ </span>
+ </button>
+ </div>
+ <div class="ProfileTweet-action ProfileTweet-action--retweet js-toggleState js-toggleRt">
+ <button class="ProfileTweet-actionButton js-actionButton js-actionRetweet js-tooltip"
+ title="Retweet" data-modal="ProfileTweet-retweet" type="button">
+ <span class="Icon Icon--retweet">
+ </span>
+ <span class="u-hiddenVisually">Retweet</span>
+ <span class="ProfileTweet-actionCount">
+ <span class="ProfileTweet-actionCountForPresentation">8</span>
+ </span>
+ </button>
+ <button class="ProfileTweet-actionButtonUndo js-actionButton js-actionRetweet"
+ data-modal="ProfileTweet-retweet" title="Undo retweet" type="button">
+ <span class="Icon Icon--retweet">
+ </span>
+ <span class="u-hiddenVisually">Retweeted</span>
+ <span class="ProfileTweet-actionCount">
+ <span class="ProfileTweet-actionCountForPresentation">8</span>
+ </span>
+ </button>
+ </div>
+ <div class="ProfileTweet-action ProfileTweet-action--favorite js-toggleState">
+ <button class="ProfileTweet-actionButton js-actionButton js-actionFavorite js-tooltip"
+ title="Favorite" type="button">
+ <span class="Icon Icon--favorite">
+ </span>
+ <span class="u-hiddenVisually">Favorite</span>
+ <span class="ProfileTweet-actionCount">
+ <span class="ProfileTweet-actionCountForPresentation">14</span>
+ </span>
+ </button>
+ <button class="ProfileTweet-actionButtonUndo u-linkClean js-actionButton
+ js-actionFavorite" title="Undo favorite" type="button">
+ <span class="Icon Icon--favorite">
+ </span>
+ <span class="u-hiddenVisually">Favorited</span>
+ <span class="ProfileTweet-actionCount">
+ <span class="ProfileTweet-actionCountForPresentation">
+ 14
+ </span>
+ </span>
+ </button>
+ </div>
+ <div class="ProfileTweet-action ProfileTweet-action--more js-more-ProfileTweet-actions">
+ <div class="dropdown">
+ <button class="ProfileTweet-actionButton u-textUserColorHover dropdown-toggle
+ js-tooltip js-dropdown-toggle" type="button" title="More">
+ <span class="Icon Icon--dots">
+ </span>
+ <span class="u-hiddenVisually">More</span>
+ </button>
+ <div class="dropdown-menu">
+ <div class="dropdown-caret">
+ <div class="caret-outer">
+ </div>
+ <div class="caret-inner">
+ </div>
+ </div>
+ <ul>
+ <li class="share-via-dm js-actionShareViaDM" data-nav="share_tweet_dm">
+ <button type="button" class="dropdown-link">
+ Share via Direct Message
+ </button>
+ </li>
+ <li class="embed-link js-actionEmbedTweet" data-nav="embed_tweet">
+ <button type="button" class="dropdown-link">
+ Embed Tweet
+ </button>
+ </li>
+ <li class="mute-user-item pretty-link">
+ <button type="button" class="dropdown-link">
+ Mute
+ </button>
+ </li>
+ <li class="unmute-user-item pretty-link">
+ <button type="button" class="dropdown-link">
+ Unmute
+ </button>
+ </li>
+ <li class="block-or-report-link js-actionBlockOrReport"
+ data-nav="block_or_report">
+ <button type="button" class="dropdown-link">
+ Block or report
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ """
+ response = mock.Mock(text=html)
+ results = twitter.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], '@TitleName')
+ self.assertEqual(results[0]['url'], 'https://twitter.com/this.is.the.url')
+ self.assertIn(u'This is the content', results[0]['content'])
+
+ html = """
+ <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
+ <div Class="sa_mc">
+ <div class="sb_tlst">
+ <h2>
+ <a href="http://this.should.be.the.link/" h="ID=SERP,5124.1">
+ <strong>This</strong> should be the title</a>
+ </h2>
+ </div>
+ <div class="sb_meta">
+ <cite>
+ <strong>this</strong>.meta.com</cite>
+ <span class="c_tlbxTrg">
+ <span class="c_tlbxH" H="BASE:CACHEDPAGEDEFAULT" K="SERP,5125.1">
+ </span>
+ </span>
+ </div>
+ <p>
+ <strong>This</strong> should be the content.</p>
+ </div>
+ </li>
+ """
+ response = mock.Mock(text=html)
+ results = twitter.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_vimeo.py b/tests/unit/engines/test_vimeo.py
new file mode 100644
index 0000000..c86b50a
--- /dev/null
+++ b/tests/unit/engines/test_vimeo.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import vimeo
+from searx.testing import SearxTestCase
+
+
+class TestVimeoEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ params = vimeo.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('vimeo.com' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, vimeo.response, None)
+ self.assertRaises(AttributeError, vimeo.response, [])
+ self.assertRaises(AttributeError, vimeo.response, '')
+ self.assertRaises(AttributeError, vimeo.response, '[]')
+
+ json = u"""
+{"filtered":{"total":274641,"page":1,"per_page":18,"paging":{"next":"?sizes=590x332&page=2","previous":null,"first":"?sizes=590x332&page=1","last":"?sizes=590x332&page=15258"},"data":[{"is_staffpick":false,"is_featured":true,"type":"clip","clip":{"uri":"\\/videos\\/106557563","name":"Hot Rod Revue: The South","link":"https:\\/\\/vimeo.com\\/106557563","duration":4069,"created_time":"2014-09-19T03:38:04+00:00","privacy":{"view":"ptv"},"pictures":{"sizes":[{"width":"590","height":"332","link":"https:\\/\\/i.vimeocdn.com\\/video\\/489717884_590x332.jpg?r=pad","link_with_play_button":"https:\\/\\/i.vimeocdn.com\\/filter\\/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F489717884_590x332.jpg&src1=http%3A%2F%2Ff.vimeocdn.com%2Fp%2Fimages%2Fcrawler_play.png"}]},"stats":{"plays":null},"metadata":{"connections":{"comments":{"total":0},"likes":{"total":5}},"interactions":[]},"user":{"name":"Cal Thorley","link":"https:\\/\\/vimeo.com\\/calthorley","pictures":{"sizes":[{"width":30,"height":30,"link":"https:\\/\\/i.vimeocdn.com\\/portrait\\/2545308_30x30?r=pad"},{"width":75,"height":75,"link":"https:\\/\\/i.vimeocdn.com\\/portrait\\/2545308_75x75?r=pad"},{"width":100,"height":100,"link":"https:\\/\\/i.vimeocdn.com\\/portrait\\/2545308_100x100?r=pad"},{"width":300,"height":300,"link":"https:\\/\\/i.vimeocdn.com\\/portrait\\/2545308_300x300?r=pad"}]}}}}]}};
+
+""" # noqa
+ response = mock.Mock(text=json)
+ results = vimeo.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], u'Hot Rod Revue: The South')
+ self.assertEqual(results[0]['url'], 'https://vimeo.com/106557563')
+ self.assertEqual(results[0]['content'], '')
+ self.assertEqual(results[0]['thumbnail'], 'https://i.vimeocdn.com/video/489717884_590x332.jpg?r=pad')
diff --git a/tests/unit/engines/test_wikidata.py b/tests/unit/engines/test_wikidata.py
new file mode 100644
index 0000000..aa69f11
--- /dev/null
+++ b/tests/unit/engines/test_wikidata.py
@@ -0,0 +1,503 @@
+# -*- coding: utf-8 -*-
+from lxml.html import fromstring
+from collections import defaultdict
+import mock
+from searx.engines import wikidata
+from searx.testing import SearxTestCase
+
+
+class TestWikidataEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['language'] = 'all'
+ params = wikidata.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('wikidata.org', params['url'])
+ self.assertIn('en', params['url'])
+
+ dicto['language'] = 'es_ES'
+ params = wikidata.request(query, dicto)
+ self.assertIn(query, params['url'])
+ self.assertIn('es', params['url'])
+
+ # successful cases are not tested here to avoid sending additional requests
+ def test_response(self):
+ self.assertRaises(AttributeError, wikidata.response, None)
+ self.assertRaises(AttributeError, wikidata.response, [])
+ self.assertRaises(AttributeError, wikidata.response, '')
+ self.assertRaises(AttributeError, wikidata.response, '[]')
+
+ response = mock.Mock(text='<html></html>', search_params={"language": "all"})
+ self.assertEqual(wikidata.response(response), [])
+
+ def test_getDetail(self):
+ response = {}
+ results = wikidata.getDetail(response, "Q123", "en", "en-US")
+ self.assertEqual(results, [])
+
+ title_html = '<div><div class="wikibase-title-label">Test</div></div>'
+ html = """
+ <div>
+ <div class="wikibase-entitytermsview-heading-description">
+ </div>
+ <div>
+ <ul class="wikibase-sitelinklistview-listview">
+ <li data-wb-siteid="enwiki"><a href="http://en.wikipedia.org/wiki/Test">Test</a></li>
+ </ul>
+ </div>
+ </div>
+ """
+ response = {"parse": {"displaytitle": title_html, "text": html}}
+
+ results = wikidata.getDetail(response, "Q123", "en", "en-US")
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['url'], 'https://en.wikipedia.org/wiki/Test')
+
+ title_html = """
+ <div>
+ <div class="wikibase-title-label">
+ <span lang="en">Test</span>
+ <sup class="wb-language-fallback-indicator">English</sup>
+ </div>
+ </div>
+ """
+ html = """
+ <div>
+ <div class="wikibase-entitytermsview-heading-description">
+ <span lang="en">Description</span>
+ <sup class="wb-language-fallback-indicator">English</sup>
+ </div>
+ <div id="P856">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P856">
+ <span lang="en">official website</span>
+ <sup class="wb-language-fallback-indicator">English</sup>
+ </a>
+ </div>
+ <div class="wikibase-statementview-mainsnak">
+ <a class="external free" href="https://officialsite.com">
+ https://officialsite.com
+ </a>
+ </div>
+ </div>
+ <div>
+ <ul class="wikibase-sitelinklistview-listview">
+ <li data-wb-siteid="enwiki"><a href="http://en.wikipedia.org/wiki/Test">Test</a></li>
+ </ul>
+ </div>
+ </div>
+ """
+ response = {"parse": {"displaytitle": title_html, "text": html}}
+
+ results = wikidata.getDetail(response, "Q123", "yua", "yua_MX")
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], 'Official website')
+ self.assertEqual(results[0]['url'], 'https://officialsite.com')
+
+ self.assertEqual(results[1]['infobox'], 'Test')
+ self.assertEqual(results[1]['id'], None)
+ self.assertEqual(results[1]['content'], 'Description')
+ self.assertEqual(results[1]['attributes'], [])
+ self.assertEqual(results[1]['urls'][0]['title'], 'Official website')
+ self.assertEqual(results[1]['urls'][0]['url'], 'https://officialsite.com')
+ self.assertEqual(results[1]['urls'][1]['title'], 'Wikipedia (en)')
+ self.assertEqual(results[1]['urls'][1]['url'], 'https://en.wikipedia.org/wiki/Test')
+
+ def test_add_image(self):
+ image_src = wikidata.add_image(fromstring("<div></div>"))
+ self.assertEqual(image_src, None)
+
+ html = u"""
+ <div>
+ <div id="P18">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P18">
+ image
+ </a>
+ </div>
+ <div class="wikibase-statementlistview">
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-rankselector">
+ <span class="wikibase-rankselector-normal"></span>
+ </div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a href="https://commons.wikimedia.org/wiki/File:image.png">
+ image.png
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ html_etree = fromstring(html)
+
+ image_src = wikidata.add_image(html_etree)
+ self.assertEqual(image_src,
+ "https://commons.wikimedia.org/wiki/Special:FilePath/image.png?width=500&height=400")
+
+ html = u"""
+ <div>
+ <div id="P2910">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P2910">
+ icon
+ </a>
+ </div>
+ <div class="wikibase-statementlistview">
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-rankselector">
+ <span class="wikibase-rankselector-normal"></span>
+ </div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a href="https://commons.wikimedia.org/wiki/File:icon.png">
+ icon.png
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div id="P154">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P154">
+ logo
+ </a>
+ </div>
+ <div class="wikibase-statementlistview">
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-rankselector">
+ <span class="wikibase-rankselector-normal"></span>
+ </div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a href="https://commons.wikimedia.org/wiki/File:logo.png">
+ logo.png
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ html_etree = fromstring(html)
+
+ image_src = wikidata.add_image(html_etree)
+ self.assertEqual(image_src,
+ "https://commons.wikimedia.org/wiki/Special:FilePath/logo.png?width=500&height=400")
+
+ def test_add_attribute(self):
+ html = u"""
+ <div>
+ <div id="P27">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P27">
+ country of citizenship
+ </a>
+ </div>
+ <div class="wikibase-statementlistview">
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-rankselector">
+ <span class="wikibase-rankselector-normal"></span>
+ </div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a href="/wiki/Q145">
+ United Kingdom
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ attributes = []
+ html_etree = fromstring(html)
+
+ wikidata.add_attribute(attributes, html_etree, "Fail")
+ self.assertEqual(attributes, [])
+
+ wikidata.add_attribute(attributes, html_etree, "P27")
+ self.assertEqual(len(attributes), 1)
+ self.assertEqual(attributes[0]["label"], "Country of citizenship")
+ self.assertEqual(attributes[0]["value"], "United Kingdom")
+
+ html = u"""
+ <div>
+ <div id="P569">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P569">
+ date of birth
+ </a>
+ </div>
+ <div class="wikibase-statementlistview">
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-rankselector">
+ <span class="wikibase-rankselector-normal"></span>
+ </div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ 27 January 1832
+ <sup class="wb-calendar-name">
+ Gregorian
+ </sup>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ attributes = []
+ html_etree = fromstring(html)
+ wikidata.add_attribute(attributes, html_etree, "P569", date=True)
+ self.assertEqual(len(attributes), 1)
+ self.assertEqual(attributes[0]["label"], "Date of birth")
+ self.assertEqual(attributes[0]["value"], "27 January 1832")
+
+ html = u"""
+ <div>
+ <div id="P6">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P27">
+ head of government
+ </a>
+ </div>
+ <div class="wikibase-statementlistview">
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-rankselector">
+ <span class="wikibase-rankselector-normal"></span>
+ </div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a href="/wiki/Q206">
+ Old Prime Minister
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-rankselector">
+ <span class="wikibase-rankselector-preferred"></span>
+ </div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a href="/wiki/Q3099714">
+ Actual Prime Minister
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ attributes = []
+ html_etree = fromstring(html)
+ wikidata.add_attribute(attributes, html_etree, "P6")
+ self.assertEqual(len(attributes), 1)
+ self.assertEqual(attributes[0]["label"], "Head of government")
+ self.assertEqual(attributes[0]["value"], "Old Prime Minister, Actual Prime Minister")
+
+ attributes = []
+ html_etree = fromstring(html)
+ wikidata.add_attribute(attributes, html_etree, "P6", trim=True)
+ self.assertEqual(len(attributes), 1)
+ self.assertEqual(attributes[0]["value"], "Actual Prime Minister")
+
+ def test_add_url(self):
+ html = u"""
+ <div>
+ <div id="P856">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P856">
+ official website
+ </a>
+ </div>
+ <div class="wikibase-statementlistview">
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a class="external free" href="https://searx.me">
+ https://searx.me/
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ urls = []
+ html_etree = fromstring(html)
+ wikidata.add_url(urls, html_etree, 'P856')
+ self.assertEquals(len(urls), 1)
+ self.assertIn({'title': 'Official website', 'url': 'https://searx.me/'}, urls)
+ urls = []
+ results = []
+ wikidata.add_url(urls, html_etree, 'P856', 'custom label', results=results)
+ self.assertEquals(len(urls), 1)
+ self.assertEquals(len(results), 1)
+ self.assertIn({'title': 'custom label', 'url': 'https://searx.me/'}, urls)
+ self.assertIn({'title': 'custom label', 'url': 'https://searx.me/'}, results)
+
+ html = u"""
+ <div>
+ <div id="P856">
+ <div class="wikibase-statementgroupview-property-label">
+ <a href="/wiki/Property:P856">
+ official website
+ </a>
+ </div>
+ <div class="wikibase-statementlistview">
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a class="external free" href="http://www.worldofwarcraft.com">
+ http://www.worldofwarcraft.com
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="wikibase-statementview listview-item">
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a class="external free" href="http://eu.battle.net/wow/en/">
+ http://eu.battle.net/wow/en/
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ urls = []
+ html_etree = fromstring(html)
+ wikidata.add_url(urls, html_etree, 'P856')
+ self.assertEquals(len(urls), 2)
+ self.assertIn({'title': 'Official website', 'url': 'http://www.worldofwarcraft.com'}, urls)
+ self.assertIn({'title': 'Official website', 'url': 'http://eu.battle.net/wow/en/'}, urls)
+
+ def test_get_imdblink(self):
+ html = u"""
+ <div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a class="wb-external-id" href="http://www.imdb.com/tt0433664">
+ tt0433664
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ html_etree = fromstring(html)
+ imdblink = wikidata.get_imdblink(html_etree, 'https://www.imdb.com/')
+
+ html = u"""
+ <div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ <a class="wb-external-id"
+ href="href="http://tools.wmflabs.org/...http://www.imdb.com/&id=nm4915994"">
+ nm4915994
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ html_etree = fromstring(html)
+ imdblink = wikidata.get_imdblink(html_etree, 'https://www.imdb.com/')
+ self.assertIn('https://www.imdb.com/name/nm4915994', imdblink)
+
+ def test_get_geolink(self):
+ html = u"""
+ <div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ 60°N, 40°E
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ html_etree = fromstring(html)
+ geolink = wikidata.get_geolink(html_etree)
+ self.assertIn('https://www.openstreetmap.org/', geolink)
+ self.assertIn('lat=60&lon=40', geolink)
+
+ html = u"""
+ <div>
+ <div class="wikibase-statementview-mainsnak">
+ <div>
+ <div class="wikibase-snakview-value">
+ 34°35'59"S, 58°22'55"W
+ </div>
+ </div>
+ </div>
+ </div>
+ """
+ html_etree = fromstring(html)
+ geolink = wikidata.get_geolink(html_etree)
+ self.assertIn('https://www.openstreetmap.org/', geolink)
+ self.assertIn('lat=-34.59', geolink)
+ self.assertIn('lon=-58.38', geolink)
+
+ def test_get_wikilink(self):
+ html = """
+ <div>
+ <div>
+ <ul class="wikibase-sitelinklistview-listview">
+ <li data-wb-siteid="arwiki"><a href="http://ar.wikipedia.org/wiki/Test">Test</a></li>
+ <li data-wb-siteid="enwiki"><a href="http://en.wikipedia.org/wiki/Test">Test</a></li>
+ </ul>
+ </div>
+ <div>
+ <ul class="wikibase-sitelinklistview-listview">
+ <li data-wb-siteid="enwikiquote"><a href="https://en.wikiquote.org/wiki/Test">Test</a></li>
+ </ul>
+ </div>
+ </div>
+ """
+ html_etree = fromstring(html)
+ wikilink = wikidata.get_wikilink(html_etree, 'nowiki')
+ self.assertEqual(wikilink, None)
+ wikilink = wikidata.get_wikilink(html_etree, 'enwiki')
+ self.assertEqual(wikilink, 'https://en.wikipedia.org/wiki/Test')
+ wikilink = wikidata.get_wikilink(html_etree, 'arwiki')
+ self.assertEqual(wikilink, 'https://ar.wikipedia.org/wiki/Test')
+ wikilink = wikidata.get_wikilink(html_etree, 'enwikiquote')
+ self.assertEqual(wikilink, 'https://en.wikiquote.org/wiki/Test')
diff --git a/tests/unit/engines/test_wikipedia.py b/tests/unit/engines/test_wikipedia.py
new file mode 100644
index 0000000..7a86514
--- /dev/null
+++ b/tests/unit/engines/test_wikipedia.py
@@ -0,0 +1,259 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import wikipedia
+from searx.testing import SearxTestCase
+
+
+class TestWikipediaEngine(SearxTestCase):
+
+ def test_request(self):
+ wikipedia.supported_languages = ['fr', 'en']
+
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['language'] = 'fr-FR'
+ params = wikipedia.request(query.encode('utf-8'), dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('test_query', params['url'])
+ self.assertIn('Test_Query', params['url'])
+ self.assertIn('fr.wikipedia.org', params['url'])
+
+ query = u'Test_Query'
+ params = wikipedia.request(query.encode('utf-8'), dicto)
+ self.assertIn('Test_Query', params['url'])
+ self.assertNotIn('test_query', params['url'])
+
+ dicto['language'] = 'all'
+ params = wikipedia.request(query, dicto)
+ self.assertIn('en', params['url'])
+
+ dicto['language'] = 'xx'
+ params = wikipedia.request(query, dicto)
+ self.assertIn('en', params['url'])
+
+ def test_response(self):
+ dicto = defaultdict(dict)
+ dicto['language'] = 'fr'
+
+ self.assertRaises(AttributeError, wikipedia.response, None)
+ self.assertRaises(AttributeError, wikipedia.response, [])
+ self.assertRaises(AttributeError, wikipedia.response, '')
+ self.assertRaises(AttributeError, wikipedia.response, '[]')
+
+ # page not found
+ json = """
+ {
+ "batchcomplete": "",
+ "query": {
+ "normalized": [],
+ "pages": {
+ "-1": {
+ "ns": 0,
+ "title": "",
+ "missing": ""
+ }
+ }
+ }
+ }"""
+ response = mock.Mock(text=json, search_params=dicto)
+ self.assertEqual(wikipedia.response(response), [])
+
+ # normal case
+ json = """
+ {
+ "batchcomplete": "",
+ "query": {
+ "normalized": [],
+ "pages": {
+ "12345": {
+ "pageid": 12345,
+ "ns": 0,
+ "title": "The Title",
+ "extract": "The Title is...",
+ "thumbnail": {
+ "source": "img_src.jpg"
+ },
+ "pageimage": "img_name.jpg"
+ }
+ }
+ }
+ }"""
+ response = mock.Mock(text=json, search_params=dicto)
+ results = wikipedia.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], u'The Title')
+ self.assertIn('fr.wikipedia.org/wiki/The_Title', results[0]['url'])
+ self.assertEqual(results[1]['infobox'], u'The Title')
+ self.assertIn('fr.wikipedia.org/wiki/The_Title', results[1]['id'])
+ self.assertIn('The Title is...', results[1]['content'])
+ self.assertEqual(results[1]['img_src'], 'img_src.jpg')
+
+ # disambiguation page
+ json = """
+ {
+ "batchcomplete": "",
+ "query": {
+ "normalized": [],
+ "pages": {
+ "12345": {
+ "pageid": 12345,
+ "ns": 0,
+ "title": "The Title",
+ "extract": "The Title can be:\\nThe Title 1\\nThe Title 2\\nThe Title 3\\nThe Title 4......................................................................................................................................." """ # noqa
+ json += """
+ }
+ }
+ }
+ }"""
+ response = mock.Mock(text=json, search_params=dicto)
+ results = wikipedia.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+
+ # no image
+ json = """
+ {
+ "batchcomplete": "",
+ "query": {
+ "normalized": [],
+ "pages": {
+ "12345": {
+ "pageid": 12345,
+ "ns": 0,
+ "title": "The Title",
+ "extract": "The Title is......................................................................................................................................................................................." """ # noqa
+ json += """
+ }
+ }
+ }
+ }"""
+ response = mock.Mock(text=json, search_params=dicto)
+ results = wikipedia.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertIn('The Title is...', results[1]['content'])
+ self.assertEqual(results[1]['img_src'], None)
+
+ # title not in first paragraph
+ json = u"""
+ {
+ "batchcomplete": "",
+ "query": {
+ "normalized": [],
+ "pages": {
+ "12345": {
+ "pageid": 12345,
+ "ns": 0,
+ "title": "披頭四樂隊",
+ "extract": "披头士乐队....................................................................................................................................................................................................\\n披頭四樂隊...", """ # noqa
+ json += """
+ "thumbnail": {
+ "source": "img_src.jpg"
+ },
+ "pageimage": "img_name.jpg"
+ }
+ }
+ }
+ }"""
+ response = mock.Mock(text=json, search_params=dicto)
+ results = wikipedia.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[1]['infobox'], u'披頭四樂隊')
+ self.assertIn(u'披头士乐队...', results[1]['content'])
+
+ def test_fetch_supported_languages(self):
+ html = u"""<html></html>"""
+ response = mock.Mock(text=html)
+ languages = wikipedia._fetch_supported_languages(response)
+ self.assertEqual(type(languages), dict)
+ self.assertEqual(len(languages), 0)
+
+ html = u"""
+ <html>
+ <body>
+ <div>
+ <div>
+ <h3>Table header</h3>
+ <table class="sortable jquery-tablesorter">
+ <thead>
+ <tr>
+ <th>N</th>
+ <th>Language</th>
+ <th>Language (local)</th>
+ <th>Wiki</th>
+ <th>Articles</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>2</td>
+ <td><a>Swedish</a></td>
+ <td><a>Svenska</a></td>
+ <td><a>sv</a></td>
+ <td><a><b>3000000</b></a></td>
+ </tr>
+ <tr>
+ <td>3</td>
+ <td><a>Cebuano</a></td>
+ <td><a>Sinugboanong Binisaya</a></td>
+ <td><a>ceb</a></td>
+ <td><a><b>3000000</b></a></td>
+ </tr>
+ </tbody>
+ </table>
+ <h3>Table header</h3>
+ <table class="sortable jquery-tablesorter">
+ <thead>
+ <tr>
+ <th>N</th>
+ <th>Language</th>
+ <th>Language (local)</th>
+ <th>Wiki</th>
+ <th>Articles</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>2</td>
+ <td><a>Norwegian (Bokmål)</a></td>
+ <td><a>Norsk (Bokmål)</a></td>
+ <td><a>no</a></td>
+ <td><a><b>100000</b></a></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </body>
+ </html>
+ """
+ response = mock.Mock(text=html)
+ languages = wikipedia._fetch_supported_languages(response)
+ self.assertEqual(type(languages), dict)
+ self.assertEqual(len(languages), 3)
+
+ self.assertIn('sv', languages)
+ self.assertIn('ceb', languages)
+ self.assertIn('no', languages)
+
+ self.assertEqual(type(languages['sv']), dict)
+ self.assertEqual(type(languages['ceb']), dict)
+ self.assertEqual(type(languages['no']), dict)
+
+ self.assertIn('name', languages['sv'])
+ self.assertIn('english_name', languages['sv'])
+ self.assertIn('articles', languages['sv'])
+
+ self.assertEqual(languages['sv']['name'], 'Svenska')
+ self.assertEqual(languages['sv']['english_name'], 'Swedish')
+ self.assertEqual(languages['sv']['articles'], 3000000)
+ self.assertEqual(languages['ceb']['name'], 'Sinugboanong Binisaya')
+ self.assertEqual(languages['ceb']['english_name'], 'Cebuano')
+ self.assertEqual(languages['ceb']['articles'], 3000000)
+ self.assertEqual(languages['no']['name'], u'Norsk (Bokmål)')
+ self.assertEqual(languages['no']['english_name'], u'Norwegian (Bokmål)')
+ self.assertEqual(languages['no']['articles'], 100000)
diff --git a/tests/unit/engines/test_wolframalpha_api.py b/tests/unit/engines/test_wolframalpha_api.py
new file mode 100644
index 0000000..30d3376
--- /dev/null
+++ b/tests/unit/engines/test_wolframalpha_api.py
@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from requests import Request
+from searx.engines import wolframalpha_api
+from searx.testing import SearxTestCase
+
+
+class TestWolframAlphaAPIEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ params = wolframalpha_api.request(query, dicto)
+
+ # TODO: test api_key
+ self.assertIn('url', params)
+ self.assertIn('https://api.wolframalpha.com/v2/query?', params['url'])
+ self.assertIn(query, params['url'])
+ self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
+
+ def test_replace_pua_chars(self):
+ self.assertEqual('i', wolframalpha_api.replace_pua_chars(u'\uf74e'))
+
+ def test_response(self):
+ self.assertRaises(AttributeError, wolframalpha_api.response, None)
+ self.assertRaises(AttributeError, wolframalpha_api.response, [])
+ self.assertRaises(AttributeError, wolframalpha_api.response, '')
+ self.assertRaises(AttributeError, wolframalpha_api.response, '[]')
+
+ referer_url = 'referer_url'
+ request = Request(headers={'Referer': referer_url})
+
+ # test failure
+ xml = '''<?xml version='1.0' encoding='UTF-8'?>
+ <queryresult success='false' error='false' />
+ '''
+ response = mock.Mock(text=xml.encode('utf-8'))
+ self.assertEqual(wolframalpha_api.response(response), [])
+
+ # test basic case
+ xml = b"""<?xml version='1.0' encoding='UTF-8'?>
+ <queryresult success='true'
+ error='false'
+ numpods='3'
+ datatypes='Math'
+ id='queryresult_id'
+ host='http://www4c.wolframalpha.com'
+ related='related_url'
+ version='2.6'>
+ <pod title='Input'
+ scanner='Identity'
+ id='Input'
+ numsubpods='1'>
+ <subpod title=''>
+ <img src='input_img_src.gif'
+ alt='input_img_alt'
+ title='input_img_title' />
+ <plaintext>input_plaintext</plaintext>
+ </subpod>
+ </pod>
+ <pod title='Result'
+ scanner='Simplification'
+ id='Result'
+ numsubpods='1'
+ primary='true'>
+ <subpod title=''>
+ <img src='result_img_src.gif'
+ alt='result_img_alt'
+ title='result_img_title' />
+ <plaintext>result_plaintext</plaintext>
+ </subpod>
+ </pod>
+ <pod title='Manipulatives illustration'
+ scanner='Arithmetic'
+ id='Illustration'
+ numsubpods='1'>
+ <subpod title=''>
+ <img src='illustration_img_src.gif'
+ alt='illustration_img_alt' />
+ <plaintext>illustration_plaintext</plaintext>
+ </subpod>
+ </pod>
+ </queryresult>
+ """
+ response = mock.Mock(text=xml, request=request)
+ results = wolframalpha_api.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual('input_plaintext', results[0]['infobox'])
+
+ self.assertEqual(len(results[0]['attributes']), 3)
+ self.assertEqual('Input', results[0]['attributes'][0]['label'])
+ self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
+ self.assertEqual('Result', results[0]['attributes'][1]['label'])
+ self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
+ self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
+ self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
+ self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
+
+ self.assertEqual(len(results[0]['urls']), 1)
+
+ self.assertEqual(referer_url, results[0]['urls'][0]['url'])
+ self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
+ self.assertEqual(referer_url, results[1]['url'])
+ self.assertEqual('Wolfram|Alpha (input_plaintext)', results[1]['title'])
+ self.assertIn('result_plaintext', results[1]['content'])
+
+ # test calc
+ xml = b"""<?xml version='1.0' encoding='UTF-8'?>
+ <queryresult success='true'
+ error='false'
+ numpods='2'
+ datatypes=''
+ parsetimedout='false'
+ id='queryresult_id'
+ host='http://www5b.wolframalpha.com'
+ related='related_url'
+ version='2.6' >
+ <pod title='Indefinite integral'
+ scanner='Integral'
+ id='IndefiniteIntegral'
+ error='false'
+ numsubpods='1'
+ primary='true'>
+ <subpod title=''>
+ <img src='integral_image.gif'
+ alt='integral_img_alt'
+ title='integral_img_title' />
+ <plaintext>integral_plaintext</plaintext>
+ </subpod>
+ </pod>
+ <pod title='Plot of the integral'
+ scanner='Integral'
+ id='Plot'
+ error='false'
+ numsubpods='1'>
+ <subpod title=''>
+ <img src='plot.gif'
+ alt='plot_alt'
+ title='' />
+ <plaintext></plaintext>
+ </subpod>
+ </pod>
+ </queryresult>
+ """
+ response = mock.Mock(text=xml, request=request)
+ results = wolframalpha_api.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual('integral_plaintext', results[0]['infobox'])
+
+ self.assertEqual(len(results[0]['attributes']), 2)
+ self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
+ self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
+ self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
+ self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
+ self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
+
+ self.assertEqual(len(results[0]['urls']), 1)
+
+ self.assertEqual(referer_url, results[0]['urls'][0]['url'])
+ self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
+ self.assertEqual(referer_url, results[1]['url'])
+ self.assertEqual('Wolfram|Alpha (integral_plaintext)', results[1]['title'])
+ self.assertIn('integral_plaintext', results[1]['content'])
diff --git a/tests/unit/engines/test_wolframalpha_noapi.py b/tests/unit/engines/test_wolframalpha_noapi.py
new file mode 100644
index 0000000..982edd9
--- /dev/null
+++ b/tests/unit/engines/test_wolframalpha_noapi.py
@@ -0,0 +1,224 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from requests import Request
+from searx.engines import wolframalpha_noapi
+from searx.testing import SearxTestCase
+
+
+class TestWolframAlphaNoAPIEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ params = wolframalpha_noapi.request(query, dicto)
+
+ self.assertIn('url', params)
+ self.assertIn('https://www.wolframalpha.com/input/json.jsp', params['url'])
+ self.assertIn(query, params['url'])
+ self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, wolframalpha_noapi.response, None)
+ self.assertRaises(AttributeError, wolframalpha_noapi.response, [])
+ self.assertRaises(AttributeError, wolframalpha_noapi.response, '')
+ self.assertRaises(AttributeError, wolframalpha_noapi.response, '[]')
+
+ referer_url = 'referer_url'
+ request = Request(headers={'Referer': referer_url})
+
+ # test failure
+ json = r'''
+ {"queryresult" : {
+ "success" : false,
+ "error" : false,
+ "numpods" : 0,
+ "id" : "",
+ "host" : "https:\/\/www5a.wolframalpha.com",
+ "didyoumeans" : {}
+ }}
+ '''
+ response = mock.Mock(text=json, request=request)
+ self.assertEqual(wolframalpha_noapi.response(response), [])
+
+ # test basic case
+ json = r'''
+ {"queryresult" : {
+ "success" : true,
+ "error" : false,
+ "numpods" : 6,
+ "datatypes" : "Math",
+ "id" : "queryresult_id",
+ "host" : "https:\/\/www5b.wolframalpha.com",
+ "related" : "related_url",
+ "version" : "2.6",
+ "pods" : [
+ {
+ "title" : "Input",
+ "scanners" : [
+ "Identity"
+ ],
+ "id" : "Input",
+ "error" : false,
+ "numsubpods" : 1,
+ "subpods" : [
+ {
+ "title" : "",
+ "img" : {
+ "src" : "input_img_src.gif",
+ "alt" : "input_img_alt",
+ "title" : "input_img_title"
+ },
+ "plaintext" : "input_plaintext",
+ "minput" : "input_minput"
+ }
+ ]
+ },
+ {
+ "title" : "Result",
+ "scanners" : [
+ "Simplification"
+ ],
+ "id" : "Result",
+ "error" : false,
+ "numsubpods" : 1,
+ "primary" : true,
+ "subpods" : [
+ {
+ "title" : "",
+ "img" : {
+ "src" : "result_img_src.gif",
+ "alt" : "result_img_alt",
+ "title" : "result_img_title"
+ },
+ "plaintext" : "result_plaintext",
+ "moutput" : "result_moutput"
+ }
+ ]
+ },
+ {
+ "title" : "Manipulatives illustration",
+ "scanners" : [
+ "Arithmetic"
+ ],
+ "id" : "Illustration",
+ "error" : false,
+ "numsubpods" : 1,
+ "subpods" : [
+ {
+ "title" : "",
+ "CDFcontent" : "Resizeable",
+ "img" : {
+ "src" : "illustration_img_src.gif",
+ "alt" : "illustration_img_alt",
+ "title" : "illustration_img_title"
+ },
+ "plaintext" : "illustration_img_plaintext"
+ }
+ ]
+ }
+ ]
+ }}
+ '''
+ response = mock.Mock(text=json, request=request)
+ results = wolframalpha_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual('input_plaintext', results[0]['infobox'])
+
+ self.assertEqual(len(results[0]['attributes']), 3)
+ self.assertEqual('Input', results[0]['attributes'][0]['label'])
+ self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
+ self.assertEqual('Result', results[0]['attributes'][1]['label'])
+ self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
+ self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
+ self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
+ self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
+
+ self.assertEqual(len(results[0]['urls']), 1)
+
+ self.assertEqual(referer_url, results[0]['urls'][0]['url'])
+ self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
+ self.assertEqual(referer_url, results[1]['url'])
+ self.assertEqual('Wolfram|Alpha (input_plaintext)', results[1]['title'])
+ self.assertIn('result_plaintext', results[1]['content'])
+
+ # test calc
+ json = r"""
+ {"queryresult" : {
+ "success" : true,
+ "error" : false,
+ "numpods" : 2,
+ "datatypes" : "",
+ "id" : "queryresult_id",
+ "host" : "https:\/\/www4b.wolframalpha.com",
+ "related" : "related_url",
+ "version" : "2.6",
+ "pods" : [
+ {
+ "title" : "Indefinite integral",
+ "scanners" : [
+ "Integral"
+ ],
+ "id" : "IndefiniteIntegral",
+ "error" : false,
+ "numsubpods" : 1,
+ "primary" : true,
+ "subpods" : [
+ {
+ "title" : "",
+ "img" : {
+ "src" : "integral_img_src.gif",
+ "alt" : "integral_img_alt",
+ "title" : "integral_img_title"
+ },
+ "plaintext" : "integral_plaintext",
+ "minput" : "integral_minput",
+ "moutput" : "integral_moutput"
+ }
+ ]
+ },
+ {
+ "title" : "Plot of the integral",
+ "scanners" : [
+ "Integral"
+ ],
+ "id" : "Plot",
+ "error" : false,
+ "numsubpods" : 1,
+ "subpods" : [
+ {
+ "title" : "",
+ "img" : {
+ "src" : "plot.gif",
+ "alt" : "plot_alt",
+ "title" : "plot_title"
+ },
+ "plaintext" : "",
+ "minput" : "plot_minput"
+ }
+ ]
+ }
+ ]
+ }}
+ """
+ response = mock.Mock(text=json, request=request)
+ results = wolframalpha_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual('integral_plaintext', results[0]['infobox'])
+
+ self.assertEqual(len(results[0]['attributes']), 2)
+ self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
+ self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
+ self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
+ self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
+ self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
+
+ self.assertEqual(len(results[0]['urls']), 1)
+
+ self.assertEqual(referer_url, results[0]['urls'][0]['url'])
+ self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
+ self.assertEqual(referer_url, results[1]['url'])
+ self.assertEqual('Wolfram|Alpha (integral_plaintext)', results[1]['title'])
+ self.assertIn('integral_plaintext', results[1]['content'])
diff --git a/tests/unit/engines/test_www1x.py b/tests/unit/engines/test_www1x.py
new file mode 100644
index 0000000..9df8de6
--- /dev/null
+++ b/tests/unit/engines/test_www1x.py
@@ -0,0 +1,57 @@
+from collections import defaultdict
+import mock
+from searx.engines import www1x
+from searx.testing import SearxTestCase
+
+
+class TestWww1xEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ params = www1x.request(query, defaultdict(dict))
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('1x.com' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, www1x.response, None)
+ self.assertRaises(AttributeError, www1x.response, [])
+ self.assertRaises(AttributeError, www1x.response, '')
+ self.assertRaises(AttributeError, www1x.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(www1x.response(response), [])
+ html = """
+ <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE characters
+ [
+ <!ELEMENT characters (character*) >
+ <!ELEMENT character (#PCDATA ) >
+
+ <!ENTITY iexcl "&#161;" >
+ <!ENTITY cent "&#162;" >
+ <!ENTITY pound "&#163;" >
+ ]
+ ><root><searchresult><![CDATA[<table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tr>
+ <td style="min-width: 220px;" valign="top">
+ <div style="font-size: 30px; margin: 0px 0px 20px 0px;">Photos</div>
+ <div>
+ <a href="/photo/123456" class="dynamiclink">
+<img border="0" class="searchresult" src="/images/user/testimage-123456.jpg" style="width: 125px; height: 120px;">
+ </a>
+ <a title="sjoerd lammers street photography" href="/member/sjoerdlammers" class="dynamiclink">
+<img border="0" class="searchresult" src="/images/profile/60c48b394c677d2fa4d9e7d263aabf44-square.jpg">
+ </a>
+ </div>
+ </td>
+ </table>
+ ]]></searchresult></root>
+ """
+ response = mock.Mock(text=html)
+ results = www1x.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['url'], 'https://1x.com/photo/123456')
+ self.assertEqual(results[0]['thumbnail_src'], 'https://1x.com/images/user/testimage-123456.jpg')
+ self.assertEqual(results[0]['content'], '')
+ self.assertEqual(results[0]['template'], 'images.html')
diff --git a/tests/unit/engines/test_www500px.py b/tests/unit/engines/test_www500px.py
new file mode 100644
index 0000000..e50601d
--- /dev/null
+++ b/tests/unit/engines/test_www500px.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import www500px
+from searx.testing import SearxTestCase
+
+
+class TestWww500pxImagesEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ params = www500px.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertTrue('500px.com' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, www500px.response, None)
+ self.assertRaises(AttributeError, www500px.response, [])
+ self.assertRaises(AttributeError, www500px.response, '')
+ self.assertRaises(AttributeError, www500px.response, '[]')
+
+ json = """
+{"current_page":1,"total_pages":1000,"total_items":862178,"photos":[{"id":64531569,"user_id":111147,"name":"Grand Canyon Afternoon","description":"Looking west on a very windy winter afternoon.","camera":"Canon EOS 5D Mark II","lens":"EF24-105mm f/4L IS USM","focal_length":"28","iso":"200","shutter_speed":"1/1250","aperture":"6.3","times_viewed":4809,"rating":48.5,"status":1,"created_at":"2014-03-22T03:44:46-04:00","category":8,"location":null,"latitude":36.0323916666667,"longitude":-111.85273,"taken_at":"2014-02-27T14:10:43-05:00","hi_res_uploaded":2,"for_sale":true,"width":5476,"height":3651,"votes_count":108,"favorites_count":35,"comments_count":5,"nsfw":false,"sales_count":0,"for_sale_date":null,"highest_rating":91.9,"highest_rating_date":"2014-03-22T22:34:54-04:00","license_type":0,"converted":31,"collections_count":10,"crop_version":0,"privacy":false,"profile":true,"image_url":["https://drscdn.500px.org/photo/64531569/w%3D70_h%3D70/449d50817f28d85395e23bbb415b3cdb?v=0","https://drscdn.500px.org/photo/64531569/q%3D50_w%3D140_h%3D140/3e3e123734a596644ede78105268bdb2?v=0","https://drscdn.500px.org/photo/64531569/q%3D80_h%3D300/2ce2f61714aebdca710967dfdc3efb04","https://drscdn.500px.org/photo/64531569/q%3D80_h%3D450/c8ec030441f2c68b9bd40a114903348a","https://drscdn.500px.org/photo/64531569/q%3D80_h%3D600/ab6562d0581b359679ecc8ef2e939396","https://drscdn.500px.org/photo/64531569/q%3D80_m%3D1000/bd7dbc54a505e041a8c9a70dfa434272","https://drscdn.500px.org/photo/64531569/q%3D80_m%3D1500/eb4d7f8f6a32d3e5c168c2cb55d29c12","https://drscdn.500px.org/photo/64531569/q%3D80_m%3D2000/d519f91b8a568e7357a8a7fa1aabbe74","https://drscdn.500px.org/photo/64531569/m%3D2048/4c52fb18cc2b2b6f91a0d04609786507","https://drscdn.500px.org/photo/64531569/m%3D900/fb620ae39569ab4a421e9170a94b1a0f","https://drscdn.500px.org/photo/64531569/m%3D900_s%3D1_k%3D1_a%3D1/02b95ce64db090c1f94f890960974612?v=0"],"images":[{"size":1,"url":"https://drscdn.500px.org/photo/64531569/w%3D70_h%3D70/449d50817f28d85395e23bbb415b3cdb?v=0","https_url":"https://drscdn.500px.org/photo/64531569/w%3D70_h%3D70/449d50817f28d85395e23bbb415b3cdb?v=0","format":"jpeg"},{"size":2,"url":"https://drscdn.500px.org/photo/64531569/q%3D50_w%3D140_h%3D140/3e3e123734a596644ede78105268bdb2?v=0","https_url":"https://drscdn.500px.org/photo/64531569/q%3D50_w%3D140_h%3D140/3e3e123734a596644ede78105268bdb2?v=0","format":"jpeg"},{"size":4,"url":"https://drscdn.500px.org/photo/64531569/m%3D900/fb620ae39569ab4a421e9170a94b1a0f","https_url":"https://drscdn.500px.org/photo/64531569/m%3D900/fb620ae39569ab4a421e9170a94b1a0f","format":"jpeg"},{"size":14,"url":"https://drscdn.500px.org/photo/64531569/m%3D900_s%3D1_k%3D1_a%3D1/02b95ce64db090c1f94f890960974612?v=0","https_url":"https://drscdn.500px.org/photo/64531569/m%3D900_s%3D1_k%3D1_a%3D1/02b95ce64db090c1f94f890960974612?v=0","format":"jpeg"},{"size":31,"url":"https://drscdn.500px.org/photo/64531569/q%3D80_h%3D450/c8ec030441f2c68b9bd40a114903348a","https_url":"https://drscdn.500px.org/photo/64531569/q%3D80_h%3D450/c8ec030441f2c68b9bd40a114903348a","format":"jpeg"},{"size":32,"url":"https://drscdn.500px.org/photo/64531569/q%3D80_h%3D300/2ce2f61714aebdca710967dfdc3efb04","https_url":"https://drscdn.500px.org/photo/64531569/q%3D80_h%3D300/2ce2f61714aebdca710967dfdc3efb04","format":"jpeg"},{"size":33,"url":"https://drscdn.500px.org/photo/64531569/q%3D80_h%3D600/ab6562d0581b359679ecc8ef2e939396","https_url":"https://drscdn.500px.org/photo/64531569/q%3D80_h%3D600/ab6562d0581b359679ecc8ef2e939396","format":"jpeg"},{"size":34,"url":"https://drscdn.500px.org/photo/64531569/q%3D80_m%3D1000/bd7dbc54a505e041a8c9a70dfa434272","https_url":"https://drscdn.500px.org/photo/64531569/q%3D80_m%3D1000/bd7dbc54a505e041a8c9a70dfa434272","format":"jpeg"},{"size":35,"url":"https://drscdn.500px.org/photo/64531569/q%3D80_m%3D1500/eb4d7f8f6a32d3e5c168c2cb55d29c12","https_url":"https://drscdn.500px.org/photo/64531569/q%3D80_m%3D1500/eb4d7f8f6a32d3e5c168c2cb55d29c12","format":"jpeg"},{"size":36,"url":"https://drscdn.500px.org/photo/64531569/q%3D80_m%3D2000/d519f91b8a568e7357a8a7fa1aabbe74","https_url":"https://drscdn.500px.org/photo/64531569/q%3D80_m%3D2000/d519f91b8a568e7357a8a7fa1aabbe74","format":"jpeg"},{"size":2048,"url":"https://drscdn.500px.org/photo/64531569/m%3D2048/4c52fb18cc2b2b6f91a0d04609786507","https_url":"https://drscdn.500px.org/photo/64531569/m%3D2048/4c52fb18cc2b2b6f91a0d04609786507","format":"jpeg"}],"url":"/photo/64531569/grand-canyon-afternoon-by-todd-hakala","positive_votes_count":108,"converted_bits":31,"tags":["landscape","river","arizona","canyon","grand","colorado","south","southwest","az","west","rim","CanonGetaway"],"watermark":false,"image_format":"jpeg","licensing_requested":false,"licensing_suggested":false,"is_free_photo":false,"user":{"id":111147,"username":"ToddHakala","firstname":"Todd","lastname":"Hakala","city":"Albuquerque","country":"US","usertype":0,"fullname":"Todd Hakala","userpic_url":"https://pacdn.500px.org/111147/ea167926a64ce9b32e44cbec61e3af4f75b762cb/1.jpg?2","userpic_https_url":"https://pacdn.500px.org/111147/ea167926a64ce9b32e44cbec61e3af4f75b762cb/1.jpg?2","cover_url":"https://pacdn.500px.org/111147/ea167926a64ce9b32e44cbec61e3af4f75b762cb/cover_2048.jpg?8","upgrade_status":1,"store_on":true,"affection":5217,"avatars":{"default":{"https":"https://pacdn.500px.org/111147/ea167926a64ce9b32e44cbec61e3af4f75b762cb/1.jpg?2"},"large":{"https":"https://pacdn.500px.org/111147/ea167926a64ce9b32e44cbec61e3af4f75b762cb/2.jpg?2"},"small":{"https":"https://pacdn.500px.org/111147/ea167926a64ce9b32e44cbec61e3af4f75b762cb/3.jpg?2"},"tiny":{"https":"https://pacdn.500px.org/111147/ea167926a64ce9b32e44cbec61e3af4f75b762cb/4.jpg?2"}},"followers_count":171}}]}
+ """ # noqa
+ response = mock.Mock(text=json)
+ results = www500px.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], u'Grand Canyon Afternoon')
+ self.assertEqual(results[0]['url'], 'https://500px.com/photo/64531569/grand-canyon-afternoon-by-todd-hakala')
+ self.assertEqual(results[0]['content'], u'Looking west on a very windy winter afternoon.')
diff --git a/tests/unit/engines/test_yacy.py b/tests/unit/engines/test_yacy.py
new file mode 100644
index 0000000..f49532c
--- /dev/null
+++ b/tests/unit/engines/test_yacy.py
@@ -0,0 +1,96 @@
+from collections import defaultdict
+import mock
+from searx.engines import yacy
+from searx.testing import SearxTestCase
+
+
+class TestYacyEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr_FR'
+ params = yacy.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('localhost', params['url'])
+ self.assertIn('fr', params['url'])
+
+ dicto['language'] = 'all'
+ params = yacy.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertNotIn('lr=lang_', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, yacy.response, None)
+ self.assertRaises(AttributeError, yacy.response, [])
+ self.assertRaises(AttributeError, yacy.response, '')
+ self.assertRaises(AttributeError, yacy.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(yacy.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(yacy.response(response), [])
+
+ json = """
+ {
+ "channels": [
+ {
+ "title": "YaCy P2P-Search for test",
+ "description": "Search for test",
+ "link": "http://search.yacy.de:7001/yacysearch.html?query=test&amp;resource=global&amp;contentdom=0",
+ "image": {
+ "url": "http://search.yacy.de:7001/env/grafics/yacy.png",
+ "title": "Search for test",
+ "link": "http://search.yacy.de:7001/yacysearch.html?query=test&amp;resource=global&amp;contentdom=0"
+ },
+ "totalResults": "249",
+ "startIndex": "0",
+ "itemsPerPage": "5",
+ "searchTerms": "test",
+ "items": [
+ {
+ "title": "This is the title",
+ "link": "http://this.is.the.url",
+ "code": "",
+ "description": "This should be the content",
+ "pubDate": "Sat, 08 Jun 2013 02:00:00 +0200",
+ "size": "44213",
+ "sizename": "43 kbyte",
+ "guid": "lzh_1T_5FP-A",
+ "faviconCode": "XTS4uQ_5FP-A",
+ "host": "www.gamestar.de",
+ "path": "/spiele/city-of-heroes-freedom/47019.html",
+ "file": "47019.html",
+ "urlhash": "lzh_1T_5FP-A",
+ "ranking": "0.20106804"
+ },
+ {
+ "title": "This is the title2",
+ "icon": "/ViewImage.png?maxwidth=96&amp;maxheight=96&amp;code=7EbAbW6BpPOA",
+ "image": "http://image.url/image.png",
+ "cache": "/ViewImage.png?quadratic=&amp;url=http://golem.ivwbox.de/cgi-bin/ivw/CP/G_INET?d=14071378",
+ "url": "http://this.is.the.url",
+ "urlhash": "7EbAbW6BpPOA",
+ "host": "www.golem.de",
+ "width": "-1",
+ "height": "-1"
+ }
+ ]
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = yacy.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url')
+ self.assertEqual(results[0]['content'], 'This should be the content')
+ self.assertEqual(results[1]['img_src'], 'http://image.url/image.png')
+ self.assertEqual(results[1]['content'], '')
+ self.assertEqual(results[1]['url'], 'http://this.is.the.url')
+ self.assertEqual(results[1]['title'], 'This is the title2')
diff --git a/tests/unit/engines/test_yahoo.py b/tests/unit/engines/test_yahoo.py
new file mode 100644
index 0000000..82c4d99
--- /dev/null
+++ b/tests/unit/engines/test_yahoo.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import yahoo
+from searx.testing import SearxTestCase
+
+
+class TestYahooEngine(SearxTestCase):
+
+ def test_parse_url(self):
+ test_url = 'http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA;_ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb' +\
+ '2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10/RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=' +\
+ 'dtcJsfP4mEeBOjnVfUQ-'
+ url = yahoo.parse_url(test_url)
+ self.assertEqual('https://this.is.the.url/', url)
+
+ test_url = 'http://r.search.yahoo.com/_ylt=A0LElb9JUSKcAEGRXNyoA;_ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb' +\
+ '2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10/RU=https%3a%2f%2fthis.is.the.url%2f/RS=' +\
+ 'dtcJsfP4mEeBOjnVfUQ-'
+ url = yahoo.parse_url(test_url)
+ self.assertEqual('https://this.is.the.url/', url)
+
+ test_url = 'https://this.is.the.url/'
+ url = yahoo.parse_url(test_url)
+ self.assertEqual('https://this.is.the.url/', url)
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['time_range'] = ''
+ dicto['language'] = 'fr_FR'
+ params = yahoo.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('search.yahoo.com', params['url'])
+ self.assertIn('fr', params['url'])
+ self.assertIn('cookies', params)
+ self.assertIn('sB', params['cookies'])
+ self.assertIn('fr', params['cookies']['sB'])
+
+ dicto['language'] = 'all'
+ params = yahoo.request(query, dicto)
+ self.assertIn('cookies', params)
+ self.assertIn('sB', params['cookies'])
+ self.assertIn('en', params['cookies']['sB'])
+ self.assertIn('en', params['url'])
+
+ def test_no_url_in_request_year_time_range(self):
+ dicto = defaultdict(dict)
+ query = 'test_query'
+ dicto['time_range'] = 'year'
+ params = yahoo.request(query, dicto)
+ self.assertEqual({}, params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, yahoo.response, None)
+ self.assertRaises(AttributeError, yahoo.response, [])
+ self.assertRaises(AttributeError, yahoo.response, '')
+ self.assertRaises(AttributeError, yahoo.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(yahoo.response(response), [])
+
+ html = """
+<ol class="reg mb-15 searchCenterMiddle">
+ <li class="first">
+ <div class="dd algo fst Sr">
+ <div class="compTitle">
+ <h3 class="title"><a class=" td-u" href="http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA;
+ _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10
+ /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-"
+ target="_blank" data-bid="54e712e13671c">
+ <b><b>This is the title</b></b></a>
+ </h3>
+ </div>
+ <div class="compText aAbs">
+ <p class="lh-18"><b><b>This is the </b>content</b>
+ </p>
+ </div>
+ </div>
+ </li>
+ <li>
+ <div class="dd algo lst Sr">
+ <div class="compTitle">
+ </div>
+ <div class="compText aAbs">
+ <p class="lh-18">This is the second content</p>
+ </div>
+ </div>
+ </li>
+</ol>
+<div class="dd assist fst lst AlsoTry" data-bid="54e712e138d04">
+ <div class="compTitle mb-4 h-17">
+ <h3 class="title">Also Try</h3> </div>
+ <table class="compTable m-0 ac-1st td-u fz-ms">
+ <tbody>
+ <tr>
+ <td class="w-50p pr-28"><a href="https://search.yahoo.com/"><B>This is the </B>suggestion<B></B></a>
+ </td>
+ </tr>
+ </table>
+</div>
+ """
+ response = mock.Mock(text=html)
+ results = yahoo.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://this.is.the.url/')
+ self.assertEqual(results[0]['content'], 'This is the content')
+ self.assertEqual(results[1]['suggestion'], 'This is the suggestion')
+
+ html = """
+<ol class="reg mb-15 searchCenterMiddle">
+ <li class="first">
+ <div class="dd algo fst Sr">
+ <div class="compTitle">
+ <h3 class="title"><a class=" td-u" href="http://r.search.yahoo.com/_ylt=A0LEb9JUSKcAEGRXNyoA;
+ _ylu=X3oDMTEzZm1qazYwBHNlYwNzcgRwb3MDMQRjb2xvA2Jm2dGlkA1NNRTcwM18x/RV=2/RE=1423106085/RO=10
+ /RU=https%3a%2f%2fthis.is.the.url%2f/RK=0/RS=dtcJsfP4mEeBOjnVfUQ-"
+ target="_blank" data-bid="54e712e13671c">
+ <b><b>This is the title</b></b></a>
+ </h3>
+ </div>
+ <div class="compText aAbs">
+ <p class="lh-18"><b><b>This is the </b>content</b>
+ </p>
+ </div>
+ </div>
+ </li>
+</ol>
+ """
+ response = mock.Mock(text=html)
+ results = yahoo.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title')
+ self.assertEqual(results[0]['url'], 'https://this.is.the.url/')
+ self.assertEqual(results[0]['content'], 'This is the content')
+
+ html = """
+ <li class="b_algo" u="0|5109|4755453613245655|UAGjXgIrPH5yh-o5oNHRx_3Zta87f_QO">
+ </li>
+ """
+ response = mock.Mock(text=html)
+ results = yahoo.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ def test_fetch_supported_languages(self):
+ html = """<html></html>"""
+ response = mock.Mock(text=html)
+ results = yahoo._fetch_supported_languages(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ html = """
+ <html>
+ <div>
+ <div id="yschlang">
+ <span>
+ <label><input value="lang_ar"></input></label>
+ </span>
+ <span>
+ <label><input value="lang_zh_chs"></input></label>
+ <label><input value="lang_zh_cht"></input></label>
+ </span>
+ </div>
+ </div>
+ </html>
+ """
+ response = mock.Mock(text=html)
+ languages = yahoo._fetch_supported_languages(response)
+ self.assertEqual(type(languages), list)
+ self.assertEqual(len(languages), 3)
+ self.assertIn('ar', languages)
+ self.assertIn('zh-chs', languages)
+ self.assertIn('zh-cht', languages)
diff --git a/tests/unit/engines/test_yahoo_news.py b/tests/unit/engines/test_yahoo_news.py
new file mode 100644
index 0000000..4d7fc0a
--- /dev/null
+++ b/tests/unit/engines/test_yahoo_news.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+from datetime import datetime
+import mock
+from searx.engines import yahoo_news
+from searx.testing import SearxTestCase
+
+
+class TestYahooNewsEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 1
+ dicto['language'] = 'fr_FR'
+ params = yahoo_news.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('news.search.yahoo.com', params['url'])
+ self.assertIn('fr', params['url'])
+ self.assertIn('cookies', params)
+ self.assertIn('sB', params['cookies'])
+ self.assertIn('fr', params['cookies']['sB'])
+
+ dicto['language'] = 'all'
+ params = yahoo_news.request(query, dicto)
+ self.assertIn('cookies', params)
+ self.assertIn('sB', params['cookies'])
+ self.assertIn('en', params['cookies']['sB'])
+ self.assertIn('en', params['url'])
+
+ def test_sanitize_url(self):
+ url = "test.url"
+ self.assertEqual(url, yahoo_news.sanitize_url(url))
+
+ url = "www.yahoo.com/;_ylt=test"
+ self.assertEqual("www.yahoo.com/", yahoo_news.sanitize_url(url))
+
+ def test_response(self):
+ self.assertRaises(AttributeError, yahoo_news.response, None)
+ self.assertRaises(AttributeError, yahoo_news.response, [])
+ self.assertRaises(AttributeError, yahoo_news.response, '')
+ self.assertRaises(AttributeError, yahoo_news.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(yahoo_news.response(response), [])
+
+ html = """
+ <ol class=" reg searchCenterMiddle">
+ <li class="first">
+ <div class="compTitle">
+ <h3>
+ <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
+ This is
+ the <b>title</b>...
+ </a>
+ </h3>
+ </div>
+ <div>
+ <span class="cite">Business via Yahoo!</span>
+ <span class="tri fc-2nd ml-10">May 01 10:00 AM</span>
+ </div>
+ <div class="compText">
+ This is the content
+ </div>
+ </li>
+ <li class="first">
+ <div class="compTitle">
+ <h3>
+ <a class="yschttl spt" target="_blank">
+ </a>
+ </h3>
+ </div>
+ <div class="compText">
+ </div>
+ </li>
+ </ol>
+ """
+ response = mock.Mock(text=html)
+ results = yahoo_news.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'This is the title...')
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
+ self.assertEqual(results[0]['content'], 'This is the content')
+
+ html = """
+ <ol class=" reg searchCenterMiddle">
+ <li class="first">
+ <div class="compTitle">
+ <h3>
+ <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
+ This is
+ the <b>title</b>...
+ </a>
+ </h3>
+ </div>
+ <div>
+ <span class="cite">Business via Yahoo!</span>
+ <span class="tri fc-2nd ml-10">2 hours, 22 minutes ago</span>
+ </div>
+ <div class="compText">
+ This is the content
+ </div>
+ </li>
+ <li>
+ <div class="compTitle">
+ <h3>
+ <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
+ This is
+ the <b>title</b>...
+ </a>
+ </h3>
+ </div>
+ <div>
+ <span class="cite">Business via Yahoo!</span>
+ <span class="tri fc-2nd ml-10">22 minutes ago</span>
+ </div>
+ <div class="compText">
+ This is the content
+ </div>
+ </li>
+ <li>
+ <div class="compTitle">
+ <h3>
+ <a class="yschttl spt" href="http://this.is.the.url" target="_blank">
+ This is
+ the <b>title</b>...
+ </a>
+ </h3>
+ </div>
+ <div>
+ <span class="cite">Business via Yahoo!</span>
+ <span class="tri fc-2nd ml-10">Feb 03 09:45AM 1900</span>
+ </div>
+ <div class="compText">
+ This is the content
+ </div>
+ </li>
+ </ol>
+ """
+ response = mock.Mock(text=html)
+ results = yahoo_news.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 3)
+ self.assertEqual(results[0]['title'], 'This is the title...')
+ self.assertEqual(results[0]['url'], 'http://this.is.the.url/')
+ self.assertEqual(results[0]['content'], 'This is the content')
+ self.assertEqual(results[2]['publishedDate'].year, datetime.now().year)
diff --git a/tests/unit/engines/test_youtube_api.py b/tests/unit/engines/test_youtube_api.py
new file mode 100644
index 0000000..0d4d478
--- /dev/null
+++ b/tests/unit/engines/test_youtube_api.py
@@ -0,0 +1,111 @@
+from collections import defaultdict
+import mock
+from searx.engines import youtube_api
+from searx.testing import SearxTestCase
+
+
+class TestYoutubeAPIEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['language'] = 'fr_FR'
+ params = youtube_api.request(query, dicto)
+ self.assertTrue('url' in params)
+ self.assertTrue(query in params['url'])
+ self.assertIn('googleapis.com', params['url'])
+ self.assertIn('youtube', params['url'])
+ self.assertIn('fr', params['url'])
+
+ dicto['language'] = 'all'
+ params = youtube_api.request(query, dicto)
+ self.assertFalse('fr' in params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, youtube_api.response, None)
+ self.assertRaises(AttributeError, youtube_api.response, [])
+ self.assertRaises(AttributeError, youtube_api.response, '')
+ self.assertRaises(AttributeError, youtube_api.response, '[]')
+
+ response = mock.Mock(text='{}')
+ self.assertEqual(youtube_api.response(response), [])
+
+ response = mock.Mock(text='{"data": []}')
+ self.assertEqual(youtube_api.response(response), [])
+
+ json = """
+ {
+ "kind": "youtube#searchListResponse",
+ "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/YJQDcTBCDcaBvl-sRZJoXdvy1ME",
+ "nextPageToken": "CAUQAA",
+ "pageInfo": {
+ "totalResults": 1000000,
+ "resultsPerPage": 20
+ },
+ "items": [
+ {
+ "kind": "youtube#searchResult",
+ "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/IbLO64BMhbHIgWLwLw7MDYe7Hs4",
+ "id": {
+ "kind": "youtube#video",
+ "videoId": "DIVZCPfAOeM"
+ },
+ "snippet": {
+ "publishedAt": "2015-05-29T22:41:04.000Z",
+ "channelId": "UCNodmx1ERIjKqvcJLtdzH5Q",
+ "title": "Title",
+ "description": "Description",
+ "thumbnails": {
+ "default": {
+ "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/default.jpg"
+ },
+ "medium": {
+ "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/mqdefault.jpg"
+ },
+ "high": {
+ "url": "https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg"
+ }
+ },
+ "channelTitle": "MinecraftUniverse",
+ "liveBroadcastContent": "none"
+ }
+ }
+ ]
+ }
+ """
+ response = mock.Mock(text=json)
+ results = youtube_api.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'https://www.youtube.com/watch?v=DIVZCPfAOeM')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertEqual(results[0]['thumbnail'], 'https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg')
+ self.assertTrue('DIVZCPfAOeM' in results[0]['embedded'])
+
+ json = """
+ {
+ "kind": "youtube#searchListResponse",
+ "etag": "xmg9xJZuZD438sF4hb-VcBBREXc/YJQDcTBCDcaBvl-sRZJoXdvy1ME",
+ "nextPageToken": "CAUQAA",
+ "pageInfo": {
+ "totalResults": 1000000,
+ "resultsPerPage": 20
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = youtube_api.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
+
+ json = """
+ {"toto":{"entry":[]
+ }
+ }
+ """
+ response = mock.Mock(text=json)
+ results = youtube_api.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)
diff --git a/tests/unit/engines/test_youtube_noapi.py b/tests/unit/engines/test_youtube_noapi.py
new file mode 100644
index 0000000..41dcbb7
--- /dev/null
+++ b/tests/unit/engines/test_youtube_noapi.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+from collections import defaultdict
+import mock
+from searx.engines import youtube_noapi
+from searx.testing import SearxTestCase
+
+
+class TestYoutubeNoAPIEngine(SearxTestCase):
+
+ def test_request(self):
+ query = 'test_query'
+ dicto = defaultdict(dict)
+ dicto['pageno'] = 0
+ dicto['time_range'] = ''
+ params = youtube_noapi.request(query, dicto)
+ self.assertIn('url', params)
+ self.assertIn(query, params['url'])
+ self.assertIn('youtube.com', params['url'])
+
+ def test_time_range_search(self):
+ dicto = defaultdict(dict)
+ query = 'test_query'
+ dicto['time_range'] = 'year'
+ params = youtube_noapi.request(query, dicto)
+ self.assertIn('&sp=EgIIBQ%253D%253D', params['url'])
+
+ dicto['time_range'] = 'month'
+ params = youtube_noapi.request(query, dicto)
+ self.assertIn('&sp=EgIIBA%253D%253D', params['url'])
+
+ dicto['time_range'] = 'week'
+ params = youtube_noapi.request(query, dicto)
+ self.assertIn('&sp=EgIIAw%253D%253D', params['url'])
+
+ dicto['time_range'] = 'day'
+ params = youtube_noapi.request(query, dicto)
+ self.assertIn('&sp=EgIIAg%253D%253D', params['url'])
+
+ def test_response(self):
+ self.assertRaises(AttributeError, youtube_noapi.response, None)
+ self.assertRaises(AttributeError, youtube_noapi.response, [])
+ self.assertRaises(AttributeError, youtube_noapi.response, '')
+ self.assertRaises(AttributeError, youtube_noapi.response, '[]')
+
+ response = mock.Mock(text='<html></html>')
+ self.assertEqual(youtube_noapi.response(response), [])
+
+ html = """
+ <ol id="item-section-063864" class="item-section">
+ <li>
+ <div class="yt-lockup yt-lockup-tile yt-lockup-video vve-check clearfix yt-uix-tile"
+ data-context-item-id="DIVZCPfAOeM"
+ data-visibility-tracking="CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JECx_-GK5uqMpcIB">
+ <div class="yt-lockup-dismissable"><div class="yt-lockup-thumbnail contains-addto">
+ <a aria-hidden="true" href="/watch?v=DIVZCPfAOeM" class=" yt-uix-sessionlink pf-link"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JFIEdGVzdA">
+ <div class="yt-thumb video-thumb"><img src="//i.ytimg.com/vi/DIVZCPfAOeM/mqdefault.jpg"
+ width="196" height="110"/></div><span class="video-time" aria-hidden="true">11:35</span></a>
+ <span class="thumb-menu dark-overflow-action-menu video-actions">
+ </span>
+ </div>
+ <div class="yt-lockup-content">
+ <h3 class="yt-lockup-title">
+ <a href="/watch?v=DIVZCPfAOeM"
+ class="yt-uix-tile-link yt-ui-ellipsis yt-ui-ellipsis-2 yt-uix-sessionlink spf-link"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JFIEdGVzdA"
+ title="Top Speed Test Kawasaki Ninja H2 (Thailand) By. MEHAY SUPERBIKE"
+ aria-describedby="description-id-259079" rel="spf-prefetch" dir="ltr">
+ Title
+ </a>
+ <span class="accessible-description" id="description-id-259079"> - Durée : 11:35.</span>
+ </h3>
+ <div class="yt-lockup-byline">de
+ <a href="/user/mheejapan" class=" yt-uix-sessionlink spf-link g-hovercard"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JA" data-ytid="UCzEesu54Hjs0uRKmpy66qeA"
+ data-name="">MEHAY SUPERBIKE</a></div><div class="yt-lockup-meta">
+ <ul class="yt-lockup-meta-info">
+ <li>il y a 20 heures</li>
+ <li>8 424 vues</li>
+ </ul>
+ </div>
+ <div class="yt-lockup-description yt-ui-ellipsis yt-ui-ellipsis-2" dir="ltr">
+ Description
+ </div>
+ <div class="yt-lockup-badges">
+ <ul class="yt-badge-list ">
+ <li class="yt-badge-item" >
+ <span class="yt-badge">Nouveauté</span>
+ </li>
+ <li class="yt-badge-item" ><span class="yt-badge " >HD</span></li>
+ </ul>
+ </div>
+ <div class="yt-lockup-action-menu yt-uix-menu-container">
+ <div class="yt-uix-menu yt-uix-videoactionmenu hide-until-delayloaded"
+ data-video-id="DIVZCPfAOeM" data-menu-content-id="yt-uix-videoactionmenu-menu">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ </ol>
+ """
+ response = mock.Mock(text=html)
+ results = youtube_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]['title'], 'Title')
+ self.assertEqual(results[0]['url'], 'https://www.youtube.com/watch?v=DIVZCPfAOeM')
+ self.assertEqual(results[0]['content'], 'Description')
+ self.assertEqual(results[0]['thumbnail'], 'https://i.ytimg.com/vi/DIVZCPfAOeM/hqdefault.jpg')
+ self.assertTrue('DIVZCPfAOeM' in results[0]['embedded'])
+
+ html = """
+ <ol id="item-section-063864" class="item-section">
+ <li>
+ <div class="yt-lockup yt-lockup-tile yt-lockup-video vve-check clearfix yt-uix-tile"
+ data-context-item-id="DIVZCPfAOeM"
+ data-visibility-tracking="CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JECx_-GK5uqMpcIB">
+ <div class="yt-lockup-dismissable"><div class="yt-lockup-thumbnail contains-addto">
+ <a aria-hidden="true" href="/watch?v=DIVZCPfAOeM" class=" yt-uix-sessionlink pf-link"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JFIEdGVzdA">
+ <div class="yt-thumb video-thumb"><img src="//i.ytimg.com/vi/DIVZCPfAOeM/mqdefault.jpg"
+ width="196" height="110"/></div><span class="video-time" aria-hidden="true">11:35</span></a>
+ <span class="thumb-menu dark-overflow-action-menu video-actions">
+ </span>
+ </div>
+ <div class="yt-lockup-content">
+ <h3 class="yt-lockup-title">
+ <span class="accessible-description" id="description-id-259079"> - Durée : 11:35.</span>
+ </h3>
+ <div class="yt-lockup-byline">de
+ <a href="/user/mheejapan" class=" yt-uix-sessionlink spf-link g-hovercard"
+ data-sessionlink="itct=CBgQ3DAYACITCPGXnYau6sUCFZEIHAod-VQASCj0JA" data-ytid="UCzEesu54Hjs0uRKmpy66qeA"
+ data-name="">MEHAY SUPERBIKE</a></div><div class="yt-lockup-meta">
+ <ul class="yt-lockup-meta-info">
+ <li>il y a 20 heures</li>
+ <li>8 424 vues</li>
+ </ul>
+ </div>
+ <div class="yt-lockup-badges">
+ <ul class="yt-badge-list ">
+ <li class="yt-badge-item" >
+ <span class="yt-badge">Nouveauté</span>
+ </li>
+ <li class="yt-badge-item" ><span class="yt-badge " >HD</span></li>
+ </ul>
+ </div>
+ <div class="yt-lockup-action-menu yt-uix-menu-container">
+ <div class="yt-uix-menu yt-uix-videoactionmenu hide-until-delayloaded"
+ data-video-id="DIVZCPfAOeM" data-menu-content-id="yt-uix-videoactionmenu-menu">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ </ol>
+ """
+ response = mock.Mock(text=html)
+ results = youtube_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 1)
+
+ html = """
+ <ol id="item-section-063864" class="item-section">
+ <li>
+ </li>
+ </ol>
+ """
+ response = mock.Mock(text=html)
+ results = youtube_noapi.response(response)
+ self.assertEqual(type(results), list)
+ self.assertEqual(len(results), 0)