diff --git a/tests/app.py b/tests/app.py index 74e0f1a..b45507c 100644 --- a/tests/app.py +++ b/tests/app.py @@ -1,6 +1,9 @@ """ SQLAlchemy JSONAPI Test App. +This app implements the backend of a web forum. It's rather simple but +provides us with a more comprehensive example for testing. + Colton Provias MIT License """ @@ -9,13 +12,15 @@ from flask import Flask, request from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import Boolean, Column, ForeignKey, Unicode, UnicodeText +from sqlalchemy import Boolean, Column, Enum, ForeignKey, Unicode, UnicodeText from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import backref, relationship, validates -from sqlalchemy_jsonapi import (INTERACTIVE_PERMISSIONS, Endpoint, - FlaskJSONAPI, Method, Permissions, - permission_test) -from sqlalchemy_utils import EmailType, PasswordType, Timestamp, UUIDType +from sqlalchemy_utils import (EmailType, IPAddressType, PasswordType, + Timestamp, URLType, UUIDType) + +import enum + +# ================================ APP CONFIG ================================ app = Flask(__name__) @@ -26,29 +31,21 @@ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' app.config['SQLALCHEMY_ECHO'] = False +#api = FlaskJSONAPI(app, db) -class User(Timestamp, db.Model): - """Quick and dirty user model.""" +# ================================== MODELS ================================== - #: If __jsonapi_type__ is not provided, it will use the class name instead. + +class User(db.Model, Timestamp): __tablename__ = 'users' - id = Column(UUIDType, default=uuid4, primary_key=True) - username = Column(Unicode(30), unique=True, nullable=False) + id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False) email = Column(EmailType, nullable=False) - password = Column( - PasswordType(schemes=['bcrypt']), - nullable=False) + display_name = Column(Unicode(100), nullable=False) + password = Column(PasswordType(schemes=['bcrypt']), nullable=False) is_admin = Column(Boolean, default=False) - - @hybrid_property - def total_comments(self): - """ - Total number of comments. - - Provides an example of a computed property. - """ - return self.comments.count() + last_ip_address = Column(IPAddressType) + website = Column(URLType) @validates('email') def validate_email(self, key, email): @@ -56,119 +53,95 @@ def validate_email(self, key, email): assert '@' in email, 'Not an email' return email - @validates('username') - def validate_username(self, key, username): - """ - Check the length of the username. - - Here's hoping nobody submits something in unicode that is 31 characters - long!! - """ - assert len(username) >= 4 and len( - username) <= 30, 'Must be 4 to 30 characters long.' - return username - - @validates('password') - def validate_password(self, key, password): - """Validate a password's length.""" - assert len(password) >= 5, 'Password must be 5 characters or longer.' - return password - - @jsonapi_access(Permissions.VIEW, 'password') - def view_password(self): - """ Never let the password be seen. """ - return False - - @jsonapi_access(Permissions.EDIT) - def prevent_edit(self): - """ Prevent editing for no reason. """ - if request.view_args['api_type'] == 'blog-posts': - return True - return False - - @jsonapi_access(Permissions.DELETE) - def allow_delete(self): - """ Just like a popular social media site, we won't delete users. """ - return False - - -class BlogPost(Timestamp, db.Model): - """Post model, as if this is a blog.""" +class Forum(db.Model, Timestamp): + __tablename__ = 'forums' + + id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False) + name = Column(Unicode(255), nullable=False) + can_public_read = Column(Boolean, default=True) + can_public_write = Column(Boolean, default=True) + + +class Thread(db.Model, Timestamp): + __tablename__ = 'threads' + + id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False) + forum_id = Column(UUIDType, ForeignKey('forums.id'), nullable=False) + started_by_id = Column(UUIDType, ForeignKey('users.id'), nullable=False) + title = Column(Unicode(255), nullable=False) + + +class Post(db.Model, Timestamp): __tablename__ = 'posts' - id = Column(UUIDType, default=uuid4, primary_key=True) - title = Column(Unicode(100), nullable=False) - slug = Column(Unicode(100)) + id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False) + user_id = Column(UUIDType, ForeignKey('posts.id'), nullable=False) content = Column(UnicodeText, nullable=False) - is_published = Column(Boolean, default=False) - author_id = Column(UUIDType, ForeignKey('users.id')) + is_removed = Column(Boolean, default=False) - author = relationship('User', - lazy='joined', - backref=backref('posts', lazy='dynamic')) - @validates('title') - def validate_title(self, key, title): - """Keep titles from getting too long.""" - assert len(title) >= 5 or len( - title) <= 100, 'Must be 5 to 100 characters long.' - return title +class ReportTypes(enum.Enum): + USER = 0 + POST = 1 - @jsonapi_access(Permissions.VIEW) - def allow_view(self): - """ Hide unpublished. """ - return self.is_published - @jsonapi_access(INTERACTIVE_PERMISSIONS, 'logs') - def prevent_altering_of_logs(self): - return False +class Report(db.Model, Timestamp): + __tablename__ = 'reports' + id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False) + report_type = Column(Enum(ReportTypes), nullable=False) + reporter_id = Column(UUIDType, ForeignKey('users.id'), nullable=False) + complaint = Column(UnicodeText, nullable=False) -class BlogComment(Timestamp, db.Model): - """Comment for each Post.""" + __mapper_args__ = { + 'polymorphic_identity': 'employee', + 'polymorphic_on': report_type, + 'with_polymorphic': '*' + } - __tablename__ = 'comments' - id = Column(UUIDType, default=uuid4, primary_key=True) - post_id = Column(UUIDType, ForeignKey('posts.id')) - author_id = Column(UUIDType, ForeignKey('users.id'), nullable=False) - content = Column(UnicodeText, nullable=False) +class UserReport(db.Model): + __tablename__ = 'user_reports' - post = relationship('BlogPost', - lazy='joined', - backref=backref('comments', lazy='dynamic')) - author = relationship('User', - lazy='joined', - backref=backref('comments', - lazy='dynamic')) + id = Column( + UUIDType, + ForeignKey('reports.id'), + default=uuid4, + primary_key=True, + nullable=False) + user_id = Column(UUIDType, ForeignKey('users.id'), nullable=False) + + __mapper_args__ = {'polymorphic_identity': ReportTypes.USER} -class Log(Timestamp, db.Model): - __tablename__ = 'logs' - id = Column(UUIDType, default=uuid4, primary_key=True) - post_id = Column(UUIDType, ForeignKey('posts.id')) - user_id = Column(UUIDType, ForeignKey('users.id')) +class PostReport(db.Model): + __tablename__ = 'post_reports' + + id = Column( + UUIDType, + ForeignKey('reports.id'), + default=uuid4, + primary_key=True, + nullable=False) + post_id = Column(UUIDType, ForeignKey('posts.id'), nullable=False) - post = relationship('BlogPost', - lazy='joined', - backref=backref('logs', lazy='dynamic')) - user = relationship('User', - lazy='joined', - backref=backref('logs', lazy='dynamic')) + __mapper_args__ = {'polymorphic_identity': ReportTypes.POST} - @jsonapi_access(INTERACTIVE_PERMISSIONS) - def block_interactive(cls): - return False +# ============================== EVENT HANDLERS ============================== -api = FlaskJSONAPI(app, db) +@app.before_request +def handle_auth(): + pass +# ============================== API OVERRIDES ============================== -@api.wrap_handler(['blog-posts'], [Method.GET], [Endpoint.COLLECTION]) -def sample_override(next, *args, **kwargs): - return next(*args, **kwargs) +#@api.wrap_handler(['blog-posts'], [Method.GET], [Endpoint.COLLECTION]) +#def sample_override(next, *args, **kwargs): +# return next(*args, **kwargs) +# ================================ APP RUNNER ================================ if __name__ == '__main__': app.run() diff --git a/tests/conftest.py b/tests/conftest.py index 9e93e06..67c026c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,18 +7,24 @@ import json +import jsonschema import pytest +from addict import Dict +from faker import Faker from flask import Response from flask.testing import FlaskClient from sqlalchemy.orm import sessionmaker + from app import db as db_ -from app import app, User, BlogPost, BlogComment, Log -from faker import Faker +from app import app Session = sessionmaker() fake = Faker() +with open('tests/jsonapi_schema.json', 'r') as f: + api_schema = json.load(f) + @pytest.yield_fixture(scope='session') def flask_app(): @@ -57,7 +63,9 @@ def validate(self, status_code, error=None): assert self.status_code == status_code assert self.headers['Content-Type'] == 'application/vnd.api+json' if status_code != 204: - self.json_data = json.loads(self.data.decode()) + json_data = json.loads(self.data.decode()) + jsonschema.validate(json_data, api_schema) + self.json_data = Dict(json_data) if error: assert self.status_code == error.status_code assert self.json_data['errors'][0]['code'] == error.code @@ -69,68 +77,7 @@ def validate(self, status_code, error=None): @pytest.fixture def client(flask_app): """Set up the testing client.""" - with FlaskClient(flask_app, - use_cookies=True, - response_wrapper=TestingResponse) as c: + with FlaskClient( + flask_app, use_cookies=True, + response_wrapper=TestingResponse) as c: return c - - -@pytest.fixture -def user(session): - new_user = User(email=fake.email(), - password=fake.sentence(), - username=fake.user_name()) - session.add(new_user) - session.commit() - return new_user - - -@pytest.fixture -def post(user, session): - new_post = BlogPost(author=user, - title=fake.sentence(), - content=fake.paragraph(), - is_published=True) - session.add(new_post) - session.commit() - return new_post - - -@pytest.fixture -def unpublished_post(user, session): - new_post = BlogPost(author=user, - title=fake.sentence(), - content=fake.paragraph(), - is_published=False) - session.add(new_post) - session.commit() - return new_post - - -@pytest.fixture -def bunch_of_posts(user, session): - for x in range(30): - new_post = BlogPost(author=user, - title=fake.sentence(), - content=fake.paragraph(), - is_published=fake.boolean()) - session.add(new_post) - new_post.comments.append(BlogComment(author=user, - content=fake.paragraph())) - session.commit() - - -@pytest.fixture -def comment(user, post, session): - new_comment = BlogComment(author=user, post=post, content=fake.paragraph()) - session.add(new_comment) - session.commit() - return new_comment - - -@pytest.fixture -def log(user, post, session): - new_log = Log(user=user, post=post) - session.add(new_log) - session.commit() - return new_log diff --git a/tests/jsonapi_schema.json b/tests/jsonapi_schema.json new file mode 100644 index 0000000..32132bc --- /dev/null +++ b/tests/jsonapi_schema.json @@ -0,0 +1,383 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "JSON API Schema", + "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", + "oneOf": [ + { + "$ref": "#/definitions/success" + }, + { + "$ref": "#/definitions/failure" + }, + { + "$ref": "#/definitions/info" + } + ], + "definitions": { + "success": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "$ref": "#/definitions/data" + }, + "included": { + "description": "To reduce the number of HTTP requests, servers **MAY** allow responses that include related resources along with the requested primary resources. Such responses are called \"compound documents\".", + "type": "array", + "items": { + "$ref": "#/definitions/resource" + }, + "uniqueItems": true + }, + "meta": { + "$ref": "#/definitions/meta" + }, + "links": { + "description": "Link members related to the primary data.", + "allOf": [ + { + "$ref": "#/definitions/links" + }, + { + "$ref": "#/definitions/pagination" + } + ] + }, + "jsonapi": { + "$ref": "#/definitions/jsonapi" + } + }, + "additionalProperties": false + }, + "failure": { + "type": "object", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/error" + }, + "uniqueItems": true + }, + "meta": { + "$ref": "#/definitions/meta" + }, + "jsonapi": { + "$ref": "#/definitions/jsonapi" + } + }, + "additionalProperties": false + }, + "info": { + "type": "object", + "required": [ + "meta" + ], + "properties": { + "meta": { + "$ref": "#/definitions/meta" + }, + "links": { + "$ref": "#/definitions/links" + }, + "jsonapi": { + "$ref": "#/definitions/jsonapi" + } + }, + "additionalProperties": false + }, + "meta": { + "description": "Non-standard meta-information that can not be represented as an attribute or relationship.", + "type": "object", + "additionalProperties": true + }, + "data": { + "description": "The document's \"primary data\" is a representation of the resource or collection of resources targeted by a request.", + "oneOf": [ + { + "$ref": "#/definitions/resource" + }, + { + "description": "An array of resource objects, an array of resource identifier objects, or an empty array ([]), for requests that target resource collections.", + "type": "array", + "items": { + "$ref": "#/definitions/resource" + }, + "uniqueItems": true + }, + { + "description": "null if the request is one that might correspond to a single resource, but doesn't currently.", + "type": "null" + } + ] + }, + "resource": { + "description": "\"Resource objects\" appear in a JSON API document to represent resources.", + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "attributes": { + "$ref": "#/definitions/attributes" + }, + "relationships": { + "$ref": "#/definitions/relationships" + }, + "links": { + "$ref": "#/definitions/links" + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + }, + "links": { + "description": "A resource object **MAY** contain references to other resource objects (\"relationships\"). Relationships may be to-one or to-many. Relationships can be specified by including a member in a resource's links object.", + "type": "object", + "properties": { + "self": { + "description": "A `self` member, whose value is a URL for the relationship itself (a \"relationship URL\"). This URL allows the client to directly manipulate the relationship. For example, it would allow a client to remove an `author` from an `article` without deleting the people resource itself.", + "type": "string", + "format": "uri" + }, + "related": { + "$ref": "#/definitions/link" + } + }, + "additionalProperties": true + }, + "link": { + "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object.", + "oneOf": [ + { + "description": "A string containing the link's URL.", + "type": "string", + "format": "uri" + }, + { + "type": "object", + "required": [ + "href" + ], + "properties": { + "href": { + "description": "A string containing the link's URL.", + "type": "string", + "format": "uri" + }, + "meta": { + "$ref": "#/definitions/meta" + } + } + } + ] + }, + "attributes": { + "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.", + "type": "object", + "patternProperties": { + "^(?!relationships$|links$)\\w[-\\w_]*$": { + "description": "Attributes may contain any valid JSON value." + } + }, + "additionalProperties": false + }, + "relationships": { + "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.", + "type": "object", + "patternProperties": { + "^\\w[-\\w_]*$": { + "properties": { + "links": { + "$ref": "#/definitions/links" + }, + "data": { + "description": "Member, whose value represents \"resource linkage\".", + "oneOf": [ + { + "$ref": "#/definitions/relationshipToOne" + }, + { + "$ref": "#/definitions/relationshipToMany" + } + ] + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "relationshipToOne": { + "description": "References to other resource objects in a to-one (\"relationship\"). Relationships can be specified by including a member in a resource's links object.", + "anyOf": [ + { + "$ref": "#/definitions/empty" + }, + { + "$ref": "#/definitions/linkage" + } + ] + }, + "relationshipToMany": { + "description": "An array of objects each containing \"type\" and \"id\" members for to-many relationships.", + "type": "array", + "items": { + "$ref": "#/definitions/linkage" + }, + "uniqueItems": true + }, + "empty": { + "description": "Describes an empty to-one relationship.", + "type": "null" + }, + "linkage": { + "description": "The \"type\" and \"id\" to non-empty members.", + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + }, + "pagination": { + "type": "object", + "properties": { + "first": { + "description": "The first page of data", + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "last": { + "description": "The last page of data", + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "prev": { + "description": "The previous page of data", + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "next": { + "description": "The next page of data", + "oneOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + } + } + }, + "jsonapi": { + "description": "An object describing the server's implementation", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + }, + "error": { + "type": "object", + "properties": { + "id": { + "description": "A unique identifier for this particular occurrence of the problem.", + "type": "string" + }, + "links": { + "$ref": "#/definitions/links" + }, + "status": { + "description": "The HTTP status code applicable to this problem, expressed as a string value.", + "type": "string" + }, + "code": { + "description": "An application-specific error code, expressed as a string value.", + "type": "string" + }, + "title": { + "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.", + "type": "string" + }, + "detail": { + "description": "A human-readable explanation specific to this occurrence of the problem.", + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "pointer": { + "description": "A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].", + "type": "string" + }, + "parameter": { + "description": "A string indicating which query parameter caused the error.", + "type": "string" + } + } + }, + "meta": { + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/tests/test_collection_get.py b/tests/test_collection_get.py index 9cc3903..6a1bbed 100644 --- a/tests/test_collection_get.py +++ b/tests/test_collection_get.py @@ -1,17 +1,43 @@ -from sqlalchemy_jsonapi.errors import BadRequestError, NotSortableError +from schema import Schema +from jsonschema import validate +def test_200_with_no_querystring(): + pass + + +def describe_bad_query_params(): + def test_bad_query_param(): + pass + +def describe_resource_inclusions(): + pass + +def describe_sparse_fieldsets(): + pass + +def describe_sorting(): + pass + +def describe_pagination(): + pass + +def describe_filtering(): + pass + + +"""from sqlalchemy_jsonapi.errors import BadRequestError, NotSortableError + # TODO: Vanilla + def test_200_with_no_querystring(bunch_of_posts, client): response = client.get('/api/blog-posts').validate(200) assert response.json_data['data'][0]['type'] == 'blog-posts' assert response.json_data['data'][0]['id'] - # TODO: Bad Query Param - # TODO: Resource Inclusions @@ -29,6 +55,7 @@ def test_200_with_including_model_and_including_inbetween(bunch_of_posts, for data in response.json_data['included']: assert data['type'] in ['blog-posts', 'users'] + def test_200_with_multiple_includes(bunch_of_posts, client): response = client.get('/api/blog-posts/?include=comments,author').validate( 200) @@ -36,7 +63,6 @@ def test_200_with_multiple_includes(bunch_of_posts, client): for data in response.json_data['included']: assert data['type'] in ['blog-comments', 'users'] - # TODO: Sparse Fieldsets @@ -47,6 +73,7 @@ def test_200_with_single_field(bunch_of_posts, client): assert {'title'} == set(item['attributes'].keys()) assert len(item['relationships']) == 0 + def test_200_with_bad_field(bunch_of_posts, client): response = client.get( '/api/blog-posts/?fields[blog-posts]=titles').validate(200) @@ -77,7 +104,6 @@ def test_200_with_single_field_across_a_relationship(bunch_of_posts, client): assert len(item['attributes']) == 0 assert {'author'} == set(item['relationships'].keys()) - # TODO: Sorting @@ -107,7 +133,6 @@ def test_409_when_given_a_missing_field_for_sorting(bunch_of_posts, client): client.get('/api/blog-posts/?sort=never_gonna_give_you_up').validate( 409, NotSortableError) - # TODO: Pagination @@ -132,5 +157,5 @@ def test_400_when_provided_crap_data_for_pagination(bunch_of_posts, client): client.get('/api/blog-posts/?page[offset]=5&page[limit]=crap').validate( 400, BadRequestError) - # TODO: Filtering +""" diff --git a/tests/test_collection_post.py b/tests/test_collection_post.py index d01dab6..df30f27 100644 --- a/tests/test_collection_post.py +++ b/tests/test_collection_post.py @@ -1,4 +1,4 @@ -import json +"""import json from sqlalchemy_jsonapi.errors import ( InvalidTypeForEndpointError, MissingTypeError, PermissionDeniedError, @@ -10,6 +10,7 @@ # TODO: Bad query param + def test_200_resource_creation(client): payload = { 'data': { @@ -56,8 +57,8 @@ def test_200_resource_creation_with_relationships(user, client): assert response.json_data['data']['type'] == 'blog-posts' assert len(response.json_data['included']) == 0 post_id = response.json_data['data']['id'] - response = client.get('/api/blog-posts/{}/'.format( - post_id)).validate(200) + response = client.get('/api/blog-posts/{}/'.format(post_id)).validate(200) + def test_200_resource_creation_with_relationships_and_include(user, client): payload = { @@ -92,6 +93,7 @@ def test_200_resource_creation_with_relationships_and_include(user, client): response = client.get('/api/blog-posts/{}/?include=author'.format( post_id)).validate(200) + def test_200_resource_creation_with_sparse_fieldset(client): payload = { 'data': { @@ -108,17 +110,19 @@ def test_200_resource_creation_with_sparse_fieldset(client): data=json.dumps(payload), content_type='application/vnd.api+json').validate(201) assert response.json_data['data']['type'] == 'users' - assert set(response.json_data['data']['attributes'].keys()) == set(['username']) + assert set(response.json_data['data']['attributes'].keys()) == set( + ['username']) user_id = response.json_data['data']['id'] response = client.get('/api/users/{}/'.format(user_id)).validate(200) def test_403_when_access_is_denied(client): payload = {'data': {'type': 'logs'}} - client.post('/api/logs/', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 403, PermissionDeniedError) + client.post( + '/api/logs/', + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 403, PermissionDeniedError) def test_409_when_id_already_exists(user, client): @@ -133,23 +137,24 @@ def test_409_when_id_already_exists(user, client): } } } - client.post('/api/users/', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.post( + '/api/users/', + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_409_when_type_doesnt_match_endpoint(client): payload = {'data': {'type': 'blog-posts'}} - client.post('/api/users/', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, InvalidTypeForEndpointError) + client.post( + '/api/users/', + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 409, InvalidTypeForEndpointError) def test_409_when_missing_content_type(client): - client.post('/api/users/', - data='{}').validate(409, MissingContentTypeError) + client.post( + '/api/users/', data='{}').validate(409, MissingContentTypeError) def test_409_when_missing_type(client): @@ -162,10 +167,11 @@ def test_409_when_missing_type(client): } } } - client.post('/api/users/', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, MissingTypeError) + client.post( + '/api/users/', + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, + MissingTypeError) def test_409_for_invalid_value(client): @@ -179,10 +185,10 @@ def test_409_for_invalid_value(client): } } } - client.post('/api/users/', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.post( + '/api/users/', + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_409_for_wrong_field_name(client): @@ -197,7 +203,8 @@ def test_409_for_wrong_field_name(client): } } } - client.post('/api/users/', - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.post( + '/api/users/', + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) +""" diff --git a/tests/test_related_get.py b/tests/test_related_get.py index 7345174..cf16a6c 100644 --- a/tests/test_related_get.py +++ b/tests/test_related_get.py @@ -1,4 +1,4 @@ -from uuid import uuid4 +"""from uuid import uuid4 from sqlalchemy_jsonapi.errors import (RelationshipNotFoundError, ResourceNotFoundError) @@ -33,3 +33,4 @@ def test_404_when_relationship_not_found(post, client): def test_404_when_resource_not_found(client): client.get('/api/blog-posts/{}/comments/'.format(uuid4())).validate( 404, ResourceNotFoundError) +""" diff --git a/tests/test_relationship_delete.py b/tests/test_relationship_delete.py index 04b07e1..f3d0d13 100644 --- a/tests/test_relationship_delete.py +++ b/tests/test_relationship_delete.py @@ -1,4 +1,4 @@ -import json +"""import json from uuid import uuid4 from sqlalchemy_jsonapi.errors import ( @@ -28,33 +28,38 @@ def test_200_on_deletion_from_to_many(comment, client): def test_404_on_resource_not_found(client): - client.delete('/api/blog-posts/{}/relationships/comments/'.format(uuid4()), - data='{}', - content_type='application/vnd.api+json').validate( - 404, ResourceNotFoundError) + client.delete( + '/api/blog-posts/{}/relationships/comments/'.format(uuid4()), + data='{}', + content_type='application/vnd.api+json').validate( + 404, ResourceNotFoundError) def test_404_on_relationship_not_found(post, client): - client.delete('/api/blog-posts/{}/relationships/comment/'.format(post.id), - data='{}', - content_type='application/vnd.api+json').validate( - 404, RelationshipNotFoundError) + client.delete( + '/api/blog-posts/{}/relationships/comment/'.format(post.id), + data='{}', + content_type='application/vnd.api+json').validate( + 404, RelationshipNotFoundError) def test_403_on_permission_denied(user, client): - client.delete('/api/users/{}/relationships/logs/'.format(user.id), - data='{"data": []}', - content_type='application/vnd.api+json').validate( - 403, PermissionDeniedError) + client.delete( + '/api/users/{}/relationships/logs/'.format(user.id), + data='{"data": []}', + content_type='application/vnd.api+json').validate( + 403, PermissionDeniedError) def test_409_on_to_one_provided(post, client): - client.delete('/api/blog-posts/{}/relationships/author/'.format(post.id), - data='{"data": {}}', - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.delete( + '/api/blog-posts/{}/relationships/author/'.format(post.id), + data='{"data": {}}', + content_type='application/vnd.api+json').validate(409, ValidationError) def test_409_missing_content_type_header(post, client): - client.delete('/api/blog-posts/{}/relationships/comment/'.format(post.id), - data='{}').validate(409, MissingContentTypeError) + client.delete( + '/api/blog-posts/{}/relationships/comment/'.format(post.id), + data='{}').validate(409, MissingContentTypeError) +""" diff --git a/tests/test_relationship_get.py b/tests/test_relationship_get.py index ef48592..a996726 100644 --- a/tests/test_relationship_get.py +++ b/tests/test_relationship_get.py @@ -1,4 +1,4 @@ -from sqlalchemy_jsonapi.errors import ( +"""from sqlalchemy_jsonapi.errors import ( RelationshipNotFoundError, ResourceNotFoundError, PermissionDeniedError) from uuid import uuid4 @@ -36,3 +36,4 @@ def test_404_on_relationship_not_found(post, client): def test_403_on_permission_denied(unpublished_post, client): client.get('/api/blog-posts/{}/relationships/comment/'.format( unpublished_post.id)).validate(403, PermissionDeniedError) +""" diff --git a/tests/test_relationship_patch.py b/tests/test_relationship_patch.py index 9c889ee..07e98ef 100644 --- a/tests/test_relationship_patch.py +++ b/tests/test_relationship_patch.py @@ -1,4 +1,4 @@ -import json +"""import json from uuid import uuid4 from sqlalchemy_jsonapi.errors import (PermissionDeniedError, @@ -52,69 +52,75 @@ def test_200_on_to_many_set_to_empty(post, client): def test_409_on_to_one_set_to_empty_list(post, client): payload = {'data': []} - client.patch('/api/blog-posts/{}/relationships/author/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.patch( + '/api/blog-posts/{}/relationships/author/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_409_on_to_many_set_to_null(post, client): payload = {'data': None} - client.patch('/api/blog-posts/{}/relationships/comments/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.patch( + '/api/blog-posts/{}/relationships/comments/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_404_on_resource_not_found(client): - client.patch('/api/blog-posts/{}/relationships/comments/'.format(uuid4()), - data='{}', - content_type='application/vnd.api+json').validate( - 404, ResourceNotFoundError) + client.patch( + '/api/blog-posts/{}/relationships/comments/'.format(uuid4()), + data='{}', + content_type='application/vnd.api+json').validate( + 404, ResourceNotFoundError) def test_404_on_relationship_not_found(client, post): - client.patch('/api/blog-posts/{}/relationships/comment/'.format(post.id), - data='{}', - content_type='application/vnd.api+json').validate( - 404, RelationshipNotFoundError) + client.patch( + '/api/blog-posts/{}/relationships/comment/'.format(post.id), + data='{}', + content_type='application/vnd.api+json').validate( + 404, RelationshipNotFoundError) def test_404_on_related_item_not_found(post, client): payload = {'data': [{'type': 'blog-comments', 'id': str(uuid4())}]} - client.patch('/api/blog-posts/{}/relationships/comments/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 404, ResourceNotFoundError) + client.patch( + '/api/blog-posts/{}/relationships/comments/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 404, ResourceNotFoundError) def test_403_on_permission_denied(user, log, client): payload = {'data': {'type': 'users', 'id': str(user.id)}} - client.patch('/api/logs/{}/relationships/user/'.format(log.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 403, PermissionDeniedError) + client.patch( + '/api/logs/{}/relationships/user/'.format(log.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 403, PermissionDeniedError) def test_403_on_permission_denied_on_related(log, user, client): payload = {'data': {'type': 'logs', 'id': str(log.id)}} - client.patch('/api/users/{}/relationships/logs/'.format(user.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 403, PermissionDeniedError) + client.patch( + '/api/users/{}/relationships/logs/'.format(user.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 403, PermissionDeniedError) def test_409_on_to_one_with_incompatible_model(post, comment, client): payload = {'data': {'type': 'blog-comments', 'id': str(comment.id)}} - client.patch('/api/blog-posts/{}/relationships/author/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.patch( + '/api/blog-posts/{}/relationships/author/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_409_on_to_many_with_incompatible_model(post, client): payload = {'data': [{'type': 'blog-posts', 'id': str(post.id)}]} - client.patch('/api/blog-posts/{}/relationships/author/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.patch( + '/api/blog-posts/{}/relationships/author/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) +""" diff --git a/tests/test_relationship_post.py b/tests/test_relationship_post.py index a771932..2c559fc 100644 --- a/tests/test_relationship_post.py +++ b/tests/test_relationship_post.py @@ -1,4 +1,4 @@ -import json +"""import json from uuid import uuid4 from sqlalchemy_jsonapi.errors import ValidationError, ResourceNotFoundError, RelationshipNotFoundError @@ -23,18 +23,18 @@ def test_200_on_to_many(comment, post, client): def test_409_on_hash_instead_of_array_provided(comment, post, client): payload = {'data': {'type': 'blog-comments', 'id': str(comment.id)}} - client.post('/api/blog-posts/{}/relationships/comments/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.post( + '/api/blog-posts/{}/relationships/comments/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_409_on_incompatible_model(user, post, client): payload = {'data': [{'type': 'users', 'id': str(user.id)}]} - client.post('/api/blog-posts/{}/relationships/comments/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.post( + '/api/blog-posts/{}/relationships/comments/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_409_on_to_one_relationship(post, client): @@ -45,14 +45,17 @@ def test_409_on_to_one_relationship(post, client): def test_404_on_resource_not_found(client): - client.post('/api/blog-posts/{}/relationships/comments/'.format(uuid4()), - data='{}', - content_type='application/vnd.api+json').validate( - 404, ResourceNotFoundError) + client.post( + '/api/blog-posts/{}/relationships/comments/'.format(uuid4()), + data='{}', + content_type='application/vnd.api+json').validate( + 404, ResourceNotFoundError) def test_404_on_relationship_not_found(post, client): - client.post('/api/blog-posts/{}/relationships/comment/'.format(post.id), - data='{}', - content_type='application/vnd.api+json').validate( - 404, RelationshipNotFoundError) + client.post( + '/api/blog-posts/{}/relationships/comment/'.format(post.id), + data='{}', + content_type='application/vnd.api+json').validate( + 404, RelationshipNotFoundError) +""" diff --git a/tests/test_resource_delete.py b/tests/test_resource_delete.py index a291c5e..ffc6f1b 100644 --- a/tests/test_resource_delete.py +++ b/tests/test_resource_delete.py @@ -1,9 +1,8 @@ -from uuid import uuid4 +"""from uuid import uuid4 from sqlalchemy_jsonapi.errors import ( PermissionDeniedError, ResourceNotFoundError, ResourceTypeNotFoundError) - # TODO: Bad query param @@ -26,3 +25,4 @@ def test_403_on_permission_denied(user, client): def test_404_on_resource_not_found(client): client.delete('/api/blog-comments/{}/'.format(uuid4())).validate( 404, ResourceNotFoundError) +""" diff --git a/tests/test_resource_get.py b/tests/test_resource_get.py index 30a2e49..3d680b3 100644 --- a/tests/test_resource_get.py +++ b/tests/test_resource_get.py @@ -1,7 +1,6 @@ -from sqlalchemy_jsonapi.errors import ResourceNotFoundError, PermissionDeniedError +"""from sqlalchemy_jsonapi.errors import ResourceNotFoundError, PermissionDeniedError from uuid import uuid4 - # TODO: Sparse Fieldsets # TODO: Related Includes # TODO: Bad query param @@ -74,3 +73,4 @@ def test_200_with_single_field_across_a_relationship(post, client): assert {'title', 'content'} == set(item['attributes'].keys()) assert len(item['attributes']) == 0 assert {'author'} == set(item['relationships'].keys()) +""" diff --git a/tests/test_resource_patch.py b/tests/test_resource_patch.py index d15a73a..c10aadf 100644 --- a/tests/test_resource_patch.py +++ b/tests/test_resource_patch.py @@ -1,10 +1,9 @@ -import json +"""import json from uuid import uuid4 from sqlalchemy_jsonapi.errors import (BadRequestError, PermissionDeniedError, ResourceNotFoundError, ValidationError) - # TODO: Sparse Fieldsets # TODO: Related Includes # TODO: Bad query param @@ -39,16 +38,17 @@ def test_200(client, post, user): def test_400_missing_type(post, client): - client.patch('/api/blog-posts/{}/'.format(post.id), - data=json.dumps({}), - content_type='application/vnd.api+json').validate( - 400, BadRequestError) + client.patch( + '/api/blog-posts/{}/'.format(post.id), + data=json.dumps({}), + content_type='application/vnd.api+json').validate(400, BadRequestError) def test_404_resource_not_found(client): - client.patch('/api/blog-posts/{}/'.format(uuid4()), - content_type='application/vnd.api+json', - data='{}').validate(404, ResourceNotFoundError) + client.patch( + '/api/blog-posts/{}/'.format(uuid4()), + content_type='application/vnd.api+json', + data='{}').validate(404, ResourceNotFoundError) def test_404_related_resource_not_found(client, post): @@ -66,10 +66,11 @@ def test_404_related_resource_not_found(client, post): } } } - client.patch('/api/blog-posts/{}/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 404, ResourceNotFoundError) + client.patch( + '/api/blog-posts/{}/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate( + 404, ResourceNotFoundError) def test_400_field_not_found(client, post, user): @@ -87,10 +88,10 @@ def test_400_field_not_found(client, post, user): } } } - client.patch('/api/blog-posts/{}/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 400, BadRequestError) + client.patch( + '/api/blog-posts/{}/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(400, BadRequestError) def test_409_type_mismatch_to_one(client, post, user): @@ -108,10 +109,10 @@ def test_409_type_mismatch_to_one(client, post, user): } } } - client.patch('/api/blog-posts/{}/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.patch( + '/api/blog-posts/{}/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_400_type_mismatch_to_many(client, post, user): @@ -129,10 +130,10 @@ def test_400_type_mismatch_to_many(client, post, user): } } } - client.patch('/api/blog-posts/{}/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 400, BadRequestError) + client.patch( + '/api/blog-posts/{}/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(400, BadRequestError) def test_409_validation_failed(client, post, user): @@ -153,10 +154,10 @@ def test_409_validation_failed(client, post, user): } } } - client.patch('/api/blog-posts/{}/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 409, ValidationError) + client.patch( + '/api/blog-posts/{}/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(409, ValidationError) def test_400_type_does_not_match_endpoint(client, post, user): @@ -177,14 +178,16 @@ def test_400_type_does_not_match_endpoint(client, post, user): } } } - client.patch('/api/blog-posts/{}/'.format(post.id), - data=json.dumps(payload), - content_type='application/vnd.api+json').validate( - 400, BadRequestError) + client.patch( + '/api/blog-posts/{}/'.format(post.id), + data=json.dumps(payload), + content_type='application/vnd.api+json').validate(400, BadRequestError) def test_403_permission_denied(user, client): - client.patch('/api/users/{}/'.format(user.id), - data='{}', - content_type='application/vnd.api+json').validate( - 403, PermissionDeniedError) + client.patch( + '/api/users/{}/'.format(user.id), + data='{}', + content_type='application/vnd.api+json').validate( + 403, PermissionDeniedError) +""" diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 38fe038..18a4506 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1,4 +1,4 @@ -from app import api +"""from app import api import uuid @@ -10,3 +10,4 @@ def test_include_different_types_same_id(session, comment): r = api.serializer.get_resource(session, {'include': 'post,author'}, 'blog-comments', comment.id) assert len(r.data['included']) == 2 +"""