Skip to content

Commit ce9e4f1

Browse files
Merge pull request #12 from ekonstantinidis/setup-tests
Setup tests
2 parents 333fbf6 + 72f6efe commit ce9e4f1

File tree

14 files changed

+416
-14
lines changed

14 files changed

+416
-14
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.coverage
2+
covhtml/
3+
14
*.pyc
25
__pycache__/
36

.travis.yml

+7-5
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ language: python
22

33
python:
44
- "2.7"
5-
- "3.3"
65
- "3.4"
76
- "3.5"
8-
- "pypy"
9-
- "pypy3"
7+
8+
env:
9+
- DJANGO_VERSION=1.8
10+
- DJANGO_VERSION=1.9
1011

1112
install:
12-
- pip install -r requirements.txt
13+
- pip install -r requirements.txt
14+
- pip install -U Django==$DJANGO_VERSION
1315

1416
script:
15-
- python runtests.py
17+
- python runtests.py
1618

1719
notifications:
1820
slack:

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1+
Django==1.8.7
2+
djangorestframework==3.3.2
3+
coverage==4.0.3
14
flake8==2.5.1

rest_framework_docs/api_endpoint.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def __init__(self, pattern, parent_pattern=None):
1111
self.path = self.__get_path__(parent_pattern)
1212
self.allowed_methods = self.__get_allowed_methods__()
1313
# self.view_name = pattern.callback.__name__
14+
self.errors = None
1415
self.fields = self.__get_serializer_fields__()
1516

1617
def __get_path__(self, parent_pattern):
@@ -27,11 +28,15 @@ def __get_serializer_fields__(self):
2728
if hasattr(self.callback.cls, 'serializer_class') and hasattr(self.callback.cls.serializer_class, 'get_fields'):
2829
serializer = self.callback.cls.serializer_class
2930
if hasattr(serializer, 'get_fields'):
30-
fields = [{
31-
"name": key,
32-
"type": str(field.__class__.__name__),
33-
"required": field.required
34-
} for key, field in serializer().get_fields().items()]
31+
try:
32+
fields = [{
33+
"name": key,
34+
"type": str(field.__class__.__name__),
35+
"required": field.required
36+
} for key, field in serializer().get_fields().items()]
37+
except KeyError as e:
38+
self.errors = e
39+
fields = []
3540

3641
# FIXME:
3742
# Show more attibutes of `field`?

rest_framework_docs/static/less/style.less

+5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ body {
8686

8787
.panel-body {
8888
padding: 0;
89+
90+
.alert {
91+
padding: 5px 10px;
92+
margin-top: 10px;
93+
}
8994
}
9095

9196
> .panel-heading +.panel-collapse > .panel-body { border: 0; }

rest_framework_docs/templates/rest_framework_docs/home.html

+7-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Jump To <span class="caret"></span></a>
77
<ul class="dropdown-menu">
88
{% for group in endpoints_grouped %}
9-
<li><a href="#{{ group.grouper|lower }}-nav">{{ group.grouper }}</a></li>
9+
<li><a href="#{{ group.grouper|lower }}-group">{{ group.grouper }}</a></li>
1010
{% endfor %}
1111
</ul>
1212
</li>
@@ -20,7 +20,7 @@
2020
{% if endpoints_grouped %}
2121
{% for group in endpoints_grouped %}
2222

23-
<h1 id="{{ group.grouper|lower }}-nav">{{group.grouper}}</h1>
23+
<h1 id="{{ group.grouper|lower }}-group">{{group.grouper}}</h1>
2424

2525
<div class="panel-group" role="tablist">
2626

@@ -48,14 +48,18 @@ <h4 class="panel-title title">
4848

4949
<div id="{{ endpoint.path|slugify }}" class="panel-collapse collapse" role="tabpanel">
5050
<div class="panel-body">
51+
{% if endpoint.errors %}
52+
<div class="alert alert-danger" role="alert">Oops! There was something wrong with {{ endpoint.errors }}. Please check your code.</div>
53+
{% endif %}
54+
5155
{% if endpoint.fields %}
5256
<p>Fields:</p>
5357
<ul class="list fields">
5458
{% for field in endpoint.fields %}
5559
<li class="field">{{ field.name }}: {{ field.type }} {% if field.required %}<span class="label label-primary label-required" title="Required">R</span>{% endif %}</li>
5660
{% endfor %}
5761
</ul>
58-
{% else %}
62+
{% elif not endpoint.errors %}
5963
<p>No fields.</p>
6064
{% endif %}
6165
</div>

runtests.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
#!/usr/bin/env python
2+
import os
23
import sys
34
import subprocess
5+
import django
6+
from coverage import coverage
7+
from django.conf import settings
8+
from django.test.utils import get_runner
49

510

6-
FLAKE8_ARGS = ['demo/project/', 'rest_framework_docs', '--ignore=E501']
11+
FLAKE8_ARGS = ['demo/project/', 'rest_framework_docs', 'tests/', '--ignore=E501']
712

813

914
def exit_on_failure(command, message=None):
@@ -17,4 +22,29 @@ def flake8_main(args):
1722
print("" if command else "Success. flake8 passed.")
1823
return command
1924

25+
26+
def run_tests_coverage():
27+
if __name__ == "__main__":
28+
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
29+
django.setup()
30+
TestRunner = get_runner(settings)
31+
test_runner = TestRunner()
32+
33+
# Setup Coverage
34+
cov = coverage(source=["rest_framework_docs"], omit=["rest_framework_docs/__init__.py"])
35+
cov.start()
36+
37+
failures = test_runner.run_tests(["tests"])
38+
39+
if bool(failures):
40+
cov.erase()
41+
sys.exit("Tests Failed. Coverage Cancelled.")
42+
43+
# If success show coverage results
44+
cov.stop()
45+
cov.save()
46+
cov.report()
47+
cov.html_report(directory='covhtml')
48+
2049
exit_on_failure(flake8_main(FLAKE8_ARGS))
50+
exit_on_failure(run_tests_coverage())

tests/__init__.py

Whitespace-only changes.

tests/models.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import uuid
2+
from django.db import models
3+
from django.contrib.auth.models import AbstractBaseUser
4+
5+
6+
class User(AbstractBaseUser):
7+
created = models.DateTimeField(auto_now_add=True, db_index=True)
8+
modified = models.DateTimeField(auto_now=True)
9+
10+
email = models.EmailField(unique=True, verbose_name='email address', max_length=255)
11+
full_name = models.CharField(max_length=255)
12+
13+
is_active = models.BooleanField(default=False)
14+
is_admin = models.BooleanField(default=False)
15+
16+
USERNAME_FIELD = 'email'
17+
REQUIRED_FIELDS = ['email', 'full_name']
18+
19+
20+
class Organisation(models.Model):
21+
22+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
23+
created = models.DateTimeField(auto_now_add=True)
24+
modified = models.DateTimeField(auto_now=True)
25+
26+
name = models.CharField(unique=True, max_length=100)
27+
slug = models.SlugField(unique=True, null=True, blank=True)
28+
members = models.ManyToManyField(User, through='Membership', through_fields=('organisation', 'user'))
29+
30+
is_active = models.BooleanField(default=False)
31+
32+
33+
class Membership(models.Model):
34+
35+
class Meta:
36+
unique_together = ("organisation", "user")
37+
38+
MEMBER_ROLES = (
39+
("ADMIN", "Admin"),
40+
("USER", "User")
41+
)
42+
43+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
44+
joined = models.DateTimeField(auto_now_add=True)
45+
46+
organisation = models.ForeignKey(Organisation)
47+
user = models.ForeignKey(User)
48+
role = models.CharField(choices=MEMBER_ROLES, max_length=20, default="USER")
49+
is_owner = models.BooleanField(default=False)

tests/serializers.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from __future__ import absolute_import, division, print_function
2+
3+
from rest_framework import serializers
4+
from tests.models import User, Organisation, Membership
5+
6+
7+
class UserRegistrationSerializer(serializers.ModelSerializer):
8+
9+
class Meta:
10+
model = User
11+
fields = ('email', 'full_name', 'password',)
12+
extra_kwargs = {'password': {'write_only': True}}
13+
14+
15+
class UserProfileSerializer(serializers.ModelSerializer):
16+
17+
class Meta:
18+
model = User
19+
fields = ('email', 'full_name', 'password', 'is_active')
20+
extra_kwargs = {
21+
'password': {'write_only': True}
22+
}
23+
read_only_fields = ('is_active',)
24+
25+
26+
class ResetPasswordSerializer(serializers.ModelSerializer):
27+
28+
id = serializers.CharField()
29+
token = serializers.CharField()
30+
31+
class Meta:
32+
model = User
33+
fields = ('id', 'token', 'password',)
34+
extra_kwargs = {'password': {'write_only': True}}
35+
36+
37+
class CreateOrganisationSerializer(serializers.ModelSerializer):
38+
39+
class Meta:
40+
model = Organisation
41+
fields = ('name', 'slug',)
42+
43+
44+
class OrganisationMembersSerializer(serializers.ModelSerializer):
45+
user = serializers.SerializerMethodField()
46+
47+
class Meta:
48+
model = Membership
49+
fields = ('joined', 'user', 'is_owner', 'role')
50+
51+
def get_user(self, obj):
52+
serializer = UserProfileSerializer(obj.user)
53+
return serializer.data
54+
55+
56+
class OrganisationErroredSerializer(serializers.ModelSerializer):
57+
58+
class Meta:
59+
model = Organisation
60+
fields = ('name', 'slug', 'is_active')
61+
62+
def __init__(self, *args, **kwargs):
63+
super(OrganisationErroredSerializer, self).__init__(*args, **kwargs)
64+
65+
# Should raise a KeyError
66+
self.context["test_value"]

tests/settings.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import os
2+
3+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
4+
5+
SECRET_KEY = 'django-rest-framework-docs-key'
6+
7+
# Database
8+
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
9+
10+
DATABASES = {
11+
'default': {
12+
'ENGINE': 'django.db.backends.sqlite3',
13+
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
14+
}
15+
}
16+
17+
INSTALLED_APPS = [
18+
# Django Apps
19+
'django.contrib.admin',
20+
'django.contrib.auth',
21+
'django.contrib.contenttypes',
22+
'django.contrib.staticfiles',
23+
24+
# External Packages
25+
"rest_framework",
26+
"rest_framework_docs",
27+
28+
# Test apps
29+
"tests"
30+
]
31+
32+
ROOT_URLCONF = 'tests.urls'
33+
34+
REST_FRAMEWORK_DOCS = {
35+
'HIDDEN': False
36+
}
37+
38+
# Static files (CSS, JavaScript, Images)
39+
# https://docs.djangoproject.com/en/1.8/howto/static-files/
40+
41+
STATIC_URL = '/static/'

tests/tests.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from django.core.urlresolvers import reverse
2+
from django.test import TestCase, override_settings
3+
from rest_framework_docs.settings import DRFSettings
4+
5+
6+
class DRFDocsViewTests(TestCase):
7+
8+
SETTINGS_HIDE_DOCS = {
9+
'HIDDEN': True # Default: False
10+
}
11+
12+
def setUp(self):
13+
super(DRFDocsViewTests, self).setUp()
14+
15+
def test_settings_module(self):
16+
17+
settings = DRFSettings()
18+
19+
self.assertEqual(settings.get_setting("HIDDEN"), False)
20+
self.assertEqual(settings.get_setting("TEST"), None)
21+
22+
def test_index_view_with_endpoints(self):
23+
"""
24+
Should load the drf focs view with all the endpoints.
25+
NOTE: Views that do **not** inherit from DRF's "APIView" are not included.
26+
"""
27+
response = self.client.get(reverse('drfdocs'))
28+
29+
self.assertEqual(response.status_code, 200)
30+
self.assertEqual(len(response.context["endpoints"]), 10)
31+
32+
# Test the login view
33+
self.assertEqual(response.context["endpoints"][0].name_parent, "accounts")
34+
self.assertEqual(response.context["endpoints"][0].allowed_methods, ['POST', 'OPTIONS'])
35+
self.assertEqual(response.context["endpoints"][0].path, "/accounts/login/")
36+
self.assertEqual(len(response.context["endpoints"][0].fields), 2)
37+
self.assertEqual(response.context["endpoints"][0].fields[0]["type"], "CharField")
38+
self.assertTrue(response.context["endpoints"][0].fields[0]["required"])
39+
40+
# The view "OrganisationErroredView" (organisations/(?P<slug>[\w-]+)/errored/) should contain an error.
41+
self.assertEqual(str(response.context["endpoints"][8].errors), "'test_value'")
42+
43+
@override_settings(REST_FRAMEWORK_DOCS=SETTINGS_HIDE_DOCS)
44+
def test_index_view_docs_hidden(self):
45+
"""
46+
Should prevent the docs from loading the "HIDDEN" is set
47+
to "False" in settings
48+
"""
49+
response = self.client.get(reverse('drfdocs'))
50+
51+
self.assertEqual(response.status_code, 404)
52+
self.assertEqual(response.reason_phrase.upper(), "NOT FOUND")

0 commit comments

Comments
 (0)