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.
 
 
 
 

196 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 implements
  8. from StringIO import StringIO
  9. from requests.structures import CaseInsensitiveDict
  10. __all__ = [ 'FakeRequests', ]
  11. class FakeHTTPRequest(object):
  12. # https://github.com/twisted/twisted/blob/twisted-19.7.0/src/twisted/web/http.py#L664
  13. implements(IKleinRequest)
  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('/')
  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 ''
  31. def getHost(self):
  32. return ''
  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. def _finished(self, arg):
  57. if arg is not None: # pragma: no cover
  58. raise NotImplementedError('cannot handle exceptions yet')
  59. self.status_code = self._req.code
  60. self.text = self._io.getvalue()
  61. self.headers = CaseInsensitiveDict((k.lower(), v[-1]) for k, v in self._req.responseHeaders.getAllRawHeaders())
  62. def write(self, data):
  63. self._io.write(data)
  64. class FakeRequests(object):
  65. '''This class wraps a Klein app into a calling interface that is similar
  66. to the requests module for testing apps.
  67. Example test:
  68. ```
  69. app = Klein()
  70. @app.route('/')
  71. def home(request):
  72. return 'hello'
  73. class TestFakeRequests(unittest.TestCase):
  74. def setUp(self):
  75. self.requests = FakeRequests(app)
  76. def test_basic(self):
  77. r = self.requests.get('/')
  78. self.assertEqual(r.status_code, 200)
  79. self.assertEqual(r.text, 'hello')
  80. ```
  81. '''
  82. def __init__(self, app):
  83. '''Wrap the passed in app as if it was a server for a requests
  84. like interface. The URLs expected will not be complete urls.'''
  85. self._app = app
  86. self._res = app.resource()
  87. def _makerequest(self, method, url, data=''):
  88. if url[0] != '/':
  89. raise ValueError('url must be absolute (start w/ a slash)')
  90. req = FakeHTTPRequest('GET', url, data)
  91. resp = FakeRequestsResponse(req)
  92. req.notifyFinish().addBoth(resp._finished)
  93. r = self._res.render(req)
  94. return resp
  95. def get(self, url):
  96. '''Return a response for the passed in url.'''
  97. return self._makerequest('GET', url)
  98. def put(self, url, data=''):
  99. '''Make a put request to the provied URL w/ the body of data.'''
  100. return self._makerequest('PUT', url, data)
  101. class TestFakeRequests(unittest.TestCase):
  102. def setUp(self):
  103. self.putdata = []
  104. app = Klein()
  105. @app.route('/')
  106. def home(request):
  107. request.setHeader('x-testing', 'value')
  108. return 'hello'
  109. @app.route('/500')
  110. def causeerror(request):
  111. raise ValueError('random exception')
  112. @app.route('/put')
  113. def putreq(request):
  114. self.putdata.append(request.content.read())
  115. request.setResponseCode(201)
  116. return ''
  117. @app.route('/404')
  118. def notfound(request):
  119. request.setResponseCode(404)
  120. return 'not found'
  121. self.requests = FakeRequests(app)
  122. def test_bad(self):
  123. self.assertRaises(ValueError, self.requests.get, 'foobar')
  124. def test_basic(self):
  125. r = self.requests.get('/')
  126. self.assertEqual(r.status_code, 200)
  127. self.assertEqual(r.text, 'hello')
  128. self.assertEqual(r.headers['X-testing'], 'value')
  129. r = self.requests.get('/404')
  130. self.assertEqual(r.status_code, 404)
  131. r = self.requests.get('/nonexistent')
  132. self.assertEqual(r.status_code, 404)
  133. r = self.requests.get('/500')
  134. self.assertEqual(r.status_code, 500)
  135. body = 'body'
  136. r = self.requests.put('/put', data=body)
  137. self.assertEqual(r.status_code, 201)
  138. self.assertEqual(r.text, '')
  139. self.assertEqual(''.join(self.putdata), body)