test_pagure_flask_api_fork_update.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. # -*- coding: utf-8 -*-
  2. """
  3. (c) 2019 - Copyright Red Hat Inc
  4. Authors:
  5. Pierre-Yves Chibon <pingou@pingoured.fr>
  6. """
  7. from __future__ import unicode_literals, absolute_import
  8. import arrow
  9. import copy
  10. import datetime
  11. import unittest
  12. import shutil
  13. import sys
  14. import time
  15. import os
  16. import flask
  17. import json
  18. import munch
  19. from mock import patch, MagicMock
  20. from sqlalchemy.exc import SQLAlchemyError
  21. sys.path.insert(
  22. 0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
  23. )
  24. import pagure.lib.query
  25. import tests
  26. class PagureFlaskApiForkUpdatetests(tests.SimplePagureTest):
  27. """ Tests for the flask API of pagure for updating a PR """
  28. maxDiff = None
  29. @patch("pagure.lib.git.update_git", MagicMock(return_value=True))
  30. @patch("pagure.lib.notify.send_email", MagicMock(return_value=True))
  31. def setUp(self):
  32. """ Set up the environnment, ran before every tests. """
  33. super(PagureFlaskApiForkUpdatetests, self).setUp()
  34. tests.create_projects(self.session)
  35. tests.add_content_git_repo(
  36. os.path.join(self.path, "repos", "test.git")
  37. )
  38. # Fork
  39. project = pagure.lib.query.get_authorized_project(self.session, "test")
  40. task = pagure.lib.query.fork_project(
  41. session=self.session, user="pingou", repo=project
  42. )
  43. self.session.commit()
  44. self.assertEqual(
  45. task.get(),
  46. {
  47. "endpoint": "ui_ns.view_repo",
  48. "repo": "test",
  49. "namespace": None,
  50. "username": "pingou",
  51. },
  52. )
  53. tests.add_readme_git_repo(
  54. os.path.join(self.path, "repos", "forks", "pingou", "test.git")
  55. )
  56. project = pagure.lib.query.get_authorized_project(self.session, "test")
  57. fork = pagure.lib.query.get_authorized_project(
  58. self.session, "test", user="pingou"
  59. )
  60. tests.create_tokens(self.session)
  61. tests.create_tokens_acl(self.session)
  62. req = pagure.lib.query.new_pull_request(
  63. session=self.session,
  64. repo_from=fork,
  65. branch_from="master",
  66. repo_to=project,
  67. branch_to="master",
  68. title="test pull-request",
  69. user="pingou",
  70. )
  71. self.session.commit()
  72. self.assertEqual(req.id, 1)
  73. self.assertEqual(req.title, "test pull-request")
  74. # Assert the PR is open
  75. self.session = pagure.lib.query.create_session(self.dbpath)
  76. project = pagure.lib.query.get_authorized_project(self.session, "test")
  77. self.assertEqual(len(project.requests), 1)
  78. self.assertEqual(project.requests[0].status, "Open")
  79. # Check how the PR renders in the API and the UI
  80. output = self.app.get("/api/0/test/pull-request/1")
  81. self.assertEqual(output.status_code, 200)
  82. output = self.app.get("/test/pull-request/1")
  83. self.assertEqual(output.status_code, 200)
  84. def test_api_pull_request_update_invalid_project_namespace(self):
  85. """ Test api_pull_request_update method when the project doesn't exist.
  86. """
  87. headers = {"Authorization": "token aaabbbcccddd"}
  88. # Valid token, wrong project
  89. output = self.app.post(
  90. "/api/0/somenamespace/test3/pull-request/1", headers=headers
  91. )
  92. self.assertEqual(output.status_code, 401)
  93. data = json.loads(output.get_data(as_text=True))
  94. self.assertDictEqual(
  95. data,
  96. {
  97. "error": "Invalid or expired token. Please visit "
  98. "http://localhost.localdomain/settings#nav-api-tab to get or renew your "
  99. "API token.",
  100. "error_code": "EINVALIDTOK",
  101. },
  102. )
  103. def test_api_pull_request_update_invalid_project(self):
  104. """ Test api_pull_request_update method when the project doesn't exist.
  105. """
  106. headers = {"Authorization": "token aaabbbcccddd"}
  107. # Invalid project
  108. output = self.app.post("/api/0/foo/pull-request/1", headers=headers)
  109. self.assertEqual(output.status_code, 404)
  110. data = json.loads(output.get_data(as_text=True))
  111. self.assertDictEqual(
  112. data, {"error": "Project not found", "error_code": "ENOPROJECT"}
  113. )
  114. def test_api_pull_request_update_invalid_project_token(self):
  115. """ Test api_pull_request_update method when the token doesn't correspond
  116. to the project.
  117. """
  118. headers = {"Authorization": "token aaabbbcccddd"}
  119. # Valid token, wrong project
  120. output = self.app.post("/api/0/test2/pull-request/1", headers=headers)
  121. self.assertEqual(output.status_code, 401)
  122. data = json.loads(output.get_data(as_text=True))
  123. self.assertEqual(sorted(data.keys()), ["error", "error_code"])
  124. self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])
  125. self.assertEqual(
  126. pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"]
  127. )
  128. def test_api_pull_request_update_invalid_pr(self):
  129. """ Test api_assign_pull_request method when asking for an invalid PR
  130. """
  131. headers = {"Authorization": "token aaabbbcccddd"}
  132. # Invalid PR id
  133. output = self.app.post("/api/0/test/pull-request/404", headers=headers)
  134. self.assertEqual(output.status_code, 404)
  135. data = json.loads(output.get_data(as_text=True))
  136. self.assertDictEqual(
  137. data, {"error": "Pull-Request not found", "error_code": "ENOREQ"}
  138. )
  139. def test_api_pull_request_update_no_input(self):
  140. """ Test api_assign_pull_request method when no input is specified
  141. """
  142. headers = {"Authorization": "token aaabbbcccddd"}
  143. # No input
  144. output = self.app.post("/api/0/test/pull-request/1", headers=headers)
  145. self.assertEqual(output.status_code, 400)
  146. data = json.loads(output.get_data(as_text=True))
  147. self.assertDictEqual(
  148. data,
  149. {
  150. "error": "Invalid or incomplete input submitted",
  151. "error_code": "EINVALIDREQ",
  152. "errors": {"title": ["This field is required."]},
  153. },
  154. )
  155. def test_api_pull_request_update_insufficient_input(self):
  156. """ Test api_assign_pull_request method when no input is specified
  157. """
  158. headers = {"Authorization": "token aaabbbcccddd"}
  159. data = {"initial_comment": "will not work"}
  160. # Missing the required title field
  161. output = self.app.post(
  162. "/api/0/test/pull-request/1", data=data, headers=headers
  163. )
  164. self.assertEqual(output.status_code, 400)
  165. data = json.loads(output.get_data(as_text=True))
  166. self.assertDictEqual(
  167. data,
  168. {
  169. "error": "Invalid or incomplete input submitted",
  170. "error_code": "EINVALIDREQ",
  171. "errors": {"title": ["This field is required."]},
  172. },
  173. )
  174. def test_api_pull_request_update_edited(self):
  175. """ Test api_assign_pull_request method when with valid input
  176. """
  177. headers = {"Authorization": "token aaabbbcccddd"}
  178. data = {
  179. "title": "edited test PR",
  180. "initial_comment": "Edited initial comment",
  181. }
  182. # Valid request
  183. output = self.app.post(
  184. "/api/0/test/pull-request/1", data=data, headers=headers
  185. )
  186. self.assertEqual(output.status_code, 200)
  187. data = json.loads(output.get_data(as_text=True))
  188. # Hard-code all the values that will change from a test to another
  189. # because either random or time-based
  190. data["date_created"] = "1551276260"
  191. data["last_updated"] = "1551276261"
  192. data["updated_on"] = "1551276260"
  193. data["commit_start"] = "5f5d609db65d447f77ba00e25afd17ba5053344b"
  194. data["commit_stop"] = "5f5d609db65d447f77ba00e25afd17ba5053344b"
  195. data["project"]["date_created"] = "1551276259"
  196. data["project"]["date_modified"] = "1551276259"
  197. data["repo_from"]["date_created"] = "1551276259"
  198. data["repo_from"]["date_modified"] = "1551276259"
  199. data["repo_from"]["parent"]["date_created"] = "1551276259"
  200. data["repo_from"]["parent"]["date_modified"] = "1551276259"
  201. data["uid"] = "a2bddecc8ea548e88c22a0df77670092"
  202. self.assertDictEqual(
  203. data,
  204. {
  205. "assignee": None,
  206. "branch": "master",
  207. "branch_from": "master",
  208. "cached_merge_status": "unknown",
  209. "closed_at": None,
  210. "closed_by": None,
  211. "comments": [],
  212. "commit_start": "5f5d609db65d447f77ba00e25afd17ba5053344b",
  213. "commit_stop": "5f5d609db65d447f77ba00e25afd17ba5053344b",
  214. "date_created": "1551276260",
  215. "id": 1,
  216. "initial_comment": "Edited initial comment",
  217. "last_updated": "1551276261",
  218. "project": {
  219. "access_groups": {"admin": [], "commit": [], "ticket": []},
  220. "access_users": {
  221. "admin": [],
  222. "commit": [],
  223. "owner": ["pingou"],
  224. "ticket": [],
  225. },
  226. "close_status": [
  227. "Invalid",
  228. "Insufficient data",
  229. "Fixed",
  230. "Duplicate",
  231. ],
  232. "custom_keys": [],
  233. "date_created": "1551276259",
  234. "date_modified": "1551276259",
  235. "description": "test project #1",
  236. "fullname": "test",
  237. "id": 1,
  238. "milestones": {},
  239. "name": "test",
  240. "namespace": None,
  241. "parent": None,
  242. "priorities": {},
  243. "tags": [],
  244. "url_path": "test",
  245. "user": {"fullname": "PY C", "name": "pingou"},
  246. },
  247. "remote_git": None,
  248. "repo_from": {
  249. "access_groups": {"admin": [], "commit": [], "ticket": []},
  250. "access_users": {
  251. "admin": [],
  252. "commit": [],
  253. "owner": ["pingou"],
  254. "ticket": [],
  255. },
  256. "close_status": [],
  257. "custom_keys": [],
  258. "date_created": "1551276259",
  259. "date_modified": "1551276259",
  260. "description": "test project #1",
  261. "fullname": "forks/pingou/test",
  262. "id": 4,
  263. "milestones": {},
  264. "name": "test",
  265. "namespace": None,
  266. "parent": {
  267. "access_groups": {
  268. "admin": [],
  269. "commit": [],
  270. "ticket": [],
  271. },
  272. "access_users": {
  273. "admin": [],
  274. "commit": [],
  275. "owner": ["pingou"],
  276. "ticket": [],
  277. },
  278. "close_status": [
  279. "Invalid",
  280. "Insufficient data",
  281. "Fixed",
  282. "Duplicate",
  283. ],
  284. "custom_keys": [],
  285. "date_created": "1551276259",
  286. "date_modified": "1551276259",
  287. "description": "test project #1",
  288. "fullname": "test",
  289. "id": 1,
  290. "milestones": {},
  291. "name": "test",
  292. "namespace": None,
  293. "parent": None,
  294. "priorities": {},
  295. "tags": [],
  296. "url_path": "test",
  297. "user": {"fullname": "PY C", "name": "pingou"},
  298. },
  299. "priorities": {},
  300. "tags": [],
  301. "url_path": "fork/pingou/test",
  302. "user": {"fullname": "PY C", "name": "pingou"},
  303. },
  304. "status": "Open",
  305. "tags": [],
  306. "threshold_reached": None,
  307. "title": "edited test PR",
  308. "uid": "a2bddecc8ea548e88c22a0df77670092",
  309. "updated_on": "1551276260",
  310. "user": {"fullname": "PY C", "name": "pingou"},
  311. },
  312. )
  313. def test_api_pull_request_update_edited_no_comment(self):
  314. """ Test api_assign_pull_request method when with valid input
  315. """
  316. headers = {"Authorization": "token aaabbbcccddd"}
  317. data = {"title": "edited test PR"}
  318. # Valid request
  319. output = self.app.post(
  320. "/api/0/test/pull-request/1", data=data, headers=headers
  321. )
  322. self.assertEqual(output.status_code, 200)
  323. data = json.loads(output.get_data(as_text=True))
  324. # Hard-code all the values that will change from a test to another
  325. # because either random or time-based
  326. data["date_created"] = "1551276260"
  327. data["last_updated"] = "1551276261"
  328. data["updated_on"] = "1551276260"
  329. data["commit_start"] = "5f5d609db65d447f77ba00e25afd17ba5053344b"
  330. data["commit_stop"] = "5f5d609db65d447f77ba00e25afd17ba5053344b"
  331. data["project"]["date_created"] = "1551276259"
  332. data["project"]["date_modified"] = "1551276259"
  333. data["repo_from"]["date_created"] = "1551276259"
  334. data["repo_from"]["date_modified"] = "1551276259"
  335. data["repo_from"]["parent"]["date_created"] = "1551276259"
  336. data["repo_from"]["parent"]["date_modified"] = "1551276259"
  337. data["uid"] = "a2bddecc8ea548e88c22a0df77670092"
  338. self.assertDictEqual(
  339. data,
  340. {
  341. "assignee": None,
  342. "branch": "master",
  343. "branch_from": "master",
  344. "cached_merge_status": "unknown",
  345. "closed_at": None,
  346. "closed_by": None,
  347. "comments": [],
  348. "commit_start": "5f5d609db65d447f77ba00e25afd17ba5053344b",
  349. "commit_stop": "5f5d609db65d447f77ba00e25afd17ba5053344b",
  350. "date_created": "1551276260",
  351. "id": 1,
  352. "initial_comment": "",
  353. "last_updated": "1551276261",
  354. "project": {
  355. "access_groups": {"admin": [], "commit": [], "ticket": []},
  356. "access_users": {
  357. "admin": [],
  358. "commit": [],
  359. "owner": ["pingou"],
  360. "ticket": [],
  361. },
  362. "close_status": [
  363. "Invalid",
  364. "Insufficient data",
  365. "Fixed",
  366. "Duplicate",
  367. ],
  368. "custom_keys": [],
  369. "date_created": "1551276259",
  370. "date_modified": "1551276259",
  371. "description": "test project #1",
  372. "fullname": "test",
  373. "id": 1,
  374. "milestones": {},
  375. "name": "test",
  376. "namespace": None,
  377. "parent": None,
  378. "priorities": {},
  379. "tags": [],
  380. "url_path": "test",
  381. "user": {"fullname": "PY C", "name": "pingou"},
  382. },
  383. "remote_git": None,
  384. "repo_from": {
  385. "access_groups": {"admin": [], "commit": [], "ticket": []},
  386. "access_users": {
  387. "admin": [],
  388. "commit": [],
  389. "owner": ["pingou"],
  390. "ticket": [],
  391. },
  392. "close_status": [],
  393. "custom_keys": [],
  394. "date_created": "1551276259",
  395. "date_modified": "1551276259",
  396. "description": "test project #1",
  397. "fullname": "forks/pingou/test",
  398. "id": 4,
  399. "milestones": {},
  400. "name": "test",
  401. "namespace": None,
  402. "parent": {
  403. "access_groups": {
  404. "admin": [],
  405. "commit": [],
  406. "ticket": [],
  407. },
  408. "access_users": {
  409. "admin": [],
  410. "commit": [],
  411. "owner": ["pingou"],
  412. "ticket": [],
  413. },
  414. "close_status": [
  415. "Invalid",
  416. "Insufficient data",
  417. "Fixed",
  418. "Duplicate",
  419. ],
  420. "custom_keys": [],
  421. "date_created": "1551276259",
  422. "date_modified": "1551276259",
  423. "description": "test project #1",
  424. "fullname": "test",
  425. "id": 1,
  426. "milestones": {},
  427. "name": "test",
  428. "namespace": None,
  429. "parent": None,
  430. "priorities": {},
  431. "tags": [],
  432. "url_path": "test",
  433. "user": {"fullname": "PY C", "name": "pingou"},
  434. },
  435. "priorities": {},
  436. "tags": [],
  437. "url_path": "fork/pingou/test",
  438. "user": {"fullname": "PY C", "name": "pingou"},
  439. },
  440. "status": "Open",
  441. "tags": [],
  442. "threshold_reached": None,
  443. "title": "edited test PR",
  444. "uid": "a2bddecc8ea548e88c22a0df77670092",
  445. "updated_on": "1551276260",
  446. "user": {"fullname": "PY C", "name": "pingou"},
  447. },
  448. )
  449. def test_api_pull_request_update_edited_linked(self):
  450. """ Test api_assign_pull_request method when with valid input
  451. """
  452. project = pagure.lib.query.get_authorized_project(self.session, "test")
  453. self.assertEqual(len(project.requests), 1)
  454. self.assertEqual(len(project.requests[0].related_issues), 0)
  455. self.assertEqual(len(project.issues), 0)
  456. # Create issues to link to
  457. msg = pagure.lib.query.new_issue(
  458. session=self.session,
  459. repo=project,
  460. title="tést íssüé",
  461. content="We should work on this",
  462. user="pingou",
  463. )
  464. self.session.commit()
  465. self.assertEqual(msg.title, "tést íssüé")
  466. headers = {"Authorization": "token aaabbbcccddd"}
  467. data = {
  468. "title": "edited test PR",
  469. "initial_comment": "Edited initial comment\n\n"
  470. "this PR fixes #2 \n\nThanks",
  471. }
  472. # Valid request
  473. output = self.app.post(
  474. "/api/0/test/pull-request/1", data=data, headers=headers
  475. )
  476. self.assertEqual(output.status_code, 200)
  477. data = json.loads(output.get_data(as_text=True))
  478. # Hard-code all the values that will change from a test to another
  479. # because either random or time-based
  480. data["date_created"] = "1551276260"
  481. data["last_updated"] = "1551276261"
  482. data["updated_on"] = "1551276260"
  483. data["commit_start"] = "5f5d609db65d447f77ba00e25afd17ba5053344b"
  484. data["commit_stop"] = "5f5d609db65d447f77ba00e25afd17ba5053344b"
  485. data["project"]["date_created"] = "1551276259"
  486. data["project"]["date_modified"] = "1551276259"
  487. data["repo_from"]["date_created"] = "1551276259"
  488. data["repo_from"]["date_modified"] = "1551276259"
  489. data["repo_from"]["parent"]["date_created"] = "1551276259"
  490. data["repo_from"]["parent"]["date_modified"] = "1551276259"
  491. data["uid"] = "a2bddecc8ea548e88c22a0df77670092"
  492. self.assertDictEqual(
  493. data,
  494. {
  495. "assignee": None,
  496. "branch": "master",
  497. "branch_from": "master",
  498. "cached_merge_status": "unknown",
  499. "closed_at": None,
  500. "closed_by": None,
  501. "comments": [],
  502. "commit_start": "5f5d609db65d447f77ba00e25afd17ba5053344b",
  503. "commit_stop": "5f5d609db65d447f77ba00e25afd17ba5053344b",
  504. "date_created": "1551276260",
  505. "id": 1,
  506. "initial_comment": "Edited initial comment\n\nthis PR "
  507. "fixes #2 \n\nThanks",
  508. "last_updated": "1551276261",
  509. "project": {
  510. "access_groups": {"admin": [], "commit": [], "ticket": []},
  511. "access_users": {
  512. "admin": [],
  513. "commit": [],
  514. "owner": ["pingou"],
  515. "ticket": [],
  516. },
  517. "close_status": [
  518. "Invalid",
  519. "Insufficient data",
  520. "Fixed",
  521. "Duplicate",
  522. ],
  523. "custom_keys": [],
  524. "date_created": "1551276259",
  525. "date_modified": "1551276259",
  526. "description": "test project #1",
  527. "fullname": "test",
  528. "id": 1,
  529. "milestones": {},
  530. "name": "test",
  531. "namespace": None,
  532. "parent": None,
  533. "priorities": {},
  534. "tags": [],
  535. "url_path": "test",
  536. "user": {"fullname": "PY C", "name": "pingou"},
  537. },
  538. "remote_git": None,
  539. "repo_from": {
  540. "access_groups": {"admin": [], "commit": [], "ticket": []},
  541. "access_users": {
  542. "admin": [],
  543. "commit": [],
  544. "owner": ["pingou"],
  545. "ticket": [],
  546. },
  547. "close_status": [],
  548. "custom_keys": [],
  549. "date_created": "1551276259",
  550. "date_modified": "1551276259",
  551. "description": "test project #1",
  552. "fullname": "forks/pingou/test",
  553. "id": 4,
  554. "milestones": {},
  555. "name": "test",
  556. "namespace": None,
  557. "parent": {
  558. "access_groups": {
  559. "admin": [],
  560. "commit": [],
  561. "ticket": [],
  562. },
  563. "access_users": {
  564. "admin": [],
  565. "commit": [],
  566. "owner": ["pingou"],
  567. "ticket": [],
  568. },
  569. "close_status": [
  570. "Invalid",
  571. "Insufficient data",
  572. "Fixed",
  573. "Duplicate",
  574. ],
  575. "custom_keys": [],
  576. "date_created": "1551276259",
  577. "date_modified": "1551276259",
  578. "description": "test project #1",
  579. "fullname": "test",
  580. "id": 1,
  581. "milestones": {},
  582. "name": "test",
  583. "namespace": None,
  584. "parent": None,
  585. "priorities": {},
  586. "tags": [],
  587. "url_path": "test",
  588. "user": {"fullname": "PY C", "name": "pingou"},
  589. },
  590. "priorities": {},
  591. "tags": [],
  592. "url_path": "fork/pingou/test",
  593. "user": {"fullname": "PY C", "name": "pingou"},
  594. },
  595. "status": "Open",
  596. "tags": [],
  597. "threshold_reached": None,
  598. "title": "edited test PR",
  599. "uid": "a2bddecc8ea548e88c22a0df77670092",
  600. "updated_on": "1551276260",
  601. "user": {"fullname": "PY C", "name": "pingou"},
  602. },
  603. )
  604. project = pagure.lib.query.get_authorized_project(self.session, "test")
  605. self.assertEqual(len(project.requests), 1)
  606. self.assertEqual(len(project.requests[0].related_issues), 1)
  607. self.assertEqual(len(project.issues), 1)
  608. self.assertEqual(len(project.issues[0].related_prs), 1)
  609. if __name__ == "__main__":
  610. unittest.main(verbosity=2)