gerrit_util_test.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. #!/usr/bin/env vpython3
  2. # coding=utf-8
  3. # Copyright (c) 2019 The Chromium Authors. All rights reserved.
  4. # Use of this source code is governed by a BSD-style license that can be
  5. # found in the LICENSE file.
  6. import json
  7. import os
  8. import socket
  9. import subprocess
  10. import sys
  11. import textwrap
  12. import unittest
  13. from io import StringIO
  14. from pathlib import Path
  15. from typing import Optional
  16. from unittest import mock
  17. import httplib2
  18. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  19. import gerrit_util
  20. import git_common
  21. import metrics
  22. import subprocess2
  23. RUN_SUBPROC_TESTS = 'RUN_SUBPROC_TESTS' in os.environ
  24. def makeConn(host: str) -> gerrit_util.HttpConn:
  25. """Makes an empty gerrit_util.HttpConn for the given host."""
  26. return gerrit_util.HttpConn(
  27. req_uri='???',
  28. req_method='GET',
  29. req_host=host,
  30. req_headers={},
  31. req_body=None,
  32. )
  33. class CookiesAuthenticatorTest(unittest.TestCase):
  34. _GITCOOKIES = '\n'.join([
  35. '\t'.join([
  36. 'chromium.googlesource.com',
  37. 'FALSE',
  38. '/',
  39. 'TRUE',
  40. '2147483647',
  41. 'o',
  42. 'git-user.chromium.org=1/chromium-secret',
  43. ]),
  44. '\t'.join([
  45. 'chromium-review.googlesource.com',
  46. 'FALSE',
  47. '/',
  48. 'TRUE',
  49. '2147483647',
  50. 'o',
  51. 'git-user.chromium.org=1/chromium-secret',
  52. ]),
  53. '\t'.join([
  54. '.example.com',
  55. 'FALSE',
  56. '/',
  57. 'TRUE',
  58. '2147483647',
  59. 'o',
  60. 'example-bearer-token',
  61. ]),
  62. '\t'.join([
  63. 'another-path.example.com',
  64. 'FALSE',
  65. '/foo',
  66. 'TRUE',
  67. '2147483647',
  68. 'o',
  69. 'git-example.com=1/another-path-secret',
  70. ]),
  71. '\t'.join([
  72. 'another-key.example.com',
  73. 'FALSE',
  74. '/',
  75. 'TRUE',
  76. '2147483647',
  77. 'not-o',
  78. 'git-example.com=1/another-key-secret',
  79. ]),
  80. '#' + '\t'.join([
  81. 'chromium-review.googlesource.com',
  82. 'FALSE',
  83. '/',
  84. 'TRUE',
  85. '2147483647',
  86. 'o',
  87. 'git-invalid-user.chromium.org=1/invalid-chromium-secret',
  88. ]),
  89. 'Some unrelated line\t that should not be here',
  90. ])
  91. def setUp(self):
  92. mock.patch('gclient_utils.FileRead',
  93. return_value=self._GITCOOKIES).start()
  94. mock.patch('os.getenv', return_value={}).start()
  95. mock.patch('os.environ', {'HOME': '$HOME'}).start()
  96. mock.patch('os.path.exists', return_value=True).start()
  97. mock.patch(
  98. 'git_common.run',
  99. side_effect=[
  100. subprocess2.CalledProcessError(1, ['cmd'], 'cwd', 'out', 'err')
  101. ],
  102. ).start()
  103. self.addCleanup(mock.patch.stopall)
  104. self.maxDiff = None
  105. def assertAuthenticatedConnAuth(self,
  106. auth: gerrit_util.CookiesAuthenticator,
  107. host: str, expected: str):
  108. conn = makeConn(host)
  109. auth.authenticate(conn)
  110. self.assertEqual(conn.req_headers['Authorization'], expected)
  111. def testGetNewPasswordUrl(self):
  112. auth = gerrit_util.CookiesAuthenticator()
  113. self.assertEqual('https://chromium.googlesource.com/new-password',
  114. auth.get_new_password_url('chromium.googlesource.com'))
  115. self.assertEqual(
  116. 'https://chrome-internal.googlesource.com/new-password',
  117. auth.get_new_password_url(
  118. 'chrome-internal-review.googlesource.com'))
  119. def testGetNewPasswordMessage(self):
  120. auth = gerrit_util.CookiesAuthenticator()
  121. self.assertIn(
  122. 'https://chromium.googlesource.com/new-password',
  123. auth._get_new_password_message('chromium-review.googlesource.com'))
  124. self.assertIn(
  125. 'https://chrome-internal.googlesource.com/new-password',
  126. auth._get_new_password_message('chrome-internal.googlesource.com'))
  127. def testGetGitcookiesPath(self):
  128. self.assertEqual(
  129. os.path.expanduser(os.path.join('~', '.gitcookies')),
  130. gerrit_util.CookiesAuthenticator().get_gitcookies_path())
  131. git_common.run.side_effect = ['http.cookiefile\nhttp.cookiefile\x00']
  132. self.assertEqual(
  133. 'http.cookiefile',
  134. gerrit_util.CookiesAuthenticator().get_gitcookies_path())
  135. git_common.run.assert_called_with('config',
  136. '--list',
  137. '-z',
  138. autostrip=False,
  139. cwd=os.getcwd(),
  140. env=mock.ANY)
  141. os.getenv.return_value = 'git-cookies-path'
  142. self.assertEqual(
  143. 'git-cookies-path',
  144. gerrit_util.CookiesAuthenticator().get_gitcookies_path())
  145. os.getenv.assert_called_with('GIT_COOKIES_PATH')
  146. def testGitcookies(self):
  147. auth = gerrit_util.CookiesAuthenticator()
  148. self.assertEqual(
  149. auth.gitcookies, {
  150. 'chromium.googlesource.com':
  151. ('git-user.chromium.org', '1/chromium-secret'),
  152. 'chromium-review.googlesource.com':
  153. ('git-user.chromium.org', '1/chromium-secret'),
  154. '.example.com': ('', 'example-bearer-token'),
  155. })
  156. def testGetAuthHeader(self):
  157. expected_chromium_header = (
  158. 'Basic Z2l0LXVzZXIuY2hyb21pdW0ub3JnOjEvY2hyb21pdW0tc2VjcmV0')
  159. auth = gerrit_util.CookiesAuthenticator()
  160. self.assertAuthenticatedConnAuth(auth, 'chromium.googlesource.com',
  161. expected_chromium_header)
  162. self.assertAuthenticatedConnAuth(auth,
  163. 'chromium-review.googlesource.com',
  164. expected_chromium_header)
  165. self.assertAuthenticatedConnAuth(auth, 'some-review.example.com',
  166. 'Bearer example-bearer-token')
  167. def testGetAuthEmail(self):
  168. auth = gerrit_util.CookiesAuthenticator()
  169. self.assertEqual('user@chromium.org',
  170. auth.get_auth_email('chromium.googlesource.com'))
  171. self.assertEqual(
  172. 'user@chromium.org',
  173. auth.get_auth_email('chromium-review.googlesource.com'))
  174. self.assertIsNone(auth.get_auth_email('some-review.example.com'))
  175. class GceAuthenticatorTest(unittest.TestCase):
  176. def setUp(self):
  177. super(GceAuthenticatorTest, self).setUp()
  178. mock.patch('httplib2.Http').start()
  179. mock.patch('os.getenv', return_value=None).start()
  180. mock.patch('gerrit_util.time_sleep').start()
  181. mock.patch('gerrit_util.time_time').start()
  182. self.addCleanup(mock.patch.stopall)
  183. # GceAuthenticator has class variables that cache the results. Build a
  184. # new class for every test to avoid inter-test dependencies.
  185. class GceAuthenticator(gerrit_util.GceAuthenticator):
  186. pass
  187. self.GceAuthenticator = GceAuthenticator
  188. def assertAuthenticatedToken(self, token: Optional[str]):
  189. conn = makeConn('some.example.com')
  190. self.GceAuthenticator().authenticate(conn)
  191. if token is None:
  192. self.assertNotIn('Authorization', conn.req_headers)
  193. else:
  194. self.assertEqual(conn.req_headers['Authorization'], token)
  195. def testIsGce_EnvVarSkip(self, *_mocks):
  196. os.getenv.return_value = '1'
  197. self.assertFalse(self.GceAuthenticator.is_applicable())
  198. os.getenv.assert_called_once_with('SKIP_GCE_AUTH_FOR_GIT')
  199. def testIsGce_Error(self):
  200. httplib2.Http().request.side_effect = httplib2.HttpLib2Error
  201. self.assertFalse(self.GceAuthenticator.is_applicable())
  202. def testIsGce_500(self):
  203. httplib2.Http().request.return_value = (mock.Mock(status=500), None)
  204. self.assertFalse(self.GceAuthenticator.is_applicable())
  205. last_call = gerrit_util.time_sleep.mock_calls[-1]
  206. self.assertLessEqual(last_call, mock.call(43.0))
  207. def testIsGce_FailsThenSucceeds(self):
  208. response = mock.Mock(status=200)
  209. response.get.return_value = 'Google'
  210. httplib2.Http().request.side_effect = [
  211. (mock.Mock(status=500), None),
  212. (response, 'who cares'),
  213. ]
  214. self.assertTrue(self.GceAuthenticator.is_applicable())
  215. def testIsGce_MetadataFlavorIsNotGoogle(self):
  216. response = mock.Mock(status=200)
  217. response.get.return_value = None
  218. httplib2.Http().request.return_value = (response, 'who cares')
  219. self.assertFalse(self.GceAuthenticator.is_applicable())
  220. response.get.assert_called_once_with('metadata-flavor')
  221. def testIsGce_ResultIsCached(self):
  222. response = mock.Mock(status=200)
  223. response.get.return_value = 'Google'
  224. httplib2.Http().request.side_effect = [(response, 'who cares')]
  225. self.assertTrue(self.GceAuthenticator.is_applicable())
  226. self.assertTrue(self.GceAuthenticator.is_applicable())
  227. httplib2.Http().request.assert_called_once()
  228. def testGetAuthHeader_Error(self):
  229. httplib2.Http().request.side_effect = httplib2.HttpLib2Error
  230. self.assertAuthenticatedToken(None)
  231. def testGetAuthHeader_500(self):
  232. httplib2.Http().request.return_value = (mock.Mock(status=500), None)
  233. self.assertAuthenticatedToken(None)
  234. def testGetAuthHeader_Non200(self):
  235. httplib2.Http().request.return_value = (mock.Mock(status=403), None)
  236. self.assertAuthenticatedToken(None)
  237. def testGetAuthHeader_OK(self):
  238. httplib2.Http().request.return_value = (
  239. mock.Mock(status=200),
  240. '{"expires_in": 125, "token_type": "TYPE", "access_token": "TOKEN"}'
  241. )
  242. gerrit_util.time_time.return_value = 0
  243. self.assertAuthenticatedToken('TYPE TOKEN')
  244. def testGetAuthHeader_Cache(self):
  245. httplib2.Http().request.return_value = (
  246. mock.Mock(status=200),
  247. '{"expires_in": 125, "token_type": "TYPE", "access_token": "TOKEN"}'
  248. )
  249. gerrit_util.time_time.return_value = 0
  250. self.assertAuthenticatedToken('TYPE TOKEN')
  251. self.assertAuthenticatedToken('TYPE TOKEN')
  252. httplib2.Http().request.assert_called_once()
  253. def testGetAuthHeader_CacheOld(self):
  254. httplib2.Http().request.return_value = (
  255. mock.Mock(status=200),
  256. '{"expires_in": 125, "token_type": "TYPE", "access_token": "TOKEN"}'
  257. )
  258. gerrit_util.time_time.side_effect = [0, 100, 200]
  259. self.assertAuthenticatedToken('TYPE TOKEN')
  260. self.assertAuthenticatedToken('TYPE TOKEN')
  261. self.assertEqual(2, len(httplib2.Http().request.mock_calls))
  262. class GerritUtilTest(unittest.TestCase):
  263. def setUp(self):
  264. super(GerritUtilTest, self).setUp()
  265. mock.patch('gerrit_util.LOGGER').start()
  266. mock.patch('gerrit_util.time_sleep').start()
  267. mock.patch('metrics.collector').start()
  268. mock.patch('metrics_utils.extract_http_metrics',
  269. return_value='http_metrics').start()
  270. self.addCleanup(mock.patch.stopall)
  271. def testQueryString(self):
  272. self.assertEqual('', gerrit_util._QueryString([]))
  273. self.assertEqual('first%20param%2B',
  274. gerrit_util._QueryString([], 'first param+'))
  275. self.assertEqual(
  276. 'key:val+foo:bar',
  277. gerrit_util._QueryString([('key', 'val'), ('foo', 'bar')]))
  278. self.assertEqual(
  279. 'first%20param%2B+key:val+foo:bar',
  280. gerrit_util._QueryString([('key', 'val'), ('foo', 'bar')],
  281. 'first param+'))
  282. @mock.patch('gerrit_util.CookiesAuthenticator._get_auth_for_host')
  283. @mock.patch('gerrit_util.Authenticator.get')
  284. def testCreateHttpConn_Basic(self, mockAuth, cookieAuth):
  285. mockAuth.return_value = gerrit_util.CookiesAuthenticator()
  286. cookieAuth.return_value = None
  287. conn = gerrit_util.CreateHttpConn('host.example.com', 'foo/bar')
  288. self.assertEqual('host.example.com', conn.req_host)
  289. self.assertEqual(
  290. {
  291. 'uri': 'https://host.example.com/a/foo/bar',
  292. 'method': 'GET',
  293. 'headers': {},
  294. 'body': None,
  295. }, conn.req_params)
  296. @mock.patch('gerrit_util.CookiesAuthenticator._get_auth_for_host')
  297. @mock.patch('gerrit_util.Authenticator.get')
  298. def testCreateHttpConn_Authenticated(self, mockAuth, cookieAuth):
  299. mockAuth.return_value = gerrit_util.CookiesAuthenticator()
  300. cookieAuth.return_value = (None, 'token')
  301. conn = gerrit_util.CreateHttpConn('host.example.com',
  302. 'foo/bar',
  303. headers={'header': 'value'})
  304. self.assertEqual('host.example.com', conn.req_host)
  305. self.assertEqual(
  306. {
  307. 'uri': 'https://host.example.com/a/foo/bar',
  308. 'method': 'GET',
  309. 'headers': {
  310. 'Authorization': 'Bearer token',
  311. 'header': 'value'
  312. },
  313. 'body': None,
  314. }, conn.req_params)
  315. @mock.patch('gerrit_util.CookiesAuthenticator._get_auth_for_host')
  316. @mock.patch('gerrit_util.Authenticator')
  317. def testCreateHttpConn_Body(self, mockAuth, cookieAuth):
  318. mockAuth.return_value = gerrit_util.CookiesAuthenticator()
  319. cookieAuth.return_value = None
  320. conn = gerrit_util.CreateHttpConn('host.example.com',
  321. 'foo/bar',
  322. body={
  323. 'l': [1, 2, 3],
  324. 'd': {
  325. 'k': 'v'
  326. }
  327. })
  328. self.assertEqual('host.example.com', conn.req_host)
  329. self.assertEqual(
  330. {
  331. 'uri': 'https://host.example.com/a/foo/bar',
  332. 'method': 'GET',
  333. 'headers': {
  334. 'Content-Type': 'application/json'
  335. },
  336. 'body': '{"d": {"k": "v"}, "l": [1, 2, 3]}',
  337. }, conn.req_params)
  338. def testReadHttpResponse_200(self):
  339. conn = mock.Mock()
  340. conn.req_params = {'uri': 'uri', 'method': 'method'}
  341. conn.request.return_value = (mock.Mock(status=200),
  342. b'content\xe2\x9c\x94')
  343. content = gerrit_util.ReadHttpResponse(conn)
  344. self.assertEqual('content✔', content.getvalue())
  345. metrics.collector.add_repeated.assert_called_once_with(
  346. 'http_requests', 'http_metrics')
  347. def testReadHttpResponse_AuthenticationIssue(self):
  348. for status in (302, 401, 403):
  349. response = mock.Mock(status=status)
  350. response.get.return_value = None
  351. conn = mock.Mock(req_params={'uri': 'uri', 'method': 'method'})
  352. conn.request.return_value = (response, b'')
  353. with mock.patch('sys.stdout', StringIO()):
  354. with self.assertRaises(gerrit_util.GerritError) as cm:
  355. gerrit_util.ReadHttpResponse(conn)
  356. self.assertEqual(status, cm.exception.http_status)
  357. self.assertIn('Your Gerrit credentials might be misconfigured',
  358. sys.stdout.getvalue())
  359. def testReadHttpResponse_ClientError(self):
  360. conn = mock.Mock(req_params={'uri': 'uri', 'method': 'method'})
  361. conn.request.return_value = (mock.Mock(status=404), b'')
  362. with self.assertRaises(gerrit_util.GerritError) as cm:
  363. gerrit_util.ReadHttpResponse(conn)
  364. self.assertEqual(404, cm.exception.http_status)
  365. def readHttpResponse_ServerErrorHelper(self, status):
  366. conn = mock.Mock(req_params={'uri': 'uri', 'method': 'method'})
  367. conn.request.return_value = (mock.Mock(status=status), b'')
  368. with self.assertRaises(gerrit_util.GerritError) as cm:
  369. gerrit_util.ReadHttpResponse(conn)
  370. self.assertEqual(status, cm.exception.http_status)
  371. self.assertEqual(gerrit_util.TRY_LIMIT, len(conn.request.mock_calls))
  372. last_call = gerrit_util.time_sleep.mock_calls[-1]
  373. self.assertLessEqual(last_call, mock.call(422.0))
  374. def testReadHttpResponse_ServerError(self):
  375. self.readHttpResponse_ServerErrorHelper(status=404)
  376. self.readHttpResponse_ServerErrorHelper(status=409)
  377. self.readHttpResponse_ServerErrorHelper(status=429)
  378. self.readHttpResponse_ServerErrorHelper(status=500)
  379. def testReadHttpResponse_ServerErrorAndSuccess(self):
  380. conn = mock.Mock(req_params={'uri': 'uri', 'method': 'method'})
  381. conn.request.side_effect = [
  382. (mock.Mock(status=500), b''),
  383. (mock.Mock(status=200), b'content\xe2\x9c\x94'),
  384. ]
  385. self.assertEqual('content✔',
  386. gerrit_util.ReadHttpResponse(conn).getvalue())
  387. self.assertEqual(2, len(conn.request.mock_calls))
  388. gerrit_util.time_sleep.assert_called_once_with(12.0)
  389. def testReadHttpResponse_TimeoutAndSuccess(self):
  390. conn = mock.Mock(req_params={'uri': 'uri', 'method': 'method'})
  391. conn.request.side_effect = [
  392. socket.timeout('timeout'),
  393. (mock.Mock(status=200), b'content\xe2\x9c\x94'),
  394. ]
  395. self.assertEqual('content✔',
  396. gerrit_util.ReadHttpResponse(conn).getvalue())
  397. self.assertEqual(2, len(conn.request.mock_calls))
  398. gerrit_util.time_sleep.assert_called_once_with(12.0)
  399. def testReadHttpResponse_Expected404(self):
  400. conn = mock.Mock()
  401. conn.req_params = {'uri': 'uri', 'method': 'method'}
  402. conn.request.return_value = (mock.Mock(status=404),
  403. b'content\xe2\x9c\x94')
  404. content = gerrit_util.ReadHttpResponse(conn, (404, ))
  405. self.assertEqual('', content.getvalue())
  406. @mock.patch('gerrit_util.ReadHttpResponse')
  407. def testReadHttpJsonResponse_NotJSON(self, mockReadHttpResponse):
  408. mockReadHttpResponse.return_value = StringIO('not json')
  409. with self.assertRaises(gerrit_util.GerritError) as cm:
  410. gerrit_util.ReadHttpJsonResponse(None)
  411. self.assertEqual(cm.exception.http_status, 200)
  412. self.assertEqual(cm.exception.message,
  413. '(200) Unexpected json output: not json')
  414. @mock.patch('gerrit_util.ReadHttpResponse')
  415. def testReadHttpJsonResponse_EmptyValue(self, mockReadHttpResponse):
  416. mockReadHttpResponse.return_value = StringIO(')]}\'')
  417. self.assertEqual(gerrit_util.ReadHttpJsonResponse(None), {})
  418. @mock.patch('gerrit_util.ReadHttpResponse')
  419. def testReadHttpJsonResponse_JSON(self, mockReadHttpResponse):
  420. expected_value = {'foo': 'bar', 'baz': [1, '2', 3]}
  421. mockReadHttpResponse.return_value = StringIO(')]}\'\n' +
  422. json.dumps(expected_value))
  423. self.assertEqual(expected_value, gerrit_util.ReadHttpJsonResponse(None))
  424. @mock.patch('gerrit_util.CreateHttpConn')
  425. @mock.patch('gerrit_util.ReadHttpJsonResponse')
  426. def testQueryChanges(self, mockJsonResponse, mockCreateHttpConn):
  427. gerrit_util.QueryChanges('host', [('key', 'val'), ('foo', 'bar baz')],
  428. 'first param',
  429. limit=500,
  430. o_params=['PARAM_A', 'PARAM_B'],
  431. start='start')
  432. mockCreateHttpConn.assert_called_once_with(
  433. 'host', ('changes/?q=first%20param+key:val+foo:bar+baz'
  434. '&start=start'
  435. '&n=500'
  436. '&o=PARAM_A'
  437. '&o=PARAM_B'),
  438. timeout=30.0)
  439. def testQueryChanges_NoParams(self):
  440. self.assertRaises(RuntimeError, gerrit_util.QueryChanges, 'host', [])
  441. @mock.patch('gerrit_util.QueryChanges')
  442. def testGenerateAllChanges(self, mockQueryChanges):
  443. mockQueryChanges.side_effect = [
  444. # First results page
  445. [
  446. {
  447. '_number': '4'
  448. },
  449. {
  450. '_number': '3'
  451. },
  452. {
  453. '_number': '2',
  454. '_more_changes': True
  455. },
  456. ],
  457. # Second results page, there are new changes, so second page
  458. # includes some results from the first page.
  459. [
  460. {
  461. '_number': '2'
  462. },
  463. {
  464. '_number': '1'
  465. },
  466. ],
  467. # GenerateAllChanges queries again from the start to get any new
  468. # changes (5 in this case).
  469. [
  470. {
  471. '_number': '5'
  472. },
  473. {
  474. '_number': '4'
  475. },
  476. {
  477. '_number': '3',
  478. '_more_changes': True
  479. },
  480. ],
  481. ]
  482. changes = list(gerrit_util.GenerateAllChanges('host', 'params'))
  483. self.assertEqual([
  484. {
  485. '_number': '4'
  486. },
  487. {
  488. '_number': '3'
  489. },
  490. {
  491. '_number': '2',
  492. '_more_changes': True
  493. },
  494. {
  495. '_number': '1'
  496. },
  497. {
  498. '_number': '5'
  499. },
  500. ], changes)
  501. self.assertEqual([
  502. mock.call('host', 'params', None, 500, None, 0),
  503. mock.call('host', 'params', None, 500, None, 3),
  504. mock.call('host', 'params', None, 500, None, 0),
  505. ], mockQueryChanges.mock_calls)
  506. @mock.patch('gerrit_util.CreateHttpConn')
  507. @mock.patch('gerrit_util.ReadHttpJsonResponse')
  508. def testIsCodeOwnersEnabledOnRepo_Disabled(self, mockJsonResponse,
  509. mockCreateHttpConn):
  510. mockJsonResponse.return_value = {'status': {'disabled': True}}
  511. self.assertFalse(gerrit_util.IsCodeOwnersEnabledOnRepo('host', 'repo'))
  512. @mock.patch('gerrit_util.CreateHttpConn')
  513. @mock.patch('gerrit_util.ReadHttpJsonResponse')
  514. def testIsCodeOwnersEnabledOnRepo_Enabled(self, mockJsonResponse,
  515. mockCreateHttpConn):
  516. mockJsonResponse.return_value = {'status': {}}
  517. self.assertTrue(gerrit_util.IsCodeOwnersEnabledOnRepo('host', 'repo'))
  518. class SSOAuthenticatorTest(unittest.TestCase):
  519. @classmethod
  520. def setUpClass(cls) -> None:
  521. cls._original_timeout_secs = gerrit_util.SSOAuthenticator._timeout_secs
  522. return super().setUpClass()
  523. def setUp(self) -> None:
  524. gerrit_util.SSOAuthenticator._sso_cmd = None
  525. gerrit_util.SSOAuthenticator._sso_info = None
  526. gerrit_util.SSOAuthenticator._testing_load_expired_cookies = True
  527. gerrit_util.SSOAuthenticator._timeout_secs = self._original_timeout_secs
  528. self.sso = gerrit_util.SSOAuthenticator()
  529. return super().setUp()
  530. def tearDown(self) -> None:
  531. gerrit_util.SSOAuthenticator._sso_cmd = None
  532. gerrit_util.SSOAuthenticator._sso_info = None
  533. gerrit_util.SSOAuthenticator._testing_load_expired_cookies = False
  534. gerrit_util.SSOAuthenticator._timeout_secs = self._original_timeout_secs
  535. return super().tearDown()
  536. @property
  537. def _input_dir(self) -> Path:
  538. base = Path(__file__).absolute().with_suffix('.inputs')
  539. # Here _testMethodName would be a string like "testCmdAssemblyFound"
  540. return base / self._testMethodName
  541. @mock.patch('shutil.which', return_value='/fake/git-remote-sso')
  542. def testCmdAssemblyFound(self, _):
  543. self.assertEqual(self.sso._resolve_sso_cmd(),
  544. ('/fake/git-remote-sso', '-print_config',
  545. 'sso://*.git.corp.google.com'))
  546. self.assertTrue(self.sso.is_applicable())
  547. @mock.patch('shutil.which', return_value=None)
  548. def testCmdAssemblyNotFound(self, _):
  549. self.assertEqual(self.sso._resolve_sso_cmd(), ())
  550. self.assertFalse(self.sso.is_applicable())
  551. @mock.patch('shutil.which', return_value='/fake/git-remote-sso')
  552. def testCmdAssemblyCached(self, which):
  553. self.sso._resolve_sso_cmd()
  554. self.sso._resolve_sso_cmd()
  555. self.assertEqual(which.called, 1)
  556. def testParseConfigOK(self):
  557. parsed = self.sso._parse_config(
  558. textwrap.dedent(f'''
  559. somekey=a value with = in it
  560. novalue=
  561. http.proxy=localhost:12345
  562. http.cookiefile={self._input_dir/'cookiefile.txt'}
  563. include.path={self._input_dir/'gitconfig'}
  564. ''').strip())
  565. self.assertDictEqual(parsed.headers, {
  566. 'Authorization': 'Basic REALLY_COOL_TOKEN',
  567. })
  568. self.assertEqual(parsed.proxy.proxy_host, b'localhost')
  569. self.assertEqual(parsed.proxy.proxy_port, 12345)
  570. c = parsed.cookies._cookies
  571. self.assertEqual(c['login.example.com']['/']['SSO'].value,
  572. 'TUVFUE1PUlAK')
  573. self.assertEqual(c['.example.com']['/']['__CoolProxy'].value,
  574. 'QkxFRVBCTE9SUAo=')
  575. @unittest.skipUnless(RUN_SUBPROC_TESTS, 'subprocess tests are flakey')
  576. def testLaunchHelperOK(self):
  577. gerrit_util.SSOAuthenticator._sso_cmd = ('python3',
  578. str(self._input_dir /
  579. 'git-remote-sso.py'))
  580. info = self.sso._get_sso_info()
  581. self.assertDictEqual(info.headers, {
  582. 'Authorization': 'Basic REALLY_COOL_TOKEN',
  583. })
  584. self.assertEqual(info.proxy.proxy_host, b'localhost')
  585. self.assertEqual(info.proxy.proxy_port, 12345)
  586. c = info.cookies._cookies
  587. self.assertEqual(c['login.example.com']['/']['SSO'].value,
  588. 'TUVFUE1PUlAK')
  589. self.assertEqual(c['.example.com']['/']['__CoolProxy'].value,
  590. 'QkxFRVBCTE9SUAo=')
  591. @unittest.skipUnless(RUN_SUBPROC_TESTS, 'subprocess tests are flakey')
  592. def testLaunchHelperFailQuick(self):
  593. gerrit_util.SSOAuthenticator._sso_cmd = ('python3',
  594. str(self._input_dir /
  595. 'git-remote-sso.py'))
  596. with self.assertRaisesRegex(SystemExit, "SSO Failure Message!!!"):
  597. self.sso._get_sso_info()
  598. @unittest.skipUnless(RUN_SUBPROC_TESTS, 'subprocess tests are flakey')
  599. def testLaunchHelperFailSlow(self):
  600. gerrit_util.SSOAuthenticator._timeout_secs = 0.2
  601. gerrit_util.SSOAuthenticator._sso_cmd = ('python3',
  602. str(self._input_dir /
  603. 'git-remote-sso.py'))
  604. with self.assertRaises(subprocess.TimeoutExpired):
  605. self.sso._get_sso_info()
  606. if __name__ == '__main__':
  607. unittest.main()