Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
08c7853
Start test case
lovelydinosaur Aug 1, 2016
5abac93
Merge branch 'master' into requests-client
lovelydinosaur Aug 12, 2016
3d1fff3
Added 'requests' test client
lovelydinosaur Aug 15, 2016
e76ca6e
Address typos
lovelydinosaur Aug 15, 2016
6ede654
Graceful fallback if requests is not installed.
lovelydinosaur Aug 17, 2016
049a39e
Add cookie support
lovelydinosaur Aug 17, 2016
64e19c7
Tests for auth and CSRF
lovelydinosaur Aug 17, 2016
da47c34
Py3 compat
lovelydinosaur Aug 17, 2016
5311769
py3 compat
lovelydinosaur Aug 17, 2016
0b3db02
py3 compat
lovelydinosaur Aug 17, 2016
0cc3f50
Add get_requests_client
lovelydinosaur Aug 18, 2016
e4f6928
Added SchemaGenerator.should_include_link
lovelydinosaur Sep 2, 2016
46b9e4e
add settings for html cutoff on related fields
Aug 22, 2016
a556b9c
Router doesn't work if prefix is blank, though project urls.py handle…
c17r Sep 14, 2016
8609c9c
Fix Django 1.10 to-many deprecation
Sep 15, 2016
197b63a
Add django.core.urlresolvers compatibility
Sep 15, 2016
bb37cb7
Update django-filter & django-guardian
Sep 15, 2016
a372a8e
Check for empty router prefix; adjust URL accordingly
c17r Sep 15, 2016
a084924
Fix misc django deprecations
Sep 15, 2016
3bfb0b7
Use TOC extension instead of header
Sep 15, 2016
3bdb0e9
Fix deprecations for py3k
Sep 16, 2016
a0a8b98
Add py3k compatibility to is_simple_callable
Sep 22, 2016
adcf653
Add is_simple_callable tests
Sep 22, 2016
b3afcb2
Drop python 3.2 support (EOL, Dropped by Django)
Sep 22, 2016
b516712
schema_renderers= should *set* the renderers, not append to them.
lovelydinosaur Sep 28, 2016
37b3475
API client (#4424)
lovelydinosaur Sep 29, 2016
c2cec78
Merge master
lovelydinosaur Sep 29, 2016
a60ef8c
Merge branch 'schema-renderers-only-for-root-view' into version-3-5
lovelydinosaur Sep 29, 2016
61b1189
Fix release notes
lovelydinosaur Sep 29, 2016
bc9b522
Merge branch 'rpkilby-fix-simple-callable' into version-3-5
lovelydinosaur Sep 29, 2016
9a4ed1b
Merge branch 'should_include_link' into version-3-5
lovelydinosaur Sep 29, 2016
24bf382
Merge branch 'html_cutoff_settings' of https://github.com/MobileWorks…
lovelydinosaur Sep 29, 2016
f455c67
Merge branch 'MobileWorks-html_cutoff_settings' into version-3-5
lovelydinosaur Sep 29, 2016
b689a3b
Add note about 'User account is disabled.' vs 'Unable to log in'
lovelydinosaur Sep 29, 2016
818ab45
Merge branch 'fix-deprecations' of https://github.com/rpkilby/django-…
lovelydinosaur Sep 29, 2016
ee2b165
Merge branch 'rpkilby-fix-deprecations' into version-3-5
lovelydinosaur Sep 29, 2016
c427144
Merge branch 'router-empty-prefix' of https://github.com/c17r/django-…
lovelydinosaur Sep 29, 2016
49ce3d6
Merge branch 'c17r-router-empty-prefix' into version-3-5
lovelydinosaur Sep 29, 2016
c3a9538
Clean up schema generation (#4527)
lovelydinosaur Sep 30, 2016
4ad5256
Handle multiple methods on custom action (#4529)
lovelydinosaur Sep 30, 2016
8044d38
RequestsClient, CoreAPIClient
lovelydinosaur Oct 4, 2016
a8501f7
exclude_from_schema
lovelydinosaur Oct 5, 2016
cd826ce
Added 'get_schema_view()' shortcut
lovelydinosaur Oct 5, 2016
7e3a3a4
Added schema descriptions
lovelydinosaur Oct 5, 2016
1084dca
Better descriptions for schemas
lovelydinosaur Oct 5, 2016
7edee80
Add type annotation to schema generation
lovelydinosaur Oct 6, 2016
b44ab76
Coerce schema 'pk' in path to actual field name
lovelydinosaur Oct 6, 2016
0724420
Deprecations move into assertion errors
lovelydinosaur Oct 6, 2016
3eb7fe6
Use get_schema_view in tests
lovelydinosaur Oct 7, 2016
5858168
Updte CoreJSON media type
lovelydinosaur Oct 7, 2016
69b4acd
Handle schema structure correctly when path prefixs exist. Closes #4401
lovelydinosaur Oct 7, 2016
fcf932f
Add PendingDeprecation to Router schema generation.
lovelydinosaur Oct 7, 2016
18aebbb
Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES
lovelydinosaur Oct 10, 2016
3f3213b
Renamed and documented 'get_schema_fields' interface.
lovelydinosaur Oct 10, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added 'requests' test client
  • Loading branch information
lovelydinosaur committed Aug 15, 2016
commit 3d1fff3f26835612be17b6624d766a56520880ec
2 changes: 1 addition & 1 deletion rest_framework/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def POST(self):
if not _hasattr(self, '_data'):
self._load_data_and_files()
if is_form_media_type(self.content_type):
return self.data
return self._data
return QueryDict('', encoding=self._request._encoding)

@property
Expand Down
86 changes: 85 additions & 1 deletion rest_framework/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
# to make it harder for the user to import the wrong thing without realizing.
from __future__ import unicode_literals

import io

from django.conf import settings
from django.core.handlers.wsgi import WSGIHandler
from django.test import testcases
from django.test.client import Client as DjangoClient
from django.test.client import RequestFactory as DjangoRequestFactory
Expand All @@ -13,6 +16,10 @@
from django.utils.encoding import force_bytes
from django.utils.http import urlencode
from requests import Session
from requests.adapters import BaseAdapter
from requests.models import Response
from requests.structures import CaseInsensitiveDict
from requests.utils import get_encoding_from_headers

from rest_framework.settings import api_settings

Expand All @@ -22,6 +29,83 @@ def force_authenticate(request, user=None, token=None):
request._force_auth_token = token


class DjangoTestAdapter(BaseAdapter):
"""
A transport adaptor for `requests`, that makes requests via the
Django WSGI app, rather than making actual HTTP requests ovet the network.
"""
def __init__(self):
self.app = WSGIHandler()
self.factory = DjangoRequestFactory()

def get_environ(self, request):
"""
Given a `requests.PreparedRequest` instance, return a WSGI environ dict.
"""
method = request.method
url = request.url
kwargs = {}

# Set request content, if any exists.
if request.body is not None:
kwargs['data'] = request.body
if 'content-type' in request.headers:
kwargs['content_type'] = request.headers['content-type']

# Set request headers.
for key, value in request.headers.items():
key = key.upper()
if key in ('CONNECTION', 'CONTENT_LENGTH', 'CONTENT-TYPE'):
continue
kwargs['HTTP_%s' % key] = value

return self.factory.generic(method, url, **kwargs).environ

def send(self, request, *args, **kwargs):
"""
Make an outgoing request to the Django WSGI application.
"""
response = Response()

def start_response(status, headers):
status_code, _, reason_phrase = status.partition(' ')
response.status_code = int(status_code)
response.reason = reason_phrase
response.headers = CaseInsensitiveDict(headers)
response.encoding = get_encoding_from_headers(response.headers)

environ = self.get_environ(request)
raw_bytes = self.app(environ, start_response)

response.request = request
response.url = request.url
response.raw = io.BytesIO(b''.join(raw_bytes))

return response

def close(self):
pass


class DjangoTestSession(Session):
def __init__(self, *args, **kwargs):
super(DjangoTestSession, self).__init__(*args, **kwargs)

adapter = DjangoTestAdapter()
hostnames = list(settings.ALLOWED_HOSTS) + ['testserver']

for hostname in hostnames:
if hostname == '*':
hostname = ''
self.mount('http://%s' % hostname, adapter)
self.mount('https://%s' % hostname, adapter)

def request(self, method, url, *args, **kwargs):
if ':' not in url:
url = 'http://testserver/' + url.lstrip('/')
return super(DjangoTestSession, self).request(method, url, *args, **kwargs)


class APIRequestFactory(DjangoRequestFactory):
renderer_classes_list = api_settings.TEST_REQUEST_RENDERER_CLASSES
default_format = api_settings.TEST_REQUEST_DEFAULT_FORMAT
Expand Down Expand Up @@ -224,7 +308,7 @@ class APITestCase(testcases.TestCase):

def _pre_setup(self):
super(APITestCase, self)._pre_setup()
self.requests = Session()
self.requests = DjangoTestSession()


class APISimpleTestCase(testcases.SimpleTestCase):
Expand Down
119 changes: 116 additions & 3 deletions tests/test_requests_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,128 @@

class Root(APIView):
def get(self, request):
return Response({'hello': 'world'})
return Response({
'method': request.method,
'query_params': request.query_params,
})

def post(self, request):
files = {
key: (value.name, value.read())
for key, value in request.FILES.items()
}
post = request.POST
json = None
if request.META.get('CONTENT_TYPE') == 'application/json':
json = request.data

return Response({
'method': request.method,
'query_params': request.query_params,
'POST': post,
'FILES': files,
'JSON': json
})


class Headers(APIView):
def get(self, request):
headers = {
key[5:]: value
for key, value in request.META.items()
if key.startswith('HTTP_')
}
return Response({
'method': request.method,
'headers': headers
})


urlpatterns = [
url(r'^$', Root.as_view()),
url(r'^headers/$', Headers.as_view()),
]


@override_settings(ROOT_URLCONF='tests.test_requests_client')
class RequestsClientTests(APITestCase):
def test_get_root(self):
print self.requests.get('http://example.com')
def test_get_request(self):
response = self.requests.get('/')
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/json'
expected = {
'method': 'GET',
'query_params': {}
}
assert response.json() == expected

def test_get_request_query_params_in_url(self):
response = self.requests.get('/?key=value')
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/json'
expected = {
'method': 'GET',
'query_params': {'key': 'value'}
}
assert response.json() == expected

def test_get_request_query_params_by_kwarg(self):
response = self.requests.get('/', params={'key': 'value'})
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/json'
expected = {
'method': 'GET',
'query_params': {'key': 'value'}
}
assert response.json() == expected

def test_get_with_headers(self):
response = self.requests.get('/headers/', headers={'User-Agent': 'example'})
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/json'
headers = response.json()['headers']
assert headers['USER-AGENT'] == 'example'

def test_post_form_request(self):
response = self.requests.post('/', data={'key': 'value'})
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/json'
expected = {
'method': 'POST',
'query_params': {},
'POST': {'key': 'value'},
'FILES': {},
'JSON': None
}
assert response.json() == expected

def test_post_json_request(self):
response = self.requests.post('/', json={'key': 'value'})
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/json'
expected = {
'method': 'POST',
'query_params': {},
'POST': {},
'FILES': {},
'JSON': {'key': 'value'}
}
assert response.json() == expected

def test_post_multipart_request(self):
files = {
'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')
}
response = self.requests.post('/', files=files)
assert response.status_code == 200
assert response.headers['Content-Type'] == 'application/json'
expected = {
'method': 'POST',
'query_params': {},
'FILES': {'file': ['report.csv', 'some,data,to,send\nanother,row,to,send\n']},
'POST': {},
'JSON': None
}
assert response.json() == expected

# cookies/session auth