# -*- coding: utf-8 -*-

from six.moves.urllib.parse import urlencode, parse_qs
import pytest

from sqlalchemy import create_engine, Column, Integer, DateTime, String, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
from flask import Blueprint, make_response, json
from marshmallow_jsonapi.flask import Schema, Relationship
from marshmallow import Schema as MarshmallowSchema
from marshmallow_jsonapi import fields
from marshmallow import ValidationError

from flask_rest_jsonapi import Api, ResourceList, ResourceDetail, ResourceRelationship, JsonApiException
from flask_rest_jsonapi.pagination import add_pagination_links
from flask_rest_jsonapi.exceptions import RelationNotFound, InvalidSort, InvalidFilters, InvalidInclude, BadRequest
from flask_rest_jsonapi.querystring import QueryStringManager as QSManager
from flask_rest_jsonapi.data_layers.alchemy import SqlalchemyDataLayer
from flask_rest_jsonapi.data_layers.base import BaseDataLayer
from flask_rest_jsonapi.data_layers.filtering.alchemy import Node
import flask_rest_jsonapi.decorators
import flask_rest_jsonapi.resource
import flask_rest_jsonapi.schema


@pytest.fixture(scope="module")
def base():
    yield declarative_base()

@pytest.fixture(scope="module")
def person_tag_model(base):
    class Person_Tag(base):

        __tablename__ = 'person_tag'

        id = Column(Integer, ForeignKey('person.person_id'), primary_key=True, index=True)
        key = Column(String, primary_key=True)
        value = Column(String, primary_key=True)
    yield Person_Tag

@pytest.fixture(scope="module")
def person_single_tag_model(base):
    class Person_Single_Tag(base):

        __tablename__ = 'person_single_tag'

        id = Column(Integer, ForeignKey('person.person_id'), primary_key=True, index=True)
        key = Column(String)
        value = Column(String)
    yield Person_Single_Tag


@pytest.fixture(scope="module")
def string_json_attribute_person_model(base):
    """
    This approach to faking JSON support for testing with sqlite is borrowed from:
    https://avacariu.me/articles/2016/compiling-json-as-text-for-sqlite-with-sqlalchemy
    """
    import sqlalchemy.types as types
    import json

    class StringyJSON(types.TypeDecorator):
        """Stores and retrieves JSON as TEXT."""

        impl = types.TEXT

        def process_bind_param(self, value, dialect):
            if value is not None:
                value = json.dumps(value)
            return value

        def process_result_value(self, value, dialect):
            if value is not None:
                value = json.loads(value)
            return value

    # TypeEngine.with_variant says "use StringyJSON instead when
    # connecting to 'sqlite'"
    MagicJSON = types.JSON().with_variant(StringyJSON, 'sqlite')

    class StringJsonAttributePerson(base):

        __tablename__ = 'string_json_attribute_person'

        person_id = Column(Integer, primary_key=True)
        name = Column(String, nullable=False)
        birth_date = Column(DateTime)
        # This model uses a String type for "json_tags" to avoid dependency on a nonstandard SQL type in testing, \
        # while still demonstrating support
        address = Column(MagicJSON)
    yield StringJsonAttributePerson

@pytest.fixture(scope="module")
def person_model(base):
    class Person(base):

        __tablename__ = 'person'

        person_id = Column(Integer, primary_key=True)
        name = Column(String, nullable=False)
        birth_date = Column(DateTime)
        computers = relationship("Computer", backref="person")
        tags = relationship("Person_Tag", cascade="save-update, merge, delete, delete-orphan")
        single_tag = relationship("Person_Single_Tag", uselist=False, cascade="save-update, merge, delete, delete-orphan")

        computers_owned = relationship("Computer")
    yield Person


@pytest.fixture(scope="module")
def computer_model(base):
    class Computer(base):

        __tablename__ = 'computer'

        id = Column(Integer, primary_key=True)
        serial = Column(String, nullable=False)
        person_id = Column(Integer, ForeignKey('person.person_id'))
    yield Computer


@pytest.fixture(scope="module")
def engine(person_tag_model, person_single_tag_model, person_model, computer_model, string_json_attribute_person_model):
    engine = create_engine("sqlite:///:memory:")
    person_tag_model.metadata.create_all(engine)
    person_single_tag_model.metadata.create_all(engine)
    person_model.metadata.create_all(engine)
    computer_model.metadata.create_all(engine)
    string_json_attribute_person_model.metadata.create_all(engine)
    return engine


@pytest.fixture(scope="module")
def session(engine):
    Session = sessionmaker(bind=engine)
    return Session()


@pytest.fixture()
def person(session, person_model):
    person_ = person_model(name='test')
    session_ = session
    session_.add(person_)
    session_.commit()
    yield person_
    session_.delete(person_)
    session_.commit()


@pytest.fixture()
def person_2(session, person_model):
    person_ = person_model(name='test2')
    session_ = session
    session_.add(person_)
    session_.commit()
    yield person_
    session_.delete(person_)
    session_.commit()


@pytest.fixture()
def computer(session, computer_model):
    computer_ = computer_model(serial='1')
    session_ = session
    session_.add(computer_)
    session_.commit()
    yield computer_
    session_.delete(computer_)
    session_.commit()


@pytest.fixture(scope="module")
def dummy_decorator():
    def deco(f):
        def wrapper_f(*args, **kwargs):
            return f(*args, **kwargs)
        return wrapper_f
    yield deco

@pytest.fixture(scope="module")
def person_tag_schema():
    class PersonTagSchema(MarshmallowSchema):
        class Meta:
            type_ = 'person_tag'

        id = fields.Str(dump_only=True, load_only=True)
        key = fields.Str()
        value = fields.Str()
    yield PersonTagSchema

@pytest.fixture(scope="module")
def person_single_tag_schema():
    class PersonSingleTagSchema(MarshmallowSchema):
        class Meta:
            type_ = 'person_single_tag'

        id = fields.Str(dump_only=True, load_only=True)
        key = fields.Str()
        value = fields.Str()
    yield PersonSingleTagSchema


@pytest.fixture(scope="module")
def address_schema():
    class AddressSchema(MarshmallowSchema):
        street = fields.String(required=True)
        city = fields.String(required=True)
        state = fields.String(missing='NC')
        zip = fields.String(required=True)

    yield AddressSchema

@pytest.fixture(scope="module")
def string_json_attribute_person_schema(address_schema):
    class StringJsonAttributePersonSchema(Schema):
        class Meta:
            type_ = 'string_json_attribute_person'
            self_view = 'api.string_json_attribute_person_detail'
            self_view_kwargs = {'person_id': '<id>'}
        id = fields.Integer(as_string=True, dump_only=True, attribute='person_id')
        name = fields.Str(required=True)
        birth_date = fields.DateTime()
        address = fields.Nested(address_schema, many=False)

    yield StringJsonAttributePersonSchema


@pytest.fixture(scope="module")
def person_schema(person_tag_schema, person_single_tag_schema):
    class PersonSchema(Schema):
        class Meta:
            type_ = 'person'
            self_view = 'api.person_detail'
            self_view_kwargs = {'person_id': '<id>'}
        id = fields.Integer(as_string=True, dump_only=True, attribute='person_id')
        name = fields.Str(required=True)
        birth_date = fields.DateTime()
        computers = Relationship(related_view='api.computer_list',
                                 related_view_kwargs={'person_id': '<person_id>'},
                                 schema='ComputerSchema',
                                 type_='computer',
                                 many=True)

        tags = fields.Nested(person_tag_schema, many=True)
        single_tag = fields.Nested(person_single_tag_schema)

        computers_owned = computers

    yield PersonSchema


@pytest.fixture(scope="module")
def computer_schema():
    class ComputerSchema(Schema):
        class Meta:
            type_ = 'computer'
            self_view = 'api.computer_detail'
            self_view_kwargs = {'id': '<id>'}
        id = fields.Integer(as_string=True, dump_only=True)
        serial = fields.Str(required=True)
        owner = Relationship(attribute='person',
                             default=None,
                             missing=None,
                             related_view='api.person_detail',
                             related_view_kwargs={'person_id': '<person.person_id>'},
                             schema='PersonSchema',
                             id_field='person_id',
                             type_='person')
    yield ComputerSchema


@pytest.fixture(scope="module")
def before_create_object():
    def before_create_object_(self, data, view_kwargs):
        pass
    yield before_create_object_


@pytest.fixture(scope="module")
def before_update_object():
    def before_update_object_(self, obj, data, view_kwargs):
        pass
    yield before_update_object_


@pytest.fixture(scope="module")
def before_delete_object():
    def before_delete_object_(self, obj, view_kwargs):
        pass
    yield before_delete_object_


@pytest.fixture(scope="module")
def person_list(session, person_model, dummy_decorator, person_schema, before_create_object):
    class PersonList(ResourceList):
        schema = person_schema
        data_layer = {'model': person_model,
                      'session': session,
                      'mzthods': {'before_create_object': before_create_object}}
        get_decorators = [dummy_decorator]
        post_decorators = [dummy_decorator]
        get_schema_kwargs = dict()
        post_schema_kwargs = dict()
    yield PersonList


@pytest.fixture(scope="module")
def person_detail(session, person_model, dummy_decorator, person_schema, before_update_object, before_delete_object):
    class PersonDetail(ResourceDetail):
        schema = person_schema
        data_layer = {'model': person_model,
                      'session': session,
                      'url_field': 'person_id',
                      'methods': {'before_update_object': before_update_object,
                                  'before_delete_object': before_delete_object}}
        get_decorators = [dummy_decorator]
        patch_decorators = [dummy_decorator]
        delete_decorators = [dummy_decorator]
        get_schema_kwargs = dict()
        patch_schema_kwargs = dict()
        delete_schema_kwargs = dict()
    yield PersonDetail


@pytest.fixture(scope="module")
def person_computers(session, person_model, dummy_decorator, person_schema):
    class PersonComputersRelationship(ResourceRelationship):
        schema = person_schema
        data_layer = {'session': session,
                      'model': person_model,
                      'url_field': 'person_id'}
        get_decorators = [dummy_decorator]
        post_decorators = [dummy_decorator]
        patch_decorators = [dummy_decorator]
        delete_decorators = [dummy_decorator]
    yield PersonComputersRelationship


@pytest.fixture(scope="module")
def person_list_raise_jsonapiexception():
    class PersonList(ResourceList):
        def get(self):
            raise JsonApiException('', '')
    yield PersonList


@pytest.fixture(scope="module")
def person_list_raise_exception():
    class PersonList(ResourceList):
        def get(self):
            raise Exception()
    yield PersonList


@pytest.fixture(scope="module")
def person_list_response():
    class PersonList(ResourceList):
        def get(self):
            return make_response('')
    yield PersonList


@pytest.fixture(scope="module")
def person_list_without_schema(session, person_model):
    class PersonList(ResourceList):
        data_layer = {'model': person_model,
                      'session': session}

        def get(self):
            return make_response('')
    yield PersonList


@pytest.fixture(scope="module")
def query():
    def query_(self, view_kwargs):
        if view_kwargs.get('person_id') is not None:
            return self.session.query(computer_model).join(person_model).filter_by(person_id=view_kwargs['person_id'])
        return self.session.query(computer_model)
    yield query_


@pytest.fixture(scope="module")
def computer_list(session, computer_model, computer_schema, query):
    class ComputerList(ResourceList):
        schema = computer_schema
        data_layer = {'model': computer_model,
                      'session': session,
                      'methods': {'query': query}}
    yield ComputerList


@pytest.fixture(scope="module")
def computer_detail(session, computer_model, dummy_decorator, computer_schema):
    class ComputerDetail(ResourceDetail):
        schema = computer_schema
        data_layer = {'model': computer_model,
                      'session': session}
        methods = ['GET', 'PATCH']
    yield ComputerDetail


@pytest.fixture(scope="module")
def computer_owner(session, computer_model, dummy_decorator, computer_schema):
    class ComputerOwnerRelationship(ResourceRelationship):
        schema = computer_schema
        data_layer = {'session': session,
                      'model': computer_model}
    yield ComputerOwnerRelationship


@pytest.fixture(scope="module")
def string_json_attribute_person_detail(session, string_json_attribute_person_model, string_json_attribute_person_schema):
    class StringJsonAttributePersonDetail(ResourceDetail):
        schema = string_json_attribute_person_schema
        data_layer = {'session': session,
                      'model': string_json_attribute_person_model}

    yield StringJsonAttributePersonDetail


@pytest.fixture(scope="module")
def string_json_attribute_person_list(session, string_json_attribute_person_model, string_json_attribute_person_schema):
    class StringJsonAttributePersonList(ResourceList):
        schema = string_json_attribute_person_schema
        data_layer = {'session': session,
                      'model': string_json_attribute_person_model}

    yield StringJsonAttributePersonList

@pytest.fixture(scope="module")
def api_blueprint(client):
    bp = Blueprint('api', __name__)
    yield bp


@pytest.fixture(scope="module")
def register_routes(client, app, api_blueprint, person_list, person_detail, person_computers,
                    person_list_raise_jsonapiexception, person_list_raise_exception, person_list_response,
                    person_list_without_schema, computer_list, computer_detail, computer_owner,
                    string_json_attribute_person_detail, string_json_attribute_person_list):
    api = Api(blueprint=api_blueprint)
    api.route(person_list, 'person_list', '/persons')
    api.route(person_detail, 'person_detail', '/persons/<int:person_id>')
    api.route(person_computers, 'person_computers', '/persons/<int:person_id>/relationships/computers')
    api.route(person_computers, 'person_computers_owned', '/persons/<int:person_id>/relationships/computers-owned')
    api.route(person_computers, 'person_computers_error', '/persons/<int:person_id>/relationships/computer')
    api.route(person_list_raise_jsonapiexception, 'person_list_jsonapiexception', '/persons_jsonapiexception')
    api.route(person_list_raise_exception, 'person_list_exception', '/persons_exception')
    api.route(person_list_response, 'person_list_response', '/persons_response')
    api.route(person_list_without_schema, 'person_list_without_schema', '/persons_without_schema')
    api.route(computer_list, 'computer_list', '/computers', '/persons/<int:person_id>/computers')
    api.route(computer_list, 'computer_detail', '/computers/<int:id>')
    api.route(computer_owner, 'computer_owner', '/computers/<int:id>/relationships/owner')
    api.route(string_json_attribute_person_list, 'string_json_attribute_person_list', '/string_json_attribute_persons')
    api.route(string_json_attribute_person_detail, 'string_json_attribute_person_detail',
              '/string_json_attribute_persons/<int:person_id>')
    api.init_app(app)


@pytest.fixture(scope="module")
def get_object_mock():
    class get_object(object):
        foo = type('foo', (object,), {
            'property': type('prop', (object,), {
                'mapper': type('map', (object,), {
                    'class_': 'test'
                })()
            })()
        })()

        def __init__(self, kwargs):
            pass
    return get_object


def test_add_pagination_links(app):
    with app.app_context():
        qs = {'page[number]': '2', 'page[size]': '10'}
        qsm = QSManager(qs, None)
        pagination_dict = dict()
        add_pagination_links(pagination_dict, 43, qsm, str())
        last_page_dict = parse_qs(pagination_dict['links']['last'][1:])
        assert len(last_page_dict['page[number]']) == 1
        assert last_page_dict['page[number]'][0] == '5'


def test_Node(person_model, person_schema, monkeypatch):
    from copy import deepcopy
    filt = {
        'val': '0000',
        'field': True,
        'not': dict(),
        'name': 'name',
        'op': 'eq',
        'strip': lambda: 's'
    }
    filt['not'] = deepcopy(filt)
    del filt['not']['not']
    n = Node(person_model,
             filt,
             None,
             person_schema)
    with pytest.raises(TypeError):
        # print(n.val is None and n.field is None)
        # # n.column
        n.resolve()
    with pytest.raises(AttributeError):
        n.model = None
        n.column
    with pytest.raises(InvalidFilters):
        n.model = person_model
        n.filter_['op'] = ''
        n.operator
    with pytest.raises(InvalidFilters):
        n.related_model
    with pytest.raises(InvalidFilters):
        n.related_schema


def test_check_method_requirements(monkeypatch):
    self = type('self', (object,), dict())
    request = type('request', (object,), dict(method='GET'))
    monkeypatch.setattr(flask_rest_jsonapi.decorators, 'request', request)
    with pytest.raises(Exception):
        flask_rest_jsonapi.decorators.check_method_requirements(lambda: 1)(self())


def test_json_api_exception():
    JsonApiException(None, None, title='test', status='test')


def test_query_string_manager(person_schema):
    query_string = {'page[slumber]': '3'}
    qsm = QSManager(query_string, person_schema)
    with pytest.raises(BadRequest):
        qsm.pagination
    qsm.qs['sort'] = 'computers'
    with pytest.raises(InvalidSort):
        qsm.sorting


def test_resource(app, person_model, person_schema, session, monkeypatch):
    def schema_load_mock(*args):
        raise ValidationError(dict(errors=[dict(status=None, title=None)]))

    with app.app_context():
        query_string = {'page[slumber]': '3'}
        app = type('app', (object,), dict(config=dict(DEBUG=True)))
        headers = {'Content-Type': 'application/vnd.api+json'}
        request = type('request', (object,), dict(method='POST',
                                                  headers=headers,
                                                  get_json=dict,
                                                  args=query_string))
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        rl = ResourceList()
        rd = ResourceDetail()
        rl._data_layer = dl
        rl.schema = person_schema
        rd._data_layer = dl
        rd.schema = person_schema
        monkeypatch.setattr(flask_rest_jsonapi.resource, 'request', request)
        monkeypatch.setattr(flask_rest_jsonapi.decorators, 'current_app', app)
        monkeypatch.setattr(flask_rest_jsonapi.decorators, 'request', request)
        monkeypatch.setattr(rl.schema, 'load', schema_load_mock)
        r = super(flask_rest_jsonapi.resource.Resource, ResourceList)\
            .__new__(ResourceList)
        with pytest.raises(Exception):
            r.dispatch_request()
        rl.post()
        rd.patch()


def test_compute_schema(person_schema):
    query_string = {'page[number]': '3', 'fields[person]': list()}
    qsm = QSManager(query_string, person_schema)
    with pytest.raises(InvalidInclude):
        flask_rest_jsonapi.schema.compute_schema(person_schema, dict(), qsm, ['id'])
    flask_rest_jsonapi.schema.compute_schema(person_schema, dict(only=list()), qsm, list())


def test_compute_schema_propagate_context(person_schema, computer_schema):
    query_string = {}
    qsm = QSManager(query_string, person_schema)
    schema = flask_rest_jsonapi.schema.compute_schema(person_schema, dict(), qsm, ['computers'])
    assert schema.declared_fields['computers'].__dict__['_Relationship__schema'].__dict__['context'] == dict()
    schema = flask_rest_jsonapi.schema.compute_schema(person_schema, dict(context=dict(foo='bar')), qsm, ['computers'])
    assert schema.declared_fields['computers'].__dict__['_Relationship__schema'].__dict__['context'] == dict(foo='bar')


# test good cases
def test_get_list(client, register_routes, person, person_2):
    with client:
        querystring = urlencode({'page[number]': 1,
                                 'page[size]': 1,
                                 'fields[person]': 'name,birth_date',
                                 'sort': '-name',
                                 'include': 'computers.owner',
                                 'filter': json.dumps(
                                     [
                                         {
                                             'and': [
                                                 {
                                                     'name': 'computers',
                                                     'op': 'any',
                                                     'val': {
                                                         'name': 'serial',
                                                         'op': 'eq',
                                                         'val': '0000'
                                                     }
                                                 },
                                                 {
                                                     'or': [
                                                         {
                                                             'name': 'name',
                                                             'op': 'like',
                                                             'val': '%test%'
                                                         },
                                                         {
                                                             'name': 'name',
                                                             'op': 'like',
                                                             'val': '%test2%'
                                                         }
                                                     ]
                                                 }
                                             ]
                                         }
                                     ])})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 200

def test_get_list_with_simple_filter(client, register_routes, person, person_2):
    with client:
        querystring = urlencode({'page[number]': 1,
                                 'page[size]': 1,
                                 'fields[person]': 'name,birth_date',
                                 'sort': '-name',
                                 'filter[name]': 'test'
                                 })
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 200

def test_get_list_disable_pagination(client, register_routes):
    with client:
        querystring = urlencode({'page[size]': 0})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_head_list(client, register_routes):
    with client:
        response = client.head('/persons', content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_post_list(client, register_routes, computer):
    payload = {
        'data': {
            'type': 'person',
            'attributes': {
                'name': 'test'
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.post('/persons', data=json.dumps(payload), content_type='application/vnd.api+json')
        assert response.status_code == 201

def test_post_list_nested_no_join(client, register_routes, computer):
    payload = {
        'data': {
            'type': 'string_json_attribute_person',
            'attributes': {
                'name': 'test_name',
                'address': {
                    'street': 'test_street',
                    'city': 'test_city',
                    'state': 'NC',
                    'zip': '00000'
                }
            }
        }
    }
    with client:
        response = client.post('/string_json_attribute_persons', data=json.dumps(payload), content_type='application/vnd.api+json')
        print(response.get_data())
        assert response.status_code == 201
        assert json.loads(response.get_data())['data']['attributes']['address']['street'] == 'test_street'

def test_post_list_nested(client, register_routes, computer):
    payload = {
        'data': {
            'type': 'person',
            'attributes': {
                'name': 'test',
                'tags': [
                    {'key': 'k1', 'value': 'v1'},
                    {'key': 'k2', 'value': 'v2'}
                ]
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.post('/persons', data=json.dumps(payload), content_type='application/vnd.api+json')
        assert response.status_code == 201
        assert json.loads(response.get_data())['data']['attributes']['tags'][0]['key'] == 'k1'


def test_post_list_single(client, register_routes, person):
    payload = {
        'data': {
            'type': 'computer',
            'attributes': {
                'serial': '1'
            },
            'relationships': {
                'owner': {
                    'data': {
                        'type': 'person',
                        'id': str(person.person_id)
                    }
                }
            }
        }
    }

    with client:
        response = client.post('/computers', data=json.dumps(payload), content_type='application/vnd.api+json')
        assert response.status_code == 201


def test_get_detail(client, register_routes, person):
    with client:
        response = client.get('/persons/' + str(person.person_id), content_type='application/vnd.api+json')
        assert response.status_code == 200

def test_patch_detail(client, register_routes, computer, person):
    payload = {
        'data': {
            'id': str(person.person_id),
            'type': 'person',
            'attributes': {
                'name': 'test2'
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id),
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_patch_detail_nested(client, register_routes, computer, person):
    payload = {
        'data': {
            'id': str(person.person_id),
            'type': 'person',
            'attributes': {
                'name': 'test2',
                'tags': [
                    {'key': 'new_key', 'value': 'new_value' }
                ],
                'single_tag': {'key': 'new_single_key', 'value': 'new_single_value' }
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id),
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 200
        response_dict = json.loads(response.get_data())
        assert response_dict['data']['attributes']['tags'][0]['key'] == 'new_key'
        assert response_dict['data']['attributes']['single_tag']['key'] == 'new_single_key'



def test_delete_detail(client, register_routes, person):
    with client:
        response = client.delete('/persons/' + str(person.person_id), content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_get_relationship(session, client, register_routes, computer, person):
    session_ = session
    person.computers = [computer]
    session_.commit()

    with client:
        response = client.get('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                              content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_get_relationship_empty(client, register_routes, person):
    with client:
        response = client.get('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                              content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_get_relationship_single(session, client, register_routes, computer, person):
    session_ = session
    computer.person = person
    session_.commit()

    with client:
        response = client.get('/computers/' + str(computer.id) + '/relationships/owner',
                              content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_get_relationship_single_empty(session, client, register_routes, computer):
    with client:
        response = client.get('/computers/' + str(computer.id) + '/relationships/owner',
                              content_type='application/vnd.api+json')
        response_json = json.loads(response.get_data())
        assert None is response_json['data']
        assert response.status_code == 200


def test_issue_49(session, client, register_routes, person, person_2):
    with client:
        for p in [person, person_2]:
            response = client.get('/persons/' + str(p.person_id) + '/relationships/computers?include=computers',
                                  content_type='application/vnd.api+json')
            assert response.status_code == 200
            assert (json.loads(response.get_data()))['links']['related'] == '/persons/' + str(p.person_id) + '/computers'


def test_post_relationship(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'type': 'computer',
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.post('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_post_relationship_not_list(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'person',
            'id': str(person.person_id)
        }
    }

    with client:
        response = client.post('/computers/' + str(computer.id) + '/relationships/owner',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_patch_relationship(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'type': 'computer',
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_patch_relationship_single(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'person',
            'id': str(person.person_id)
        }
    }
    with client:
        response = client.patch('/computers/' + str(computer.id) + '/relationships/owner',
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_delete_relationship(session, client, register_routes, computer, person):
    session_ = session
    person.computers = [computer]
    session_.commit()

    payload = {
        'data': [
            {
                'type': 'computer',
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.delete('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                 data=json.dumps(payload),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_delete_relationship_single(session, client, register_routes, computer, person):
    session_ = session
    computer.person = person
    session_.commit()

    payload = {
        'data': {
            'type': 'person',
            'id': str(person.person_id)
        }
    }

    with client:
        response = client.delete('/computers/' + str(computer.id) + '/relationships/owner',
                                 data=json.dumps(payload),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_get_list_response(client, register_routes):
    with client:
        response = client.get('/persons_response', content_type='application/vnd.api+json')
        assert response.status_code == 200


# test various Accept headers
def test_single_accept_header(client, register_routes):
    with client:
        response = client.get('/persons', content_type='application/vnd.api+json', headers={'Accept': 'application/vnd.api+json'})
        assert response.status_code == 200


def test_multiple_accept_header(client, register_routes):
    with client:
        response = client.get('/persons', content_type='application/vnd.api+json', headers={'Accept': '*/*, application/vnd.api+json, application/vnd.api+json;q=0.9'})
        assert response.status_code == 200


def test_wrong_accept_header(client, register_routes):
    with client:
        response = client.get('/persons', content_type='application/vnd.api+json', headers={'Accept': 'application/vnd.api+json;q=0.7, application/vnd.api+json;q=0.9'})
        assert response.status_code == 406


# test Content-Type error
def test_wrong_content_type(client, register_routes):
    with client:
        response = client.post('/persons', headers={'Content-Type': 'application/vnd.api+json;q=0.8'})
        assert response.status_code == 415


@pytest.fixture(scope="module")
def wrong_data_layer():
    class WrongDataLayer(object):
        pass
    yield WrongDataLayer


def test_wrong_data_layer_inheritence(wrong_data_layer):
    with pytest.raises(Exception):
        class PersonDetail(ResourceDetail):
            data_layer = {'class': wrong_data_layer}
        PersonDetail()


def test_wrong_data_layer_kwargs_type():
    with pytest.raises(Exception):
        class PersonDetail(ResourceDetail):
            data_layer = list()
        PersonDetail()


def test_get_list_jsonapiexception(client, register_routes):
    with client:
        response = client.get('/persons_jsonapiexception', content_type='application/vnd.api+json')
        assert response.status_code == 500


def test_get_list_exception(client, register_routes):
    with client:
        response = client.get('/persons_exception', content_type='application/vnd.api+json')
        assert response.status_code == 500


def test_get_list_without_schema(client, register_routes):
    with client:
        response = client.post('/persons_without_schema', content_type='application/vnd.api+json')
        assert response.status_code == 500


def test_get_list_bad_request(client, register_routes):
    with client:
        querystring = urlencode({'page[number': 3})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_invalid_fields(client, register_routes):
    with client:
        querystring = urlencode({'fields[person]': 'error'})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_invalid_include(client, register_routes):
    with client:
        querystring = urlencode({'include': 'error'})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_invalid_filters_parsing(client, register_routes):
    with client:
        querystring = urlencode({'filter': 'error'})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_invalid_page(client, register_routes):
    with client:
        querystring = urlencode({'page[number]': 'error'})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_invalid_sort(client, register_routes):
    with client:
        querystring = urlencode({'sort': 'error'})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_detail_object_not_found(client, register_routes):
    with client:
        response = client.get('/persons/3', content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_post_relationship_related_object_not_found(client, register_routes, person):
    payload = {
        'data': [
            {
                'type': 'computer',
                'id': '2'
            }
        ]
    }

    with client:
        response = client.post('/persons/' + str(person.person_id) + '/relationships/computers',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 404


def test_get_relationship_relationship_field_not_found(client, register_routes, person):
    with client:
        response = client.get('/persons/' + str(person.person_id) + '/relationships/computer',
                              content_type='application/vnd.api+json')
        assert response.status_code == 500


def test_get_list_invalid_filters_val(client, register_routes):
    with client:
        querystring = urlencode({'filter': json.dumps([{'name': 'computers', 'op': 'any'}])})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_name(client, register_routes):
    with client:
        querystring = urlencode({'filter': json.dumps([{'name': 'computers__serial', 'op': 'any', 'val': '1'}])})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 200


def test_get_list_no_name(client, register_routes):
    with client:
        querystring = urlencode({'filter': json.dumps([{'op': 'any', 'val': '1'}])})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_no_op(client, register_routes):
    with client:
        querystring = urlencode({'filter': json.dumps([{'name': 'computers__serial', 'val': '1'}])})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_attr_error(client, register_routes):
    with client:
        querystring = urlencode({'filter': json.dumps([{'name': 'error', 'op': 'eq', 'val': '1'}])})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_get_list_field_error(client, register_routes):
    with client:
        querystring = urlencode({'filter': json.dumps([{'name': 'name', 'op': 'eq', 'field': 'error'}])})
        response = client.get('/persons' + '?' + querystring, content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_sqlalchemy_data_layer_without_session(person_model, person_list):
    with pytest.raises(Exception):
        SqlalchemyDataLayer(dict(model=person_model, resource=person_list))


def test_sqlalchemy_data_layer_without_model(session, person_list):
    with pytest.raises(Exception):
        SqlalchemyDataLayer(dict(session=session, resource=person_list))


def test_sqlalchemy_data_layer_create_object_error(session, person_model, person_list):
    with pytest.raises(JsonApiException):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model, resource=person_list))
        dl.create_object(dict(), dict())

def test_sqlalchemy_data_layer_get_object_error(session, person_model):
    with pytest.raises(Exception):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model, id_field='error'))
        dl.get_object(dict())


def test_sqlalchemy_data_layer_update_object_error(session, person_model, person_list, monkeypatch):
    def commit_mock():
        raise JsonApiException()
    with pytest.raises(JsonApiException):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model, resource=person_list))
        monkeypatch.setattr(dl.session, 'commit', commit_mock)
        dl.update_object(dict(), dict(), dict())


def test_sqlalchemy_data_layer_delete_object_error(session, person_model, person_list, monkeypatch):
    def commit_mock():
        raise JsonApiException()

    def delete_mock(obj):
        pass
    with pytest.raises(JsonApiException):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model, resource=person_list))
        monkeypatch.setattr(dl.session, 'commit', commit_mock)
        monkeypatch.setattr(dl.session, 'delete', delete_mock)
        dl.delete_object(dict(), dict())


def test_sqlalchemy_data_layer_create_relationship_field_not_found(session, person_model):
    with pytest.raises(Exception):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        dl.create_relationship(dict(), 'error', '', dict(id=1))


def test_sqlalchemy_data_layer_create_relationship_error(session, person_model, get_object_mock, monkeypatch):
    def commit_mock():
        raise JsonApiException()
    with pytest.raises(JsonApiException):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        monkeypatch.setattr(dl.session, 'commit', commit_mock)
        monkeypatch.setattr(dl, 'get_object', get_object_mock)
        dl.create_relationship(dict(data=None), 'foo', '', dict(id=1))


def test_sqlalchemy_data_layer_get_relationship_field_not_found(session, person_model):
    with pytest.raises(RelationNotFound):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        dl.get_relationship('error', '', '', dict(id=1))


def test_sqlalchemy_data_layer_update_relationship_field_not_found(session, person_model):
    with pytest.raises(Exception):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        dl.update_relationship(dict(), 'error', '', dict(id=1))


def test_sqlalchemy_data_layer_update_relationship_error(session, person_model, get_object_mock, monkeypatch):
    def commit_mock():
        raise JsonApiException()
    with pytest.raises(JsonApiException):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        monkeypatch.setattr(dl.session, 'commit', commit_mock)
        monkeypatch.setattr(dl, 'get_object', get_object_mock)
        dl.update_relationship(dict(data=None), 'foo', '', dict(id=1))


def test_sqlalchemy_data_layer_delete_relationship_field_not_found(session, person_model):
    with pytest.raises(Exception):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        dl.delete_relationship(dict(), 'error', '', dict(id=1))


def test_sqlalchemy_data_layer_delete_relationship_error(session, person_model, get_object_mock, monkeypatch):
    def commit_mock():
        raise JsonApiException()
    with pytest.raises(JsonApiException):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        monkeypatch.setattr(dl.session, 'commit', commit_mock)
        monkeypatch.setattr(dl, 'get_object', get_object_mock)
        dl.delete_relationship(dict(data=None), 'foo', '', dict(id=1))


def test_sqlalchemy_data_layer_sort_query_error(session, person_model, monkeypatch):
    with pytest.raises(InvalidSort):
        dl = SqlalchemyDataLayer(dict(session=session, model=person_model))
        dl.sort_query(None, [dict(field='test')])


def test_post_list_incorrect_type(client, register_routes, computer):
    payload = {
        'data': {
            'type': 'error',
            'attributes': {
                'name': 'test'
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.post('/persons', data=json.dumps(payload), content_type='application/vnd.api+json')
        assert response.status_code == 409


def test_post_list_validation_error(client, register_routes, computer):
    payload = {
        'data': {
            'type': 'person',
            'attributes': {},
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.post('/persons', data=json.dumps(payload), content_type='application/vnd.api+json')
        assert response.status_code == 422


def test_patch_detail_incorrect_type(client, register_routes, computer, person):
    payload = {
        'data': {
            'id': str(person.person_id),
            'type': 'error',
            'attributes': {
                'name': 'test2'
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id),
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 409


def test_patch_detail_validation_error(client, register_routes, computer, person):
    payload = {
        'data': {
            'id': str(person.person_id),
            'type': 'person',
            'attributes': {
                'name': {'test2': 'error'}
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id),
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 422


def test_patch_detail_missing_id(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'person',
            'attributes': {
                'name': 'test2'
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id),
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_patch_detail_wrong_id(client, register_routes, computer, person):
    payload = {
        'data': {
            'id': 'error',
            'type': 'person',
            'attributes': {
                'name': 'test2'
            },
            'relationships': {
                'computers': {
                    'data': [
                        {
                            'type': 'computer',
                            'id': str(computer.id)
                        }
                    ]
                }
            }
        }
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id),
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_post_relationship_no_data(client, register_routes, computer, person):
    with client:
        response = client.post('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                               data=json.dumps(dict()),
                               content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_post_relationship_not_list_missing_type(client, register_routes, computer, person):
    payload = {
        'data': {
            'id': str(person.person_id)
        }
    }

    with client:
        response = client.post('/computers/' + str(computer.id) + '/relationships/owner',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_post_relationship_not_list_missing_id(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'person'
        }
    }

    with client:
        response = client.post('/computers/' + str(computer.id) + '/relationships/owner',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_post_relationship_not_list_wrong_type(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'error',
            'id': str(person.person_id)
        }
    }

    with client:
        response = client.post('/computers/' + str(computer.id) + '/relationships/owner',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 409


def test_post_relationship_missing_type(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.post('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_post_relationship_missing_id(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'type': 'computer',
            }
        ]
    }

    with client:
        response = client.post('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_post_relationship_wrong_type(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'type': 'error',
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.post('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                               data=json.dumps(payload),
                               content_type='application/vnd.api+json')
        assert response.status_code == 409


def test_patch_relationship_no_data(client, register_routes, computer, person):
    with client:
        response = client.patch('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                data=json.dumps(dict()),
                                content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_patch_relationship_not_list_missing_type(client, register_routes, computer, person):
    payload = {
        'data': {
            'id': str(person.person_id)
        }
    }

    with client:
        response = client.patch('/computers/' + str(computer.id) + '/relationships/owner',
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_patch_relationship_not_list_missing_id(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'person'
        }
    }

    with client:
        response = client.patch('/computers/' + str(computer.id) + '/relationships/owner',
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_patch_relationship_not_list_wrong_type(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'error',
            'id': str(person.person_id)
        }
    }

    with client:
        response = client.patch('/computers/' + str(computer.id) + '/relationships/owner',
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 409


def test_patch_relationship_missing_type(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_patch_relationship_missing_id(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'type': 'computer',
            }
        ]
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_patch_relationship_wrong_type(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'type': 'error',
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.patch('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                data=json.dumps(payload),
                                content_type='application/vnd.api+json')
        assert response.status_code == 409


def test_delete_relationship_no_data(client, register_routes, computer, person):
    with client:
        response = client.delete('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                 data=json.dumps(dict()),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_delete_relationship_not_list_missing_type(client, register_routes, computer, person):
    payload = {
        'data': {
            'id': str(person.person_id)
        }
    }

    with client:
        response = client.delete('/computers/' + str(computer.id) + '/relationships/owner',
                                 data=json.dumps(payload),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_delete_relationship_not_list_missing_id(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'person'
        }
    }

    with client:
        response = client.delete('/computers/' + str(computer.id) + '/relationships/owner',
                                 data=json.dumps(payload),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_delete_relationship_not_list_wrong_type(client, register_routes, computer, person):
    payload = {
        'data': {
            'type': 'error',
            'id': str(person.person_id)
        }
    }

    with client:
        response = client.delete('/computers/' + str(computer.id) + '/relationships/owner',
                                 data=json.dumps(payload),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 409


def test_delete_relationship_missing_type(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.delete('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                 data=json.dumps(payload),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_delete_relationship_missing_id(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'type': 'computer',
            }
        ]
    }

    with client:
        response = client.delete('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                 data=json.dumps(payload),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 400


def test_delete_relationship_wrong_type(client, register_routes, computer, person):
    payload = {
        'data': [
            {
                'type': 'error',
                'id': str(computer.id)
            }
        ]
    }

    with client:
        response = client.delete('/persons/' + str(person.person_id) + '/relationships/computers?include=computers',
                                 data=json.dumps(payload),
                                 content_type='application/vnd.api+json')
        assert response.status_code == 409


def test_base_data_layer():
    base_dl = BaseDataLayer(dict())
    with pytest.raises(NotImplementedError):
        base_dl.create_object(None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.get_object(dict())
    with pytest.raises(NotImplementedError):
        base_dl.get_collection(None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.update_object(None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.delete_object(None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.create_relationship(None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.get_relationship(None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.update_relationship(None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.delete_relationship(None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.query(dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_create_object(None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_create_object(None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_get_object(dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_get_object(None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_get_collection(None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_get_collection(None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_update_object(None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_update_object(None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_delete_object(None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_delete_object(None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_create_relationship(None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_create_relationship(None, None, None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_get_relationship(None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_get_relationship(None, None, None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_update_relationship(None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_update_relationship(None, None, None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.before_delete_relationship(None, None, None, dict())
    with pytest.raises(NotImplementedError):
        base_dl.after_delete_relationship(None, None, None, None, None, dict())


def test_qs_manager():
    with pytest.raises(ValueError):
        QSManager([], None)


def test_api(app, person_list):
    api = Api(app)
    api.route(person_list, 'person_list', '/persons', '/person_list')
    api.init_app()


def test_api_resources(app, person_list):
    api = Api()
    api.route(person_list, 'person_list2', '/persons', '/person_list')
    api.init_app(app)


def test_relationship_containing_hyphens(client, register_routes, person_computers, computer_schema, person):
    response = client.get('/persons/{}/relationships/computers-owned'.format(person.person_id), content_type='application/vnd.api+json')
    assert response.status_code == 200