MetaData Sharing
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

199 lines
4.5 KiB

  1. #!/usr/bin/env python
  2. from klein import Klein
  3. from klein.interfaces import IKleinRequest
  4. from twisted.internet.defer import Deferred
  5. from twisted.trial import unittest
  6. from twisted.web.http_headers import Headers
  7. from zope.interface import implementer
  8. from io import StringIO
  9. from requests.structures import CaseInsensitiveDict
  10. __all__ = [ 'FakeRequests', ]
  11. # https://github.com/twisted/twisted/blob/twisted-19.7.0/src/twisted/web/http.py#L664
  12. @implementer(IKleinRequest)
  13. class FakeHTTPRequest(object):
  14. code = 200
  15. def __init__(self, meth, uri, data):
  16. #self.requestHeaders = Headers()
  17. self.responseHeaders = Headers()
  18. self.content = StringIO(data)
  19. self.path = uri
  20. self.prepath = []
  21. self.postpath = uri.split(b'/')
  22. self.method = meth
  23. self.notifications = []
  24. self.finished = False
  25. def setHeader(self, name, value):
  26. self.responseHeaders.setRawHeaders(name, [value])
  27. def setResponseCode(self, code, message=None):
  28. self.code = code
  29. def getRequestHostname(self):
  30. return b''
  31. def getHost(self):
  32. return b''
  33. def isSecure(self):
  34. return False
  35. def processingFailed(self, failure):
  36. self.setResponseCode(500, 'Internal Server Error')
  37. #print 'f:', `failure`
  38. #print 'b:', failure.getTraceback()
  39. def _cleanup(self):
  40. for d in self.notifications:
  41. d.callback(None)
  42. self.notifications = []
  43. def finish(self):
  44. if self.finished: # pragma: no cover
  45. warnings.warn('Warning! request.finish called twice.', stacklevel=2)
  46. self.finished = True
  47. self._cleanup()
  48. def notifyFinish(self):
  49. self.notifications.append(Deferred())
  50. return self.notifications[-1]
  51. class FakeRequestsResponse(object):
  52. def __init__(self, req):
  53. self._req = req
  54. self._io = StringIO()
  55. req.write = self.write
  56. self.status_code = 500
  57. def _finished(self, arg):
  58. if arg is not None: # pragma: no cover
  59. raise NotImplementedError('cannot handle exceptions yet')
  60. self.status_code = self._req.code
  61. self.text = self._io.getvalue()
  62. self.headers = CaseInsensitiveDict((k.lower(), v[-1]) for k, v in self._req.responseHeaders.getAllRawHeaders())
  63. def write(self, data):
  64. self._io.write(data)
  65. class FakeRequests(object):
  66. '''This class wraps a Klein app into a calling interface that is similar
  67. to the requests module for testing apps.
  68. Example test:
  69. ```
  70. app = Klein()
  71. @app.route('/')
  72. def home(request):
  73. return 'hello'
  74. class TestFakeRequests(unittest.TestCase):
  75. def setUp(self):
  76. self.requests = FakeRequests(app)
  77. def test_basic(self):
  78. r = self.requests.get('/')
  79. self.assertEqual(r.status_code, 200)
  80. self.assertEqual(r.text, 'hello')
  81. ```
  82. '''
  83. def __init__(self, app):
  84. '''Wrap the passed in app as if it was a server for a requests
  85. like interface. The URLs expected will not be complete urls.'''
  86. self._app = app
  87. self._res = app.resource()
  88. def _makerequest(self, method, url, data=''):
  89. if url[0:1] != b'/':
  90. raise ValueError('url must be absolute (start w/ a slash)')
  91. req = FakeHTTPRequest('GET', url, data)
  92. resp = FakeRequestsResponse(req)
  93. req.notifyFinish().addBoth(resp._finished)
  94. r = self._res.render(req)
  95. return resp
  96. def get(self, url):
  97. '''Return a response for the passed in url.'''
  98. print('ktg:', repr(url))
  99. return self._makerequest('GET', url)
  100. def put(self, url, data=''):
  101. '''Make a put request to the provied URL w/ the body of data.'''
  102. return self._makerequest('PUT', url, data)
  103. class TestFakeRequests(unittest.TestCase):
  104. def setUp(self):
  105. self.putdata = []
  106. app = Klein()
  107. @app.route('/')
  108. def home(request):
  109. request.setHeader('x-testing', 'value')
  110. return b'hello'
  111. @app.route('/500')
  112. def causeerror(request):
  113. raise ValueError('random exception')
  114. @app.route('/put')
  115. def putreq(request):
  116. self.putdata.append(request.content.read())
  117. request.setResponseCode(201)
  118. return ''
  119. @app.route('/404')
  120. def notfound(request):
  121. request.setResponseCode(404)
  122. return 'not found'
  123. self.requests = FakeRequests(app)
  124. def test_bad(self):
  125. self.assertRaises(ValueError, self.requests.get, b'foobar')
  126. def test_basic(self):
  127. r = self.requests.get(b'/')
  128. print(repr(r))
  129. self.assertEqual(r.status_code, 200)
  130. self.assertEqual(r.text, 'hello')
  131. self.assertEqual(r.headers['X-testing'], 'value')
  132. r = self.requests.get('/404')
  133. self.assertEqual(r.status_code, 404)
  134. r = self.requests.get('/nonexistent')
  135. self.assertEqual(r.status_code, 404)
  136. r = self.requests.get('/500')
  137. self.assertEqual(r.status_code, 500)
  138. body = 'body'
  139. r = self.requests.put('/put', data=body)
  140. self.assertEqual(r.status_code, 201)
  141. self.assertEqual(r.text, '')
  142. self.assertEqual(''.join(self.putdata), body)