mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-01 11:00:13 +03:00
removed mock library from tests
This commit is contained in:
parent
879ac30482
commit
576b70a7e7
|
@ -1,4 +1,5 @@
|
||||||
# Pytest for running the tests.
|
# Pytest for running the tests.
|
||||||
|
coverage_enable_subprocess
|
||||||
pytest==3.6.2
|
pytest==3.6.2
|
||||||
pytest-django==3.3.2
|
pytest-django==3.3.2
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
|
|
|
@ -1,184 +1,404 @@
|
||||||
try:
|
import os
|
||||||
from unittest.mock import PropertyMock, patch
|
import coverage
|
||||||
except ImportError:
|
import shutil
|
||||||
from mock import PropertyMock, patch
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from django.core.management import call_command
|
import django
|
||||||
|
from django import conf
|
||||||
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.six import StringIO
|
|
||||||
|
|
||||||
from .test_api_client import get_schema
|
|
||||||
|
|
||||||
|
|
||||||
class GenerateSchemaTests(TestCase):
|
class BaseTestProjectTestsCase(TestCase):
|
||||||
"""Tests for management command generateeschema."""
|
|
||||||
|
|
||||||
def test_WIP_should_raise_AssertionError_if_coreapi_is_not_installed(self): # noqa
|
def setUp(self):
|
||||||
m_coreapi = PropertyMock(return_value=None)
|
tmpdir = tempfile.TemporaryDirectory()
|
||||||
with patch("rest_framework.management.commands.generateschema.coreapi",
|
self.addCleanup(tmpdir.cleanup)
|
||||||
new_callable=m_coreapi):
|
self.test_dir = os.path.join(tmpdir.name, 'test_project')
|
||||||
with self.assertRaisesRegexp(
|
os.mkdir(self.test_dir)
|
||||||
AssertionError, "coreapi must be installed"):
|
with open(os.path.join(self.test_dir, '__init__.py'), 'w'):
|
||||||
call_command('generateschema')
|
pass
|
||||||
|
|
||||||
@patch('sys.stdout', new_callable=StringIO)
|
def write_settings(self, filename, apps=[], sdict={}):
|
||||||
@patch('rest_framework.management.commands.generateschema.SchemaGenerator')
|
settings_file_path = os.path.join(self.test_dir, filename)
|
||||||
def test_WIP_should_call_SchemaGenerator_with_options(self, m_SchemaGenerator, m_stdout): # noqa
|
|
||||||
m_SchemaGenerator.return_value.get_schema.return_value = get_schema()
|
|
||||||
|
|
||||||
call_command('generateschema', '--title=Example API',
|
with open(settings_file_path, 'w') as settings_file:
|
||||||
'--url=http://api.example.com', '--description=Example')
|
exports = [
|
||||||
|
'DATABASES',
|
||||||
|
'SECRET_KEY',
|
||||||
|
'ROOT_URLCONF',
|
||||||
|
]
|
||||||
|
for s in exports:
|
||||||
|
if hasattr(settings, s):
|
||||||
|
o = getattr(settings, s)
|
||||||
|
if not isinstance(o, (dict, tuple, list)):
|
||||||
|
o = "'%s'" % o
|
||||||
|
settings_file.write("%s = %s\n" % (s, sdict.pop(s, o)))
|
||||||
|
|
||||||
m_SchemaGenerator.assert_called_once_with(
|
installed_apps = [
|
||||||
description='Example', title='Example API',
|
'django.contrib.auth',
|
||||||
url='http://api.example.com')
|
'django.contrib.contenttypes',
|
||||||
|
'rest_framework'
|
||||||
|
]
|
||||||
|
installed_apps.extend(apps)
|
||||||
|
|
||||||
@patch('sys.stdout', new_callable=StringIO)
|
settings_file.write("INSTALLED_APPS = %s\n" % installed_apps)
|
||||||
@patch('rest_framework.management.commands.generateschema.SchemaGenerator')
|
|
||||||
def test_WIP_should_render_openapi_schema(self, m_SchemaGenerator, m_stdout): # noqa
|
if sdict:
|
||||||
m_SchemaGenerator.return_value.get_schema.return_value = get_schema()
|
for k, v in sdict.items():
|
||||||
expected_stdout = """info:
|
settings_file.write("%s = %s\n" % (k, v))
|
||||||
|
|
||||||
|
def run_test(self, script, args, settings_file=None):
|
||||||
|
base_dir = os.path.dirname(self.test_dir)
|
||||||
|
tests_dir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
django_dir = os.path.dirname(tests_dir)
|
||||||
|
ext_backend_base_dirs = self._ext_backend_paths()
|
||||||
|
|
||||||
|
# Define a temporary environment for the subprocess
|
||||||
|
test_environ = os.environ.copy()
|
||||||
|
|
||||||
|
# Set the test environment
|
||||||
|
if settings_file:
|
||||||
|
test_environ['DJANGO_SETTINGS_MODULE'] = settings_file
|
||||||
|
elif 'DJANGO_SETTINGS_MODULE' in test_environ:
|
||||||
|
del test_environ['DJANGO_SETTINGS_MODULE']
|
||||||
|
python_path = [base_dir, django_dir, tests_dir]
|
||||||
|
python_path.extend(ext_backend_base_dirs)
|
||||||
|
test_environ['PYTHONPATH'] = os.pathsep.join(python_path)
|
||||||
|
test_environ['PYTHONWARNINGS'] = ''
|
||||||
|
|
||||||
|
coverage.process_startup()
|
||||||
|
return subprocess.Popen(
|
||||||
|
[sys.executable, script] + args,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||||
|
cwd=self.test_dir,
|
||||||
|
env=test_environ, universal_newlines=True,
|
||||||
|
).communicate()
|
||||||
|
|
||||||
|
def run_django_admin(self, args, settings_file=None):
|
||||||
|
script_dir = os.path.abspath(os.path.join(os.path.dirname(django.__file__), 'bin'))
|
||||||
|
return self.run_test(os.path.join(script_dir, 'django-admin.py'), args, settings_file)
|
||||||
|
|
||||||
|
def run_manage(self, args, settings_file=None, configured_settings=False):
|
||||||
|
template_manage_py = (
|
||||||
|
os.path.join(os.path.dirname(__file__), 'configured_settings_manage.py')
|
||||||
|
if configured_settings else
|
||||||
|
os.path.join(os.path.dirname(conf.__file__), 'project_template', 'manage.py-tpl')
|
||||||
|
)
|
||||||
|
test_manage_py = os.path.join(self.test_dir, 'manage.py')
|
||||||
|
shutil.copyfile(template_manage_py, test_manage_py)
|
||||||
|
|
||||||
|
with open(test_manage_py) as fp:
|
||||||
|
manage_py_contents = fp.read()
|
||||||
|
manage_py_contents = manage_py_contents.replace(
|
||||||
|
"{{ project_name }}", "test_project")
|
||||||
|
with open(test_manage_py, 'w') as fp:
|
||||||
|
fp.write(manage_py_contents)
|
||||||
|
|
||||||
|
return self.run_test('./manage.py', args, settings_file)
|
||||||
|
|
||||||
|
def create_mysite_app(self):
|
||||||
|
self.run_django_admin(['startapp', 'mysite'], 'settings.py')
|
||||||
|
self.app_path = os.path.join(self.test_dir, 'mysite')
|
||||||
|
self._prepare_models()
|
||||||
|
self._prepare_serializers()
|
||||||
|
self._prepare_views()
|
||||||
|
self.write_settings('settings.py', apps=['mysite'])
|
||||||
|
self._create_urls()
|
||||||
|
self.write_settings('settings.py', apps=['mysite'],
|
||||||
|
sdict={'ROOT_URLCONF': "'urls'"})
|
||||||
|
|
||||||
|
def _ext_backend_paths(self):
|
||||||
|
"""
|
||||||
|
Returns the paths for any external backend packages.
|
||||||
|
"""
|
||||||
|
paths = []
|
||||||
|
for backend in settings.DATABASES.values():
|
||||||
|
package = backend['ENGINE'].split('.')[0]
|
||||||
|
if package != 'django':
|
||||||
|
backend_pkg = __import__(package)
|
||||||
|
backend_dir = os.path.dirname(backend_pkg.__file__)
|
||||||
|
paths.append(os.path.dirname(backend_dir))
|
||||||
|
return paths
|
||||||
|
|
||||||
|
def _create_urls(self):
|
||||||
|
with open(os.path.join(self.test_dir, 'urls.py'), 'w') as f:
|
||||||
|
f.write("""from django.urls import include, path
|
||||||
|
|
||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
|
from mysite import views
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r'sample', views.SampleViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
|
||||||
|
def _prepare_models(self):
|
||||||
|
with open(os.path.join(self.app_path, 'models.py'), 'w') as f:
|
||||||
|
f.write("""from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Sample(models.Model):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
|
||||||
|
def _prepare_serializers(self):
|
||||||
|
with open(os.path.join(self.app_path, 'serializers.py'), 'w') as f:
|
||||||
|
f.write("""from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import Sample
|
||||||
|
|
||||||
|
|
||||||
|
class SampleSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Sample
|
||||||
|
fields = '__all__'
|
||||||
|
""")
|
||||||
|
|
||||||
|
def _prepare_views(self):
|
||||||
|
with open(os.path.join(self.app_path, 'views.py'), 'w') as f:
|
||||||
|
f.write('''from rest_framework import viewsets
|
||||||
|
|
||||||
|
from .models import Sample
|
||||||
|
from .serializers import SampleSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SampleViewSet(viewsets.ModelViewSet):
|
||||||
|
"""Test API description."""
|
||||||
|
|
||||||
|
queryset = Sample.objects.all()
|
||||||
|
serializer_class = SampleSerializer
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateSchemaTests(BaseTestProjectTestsCase):
|
||||||
|
"""Tests for management command generateschema."""
|
||||||
|
|
||||||
|
def setUp(self): # noqa
|
||||||
|
super(GenerateSchemaTests, self).setUp()
|
||||||
|
self.write_settings('settings.py')
|
||||||
|
self.create_mysite_app()
|
||||||
|
|
||||||
|
def test_should_r_custom_title_url_and_description(self):
|
||||||
|
expected_nodes = """description: Sample description
|
||||||
|
title: Sample API
|
||||||
|
url: http://api.sample.com
|
||||||
|
"""
|
||||||
|
out, err = self.run_manage(['generateschema', '--title=Sample API',
|
||||||
|
'--url=http://api.sample.com',
|
||||||
|
'--description=Sample description'])
|
||||||
|
|
||||||
|
self.assertEqual(err, '')
|
||||||
|
for node in expected_nodes.splitlines():
|
||||||
|
self.assertIn(node, out)
|
||||||
|
|
||||||
|
def test_should_render_default_schema(self):
|
||||||
|
expected_out = """info:
|
||||||
description: ''
|
description: ''
|
||||||
title: Example API
|
title: ''
|
||||||
version: ''
|
version: ''
|
||||||
openapi: 3.0.0
|
openapi: 3.0.0
|
||||||
paths:
|
paths:
|
||||||
/download/:
|
/sample/:
|
||||||
? ''
|
get:
|
||||||
: operationId: response_download
|
description: Test API description.
|
||||||
|
operationId: sample_list
|
||||||
tags:
|
tags:
|
||||||
- response
|
- sample
|
||||||
/example/:
|
|
||||||
? ''
|
|
||||||
: operationId: location_query
|
|
||||||
tags:
|
|
||||||
- location
|
|
||||||
post:
|
post:
|
||||||
operationId: location_body
|
description: Test API description.
|
||||||
|
operationId: sample_create
|
||||||
tags:
|
tags:
|
||||||
- location
|
- sample
|
||||||
/example/{id}:
|
/sample/{id}/:
|
||||||
? ''
|
delete:
|
||||||
: operationId: location_path
|
description: Test API description.
|
||||||
|
operationId: sample_delete
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- in: path
|
||||||
name: id
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
description: A unique integer value identifying this sample.
|
||||||
|
title: ID
|
||||||
|
type: integer
|
||||||
tags:
|
tags:
|
||||||
- location
|
- sample
|
||||||
/headers/:
|
get:
|
||||||
? ''
|
description: Test API description.
|
||||||
: operationId: headers
|
operationId: sample_read
|
||||||
/text/:
|
parameters:
|
||||||
? ''
|
- in: path
|
||||||
: operationId: response_text
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
description: A unique integer value identifying this sample.
|
||||||
|
title: ID
|
||||||
|
type: integer
|
||||||
tags:
|
tags:
|
||||||
- response
|
- sample
|
||||||
/upload/:
|
patch:
|
||||||
post:
|
description: Test API description.
|
||||||
operationId: encoding_raw_upload
|
operationId: sample_partial_update
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
description: A unique integer value identifying this sample.
|
||||||
|
title: ID
|
||||||
|
type: integer
|
||||||
tags:
|
tags:
|
||||||
- encoding
|
- sample
|
||||||
|
put:
|
||||||
|
description: Test API description.
|
||||||
|
operationId: sample_update
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
description: A unique integer value identifying this sample.
|
||||||
|
title: ID
|
||||||
|
type: integer
|
||||||
|
tags:
|
||||||
|
- sample
|
||||||
servers:
|
servers:
|
||||||
- url: https://api.example.com/
|
- url: ''
|
||||||
""" # noqa
|
"""
|
||||||
|
|
||||||
call_command('generateschema', '--title=Example API',
|
out, err = self.run_manage(['generateschema'])
|
||||||
'--url=http://api.example.com', '--description=Example')
|
|
||||||
|
|
||||||
m_SchemaGenerator.assert_called_once_with(
|
self.assertEqual(err, '')
|
||||||
description='Example', title='Example API',
|
self.assertIn(out, expected_out)
|
||||||
url='http://api.example.com')
|
|
||||||
self.assertEqual(expected_stdout, m_stdout.getvalue())
|
|
||||||
|
|
||||||
@patch('sys.stdout', new_callable=StringIO)
|
def test_should_render_openapi_json_schema(self):
|
||||||
@patch('rest_framework.management.commands.generateschema.SchemaGenerator')
|
expected_out = """{
|
||||||
def test_WIP_should_render_openapi_json_schema(self, m_SchemaGenerator, m_stdout): # noqa
|
|
||||||
m_SchemaGenerator.return_value.get_schema.return_value = get_schema()
|
|
||||||
expected_stdout = '''{
|
|
||||||
"openapi": "3.0.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"version": "",
|
"version": "",
|
||||||
"title": "Example API",
|
"title": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"url": "https://api.example.com/"
|
"url": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/example/": {
|
"/sample/": {
|
||||||
"": {
|
"get": {
|
||||||
"operationId": "location_query",
|
"operationId": "sample_list",
|
||||||
|
"description": "Test API description.",
|
||||||
"tags": [
|
"tags": [
|
||||||
"location"
|
"sample"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "location_body",
|
"operationId": "sample_create",
|
||||||
|
"description": "Test API description.",
|
||||||
"tags": [
|
"tags": [
|
||||||
"location"
|
"sample"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/headers/": {
|
"/sample/{id}/": {
|
||||||
"": {
|
"get": {
|
||||||
"operationId": "headers"
|
"operationId": "sample_read",
|
||||||
}
|
"description": "Test API description.",
|
||||||
},
|
|
||||||
"/upload/": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "encoding_raw_upload",
|
|
||||||
"tags": [
|
|
||||||
"encoding"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/example/{id}": {
|
|
||||||
"": {
|
|
||||||
"operationId": "location_path",
|
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path"
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "ID",
|
||||||
|
"description": "A unique integer value identifying this sample."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"location"
|
"sample"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
},
|
"put": {
|
||||||
"/download/": {
|
"operationId": "sample_update",
|
||||||
"": {
|
"description": "Test API description.",
|
||||||
"operationId": "response_download",
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "ID",
|
||||||
|
"description": "A unique integer value identifying this sample."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"response"
|
"sample"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
},
|
"patch": {
|
||||||
"/text/": {
|
"operationId": "sample_partial_update",
|
||||||
"": {
|
"description": "Test API description.",
|
||||||
"operationId": "response_text",
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "ID",
|
||||||
|
"description": "A unique integer value identifying this sample."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"response"
|
"sample"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "sample_delete",
|
||||||
|
"description": "Test API description.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "ID",
|
||||||
|
"description": "A unique integer value identifying this sample."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"sample"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''' # noqa
|
"""
|
||||||
|
|
||||||
call_command('generateschema', '--format=openapi-json')
|
out, err = self.run_manage(['generateschema', '--format=openapi-json'])
|
||||||
|
|
||||||
self.assertEqual(expected_stdout, m_stdout.getvalue())
|
self.assertEqual(err, '')
|
||||||
|
self.assertIn(out, expected_out)
|
||||||
|
|
||||||
@patch('sys.stdout', new_callable=StringIO)
|
def test_should_render_corejson_schema(self):
|
||||||
@patch('rest_framework.management.commands.generateschema.SchemaGenerator')
|
expected_out = """{"_type":"document","sample":{"list":{"_type":"link","url":"/sample/","action":"get","description":"Test API description."},"create":{"_type":"link","url":"/sample/","action":"post","description":"Test API description."},"read":{"_type":"link","url":"/sample/{id}/","action":"get","description":"Test API description.","fields":[{"name":"id","required":true,"location":"path","schema":{"_type":"integer","title":"ID","description":"A unique integer value identifying this sample."}}]},"update":{"_type":"link","url":"/sample/{id}/","action":"put","description":"Test API description.","fields":[{"name":"id","required":true,"location":"path","schema":{"_type":"integer","title":"ID","description":"A unique integer value identifying this sample."}}]},"partial_update":{"_type":"link","url":"/sample/{id}/","action":"patch","description":"Test API description.","fields":[{"name":"id","required":true,"location":"path","schema":{"_type":"integer","title":"ID","description":"A unique integer value identifying this sample."}}]},"delete":{"_type":"link","url":"/sample/{id}/","action":"delete","description":"Test API description.","fields":[{"name":"id","required":true,"location":"path","schema":{"_type":"integer","title":"ID","description":"A unique integer value identifying this sample."}}]}}}""" # noqa
|
||||||
def test_WIP_should_render_corejson_schema(self, m_SchemaGenerator, m_stdout): # noqa
|
|
||||||
m_SchemaGenerator.return_value.get_schema.return_value = get_schema()
|
|
||||||
expected_stdout = '''{"_type":"document","_meta":{"url":"https://api.example.com/","title":"Example API"},"encoding":{"multipart":{"_type":"link","url":"/example/","action":"post","encoding":"multipart/form-data","fields":[{"name":"example"}]},"multipart-body":{"_type":"link","url":"/example/","action":"post","encoding":"multipart/form-data","fields":[{"name":"example","location":"body"}]},"urlencoded":{"_type":"link","url":"/example/","action":"post","encoding":"application/x-www-form-urlencoded","fields":[{"name":"example"}]},"urlencoded-body":{"_type":"link","url":"/example/","action":"post","encoding":"application/x-www-form-urlencoded","fields":[{"name":"example","location":"body"}]},"raw_upload":{"_type":"link","url":"/upload/","action":"post","encoding":"application/octet-stream","fields":[{"name":"example","location":"body"}]}},"location":{"form":{"_type":"link","url":"/example/","action":"post","fields":[{"name":"example"}]},"body":{"_type":"link","url":"/example/","action":"post","fields":[{"name":"example","location":"body"}]},"query":{"_type":"link","url":"/example/","fields":[{"name":"example","schema":{"_type":"string","title":"","description":"example field"}}]},"path":{"_type":"link","url":"/example/{id}","fields":[{"name":"id","location":"path"}]}},"response":{"download":{"_type":"link","url":"/download/"},"text":{"_type":"link","url":"/text/"}},"simple_link":{"_type":"link","url":"/example/","description":"example link"},"headers":{"_type":"link","url":"/headers/"}}
|
|
||||||
''' # noqa
|
|
||||||
|
|
||||||
call_command('generateschema', '--format=corejson')
|
out, err = self.run_manage(['generateschema', '--format=corejson'])
|
||||||
|
|
||||||
self.assertEqual(expected_stdout, m_stdout.getvalue())
|
self.assertEqual(err, '')
|
||||||
|
self.assertJSONEqual(out, expected_out)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user