test_pagure_flask_ui_remote_pr.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. # -*- coding: utf-8 -*-
  2. """
  3. (c) 2018 - Copyright Red Hat Inc
  4. Authors:
  5. Pierre-Yves Chibon <pingou@pingoured.fr>
  6. """
  7. from __future__ import unicode_literals
  8. __requires__ = ['SQLAlchemy >= 0.8']
  9. import pkg_resources
  10. import json
  11. import os
  12. import re
  13. import shutil
  14. import sys
  15. import tempfile
  16. import time
  17. import unittest
  18. import pygit2
  19. import wtforms
  20. from mock import patch, MagicMock
  21. from bs4 import BeautifulSoup
  22. sys.path.insert(0, os.path.join(os.path.dirname(
  23. os.path.abspath(__file__)), '..'))
  24. import pagure
  25. import pagure.lib
  26. import tests
  27. from pagure.lib.repo import PagureRepo
  28. from pagure.lib.git import _make_signature
  29. class PagureRemotePRtests(tests.Modeltests):
  30. """ Tests for remote PRs in pagure """
  31. def setUp(self):
  32. """ Set up the environment. """
  33. super(PagureRemotePRtests, self).setUp()
  34. self.newpath = tempfile.mkdtemp(prefix='pagure-fork-test')
  35. self.old_value = pagure.config.config['REMOTE_GIT_FOLDER']
  36. pagure.config.config['REMOTE_GIT_FOLDER'] = os.path.join(
  37. self.path, 'remotes')
  38. def tearDown(self):
  39. """ Clear things up. """
  40. super(PagureRemotePRtests, self).tearDown()
  41. pagure.config.config['REMOTE_GIT_FOLDER'] = self.old_value
  42. shutil.rmtree(self.newpath)
  43. def set_up_git_repo(self, new_project=None, branch_from='feature'):
  44. """ Set up the git repo and create the corresponding PullRequest
  45. object.
  46. """
  47. # Create a git repo to play with
  48. gitrepo = os.path.join(self.path, 'repos', 'test.git')
  49. repo = pygit2.init_repository(gitrepo, bare=True)
  50. repopath = os.path.join(self.newpath, 'test')
  51. clone_repo = pygit2.clone_repository(gitrepo, repopath)
  52. # Create a file in that git repo
  53. with open(os.path.join(repopath, 'sources'), 'w') as stream:
  54. stream.write('foo\n bar')
  55. clone_repo.index.add('sources')
  56. clone_repo.index.write()
  57. try:
  58. com = repo.revparse_single('HEAD')
  59. prev_commit = [com.oid.hex]
  60. except:
  61. prev_commit = []
  62. # Commits the files added
  63. tree = clone_repo.index.write_tree()
  64. author = _make_signature(
  65. 'Alice Author', 'alice@authors.tld')
  66. committer = _make_signature(
  67. 'Cecil Committer', 'cecil@committers.tld')
  68. clone_repo.create_commit(
  69. 'refs/heads/master', # the name of the reference to update
  70. author,
  71. committer,
  72. 'Add sources file for testing',
  73. # binary string representing the tree object ID
  74. tree,
  75. # list of binary strings representing parents of the new commit
  76. prev_commit
  77. )
  78. time.sleep(1)
  79. refname = 'refs/heads/master:refs/heads/master'
  80. ori_remote = clone_repo.remotes[0]
  81. PagureRepo.push(ori_remote, refname)
  82. first_commit = repo.revparse_single('HEAD')
  83. with open(os.path.join(repopath, '.gitignore'), 'w') as stream:
  84. stream.write('*~')
  85. clone_repo.index.add('.gitignore')
  86. clone_repo.index.write()
  87. # Commits the files added
  88. tree = clone_repo.index.write_tree()
  89. author = _make_signature(
  90. 'Alice Äuthòr', 'alice@äuthòrs.tld')
  91. committer = _make_signature(
  92. 'Cecil Cõmmîttër', 'cecil@cõmmîttërs.tld')
  93. clone_repo.create_commit(
  94. 'refs/heads/master',
  95. author,
  96. committer,
  97. 'Add .gitignore file for testing',
  98. # binary string representing the tree object ID
  99. tree,
  100. # list of binary strings representing parents of the new commit
  101. [first_commit.oid.hex]
  102. )
  103. refname = 'refs/heads/master:refs/heads/master'
  104. ori_remote = clone_repo.remotes[0]
  105. PagureRepo.push(ori_remote, refname)
  106. # Set the second repo
  107. new_gitrepo = repopath
  108. if new_project:
  109. # Create a new git repo to play with
  110. new_gitrepo = os.path.join(self.newpath, new_project.fullname)
  111. if not os.path.exists(new_gitrepo):
  112. os.makedirs(new_gitrepo)
  113. new_repo = pygit2.clone_repository(gitrepo, new_gitrepo)
  114. repo = pygit2.Repository(new_gitrepo)
  115. # Edit the sources file again
  116. with open(os.path.join(new_gitrepo, 'sources'), 'w') as stream:
  117. stream.write('foo\n bar\nbaz\n boose')
  118. repo.index.add('sources')
  119. repo.index.write()
  120. # Commits the files added
  121. tree = repo.index.write_tree()
  122. author = _make_signature(
  123. 'Alice Author', 'alice@authors.tld')
  124. committer = _make_signature(
  125. 'Cecil Committer', 'cecil@committers.tld')
  126. repo.create_commit(
  127. 'refs/heads/%s' % branch_from,
  128. author,
  129. committer,
  130. 'A commit on branch %s' % branch_from,
  131. tree,
  132. [first_commit.oid.hex]
  133. )
  134. refname = 'refs/heads/%s' % (branch_from)
  135. ori_remote = repo.remotes[0]
  136. PagureRepo.push(ori_remote, refname)
  137. @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
  138. def test_new_remote_pr_unauth(self):
  139. """ Test creating a new remote PR un-authenticated. """
  140. tests.create_projects(self.session)
  141. tests.create_projects_git(
  142. os.path.join(self.path, 'requests'), bare=True)
  143. self.set_up_git_repo()
  144. # Before
  145. project = pagure.lib.get_authorized_project(self.session, 'test')
  146. self.assertEqual(len(project.requests), 0)
  147. # Try creating a remote PR
  148. output = self.app.get('/test/diff/remote')
  149. self.assertEqual(output.status_code, 302)
  150. self.assertIn(
  151. 'You should be redirected automatically to target URL: '
  152. '<a href="/login/?', output.get_data(as_text=True))
  153. @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
  154. def test_new_remote_pr_auth(self):
  155. """ Test creating a new remote PR authenticated. """
  156. tests.create_projects(self.session)
  157. tests.create_projects_git(
  158. os.path.join(self.path, 'requests'), bare=True)
  159. self.set_up_git_repo()
  160. # Before
  161. self.session = pagure.lib.create_session(self.dbpath)
  162. project = pagure.lib.get_authorized_project(self.session, 'test')
  163. self.assertEqual(len(project.requests), 0)
  164. # Try creating a remote PR
  165. user = tests.FakeUser(username='foo')
  166. with tests.user_set(self.app.application, user):
  167. output = self.app.get('/test/diff/remote')
  168. self.assertEqual(output.status_code, 200)
  169. self.assertIn(
  170. '<h2>New remote pull-request</h2>',
  171. output.get_data(as_text=True))
  172. csrf_token = self.get_csrf(output=output)
  173. with patch(
  174. 'pagure.forms.RemoteRequestPullForm.git_repo.args',
  175. MagicMock(return_value=(
  176. u'Git Repo address', [wtforms.validators.Required()]))):
  177. data = {
  178. 'csrf_token': csrf_token,
  179. 'title': 'Remote PR title',
  180. 'branch_from': 'feature',
  181. 'branch_to': 'master',
  182. 'git_repo': os.path.join(self.newpath, 'test'),
  183. }
  184. output = self.app.post('/test/diff/remote', data=data)
  185. self.assertEqual(output.status_code, 200)
  186. output_text = output.get_data(as_text=True)
  187. self.assertIn('Create Pull Request\n </div>\n', output_text)
  188. self.assertIn(
  189. '<div class="card mb-3" id="_1">\n', output_text)
  190. self.assertIn(
  191. '<div class="card mb-3" id="_2">\n', output_text)
  192. self.assertNotIn(
  193. '<div class="card mb-3" id="_3">\n', output_text)
  194. # Not saved yet
  195. self.session = pagure.lib.create_session(self.dbpath)
  196. project = pagure.lib.get_authorized_project(self.session, 'test')
  197. self.assertEqual(len(project.requests), 0)
  198. data = {
  199. 'csrf_token': csrf_token,
  200. 'title': 'Remote PR title',
  201. 'branch_from': 'feature',
  202. 'branch_to': 'master',
  203. 'git_repo': os.path.join(self.newpath, 'test'),
  204. 'confirm': 1,
  205. }
  206. output = self.app.post(
  207. '/test/diff/remote', data=data, follow_redirects=True)
  208. self.assertEqual(output.status_code, 200)
  209. output_text = output.get_data(as_text=True)
  210. self.assertIn(
  211. '<span class="text-success font-weight-bold">#1',
  212. output_text)
  213. self.assertIn(
  214. '<div class="card mb-3" id="_1">\n', output_text)
  215. self.assertIn(
  216. '<div class="card mb-3" id="_2">\n', output_text)
  217. self.assertNotIn(
  218. '<div class="card mb-3" id="_3">\n', output_text)
  219. # Show the filename in the Changes summary
  220. self.assertIn(
  221. '<a href="#_1" class="list-group-item', output_text)
  222. self.assertIn(
  223. '<div class="ellipsis pr-changes-description">'
  224. '\n <small>.gitignore</small>', output_text)
  225. self.assertIn(
  226. '<a href="#_2" class="list-group-item', output_text)
  227. self.assertIn(
  228. '<div class="ellipsis pr-changes-description">'
  229. '\n <small>sources</small>', output_text)
  230. # Remote PR Created
  231. self.session = pagure.lib.create_session(self.dbpath)
  232. project = pagure.lib.get_authorized_project(self.session, 'test')
  233. self.assertEqual(len(project.requests), 1)
  234. @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
  235. def test_new_remote_pr_empty_target(self):
  236. """ Test creating a new remote PR authenticated against an empty
  237. git repo. """
  238. tests.create_projects(self.session)
  239. tests.create_projects_git(
  240. os.path.join(self.path, 'requests'), bare=True)
  241. # Create empty target git repo
  242. gitrepo = os.path.join(self.path, 'repos', 'test.git')
  243. pygit2.init_repository(gitrepo, bare=True)
  244. # Create git repo we'll pull from
  245. gitrepo = os.path.join(self.path, 'repos', 'test_origin.git')
  246. repo = pygit2.init_repository(gitrepo)
  247. # Create a file in that git repo
  248. with open(os.path.join(gitrepo, 'sources'), 'w') as stream:
  249. stream.write('foo\n bar')
  250. repo.index.add('sources')
  251. repo.index.write()
  252. prev_commit = []
  253. # Commits the files added
  254. tree = repo.index.write_tree()
  255. author = _make_signature(
  256. 'Alice Author', 'alice@authors.tld')
  257. committer = _make_signature(
  258. 'Cecil Committer', 'cecil@committers.tld')
  259. repo.create_commit(
  260. 'refs/heads/feature', # the name of the reference to update
  261. author,
  262. committer,
  263. 'Add sources file for testing',
  264. # binary string representing the tree object ID
  265. tree,
  266. # list of binary strings representing parents of the new commit
  267. prev_commit
  268. )
  269. # Before
  270. self.session = pagure.lib.create_session(self.dbpath)
  271. project = pagure.lib.get_authorized_project(self.session, 'test')
  272. self.assertEqual(len(project.requests), 0)
  273. # Try creating a remote PR
  274. user = tests.FakeUser(username='foo')
  275. with tests.user_set(self.app.application, user):
  276. output = self.app.get('/test/diff/remote')
  277. self.assertEqual(output.status_code, 200)
  278. self.assertIn(
  279. '<h2>New remote pull-request</h2>',
  280. output.get_data(as_text=True))
  281. csrf_token = self.get_csrf(output=output)
  282. with patch(
  283. 'pagure.forms.RemoteRequestPullForm.git_repo.args',
  284. MagicMock(return_value=(
  285. u'Git Repo address', [wtforms.validators.Required()]))):
  286. data = {
  287. 'csrf_token': csrf_token,
  288. 'title': 'Remote PR title',
  289. 'branch_from': 'feature',
  290. 'branch_to': 'master',
  291. 'git_repo': gitrepo,
  292. }
  293. output = self.app.post('/test/diff/remote', data=data)
  294. self.assertEqual(output.status_code, 200)
  295. output_text = output.get_data(as_text=True)
  296. self.assertIn('Create Pull Request\n </div>\n', output_text)
  297. self.assertIn(
  298. '<div class="card mb-3" id="_1">\n', output_text)
  299. self.assertNotIn(
  300. '<div class="card mb-3" id="_2">\n', output_text)
  301. # Not saved yet
  302. self.session = pagure.lib.create_session(self.dbpath)
  303. project = pagure.lib.get_authorized_project(self.session, 'test')
  304. self.assertEqual(len(project.requests), 0)
  305. data = {
  306. 'csrf_token': csrf_token,
  307. 'title': 'Remote PR title',
  308. 'branch_from': 'feature',
  309. 'branch_to': 'master',
  310. 'git_repo': gitrepo,
  311. 'confirm': 1,
  312. }
  313. output = self.app.post(
  314. '/test/diff/remote', data=data, follow_redirects=True)
  315. self.assertEqual(output.status_code, 200)
  316. output_text = output.get_data(as_text=True)
  317. self.assertIn(
  318. '<title>PR#1: Remote PR title - test\n - Pagure</title>',
  319. output_text)
  320. self.assertIn(
  321. '<div class="card mb-3" id="_1">\n', output_text)
  322. self.assertNotIn(
  323. '<div class="card mb-3" id="_2">\n', output_text)
  324. # Show the filename in the Changes summary
  325. self.assertIn(
  326. '<a href="#_1" class="list-group-item', output_text)
  327. self.assertIn(
  328. '<div class="ellipsis pr-changes-description">'
  329. '\n <small>sources</small>', output_text)
  330. # Remote PR Created
  331. self.session = pagure.lib.create_session(self.dbpath)
  332. project = pagure.lib.get_authorized_project(self.session, 'test')
  333. self.assertEqual(len(project.requests), 1)
  334. # Check the merge state of the PR
  335. data = {
  336. 'csrf_token': csrf_token,
  337. 'requestid': project.requests[0].uid,
  338. }
  339. output = self.app.post('/pv/pull-request/merge', data=data)
  340. self.assertEqual(output.status_code, 200)
  341. output_text = output.get_data(as_text=True)
  342. data = json.loads(output_text)
  343. self.assertEqual(
  344. data,
  345. {
  346. "code": "FFORWARD",
  347. "message": "The pull-request can be merged and fast-forwarded",
  348. "short_code": "Ok"
  349. }
  350. )
  351. user = tests.FakeUser(username='pingou')
  352. with tests.user_set(self.app.application, user):
  353. # Merge the PR
  354. data = {
  355. 'csrf_token': csrf_token,
  356. }
  357. output = self.app.post(
  358. '/test/pull-request/1/merge', data=data, follow_redirects=True)
  359. output_text = output.get_data(as_text=True)
  360. self.assertEqual(output.status_code, 200)
  361. self.assertIn(
  362. '<title>Overview - test - Pagure</title>', output_text
  363. )
  364. @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
  365. @patch('pagure.lib.tasks_services.trigger_ci_build')
  366. def test_new_remote_pr_ci_off(self, trigger_ci):
  367. """ Test creating a new remote PR when CI is not configured. """
  368. tests.create_projects(self.session)
  369. tests.create_projects_git(
  370. os.path.join(self.path, 'requests'), bare=True)
  371. self.set_up_git_repo()
  372. # Before
  373. self.session = pagure.lib.create_session(self.dbpath)
  374. project = pagure.lib.get_authorized_project(self.session, 'test')
  375. self.assertEqual(len(project.requests), 0)
  376. # Create a remote PR
  377. user = tests.FakeUser(username='foo')
  378. with tests.user_set(self.app.application, user):
  379. csrf_token = self.get_csrf()
  380. data = {
  381. 'csrf_token': csrf_token,
  382. 'title': 'Remote PR title',
  383. 'branch_from': 'feature',
  384. 'branch_to': 'master',
  385. 'git_repo': os.path.join(self.newpath, 'test'),
  386. }
  387. with patch(
  388. 'pagure.forms.RemoteRequestPullForm.git_repo.args',
  389. MagicMock(return_value=(
  390. u'Git Repo address', [wtforms.validators.Required()]))):
  391. output = self.app.post(
  392. '/test/diff/remote', data=data, follow_redirects=True)
  393. self.assertEqual(output.status_code, 200)
  394. data['confirm'] = 1
  395. output = self.app.post(
  396. '/test/diff/remote', data=data, follow_redirects=True)
  397. self.assertEqual(output.status_code, 200)
  398. output_text = output.get_data(as_text=True)
  399. self.assertIn(
  400. '<span class="text-success font-weight-bold">#1',
  401. output_text)
  402. self.assertIn(
  403. '<div class="card mb-3" id="_1">\n', output_text)
  404. self.assertIn(
  405. '<div class="card mb-3" id="_2">\n', output_text)
  406. self.assertNotIn(
  407. '<div class="card mb-3" id="_3">\n', output_text)
  408. # Remote PR Created
  409. self.session = pagure.lib.create_session(self.dbpath)
  410. project = pagure.lib.get_authorized_project(self.session, 'test')
  411. self.assertEqual(len(project.requests), 1)
  412. trigger_ci.assert_not_called()
  413. @patch('pagure.lib.notify.send_email', MagicMock(return_value=True))
  414. @patch('pagure.lib.tasks_services.trigger_ci_build')
  415. def test_new_remote_pr_ci_on(self, trigger_ci):
  416. """ Test creating a new remote PR when CI is configured. """
  417. tests.create_projects(self.session)
  418. tests.create_projects_git(
  419. os.path.join(self.path, 'requests'), bare=True)
  420. self.set_up_git_repo()
  421. # Before
  422. self.session = pagure.lib.create_session(self.dbpath)
  423. project = pagure.lib.get_authorized_project(self.session, 'test')
  424. self.assertEqual(len(project.requests), 0)
  425. # Create a remote PR
  426. user = tests.FakeUser(username='pingou')
  427. with tests.user_set(self.app.application, user):
  428. csrf_token = self.get_csrf()
  429. # Activate CI hook
  430. data = {
  431. 'active_pr': 'y',
  432. 'ci_url': 'https://jenkins.fedoraproject.org',
  433. 'ci_job': 'test/job',
  434. 'ci_type': 'jenkins',
  435. 'csrf_token': csrf_token,
  436. }
  437. output = self.app.post(
  438. '/test/settings/Pagure CI', data=data, follow_redirects=True)
  439. self.assertEqual(output.status_code, 200)
  440. user = tests.FakeUser(username='foo')
  441. with tests.user_set(self.app.application, user):
  442. data = {
  443. 'csrf_token': csrf_token,
  444. 'title': 'Remote PR title',
  445. 'branch_from': 'feature',
  446. 'branch_to': 'master',
  447. 'git_repo': os.path.join(self.newpath, 'test'),
  448. }
  449. # Disables checking the URL pattern for git_repo
  450. with patch(
  451. 'pagure.forms.RemoteRequestPullForm.git_repo.args',
  452. MagicMock(return_value=(
  453. u'Git Repo address', [wtforms.validators.Required()]))):
  454. # Do the preview, triggers the cache & all
  455. output = self.app.post(
  456. '/test/diff/remote', data=data, follow_redirects=True)
  457. self.assertEqual(output.status_code, 200)
  458. # Confirm the PR creation
  459. data['confirm'] = 1
  460. output = self.app.post(
  461. '/test/diff/remote', data=data, follow_redirects=True)
  462. self.assertEqual(output.status_code, 200)
  463. output_text = output.get_data(as_text=True)
  464. self.assertIn(
  465. '<span class="text-success font-weight-bold">#1',
  466. output_text)
  467. self.assertIn(
  468. '<div class="card mb-3" id="_1">\n', output_text)
  469. self.assertIn(
  470. '<div class="card mb-3" id="_2">\n', output_text)
  471. self.assertNotIn(
  472. '<div class="card mb-3" id="_3">\n', output_text)
  473. # Remote PR Created
  474. self.session = pagure.lib.create_session(self.dbpath)
  475. project = pagure.lib.get_authorized_project(self.session, 'test')
  476. self.assertEqual(len(project.requests), 1)
  477. trigger_ci.assert_not_called()
  478. if __name__ == '__main__':
  479. unittest.main(verbosity=2)