Files
pingu-concerts/app/tests/test_gitea.py
T

105 lines
6.1 KiB
Python

import json
import unittest
from unittest.mock import patch
import httpx
from starlette.requests import Request
from bug_reporter import browser_summary, issue_body, safe_route
from gitea_service import GiteaConfig, GiteaError, GiteaService, redact
class GiteaTests(unittest.TestCase):
def service(self, handler):
return GiteaService(GiteaConfig('http://gitea.invalid', 'test-secret-never-print', 'kai', 'pingu-concerts'), httpx.MockTransport(handler))
def test_existing_labels_bot_and_payload(self):
requests = []
def handler(request):
requests.append(request)
if request.url.path.endswith('/user'):
return httpx.Response(200, json={'login': 'metalcircle-bot'})
if request.url.path.endswith('/labels'):
return httpx.Response(200, json=[{'id': 7, 'name': 'reported-from-metalcircle'}, {'id': 9, 'name': 'android'}, {'id': 99, 'name': 'unrelated'}])
return httpx.Response(201, json={'number': 123, 'user': {'login': 'metalcircle-bot'}})
self.assertEqual(self.service(handler).create_issue('A test title', 'A test body', 'android'), 123)
payload = json.loads(requests[-1].content)
self.assertEqual(payload['labels'], [7, 9])
self.assertEqual(payload['title'], '[MetalCircle Bug] A test title')
self.assertEqual(requests[-1].url.path, '/api/v1/repos/kai/pingu-concerts/issues')
self.assertNotIn('test-secret', str(requests[-1].url))
def test_missing_or_inaccessible_labels_do_not_block(self):
for response in (httpx.Response(200, json=[]), httpx.Response(403), httpx.Response(200, json={'invalid': True})):
def handler(request):
if request.url.path.endswith('/user'): return httpx.Response(200, json={'login': 'metalcircle-bot'})
if request.url.path.endswith('/labels'): return response
self.assertEqual(json.loads(request.content)['labels'], [])
return httpx.Response(201, json={'number': 4, 'user': {'login': 'metalcircle-bot'}})
self.assertEqual(self.service(handler).create_issue('Test title', 'body', 'general'), 4)
def test_personal_account_is_rejected_before_post(self):
def handler(request):
self.assertEqual(request.method, 'GET')
return httpx.Response(200, json={'login': 'kai'})
with self.assertRaisesRegex(GiteaError, 'wrong_account'):
self.service(handler).create_issue('title', 'body', 'general')
def test_http_errors_and_secret_free_logs(self):
for code in (401, 403, 404, 500, 502):
def handler(request):
if request.url.path.endswith('/user'): return httpx.Response(200, json={'login': 'metalcircle-bot'})
if request.url.path.endswith('/labels'): return httpx.Response(200, json=[])
return httpx.Response(code, text='test-secret-never-print')
with self.subTest(code=code), self.assertLogs('gitea_service', 'WARNING') as logs:
with self.assertRaises(GiteaError) as caught:
self.service(handler).create_issue('title', 'body', 'general')
self.assertEqual(caught.exception.uncertain, code >= 500)
self.assertNotIn('test-secret-never-print', '\n'.join(logs.output))
def test_timeout_unreachable_and_invalid_response(self):
for failure in ('timeout', 'unreachable', 'invalid_json', 'invalid_issue'):
def handler(request):
if failure == 'unreachable': raise httpx.ConnectError('test-secret-never-print')
if request.url.path.endswith('/user'): return httpx.Response(200, json={'login': 'metalcircle-bot'})
if request.url.path.endswith('/labels'): return httpx.Response(200, json=[])
if failure == 'timeout': raise httpx.ReadTimeout('test-secret-never-print')
if failure == 'invalid_json': return httpx.Response(201, text='not json')
return httpx.Response(201, json={'number': 'x'})
with self.subTest(failure=failure), self.assertLogs('gitea_service', 'WARNING') as logs:
with self.assertRaises(GiteaError) as caught:
self.service(handler).create_issue('title', 'body', 'general')
self.assertEqual(caught.exception.uncertain, failure != 'unreachable')
self.assertNotIn('test-secret', '\n'.join(logs.output))
def test_context_allowlist_and_redaction(self):
secret = 'session-secret-never-send'
request = Request({'type': 'http', 'headers': [
(b'cookie', ('pingu_session='+secret).encode()),
(b'authorization', b'Bearer authorization-secret'),
(b'user-agent', b'Chrome/128.0 secret-header'),
]})
data = dict(description='This is '+secret, expected='Expected result', steps='', category='android',
severity='normal', technical=True, platform='Android', app_version='1.1.0-debug',
route='/concerts/1?token=secret-query')
with patch.dict('os.environ', {'GITEA_TOKEN': 'bot-secret-never-send'}):
body = issue_body(data, {'id': 12, 'username': 'real-user'}, request)
for excluded in (secret, 'authorization-secret', 'secret-header', 'secret-query', 'bot-secret-never-send'):
self.assertNotIn(excluded, body)
self.assertIn('real-user', body)
self.assertIn('12', body)
self.assertIn('/concerts/1', body)
self.assertIn('Chrome 128.0', body)
self.assertEqual(safe_route('/password-reset/sensitive'), '/')
self.assertEqual(safe_route('/register/sensitive'), '/')
self.assertEqual(browser_summary('Cookie: evil'), 'Unknown')
data['technical'] = False
self.assertNotIn('Chrome', issue_body(data, {'id': 12, 'username': 'real-user'}, request))
self.assertNotIn('/concerts/1', issue_body(data, {'id': 12, 'username': 'real-user'}, request))
self.assertNotIn('super-secret', redact('password=super-secret'))
self.assertNotIn('private bytes', redact('-----BEGIN PRIVATE KEY-----\nprivate bytes\n-----END PRIVATE KEY-----'))
if __name__ == '__main__':
unittest.main()