mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-21 19:18:41 +00:00
Merge pull request #319 from p1c2u/feature/django-integration-tests
Django integration tests
This commit is contained in:
commit
b79c49420e
13 changed files with 303 additions and 3 deletions
27
openapi_core/contrib/django/backports.py
Normal file
27
openapi_core/contrib/django/backports.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
"""OpenAPI core contrib django backports module"""
|
||||||
|
|
||||||
|
|
||||||
|
class HttpHeaders(dict):
|
||||||
|
HTTP_PREFIX = 'HTTP_'
|
||||||
|
# PEP 333 gives two headers which aren't prepended with HTTP_.
|
||||||
|
UNPREFIXED_HEADERS = {'CONTENT_TYPE', 'CONTENT_LENGTH'}
|
||||||
|
|
||||||
|
def __init__(self, environ):
|
||||||
|
headers = {}
|
||||||
|
for header, value in environ.items():
|
||||||
|
name = self.parse_header_name(header)
|
||||||
|
if name:
|
||||||
|
headers[name] = value
|
||||||
|
super(HttpHeaders, self).__init__(headers)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_header_name(cls, header):
|
||||||
|
if header.startswith(cls.HTTP_PREFIX):
|
||||||
|
header = header[len(cls.HTTP_PREFIX):]
|
||||||
|
elif header not in cls.UNPREFIXED_HEADERS:
|
||||||
|
return None
|
||||||
|
return header.replace('_', '-').title()
|
||||||
|
|
||||||
|
|
||||||
|
def request_current_scheme_host(req):
|
||||||
|
return '{}://{}'.format(req.scheme, req.get_host())
|
16
openapi_core/contrib/django/compat.py
Normal file
16
openapi_core/contrib/django/compat.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""OpenAPI core contrib django compat module"""
|
||||||
|
from openapi_core.contrib.django.backports import (
|
||||||
|
HttpHeaders, request_current_scheme_host,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_headers(req):
|
||||||
|
# in Django 1 headers is not defined
|
||||||
|
return req.headers if hasattr(req, 'headers') else \
|
||||||
|
HttpHeaders(req.META)
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_scheme_host(req):
|
||||||
|
# in Django 1 _current_scheme_host is not defined
|
||||||
|
return req._current_scheme_host if hasattr(req, '_current_scheme_host') \
|
||||||
|
else request_current_scheme_host(req)
|
|
@ -3,6 +3,9 @@ import re
|
||||||
|
|
||||||
from six.moves.urllib.parse import urljoin
|
from six.moves.urllib.parse import urljoin
|
||||||
|
|
||||||
|
from openapi_core.contrib.django.compat import (
|
||||||
|
get_headers, get_current_scheme_host,
|
||||||
|
)
|
||||||
from openapi_core.validation.request.datatypes import (
|
from openapi_core.validation.request.datatypes import (
|
||||||
RequestParameters, OpenAPIRequest,
|
RequestParameters, OpenAPIRequest,
|
||||||
)
|
)
|
||||||
|
@ -36,14 +39,15 @@ class DjangoOpenAPIRequestFactory(object):
|
||||||
path_pattern = '/' + route
|
path_pattern = '/' + route
|
||||||
|
|
||||||
path = request.resolver_match and request.resolver_match.kwargs or {}
|
path = request.resolver_match and request.resolver_match.kwargs or {}
|
||||||
|
headers = get_headers(request)
|
||||||
parameters = RequestParameters(
|
parameters = RequestParameters(
|
||||||
path=path,
|
path=path,
|
||||||
query=request.GET,
|
query=request.GET,
|
||||||
header=request.headers.items(),
|
header=headers.items(),
|
||||||
cookie=request.COOKIES,
|
cookie=request.COOKIES,
|
||||||
)
|
)
|
||||||
full_url_pattern = urljoin(
|
current_scheme_host = get_current_scheme_host(request)
|
||||||
request._current_scheme_host, path_pattern)
|
full_url_pattern = urljoin(current_scheme_host, path_pattern)
|
||||||
return OpenAPIRequest(
|
return OpenAPIRequest(
|
||||||
full_url_pattern=full_url_pattern,
|
full_url_pattern=full_url_pattern,
|
||||||
method=method,
|
method=method,
|
||||||
|
|
|
@ -5,7 +5,9 @@ pytest-cov==2.5.1
|
||||||
falcon==2.0.0; python_version<"3.0"
|
falcon==2.0.0; python_version<"3.0"
|
||||||
falcon==3.0.0; python_version>="3.0"
|
falcon==3.0.0; python_version>="3.0"
|
||||||
flask
|
flask
|
||||||
|
django==1.11.29; python_version<"3.0"
|
||||||
django==2.2.18; python_version>="3.0"
|
django==2.2.18; python_version>="3.0"
|
||||||
|
djangorestframework==3.9.4
|
||||||
requests==2.22.0
|
requests==2.22.0
|
||||||
responses==0.10.12
|
responses==0.10.12
|
||||||
webob
|
webob
|
||||||
|
|
21
tests/integration/contrib/django/conftest.py
Normal file
21
tests/integration/contrib/django/conftest.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import mock
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.yield_fixture(autouse=True, scope='module')
|
||||||
|
def django_setup():
|
||||||
|
directory = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
django_project_dir = os.path.join(directory, 'data')
|
||||||
|
sys.path.insert(0, django_project_dir)
|
||||||
|
with mock.patch.dict(
|
||||||
|
os.environ,
|
||||||
|
{
|
||||||
|
'DJANGO_SETTINGS_MODULE': 'djangoproject.settings',
|
||||||
|
}
|
||||||
|
):
|
||||||
|
import django
|
||||||
|
django.setup()
|
||||||
|
yield
|
||||||
|
sys.path.remove(django_project_dir)
|
110
tests/integration/contrib/django/data/djangoproject/settings.py
Normal file
110
tests/integration/contrib/django/data/djangoproject/settings.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
"""
|
||||||
|
Django settings for djangotest project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 2.2.18.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/2.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/2.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '9=z^yj5yo%g_dyvgdzbceyph^nae)91lq(7^!qqmr1t9wi8b^='
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['testserver']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'rest_framework',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'djangotest.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'djangotest.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
OPENAPI_SPEC_PATH = os.path.join(BASE_DIR, 'openapi.yaml')
|
|
@ -0,0 +1,43 @@
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from openapi_core import create_spec
|
||||||
|
from openapi_core.validation.request.validators import RequestValidator
|
||||||
|
from openapi_core.validation.response.validators import ResponseValidator
|
||||||
|
from openapi_core.contrib.django import (
|
||||||
|
DjangoOpenAPIRequest, DjangoOpenAPIResponse,
|
||||||
|
)
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from djangoproject import settings
|
||||||
|
|
||||||
|
|
||||||
|
class TestView(APIView):
|
||||||
|
|
||||||
|
def get(self, request, pk):
|
||||||
|
with open(settings.OPENAPI_SPEC_PATH) as file:
|
||||||
|
spec_yaml = file.read()
|
||||||
|
spec_dict = yaml.load(spec_yaml)
|
||||||
|
spec = create_spec(spec_dict)
|
||||||
|
|
||||||
|
openapi_request = DjangoOpenAPIRequest(request)
|
||||||
|
|
||||||
|
request_validator = RequestValidator(spec)
|
||||||
|
result = request_validator.validate(openapi_request)
|
||||||
|
result.raise_for_errors()
|
||||||
|
|
||||||
|
response_dict = {
|
||||||
|
"test": "test_val",
|
||||||
|
}
|
||||||
|
django_response = JsonResponse(response_dict)
|
||||||
|
|
||||||
|
openapi_response = DjangoOpenAPIResponse(django_response)
|
||||||
|
validator = ResponseValidator(spec)
|
||||||
|
result = validator.validate(openapi_request, openapi_response)
|
||||||
|
result.raise_for_errors()
|
||||||
|
|
||||||
|
return django_response
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_extra_actions():
|
||||||
|
return []
|
31
tests/integration/contrib/django/data/djangoproject/urls.py
Normal file
31
tests/integration/contrib/django/data/djangoproject/urls.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"""djangotest URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/2.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path
|
||||||
|
from djangotest.testapp import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path(
|
||||||
|
'api-auth/',
|
||||||
|
include('rest_framework.urls', namespace='rest_framework'),
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'test/<int:pk>',
|
||||||
|
views.TestView.as_view(),
|
||||||
|
name='test',
|
||||||
|
),
|
||||||
|
]
|
26
tests/integration/contrib/django/data/openapi.yaml
Normal file
26
tests/integration/contrib/django/data/openapi.yaml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
openapi: '3.0.0'
|
||||||
|
info:
|
||||||
|
version: '0.0.1'
|
||||||
|
title: Test Service
|
||||||
|
paths:
|
||||||
|
'/test/{pk}':
|
||||||
|
get:
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Default
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
test:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- test
|
||||||
|
parameters:
|
||||||
|
- required: true
|
||||||
|
in: path
|
||||||
|
name: pk
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
|
@ -0,0 +1,20 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from six import b
|
||||||
|
|
||||||
|
|
||||||
|
class TestDjangoRESTFrameworkAPIView(object):
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def api_request_factory(self):
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
|
return APIRequestFactory()
|
||||||
|
|
||||||
|
def test_get(self, api_request_factory):
|
||||||
|
from djangoproject.testapp.views import TestView
|
||||||
|
view = TestView.as_view()
|
||||||
|
request = api_request_factory.get('/test/4')
|
||||||
|
|
||||||
|
response = view(request, pk='4')
|
||||||
|
|
||||||
|
assert response.content == b('{"test": "test_val"}')
|
Loading…
Reference in a new issue