test_pagure_flask_ui_remote_pr.py 24 KB

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