<reponame>alexschiller/osf.io<filename>api_tests/collections/test_views.py
from urlparse import urlparse

import pytest
from nose.tools import *  # flake8: noqa

from website.models import Node, NodeLog
from website.util.sanitize import strip_html
from website.util import disconnected_from_listeners
from website.project.signals import contributor_removed
from api.base.settings.defaults import API_BASE
from tests.base import ApiTestCase
from osf_tests.factories import (
    CollectionFactory,
    NodeFactory,
    RegistrationFactory,
    ProjectFactory,
    AuthUserFactory
)
from tests.utils import assert_logs
from framework.auth.core import Auth


def node_url_for(node_id):
    return '/{}nodes/{}/'.format(API_BASE, node_id)


class TestCollectionList(ApiTestCase):
    def setUp(self):
        super(TestCollectionList, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.deleted_one = CollectionFactory(creator=self.user_one, is_deleted=True)
        self.collection_one = CollectionFactory(creator=self.user_one)

        self.url = '/{}collections/'.format(API_BASE)

    def test_user_one_gets_user_one_collections(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        ids = [each['id'] for each in res.json['data']]
        assert_not_in(self.deleted_one._id, ids)
        assert_in(self.collection_one._id, ids)

    def test_user_two_gets_nothing(self):
        res = self.app.get(self.url, auth=self.user_two.auth)
        ids = [each['id'] for each in res.json['data']]
        assert_not_in(self.deleted_one._id, ids)
        assert_not_in(self.collection_one._id, ids)

    def test_unauthorized_gets_nothing(self):
        res = self.app.get(self.url)
        ids = [each['id'] for each in res.json['data']]
        assert_not_in(self.deleted_one._id, ids)
        assert_not_in(self.collection_one._id, ids)


class TestCollectionCreate(ApiTestCase):

    def setUp(self):
        super(TestCollectionCreate, self).setUp()
        self.user_one = AuthUserFactory()
        self.url = '/{}collections/'.format(API_BASE)

        self.title = 'Cool Collection'

        self.user_two = AuthUserFactory()

        self.collection = {
            'data': {
                'type': 'collections',
                'attributes':
                    {
                        'title': self.title,
                    }
            }
        }

    def test_collection_create_invalid_data(self):
        res = self.app.post_json_api(self.url, "Incorrect data", auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], "Malformed request.")

        res = self.app.post_json_api(self.url, ["Incorrect data"], auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], "Malformed request.")

    def test_creates_collection_logged_out(self):
        res = self.app.post_json_api(self.url, self.collection, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_creates_collection_logged_in(self):
        res = self.app.post_json_api(self.url, self.collection, auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        pid = res.json['data']['id']
        assert_equal(res.json['data']['attributes']['title'], self.title)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['type'], 'collections')
        res = self.app.get(self.url+'?filter[title]={}'.format(self.title), auth=self.user_one.auth)
        ids = [each['id'] for each in res.json['data']]
        assert_in(pid, ids)
        collection = Node.load(pid)
        assert_equal(collection.logs.order_by('date').first().action, NodeLog.PROJECT_CREATED)
        assert_equal(collection.title, self.title)

    def test_create_collection_creates_collection_and_sanitizes_html(self):
        title = '<em>Cool</em> <script>alert("even cooler")</script> <strong>Project</strong>'

        res = self.app.post_json_api(self.url, {
            'data': {
                'attributes': {
                    'title': title,
                },
                'type': 'collections'
            }
        }, auth=self.user_one.auth)
        collection_id = res.json['data']['id']
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')

        collection = Node.load(collection_id)
        assert_equal(collection.logs.latest().action, NodeLog.PROJECT_CREATED)
        assert_equal(collection.title, strip_html(title))

    def test_creates_project_no_type(self):
        collection = {
            'data': {
                'attributes': {
                    'title': self.title,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/type')

    def test_creates_project_incorrect_type(self):
        collection = {
            'data': {
                'attributes': {
                    'title': self.title,
                },
                'type': 'Wrong type.'
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 409)
        assert_equal(res.json['errors'][0]['detail'], 'This resource has a type of "collections", but you set the json body\'s type field to "Wrong type.". You probably need to change the type field to match the resource\'s type.')

    def test_creates_collection_properties_not_nested(self):
        project = {
            'data': {
                'title': self.title,
                'type': 'collections'
            }
        }
        res = self.app.post_json_api(self.url, project, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data/attributes.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/attributes')

    def test_create_project_invalid_title(self):
        project = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': 'A' * 201,
                }
            }
        }
        res = self.app.post_json_api(self.url, project, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Title cannot exceed 200 characters.')

    def test_create_bookmark_collection(self):
        collection = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': 'Bookmarks',
                    'bookmarks': True,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.json['data']['attributes']['title'], 'Bookmarks')
        assert_true(res.json['data']['attributes']['bookmarks'])

    def test_cannot_create_multiple_bookmark_collection(self):
        collection = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': 'Bookmarks',
                    'bookmarks': True,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Each user cannot have more than one Bookmark collection.')

    def test_create_bookmark_collection_with_wrong_title(self):
        collection = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': 'Not Bookmarks',
                    'bookmarks': True,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.json['data']['attributes']['title'], 'Bookmarks')
        assert_true(res.json['data']['attributes']['bookmarks'])

    def test_create_bookmark_collection_with_no_title(self):
        collection = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'bookmarks': True,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)

class TestCollectionFiltering(ApiTestCase):

    def setUp(self):
        super(TestCollectionFiltering, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.collection_one = CollectionFactory(title="Collection One", creator=self.user_one)
        self.collection_two = CollectionFactory(title="Collection Two", creator=self.user_one)
        self.collection_three = CollectionFactory(title="Three", creator=self.user_one)

        self.url = "/{}collections/".format(API_BASE)

    def test_get_all_projects_with_no_filter_logged_in(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_in(self.collection_one._id, ids)
        assert_in(self.collection_two._id, ids)
        assert_in(self.collection_three._id, ids)

    def test_get_no_collections_with_no_filter_not_logged_in(self):
        res = self.app.get(self.url)
        node_json = res.json['data']
        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_no_collections_with_no_filter_logged_in_as_wrong_user(self):
        res = self.app.get(self.url, auth=self.user_two.auth)
        node_json = res.json['data']
        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_one_collection_with_exact_filter_logged_in(self):
        url = "/{}collections/?filter[title]=Collection%20One".format(API_BASE)

        res = self.app.get(url, auth=self.user_one.auth)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_one_collection_with_exact_filter_not_logged_in(self):
        url = "/{}collections/?filter[title]=Collection%20One".format(API_BASE)

        res = self.app.get(url)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_some_collections_with_substring_logged_in(self):
        url = "/{}collections/?filter[title]=Two".format(API_BASE)

        res = self.app.get(url, auth=self.user_one.auth)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_no_projects_with_substring_not_logged_in(self):
        url = "/{}collections/?filter[title]=Two".format(API_BASE)

        res = self.app.get(url)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_incorrect_filtering_field_logged_in(self):
        url = '/{}collections/?filter[notafield]=bogus'.format(API_BASE)

        res = self.app.get(url, expect_errors=True)
        assert_equal(res.status_code, 400)
        errors = res.json['errors']
        assert_equal(len(errors), 1)
        assert_equal(errors[0]['detail'], "'notafield' is not a valid field for this endpoint.")
        assert_equal(errors[0]['source'], {'parameter': 'filter'})


class TestCollectionDetail(ApiTestCase):
    def setUp(self):
        super(TestCollectionDetail, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()

        self.collection = CollectionFactory(title="Test collection", creator=self.user_one)
        self.url = '/{}collections/{}/'.format(API_BASE, self.collection._id)

    def test_do_not_return_collection_details_logged_out(self):
        res = self.app.get(self.url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_return_collection_details_logged_in(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['attributes']['title'], self.collection.title)
        node_links_url = res.json['data']['relationships']['node_links']['links']['related']['href']
        expected_url = self.url + 'node_links/'
        assert_equal(urlparse(node_links_url).path, expected_url)

    def test_do_not_return_collection_details_logged_in_non_contributor(self):
        res = self.app.get(self.url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_requesting_node_returns_error(self):
        node = NodeFactory(creator=self.user_one)
        res = self.app.get(
            '/{}collections/{}/'.format(API_BASE, node._id),
            auth=self.user_one.auth,
            expect_errors=True
        )
        assert_equal(res.status_code, 404)


class CollectionCRUDTestCase(ApiTestCase):

    def setUp(self):
        super(CollectionCRUDTestCase, self).setUp()
        self.user = AuthUserFactory()
        self.user_two = AuthUserFactory()

        self.title = 'Cool Collection'
        self.new_title = 'Super Cool Collection'

        self.collection = CollectionFactory(title=self.title, creator=self.user)
        self.url = '/{}collections/{}/'.format(API_BASE, self.collection._id)
        self.fake_url = '/{}collections/{}/'.format(API_BASE, '12345')


def make_collection_payload(collection, attributes):
    return {
        'data': {
            'id': collection._id,
            'type': 'collections',
            'attributes': attributes,
        }
    }


class TestCollectionUpdate(CollectionCRUDTestCase):

    def test_node_update_invalid_data(self):
        res = self.app.put_json_api(self.url, "Incorrect data", auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], "Malformed request.")

        res = self.app.patch_json_api(self.url, ["Incorrect data"], auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], "Malformed request.")

    def test_update_collection_properties_not_nested(self):
        res = self.app.put_json_api(self.url, {
            'id': self.collection._id,
            'type': 'collections',
            'title': self.new_title,
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

    def test_update_invalid_id(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': '12345',
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 409)

    def test_update_invalid_type(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collection',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 409)

    def test_update_no_id(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/id')

    def test_update_no_type(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/type')

    def test_cannot_update_collection_logged_out(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.EDITED_TITLE, 'collection')
    def test_update_collection_logged_in(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['attributes']['title'], self.new_title)
        self.collection.reload()
        assert_equal(self.collection.title, self.new_title)

    @assert_logs(NodeLog.EDITED_TITLE, 'collection')
    def test_partial_update_collection_logged_in(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['attributes']['title'], self.new_title)
        self.collection.reload()
        assert_equal(self.collection.title, self.new_title)

    def test_cannot_update_collection_logged_in_but_unauthorized(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.EDITED_TITLE, 'collection')
    def test_update_collection_sanitizes_html_properly(self):
        """Post request should update resource, and any HTML in fields should be stripped"""
        new_title = '<strong>Super</strong><script>alert("even cooler")</script> Cool Project'
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': new_title,
                }
            }
        }, auth=self.user.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['attributes']['title'], strip_html(new_title))
        self.collection.reload()
        assert_equal(self.collection.title, strip_html(new_title))

    @assert_logs(NodeLog.EDITED_TITLE, 'collection')
    def test_partial_update_collection_updates_project_correctly_and_sanitizes_html(self):
        new_title = 'An <script>alert("even cooler")</script> project'
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': new_title
                }
            }
        }, auth=self.user.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        self.collection.reload()
        assert_equal(self.collection.title, strip_html(new_title))

    def test_partial_update_collection_logged_in_but_unauthorized(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'attributes': {
                    'title': self.new_title},
                'id': self.collection._id,
                'type': 'nodes',
            }
        }, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_multiple_patch_requests_with_same_title_generates_one_log(self):
        payload = make_collection_payload(collection=self.collection, attributes={'title': self.new_title})
        original_n_logs = self.collection.logs.count()

        for x in range(0, 2):
            res = self.app.patch_json_api(self.url, payload, auth=self.user.auth)
            assert_equal(res.status_code, 200)
            self.collection.reload()
            assert_equal(self.collection.title, self.new_title)
            assert_equal(self.collection.logs.count(), original_n_logs + 1)  # sanity check

    def test_partial_update_invalid_id(self):
        res = self.app.patch_json_api(self.url, {
                'data': {
                    'id': '12345',
                    'type': 'collections',
                    'attributes': {
                        'title': self.new_title,
                    }
                }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 409)

    def test_partial_update_invalid_type(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collection',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 409)

    def test_partial_update_no_id(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/id')

    def test_partial_update_no_type(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/type')

    # Nothing will be updated here
    def test_partial_update_collection_properties_not_nested(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'nodes',
                'title': self.new_title,
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)

    def test_update_collection_invalid_title(self):
        project = {
            'data': {
                'type': 'collections',
                'id': self.collection._id,
                'attributes': {
                    'title': 'A' * 201,
                    'category': 'project',
                }
            }
        }
        res = self.app.put_json_api(self.url, project, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Title cannot exceed 200 characters.')


class TestCollectionDelete(CollectionCRUDTestCase):

    def test_do_not_delete_collection_unauthenticated(self):
        res = self.app.delete(self.url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert 'detail' in res.json['errors'][0]

    def test_do_not_return_deleted_collection(self):
        self.collection.is_deleted = True
        self.collection.save()
        res = self.app.get(self.url, expect_errors=True)
        assert_equal(res.status_code, 410)
        assert 'detail' in res.json['errors'][0]

    def test_do_not_delete_collection_unauthorized(self):
        res = self.app.delete_json_api(self.url, auth=self.user_two.auth, expect_errors=True)
        self.collection.reload()
        assert_equal(res.status_code, 403)
        assert_equal(self.collection.is_deleted, False)
        assert 'detail' in res.json['errors'][0]

    @assert_logs(NodeLog.PROJECT_DELETED, 'collection')
    def test_delete_collection_authorized(self):
        res = self.app.delete_json_api(self.url, auth=self.user.auth, expect_errors=True)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(self.collection.is_deleted, True)

    def test_cannot_delete_invalid_collection(self):
        res = self.app.delete(self.fake_url, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 404)
        assert 'detail' in res.json['errors'][0]


class TestCollectionNodeLinksList(ApiTestCase):

    def setUp(self):
        super(TestCollectionNodeLinksList, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.collection = CollectionFactory(creator=self.user_one)
        self.project = ProjectFactory(is_public=False, creator=self.user_one)
        self.public_project = ProjectFactory(is_public=True, creator=self.user_two)
        self.registration = RegistrationFactory(is_public=False, creator=self.user_one)
        self.public_registration = RegistrationFactory(is_public=True, creator=self.user_two)
        # self.private_project = ProjectFactory(is_public=False, creator=self.user_two)
        self.collection.add_pointer(self.project, auth=Auth(self.user_one))
        self.collection.add_pointer(self.public_project, auth=Auth(self.user_one))
        self.collection.add_pointer(self.registration, auth=Auth(self.user_one))
        self.collection.add_pointer(self.public_registration, auth=Auth(self.user_one))
        self.url = '/{}collections/{}/node_links/'.format(API_BASE, self.collection._id)

    def test_do_not_return_node_pointers_logged_out(self):
        res = self.app.get(self.url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_return_node_pointers_logged_in(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(len(res_json), 4)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        first_embedded = res_json[0]['embeds']['target_node']['data']['id']
        second_embedded = res_json[1]['embeds']['target_node']['data']['id']
        # node_links end point does not handle registrations correctly
        third_embedded = res_json[2]['embeds']['target_node']['errors'][0]['detail']
        fourth_embedded = res_json[3]['embeds']['target_node']['errors'][0]['detail']
        assert_items_equal([first_embedded, second_embedded, third_embedded, fourth_embedded], [self.project._id, self.public_project._id, 'Not found.', 'Not found.'])

    def test_return_private_node_pointers_logged_in_non_contributor(self):
        res = self.app.get(self.url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_deleted_links_not_returned(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        res_json = res.json['data']
        original_length = len(res_json)

        self.public_project.is_deleted = True
        self.public_project.save()

        res = self.app.get(self.url, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(len(res_json), original_length - 1)


class TestCollectionNodeLinkCreate(ApiTestCase):

    def setUp(self):
        super(TestCollectionNodeLinkCreate, self).setUp()
        node_link_string = '/{}collections/{}/node_links/'
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()

        self.collection = CollectionFactory(creator=self.user_one)
        self.collection_two = CollectionFactory(creator=self.user_one)

        self.project = ProjectFactory(is_public=False, creator=self.user_one)
        self.public_project = ProjectFactory(is_public=True, creator=self.user_one)
        self.registration = RegistrationFactory(is_public=False, creator=self.user_one)
        self.public_registration = RegistrationFactory(is_public=True, creator=self.user_one)
        self.user_two_private_project = ProjectFactory(is_public=False, creator=self.user_two)
        self.user_two_public_project = ProjectFactory(is_public=True, creator=self.user_two)
        self.user_two_private_registration = RegistrationFactory(is_public=False, creator=self.user_two)
        self.user_two_public_registration = RegistrationFactory(is_public=True, creator=self.user_two)

        self.url = node_link_string.format(API_BASE, self.collection._id)
        self.fake_node_id = 'fakeident'
        self.fake_url = node_link_string.format(API_BASE, self.fake_node_id)

    @staticmethod
    def post_payload(target_node_id, outer_type='node_links', inner_type='nodes'):
        payload = {
            'data': {
                'relationships': {
                    'nodes': {
                        'data': {
                            'id': target_node_id,
                        }
                    }
                }
            }
        }

        if outer_type:
            payload['data']['type'] = outer_type
        if inner_type:
            payload['data']['relationships']['nodes']['data']['type'] = inner_type
        return payload

    def test_does_not_create_link_when_payload_not_nested(self):
        payload = {'data': {'type': 'node_links', 'target_node_id': self.project._id}}
        res = self.app.post_json_api(self.url, payload, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/relationships')
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data/relationships.')

    def test_does_not_create_node_link_logged_out(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.project._id), expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_creates_node_link_to_public_project_logged_in(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.public_project._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded_node_id = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded_node_id, self.public_project._id)

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_creates_node_link_to_public_registration_logged_in(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.public_registration._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        # node_links end point does not handle registrations correctly
        embedded_node_id = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded_node_id, 'Not found.')

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_creates_node_link_to_private_project_logged_in(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.project._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        res_json = res.json['data']
        embedded_node_id = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded_node_id, self.project._id)

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_creates_node_link_to_private_registration_logged_in(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.registration._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        res_json = res.json['data']
        # node_links end point does not handle registrations correctly
        embedded_node_id = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded_node_id, 'Not found.')

    def test_does_not_create_node_link_unauthorized(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.user_two_private_project._id), auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_does_not_create_registration_link_unauthorized(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.user_two_private_registration._id), auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_create_node_link_to_non_contributing_node(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.user_two_public_project._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded_node_id = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded_node_id, self.user_two_public_project._id)

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_create_node_link_to_non_contributing_registration(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.user_two_public_registration._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        # node_links end point does not handle registrations correctly
        embedded_node_id = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded_node_id, 'Not found.')

    def test_create_node_link_to_fake_node(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.fake_node_id), auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_in('detail', res.json['errors'][0])
        assert_in('source', res.json['errors'][0])

    def test_fake_collection_pointing_to_valid_node(self):
        res = self.app.post_json_api(self.fake_url, self.post_payload(self.project._id), auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)
        assert_in('detail', res.json['errors'][0])

        res = self.app.post_json_api(self.fake_url, self.post_payload(self.project._id), auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 404)
        assert_in('detail', res.json['errors'][0])

    def test_create_collection_node_pointer_to_itself(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.collection._id), auth=self.user_one.auth, expect_errors=True)
        res_json = res.json
        assert_equal(res.status_code, 400)
        error = res_json['errors'][0]
        assert_in('detail', error)
        assert_equal("Target Node '{}' not found.".format(self.collection._id), error['detail'])
        assert_in('source', error)
        assert_in('pointer', error['source'])
        assert_equal('/data/relationships/node_links/data/id', error['source']['pointer'])

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_create_node_pointer_already_connected(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.project._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded_node_id = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded_node_id, self.project._id)

        res = self.app.post_json_api(self.url, self.post_payload(self.project._id), auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        error = res.json['errors'][0]
        assert_in('detail', error)
        assert_equal('Target Node \'{}\' already pointed to by \'{}\'.'.format(self.project._id, self.collection._id), error['detail'])
        assert_in('source', error)
        assert_in('pointer', error['source'])
        assert_equal('/data/relationships/node_links/data/id', error['source']['pointer'])

    def test_create_node_pointer_no_type(self):
        payload = self.post_payload(self.public_project._id, outer_type=None)
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/type')

    def test_create_node_pointer_incorrect_type(self):
        payload = self.post_payload(self.public_project._id, outer_type='wrong_type')
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 409)
        assert_equal(res.json['errors'][0]['detail'], 'This resource has a type of "node_links", but you set the json body\'s type field to "wrong_type". You probably need to change the type field to match the resource\'s type.')


class TestCollectionNodeLinkDetail(ApiTestCase):

    def setUp(self):
        super(TestCollectionNodeLinkDetail, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.collection = CollectionFactory(creator=self.user_one)
        self.project = ProjectFactory(creator=self.user_one, is_public=False)
        self.project_public = ProjectFactory(creator=self.user_one, is_public=False)
        self.registration = RegistrationFactory(creator=self.user_one, is_public=False)
        self.registration_public = RegistrationFactory(creator=self.user_one, is_public=False)
        self.node_link = self.collection.add_pointer(self.project, auth=Auth(self.user_one), save=True)
        self.node_link_public = self.collection.add_pointer(self.project_public, auth=Auth(self.user_one), save=True)
        self.registration_link = self.collection.add_pointer(self.registration, auth=Auth(self.user_one), save=True)
        self.registration_link_public = self.collection.add_pointer(self.registration_public, auth=Auth(self.user_one), save=True)
        self.url = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, self.node_link._id)
        self.url_public = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, self.node_link_public._id)
        self.reg_url = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, self.registration_link._id)
        self.reg_url_public = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, self.registration_link_public._id)

    def test_returns_error_public_node_link_detail_unauthenticated(self):
        res = self.app.get(self.url_public, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_returns_error_public_registration_link_detail_unauthenticated(self):
        res = self.app.get(self.reg_url_public, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_returns_public_node_pointer_detail_authorized(self):
        res = self.app.get(self.url_public, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        embedded = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.project_public._id)

    def test_returns_public_registration_pointer_detail_authorized(self):
        res = self.app.get(self.reg_url_public, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        # node_links end point does not handle registrations correctly
        embedded = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

    def test_returns_error_private_node_link_detail_unauthenticated(self):
        res = self.app.get(self.url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_returns_error_private_registration_link_detail_unauthenticated(self):
        res = self.app.get(self.reg_url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_returns_private_node_link_detail_authorized(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        embedded = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.project._id)

    def test_returns_private_registration_link_detail_authorized(self):
        res = self.app.get(self.reg_url, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        # node_links end point does not handle registrations correctly
        embedded = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

    def test_returns_error_private_node_link_detail_unauthorized(self):
        res = self.app.get(self.url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_returns_error_private_registration_link_detail_unauthorized(self):
        res = self.app.get(self.reg_url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_self_link_points_to_node_link_detail_url(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)
        url = res.json['data']['links']['self']
        assert_in(self.url, url)

    def test_delete_node_link_no_permissions_for_target_node(self):
        pointer_project = CollectionFactory(creator=self.user_two)
        pointer = self.collection.add_pointer(pointer_project, auth=Auth(self.user_one), save=True)
        assert_in(pointer.child, self.collection.linked_nodes.all())
        url = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, pointer._id)
        res = self.app.delete_json_api(url, auth=self.user_one.auth)
        assert_equal(res.status_code, 204)

    def test_can_not_delete_collection_public_node_link_unauthenticated(self):
        res = self.app.delete(self.url_public, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0].keys())

    def test_can_not_delete_collection_public_node_pointer_unauthorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.url_public, auth=self.user_two.auth, expect_errors=True)
        # This is could arguably be a 405, but we don't need to go crazy with status codes
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])
        self.collection.reload()
        assert_equal(node_count_before, len(self.collection.nodes_pointer))

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_delete_public_node_pointer_authorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.url_public, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 1, self.collection.nodes_pointer.count())

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_delete_public_registration_pointer_authorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.reg_url_public, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 1, len(self.collection.nodes_pointer))

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_delete_private_node_link_authorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.url, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 1, len(self.collection.nodes_pointer))

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_delete_private_registration_link_authorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.reg_url, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 1, self.collection.nodes_pointer.count())

    def test_can_not_delete_collection_private_node_link_unauthorized(self):
        res = self.app.delete(self.url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_can_not_delete_collection_private_registration_link_unauthorized(self):
        res = self.app.delete(self.reg_url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_can_not_return_deleted_collection_public_node_pointer(self):
        res = self.app.delete(self.url_public, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)

        res = self.app.get(self.url_public, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_can_not_return_deleted_collection_public_registration_pointer(self):
        res = self.app.delete(self.reg_url_public, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)

        res = self.app.get(self.reg_url_public, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_return_deleted_private_node_pointer(self):
        res = self.app.delete(self.url, auth=self.user_one.auth)
        self.project.reload()
        assert_equal(res.status_code, 204)

        res = self.app.get(self.url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_return_deleted_private_registration_pointer(self):
        res = self.app.delete(self.reg_url, auth=self.user_one.auth)
        self.project.reload()
        assert_equal(res.status_code, 204)

        res = self.app.get(self.reg_url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    # Regression test for https://openscience.atlassian.net/browse/OSF-4322
    def test_delete_link_that_is_not_linked_to_correct_node(self):
        collection = CollectionFactory(creator=self.user_one)
        # The node link belongs to a different project
        res = self.app.delete(
            '/{}nodes/{}/node_links/{}/'.format(API_BASE, collection._id, self.node_link._id),
            auth=self.user_one.auth,
            expect_errors=True
        )
        assert_equal(res.status_code, 404)
        errors = res.json['errors']
        assert_equal(len(errors), 1)
        assert_equal(errors[0]['detail'], 'Not found.')


class TestReturnDeletedCollection(ApiTestCase):
    def setUp(self):

        super(TestReturnDeletedCollection, self).setUp()
        self.user = AuthUserFactory()
        self.non_contrib = AuthUserFactory()

        self.deleted = CollectionFactory(is_deleted=True, creator=self.user, title='This collection has been deleted')
        self.collection = CollectionFactory(creator=self.user, title='A boring collection')

        self.new_title = 'This deleted node has been edited'
        self.deleted_url = '/{}collections/{}/'.format(API_BASE, self.deleted._id)

    def test_return_deleted_collection(self):
        res = self.app.get(self.deleted_url, expect_errors=True)
        assert_equal(res.status_code, 410)

    def test_edit_deleted_collection(self):
        res = self.app.put_json_api(self.deleted_url, params={'title': self.new_title}, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 410)

    def test_delete_deleted_collection(self):
        res = self.app.delete(self.deleted_url, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 410)


class TestCollectionBulkCreate(ApiTestCase):

    def setUp(self):
        super(TestCollectionBulkCreate, self).setUp()
        self.user_one = AuthUserFactory()
        self.url = '/{}collections/'.format(API_BASE)

        self.title = 'Cool Collection'
        self.title_two = 'Cool Collection, Too'

        self.user_two = AuthUserFactory()

        self.collection = {
                'type': 'collections',
                'attributes': {
                    'title': self.title,
                }
        }

        self.collection_two = {
                'type': 'collections',
                'attributes': {
                    'title': self.title_two,
                }
        }

        self.empty_collection = {'type': 'collections', 'attributes': {'title': "",}}

    def test_bulk_create_collections_blank_request(self):
        res = self.app.post_json_api(self.url, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_create_all_or_nothing(self):
        res = self.app.post_json_api(self.url, {'data': [self.collection, self.empty_collection]}, bulk=True, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_logged_out(self):
        res = self.app.post_json_api(self.url, {'data': [self.collection, self.collection_two]}, bulk=True, expect_errors=True)
        assert_equal(res.status_code, 401)

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_error_formatting(self):
        res = self.app.post_json_api(self.url, {'data': [self.empty_collection, self.empty_collection]}, bulk=True, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(len(res.json['errors']), 2)
        errors = res.json['errors']
        assert_items_equal([errors[0]['source'], errors[1]['source']],
                           [{'pointer': '/data/0/attributes/title'}, {'pointer': '/data/1/attributes/title'}])
        assert_items_equal([errors[0]['detail'], errors[1]['detail']],
                           ["This field may not be blank.", "This field may not be blank."])

    def test_bulk_create_limits(self):
        node_create_list = {'data': [self.collection] * 101}
        res = self.app.post_json_api(self.url, node_create_list, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_no_type(self):
        payload = {'data': [{"attributes": {'title': self.title}}]}
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/0/type')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_incorrect_type(self):
        payload = {'data': [self.collection, {'type': 'Incorrect type.', 'attributes': {'title': self.title}}]}
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_no_attributes(self):
        payload = {'data': [self.collection, {'type': 'collections', }]}
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)

        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/attributes')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_no_title(self):
        payload = {'data': [self.collection, {'type': 'collections', "attributes": {}}]}
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)

        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/1/attributes/title')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_ugly_payload(self):
        payload = 'sdf;jlasfd'
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_logged_in(self):
        res = self.app.post_json_api(self.url, {'data': [self.collection, self.collection_two]}, auth=self.user_one.auth, bulk=True)
        assert_equal(res.status_code, 201)
        assert_equal(len(res.json['data']), 2)
        assert_equal(res.json['data'][0]['attributes']['title'], self.collection['attributes']['title'])
        assert_equal(res.json['data'][1]['attributes']['title'], self.collection_two['attributes']['title'])
        assert_equal(res.content_type, 'application/vnd.api+json')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 2)
        id_one = res.json['data'][0]['id']
        id_two = res.json['data'][1]['id']

        res = self.app.delete_json_api(self.url, {'data': [{'id': id_one, 'type': 'collections'},
                                                           {'id': id_two, 'type': 'collections'}]},
                                       auth=self.user_one.auth, bulk=True)
        assert_equal(res.status_code, 204)


class TestCollectionBulkUpdate(ApiTestCase):

    def setUp(self):
        super(TestCollectionBulkUpdate, self).setUp()
        self.user = AuthUserFactory()

        self.title = 'Cool Project'
        self.new_title = 'Super Cool Project'

        self.collection = CollectionFactory(title=self.title,
                                            creator=self.user)

        self.collection_two = CollectionFactory(title=self.title,
                                                creator=self.user)

        self.collection_payload = {
            'data': [
                {
                    'id': self.collection._id,
                    'type': 'collections',
                    'attributes': {
                        'title': self.new_title,
                    }
                },
                {
                    'id': self.collection_two._id,
                    'type': 'collections',
                    'attributes': {
                        'title': self.new_title,
                    }
                }
            ]
        }

        self.url = '/{}collections/'.format(API_BASE)
        self.detail_url_base = '/{}collections/{}/'

        self.empty_payload = {'data': [
            {'id': self.collection._id, 'type': 'collections', 'attributes': {'title': "",}},
            {'id': self.collection_two._id, 'type': 'collections', 'attributes': {'title': "",}}
        ]}

    def test_bulk_update_nodes_blank_request(self):
        res = self.app.put_json_api(self.url, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_update_blank_but_not_empty_title(self):
        payload = {
            "data": [
                {
                  "id": self.collection._id,
                  "type": "collections",
                  "attributes": {
                    "title": "This shouldn't update."
                  }
                },
                {
                  "id": self.collection_two._id,
                  "type": "collections",
                  "attributes": {
                    "title": " "
                  }
                }
              ]
            }
        url = self.detail_url_base.format(API_BASE, self.collection._id)
        res = self.app.put_json_api(self.url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_collections_one_not_found(self):
        empty_payload = {'data': [
            {
                'id': '12345',
                'type': 'collections',
                'attributes': {
                    'title': self.new_title
                }
            }, self.collection_payload['data'][0]
        ]}

        res = self.app.put_json_api(self.url, empty_payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Could not find all objects to update.')

        url = self.detail_url_base.format(API_BASE, self.collection._id)
        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_collections_logged_out(self):
        res = self.app.put_json_api(self.url, self.collection_payload, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 401)
        assert_equal(res.json['errors'][0]['detail'], "Authentication credentials were not provided.")

        url = self.detail_url_base.format(API_BASE, self.collection._id)
        url_two = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

        res = self.app.get(url_two, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_collections_logged_in(self):
        res = self.app.put_json_api(self.url, self.collection_payload, auth=self.user.auth, bulk=True)
        assert_equal(res.status_code, 200)
        assert_equal({self.collection._id, self.collection_two._id},
                     {res.json['data'][0]['id'], res.json['data'][1]['id']})
        assert_equal(res.json['data'][0]['attributes']['title'], self.new_title)
        assert_equal(res.json['data'][1]['attributes']['title'], self.new_title)

    def test_bulk_update_collections_send_dictionary_not_list(self):
        res = self.app.put_json_api(self.url, {'data': {'id': self.collection._id, 'type': 'nodes',
                                                        'attributes': {'title': self.new_title}}},
                                    auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Expected a list of items but got type "dict".')

    def test_bulk_update_error_formatting(self):
        res = self.app.put_json_api(self.url, self.empty_payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(len(res.json['errors']), 2)
        errors = res.json['errors']
        assert_items_equal([errors[0]['source'], errors[1]['source']],
                           [{'pointer': '/data/0/attributes/title'}, {'pointer': '/data/1/attributes/title'}])
        assert_items_equal([errors[0]['detail'], errors[1]['detail']],
                           ['This field may not be blank.'] * 2)

    def test_bulk_update_id_not_supplied(self):
        res = self.app.put_json_api(self.url, {'data': [self.collection_payload['data'][1], {'type': 'collections', 'attributes':
            {'title': self.new_title}}]}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(len(res.json['errors']), 1)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/1/id')
        assert_equal(res.json['errors'][0]['detail'], "This field may not be null.")

        url = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_type_not_supplied(self):
        res = self.app.put_json_api(self.url, {'data': [self.collection_payload['data'][1], {'id': self.collection._id, 'attributes':
            {'title': self.new_title}}]}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(len(res.json['errors']), 1)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/1/type')
        assert_equal(res.json['errors'][0]['detail'], "This field may not be null.")

        url = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_incorrect_type(self):
        res = self.app.put_json_api(self.url, {'data': [self.collection_payload['data'][1], {'id': self.collection._id, 'type': 'Incorrect', 'attributes':
            {'title': self.new_title}}]}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)

        url = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_limits(self):
        node_update_list = {'data': [self.collection_payload['data'][0]] * 101}
        res = self.app.put_json_api(self.url, node_update_list, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

    def test_bulk_update_no_title(self):
        new_payload = {'id': self.collection._id, 'type': 'collections', 'attributes': {}}
        res = self.app.put_json_api(self.url, {'data': [self.collection_payload['data'][1], new_payload]}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

        url = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)


class TestNodeBulkDelete(ApiTestCase):

    def setUp(self):
        super(TestNodeBulkDelete, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.collection_one = CollectionFactory(title="Collection One", creator=self.user_one)
        self.collection_two = CollectionFactory(title="Collection Two", creator=self.user_one)
        self.collection_three = CollectionFactory(title="Collection Three", creator=self.user_one)
        self.collection_user_two = CollectionFactory(title="Collection User Two", creator=self.user_two)

        self.url = "/{}collections/".format(API_BASE)
        self.project_one_url = '/{}collections/{}/'.format(API_BASE, self.collection_one._id)
        self.project_two_url = '/{}collections/{}/'.format(API_BASE, self.collection_two._id)
        self.private_project_url = "/{}collections/{}/".format(API_BASE, self.collection_three._id)

        self.payload_one = {'data': [{'id': self.collection_one._id, 'type': 'collections'},
                                     {'id': self.collection_two._id, 'type': 'collections'}]}
        self.payload_two = {'data': [{'id': self.collection_three._id, 'type': 'collections'}]}

    def test_bulk_delete_nodes_blank_request(self):
        res = self.app.delete_json_api(self.url, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_delete_no_type(self):
        payload = {'data': [
            {'id': self.collection_one._id},
            {'id': self.collection_two._id}
        ]}
        res = self.app.delete_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /type.')

    def test_bulk_delete_no_id(self):
        payload = {'data': [
            {'type': 'collections'}
        ]}
        res = self.app.delete_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data/id.')

    def test_bulk_delete_dict_inside_data(self):
        res = self.app.delete_json_api(self.url, {'data': {'id': self.collection_one._id, 'type': 'collections'}},
                                       auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Expected a list of items but got type "dict".')

    def test_bulk_delete_invalid_type(self):
        res = self.app.delete_json_api(self.url, {'data': [{'type': 'Wrong type', 'id': self.collection_one._id}]},
                                       auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)

    def test_bulk_delete_collections_logged_in(self):
        res = self.app.delete_json_api(self.url, self.payload_one, auth=self.user_one.auth, bulk=True)
        assert_equal(res.status_code, 204)

        res = self.app.get(self.project_one_url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 410)
        self.collection_one.reload()
        self.collection_two.reload()

    def test_bulk_delete_collections_logged_out(self):
        res = self.app.delete_json_api(self.url, self.payload_one, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 401)
        assert_equal(res.json['errors'][0]['detail'], 'Authentication credentials were not provided.')

        res = self.app.get(self.project_one_url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 200)

        res = self.app.get(self.project_two_url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 200)

    def test_bulk_delete_collections_logged_in_non_contributor(self):
        res = self.app.delete_json_api(self.url, self.payload_two,
                                       auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 403)
        assert_equal(res.json['errors'][0]['detail'], 'You do not have permission to perform this action.')

        res = self.app.get(self.private_project_url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)

    def test_bulk_delete_all_or_nothing(self):
        new_payload = {'data': [{'id': self.collection_three._id, 'type': 'collections'}, {'id': self.collection_user_two
            ._id, 'type': 'collections'}]}
        res = self.app.delete_json_api(self.url, new_payload,
                                       auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 403)
        assert_equal(res.json['errors'][0]['detail'], 'You do not have permission to perform this action.')

        res = self.app.get(self.private_project_url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)

        url = "/{}collections/{}/".format(API_BASE, self.collection_user_two._id)
        res = self.app.get(url, auth=self.user_two.auth)
        assert_equal(res.status_code, 200)

    def test_bulk_delete_limits(self):
        new_payload = {'data': [{'id': self.collection_three._id, 'type': 'nodes'}] * 101}
        res = self.app.delete_json_api(self.url, new_payload,
                                       auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

    def test_bulk_delete_invalid_payload_one_not_found(self):
        new_payload = {'data': [self.payload_one['data'][0], {'id': '12345', 'type': 'collections'}]}
        res = self.app.delete_json_api(self.url, new_payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Could not find all objects to delete.')

        res = self.app.get(self.project_one_url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)

    def test_bulk_delete_no_payload(self):
        res = self.app.delete_json_api(self.url, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)


class TestCollectionLinksBulkCreate(ApiTestCase):

    def setUp(self):
        super(TestCollectionLinksBulkCreate, self).setUp()
        self.user = AuthUserFactory()

        self.collection_one = CollectionFactory(is_public=False, creator=self.user)
        self.private_pointer_project = ProjectFactory(is_public=False, creator=self.user)
        self.private_pointer_project_two = ProjectFactory(is_public=False, creator=self.user)
        self.private_pointer_registration = RegistrationFactory(is_public=False, creator=self.user)
        self.private_pointer_registration_two = RegistrationFactory(is_public=False, creator=self.user)

        self.collection_url = '/{}collections/{}/node_links/'.format(API_BASE, self.collection_one._id)

        self.collection_payload = {
            'data': [{
                "type": "node_links",
                "relationships": {
                    'target_node': {
                        'data': {
                            "id": self.private_pointer_project._id,
                            "type": 'nodes'
                        }
                    }
                }
            },
            {
                "type": "node_links",
                "relationships": {
                    'target_node': {
                        'data': {
                            "id": self.private_pointer_project_two._id,
                            "type": 'nodes'
                        }
                    }
                }
            },
            {
                "type": "node_links",
                "relationships": {
                    'target_node': {
                        'data': {
                            "id": self.private_pointer_registration._id,
                            "type": 'nodes'
                        }
                    }
                }
            },
            {
                "type": "node_links",
                "relationships": {
                    'target_node': {
                        'data': {
                            "id": self.private_pointer_registration_two._id,
                            "type": 'nodes'
                        }
                    }
                }
            }]
        }

        self.public_pointer_project = ProjectFactory(is_public=True, creator=self.user)
        self.public_pointer_project_two = ProjectFactory(is_public=True, creator=self.user)

        self.user_two = AuthUserFactory()
        self.user_two_collection = CollectionFactory(creator=self.user_two)
        self.user_two_project = ProjectFactory(is_public=True, creator=self.user_two)
        self.user_two_registration = RegistrationFactory(is_public=True, creator=self.user_two)
        self.user_two_url = '/{}collections/{}/node_links/'.format(API_BASE, self.user_two_collection._id)
        self.user_two_payload = {'data': [{
            'type': 'node_links',
            'relationships': {
                'nodes': {
                     'data': {
                         'id': self.user_two_project._id,
                         'type': 'nodes'
                     }
                }
            }
        },
        { 'type': 'node_links',
            'relationships': {
                'nodes': {
                     'data': {
                         'id': self.user_two_registration._id,
                         'type': 'nodes'
                     }
                }
            }
        }
    ]}

    def test_bulk_create_node_links_blank_request(self):
        res = self.app.post_json_api(self.collection_url, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_creates_pointers_limits(self):
        payload = {'data': [self.collection_payload['data'][0]] * 101}
        res = self.app.post_json_api(self.collection_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

        res = self.app.get(self.collection_url, auth=self.user.auth)
        assert_equal(res.json['data'], [])

    def test_bulk_creates_project_target_not_nested(self):
        payload = {'data': [{'type': 'node_links', 'target_node_id': self.private_pointer_project._id}]}
        res = self.app.post_json_api(self.collection_url, payload, auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/relationships')
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data/relationships.')

    def test_bulk_creates_collection_node_pointers_logged_out(self):
        res = self.app.post_json_api(self.collection_url, self.collection_payload, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

        res = self.app.get(self.collection_url, auth=self.user.auth)
        assert_equal(res.json['data'], [])

    def test_bulk_creates_collection_node_pointer_logged_in_non_contrib(self):
        res = self.app.post_json_api(self.collection_url, self.collection_payload,
                                     auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 403)

    def test_bulk_creates_collection_node_pointer_logged_in_contrib(self):
        res = self.app.post_json_api(self.collection_url, self.collection_payload, auth=self.user.auth, bulk=True)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded = res_json[0]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.private_pointer_project._id)

        embedded = res_json[1]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.private_pointer_project_two._id)

        # linked_node endpoint can create linked_registrations,
        # but will not return a valid embed
        embedded = res_json[2]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

        embedded = res_json[3]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

    def test_bulk_creates_node_pointers_collection_to_non_contributing_node(self):
        res = self.app.post_json_api(self.collection_url, self.user_two_payload, auth=self.user.auth, bulk=True)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded = res_json[0]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.user_two_project._id)
        embedded = res_json[1]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

        res = self.app.get(self.collection_url, auth=self.user.auth)
        res_json = res.json['data']
        embedded = res_json[0]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.user_two_project._id)
        embedded = res_json[1]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

    def test_bulk_creates_pointers_non_contributing_node_to_fake_node(self):
        fake_payload = {'data': [{'type': 'node_links', 'relationships': {'nodes': {'data': {'id': 'fdxlq', 'type': 'nodes'}}}}]}

        res = self.app.post_json_api(self.collection_url, fake_payload,
                                     auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_bulk_creates_pointers_contributing_node_to_fake_node(self):
        fake_payload = {'data': [{'type': 'node_links', 'relationships': {'nodes': {'data': {'id': 'fdxlq', 'type': 'nodes'}}}}]}

        res = self.app.post_json_api(self.collection_url, fake_payload,
                                     auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_in('detail', res.json['errors'][0])

    def test_bulk_creates_fake_nodes_pointing_to_contributing_node(self):
        fake_url = '/{}collections/{}/node_links/'.format(API_BASE, 'fdxlq')

        res = self.app.post_json_api(fake_url, self.collection_payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 404)
        assert_in('detail', res.json['errors'][0])

        res = self.app.post_json_api(fake_url, self.collection_payload, auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 404)
        assert_in('detail', res.json['errors'][0])

    def test_bulk_creates_node_pointer_already_connected(self):
        res = self.app.post_json_api(self.collection_url, self.collection_payload, auth=self.user.auth, bulk=True)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded = res_json[0]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.private_pointer_project._id)

        embedded_two = res_json[1]['embeds']['target_node']['data']['id']
        assert_equal(embedded_two, self.private_pointer_project_two._id)

        # linked_node endpoint can create linked_registrations,
        # but will not return a valid embed
        embedded = res_json[2]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

        embedded = res_json[3]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

        res = self.app.post_json_api(self.collection_url, self.collection_payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_in("Target Node '{}' already pointed to by '{}'.".format(self.private_pointer_project._id, self.collection_one._id), res.json['errors'][0]['detail'])

    def test_bulk_creates_node_pointer_no_type(self):
        payload = {'data': [{'relationships': {'nodes': {'data': {'type': 'nodes', 'id': self.user_two_collection._id}}}}]}
        res = self.app.post_json_api(self.collection_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/0/type')

    def test_bulk_creates_node_pointer_incorrect_type(self):
        payload = {'data': [{'type': 'Wrong type.', 'relationships': {'nodes': {'data': {'type': 'nodes', 'id': self.user_two_collection._id}}}}]}
        res = self.app.post_json_api(self.collection_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)
        assert_equal(res.json['errors'][0]['detail'], 'This resource has a type of "node_links", but you set the json body\'s type field to "Wrong type.". You probably need to change the type field to match the resource\'s type.')


class TestBulkDeleteCollectionNodeLinks(ApiTestCase):

    def setUp(self):
        super(TestBulkDeleteCollectionNodeLinks, self).setUp()
        self.user = AuthUserFactory()
        self.collection = CollectionFactory(creator=self.user)
        self.pointer_project = ProjectFactory(creator=self.user, is_public=True)
        self.pointer_project_two = ProjectFactory(creator=self.user, is_public=True)

        self.pointer = self.collection.add_pointer(self.pointer_project, auth=Auth(self.user), save=True)
        self.pointer_two = self.collection.add_pointer(self.pointer_project_two, auth=Auth(self.user), save=True)

        self.collection_payload = {
              "data": [
                {"type": "node_links", "id": self.pointer._id},
                {"type": "node_links", "id": self.pointer_two._id}
              ]
            }

        self.collection_url = '/{}collections/{}/node_links/'.format(API_BASE, self.collection._id)

        self.user_two = AuthUserFactory()

        self.collection_two = CollectionFactory(creator=self.user)
        self.collection_two_pointer_project = ProjectFactory(is_public=True, creator=self.user)
        self.collection_two_pointer_project_two = ProjectFactory(is_public=True, creator=self.user)

        self.collection_two_pointer = self.collection_two.add_pointer(self.collection_two_pointer_project,
                                                              auth=Auth(self.user),
                                                              save=True)
        self.collection_two_pointer_two = self.collection_two.add_pointer(self.collection_two_pointer_project_two,
                                                              auth=Auth(self.user),
                                                              save=True)

        self.collection_two_payload = {
              'data': [
                {'type': 'node_links', 'id': self.collection_two_pointer._id},
                {'type': 'node_links', 'id': self.collection_two_pointer_two._id}
              ]
            }

        self.collection_two_url = '/{}collections/{}/node_links/'.format(API_BASE, self.collection_two._id)

    def test_bulk_delete_node_links_blank_request(self):
        res = self.app.delete_json_api(self.collection_two_url, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_delete_pointer_limits(self):
        res = self.app.delete_json_api(self.collection_two_url, {'data': [self.collection_two_payload['data'][0]] * 101},
                                       auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

    def test_bulk_delete_dict_inside_data(self):
        res = self.app.delete_json_api(self.collection_two_url,
                                       {'data': {'id': self.collection_two._id, 'type': 'node_links'}},
                                       auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Expected a list of items but got type "dict".')

    def test_bulk_delete_pointers_no_type(self):
        payload = {'data': [
            {'id': self.collection_two_pointer._id},
            {'id': self.collection_two_pointer_two._id}
        ]}
        res = self.app.delete_json_api(self.collection_two_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], "/data/type")

    def test_bulk_delete_pointers_incorrect_type(self):
        payload = {'data': [
            {'id': self.collection_two_pointer._id, 'type': 'Incorrect type.'},
            {'id': self.collection_two_pointer_two._id, 'type': 'Incorrect type.'}
        ]}
        res = self.app.delete_json_api(self.collection_two_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)

    def test_bulk_delete_pointers_no_id(self):
        payload = {'data': [
            {'type': 'node_links'},
            {'type': 'node_links'}
        ]}
        res = self.app.delete_json_api(self.collection_two_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], "/data/id")

    def test_bulk_delete_pointers_no_data(self):
        res = self.app.delete_json_api(self.collection_two_url, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must contain array of resource identifier objects.')

    def test_bulk_delete_pointers_payload_is_empty_dict(self):
        res = self.app.delete_json_api(self.collection_two_url, {}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data.')

    def test_bulk_deletes_collection_node_pointers_logged_out(self):
        res = self.app.delete_json_api(self.collection_two_url, self.collection_two_payload, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_bulk_deletes_collection_node_pointers_fails_if_bad_auth(self):
        node_count_before = self.collection_two.nodes_pointer.count()
        res = self.app.delete_json_api(self.collection_two_url, self.collection_two_payload,
                                       auth=self.user_two.auth, expect_errors=True, bulk=True)
        # This is could arguably be a 405, but we don't need to go crazy with status codes
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])
        self.collection_two.reload()
        assert_equal(node_count_before, self.collection_two.nodes_pointer.count())

    def test_bulk_deletes_collection_node_pointers_succeeds_as_owner(self):
        node_count_before = self.collection_two.nodes_pointer.count()
        res = self.app.delete_json_api(self.collection_two_url, self.collection_two_payload, auth=self.user.auth, bulk=True)
        self.collection_two.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 2, self.collection_two.nodes_pointer.count())
        self.collection_two.reload()

    def test_return_bulk_deleted_collection_node_pointer(self):
        res = self.app.delete_json_api(self.collection_two_url, self.collection_two_payload, auth=self.user.auth, bulk=True)
        self.collection_two.reload()  # Update the model to reflect changes made by post request
        assert_equal(res.status_code, 204)

        pointer_url = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection_two._id, self.collection_two_pointer._id)

        #check that deleted pointer can not be returned
        res = self.app.get(pointer_url, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    # Regression test for https://openscience.atlassian.net/browse/OSF-4322
    def test_bulk_delete_link_that_is_not_linked_to_correct_node(self):
        project = ProjectFactory(creator=self.user)
        # The node link belongs to a different project
        res = self.app.delete_json_api(
            self.collection_url, self.collection_two_payload,
            auth=self.user.auth,
            expect_errors=True,
            bulk=True
        )
        assert_equal(res.status_code, 400)
        errors = res.json['errors']
        assert_equal(len(errors), 1)
        assert_equal(errors[0]['detail'], 'Node link does not belong to the requested node.')


class TestCollectionRelationshipNodeLinks(ApiTestCase):

    def setUp(self):
        super(TestCollectionRelationshipNodeLinks, self).setUp()
        self.user = AuthUserFactory()
        self.user2 = AuthUserFactory()
        self.auth = Auth(self.user)
        self.collection = CollectionFactory(creator=self.user)
        self.admin_node = NodeFactory(creator=self.user)
        self.contributor_node = NodeFactory(creator=self.user2)
        self.contributor_node.add_contributor(self.user, auth=Auth(self.user2))
        self.contributor_node.save()
        self.other_node = NodeFactory()
        self.private_node = NodeFactory(creator=self.user)
        self.private_registration = RegistrationFactory(creator=self.user)
        self.public_node = NodeFactory(is_public=True)
        self.public_registration = RegistrationFactory(is_public=True)
        self.collection.add_pointer(self.private_node, auth=self.auth)
        self.collection.add_pointer(self.private_registration, auth=self.auth)
        self.public_collection = CollectionFactory(is_public=True, creator=self.user2)
        self.public_collection.add_pointer(self.private_node, auth=Auth(self.user2))
        self.public_collection.add_pointer(self.private_registration, auth=Auth(self.user2))
        self.public_collection.add_pointer(self.public_node, auth=Auth(self.user2))
        self.public_collection.add_pointer(self.public_registration, auth=Auth(self.user2))
        self.url = '/{}collections/{}/relationships/linked_nodes/'.format(API_BASE, self.collection._id)
        self.reg_url = '/{}collections/{}/relationships/linked_registrations/'.format(API_BASE, self.collection._id)
        self.public_url = '/{}collections/{}/relationships/linked_nodes/'.format(API_BASE, self.public_collection._id)
        self.public_reg_url = '/{}collections/{}/relationships/linked_registrations/'.format(API_BASE, self.public_collection._id)

    def payload(self, node_ids=None):
        node_ids = node_ids or [self.admin_node._id]
        env_linked_nodes = [{"type": "linked_nodes", "id": node_id} for node_id in node_ids]
        return {"data": env_linked_nodes}

    def test_get_relationship_linked_nodes(self):
        res = self.app.get(
            self.url, auth=self.user.auth
        )

        assert_equal(res.status_code, 200)

        assert_in(self.collection.linked_nodes_self_url, res.json['links']['self'])
        assert_in(self.collection.linked_nodes_related_url, res.json['links']['html'])
        assert_equal(res.json['data'][0]['id'], self.private_node._id)

    def test_get_relationship_linked_registrations(self):
        res = self.app.get(
            self.reg_url, auth=self.user.auth
        )

        assert_equal(res.status_code, 200)

        assert_in(self.collection.linked_registrations_self_url, res.json['links']['self'])
        assert_in(self.collection.linked_registrations_related_url, res.json['links']['html'])
        assert_equal(res.json['data'][0]['id'], self.private_registration._id)

    def test_get_public_relationship_linked_nodes_logged_out(self):
        res = self.app.get(self.public_url)

        assert_equal(res.status_code, 200)
        assert_equal(len(res.json['data']), 1)
        assert_equal(res.json['data'][0]['id'], self.public_node._id)

    def test_get_public_relationship_linked_registrations_logged_out(self):
        res = self.app.get(self.public_reg_url)

        assert_equal(res.status_code, 200)
        assert_equal(len(res.json['data']), 1)
        assert_equal(res.json['data'][0]['id'], self.public_registration._id)

    def test_get_public_relationship_linked_nodes_logged_in(self):
        res = self.app.get(self.public_url, auth=self.user.auth)

        assert_equal(res.status_code, 200)
        assert_equal(len(res.json['data']), 2)

    def test_get_public_relationship_linked_registrations_logged_in(self):
        res = self.app.get(self.public_reg_url, auth=self.user.auth)

        assert_equal(res.status_code, 200)
        assert_equal(len(res.json['data']), 2)

    def test_get_private_relationship_linked_nodes_logged_out(self):
        res = self.app.get(self.url, expect_errors=True)

        assert_equal(res.status_code, 401)

    def test_get_private_relationship_linked_registrations_logged_out(self):
        res = self.app.get(self.reg_url, expect_errors=True)

        assert_equal(res.status_code, 401)

    def test_post_contributing_node(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.contributor_node._id]),
            auth=self.user.auth
        )

        assert_equal(res.status_code, 201)

        ids = [data['id'] for data in res.json['data']]
        assert_in(self.contributor_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_post_public_node(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.public_node._id]),
            auth=self.user.auth
        )

        assert_equal(res.status_code, 201)

        ids = [data['id'] for data in res.json['data']]
        assert_in(self.public_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_post_private_node(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.other_node._id]),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        ids = [data['id'] for data in res.json['data']]
        assert_not_in(self.other_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_post_mixed_nodes(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.other_node._id, self.contributor_node._id]),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        ids = [data['id'] for data in res.json['data']]
        assert_not_in(self.other_node._id, ids)
        assert_not_in(self.contributor_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_post_node_already_linked(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.private_node._id]),
            auth=self.user.auth
        )

        assert_equal(res.status_code, 204)

    def test_put_contributing_node(self):
        res = self.app.put_json_api(
            self.url, self.payload([self.contributor_node._id]),
            auth=self.user.auth
        )

        assert_equal(res.status_code, 200)

        ids = [data['id'] for data in res.json['data']]
        assert_in(self.contributor_node._id, ids)
        assert_not_in(self.private_node._id, ids)

    def test_put_private_node(self):
        res = self.app.put_json_api(
            self.url, self.payload([self.other_node._id]),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        ids = [data['id'] for data in res.json['data']]
        assert_not_in(self.other_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_put_mixed_nodes(self):
        res = self.app.put_json_api(
            self.url, self.payload([self.other_node._id, self.contributor_node._id]),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        ids = [data['id'] for data in res.json['data']]
        assert_not_in(self.other_node._id, ids)
        assert_not_in(self.contributor_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_delete_with_put_empty_array(self):
        self.collection.add_pointer(self.admin_node, auth=self.auth)
        payload = self.payload()
        payload['data'].pop()
        res = self.app.put_json_api(
            self.url, payload,
            auth=self.user.auth
        )
        assert_equal(res.status_code, 200)
        assert_equal(res.json['data'], payload['data'])

    def test_delete_one(self):
        self.collection.add_pointer(self.admin_node, auth=self.auth)
        res = self.app.delete_json_api(
            self.url, self.payload([self.private_node._id]),
            auth=self.user.auth,
        )
        assert_equal(res.status_code, 204)

        res = self.app.get(self.url, auth=self.user.auth)

        ids = [data['id'] for data in res.json['data']]
        assert_in(self.admin_node._id, ids)
        assert_not_in(self.private_node._id, ids)

    def test_delete_multiple(self):
        self.collection.add_pointer(self.admin_node, auth=self.auth)
        res = self.app.delete_json_api(
            self.url, self.payload([self.private_node._id, self.admin_node._id]),
            auth=self.user.auth,
        )
        assert_equal(res.status_code, 204)

        res = self.app.get(self.url, auth=self.user.auth)
        assert_equal(res.json['data'], [])

    def test_delete_not_present(self):
        number_of_links = self.collection.linked_nodes.count()
        res = self.app.delete_json_api(
            self.url, self.payload([self.other_node._id]),
            auth=self.user.auth
        )
        assert_equal(res.status_code, 204)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        reg_res = self.app.get(
            self.reg_url, auth=self.user.auth
        )
        assert_equal(len(res.json['data']) + len(reg_res.json['data']), number_of_links)

    def test_access_other_collection(self):
        other_collection = CollectionFactory(creator=self.user2)
        url = '/{}collections/{}/relationships/linked_nodes/'.format(API_BASE, other_collection._id)
        res = self.app.get(
            url, auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

    def test_node_doesnt_exist(self):
        res = self.app.post_json_api(
            self.url, self.payload(['aquarela']),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 404)

    def test_type_mistyped(self):
        res = self.app.post_json_api(
            self.url,
            {
                'data': [{'type': 'not_linked_nodes', 'id': self.contributor_node._id}]
            },
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 409)

    def test_creates_public_linked_node_relationship_logged_out(self):
        res = self.app.post_json_api(
                self.public_url, self.payload([self.public_node._id]),
                expect_errors=True
        )

        assert_equal(res.status_code, 401)

    def test_creates_public_linked_node_relationship_logged_in(self):
        res = self.app.post_json_api(
                self.public_url, self.payload([self.public_node._id]),
                auth=self.user.auth, expect_errors=True
        )

        assert_equal(res.status_code, 403)

    def test_creates_private_linked_node_relationship_logged_out(self):
        res = self.app.post_json_api(
                self.url, self.payload([self.other_node._id]),
                expect_errors=True
        )

        assert_equal(res.status_code, 401)

    def test_put_public_nodes_relationships_logged_out(self):
        res = self.app.put_json_api(
                self.public_url, self.payload([self.public_node._id]),
                expect_errors=True
        )

        assert_equal(res.status_code, 401)

    def test_put_public_nodes_relationships_logged_in(self):
        res = self.app.put_json_api(
                self.public_url, self.payload([self.private_node._id]),
                auth=self.user.auth, expect_errors=True
        )

        assert_equal(res.status_code, 403)

    def test_delete_public_nodes_relationships_logged_out(self):
        res = self.app.delete_json_api(
            self.public_url, self.payload([self.public_node._id]),
            expect_errors=True
        )

        assert_equal(res.status_code, 401)

    def test_delete_public_nodes_relationships_logged_in(self):
        res = self.app.delete_json_api(
                self.public_url, self.payload([self.private_node._id]),
                auth=self.user.auth, expect_errors=True
        )

        assert_equal(res.status_code, 403)

    def test_node_links_and_relationship_represent_same_nodes(self):
        self.collection.add_pointer(self.admin_node, auth=self.auth)
        self.collection.add_pointer(self.contributor_node, auth=self.auth)
        res_relationship = self.app.get(
            self.url, auth=self.user.auth
        )
        res_node_links = self.app.get(
            '/{}collections/{}/node_links/'.format(API_BASE, self.collection._id),
            auth=self.user.auth
        )
        node_links_id = []
        for data in res_node_links.json['data']:
            try:
                node_links_id.append(data['embeds']['target_node']['data']['id'])
            # node_links does not handle registrations correctly, skip them
            except KeyError:
                continue
        relationship_id = [data['id'] for data in res_relationship.json['data']]

        assert_equal(set(node_links_id), set(relationship_id))

    def test_attempt_to_add_collection_to_collection(self):
        other_collection = CollectionFactory(creator=self.user)
        res = self.app.post_json_api(
            self.url, self.payload([other_collection._id]),
            auth=self.user.auth, expect_errors=True
        )

        assert_equal(res.status_code, 404)


class TestCollectionLinkedNodes(ApiTestCase):
    def setUp(self):
        super(TestCollectionLinkedNodes, self).setUp()
        self.user = AuthUserFactory()
        self.auth = Auth(self.user)
        self.collection = CollectionFactory(creator=self.user)
        self.linked_node = NodeFactory(creator=self.user)
        self.linked_node2 = NodeFactory(creator=self.user)
        self.linked_registration = RegistrationFactory(creator=self.user)
        self.linked_registration2 = RegistrationFactory(creator=self.user)
        self.public_node = NodeFactory(is_public=True, creator=self.user)
        self.public_registration = RegistrationFactory(is_public=True, creator=self.user)
        self.collection.add_pointer(self.linked_node, auth=self.auth)
        self.collection.add_pointer(self.linked_node2, auth=self.auth)
        self.collection.add_pointer(self.linked_registration, auth=self.auth)
        self.collection.add_pointer(self.linked_registration2, auth=self.auth)
        self.collection.add_pointer(self.public_node, auth=self.auth)
        self.collection.add_pointer(self.public_registration, auth=self.auth)
        self.collection.save()
        self.url = '/{}collections/{}/linked_nodes/'.format(API_BASE, self.collection._id)
        self.reg_url = '/{}collections/{}/linked_registrations/'.format(API_BASE, self.collection._id)
        self.node_ids = list(self.collection.linked_nodes.values_list('guids___id', flat=True))

    def test_linked_nodes_returns_everything(self):
        res = self.app.get(self.url, auth=self.user.auth)
        reg_res = self.app.get(self.reg_url, auth=self.user.auth)

        assert_equal(res.status_code, 200)
        nodes_returned = [linked_node['id'] for linked_node in res.json['data']]
        registrations_returned = [linked_registration['id'] for linked_registration in reg_res.json['data']]
        assert_equal(len(nodes_returned) + len(registrations_returned), len(self.node_ids))

        for node_returned in nodes_returned:
            assert_in(node_returned, self.node_ids)
        for registration_returned in registrations_returned:
            assert_in(registration_returned, self.node_ids)

    def test_linked_nodes_only_return_viewable_nodes(self):
        user = AuthUserFactory()
        collection = CollectionFactory(creator=user)
        self.linked_node.add_contributor(user, auth=self.auth, save=True)
        self.linked_node2.add_contributor(user, auth=self.auth, save=True)
        self.linked_registration.add_contributor(user, auth=self.auth, save=True)
        self.linked_registration2.add_contributor(user, auth=self.auth, save=True)
        self.public_node.add_contributor(user, auth=self.auth, save=True)
        self.public_registration.add_contributor(user, auth=self.auth, save=True)
        collection.add_pointer(self.linked_node, auth=Auth(user))
        collection.add_pointer(self.linked_node2, auth=Auth(user))
        collection.add_pointer(self.linked_registration, auth=Auth(user))
        collection.add_pointer(self.linked_registration2, auth=Auth(user))
        collection.add_pointer(self.public_node, auth=Auth(user))
        collection.add_pointer(self.public_registration, auth=Auth(user))
        collection.save()

        res = self.app.get(
            '/{}collections/{}/linked_nodes/'.format(API_BASE, collection._id),
            auth=user.auth
        )
        reg_res = self.app.get(
            '/{}collections/{}/linked_registrations/'.format(API_BASE, collection._id),
            auth=user.auth
        )

        assert_equal(res.status_code, 200)
        assert_equal(reg_res.status_code, 200)
        nodes_returned = [linked_node['id'] for linked_node in res.json['data']]
        registrations_returned = [linked_registration['id'] for linked_registration in res.json['data']]
        assert_equal(len(nodes_returned) + len(registrations_returned), len(self.node_ids))

        for node_returned in nodes_returned:
            assert_in(node_returned, self.node_ids)
        for registration_returned in registrations_returned:
            assert_in(registration_returned, self.node_ids)

        # Disconnect contributor_removed so that we don't check in files
        # We can remove this when StoredFileNode is implemented in osf-models
        with disconnected_from_listeners(contributor_removed):
            self.linked_node2.remove_contributor(user, auth=self.auth)
            self.public_node.remove_contributor(user, auth=self.auth)
            self.linked_registration2.remove_contributor(user, auth=self.auth)
            self.public_registration.remove_contributor(user, auth=self.auth)

        res = self.app.get(
            '/{}collections/{}/linked_nodes/'.format(API_BASE, collection._id),
            auth=user.auth
        )
        reg_res = self.app.get(
            '/{}collections/{}/linked_registrations/'.format(API_BASE, collection._id),
            auth=user.auth
        )
        nodes_returned = [linked_node['id'] for linked_node in res.json['data']]
        registrations_returned = [linked_registration['id'] for linked_registration in reg_res.json['data']]
        assert_equal(len(nodes_returned) + len(registrations_returned), len(self.node_ids) - 2)

        assert_in(self.linked_node._id, nodes_returned)
        assert_in(self.public_node._id, nodes_returned)
        assert_in(self.linked_registration._id, registrations_returned)
        assert_in(self.public_registration._id, registrations_returned)
        assert_not_in(self.linked_node2._id, nodes_returned)
        assert_not_in(self.linked_registration2._id, registrations_returned)

    def test_linked_nodes_doesnt_return_deleted_nodes(self):
        self.linked_node.is_deleted = True
        self.linked_node.save()
        res = self.app.get(self.url, auth=self.user.auth)

        self.linked_registration.is_deleted = True
        self.linked_registration.save()
        reg_res = self.app.get(self.reg_url, auth=self.user.auth)

        assert_equal(res.status_code, 200)
        nodes_returned = [linked_node['id'] for linked_node in res.json['data']]
        assert_equal(len(nodes_returned), len(self.node_ids) - 4)

        assert_equal(reg_res.status_code, 200)
        registrations_returned = [linked_registration['id'] for linked_registration in reg_res.json['data']]
        assert_equal(len(registrations_returned), len(self.node_ids) - 4)

        assert_not_in(self.linked_node._id, nodes_returned)
        assert_in(self.linked_node2._id, nodes_returned)
        assert_in(self.public_node._id, nodes_returned)

        assert_not_in(self.linked_registration._id, registrations_returned)
        assert_in(self.linked_registration2._id, registrations_returned)
        assert_in(self.public_registration._id, registrations_returned)

    def test_attempt_to_return_linked_nodes_logged_out(self):
        res = self.app.get(
            self.url, auth=None,
            expect_errors=True
        )

        assert_equal(res.status_code, 401)

from urlparse import urlparse

import pytest
from nose.tools import *  # flake8: noqa

from website.models import Node, NodeLog
from website.util.sanitize import strip_html
from website.util import disconnected_from_listeners
from website.project.signals import contributor_removed
from api.base.settings.defaults import API_BASE
from tests.base import ApiTestCase
from osf_tests.factories import (
    CollectionFactory,
    NodeFactory,
    RegistrationFactory,
    ProjectFactory,
    AuthUserFactory
)
from tests.utils import assert_logs
from framework.auth.core import Auth


def node_url_for(node_id):
    return '/{}nodes/{}/'.format(API_BASE, node_id)


class TestCollectionList(ApiTestCase):
    def setUp(self):
        super(TestCollectionList, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.deleted_one = CollectionFactory(creator=self.user_one, is_deleted=True)
        self.collection_one = CollectionFactory(creator=self.user_one)

        self.url = '/{}collections/'.format(API_BASE)

    def test_user_one_gets_user_one_collections(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        ids = [each['id'] for each in res.json['data']]
        assert_not_in(self.deleted_one._id, ids)
        assert_in(self.collection_one._id, ids)

    def test_user_two_gets_nothing(self):
        res = self.app.get(self.url, auth=self.user_two.auth)
        ids = [each['id'] for each in res.json['data']]
        assert_not_in(self.deleted_one._id, ids)
        assert_not_in(self.collection_one._id, ids)

    def test_unauthorized_gets_nothing(self):
        res = self.app.get(self.url)
        ids = [each['id'] for each in res.json['data']]
        assert_not_in(self.deleted_one._id, ids)
        assert_not_in(self.collection_one._id, ids)


class TestCollectionCreate(ApiTestCase):

    def setUp(self):
        super(TestCollectionCreate, self).setUp()
        self.user_one = AuthUserFactory()
        self.url = '/{}collections/'.format(API_BASE)

        self.title = 'Cool Collection'

        self.user_two = AuthUserFactory()

        self.collection = {
            'data': {
                'type': 'collections',
                'attributes':
                    {
                        'title': self.title,
                    }
            }
        }

    def test_collection_create_invalid_data(self):
        res = self.app.post_json_api(self.url, "Incorrect data", auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], "Malformed request.")

        res = self.app.post_json_api(self.url, ["Incorrect data"], auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], "Malformed request.")

    def test_creates_collection_logged_out(self):
        res = self.app.post_json_api(self.url, self.collection, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_creates_collection_logged_in(self):
        res = self.app.post_json_api(self.url, self.collection, auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        pid = res.json['data']['id']
        assert_equal(res.json['data']['attributes']['title'], self.title)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['type'], 'collections')
        res = self.app.get(self.url+'?filter[title]={}'.format(self.title), auth=self.user_one.auth)
        ids = [each['id'] for each in res.json['data']]
        assert_in(pid, ids)
        collection = Node.load(pid)
        assert_equal(collection.logs.order_by('date').first().action, NodeLog.PROJECT_CREATED)
        assert_equal(collection.title, self.title)

    def test_create_collection_creates_collection_and_sanitizes_html(self):
        title = '<em>Cool</em> <script>alert("even cooler")</script> <strong>Project</strong>'

        res = self.app.post_json_api(self.url, {
            'data': {
                'attributes': {
                    'title': title,
                },
                'type': 'collections'
            }
        }, auth=self.user_one.auth)
        collection_id = res.json['data']['id']
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')

        collection = Node.load(collection_id)
        assert_equal(collection.logs.latest().action, NodeLog.PROJECT_CREATED)
        assert_equal(collection.title, strip_html(title))

    def test_creates_project_no_type(self):
        collection = {
            'data': {
                'attributes': {
                    'title': self.title,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/type')

    def test_creates_project_incorrect_type(self):
        collection = {
            'data': {
                'attributes': {
                    'title': self.title,
                },
                'type': 'Wrong type.'
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 409)
        assert_equal(res.json['errors'][0]['detail'], 'This resource has a type of "collections", but you set the json body\'s type field to "Wrong type.". You probably need to change the type field to match the resource\'s type.')

    def test_creates_collection_properties_not_nested(self):
        project = {
            'data': {
                'title': self.title,
                'type': 'collections'
            }
        }
        res = self.app.post_json_api(self.url, project, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data/attributes.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/attributes')

    def test_create_project_invalid_title(self):
        project = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': 'A' * 201,
                }
            }
        }
        res = self.app.post_json_api(self.url, project, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Title cannot exceed 200 characters.')

    def test_create_bookmark_collection(self):
        collection = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': 'Bookmarks',
                    'bookmarks': True,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.json['data']['attributes']['title'], 'Bookmarks')
        assert_true(res.json['data']['attributes']['bookmarks'])

    def test_cannot_create_multiple_bookmark_collection(self):
        collection = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': 'Bookmarks',
                    'bookmarks': True,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Each user cannot have more than one Bookmark collection.')

    def test_create_bookmark_collection_with_wrong_title(self):
        collection = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': 'Not Bookmarks',
                    'bookmarks': True,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.json['data']['attributes']['title'], 'Bookmarks')
        assert_true(res.json['data']['attributes']['bookmarks'])

    def test_create_bookmark_collection_with_no_title(self):
        collection = {
            'data': {
                'type': 'collections',
                'attributes': {
                    'bookmarks': True,
                }
            }
        }
        res = self.app.post_json_api(self.url, collection, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)

class TestCollectionFiltering(ApiTestCase):

    def setUp(self):
        super(TestCollectionFiltering, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.collection_one = CollectionFactory(title="Collection One", creator=self.user_one)
        self.collection_two = CollectionFactory(title="Collection Two", creator=self.user_one)
        self.collection_three = CollectionFactory(title="Three", creator=self.user_one)

        self.url = "/{}collections/".format(API_BASE)

    def test_get_all_projects_with_no_filter_logged_in(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_in(self.collection_one._id, ids)
        assert_in(self.collection_two._id, ids)
        assert_in(self.collection_three._id, ids)

    def test_get_no_collections_with_no_filter_not_logged_in(self):
        res = self.app.get(self.url)
        node_json = res.json['data']
        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_no_collections_with_no_filter_logged_in_as_wrong_user(self):
        res = self.app.get(self.url, auth=self.user_two.auth)
        node_json = res.json['data']
        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_one_collection_with_exact_filter_logged_in(self):
        url = "/{}collections/?filter[title]=Collection%20One".format(API_BASE)

        res = self.app.get(url, auth=self.user_one.auth)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_one_collection_with_exact_filter_not_logged_in(self):
        url = "/{}collections/?filter[title]=Collection%20One".format(API_BASE)

        res = self.app.get(url)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_some_collections_with_substring_logged_in(self):
        url = "/{}collections/?filter[title]=Two".format(API_BASE)

        res = self.app.get(url, auth=self.user_one.auth)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_get_no_projects_with_substring_not_logged_in(self):
        url = "/{}collections/?filter[title]=Two".format(API_BASE)

        res = self.app.get(url)
        node_json = res.json['data']

        ids = [each['id'] for each in node_json]
        assert_not_in(self.collection_one._id, ids)
        assert_not_in(self.collection_two._id, ids)
        assert_not_in(self.collection_three._id, ids)

    def test_incorrect_filtering_field_logged_in(self):
        url = '/{}collections/?filter[notafield]=bogus'.format(API_BASE)

        res = self.app.get(url, expect_errors=True)
        assert_equal(res.status_code, 400)
        errors = res.json['errors']
        assert_equal(len(errors), 1)
        assert_equal(errors[0]['detail'], "'notafield' is not a valid field for this endpoint.")
        assert_equal(errors[0]['source'], {'parameter': 'filter'})


class TestCollectionDetail(ApiTestCase):
    def setUp(self):
        super(TestCollectionDetail, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()

        self.collection = CollectionFactory(title="Test collection", creator=self.user_one)
        self.url = '/{}collections/{}/'.format(API_BASE, self.collection._id)

    def test_do_not_return_collection_details_logged_out(self):
        res = self.app.get(self.url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_return_collection_details_logged_in(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['attributes']['title'], self.collection.title)
        node_links_url = res.json['data']['relationships']['node_links']['links']['related']['href']
        expected_url = self.url + 'node_links/'
        assert_equal(urlparse(node_links_url).path, expected_url)

    def test_do_not_return_collection_details_logged_in_non_contributor(self):
        res = self.app.get(self.url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_requesting_node_returns_error(self):
        node = NodeFactory(creator=self.user_one)
        res = self.app.get(
            '/{}collections/{}/'.format(API_BASE, node._id),
            auth=self.user_one.auth,
            expect_errors=True
        )
        assert_equal(res.status_code, 404)


class CollectionCRUDTestCase(ApiTestCase):

    def setUp(self):
        super(CollectionCRUDTestCase, self).setUp()
        self.user = AuthUserFactory()
        self.user_two = AuthUserFactory()

        self.title = 'Cool Collection'
        self.new_title = 'Super Cool Collection'

        self.collection = CollectionFactory(title=self.title, creator=self.user)
        self.url = '/{}collections/{}/'.format(API_BASE, self.collection._id)
        self.fake_url = '/{}collections/{}/'.format(API_BASE, '12345')


def make_collection_payload(collection, attributes):
    return {
        'data': {
            'id': collection._id,
            'type': 'collections',
            'attributes': attributes,
        }
    }


class TestCollectionUpdate(CollectionCRUDTestCase):

    def test_node_update_invalid_data(self):
        res = self.app.put_json_api(self.url, "Incorrect data", auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], "Malformed request.")

        res = self.app.patch_json_api(self.url, ["Incorrect data"], auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], "Malformed request.")

    def test_update_collection_properties_not_nested(self):
        res = self.app.put_json_api(self.url, {
            'id': self.collection._id,
            'type': 'collections',
            'title': self.new_title,
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

    def test_update_invalid_id(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': '12345',
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 409)

    def test_update_invalid_type(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collection',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 409)

    def test_update_no_id(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/id')

    def test_update_no_type(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/type')

    def test_cannot_update_collection_logged_out(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.EDITED_TITLE, 'collection')
    def test_update_collection_logged_in(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['attributes']['title'], self.new_title)
        self.collection.reload()
        assert_equal(self.collection.title, self.new_title)

    @assert_logs(NodeLog.EDITED_TITLE, 'collection')
    def test_partial_update_collection_logged_in(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['attributes']['title'], self.new_title)
        self.collection.reload()
        assert_equal(self.collection.title, self.new_title)

    def test_cannot_update_collection_logged_in_but_unauthorized(self):
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.EDITED_TITLE, 'collection')
    def test_update_collection_sanitizes_html_properly(self):
        """Post request should update resource, and any HTML in fields should be stripped"""
        new_title = '<strong>Super</strong><script>alert("even cooler")</script> Cool Project'
        res = self.app.put_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': new_title,
                }
            }
        }, auth=self.user.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        assert_equal(res.json['data']['attributes']['title'], strip_html(new_title))
        self.collection.reload()
        assert_equal(self.collection.title, strip_html(new_title))

    @assert_logs(NodeLog.EDITED_TITLE, 'collection')
    def test_partial_update_collection_updates_project_correctly_and_sanitizes_html(self):
        new_title = 'An <script>alert("even cooler")</script> project'
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collections',
                'attributes': {
                    'title': new_title
                }
            }
        }, auth=self.user.auth)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        self.collection.reload()
        assert_equal(self.collection.title, strip_html(new_title))

    def test_partial_update_collection_logged_in_but_unauthorized(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'attributes': {
                    'title': self.new_title},
                'id': self.collection._id,
                'type': 'nodes',
            }
        }, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_multiple_patch_requests_with_same_title_generates_one_log(self):
        payload = make_collection_payload(collection=self.collection, attributes={'title': self.new_title})
        original_n_logs = self.collection.logs.count()

        for x in range(0, 2):
            res = self.app.patch_json_api(self.url, payload, auth=self.user.auth)
            assert_equal(res.status_code, 200)
            self.collection.reload()
            assert_equal(self.collection.title, self.new_title)
            assert_equal(self.collection.logs.count(), original_n_logs + 1)  # sanity check

    def test_partial_update_invalid_id(self):
        res = self.app.patch_json_api(self.url, {
                'data': {
                    'id': '12345',
                    'type': 'collections',
                    'attributes': {
                        'title': self.new_title,
                    }
                }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 409)

    def test_partial_update_invalid_type(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'collection',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 409)

    def test_partial_update_no_id(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'type': 'collections',
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/id')

    def test_partial_update_no_type(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'attributes': {
                    'title': self.new_title,
                }
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/type')

    # Nothing will be updated here
    def test_partial_update_collection_properties_not_nested(self):
        res = self.app.patch_json_api(self.url, {
            'data': {
                'id': self.collection._id,
                'type': 'nodes',
                'title': self.new_title,
            }
        }, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)

    def test_update_collection_invalid_title(self):
        project = {
            'data': {
                'type': 'collections',
                'id': self.collection._id,
                'attributes': {
                    'title': 'A' * 201,
                    'category': 'project',
                }
            }
        }
        res = self.app.put_json_api(self.url, project, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Title cannot exceed 200 characters.')


class TestCollectionDelete(CollectionCRUDTestCase):

    def test_do_not_delete_collection_unauthenticated(self):
        res = self.app.delete(self.url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert 'detail' in res.json['errors'][0]

    def test_do_not_return_deleted_collection(self):
        self.collection.is_deleted = True
        self.collection.save()
        res = self.app.get(self.url, expect_errors=True)
        assert_equal(res.status_code, 410)
        assert 'detail' in res.json['errors'][0]

    def test_do_not_delete_collection_unauthorized(self):
        res = self.app.delete_json_api(self.url, auth=self.user_two.auth, expect_errors=True)
        self.collection.reload()
        assert_equal(res.status_code, 403)
        assert_equal(self.collection.is_deleted, False)
        assert 'detail' in res.json['errors'][0]

    @assert_logs(NodeLog.PROJECT_DELETED, 'collection')
    def test_delete_collection_authorized(self):
        res = self.app.delete_json_api(self.url, auth=self.user.auth, expect_errors=True)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(self.collection.is_deleted, True)

    def test_cannot_delete_invalid_collection(self):
        res = self.app.delete(self.fake_url, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 404)
        assert 'detail' in res.json['errors'][0]


class TestCollectionNodeLinksList(ApiTestCase):

    def setUp(self):
        super(TestCollectionNodeLinksList, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.collection = CollectionFactory(creator=self.user_one)
        self.project = ProjectFactory(is_public=False, creator=self.user_one)
        self.public_project = ProjectFactory(is_public=True, creator=self.user_two)
        self.registration = RegistrationFactory(is_public=False, creator=self.user_one)
        self.public_registration = RegistrationFactory(is_public=True, creator=self.user_two)
        # self.private_project = ProjectFactory(is_public=False, creator=self.user_two)
        self.collection.add_pointer(self.project, auth=Auth(self.user_one))
        self.collection.add_pointer(self.public_project, auth=Auth(self.user_one))
        self.collection.add_pointer(self.registration, auth=Auth(self.user_one))
        self.collection.add_pointer(self.public_registration, auth=Auth(self.user_one))
        self.url = '/{}collections/{}/node_links/'.format(API_BASE, self.collection._id)

    def test_do_not_return_node_pointers_logged_out(self):
        res = self.app.get(self.url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_return_node_pointers_logged_in(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(len(res_json), 4)
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        first_embedded = res_json[0]['embeds']['target_node']['data']['id']
        second_embedded = res_json[1]['embeds']['target_node']['data']['id']
        # node_links end point does not handle registrations correctly
        third_embedded = res_json[2]['embeds']['target_node']['errors'][0]['detail']
        fourth_embedded = res_json[3]['embeds']['target_node']['errors'][0]['detail']
        assert_items_equal([first_embedded, second_embedded, third_embedded, fourth_embedded], [self.project._id, self.public_project._id, 'Not found.', 'Not found.'])

    def test_return_private_node_pointers_logged_in_non_contributor(self):
        res = self.app.get(self.url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_deleted_links_not_returned(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        res_json = res.json['data']
        original_length = len(res_json)

        self.public_project.is_deleted = True
        self.public_project.save()

        res = self.app.get(self.url, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(len(res_json), original_length - 1)


class TestCollectionNodeLinkCreate(ApiTestCase):

    def setUp(self):
        super(TestCollectionNodeLinkCreate, self).setUp()
        node_link_string = '/{}collections/{}/node_links/'
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()

        self.collection = CollectionFactory(creator=self.user_one)
        self.collection_two = CollectionFactory(creator=self.user_one)

        self.project = ProjectFactory(is_public=False, creator=self.user_one)
        self.public_project = ProjectFactory(is_public=True, creator=self.user_one)
        self.registration = RegistrationFactory(is_public=False, creator=self.user_one)
        self.public_registration = RegistrationFactory(is_public=True, creator=self.user_one)
        self.user_two_private_project = ProjectFactory(is_public=False, creator=self.user_two)
        self.user_two_public_project = ProjectFactory(is_public=True, creator=self.user_two)
        self.user_two_private_registration = RegistrationFactory(is_public=False, creator=self.user_two)
        self.user_two_public_registration = RegistrationFactory(is_public=True, creator=self.user_two)

        self.url = node_link_string.format(API_BASE, self.collection._id)
        self.fake_node_id = 'fakeident'
        self.fake_url = node_link_string.format(API_BASE, self.fake_node_id)

    @staticmethod
    def post_payload(target_node_id, outer_type='node_links', inner_type='nodes'):
        payload = {
            'data': {
                'relationships': {
                    'nodes': {
                        'data': {
                            'id': target_node_id,
                        }
                    }
                }
            }
        }

        if outer_type:
            payload['data']['type'] = outer_type
        if inner_type:
            payload['data']['relationships']['nodes']['data']['type'] = inner_type
        return payload

    def test_does_not_create_link_when_payload_not_nested(self):
        payload = {'data': {'type': 'node_links', 'target_node_id': self.project._id}}
        res = self.app.post_json_api(self.url, payload, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/relationships')
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data/relationships.')

    def test_does_not_create_node_link_logged_out(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.project._id), expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_creates_node_link_to_public_project_logged_in(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.public_project._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded_node_id = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded_node_id, self.public_project._id)

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_creates_node_link_to_public_registration_logged_in(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.public_registration._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        # node_links end point does not handle registrations correctly
        embedded_node_id = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded_node_id, 'Not found.')

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_creates_node_link_to_private_project_logged_in(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.project._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        res_json = res.json['data']
        embedded_node_id = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded_node_id, self.project._id)

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_creates_node_link_to_private_registration_logged_in(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.registration._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        res_json = res.json['data']
        # node_links end point does not handle registrations correctly
        embedded_node_id = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded_node_id, 'Not found.')

    def test_does_not_create_node_link_unauthorized(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.user_two_private_project._id), auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_does_not_create_registration_link_unauthorized(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.user_two_private_registration._id), auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_create_node_link_to_non_contributing_node(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.user_two_public_project._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded_node_id = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded_node_id, self.user_two_public_project._id)

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_create_node_link_to_non_contributing_registration(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.user_two_public_registration._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        # node_links end point does not handle registrations correctly
        embedded_node_id = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded_node_id, 'Not found.')

    def test_create_node_link_to_fake_node(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.fake_node_id), auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_in('detail', res.json['errors'][0])
        assert_in('source', res.json['errors'][0])

    def test_fake_collection_pointing_to_valid_node(self):
        res = self.app.post_json_api(self.fake_url, self.post_payload(self.project._id), auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)
        assert_in('detail', res.json['errors'][0])

        res = self.app.post_json_api(self.fake_url, self.post_payload(self.project._id), auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 404)
        assert_in('detail', res.json['errors'][0])

    def test_create_collection_node_pointer_to_itself(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.collection._id), auth=self.user_one.auth, expect_errors=True)
        res_json = res.json
        assert_equal(res.status_code, 400)
        error = res_json['errors'][0]
        assert_in('detail', error)
        assert_equal("Target Node '{}' not found.".format(self.collection._id), error['detail'])
        assert_in('source', error)
        assert_in('pointer', error['source'])
        assert_equal('/data/relationships/node_links/data/id', error['source']['pointer'])

    @assert_logs(NodeLog.POINTER_CREATED, 'collection')
    def test_create_node_pointer_already_connected(self):
        res = self.app.post_json_api(self.url, self.post_payload(self.project._id), auth=self.user_one.auth)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded_node_id = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded_node_id, self.project._id)

        res = self.app.post_json_api(self.url, self.post_payload(self.project._id), auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        error = res.json['errors'][0]
        assert_in('detail', error)
        assert_equal('Target Node \'{}\' already pointed to by \'{}\'.'.format(self.project._id, self.collection._id), error['detail'])
        assert_in('source', error)
        assert_in('pointer', error['source'])
        assert_equal('/data/relationships/node_links/data/id', error['source']['pointer'])

    def test_create_node_pointer_no_type(self):
        payload = self.post_payload(self.public_project._id, outer_type=None)
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/type')

    def test_create_node_pointer_incorrect_type(self):
        payload = self.post_payload(self.public_project._id, outer_type='wrong_type')
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 409)
        assert_equal(res.json['errors'][0]['detail'], 'This resource has a type of "node_links", but you set the json body\'s type field to "wrong_type". You probably need to change the type field to match the resource\'s type.')


class TestCollectionNodeLinkDetail(ApiTestCase):

    def setUp(self):
        super(TestCollectionNodeLinkDetail, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.collection = CollectionFactory(creator=self.user_one)
        self.project = ProjectFactory(creator=self.user_one, is_public=False)
        self.project_public = ProjectFactory(creator=self.user_one, is_public=False)
        self.registration = RegistrationFactory(creator=self.user_one, is_public=False)
        self.registration_public = RegistrationFactory(creator=self.user_one, is_public=False)
        self.node_link = self.collection.add_pointer(self.project, auth=Auth(self.user_one), save=True)
        self.node_link_public = self.collection.add_pointer(self.project_public, auth=Auth(self.user_one), save=True)
        self.registration_link = self.collection.add_pointer(self.registration, auth=Auth(self.user_one), save=True)
        self.registration_link_public = self.collection.add_pointer(self.registration_public, auth=Auth(self.user_one), save=True)
        self.url = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, self.node_link._id)
        self.url_public = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, self.node_link_public._id)
        self.reg_url = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, self.registration_link._id)
        self.reg_url_public = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, self.registration_link_public._id)

    def test_returns_error_public_node_link_detail_unauthenticated(self):
        res = self.app.get(self.url_public, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_returns_error_public_registration_link_detail_unauthenticated(self):
        res = self.app.get(self.reg_url_public, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_returns_public_node_pointer_detail_authorized(self):
        res = self.app.get(self.url_public, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        embedded = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.project_public._id)

    def test_returns_public_registration_pointer_detail_authorized(self):
        res = self.app.get(self.reg_url_public, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        # node_links end point does not handle registrations correctly
        embedded = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

    def test_returns_error_private_node_link_detail_unauthenticated(self):
        res = self.app.get(self.url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_returns_error_private_registration_link_detail_unauthenticated(self):
        res = self.app.get(self.reg_url, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_returns_private_node_link_detail_authorized(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        embedded = res_json['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.project._id)

    def test_returns_private_registration_link_detail_authorized(self):
        res = self.app.get(self.reg_url, auth=self.user_one.auth)
        res_json = res.json['data']
        assert_equal(res.status_code, 200)
        assert_equal(res.content_type, 'application/vnd.api+json')
        # node_links end point does not handle registrations correctly
        embedded = res_json['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

    def test_returns_error_private_node_link_detail_unauthorized(self):
        res = self.app.get(self.url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_returns_error_private_registration_link_detail_unauthorized(self):
        res = self.app.get(self.reg_url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_self_link_points_to_node_link_detail_url(self):
        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)
        url = res.json['data']['links']['self']
        assert_in(self.url, url)

    def test_delete_node_link_no_permissions_for_target_node(self):
        pointer_project = CollectionFactory(creator=self.user_two)
        pointer = self.collection.add_pointer(pointer_project, auth=Auth(self.user_one), save=True)
        assert_in(pointer.child, self.collection.linked_nodes.all())
        url = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection._id, pointer._id)
        res = self.app.delete_json_api(url, auth=self.user_one.auth)
        assert_equal(res.status_code, 204)

    def test_can_not_delete_collection_public_node_link_unauthenticated(self):
        res = self.app.delete(self.url_public, expect_errors=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0].keys())

    def test_can_not_delete_collection_public_node_pointer_unauthorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.url_public, auth=self.user_two.auth, expect_errors=True)
        # This is could arguably be a 405, but we don't need to go crazy with status codes
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])
        self.collection.reload()
        assert_equal(node_count_before, len(self.collection.nodes_pointer))

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_delete_public_node_pointer_authorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.url_public, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 1, self.collection.nodes_pointer.count())

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_delete_public_registration_pointer_authorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.reg_url_public, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 1, len(self.collection.nodes_pointer))

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_delete_private_node_link_authorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.url, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 1, len(self.collection.nodes_pointer))

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_delete_private_registration_link_authorized(self):
        node_count_before = self.collection.nodes_pointer.count()
        res = self.app.delete(self.reg_url, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 1, self.collection.nodes_pointer.count())

    def test_can_not_delete_collection_private_node_link_unauthorized(self):
        res = self.app.delete(self.url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_can_not_delete_collection_private_registration_link_unauthorized(self):
        res = self.app.delete(self.reg_url, auth=self.user_two.auth, expect_errors=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_can_not_return_deleted_collection_public_node_pointer(self):
        res = self.app.delete(self.url_public, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)

        res = self.app.get(self.url_public, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_can_not_return_deleted_collection_public_registration_pointer(self):
        res = self.app.delete(self.reg_url_public, auth=self.user_one.auth)
        self.collection.reload()
        assert_equal(res.status_code, 204)

        res = self.app.get(self.reg_url_public, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_return_deleted_private_node_pointer(self):
        res = self.app.delete(self.url, auth=self.user_one.auth)
        self.project.reload()
        assert_equal(res.status_code, 204)

        res = self.app.get(self.url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    @assert_logs(NodeLog.POINTER_REMOVED, 'collection')
    def test_return_deleted_private_registration_pointer(self):
        res = self.app.delete(self.reg_url, auth=self.user_one.auth)
        self.project.reload()
        assert_equal(res.status_code, 204)

        res = self.app.get(self.reg_url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    # Regression test for https://openscience.atlassian.net/browse/OSF-4322
    def test_delete_link_that_is_not_linked_to_correct_node(self):
        collection = CollectionFactory(creator=self.user_one)
        # The node link belongs to a different project
        res = self.app.delete(
            '/{}nodes/{}/node_links/{}/'.format(API_BASE, collection._id, self.node_link._id),
            auth=self.user_one.auth,
            expect_errors=True
        )
        assert_equal(res.status_code, 404)
        errors = res.json['errors']
        assert_equal(len(errors), 1)
        assert_equal(errors[0]['detail'], 'Not found.')


class TestReturnDeletedCollection(ApiTestCase):
    def setUp(self):

        super(TestReturnDeletedCollection, self).setUp()
        self.user = AuthUserFactory()
        self.non_contrib = AuthUserFactory()

        self.deleted = CollectionFactory(is_deleted=True, creator=self.user, title='This collection has been deleted')
        self.collection = CollectionFactory(creator=self.user, title='A boring collection')

        self.new_title = 'This deleted node has been edited'
        self.deleted_url = '/{}collections/{}/'.format(API_BASE, self.deleted._id)

    def test_return_deleted_collection(self):
        res = self.app.get(self.deleted_url, expect_errors=True)
        assert_equal(res.status_code, 410)

    def test_edit_deleted_collection(self):
        res = self.app.put_json_api(self.deleted_url, params={'title': self.new_title}, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 410)

    def test_delete_deleted_collection(self):
        res = self.app.delete(self.deleted_url, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 410)


class TestCollectionBulkCreate(ApiTestCase):

    def setUp(self):
        super(TestCollectionBulkCreate, self).setUp()
        self.user_one = AuthUserFactory()
        self.url = '/{}collections/'.format(API_BASE)

        self.title = 'Cool Collection'
        self.title_two = 'Cool Collection, Too'

        self.user_two = AuthUserFactory()

        self.collection = {
                'type': 'collections',
                'attributes': {
                    'title': self.title,
                }
        }

        self.collection_two = {
                'type': 'collections',
                'attributes': {
                    'title': self.title_two,
                }
        }

        self.empty_collection = {'type': 'collections', 'attributes': {'title': "",}}

    def test_bulk_create_collections_blank_request(self):
        res = self.app.post_json_api(self.url, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_create_all_or_nothing(self):
        res = self.app.post_json_api(self.url, {'data': [self.collection, self.empty_collection]}, bulk=True, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_logged_out(self):
        res = self.app.post_json_api(self.url, {'data': [self.collection, self.collection_two]}, bulk=True, expect_errors=True)
        assert_equal(res.status_code, 401)

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_error_formatting(self):
        res = self.app.post_json_api(self.url, {'data': [self.empty_collection, self.empty_collection]}, bulk=True, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 400)
        assert_equal(len(res.json['errors']), 2)
        errors = res.json['errors']
        assert_items_equal([errors[0]['source'], errors[1]['source']],
                           [{'pointer': '/data/0/attributes/title'}, {'pointer': '/data/1/attributes/title'}])
        assert_items_equal([errors[0]['detail'], errors[1]['detail']],
                           ["This field may not be blank.", "This field may not be blank."])

    def test_bulk_create_limits(self):
        node_create_list = {'data': [self.collection] * 101}
        res = self.app.post_json_api(self.url, node_create_list, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_no_type(self):
        payload = {'data': [{"attributes": {'title': self.title}}]}
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/0/type')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_incorrect_type(self):
        payload = {'data': [self.collection, {'type': 'Incorrect type.', 'attributes': {'title': self.title}}]}
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_no_attributes(self):
        payload = {'data': [self.collection, {'type': 'collections', }]}
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)

        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/attributes')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_no_title(self):
        payload = {'data': [self.collection, {'type': 'collections', "attributes": {}}]}
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)

        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/1/attributes/title')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_ugly_payload(self):
        payload = 'sdf;jlasfd'
        res = self.app.post_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 0)

    def test_bulk_create_logged_in(self):
        res = self.app.post_json_api(self.url, {'data': [self.collection, self.collection_two]}, auth=self.user_one.auth, bulk=True)
        assert_equal(res.status_code, 201)
        assert_equal(len(res.json['data']), 2)
        assert_equal(res.json['data'][0]['attributes']['title'], self.collection['attributes']['title'])
        assert_equal(res.json['data'][1]['attributes']['title'], self.collection_two['attributes']['title'])
        assert_equal(res.content_type, 'application/vnd.api+json')

        res = self.app.get(self.url, auth=self.user_one.auth)
        assert_equal(len(res.json['data']), 2)
        id_one = res.json['data'][0]['id']
        id_two = res.json['data'][1]['id']

        res = self.app.delete_json_api(self.url, {'data': [{'id': id_one, 'type': 'collections'},
                                                           {'id': id_two, 'type': 'collections'}]},
                                       auth=self.user_one.auth, bulk=True)
        assert_equal(res.status_code, 204)


class TestCollectionBulkUpdate(ApiTestCase):

    def setUp(self):
        super(TestCollectionBulkUpdate, self).setUp()
        self.user = AuthUserFactory()

        self.title = 'Cool Project'
        self.new_title = 'Super Cool Project'

        self.collection = CollectionFactory(title=self.title,
                                            creator=self.user)

        self.collection_two = CollectionFactory(title=self.title,
                                                creator=self.user)

        self.collection_payload = {
            'data': [
                {
                    'id': self.collection._id,
                    'type': 'collections',
                    'attributes': {
                        'title': self.new_title,
                    }
                },
                {
                    'id': self.collection_two._id,
                    'type': 'collections',
                    'attributes': {
                        'title': self.new_title,
                    }
                }
            ]
        }

        self.url = '/{}collections/'.format(API_BASE)
        self.detail_url_base = '/{}collections/{}/'

        self.empty_payload = {'data': [
            {'id': self.collection._id, 'type': 'collections', 'attributes': {'title': "",}},
            {'id': self.collection_two._id, 'type': 'collections', 'attributes': {'title': "",}}
        ]}

    def test_bulk_update_nodes_blank_request(self):
        res = self.app.put_json_api(self.url, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_update_blank_but_not_empty_title(self):
        payload = {
            "data": [
                {
                  "id": self.collection._id,
                  "type": "collections",
                  "attributes": {
                    "title": "This shouldn't update."
                  }
                },
                {
                  "id": self.collection_two._id,
                  "type": "collections",
                  "attributes": {
                    "title": " "
                  }
                }
              ]
            }
        url = self.detail_url_base.format(API_BASE, self.collection._id)
        res = self.app.put_json_api(self.url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_collections_one_not_found(self):
        empty_payload = {'data': [
            {
                'id': '12345',
                'type': 'collections',
                'attributes': {
                    'title': self.new_title
                }
            }, self.collection_payload['data'][0]
        ]}

        res = self.app.put_json_api(self.url, empty_payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Could not find all objects to update.')

        url = self.detail_url_base.format(API_BASE, self.collection._id)
        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_collections_logged_out(self):
        res = self.app.put_json_api(self.url, self.collection_payload, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 401)
        assert_equal(res.json['errors'][0]['detail'], "Authentication credentials were not provided.")

        url = self.detail_url_base.format(API_BASE, self.collection._id)
        url_two = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

        res = self.app.get(url_two, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_collections_logged_in(self):
        res = self.app.put_json_api(self.url, self.collection_payload, auth=self.user.auth, bulk=True)
        assert_equal(res.status_code, 200)
        assert_equal({self.collection._id, self.collection_two._id},
                     {res.json['data'][0]['id'], res.json['data'][1]['id']})
        assert_equal(res.json['data'][0]['attributes']['title'], self.new_title)
        assert_equal(res.json['data'][1]['attributes']['title'], self.new_title)

    def test_bulk_update_collections_send_dictionary_not_list(self):
        res = self.app.put_json_api(self.url, {'data': {'id': self.collection._id, 'type': 'nodes',
                                                        'attributes': {'title': self.new_title}}},
                                    auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Expected a list of items but got type "dict".')

    def test_bulk_update_error_formatting(self):
        res = self.app.put_json_api(self.url, self.empty_payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(len(res.json['errors']), 2)
        errors = res.json['errors']
        assert_items_equal([errors[0]['source'], errors[1]['source']],
                           [{'pointer': '/data/0/attributes/title'}, {'pointer': '/data/1/attributes/title'}])
        assert_items_equal([errors[0]['detail'], errors[1]['detail']],
                           ['This field may not be blank.'] * 2)

    def test_bulk_update_id_not_supplied(self):
        res = self.app.put_json_api(self.url, {'data': [self.collection_payload['data'][1], {'type': 'collections', 'attributes':
            {'title': self.new_title}}]}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(len(res.json['errors']), 1)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/1/id')
        assert_equal(res.json['errors'][0]['detail'], "This field may not be null.")

        url = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_type_not_supplied(self):
        res = self.app.put_json_api(self.url, {'data': [self.collection_payload['data'][1], {'id': self.collection._id, 'attributes':
            {'title': self.new_title}}]}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(len(res.json['errors']), 1)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/1/type')
        assert_equal(res.json['errors'][0]['detail'], "This field may not be null.")

        url = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_incorrect_type(self):
        res = self.app.put_json_api(self.url, {'data': [self.collection_payload['data'][1], {'id': self.collection._id, 'type': 'Incorrect', 'attributes':
            {'title': self.new_title}}]}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)

        url = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)

    def test_bulk_update_limits(self):
        node_update_list = {'data': [self.collection_payload['data'][0]] * 101}
        res = self.app.put_json_api(self.url, node_update_list, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

    def test_bulk_update_no_title(self):
        new_payload = {'id': self.collection._id, 'type': 'collections', 'attributes': {}}
        res = self.app.put_json_api(self.url, {'data': [self.collection_payload['data'][1], new_payload]}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

        url = self.detail_url_base.format(API_BASE, self.collection_two._id)

        res = self.app.get(url, auth=self.user.auth)
        assert_equal(res.json['data']['attributes']['title'], self.title)


class TestNodeBulkDelete(ApiTestCase):

    def setUp(self):
        super(TestNodeBulkDelete, self).setUp()
        self.user_one = AuthUserFactory()
        self.user_two = AuthUserFactory()
        self.collection_one = CollectionFactory(title="Collection One", creator=self.user_one)
        self.collection_two = CollectionFactory(title="Collection Two", creator=self.user_one)
        self.collection_three = CollectionFactory(title="Collection Three", creator=self.user_one)
        self.collection_user_two = CollectionFactory(title="Collection User Two", creator=self.user_two)

        self.url = "/{}collections/".format(API_BASE)
        self.project_one_url = '/{}collections/{}/'.format(API_BASE, self.collection_one._id)
        self.project_two_url = '/{}collections/{}/'.format(API_BASE, self.collection_two._id)
        self.private_project_url = "/{}collections/{}/".format(API_BASE, self.collection_three._id)

        self.payload_one = {'data': [{'id': self.collection_one._id, 'type': 'collections'},
                                     {'id': self.collection_two._id, 'type': 'collections'}]}
        self.payload_two = {'data': [{'id': self.collection_three._id, 'type': 'collections'}]}

    def test_bulk_delete_nodes_blank_request(self):
        res = self.app.delete_json_api(self.url, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_delete_no_type(self):
        payload = {'data': [
            {'id': self.collection_one._id},
            {'id': self.collection_two._id}
        ]}
        res = self.app.delete_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /type.')

    def test_bulk_delete_no_id(self):
        payload = {'data': [
            {'type': 'collections'}
        ]}
        res = self.app.delete_json_api(self.url, payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data/id.')

    def test_bulk_delete_dict_inside_data(self):
        res = self.app.delete_json_api(self.url, {'data': {'id': self.collection_one._id, 'type': 'collections'}},
                                       auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Expected a list of items but got type "dict".')

    def test_bulk_delete_invalid_type(self):
        res = self.app.delete_json_api(self.url, {'data': [{'type': 'Wrong type', 'id': self.collection_one._id}]},
                                       auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)

    def test_bulk_delete_collections_logged_in(self):
        res = self.app.delete_json_api(self.url, self.payload_one, auth=self.user_one.auth, bulk=True)
        assert_equal(res.status_code, 204)

        res = self.app.get(self.project_one_url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 410)
        self.collection_one.reload()
        self.collection_two.reload()

    def test_bulk_delete_collections_logged_out(self):
        res = self.app.delete_json_api(self.url, self.payload_one, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 401)
        assert_equal(res.json['errors'][0]['detail'], 'Authentication credentials were not provided.')

        res = self.app.get(self.project_one_url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 200)

        res = self.app.get(self.project_two_url, auth=self.user_one.auth, expect_errors=True)
        assert_equal(res.status_code, 200)

    def test_bulk_delete_collections_logged_in_non_contributor(self):
        res = self.app.delete_json_api(self.url, self.payload_two,
                                       auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 403)
        assert_equal(res.json['errors'][0]['detail'], 'You do not have permission to perform this action.')

        res = self.app.get(self.private_project_url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)

    def test_bulk_delete_all_or_nothing(self):
        new_payload = {'data': [{'id': self.collection_three._id, 'type': 'collections'}, {'id': self.collection_user_two
            ._id, 'type': 'collections'}]}
        res = self.app.delete_json_api(self.url, new_payload,
                                       auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 403)
        assert_equal(res.json['errors'][0]['detail'], 'You do not have permission to perform this action.')

        res = self.app.get(self.private_project_url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)

        url = "/{}collections/{}/".format(API_BASE, self.collection_user_two._id)
        res = self.app.get(url, auth=self.user_two.auth)
        assert_equal(res.status_code, 200)

    def test_bulk_delete_limits(self):
        new_payload = {'data': [{'id': self.collection_three._id, 'type': 'nodes'}] * 101}
        res = self.app.delete_json_api(self.url, new_payload,
                                       auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

    def test_bulk_delete_invalid_payload_one_not_found(self):
        new_payload = {'data': [self.payload_one['data'][0], {'id': '12345', 'type': 'collections'}]}
        res = self.app.delete_json_api(self.url, new_payload, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Could not find all objects to delete.')

        res = self.app.get(self.project_one_url, auth=self.user_one.auth)
        assert_equal(res.status_code, 200)

    def test_bulk_delete_no_payload(self):
        res = self.app.delete_json_api(self.url, auth=self.user_one.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)


class TestCollectionLinksBulkCreate(ApiTestCase):

    def setUp(self):
        super(TestCollectionLinksBulkCreate, self).setUp()
        self.user = AuthUserFactory()

        self.collection_one = CollectionFactory(is_public=False, creator=self.user)
        self.private_pointer_project = ProjectFactory(is_public=False, creator=self.user)
        self.private_pointer_project_two = ProjectFactory(is_public=False, creator=self.user)
        self.private_pointer_registration = RegistrationFactory(is_public=False, creator=self.user)
        self.private_pointer_registration_two = RegistrationFactory(is_public=False, creator=self.user)

        self.collection_url = '/{}collections/{}/node_links/'.format(API_BASE, self.collection_one._id)

        self.collection_payload = {
            'data': [{
                "type": "node_links",
                "relationships": {
                    'target_node': {
                        'data': {
                            "id": self.private_pointer_project._id,
                            "type": 'nodes'
                        }
                    }
                }
            },
            {
                "type": "node_links",
                "relationships": {
                    'target_node': {
                        'data': {
                            "id": self.private_pointer_project_two._id,
                            "type": 'nodes'
                        }
                    }
                }
            },
            {
                "type": "node_links",
                "relationships": {
                    'target_node': {
                        'data': {
                            "id": self.private_pointer_registration._id,
                            "type": 'nodes'
                        }
                    }
                }
            },
            {
                "type": "node_links",
                "relationships": {
                    'target_node': {
                        'data': {
                            "id": self.private_pointer_registration_two._id,
                            "type": 'nodes'
                        }
                    }
                }
            }]
        }

        self.public_pointer_project = ProjectFactory(is_public=True, creator=self.user)
        self.public_pointer_project_two = ProjectFactory(is_public=True, creator=self.user)

        self.user_two = AuthUserFactory()
        self.user_two_collection = CollectionFactory(creator=self.user_two)
        self.user_two_project = ProjectFactory(is_public=True, creator=self.user_two)
        self.user_two_registration = RegistrationFactory(is_public=True, creator=self.user_two)
        self.user_two_url = '/{}collections/{}/node_links/'.format(API_BASE, self.user_two_collection._id)
        self.user_two_payload = {'data': [{
            'type': 'node_links',
            'relationships': {
                'nodes': {
                     'data': {
                         'id': self.user_two_project._id,
                         'type': 'nodes'
                     }
                }
            }
        },
        { 'type': 'node_links',
            'relationships': {
                'nodes': {
                     'data': {
                         'id': self.user_two_registration._id,
                         'type': 'nodes'
                     }
                }
            }
        }
    ]}

    def test_bulk_create_node_links_blank_request(self):
        res = self.app.post_json_api(self.collection_url, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_creates_pointers_limits(self):
        payload = {'data': [self.collection_payload['data'][0]] * 101}
        res = self.app.post_json_api(self.collection_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

        res = self.app.get(self.collection_url, auth=self.user.auth)
        assert_equal(res.json['data'], [])

    def test_bulk_creates_project_target_not_nested(self):
        payload = {'data': [{'type': 'node_links', 'target_node_id': self.private_pointer_project._id}]}
        res = self.app.post_json_api(self.collection_url, payload, auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/relationships')
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data/relationships.')

    def test_bulk_creates_collection_node_pointers_logged_out(self):
        res = self.app.post_json_api(self.collection_url, self.collection_payload, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

        res = self.app.get(self.collection_url, auth=self.user.auth)
        assert_equal(res.json['data'], [])

    def test_bulk_creates_collection_node_pointer_logged_in_non_contrib(self):
        res = self.app.post_json_api(self.collection_url, self.collection_payload,
                                     auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 403)

    def test_bulk_creates_collection_node_pointer_logged_in_contrib(self):
        res = self.app.post_json_api(self.collection_url, self.collection_payload, auth=self.user.auth, bulk=True)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded = res_json[0]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.private_pointer_project._id)

        embedded = res_json[1]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.private_pointer_project_two._id)

        # linked_node endpoint can create linked_registrations,
        # but will not return a valid embed
        embedded = res_json[2]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

        embedded = res_json[3]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

    def test_bulk_creates_node_pointers_collection_to_non_contributing_node(self):
        res = self.app.post_json_api(self.collection_url, self.user_two_payload, auth=self.user.auth, bulk=True)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded = res_json[0]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.user_two_project._id)
        embedded = res_json[1]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

        res = self.app.get(self.collection_url, auth=self.user.auth)
        res_json = res.json['data']
        embedded = res_json[0]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.user_two_project._id)
        embedded = res_json[1]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

    def test_bulk_creates_pointers_non_contributing_node_to_fake_node(self):
        fake_payload = {'data': [{'type': 'node_links', 'relationships': {'nodes': {'data': {'id': 'fdxlq', 'type': 'nodes'}}}}]}

        res = self.app.post_json_api(self.collection_url, fake_payload,
                                     auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])

    def test_bulk_creates_pointers_contributing_node_to_fake_node(self):
        fake_payload = {'data': [{'type': 'node_links', 'relationships': {'nodes': {'data': {'id': 'fdxlq', 'type': 'nodes'}}}}]}

        res = self.app.post_json_api(self.collection_url, fake_payload,
                                     auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_in('detail', res.json['errors'][0])

    def test_bulk_creates_fake_nodes_pointing_to_contributing_node(self):
        fake_url = '/{}collections/{}/node_links/'.format(API_BASE, 'fdxlq')

        res = self.app.post_json_api(fake_url, self.collection_payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 404)
        assert_in('detail', res.json['errors'][0])

        res = self.app.post_json_api(fake_url, self.collection_payload, auth=self.user_two.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 404)
        assert_in('detail', res.json['errors'][0])

    def test_bulk_creates_node_pointer_already_connected(self):
        res = self.app.post_json_api(self.collection_url, self.collection_payload, auth=self.user.auth, bulk=True)
        assert_equal(res.status_code, 201)
        assert_equal(res.content_type, 'application/vnd.api+json')
        res_json = res.json['data']
        embedded = res_json[0]['embeds']['target_node']['data']['id']
        assert_equal(embedded, self.private_pointer_project._id)

        embedded_two = res_json[1]['embeds']['target_node']['data']['id']
        assert_equal(embedded_two, self.private_pointer_project_two._id)

        # linked_node endpoint can create linked_registrations,
        # but will not return a valid embed
        embedded = res_json[2]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

        embedded = res_json[3]['embeds']['target_node']['errors'][0]['detail']
        assert_equal(embedded, 'Not found.')

        res = self.app.post_json_api(self.collection_url, self.collection_payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_in("Target Node '{}' already pointed to by '{}'.".format(self.private_pointer_project._id, self.collection_one._id), res.json['errors'][0]['detail'])

    def test_bulk_creates_node_pointer_no_type(self):
        payload = {'data': [{'relationships': {'nodes': {'data': {'type': 'nodes', 'id': self.user_two_collection._id}}}}]}
        res = self.app.post_json_api(self.collection_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'This field may not be null.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data/0/type')

    def test_bulk_creates_node_pointer_incorrect_type(self):
        payload = {'data': [{'type': 'Wrong type.', 'relationships': {'nodes': {'data': {'type': 'nodes', 'id': self.user_two_collection._id}}}}]}
        res = self.app.post_json_api(self.collection_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)
        assert_equal(res.json['errors'][0]['detail'], 'This resource has a type of "node_links", but you set the json body\'s type field to "Wrong type.". You probably need to change the type field to match the resource\'s type.')


class TestBulkDeleteCollectionNodeLinks(ApiTestCase):

    def setUp(self):
        super(TestBulkDeleteCollectionNodeLinks, self).setUp()
        self.user = AuthUserFactory()
        self.collection = CollectionFactory(creator=self.user)
        self.pointer_project = ProjectFactory(creator=self.user, is_public=True)
        self.pointer_project_two = ProjectFactory(creator=self.user, is_public=True)

        self.pointer = self.collection.add_pointer(self.pointer_project, auth=Auth(self.user), save=True)
        self.pointer_two = self.collection.add_pointer(self.pointer_project_two, auth=Auth(self.user), save=True)

        self.collection_payload = {
              "data": [
                {"type": "node_links", "id": self.pointer._id},
                {"type": "node_links", "id": self.pointer_two._id}
              ]
            }

        self.collection_url = '/{}collections/{}/node_links/'.format(API_BASE, self.collection._id)

        self.user_two = AuthUserFactory()

        self.collection_two = CollectionFactory(creator=self.user)
        self.collection_two_pointer_project = ProjectFactory(is_public=True, creator=self.user)
        self.collection_two_pointer_project_two = ProjectFactory(is_public=True, creator=self.user)

        self.collection_two_pointer = self.collection_two.add_pointer(self.collection_two_pointer_project,
                                                              auth=Auth(self.user),
                                                              save=True)
        self.collection_two_pointer_two = self.collection_two.add_pointer(self.collection_two_pointer_project_two,
                                                              auth=Auth(self.user),
                                                              save=True)

        self.collection_two_payload = {
              'data': [
                {'type': 'node_links', 'id': self.collection_two_pointer._id},
                {'type': 'node_links', 'id': self.collection_two_pointer_two._id}
              ]
            }

        self.collection_two_url = '/{}collections/{}/node_links/'.format(API_BASE, self.collection_two._id)

    def test_bulk_delete_node_links_blank_request(self):
        res = self.app.delete_json_api(self.collection_two_url, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)

    def test_bulk_delete_pointer_limits(self):
        res = self.app.delete_json_api(self.collection_two_url, {'data': [self.collection_two_payload['data'][0]] * 101},
                                       auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Bulk operation limit is 100, got 101.')
        assert_equal(res.json['errors'][0]['source']['pointer'], '/data')

    def test_bulk_delete_dict_inside_data(self):
        res = self.app.delete_json_api(self.collection_two_url,
                                       {'data': {'id': self.collection_two._id, 'type': 'node_links'}},
                                       auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Expected a list of items but got type "dict".')

    def test_bulk_delete_pointers_no_type(self):
        payload = {'data': [
            {'id': self.collection_two_pointer._id},
            {'id': self.collection_two_pointer_two._id}
        ]}
        res = self.app.delete_json_api(self.collection_two_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], "/data/type")

    def test_bulk_delete_pointers_incorrect_type(self):
        payload = {'data': [
            {'id': self.collection_two_pointer._id, 'type': 'Incorrect type.'},
            {'id': self.collection_two_pointer_two._id, 'type': 'Incorrect type.'}
        ]}
        res = self.app.delete_json_api(self.collection_two_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 409)

    def test_bulk_delete_pointers_no_id(self):
        payload = {'data': [
            {'type': 'node_links'},
            {'type': 'node_links'}
        ]}
        res = self.app.delete_json_api(self.collection_two_url, payload, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['source']['pointer'], "/data/id")

    def test_bulk_delete_pointers_no_data(self):
        res = self.app.delete_json_api(self.collection_two_url, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must contain array of resource identifier objects.')

    def test_bulk_delete_pointers_payload_is_empty_dict(self):
        res = self.app.delete_json_api(self.collection_two_url, {}, auth=self.user.auth, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 400)
        assert_equal(res.json['errors'][0]['detail'], 'Request must include /data.')

    def test_bulk_deletes_collection_node_pointers_logged_out(self):
        res = self.app.delete_json_api(self.collection_two_url, self.collection_two_payload, expect_errors=True, bulk=True)
        assert_equal(res.status_code, 401)
        assert_in('detail', res.json['errors'][0])

    def test_bulk_deletes_collection_node_pointers_fails_if_bad_auth(self):
        node_count_before = self.collection_two.nodes_pointer.count()
        res = self.app.delete_json_api(self.collection_two_url, self.collection_two_payload,
                                       auth=self.user_two.auth, expect_errors=True, bulk=True)
        # This is could arguably be a 405, but we don't need to go crazy with status codes
        assert_equal(res.status_code, 403)
        assert_in('detail', res.json['errors'][0])
        self.collection_two.reload()
        assert_equal(node_count_before, self.collection_two.nodes_pointer.count())

    def test_bulk_deletes_collection_node_pointers_succeeds_as_owner(self):
        node_count_before = self.collection_two.nodes_pointer.count()
        res = self.app.delete_json_api(self.collection_two_url, self.collection_two_payload, auth=self.user.auth, bulk=True)
        self.collection_two.reload()
        assert_equal(res.status_code, 204)
        assert_equal(node_count_before - 2, self.collection_two.nodes_pointer.count())
        self.collection_two.reload()

    def test_return_bulk_deleted_collection_node_pointer(self):
        res = self.app.delete_json_api(self.collection_two_url, self.collection_two_payload, auth=self.user.auth, bulk=True)
        self.collection_two.reload()  # Update the model to reflect changes made by post request
        assert_equal(res.status_code, 204)

        pointer_url = '/{}collections/{}/node_links/{}/'.format(API_BASE, self.collection_two._id, self.collection_two_pointer._id)

        #check that deleted pointer can not be returned
        res = self.app.get(pointer_url, auth=self.user.auth, expect_errors=True)
        assert_equal(res.status_code, 404)

    # Regression test for https://openscience.atlassian.net/browse/OSF-4322
    def test_bulk_delete_link_that_is_not_linked_to_correct_node(self):
        project = ProjectFactory(creator=self.user)
        # The node link belongs to a different project
        res = self.app.delete_json_api(
            self.collection_url, self.collection_two_payload,
            auth=self.user.auth,
            expect_errors=True,
            bulk=True
        )
        assert_equal(res.status_code, 400)
        errors = res.json['errors']
        assert_equal(len(errors), 1)
        assert_equal(errors[0]['detail'], 'Node link does not belong to the requested node.')


class TestCollectionRelationshipNodeLinks(ApiTestCase):

    def setUp(self):
        super(TestCollectionRelationshipNodeLinks, self).setUp()
        self.user = AuthUserFactory()
        self.user2 = AuthUserFactory()
        self.auth = Auth(self.user)
        self.collection = CollectionFactory(creator=self.user)
        self.admin_node = NodeFactory(creator=self.user)
        self.contributor_node = NodeFactory(creator=self.user2)
        self.contributor_node.add_contributor(self.user, auth=Auth(self.user2))
        self.contributor_node.save()
        self.other_node = NodeFactory()
        self.private_node = NodeFactory(creator=self.user)
        self.private_registration = RegistrationFactory(creator=self.user)
        self.public_node = NodeFactory(is_public=True)
        self.public_registration = RegistrationFactory(is_public=True)
        self.collection.add_pointer(self.private_node, auth=self.auth)
        self.collection.add_pointer(self.private_registration, auth=self.auth)
        self.public_collection = CollectionFactory(is_public=True, creator=self.user2)
        self.public_collection.add_pointer(self.private_node, auth=Auth(self.user2))
        self.public_collection.add_pointer(self.private_registration, auth=Auth(self.user2))
        self.public_collection.add_pointer(self.public_node, auth=Auth(self.user2))
        self.public_collection.add_pointer(self.public_registration, auth=Auth(self.user2))
        self.url = '/{}collections/{}/relationships/linked_nodes/'.format(API_BASE, self.collection._id)
        self.reg_url = '/{}collections/{}/relationships/linked_registrations/'.format(API_BASE, self.collection._id)
        self.public_url = '/{}collections/{}/relationships/linked_nodes/'.format(API_BASE, self.public_collection._id)
        self.public_reg_url = '/{}collections/{}/relationships/linked_registrations/'.format(API_BASE, self.public_collection._id)

    def payload(self, node_ids=None):
        node_ids = node_ids or [self.admin_node._id]
        env_linked_nodes = [{"type": "linked_nodes", "id": node_id} for node_id in node_ids]
        return {"data": env_linked_nodes}

    def test_get_relationship_linked_nodes(self):
        res = self.app.get(
            self.url, auth=self.user.auth
        )

        assert_equal(res.status_code, 200)

        assert_in(self.collection.linked_nodes_self_url, res.json['links']['self'])
        assert_in(self.collection.linked_nodes_related_url, res.json['links']['html'])
        assert_equal(res.json['data'][0]['id'], self.private_node._id)

    def test_get_relationship_linked_registrations(self):
        res = self.app.get(
            self.reg_url, auth=self.user.auth
        )

        assert_equal(res.status_code, 200)

        assert_in(self.collection.linked_registrations_self_url, res.json['links']['self'])
        assert_in(self.collection.linked_registrations_related_url, res.json['links']['html'])
        assert_equal(res.json['data'][0]['id'], self.private_registration._id)

    def test_get_public_relationship_linked_nodes_logged_out(self):
        res = self.app.get(self.public_url)

        assert_equal(res.status_code, 200)
        assert_equal(len(res.json['data']), 1)
        assert_equal(res.json['data'][0]['id'], self.public_node._id)

    def test_get_public_relationship_linked_registrations_logged_out(self):
        res = self.app.get(self.public_reg_url)

        assert_equal(res.status_code, 200)
        assert_equal(len(res.json['data']), 1)
        assert_equal(res.json['data'][0]['id'], self.public_registration._id)

    def test_get_public_relationship_linked_nodes_logged_in(self):
        res = self.app.get(self.public_url, auth=self.user.auth)

        assert_equal(res.status_code, 200)
        assert_equal(len(res.json['data']), 2)

    def test_get_public_relationship_linked_registrations_logged_in(self):
        res = self.app.get(self.public_reg_url, auth=self.user.auth)

        assert_equal(res.status_code, 200)
        assert_equal(len(res.json['data']), 2)

    def test_get_private_relationship_linked_nodes_logged_out(self):
        res = self.app.get(self.url, expect_errors=True)

        assert_equal(res.status_code, 401)

    def test_get_private_relationship_linked_registrations_logged_out(self):
        res = self.app.get(self.reg_url, expect_errors=True)

        assert_equal(res.status_code, 401)

    def test_post_contributing_node(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.contributor_node._id]),
            auth=self.user.auth
        )

        assert_equal(res.status_code, 201)

        ids = [data['id'] for data in res.json['data']]
        assert_in(self.contributor_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_post_public_node(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.public_node._id]),
            auth=self.user.auth
        )

        assert_equal(res.status_code, 201)

        ids = [data['id'] for data in res.json['data']]
        assert_in(self.public_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_post_private_node(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.other_node._id]),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        ids = [data['id'] for data in res.json['data']]
        assert_not_in(self.other_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_post_mixed_nodes(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.other_node._id, self.contributor_node._id]),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        ids = [data['id'] for data in res.json['data']]
        assert_not_in(self.other_node._id, ids)
        assert_not_in(self.contributor_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_post_node_already_linked(self):
        res = self.app.post_json_api(
            self.url, self.payload([self.private_node._id]),
            auth=self.user.auth
        )

        assert_equal(res.status_code, 204)

    def test_put_contributing_node(self):
        res = self.app.put_json_api(
            self.url, self.payload([self.contributor_node._id]),
            auth=self.user.auth
        )

        assert_equal(res.status_code, 200)

        ids = [data['id'] for data in res.json['data']]
        assert_in(self.contributor_node._id, ids)
        assert_not_in(self.private_node._id, ids)

    def test_put_private_node(self):
        res = self.app.put_json_api(
            self.url, self.payload([self.other_node._id]),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        ids = [data['id'] for data in res.json['data']]
        assert_not_in(self.other_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_put_mixed_nodes(self):
        res = self.app.put_json_api(
            self.url, self.payload([self.other_node._id, self.contributor_node._id]),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        ids = [data['id'] for data in res.json['data']]
        assert_not_in(self.other_node._id, ids)
        assert_not_in(self.contributor_node._id, ids)
        assert_in(self.private_node._id, ids)

    def test_delete_with_put_empty_array(self):
        self.collection.add_pointer(self.admin_node, auth=self.auth)
        payload = self.payload()
        payload['data'].pop()
        res = self.app.put_json_api(
            self.url, payload,
            auth=self.user.auth
        )
        assert_equal(res.status_code, 200)
        assert_equal(res.json['data'], payload['data'])

    def test_delete_one(self):
        self.collection.add_pointer(self.admin_node, auth=self.auth)
        res = self.app.delete_json_api(
            self.url, self.payload([self.private_node._id]),
            auth=self.user.auth,
        )
        assert_equal(res.status_code, 204)

        res = self.app.get(self.url, auth=self.user.auth)

        ids = [data['id'] for data in res.json['data']]
        assert_in(self.admin_node._id, ids)
        assert_not_in(self.private_node._id, ids)

    def test_delete_multiple(self):
        self.collection.add_pointer(self.admin_node, auth=self.auth)
        res = self.app.delete_json_api(
            self.url, self.payload([self.private_node._id, self.admin_node._id]),
            auth=self.user.auth,
        )
        assert_equal(res.status_code, 204)

        res = self.app.get(self.url, auth=self.user.auth)
        assert_equal(res.json['data'], [])

    def test_delete_not_present(self):
        number_of_links = self.collection.linked_nodes.count()
        res = self.app.delete_json_api(
            self.url, self.payload([self.other_node._id]),
            auth=self.user.auth
        )
        assert_equal(res.status_code, 204)

        res = self.app.get(
            self.url, auth=self.user.auth
        )

        reg_res = self.app.get(
            self.reg_url, auth=self.user.auth
        )
        assert_equal(len(res.json['data']) + len(reg_res.json['data']), number_of_links)

    def test_access_other_collection(self):
        other_collection = CollectionFactory(creator=self.user2)
        url = '/{}collections/{}/relationships/linked_nodes/'.format(API_BASE, other_collection._id)
        res = self.app.get(
            url, auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 403)

    def test_node_doesnt_exist(self):
        res = self.app.post_json_api(
            self.url, self.payload(['aquarela']),
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 404)

    def test_type_mistyped(self):
        res = self.app.post_json_api(
            self.url,
            {
                'data': [{'type': 'not_linked_nodes', 'id': self.contributor_node._id}]
            },
            auth=self.user.auth,
            expect_errors=True
        )

        assert_equal(res.status_code, 409)

    def test_creates_public_linked_node_relationship_logged_out(self):
        res = self.app.post_json_api(
                self.public_url, self.payload([self.public_node._id]),
                expect_errors=True
        )

        assert_equal(res.status_code, 401)

    def test_creates_public_linked_node_relationship_logged_in(self):
        res = self.app.post_json_api(
                self.public_url, self.payload([self.public_node._id]),
                auth=self.user.auth, expect_errors=True
        )

        assert_equal(res.status_code, 403)

    def test_creates_private_linked_node_relationship_logged_out(self):
        res = self.app.post_json_api(
                self.url, self.payload([self.other_node._id]),
                expect_errors=True
        )

        assert_equal(res.status_code, 401)

    def test_put_public_nodes_relationships_logged_out(self):
        res = self.app.put_json_api(
                self.public_url, self.payload([self.public_node._id]),
                expect_errors=True
        )

        assert_equal(res.status_code, 401)

    def test_put_public_nodes_relationships_logged_in(self):
        res = self.app.put_json_api(
                self.public_url, self.payload([self.private_node._id]),
                auth=self.user.auth, expect_errors=True
        )

        assert_equal(res.status_code, 403)

    def test_delete_public_nodes_relationships_logged_out(self):
        res = self.app.delete_json_api(
            self.public_url, self.payload([self.public_node._id]),
            expect_errors=True
        )

        assert_equal(res.status_code, 401)

    def test_delete_public_nodes_relationships_logged_in(self):
        res = self.app.delete_json_api(
                self.public_url, self.payload([self.private_node._id]),
                auth=self.user.auth, expect_errors=True
        )

        assert_equal(res.status_code, 403)

    def test_node_links_and_relationship_represent_same_nodes(self):
        self.collection.add_pointer(self.admin_node, auth=self.auth)
        self.collection.add_pointer(self.contributor_node, auth=self.auth)
        res_relationship = self.app.get(
            self.url, auth=self.user.auth
        )
        res_node_links = self.app.get(
            '/{}collections/{}/node_links/'.format(API_BASE, self.collection._id),
            auth=self.user.auth
        )
        node_links_id = []
        for data in res_node_links.json['data']:
            try:
                node_links_id.append(data['embeds']['target_node']['data']['id'])
            # node_links does not handle registrations correctly, skip them
            except KeyError:
                continue
        relationship_id = [data['id'] for data in res_relationship.json['data']]

        assert_equal(set(node_links_id), set(relationship_id))

    def test_attempt_to_add_collection_to_collection(self):
        other_collection = CollectionFactory(creator=self.user)
        res = self.app.post_json_api(
            self.url, self.payload([other_collection._id]),
            auth=self.user.auth, expect_errors=True
        )

        assert_equal(res.status_code, 404)


class TestCollectionLinkedNodes(ApiTestCase):
    def setUp(self):
        super(TestCollectionLinkedNodes, self).setUp()
        self.user = AuthUserFactory()
        self.auth = Auth(self.user)
        self.collection = CollectionFactory(creator=self.user)
        self.linked_node = NodeFactory(creator=self.user)
        self.linked_node2 = NodeFactory(creator=self.user)
        self.linked_registration = RegistrationFactory(creator=self.user)
        self.linked_registration2 = RegistrationFactory(creator=self.user)
        self.public_node = NodeFactory(is_public=True, creator=self.user)
        self.public_registration = RegistrationFactory(is_public=True, creator=self.user)
        self.collection.add_pointer(self.linked_node, auth=self.auth)
        self.collection.add_pointer(self.linked_node2, auth=self.auth)
        self.collection.add_pointer(self.linked_registration, auth=self.auth)
        self.collection.add_pointer(self.linked_registration2, auth=self.auth)
        self.collection.add_pointer(self.public_node, auth=self.auth)
        self.collection.add_pointer(self.public_registration, auth=self.auth)
        self.collection.save()
        self.url = '/{}collections/{}/linked_nodes/'.format(API_BASE, self.collection._id)
        self.reg_url = '/{}collections/{}/linked_registrations/'.format(API_BASE, self.collection._id)
        self.node_ids = list(self.collection.linked_nodes.values_list('guids___id', flat=True))

    def test_linked_nodes_returns_everything(self):
        res = self.app.get(self.url, auth=self.user.auth)
        reg_res = self.app.get(self.reg_url, auth=self.user.auth)

        assert_equal(res.status_code, 200)
        nodes_returned = [linked_node['id'] for linked_node in res.json['data']]
        registrations_returned = [linked_registration['id'] for linked_registration in reg_res.json['data']]
        assert_equal(len(nodes_returned) + len(registrations_returned), len(self.node_ids))

        for node_returned in nodes_returned:
            assert_in(node_returned, self.node_ids)
        for registration_returned in registrations_returned:
            assert_in(registration_returned, self.node_ids)

    def test_linked_nodes_only_return_viewable_nodes(self):
        user = AuthUserFactory()
        collection = CollectionFactory(creator=user)
        self.linked_node.add_contributor(user, auth=self.auth, save=True)
        self.linked_node2.add_contributor(user, auth=self.auth, save=True)
        self.linked_registration.add_contributor(user, auth=self.auth, save=True)
        self.linked_registration2.add_contributor(user, auth=self.auth, save=True)
        self.public_node.add_contributor(user, auth=self.auth, save=True)
        self.public_registration.add_contributor(user, auth=self.auth, save=True)
        collection.add_pointer(self.linked_node, auth=Auth(user))
        collection.add_pointer(self.linked_node2, auth=Auth(user))
        collection.add_pointer(self.linked_registration, auth=Auth(user))
        collection.add_pointer(self.linked_registration2, auth=Auth(user))
        collection.add_pointer(self.public_node, auth=Auth(user))
        collection.add_pointer(self.public_registration, auth=Auth(user))
        collection.save()

        res = self.app.get(
            '/{}collections/{}/linked_nodes/'.format(API_BASE, collection._id),
            auth=user.auth
        )
        reg_res = self.app.get(
            '/{}collections/{}/linked_registrations/'.format(API_BASE, collection._id),
            auth=user.auth
        )

        assert_equal(res.status_code, 200)
        assert_equal(reg_res.status_code, 200)
        nodes_returned = [linked_node['id'] for linked_node in res.json['data']]
        registrations_returned = [linked_registration['id'] for linked_registration in res.json['data']]
        assert_equal(len(nodes_returned) + len(registrations_returned), len(self.node_ids))

        for node_returned in nodes_returned:
            assert_in(node_returned, self.node_ids)
        for registration_returned in registrations_returned:
            assert_in(registration_returned, self.node_ids)

        # Disconnect contributor_removed so that we don't check in files
        # We can remove this when StoredFileNode is implemented in osf-models
        with disconnected_from_listeners(contributor_removed):
            self.linked_node2.remove_contributor(user, auth=self.auth)
            self.public_node.remove_contributor(user, auth=self.auth)
            self.linked_registration2.remove_contributor(user, auth=self.auth)
            self.public_registration.remove_contributor(user, auth=self.auth)

        res = self.app.get(
            '/{}collections/{}/linked_nodes/'.format(API_BASE, collection._id),
            auth=user.auth
        )
        reg_res = self.app.get(
            '/{}collections/{}/linked_registrations/'.format(API_BASE, collection._id),
            auth=user.auth
        )
        nodes_returned = [linked_node['id'] for linked_node in res.json['data']]
        registrations_returned = [linked_registration['id'] for linked_registration in reg_res.json['data']]
        assert_equal(len(nodes_returned) + len(registrations_returned), len(self.node_ids) - 2)

        assert_in(self.linked_node._id, nodes_returned)
        assert_in(self.public_node._id, nodes_returned)
        assert_in(self.linked_registration._id, registrations_returned)
        assert_in(self.public_registration._id, registrations_returned)
        assert_not_in(self.linked_node2._id, nodes_returned)
        assert_not_in(self.linked_registration2._id, registrations_returned)

    def test_linked_nodes_doesnt_return_deleted_nodes(self):
        self.linked_node.is_deleted = True
        self.linked_node.save()
        res = self.app.get(self.url, auth=self.user.auth)

        self.linked_registration.is_deleted = True
        self.linked_registration.save()
        reg_res = self.app.get(self.reg_url, auth=self.user.auth)

        assert_equal(res.status_code, 200)
        nodes_returned = [linked_node['id'] for linked_node in res.json['data']]
        assert_equal(len(nodes_returned), len(self.node_ids) - 4)

        assert_equal(reg_res.status_code, 200)
        registrations_returned = [linked_registration['id'] for linked_registration in reg_res.json['data']]
        assert_equal(len(registrations_returned), len(self.node_ids) - 4)

        assert_not_in(self.linked_node._id, nodes_returned)
        assert_in(self.linked_node2._id, nodes_returned)
        assert_in(self.public_node._id, nodes_returned)

        assert_not_in(self.linked_registration._id, registrations_returned)
        assert_in(self.linked_registration2._id, registrations_returned)
        assert_in(self.public_registration._id, registrations_returned)

    def test_attempt_to_return_linked_nodes_logged_out(self):
        res = self.app.get(
            self.url, auth=None,
            expect_errors=True
        )

        assert_equal(res.status_code, 401)
# flake8: noqa Post request should update resource, and any HTML in fields should be stripped # sanity check # Nothing will be updated here # self.private_project = ProjectFactory(is_public=False, creator=self.user_two) # node_links end point does not handle registrations correctly # node_links end point does not handle registrations correctly # node_links end point does not handle registrations correctly # node_links end point does not handle registrations correctly # node_links end point does not handle registrations correctly # node_links end point does not handle registrations correctly # This is could arguably be a 405, but we don't need to go crazy with status codes # Regression test for https://openscience.atlassian.net/browse/OSF-4322 # The node link belongs to a different project # linked_node endpoint can create linked_registrations, # but will not return a valid embed # linked_node endpoint can create linked_registrations, # but will not return a valid embed # This is could arguably be a 405, but we don't need to go crazy with status codes # Update the model to reflect changes made by post request #check that deleted pointer can not be returned # Regression test for https://openscience.atlassian.net/browse/OSF-4322 # The node link belongs to a different project # node_links does not handle registrations correctly, skip them # Disconnect contributor_removed so that we don't check in files # We can remove this when StoredFileNode is implemented in osf-models