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.
 
 
 
 

163 lines
3.6 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):
  16. #self.requestHeaders = Headers()
  17. self.responseHeaders = Headers()
  18. self.path = uri
  19. self.prepath = []
  20. self.postpath = uri.split('/')
  21. self.method = meth
  22. self.notifications = []
  23. self.finished = False
  24. def setHeader(self, name, value):
  25. self.responseHeaders.setRawHeaders(name, [value])
  26. def setResponseCode(self, code, message=None):
  27. self.code = code
  28. def getRequestHostname(self):
  29. return ''
  30. def getHost(self):
  31. return ''
  32. def isSecure(self):
  33. return False
  34. #def processingFailed(self, failure):
  35. # print 'f:', `failure`
  36. # print 'b:', failure.getTraceback()
  37. def _cleanup(self):
  38. for d in self.notifications:
  39. d.callback(None)
  40. self.notifications = []
  41. def finish(self):
  42. if self.finished: # pragma: no cover
  43. warnings.warn('Warning! request.finish called twice.', stacklevel=2)
  44. self.finished = True
  45. self._cleanup()
  46. #def processingFailed(self, failure):
  47. # print 'pf:', failure.getTraceback()
  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:
  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 get(self, url):
  88. '''Return a response for the passed in url.'''
  89. if url[0] != '/':
  90. raise ValueError('url must be absolute (start w/ a slash)')
  91. req = FakeHTTPRequest('GET', url)
  92. resp = FakeRequestsResponse(req)
  93. req.notifyFinish().addBoth(resp._finished)
  94. r = self._res.render(req)
  95. return resp
  96. class TestFakeRequests(unittest.TestCase):
  97. def setUp(self):
  98. app = Klein()
  99. @app.route('/')
  100. def home(request):
  101. request.setHeader('x-testing', 'value')
  102. return 'hello'
  103. @app.route('/404')
  104. def notfound(request):
  105. request.setResponseCode(404)
  106. return 'not found'
  107. self.requests = FakeRequests(app)
  108. def test_bad(self):
  109. self.assertRaises(ValueError, self.requests.get, 'foobar')
  110. def test_basic(self):
  111. r = self.requests.get('/')
  112. self.assertEqual(r.status_code, 200)
  113. self.assertEqual(r.text, 'hello')
  114. self.assertEqual(r.headers['X-testing'], 'value')
  115. r = self.requests.get('/404')
  116. self.assertEqual(r.status_code, 404)