test_pagure_flask_ui_remote_pr.py 24 KB

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