test.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. import contextlib
  2. from selenium import webdriver
  3. from selenium.webdriver import DesiredCapabilities
  4. from selenium.webdriver.common.by import By
  5. from selenium.webdriver.support import expected_conditions
  6. from selenium.webdriver.support.expected_conditions import staleness_of
  7. from selenium.webdriver.support.wait import WebDriverWait
  8. import configparser
  9. import json
  10. import os.path
  11. import requests
  12. import string
  13. import threading
  14. import time
  15. import unittest
  16. import tornado.httpserver
  17. import tornado.testing
  18. from terroroftinytown.tracker.app import Application
  19. from terroroftinytown.tracker.bootstrap import ApplicationBootstrap
  20. from terroroftinytown.tracker.database import Database
  21. from terroroftinytown.tracker.model import MIN_CLIENT_VERSION_OVERRIDE
  22. from terroroftinytown.tracker.stats import Stats
  23. from terroroftinytown.util.jsonutil import NativeStringJSONEncoder
  24. from terroroftinytown.client import VERSION
  25. class IOLoopThread(threading.Thread):
  26. def __init__(self):
  27. threading.Thread.__init__(self)
  28. self.daemon = True
  29. self.io_loop = tornado.ioloop.IOLoop()
  30. def run(self):
  31. self.io_loop.start()
  32. def stop(self):
  33. self.io_loop.add_callback(self.io_loop.stop)
  34. class TestTracker(unittest.TestCase, ApplicationBootstrap):
  35. def __init__(self, *args):
  36. unittest.TestCase.__init__(self, *args)
  37. ApplicationBootstrap.__init__(self)
  38. def parse_args(self, args=None):
  39. pass
  40. def load_config(self):
  41. config_path = os.path.join(
  42. os.path.dirname(__file__),
  43. 'tracker_unittest.conf'
  44. )
  45. self.config.read([config_path])
  46. def setup_database(self):
  47. print('Set up database')
  48. self.database = Database(
  49. path=self.config['database']['path'],
  50. delete_everything='yes-really!'
  51. )
  52. def setup_application(self):
  53. self.application = Application(self.database, debug=True, cookie_secret='TEST')
  54. def boot(self):
  55. self.io_loop_thread = IOLoopThread()
  56. socket_obj, self.port = tornado.testing.bind_unused_port()
  57. http_server = tornado.httpserver.HTTPServer(
  58. self.application, io_loop=self.io_loop_thread.io_loop
  59. )
  60. http_server.add_socket(socket_obj)
  61. self.io_loop_thread.start()
  62. def setUp(self):
  63. self.start()
  64. Stats.instance.clear()
  65. if os.environ.get('RUN_MARIONETTE'):
  66. # Firefox 47+
  67. firefox_capabilities = DesiredCapabilities.FIREFOX
  68. firefox_capabilities['marionette'] = True
  69. self.driver = webdriver.Firefox(capabilities=firefox_capabilities)
  70. elif os.environ.get('RUN_CHROMEDRIVER'):
  71. self.driver = webdriver.Chrome()
  72. else:
  73. self.driver = webdriver.Firefox()
  74. def tearDown(self):
  75. self.io_loop_thread.stop()
  76. self.driver.close()
  77. def get_url(self, path):
  78. return 'http://localhost:{0}{1}'.format(self.port, path)
  79. def sleep(self, seconds=0.5):
  80. time.sleep(seconds)
  81. @contextlib.contextmanager
  82. def wait_for_page_load(self, timeout=30):
  83. # http://www.obeythetestinggoat.com/how-to-get-selenium-to-wait-for-page-load-after-a-click.html
  84. old_page = self.driver.find_element_by_tag_name('html')
  85. yield
  86. WebDriverWait(self.driver, timeout).until(
  87. staleness_of(old_page)
  88. )
  89. def test_all(self):
  90. self.sign_in()
  91. self.sleep()
  92. self.sign_out()
  93. self.sleep()
  94. self.sign_in_bad()
  95. self.sleep()
  96. self.sign_in()
  97. self.sleep()
  98. self.create_user()
  99. self.sleep()
  100. self.sign_out()
  101. self.sleep()
  102. self.sign_in_second_user()
  103. self.sleep()
  104. self.sign_out()
  105. self.sleep()
  106. self.sign_in()
  107. self.sleep()
  108. self.create_project()
  109. self.sleep()
  110. self.config_project_settings()
  111. self.sleep()
  112. self.populate_queue()
  113. self.sleep()
  114. self.enable_queue()
  115. self.sleep()
  116. self.get_project_settings()
  117. self.sleep()
  118. self.claim_with_outdated_script()
  119. self.sleep()
  120. self.claim_and_return_an_item()
  121. self.sleep()
  122. # these tests are run after an item have been submitted
  123. self.global_stats()
  124. self.sleep()
  125. self.live_stats()
  126. self.sleep()
  127. self.live_stats_update()
  128. def global_stats(self):
  129. self.driver.get(self.get_url('/'))
  130. WebDriverWait(self.driver, 10).until(
  131. expected_conditions.presence_of_element_located((By.CLASS_NAME, 'ng-binding'))
  132. )
  133. self.sleep(1)
  134. self.assertEqual(
  135. self.driver.find_element_by_xpath('id("globalstats")//strong[1]').text,
  136. '20'
  137. )
  138. self.assertEqual(
  139. self.driver.find_element_by_xpath('id("globalstats")//strong[2]').text,
  140. '1'
  141. )
  142. def live_stats(self):
  143. self.driver.get(self.get_url('/'))
  144. WebDriverWait(self.driver, 10).until(
  145. expected_conditions.presence_of_element_located((By.CLASS_NAME, 'ng-binding'))
  146. )
  147. self.sleep(1)
  148. self.assertEqual(
  149. self.driver.find_element_by_xpath('id("leaderboard-recent")//tbody/tr[1]/td[1]').text,
  150. 'SMAUG'
  151. )
  152. self.assertEqual(
  153. self.driver.find_element_by_xpath('id("leaderboard-recent")//tbody/tr[1]/td[2]').text,
  154. '1'
  155. )
  156. self.assertEqual(
  157. self.driver.find_element_by_xpath('id("leaderboard-recent")//tbody/tr[1]/td[3]').text,
  158. '20'
  159. )
  160. self.assertEqual(
  161. self.driver.find_element_by_xpath('id("leaderboard-recent")//tbody/tr[1]/td[4]').text,
  162. 'test_project'
  163. )
  164. def live_stats_update(self):
  165. self.driver.get(self.get_url('/'))
  166. self.sleep(1)
  167. WebDriverWait(self.driver, 10).until(
  168. expected_conditions.presence_of_element_located((By.CLASS_NAME, 'ng-binding'))
  169. )
  170. self.claim_and_return_an_item()
  171. WebDriverWait(self.driver, 10).until(
  172. expected_conditions.presence_of_element_located((By.XPATH, 'id("leaderboard-recent")//tbody/tr[2]'))
  173. )
  174. self.sleep(1)
  175. self.assertEqual(
  176. self.driver.find_element_by_xpath('id("leaderboard-recent")//tbody/tr[1]/td[3]').text,
  177. '20'
  178. )
  179. self.assertEqual(
  180. self.driver.find_element_by_xpath('id("leaderboard-totals")//tbody/tr[1]/td[2]').text,
  181. '2'
  182. )
  183. self.assertEqual(
  184. self.driver.find_element_by_xpath('id("leaderboard-totals")//tbody/tr[1]/td[3]').text,
  185. '40'
  186. )
  187. self.assertEqual(
  188. self.driver.find_element_by_xpath('id("globalstats")//strong[1]').text,
  189. '40'
  190. )
  191. self.assertEqual(
  192. self.driver.find_element_by_xpath('id("globalstats")//strong[2]').text,
  193. '2'
  194. )
  195. def sign_in(self):
  196. self.driver.get(self.get_url('/'))
  197. element = self.driver.find_element_by_link_text('Tracker admin')
  198. with self.wait_for_page_load():
  199. element.click()
  200. element = self.driver.find_element_by_name('username')
  201. element.send_keys('test_user')
  202. element = self.driver.find_element_by_name('password')
  203. element.send_keys('test_password')
  204. element.submit()
  205. WebDriverWait(self.driver, 10).until(
  206. expected_conditions.title_is('Projects')
  207. )
  208. def sign_in_bad(self):
  209. self.driver.get(self.get_url('/'))
  210. element = self.driver.find_element_by_link_text('Tracker admin')
  211. with self.wait_for_page_load():
  212. element.click()
  213. element = self.driver.find_element_by_name('username')
  214. element.send_keys('test_user')
  215. element = self.driver.find_element_by_name('password')
  216. element.send_keys('badpass')
  217. element.submit()
  218. WebDriverWait(self.driver, 20).until(
  219. expected_conditions.text_to_be_present_in_element(
  220. (By.TAG_NAME, 'body'), 'Log in failed'
  221. )
  222. )
  223. def sign_out(self):
  224. element = self.driver.find_element_by_partial_link_text('Log out')
  225. element.click()
  226. WebDriverWait(self.driver, 10).until(
  227. expected_conditions.title_is('URLTeam Tracker')
  228. )
  229. def create_user(self):
  230. element = self.driver.find_element_by_link_text('Users')
  231. element.click()
  232. WebDriverWait(self.driver, 10).until(
  233. expected_conditions.title_is('Users')
  234. )
  235. element = self.driver.find_element_by_name('username')
  236. element.send_keys('user2')
  237. element = self.driver.find_element_by_name('password')
  238. element.send_keys('userpass1')
  239. element.submit()
  240. element = self.driver.find_element_by_link_text('Users')
  241. element.click()
  242. WebDriverWait(self.driver, 10).until(
  243. expected_conditions.text_to_be_present_in_element(
  244. (By.TAG_NAME, 'body'), 'user2'
  245. )
  246. )
  247. def sign_in_second_user(self):
  248. self.driver.get(self.get_url('/'))
  249. element = self.driver.find_element_by_link_text('Tracker admin')
  250. with self.wait_for_page_load():
  251. element.click()
  252. element = self.driver.find_element_by_name('username')
  253. element.send_keys('user2')
  254. element = self.driver.find_element_by_name('password')
  255. element.send_keys('userpass1')
  256. element.submit()
  257. WebDriverWait(self.driver, 10).until(
  258. expected_conditions.title_is('Projects')
  259. )
  260. def create_project(self):
  261. element = self.driver.find_element_by_link_text('Projects')
  262. with self.wait_for_page_load():
  263. element.click()
  264. WebDriverWait(self.driver, 10).until(
  265. expected_conditions.title_is('Projects')
  266. )
  267. element = self.driver.find_element_by_name('name')
  268. element.send_keys('test_project')
  269. element.submit()
  270. def config_project_settings(self):
  271. self.driver.get(self.get_url('/admin/'))
  272. element = self.driver.find_element_by_link_text('Projects')
  273. with self.wait_for_page_load():
  274. element.click()
  275. WebDriverWait(self.driver, 10).until(
  276. expected_conditions.title_is('Projects')
  277. )
  278. element = self.driver.find_element_by_link_text('test_project')
  279. with self.wait_for_page_load():
  280. element.click()
  281. element = self.driver.find_element_by_link_text('Shortener Settings')
  282. with self.wait_for_page_load():
  283. element.click()
  284. element = self.driver.find_element_by_name('alphabet')
  285. element.clear()
  286. element.send_keys(string.ascii_lowercase)
  287. element.send_keys(string.ascii_uppercase)
  288. element.send_keys(string.digits)
  289. element = self.driver.find_element_by_name('url_template')
  290. element.clear()
  291. element.send_keys('http://www.example.com/{shortcode}')
  292. element = self.driver.find_element_by_name('request_delay')
  293. element.clear()
  294. element.send_keys('1.0')
  295. element = self.driver.find_element_by_name('redirect_codes')
  296. element.clear()
  297. element.send_keys('301 302')
  298. element = self.driver.find_element_by_name('no_redirect_codes')
  299. element.clear()
  300. element.send_keys('404')
  301. element = self.driver.find_element_by_name('unavailable_codes')
  302. element.clear()
  303. element.send_keys('200')
  304. element = self.driver.find_element_by_name('banned_codes')
  305. element.clear()
  306. element.send_keys('420')
  307. element = self.driver.find_element_by_name('body_regex')
  308. element.clear()
  309. element.send_keys('<a id="redir_link" href="[^"]+">')
  310. element = self.driver.find_element_by_name('location_anti_regex')
  311. element.clear()
  312. element.send_keys('^/error.php$')
  313. element.submit()
  314. def populate_queue(self):
  315. element = self.driver.find_element_by_link_text('Claims')
  316. element.click()
  317. WebDriverWait(self.driver, 10).until(
  318. expected_conditions.title_is('Items')
  319. )
  320. element = self.driver.find_element_by_name('items')
  321. element.send_keys('0-19\n20-39')
  322. element.submit()
  323. def enable_queue(self):
  324. element = self.driver.find_element_by_link_text('Queue Settings')
  325. element.click()
  326. WebDriverWait(self.driver, 10).until(
  327. expected_conditions.title_is('Queue Settings')
  328. )
  329. element = self.driver.find_element_by_name('enabled')
  330. element.click()
  331. element.submit()
  332. def get_project_settings(self):
  333. response = requests.get(
  334. self.get_url('/api/project_settings?name=test_project'),
  335. )
  336. print(response.reason)
  337. self.assertEqual(200, response.status_code)
  338. settings = response.json()
  339. self.assertEqual('test_project', settings['name'])
  340. def claim_with_outdated_script(self):
  341. response = requests.post(
  342. self.get_url('/api/get'),
  343. data={
  344. 'username': 'SMAUG',
  345. 'version':-1,
  346. 'client_version':-1,
  347. }
  348. )
  349. print(response.reason)
  350. self.assertEqual(412, response.status_code)
  351. def claim_and_return_an_item(self):
  352. response = requests.post(
  353. self.get_url('/api/get'),
  354. data={
  355. 'username': 'SMAUG',
  356. 'version': VERSION,
  357. 'client_version': MIN_CLIENT_VERSION_OVERRIDE
  358. }
  359. )
  360. print(response.reason)
  361. self.assertEqual(200, response.status_code)
  362. item = response.json()
  363. print(item)
  364. item['project']
  365. self.report_error(item)
  366. response = requests.post(
  367. self.get_url('/api/done'),
  368. data={
  369. 'claim_id': item['id'],
  370. 'tamper_key': item['tamper_key'],
  371. 'results': json.dumps({
  372. 'abcd': {
  373. 'url': 'http://ultraarchive.org',
  374. 'encoding': 'ascii',
  375. }
  376. }, cls=NativeStringJSONEncoder)
  377. }
  378. )
  379. print(response.reason)
  380. self.assertEqual(200, response.status_code)
  381. doc = response.json()
  382. print(doc)
  383. def report_error(self, item):
  384. response = requests.post(
  385. self.get_url('/api/error'),
  386. data={
  387. 'claim_id': item['id'],
  388. 'tamper_key': item['tamper_key'],
  389. 'message': 'asdfasdf'
  390. }
  391. )
  392. print(response.reason)
  393. self.assertEqual(200, response.status_code)