# -*- coding: utf-8 -*-
"""Unit/Functional tests"""

from __future__ import unicode_literals
import datetime
import os

from django import VERSION as DJANGO_VERSION
from django.contrib.admin.sites import AdminSite
from django.contrib.admin.views.main import ChangeList
from django.contrib.auth.models import User, AnonymousUser
from django.contrib.messages.storage.fallback import FallbackStorage
from django.db.models import Q
from django.template import Template, Context
from django.test import TestCase
from django.test.client import RequestFactory
import pytest

from treebeard import numconv
from treebeard.admin import admin_factory, TO_FIELD_VAR
from treebeard.exceptions import InvalidPosition, InvalidMoveToDescendant,\
    PathOverflow, MissingNodeOrderBy, NodeAlreadySaved
from treebeard.forms import movenodeform_factory
from treebeard.templatetags.admin_tree import get_static_url
from treebeard.tests import models
from treebeard.tests.admin import register_all as admin_register_all


admin_register_all()


BASE_DATA = [
    {'data': {'desc': '1'}},
    {'data': {'desc': '2'}, 'children': [
        {'data': {'desc': '21'}},
        {'data': {'desc': '22'}},
        {'data': {'desc': '23'}, 'children': [
            {'data': {'desc': '231'}},
        ]},
        {'data': {'desc': '24'}},
    ]},
    {'data': {'desc': '3'}},
    {'data': {'desc': '4'}, 'children': [
        {'data': {'desc': '41'}},
    ]}]
UNCHANGED = [
    ('1', 1, 0),
    ('2', 1, 4),
    ('21', 2, 0),
    ('22', 2, 0),
    ('23', 2, 1),
    ('231', 3, 0),
    ('24', 2, 0),
    ('3', 1, 0),
    ('4', 1, 1),
    ('41', 2, 0)]


def _prepare_db_test(request):
    case = TestCase(methodName='__init__')
    case._pre_setup()
    request.addfinalizer(case._post_teardown)
    return request.param


def idfn(fixture_value):
    return fixture_value.__name__

@pytest.fixture(scope='function',
                params=models.BASE_MODELS + models.PROXY_MODELS,
                ids=idfn)
def model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=models.BASE_MODELS, ids=idfn)
def model_without_proxy(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=models.UNICODE_MODELS, ids=idfn)
def model_with_unicode(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=models.SORTED_MODELS, ids=idfn)
def sorted_model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=models.RELATED_MODELS, ids=idfn)
def related_model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=models.INHERITED_MODELS, ids=idfn)
def inherited_model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=models.MP_SHORTPATH_MODELS, ids=idfn)
def mpshort_model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=[models.MP_TestNodeShortPath], ids=idfn)
def mpshortnotsorted_model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=[models.MP_TestNodeAlphabet], ids=idfn)
def mpalphabet_model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=[models.MP_TestNodeSortedAutoNow], ids=idfn)
def mpsortedautonow_model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=[models.MP_TestNodeSmallStep])
def mpsmallstep_model(request):
    return _prepare_db_test(request)


@pytest.fixture(scope='function', params=[models.MP_TestManyToManyWithUser])
def mpm2muser_model(request):
    return _prepare_db_test(request)


class TestTreeBase(object):
    def got(self, model):
        if model in [models.NS_TestNode, models.NS_TestNode_Proxy]:
            # this slows down nested sets tests quite a bit, but it has the
            # advantage that we'll check the node edges are correct
            d = {}
            for tree_id, lft, rgt in model.objects.values_list('tree_id',
                                                               'lft',
                                                               'rgt'):
                d.setdefault(tree_id, []).extend([lft, rgt])
            for tree_id, got_edges in d.items():
                assert len(got_edges) == max(got_edges)
                good_edges = list(range(1, len(got_edges) + 1))
                assert sorted(got_edges) == good_edges

        return [(o.desc, o.get_depth(), o.get_children_count())
                for o in model.get_tree()]

    def _assert_get_annotated_list(self, model, expected, parent=None):
        results = model.get_annotated_list(parent)
        got = [
            (obj[0].desc, obj[1]['open'], obj[1]['close'], obj[1]['level'])
            for obj in results
        ]
        assert expected == got
        assert all([type(obj[0]) == model for obj in results])


class TestEmptyTree(TestTreeBase):

    def test_load_bulk_empty(self, model):
        ids = model.load_bulk(BASE_DATA)
        got_descs = [obj.desc
                     for obj in model.objects.filter(id__in=ids)]
        expected_descs = [x[0] for x in UNCHANGED]
        assert sorted(got_descs) == sorted(expected_descs)
        assert self.got(model) == UNCHANGED

    def test_dump_bulk_empty(self, model):
        assert model.dump_bulk() == []

    def test_add_root_empty(self, model):
        model.add_root(desc='1')
        expected = [('1', 1, 0)]
        assert self.got(model) == expected

    def test_get_root_nodes_empty(self, model):
        got = model.get_root_nodes()
        expected = []
        assert [node.desc for node in got] == expected

    def test_get_first_root_node_empty(self, model):
        got = model.get_first_root_node()
        assert got is None

    def test_get_last_root_node_empty(self, model):
        got = model.get_last_root_node()
        assert got is None

    def test_get_tree(self, model):
        got = list(model.get_tree())
        assert got == []

    def test_get_annotated_list(self, model):
        expected = []
        self._assert_get_annotated_list(model, expected)


class TestNonEmptyTree(TestTreeBase):

    @classmethod
    def setup_class(cls):
        for model in models.BASE_MODELS:
            model.load_bulk(BASE_DATA)

    @classmethod
    def teardown_class(cls):
        models.empty_models_tables(models.BASE_MODELS)


class TestClassMethods(TestNonEmptyTree):

    def test_load_bulk_existing(self, model):
        # inserting on an existing node
        node = model.objects.get(desc='231')
        ids = model.load_bulk(BASE_DATA, node)
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 4),
                    ('1', 4, 0),
                    ('2', 4, 4),
                    ('21', 5, 0),
                    ('22', 5, 0),
                    ('23', 5, 1),
                    ('231', 6, 0),
                    ('24', 5, 0),
                    ('3', 4, 0),
                    ('4', 4, 1),
                    ('41', 5, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        expected_descs = ['1', '2', '21', '22', '23', '231', '24',
                          '3', '4', '41']
        got_descs = [obj.desc for obj in model.objects.filter(id__in=ids)]
        assert sorted(got_descs) == sorted(expected_descs)
        assert self.got(model) == expected

    def test_get_tree_all(self, model):
        nodes = model.get_tree()
        got = [(o.desc, o.get_depth(), o.get_children_count())
               for o in nodes]
        assert got == UNCHANGED
        assert all([type(o) == model for o in nodes])

    def test_dump_bulk_all(self, model):
        assert model.dump_bulk(keep_ids=False) == BASE_DATA

    def test_get_tree_node(self, model):
        node = model.objects.get(desc='231')
        model.load_bulk(BASE_DATA, node)

        # the tree was modified by load_bulk, so we reload our node object
        node = model.objects.get(pk=node.pk)

        nodes = model.get_tree(node)
        got = [(o.desc, o.get_depth(), o.get_children_count())
               for o in nodes]
        expected = [('231', 3, 4),
                    ('1', 4, 0),
                    ('2', 4, 4),
                    ('21', 5, 0),
                    ('22', 5, 0),
                    ('23', 5, 1),
                    ('231', 6, 0),
                    ('24', 5, 0),
                    ('3', 4, 0),
                    ('4', 4, 1),
                    ('41', 5, 0)]
        assert got == expected
        assert all([type(o) == model for o in nodes])

    def test_get_tree_leaf(self, model):
        node = model.objects.get(desc='1')

        assert 0 == node.get_children_count()
        nodes = model.get_tree(node)
        got = [(o.desc, o.get_depth(), o.get_children_count())
               for o in nodes]
        expected = [('1', 1, 0)]
        assert got == expected
        assert all([type(o) == model for o in nodes])

    def test_get_annotated_list_all(self, model):
        expected = [('1', True, [], 0), ('2', False, [], 0),
                    ('21', True, [], 1), ('22', False, [], 1),
                    ('23', False, [], 1), ('231', True, [0], 2),
                    ('24', False, [0], 1), ('3', False, [], 0),
                    ('4', False, [], 0), ('41', True, [0, 1], 1)]
        self._assert_get_annotated_list(model, expected)

    def test_get_annotated_list_node(self, model):
        node = model.objects.get(desc='2')
        expected = [('2', True, [], 0), ('21', True, [], 1),
                    ('22', False, [], 1), ('23', False, [], 1),
                    ('231', True, [0], 2), ('24', False, [0, 1], 1)]
        self._assert_get_annotated_list(model, expected, node)

    def test_get_annotated_list_leaf(self, model):
        node = model.objects.get(desc='1')
        expected = [('1', True, [0], 0)]
        self._assert_get_annotated_list(model, expected, node)

    def test_dump_bulk_node(self, model):
        node = model.objects.get(desc='231')
        model.load_bulk(BASE_DATA, node)

        # the tree was modified by load_bulk, so we reload our node object
        node = model.objects.get(pk=node.pk)

        got = model.dump_bulk(node, False)
        expected = [{'data': {'desc': '231'}, 'children': BASE_DATA}]
        assert got == expected

    def test_load_and_dump_bulk_keeping_ids(self, model):
        exp = model.dump_bulk(keep_ids=True)
        model.objects.all().delete()
        model.load_bulk(exp, None, True)
        got = model.dump_bulk(keep_ids=True)
        assert got == exp
        # do we really have an unchaged tree after the dump/delete/load?
        got = [(o.desc, o.get_depth(), o.get_children_count())
               for o in model.get_tree()]
        assert got == UNCHANGED

    def test_load_and_dump_bulk_with_fk(self, related_model):
        # https://bitbucket.org/tabo/django-treebeard/issue/48/
        related_model.objects.all().delete()
        related, created = models.RelatedModel.objects.get_or_create(
            desc="Test %s" % related_model.__name__)

        related_data = [
            {'data': {'desc': '1', 'related': related.pk}},
            {'data': {'desc': '2', 'related': related.pk}, 'children': [
                {'data': {'desc': '21', 'related': related.pk}},
                {'data': {'desc': '22', 'related': related.pk}},
                {'data': {'desc': '23', 'related': related.pk}, 'children': [
                    {'data': {'desc': '231', 'related': related.pk}},
                ]},
                {'data': {'desc': '24', 'related': related.pk}},
            ]},
            {'data': {'desc': '3', 'related': related.pk}},
            {'data': {'desc': '4', 'related': related.pk}, 'children': [
                {'data': {'desc': '41', 'related': related.pk}},
            ]}]
        related_model.load_bulk(related_data)
        got = related_model.dump_bulk(keep_ids=False)
        assert got == related_data

    def test_get_root_nodes(self, model):
        got = model.get_root_nodes()
        expected = ['1', '2', '3', '4']
        assert [node.desc for node in got] == expected
        assert all([type(node) == model for node in got])

    def test_get_first_root_node(self, model):
        got = model.get_first_root_node()
        assert got.desc == '1'
        assert type(got) == model

    def test_get_last_root_node(self, model):
        got = model.get_last_root_node()
        assert got.desc == '4'
        assert type(got) == model

    def test_add_root(self, model):
        obj = model.add_root(desc='5')
        assert obj.get_depth() == 1
        got = model.get_last_root_node()
        assert got.desc == '5'
        assert type(got) == model

    def test_add_root_with_passed_instance(self, model):
        obj = model(desc='5')
        result = model.add_root(instance=obj)
        assert result == obj
        got = model.get_last_root_node()
        assert got.desc == '5'
        assert type(got) == model

    def test_add_root_with_already_saved_instance(self, model):
        obj = model.objects.get(desc='4')
        with pytest.raises(NodeAlreadySaved):
            model.add_root(instance=obj)


class TestSimpleNodeMethods(TestNonEmptyTree):
    def test_is_root(self, model):
        data = [
            ('2', True),
            ('1', True),
            ('4', True),
            ('21', False),
            ('24', False),
            ('22', False),
            ('231', False),
        ]
        for desc, expected in data:
            got = model.objects.get(desc=desc).is_root()
            assert got == expected

    def test_is_leaf(self, model):
        data = [
            ('2', False),
            ('23', False),
            ('231', True),
        ]
        for desc, expected in data:
            got = model.objects.get(desc=desc).is_leaf()
            assert got == expected

    def test_get_root(self, model):
        data = [
            ('2', '2'),
            ('1', '1'),
            ('4', '4'),
            ('21', '2'),
            ('24', '2'),
            ('22', '2'),
            ('231', '2'),
        ]
        for desc, expected in data:
            node = model.objects.get(desc=desc).get_root()
            assert node.desc == expected
            assert type(node) == model

    def test_get_parent(self, model):
        data = [
            ('2', None),
            ('1', None),
            ('4', None),
            ('21', '2'),
            ('24', '2'),
            ('22', '2'),
            ('231', '23'),
        ]
        data = dict(data)
        objs = {}
        for desc, expected in data.items():
            node = model.objects.get(desc=desc)
            parent = node.get_parent()
            if expected:
                assert parent.desc == expected
                assert type(parent) == model
            else:
                assert parent is None
            objs[desc] = node
            # corrupt the objects' parent cache
            node._parent_obj = 'CORRUPTED!!!'

        for desc, expected in data.items():
            node = objs[desc]
            # asking get_parent to not use the parent cache (since we
            # corrupted it in the previous loop)
            parent = node.get_parent(True)
            if expected:
                assert parent.desc == expected
                assert type(parent) == model
            else:
                assert parent is None

    def test_get_children(self, model):
        data = [
            ('2', ['21', '22', '23', '24']),
            ('23', ['231']),
            ('231', []),
        ]
        for desc, expected in data:
            children = model.objects.get(desc=desc).get_children()
            assert [node.desc for node in children] == expected
            assert all([type(node) == model for node in children])

    def test_get_children_count(self, model):
        data = [
            ('2', 4),
            ('23', 1),
            ('231', 0),
        ]
        for desc, expected in data:
            got = model.objects.get(desc=desc).get_children_count()
            assert got == expected

    def test_get_siblings(self, model):
        data = [
            ('2', ['1', '2', '3', '4']),
            ('21', ['21', '22', '23', '24']),
            ('231', ['231']),
        ]
        for desc, expected in data:
            siblings = model.objects.get(desc=desc).get_siblings()
            assert [node.desc for node in siblings] == expected
            assert all([type(node) == model for node in siblings])

    def test_get_first_sibling(self, model):
        data = [
            ('2', '1'),
            ('1', '1'),
            ('4', '1'),
            ('21', '21'),
            ('24', '21'),
            ('22', '21'),
            ('231', '231'),
        ]
        for desc, expected in data:
            node = model.objects.get(desc=desc).get_first_sibling()
            assert node.desc == expected
            assert type(node) == model

    def test_get_prev_sibling(self, model):
        data = [
            ('2', '1'),
            ('1', None),
            ('4', '3'),
            ('21', None),
            ('24', '23'),
            ('22', '21'),
            ('231', None),
        ]
        for desc, expected in data:
            node = model.objects.get(desc=desc).get_prev_sibling()
            if expected is None:
                assert node is None
            else:
                assert node.desc == expected
                assert type(node) == model

    def test_get_next_sibling(self, model):
        data = [
            ('2', '3'),
            ('1', '2'),
            ('4', None),
            ('21', '22'),
            ('24', None),
            ('22', '23'),
            ('231', None),
        ]
        for desc, expected in data:
            node = model.objects.get(desc=desc).get_next_sibling()
            if expected is None:
                assert node is None
            else:
                assert node.desc == expected
                assert type(node) == model

    def test_get_last_sibling(self, model):
        data = [
            ('2', '4'),
            ('1', '4'),
            ('4', '4'),
            ('21', '24'),
            ('24', '24'),
            ('22', '24'),
            ('231', '231'),
        ]
        for desc, expected in data:
            node = model.objects.get(desc=desc).get_last_sibling()
            assert node.desc == expected
            assert type(node) == model

    def test_get_first_child(self, model):
        data = [
            ('2', '21'),
            ('21', None),
            ('23', '231'),
            ('231', None),
        ]
        for desc, expected in data:
            node = model.objects.get(desc=desc).get_first_child()
            if expected is None:
                assert node is None
            else:
                assert node.desc == expected
                assert type(node) == model

    def test_get_last_child(self, model):
        data = [
            ('2', '24'),
            ('21', None),
            ('23', '231'),
            ('231', None),
        ]
        for desc, expected in data:
            node = model.objects.get(desc=desc).get_last_child()
            if expected is None:
                assert node is None
            else:
                assert node.desc == expected
                assert type(node) == model

    def test_get_ancestors(self, model):
        data = [
            ('2', []),
            ('21', ['2']),
            ('231', ['2', '23']),
        ]
        for desc, expected in data:
            nodes = model.objects.get(desc=desc).get_ancestors()
            assert [node.desc for node in nodes] == expected
            assert all([type(node) == model for node in nodes])

    def test_get_descendants(self, model):
        data = [
            ('2', ['21', '22', '23', '231', '24']),
            ('23', ['231']),
            ('231', []),
            ('1', []),
            ('4', ['41']),
        ]
        for desc, expected in data:
            nodes = model.objects.get(desc=desc).get_descendants()
            assert [node.desc for node in nodes] == expected
            assert all([type(node) == model for node in nodes])

    def test_get_descendant_count(self, model):
        data = [
            ('2', 5),
            ('23', 1),
            ('231', 0),
            ('1', 0),
            ('4', 1),
        ]
        for desc, expected in data:
            got = model.objects.get(desc=desc).get_descendant_count()
            assert got == expected

    def test_is_sibling_of(self, model):
        data = [
            ('2', '2', True),
            ('2', '1', True),
            ('21', '2', False),
            ('231', '2', False),
            ('22', '23', True),
            ('231', '23', False),
            ('231', '231', True),
        ]
        for desc1, desc2, expected in data:
            node1 = model.objects.get(desc=desc1)
            node2 = model.objects.get(desc=desc2)
            assert node1.is_sibling_of(node2) == expected

    def test_is_child_of(self, model):
        data = [
            ('2', '2', False),
            ('2', '1', False),
            ('21', '2', True),
            ('231', '2', False),
            ('231', '23', True),
            ('231', '231', False),
        ]
        for desc1, desc2, expected in data:
            node1 = model.objects.get(desc=desc1)
            node2 = model.objects.get(desc=desc2)
            assert node1.is_child_of(node2) == expected

    def test_is_descendant_of(self, model):
        data = [
            ('2', '2', False),
            ('2', '1', False),
            ('21', '2', True),
            ('231', '2', True),
            ('231', '23', True),
            ('231', '231', False),
        ]
        for desc1, desc2, expected in data:
            node1 = model.objects.get(desc=desc1)
            node2 = model.objects.get(desc=desc2)
            assert node1.is_descendant_of(node2) == expected


class TestAddChild(TestNonEmptyTree):
    def test_add_child_to_leaf(self, model):
        model.objects.get(desc='231').add_child(desc='2311')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 1),
                    ('2311', 4, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_child_to_node(self, model):
        model.objects.get(desc='2').add_child(desc='25')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('25', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_child_with_passed_instance(self, model):
        child = model(desc='2311')
        result = model.objects.get(desc='231').add_child(instance=child)
        assert result == child
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 1),
                    ('2311', 4, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_child_with_already_saved_instance(self, model):
        child = model.objects.get(desc='21')
        with pytest.raises(NodeAlreadySaved):
            model.objects.get(desc='2').add_child(instance=child)


class TestAddSibling(TestNonEmptyTree):
    def test_add_sibling_invalid_pos(self, model):
        with pytest.raises(InvalidPosition):
            model.objects.get(desc='231').add_sibling('invalid_pos')

    def test_add_sibling_missing_nodeorderby(self, model):
        node_wchildren = model.objects.get(desc='2')
        with pytest.raises(MissingNodeOrderBy):
            node_wchildren.add_sibling('sorted-sibling', desc='aaa')

    def test_add_sibling_last_root(self, model):
        node_wchildren = model.objects.get(desc='2')
        obj = node_wchildren.add_sibling('last-sibling', desc='5')
        assert obj.get_depth() == 1
        assert node_wchildren.get_last_sibling().desc == '5'

    def test_add_sibling_last(self, model):
        node = model.objects.get(desc='231')
        obj = node.add_sibling('last-sibling', desc='232')
        assert obj.get_depth() == 3
        assert node.get_last_sibling().desc == '232'

    def test_add_sibling_first_root(self, model):
        node_wchildren = model.objects.get(desc='2')
        obj = node_wchildren.add_sibling('first-sibling', desc='new')
        assert obj.get_depth() == 1
        expected = [('new', 1, 0),
                    ('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_first(self, model):
        node_wchildren = model.objects.get(desc='23')
        obj = node_wchildren.add_sibling('first-sibling', desc='new')
        assert obj.get_depth() == 2
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('new', 2, 0),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_left_root(self, model):
        node_wchildren = model.objects.get(desc='2')
        obj = node_wchildren.add_sibling('left', desc='new')
        assert obj.get_depth() == 1
        expected = [('1', 1, 0),
                    ('new', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_left(self, model):
        node_wchildren = model.objects.get(desc='23')
        obj = node_wchildren.add_sibling('left', desc='new')
        assert obj.get_depth() == 2
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('new', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_left_noleft_root(self, model):
        node = model.objects.get(desc='1')
        obj = node.add_sibling('left', desc='new')
        assert obj.get_depth() == 1
        expected = [('new', 1, 0),
                    ('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_left_noleft(self, model):
        node = model.objects.get(desc='231')
        obj = node.add_sibling('left', desc='new')
        assert obj.get_depth() == 3
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 2),
                    ('new', 3, 0),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_right_root(self, model):
        node_wchildren = model.objects.get(desc='2')
        obj = node_wchildren.add_sibling('right', desc='new')
        assert obj.get_depth() == 1
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('new', 1, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_right(self, model):
        node_wchildren = model.objects.get(desc='23')
        obj = node_wchildren.add_sibling('right', desc='new')
        assert obj.get_depth() == 2
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('new', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_right_noright_root(self, model):
        node = model.objects.get(desc='4')
        obj = node.add_sibling('right', desc='new')
        assert obj.get_depth() == 1
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0),
                    ('new', 1, 0)]
        assert self.got(model) == expected

    def test_add_sibling_right_noright(self, model):
        node = model.objects.get(desc='231')
        obj = node.add_sibling('right', desc='new')
        assert obj.get_depth() == 3
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 2),
                    ('231', 3, 0),
                    ('new', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_add_sibling_with_passed_instance(self, model):
        node_wchildren = model.objects.get(desc='2')
        obj = model(desc='5')
        result = node_wchildren.add_sibling('last-sibling', instance=obj)
        assert result == obj
        assert obj.get_depth() == 1
        assert node_wchildren.get_last_sibling().desc == '5'

    def test_add_sibling_already_saved_instance(self, model):
        node_wchildren = model.objects.get(desc='2')
        existing_node = model.objects.get(desc='4')
        with pytest.raises(NodeAlreadySaved):
            node_wchildren.add_sibling('last-sibling', instance=existing_node)


class TestDelete(TestNonEmptyTree):

    @classmethod
    def setup_class(cls):
        TestNonEmptyTree.setup_class()
        for model, dep_model in zip(models.BASE_MODELS, models.DEP_MODELS):
            for node in model.objects.all():
                dep_model(node=node).save()

    @classmethod
    def teardown_class(cls):
        models.empty_models_tables(models.DEP_MODELS + models.BASE_MODELS)

    def test_delete_leaf(self, model):
        model.objects.get(desc='231').delete()
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_delete_node(self, model):
        model.objects.get(desc='23').delete()
        expected = [('1', 1, 0),
                    ('2', 1, 3),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_delete_root(self, model):
        model.objects.get(desc='2').delete()
        expected = [('1', 1, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_delete_filter_root_nodes(self, model):
        model.objects.filter(desc__in=('2', '3')).delete()
        expected = [('1', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_delete_filter_children(self, model):
        model.objects.filter(desc__in=('2', '23', '231')).delete()
        expected = [('1', 1, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_delete_nonexistant_nodes(self, model):
        model.objects.filter(desc__in=('ZZZ', 'XXX')).delete()
        assert self.got(model) == UNCHANGED

    def test_delete_same_node_twice(self, model):
        model.objects.filter(desc__in=('2', '2')).delete()
        expected = [('1', 1, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_delete_all_root_nodes(self, model):
        model.get_root_nodes().delete()
        count = model.objects.count()
        assert count == 0

    def test_delete_all_nodes(self, model):
        model.objects.all().delete()
        count = model.objects.count()
        assert count == 0


class TestMoveErrors(TestNonEmptyTree):
    def test_move_invalid_pos(self, model):
        node = model.objects.get(desc='231')
        with pytest.raises(InvalidPosition):
            node.move(node, 'invalid_pos')

    def test_move_to_descendant(self, model):
        node = model.objects.get(desc='2')
        target = model.objects.get(desc='231')
        with pytest.raises(InvalidMoveToDescendant):
            node.move(target, 'first-sibling')

    def test_move_missing_nodeorderby(self, model):
        node = model.objects.get(desc='231')
        with pytest.raises(MissingNodeOrderBy):
            node.move(node, 'sorted-child')
        with pytest.raises(MissingNodeOrderBy):
            node.move(node, 'sorted-sibling')


class TestMoveSortedErrors(TestTreeBase):

    def test_nonsorted_move_in_sorted(self, sorted_model):
        node = sorted_model.add_root(val1=3, val2=3, desc='zxy')
        with pytest.raises(InvalidPosition):
            node.move(node, 'left')


class TestMoveLeafRoot(TestNonEmptyTree):
    def test_move_leaf_last_sibling_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='231').move(target, 'last-sibling')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0),
                    ('231', 1, 0)]
        assert self.got(model) == expected

    def test_move_leaf_first_sibling_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='231').move(target, 'first-sibling')
        expected = [('231', 1, 0),
                    ('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_left_sibling_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='231').move(target, 'left')
        expected = [('1', 1, 0),
                    ('231', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_right_sibling_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='231').move(target, 'right')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('231', 1, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_last_child_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='231').move(target, 'last-child')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('231', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_first_child_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='231').move(target, 'first-child')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('231', 2, 0),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected


class TestMoveLeaf(TestNonEmptyTree):
    def test_move_leaf_last_sibling(self, model):
        target = model.objects.get(desc='22')
        model.objects.get(desc='231').move(target, 'last-sibling')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('231', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_first_sibling(self, model):
        target = model.objects.get(desc='22')
        model.objects.get(desc='231').move(target, 'first-sibling')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('231', 2, 0),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_left_sibling(self, model):
        target = model.objects.get(desc='22')
        model.objects.get(desc='231').move(target, 'left')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('231', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_right_sibling(self, model):
        target = model.objects.get(desc='22')
        model.objects.get(desc='231').move(target, 'right')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('231', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_left_sibling_itself(self, model):
        target = model.objects.get(desc='231')
        model.objects.get(desc='231').move(target, 'left')
        assert self.got(model) == UNCHANGED

    def test_move_leaf_last_child(self, model):
        target = model.objects.get(desc='22')
        model.objects.get(desc='231').move(target, 'last-child')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 1),
                    ('231', 3, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_leaf_first_child(self, model):
        target = model.objects.get(desc='22')
        model.objects.get(desc='231').move(target, 'first-child')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 1),
                    ('231', 3, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected


class TestMoveBranchRoot(TestNonEmptyTree):
    def test_move_branch_first_sibling_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='4').move(target, 'first-sibling')
        expected = [('4', 1, 1),
                    ('41', 2, 0),
                    ('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_last_sibling_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='4').move(target, 'last-sibling')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_branch_left_sibling_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='4').move(target, 'left')
        expected = [('1', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_right_sibling_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='4').move(target, 'right')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('4', 1, 1),
                    ('41', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_left_noleft_sibling_root(self, model):
        target = model.objects.get(desc='2').get_first_sibling()
        model.objects.get(desc='4').move(target, 'left')
        expected = [('4', 1, 1),
                    ('41', 2, 0),
                    ('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_right_noright_sibling_root(self, model):
        target = model.objects.get(desc='2').get_last_sibling()
        model.objects.get(desc='4').move(target, 'right')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_branch_first_child_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='4').move(target, 'first-child')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('4', 2, 1),
                    ('41', 3, 0),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_last_child_root(self, model):
        target = model.objects.get(desc='2')
        model.objects.get(desc='4').move(target, 'last-child')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('4', 2, 1),
                    ('41', 3, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected


class TestMoveBranch(TestNonEmptyTree):
    def test_move_branch_first_sibling(self, model):
        target = model.objects.get(desc='23')
        model.objects.get(desc='4').move(target, 'first-sibling')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('4', 2, 1),
                    ('41', 3, 0),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_last_sibling(self, model):
        target = model.objects.get(desc='23')
        model.objects.get(desc='4').move(target, 'last-sibling')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('4', 2, 1),
                    ('41', 3, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_left_sibling(self, model):
        target = model.objects.get(desc='23')
        model.objects.get(desc='4').move(target, 'left')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('4', 2, 1),
                    ('41', 3, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_right_sibling(self, model):
        target = model.objects.get(desc='23')
        model.objects.get(desc='4').move(target, 'right')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('4', 2, 1),
                    ('41', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_left_noleft_sibling(self, model):
        target = model.objects.get(desc='23').get_first_sibling()
        model.objects.get(desc='4').move(target, 'left')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('4', 2, 1),
                    ('41', 3, 0),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_right_noright_sibling(self, model):
        target = model.objects.get(desc='23').get_last_sibling()
        model.objects.get(desc='4').move(target, 'right')
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 1),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('4', 2, 1),
                    ('41', 3, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_left_itself_sibling(self, model):
        target = model.objects.get(desc='4')
        model.objects.get(desc='4').move(target, 'left')
        assert self.got(model) == UNCHANGED

    def test_move_branch_first_child(self, model):
        target = model.objects.get(desc='23')
        model.objects.get(desc='4').move(target, 'first-child')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 2),
                    ('4', 3, 1),
                    ('41', 4, 0),
                    ('231', 3, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected

    def test_move_branch_last_child(self, model):
        target = model.objects.get(desc='23')
        model.objects.get(desc='4').move(target, 'last-child')
        expected = [('1', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 2),
                    ('231', 3, 0),
                    ('4', 3, 1),
                    ('41', 4, 0),
                    ('24', 2, 0),
                    ('3', 1, 0)]
        assert self.got(model) == expected


class TestTreeSorted(TestTreeBase):

    def got(self, sorted_model):
        return [(o.val1, o.val2, o.desc, o.get_depth(), o.get_children_count())
                for o in sorted_model.get_tree()]

    def test_add_root_sorted(self, sorted_model):
        sorted_model.add_root(val1=3, val2=3, desc='zxy')
        sorted_model.add_root(val1=1, val2=4, desc='bcd')
        sorted_model.add_root(val1=2, val2=5, desc='zxy')
        sorted_model.add_root(val1=3, val2=3, desc='abc')
        sorted_model.add_root(val1=4, val2=1, desc='fgh')
        sorted_model.add_root(val1=3, val2=3, desc='abc')
        sorted_model.add_root(val1=2, val2=2, desc='qwe')
        sorted_model.add_root(val1=3, val2=2, desc='vcx')
        expected = [(1, 4, 'bcd', 1, 0),
                    (2, 2, 'qwe', 1, 0),
                    (2, 5, 'zxy', 1, 0),
                    (3, 2, 'vcx', 1, 0),
                    (3, 3, 'abc', 1, 0),
                    (3, 3, 'abc', 1, 0),
                    (3, 3, 'zxy', 1, 0),
                    (4, 1, 'fgh', 1, 0)]
        assert self.got(sorted_model) == expected

    def test_add_child_root_sorted(self, sorted_model):
        root = sorted_model.add_root(val1=0, val2=0, desc='aaa')
        root.add_child(val1=3, val2=3, desc='zxy')
        root.add_child(val1=1, val2=4, desc='bcd')
        root.add_child(val1=2, val2=5, desc='zxy')
        root.add_child(val1=3, val2=3, desc='abc')
        root.add_child(val1=4, val2=1, desc='fgh')
        root.add_child(val1=3, val2=3, desc='abc')
        root.add_child(val1=2, val2=2, desc='qwe')
        root.add_child(val1=3, val2=2, desc='vcx')
        expected = [(0, 0, 'aaa', 1, 8),
                    (1, 4, 'bcd', 2, 0),
                    (2, 2, 'qwe', 2, 0),
                    (2, 5, 'zxy', 2, 0),
                    (3, 2, 'vcx', 2, 0),
                    (3, 3, 'abc', 2, 0),
                    (3, 3, 'abc', 2, 0),
                    (3, 3, 'zxy', 2, 0),
                    (4, 1, 'fgh', 2, 0)]
        assert self.got(sorted_model) == expected

    def test_add_child_nonroot_sorted(self, sorted_model):
        get_node = lambda node_id: sorted_model.objects.get(pk=node_id)

        root_id = sorted_model.add_root(val1=0, val2=0, desc='a').pk
        node_id = get_node(root_id).add_child(val1=0, val2=0, desc='ac').pk
        get_node(root_id).add_child(val1=0, val2=0, desc='aa')
        get_node(root_id).add_child(val1=0, val2=0, desc='av')
        get_node(node_id).add_child(val1=0, val2=0, desc='aca')
        get_node(node_id).add_child(val1=0, val2=0, desc='acc')
        get_node(node_id).add_child(val1=0, val2=0, desc='acb')

        expected = [(0, 0, 'a', 1, 3),
                    (0, 0, 'aa', 2, 0),
                    (0, 0, 'ac', 2, 3),
                    (0, 0, 'aca', 3, 0),
                    (0, 0, 'acb', 3, 0),
                    (0, 0, 'acc', 3, 0),
                    (0, 0, 'av', 2, 0)]
        assert self.got(sorted_model) == expected

    def test_move_sorted(self, sorted_model):
        sorted_model.add_root(val1=3, val2=3, desc='zxy')
        sorted_model.add_root(val1=1, val2=4, desc='bcd')
        sorted_model.add_root(val1=2, val2=5, desc='zxy')
        sorted_model.add_root(val1=3, val2=3, desc='abc')
        sorted_model.add_root(val1=4, val2=1, desc='fgh')
        sorted_model.add_root(val1=3, val2=3, desc='abc')
        sorted_model.add_root(val1=2, val2=2, desc='qwe')
        sorted_model.add_root(val1=3, val2=2, desc='vcx')
        root_nodes = sorted_model.get_root_nodes()
        target = root_nodes[0]
        for node in root_nodes[1:]:
            # because raw queries don't update django objects
            node = sorted_model.objects.get(pk=node.pk)
            target = sorted_model.objects.get(pk=target.pk)
            node.move(target, 'sorted-child')
        expected = [(1, 4, 'bcd', 1, 7),
                    (2, 2, 'qwe', 2, 0),
                    (2, 5, 'zxy', 2, 0),
                    (3, 2, 'vcx', 2, 0),
                    (3, 3, 'abc', 2, 0),
                    (3, 3, 'abc', 2, 0),
                    (3, 3, 'zxy', 2, 0),
                    (4, 1, 'fgh', 2, 0)]
        assert self.got(sorted_model) == expected

    def test_move_sortedsibling(self, sorted_model):
        # https://bitbucket.org/tabo/django-treebeard/issue/27
        sorted_model.add_root(val1=3, val2=3, desc='zxy')
        sorted_model.add_root(val1=1, val2=4, desc='bcd')
        sorted_model.add_root(val1=2, val2=5, desc='zxy')
        sorted_model.add_root(val1=3, val2=3, desc='abc')
        sorted_model.add_root(val1=4, val2=1, desc='fgh')
        sorted_model.add_root(val1=3, val2=3, desc='abc')
        sorted_model.add_root(val1=2, val2=2, desc='qwe')
        sorted_model.add_root(val1=3, val2=2, desc='vcx')
        root_nodes = sorted_model.get_root_nodes()
        target = root_nodes[0]
        for node in root_nodes[1:]:
            # because raw queries don't update django objects
            node = sorted_model.objects.get(pk=node.pk)
            target = sorted_model.objects.get(pk=target.pk)
            node.val1 -= 2
            node.save()
            node.move(target, 'sorted-sibling')
        expected = [(0, 2, 'qwe', 1, 0),
                    (0, 5, 'zxy', 1, 0),
                    (1, 2, 'vcx', 1, 0),
                    (1, 3, 'abc', 1, 0),
                    (1, 3, 'abc', 1, 0),
                    (1, 3, 'zxy', 1, 0),
                    (1, 4, 'bcd', 1, 0),
                    (2, 1, 'fgh', 1, 0)]
        assert self.got(sorted_model) == expected


class TestInheritedModels(TestTreeBase):

    @classmethod
    def setup_class(cls):
        themodels = zip(models.BASE_MODELS, models.INHERITED_MODELS)
        for model, inherited_model in themodels:
            model.add_root(desc='1')
            model.add_root(desc='2')

            node21 = inherited_model(desc='21')
            model.objects.get(desc='2').add_child(instance=node21)

            model.objects.get(desc='21').add_child(desc='211')
            model.objects.get(desc='21').add_child(desc='212')
            model.objects.get(desc='2').add_child(desc='22')

            node3 = inherited_model(desc='3')
            model.add_root(instance=node3)

    @classmethod
    def teardown_class(cls):
        # Will also empty INHERITED_MODELS by cascade
        models.empty_models_tables(models.BASE_MODELS)

    def test_get_tree_all(self, inherited_model):
        got = [(o.desc, o.get_depth(), o.get_children_count())
               for o in inherited_model.get_tree()]
        expected = [
            ('1', 1, 0),
            ('2', 1, 2),
            ('21', 2, 2),
            ('211', 3, 0),
            ('212', 3, 0),
            ('22', 2, 0),
            ('3', 1, 0),
        ]
        assert got == expected

    def test_get_tree_node(self, inherited_model):
        node = inherited_model.objects.get(desc='21')

        got = [(o.desc, o.get_depth(), o.get_children_count())
               for o in inherited_model.get_tree(node)]
        expected = [
            ('21', 2, 2),
            ('211', 3, 0),
            ('212', 3, 0),
        ]
        assert got == expected

    def test_get_root_nodes(self, inherited_model):
        got = inherited_model.get_root_nodes()
        expected = ['1', '2', '3']
        assert [node.desc for node in got] == expected

    def test_get_first_root_node(self, inherited_model):
        got = inherited_model.get_first_root_node()
        assert got.desc == '1'

    def test_get_last_root_node(self, inherited_model):
        got = inherited_model.get_last_root_node()
        assert got.desc == '3'

    def test_is_root(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.is_root() is False
        assert node3.is_root() is True

    def test_is_leaf(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.is_leaf() is False
        assert node3.is_leaf() is True

    def test_get_root(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_root().desc == '2'
        assert node3.get_root().desc == '3'

    def test_get_parent(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_parent().desc == '2'
        assert node3.get_parent() is None

    def test_get_children(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert [node.desc for node in node21.get_children()] == ['211', '212']
        assert [node.desc for node in node3.get_children()] == []

    def test_get_children_count(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_children_count() == 2
        assert node3.get_children_count() == 0

    def test_get_siblings(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert [node.desc for node in node21.get_siblings()] == ['21', '22']
        assert [node.desc for node in node3.get_siblings()] == ['1', '2', '3']

    def test_get_first_sibling(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_first_sibling().desc == '21'
        assert node3.get_first_sibling().desc == '1'

    def test_get_prev_sibling(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_prev_sibling() is None
        assert node3.get_prev_sibling().desc == '2'

    def test_get_next_sibling(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_next_sibling().desc == '22'
        assert node3.get_next_sibling() is None

    def test_get_last_sibling(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_last_sibling().desc == '22'
        assert node3.get_last_sibling().desc == '3'

    def test_get_first_child(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_first_child().desc == '211'
        assert node3.get_first_child() is None

    def test_get_last_child(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_last_child().desc == '212'
        assert node3.get_last_child() is None

    def test_get_ancestors(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert [node.desc for node in node21.get_ancestors()] == ['2']
        assert [node.desc for node in node3.get_ancestors()] == []

    def test_get_descendants(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert [node.desc for node in node21.get_descendants()] == [
            '211', '212']
        assert [node.desc for node in node3.get_descendants()] == []

    def test_get_descendant_count(self, inherited_model):
        node21 = inherited_model.objects.get(desc='21')
        node3 = inherited_model.objects.get(desc='3')
        assert node21.get_descendant_count() == 2
        assert node3.get_descendant_count() == 0

    def test_cascading_deletion(self, inherited_model):
        # Deleting a node by calling delete() on the inherited_model class
        # should delete descendants, even if those descendants are not
        # instances of inherited_model
        base_model = inherited_model.__bases__[0]

        node21 = inherited_model.objects.get(desc='21')
        node21.delete()
        node2 = base_model.objects.get(desc='2')
        for desc in ['21', '211', '212']:
            assert not base_model.objects.filter(desc=desc).exists()
        assert [node.desc for node in node2.get_descendants()] == ['22']


class TestMP_TreeAlphabet(TestTreeBase):
    @pytest.mark.skipif(
        not os.getenv('TREEBEARD_TEST_ALPHABET', False),
        reason='TREEBEARD_TEST_ALPHABET env variable not set.'
    )
    def test_alphabet(self, mpalphabet_model):
        """This isn't actually a test, it's an informational routine."""
        basealpha = numconv.BASE85
        got_err = False
        last_good = None
        for alphabetlen in range(3, len(basealpha) + 1):
            alphabet = basealpha[0:alphabetlen]
            assert len(alphabet) >= 3
            expected = [alphabet[0] + char for char in alphabet[1:]]
            expected.extend([alphabet[1] + char for char in alphabet])
            expected.append(alphabet[2] + alphabet[0])

            # remove all nodes
            mpalphabet_model.objects.all().delete()

            # change the model's alphabet
            mpalphabet_model.alphabet = alphabet
            mpalphabet_model.numconv_obj_ = None

            # insert root nodes
            for pos in range(len(alphabet) * 2):
                try:
                    mpalphabet_model.add_root(numval=pos)
                except:
                    got_err = True
                    break
            if got_err:
                break
            got = [obj.path
                   for obj in mpalphabet_model.objects.all()]
            if got != expected:
                break
            last_good = alphabet
        assert False, (
            'Best BASE85 based alphabet for your setup: {} (base {})'.format(
                last_good, len(last_good))
        )


class TestHelpers(TestTreeBase):

    @classmethod
    def setup_class(cls):
        for model in models.BASE_MODELS:
            model.load_bulk(BASE_DATA)
            for node in model.get_root_nodes():
                model.load_bulk(BASE_DATA, node)
            model.add_root(desc='5')

    @classmethod
    def teardown_class(cls):
        models.empty_models_tables(models.BASE_MODELS)

    def test_descendants_group_count_root(self, model):
        expected = [(o.desc, o.get_descendant_count())
                    for o in model.get_root_nodes()]
        got = [(o.desc, o.descendants_count)
               for o in model.get_descendants_group_count()]
        assert got == expected

    def test_descendants_group_count_node(self, model):
        parent = model.get_root_nodes().get(desc='2')
        expected = [(o.desc, o.get_descendant_count())
                    for o in parent.get_children()]
        got = [(o.desc, o.descendants_count)
               for o in model.get_descendants_group_count(parent)]
        assert got == expected


class TestMP_TreeSortedAutoNow(TestTreeBase):
    """
    The sorting mechanism used by treebeard when adding a node can fail if the
    ordering is using an "auto_now" field
    """

    def test_sorted_by_autonow_workaround(self, mpsortedautonow_model):
        # workaround
        for i in range(1, 5):
            mpsortedautonow_model.add_root(desc='node%d' % (i, ),
                                           created=datetime.datetime.now())

    def test_sorted_by_autonow_FAIL(self, mpsortedautonow_model):
        """
        This test asserts that we have a problem.
        fix this, somehow
        """
        mpsortedautonow_model.add_root(desc='node1')
        with pytest.raises(ValueError):
            mpsortedautonow_model.add_root(desc='node2')


class TestMP_TreeStepOverflow(TestTreeBase):
    def test_add_root(self, mpsmallstep_model):
        method = mpsmallstep_model.add_root
        for i in range(1, 10):
            method()
        with pytest.raises(PathOverflow):
            method()

    def test_add_child(self, mpsmallstep_model):
        root = mpsmallstep_model.add_root()
        method = root.add_child
        for i in range(1, 10):
            method()
        with pytest.raises(PathOverflow):
            method()

    def test_add_sibling(self, mpsmallstep_model):
        root = mpsmallstep_model.add_root()
        for i in range(1, 10):
            root.add_child()
        positions = ('first-sibling', 'left', 'right', 'last-sibling')
        for pos in positions:
            with pytest.raises(PathOverflow):
                root.get_last_child().add_sibling(pos)

    def test_move(self, mpsmallstep_model):
        root = mpsmallstep_model.add_root()
        for i in range(1, 10):
            root.add_child()
        newroot = mpsmallstep_model.add_root()
        targets = [(root, ['first-child', 'last-child']),
                   (root.get_first_child(), ['first-sibling',
                                             'left',
                                             'right',
                                             'last-sibling'])]
        for target, positions in targets:
            for pos in positions:
                with pytest.raises(PathOverflow):
                    newroot.move(target, pos)


class TestMP_TreeShortPath(TestTreeBase):
    """Test a tree with a very small path field (max_length=4) and a
    steplen of 1
    """

    def test_short_path(self, mpshortnotsorted_model):
        obj = mpshortnotsorted_model.add_root()
        obj = obj.add_child().add_child().add_child()
        with pytest.raises(PathOverflow):
            obj.add_child()


class TestMP_TreeFindProblems(TestTreeBase):
    def test_find_problems(self, mpalphabet_model):
        mpalphabet_model.alphabet = '01234'
        mpalphabet_model(path='01', depth=1, numchild=0, numval=0).save()
        mpalphabet_model(path='1', depth=1, numchild=0, numval=0).save()
        mpalphabet_model(path='111', depth=1, numchild=0, numval=0).save()
        mpalphabet_model(path='abcd', depth=1, numchild=0, numval=0).save()
        mpalphabet_model(path='qa#$%!', depth=1, numchild=0, numval=0).save()
        mpalphabet_model(path='0201', depth=2, numchild=0, numval=0).save()
        mpalphabet_model(path='020201', depth=3, numchild=0, numval=0).save()
        mpalphabet_model(path='03', depth=1, numchild=2, numval=0).save()
        mpalphabet_model(path='0301', depth=2, numchild=0, numval=0).save()
        mpalphabet_model(path='030102', depth=3, numchild=10, numval=0).save()
        mpalphabet_model(path='04', depth=10, numchild=1, numval=0).save()
        mpalphabet_model(path='0401', depth=20, numchild=0, numval=0).save()

        def got(ids):
            return [o.path for o in
                    mpalphabet_model.objects.filter(id__in=ids)]

        (evil_chars, bad_steplen, orphans, wrong_depth, wrong_numchild) = (
            mpalphabet_model.find_problems())
        assert ['abcd', 'qa#$%!'] == got(evil_chars)
        assert ['1', '111'] == got(bad_steplen)
        assert ['0201', '020201'] == got(orphans)
        assert ['03', '0301', '030102'] == got(wrong_numchild)
        assert ['04', '0401'] == got(wrong_depth)


class TestMP_TreeFix(TestTreeBase):

    expected_no_holes = {
        models.MP_TestNodeShortPath: [
            ('1', 'b', 1, 2),
            ('11', 'u', 2, 1),
            ('111', 'i', 3, 1),
            ('1111', 'e', 4, 0),
            ('12', 'o', 2, 0),
            ('2', 'd', 1, 0),
            ('3', 'g', 1, 0),
            ('4', 'a', 1, 4),
            ('41', 'a', 2, 0),
            ('42', 'a', 2, 0),
            ('43', 'u', 2, 1),
            ('431', 'i', 3, 1),
            ('4311', 'e', 4, 0),
            ('44', 'o', 2, 0)],
        models.MP_TestSortedNodeShortPath: [
            ('1', 'a', 1, 4),
            ('11', 'a', 2, 0),
            ('12', 'a', 2, 0),
            ('13', 'o', 2, 0),
            ('14', 'u', 2, 1),
            ('141', 'i', 3, 1),
            ('1411', 'e', 4, 0),
            ('2', 'b', 1, 2),
            ('21', 'o', 2, 0),
            ('22', 'u', 2, 1),
            ('221', 'i', 3, 1),
            ('2211', 'e', 4, 0),
            ('3', 'd', 1, 0),
            ('4', 'g', 1, 0)]}
    expected_with_holes = {
        models.MP_TestNodeShortPath: [
            ('1', 'b', 1, 2),
            ('13', 'u', 2, 1),
            ('134', 'i', 3, 1),
            ('1343', 'e', 4, 0),
            ('14', 'o', 2, 0),
            ('2', 'd', 1, 0),
            ('3', 'g', 1, 0),
            ('4', 'a', 1, 4),
            ('41', 'a', 2, 0),
            ('42', 'a', 2, 0),
            ('43', 'u', 2, 1),
            ('434', 'i', 3, 1),
            ('4343', 'e', 4, 0),
            ('44', 'o', 2, 0)],
        models.MP_TestSortedNodeShortPath: [
            ('1', 'b', 1, 2),
            ('13', 'u', 2, 1),
            ('134', 'i', 3, 1),
            ('1343', 'e', 4, 0),
            ('14', 'o', 2, 0),
            ('2', 'd', 1, 0),
            ('3', 'g', 1, 0),
            ('4', 'a', 1, 4),
            ('41', 'a', 2, 0),
            ('42', 'a', 2, 0),
            ('43', 'u', 2, 1),
            ('434', 'i', 3, 1),
            ('4343', 'e', 4, 0),
            ('44', 'o', 2, 0)]}

    def got(self, model):
        return [(o.path, o.desc, o.get_depth(), o.get_children_count())
                for o in model.get_tree()]

    def add_broken_test_data(self, model):
        model(path='4', depth=2, numchild=2, desc='a').save()
        model(path='13', depth=1000, numchild=0, desc='u').save()
        model(path='14', depth=4, numchild=500, desc='o').save()
        model(path='134', depth=321, numchild=543, desc='i').save()
        model(path='1343', depth=321, numchild=543, desc='e').save()
        model(path='42', depth=1, numchild=1, desc='a').save()
        model(path='43', depth=1000, numchild=0, desc='u').save()
        model(path='44', depth=4, numchild=500, desc='o').save()
        model(path='434', depth=321, numchild=543, desc='i').save()
        model(path='4343', depth=321, numchild=543, desc='e').save()
        model(path='41', depth=1, numchild=1, desc='a').save()
        model(path='3', depth=221, numchild=322, desc='g').save()
        model(path='1', depth=10, numchild=3, desc='b').save()
        model(path='2', depth=10, numchild=3, desc='d').save()

    def test_fix_tree_non_destructive(self, mpshort_model):
        self.add_broken_test_data(mpshort_model)
        mpshort_model.fix_tree(destructive=False)
        got = self.got(mpshort_model)
        expected = self.expected_with_holes[mpshort_model]
        assert got == expected
        mpshort_model.find_problems()

    def test_fix_tree_destructive(self, mpshort_model):
        self.add_broken_test_data(mpshort_model)
        mpshort_model.fix_tree(destructive=True)
        got = self.got(mpshort_model)
        expected = self.expected_no_holes[mpshort_model]
        assert got == expected
        mpshort_model.find_problems()


class TestIssues(TestTreeBase):
    # test for http://code.google.com/p/django-treebeard/issues/detail?id=14

    def test_many_to_many_django_user_anonymous(self, mpm2muser_model):
        # Using AnonymousUser() in the querysets will expose non-treebeard
        # related problems in Django 1.0
        #
        # Postgres:
        #   ProgrammingError: can't adapt
        # SQLite:
        #   InterfaceError: Error binding parameter 4 - probably unsupported
        #   type.
        # MySQL compared a string to an integer field:
        #   `treebeard_mp_testissue14_users`.`user_id` = 'AnonymousUser'
        #
        # Using a None field instead works (will be translated to IS NULL).
        #
        # anonuserobj = AnonymousUser()
        anonuserobj = None

        def qs_check(qs, expected):
            assert [o.name for o in qs] == expected

        def qs_check_first_or_user(expected, root, user):
            qs_check(
                root.get_children().filter(Q(name="first") | Q(users=user)),
                expected)

        user = User.objects.create_user('test_user', 'test@example.com',
                                        'testpasswd')
        user.save()
        root = mpm2muser_model.add_root(name="the root node")

        root.add_child(name="first")
        second = root.add_child(name="second")

        qs_check(root.get_children(), ['first', 'second'])
        qs_check(root.get_children().filter(Q(name="first")), ['first'])
        qs_check(root.get_children().filter(Q(users=user)), [])

        qs_check_first_or_user(['first'], root, user)

        qs_check_first_or_user(['first', 'second'], root, anonuserobj)

        user = User.objects.get(username="test_user")
        second.users.add(user)
        qs_check_first_or_user(['first', 'second'], root, user)

        qs_check_first_or_user(['first'], root, anonuserobj)


class TestMoveNodeForm(TestNonEmptyTree):
    def _get_nodes_list(self, nodes):
        return [(pk, '%sNode %d' % ('&nbsp;' * 4 * (depth - 1), pk))
                for pk, depth in nodes]

    def _assert_nodes_in_choices(self, form, nodes):
        choices = form.fields['_ref_node_id'].choices
        assert 0 == choices.pop(0)[0]
        assert nodes == [(choice[0], choice[1]) for choice in choices]

    def _move_node_helper(self, node, safe_parent_nodes):
        form_class = movenodeform_factory(type(node))
        form = form_class(instance=node)
        assert ['desc', '_position', '_ref_node_id'] == list(
            form.base_fields.keys())
        got = [choice[0] for choice in form.fields['_position'].choices]
        assert ['first-child', 'left', 'right'] == got
        nodes = self._get_nodes_list(safe_parent_nodes)
        self._assert_nodes_in_choices(form, nodes)

    def _get_node_ids_and_depths(self, nodes):
        return [(node.pk, node.get_depth()) for node in nodes]

    def test_form_root_node(self, model):
        nodes = list(model.get_tree())
        node = nodes.pop(0)
        safe_parent_nodes = self._get_node_ids_and_depths(nodes)
        self._move_node_helper(node, safe_parent_nodes)

    def test_form_leaf_node(self, model):
        nodes = list(model.get_tree())
        node = nodes.pop()
        safe_parent_nodes = self._get_node_ids_and_depths(nodes)
        self._move_node_helper(node, safe_parent_nodes)

    def test_form_admin(self, model):
        request = None
        nodes = list(model.get_tree())
        safe_parent_nodes = self._get_node_ids_and_depths(nodes)
        for node in model.objects.all():
            site = AdminSite()
            form_class = movenodeform_factory(model)
            admin_class = admin_factory(form_class)
            ma = admin_class(model, site)
            got = list(ma.get_form(request).base_fields.keys())
            desc_pos_refnodeid = ['desc', '_position', '_ref_node_id']
            assert desc_pos_refnodeid == got
            got = ma.get_fieldsets(request)
            expected = [(None, {'fields': desc_pos_refnodeid})]
            assert got == expected
            got = ma.get_fieldsets(request, node)
            assert got == expected
            form = ma.get_form(request)()
            nodes = self._get_nodes_list(safe_parent_nodes)
            self._assert_nodes_in_choices(form, nodes)


class TestModelAdmin(TestNonEmptyTree):
    def test_default_fields(self, model):
        site = AdminSite()
        form_class = movenodeform_factory(model)
        admin_class = admin_factory(form_class)
        ma = admin_class(model, site)
        assert list(ma.get_form(None).base_fields.keys()) == [
            'desc', '_position', '_ref_node_id']


class TestSortedForm(TestTreeSorted):
    def test_sorted_form(self, sorted_model):
        sorted_model.add_root(val1=3, val2=3, desc='zxy')
        sorted_model.add_root(val1=1, val2=4, desc='bcd')
        sorted_model.add_root(val1=2, val2=5, desc='zxy')
        sorted_model.add_root(val1=3, val2=3, desc='abc')
        sorted_model.add_root(val1=4, val2=1, desc='fgh')
        sorted_model.add_root(val1=3, val2=3, desc='abc')
        sorted_model.add_root(val1=2, val2=2, desc='qwe')
        sorted_model.add_root(val1=3, val2=2, desc='vcx')

        form_class = movenodeform_factory(sorted_model)
        form = form_class()
        assert list(form.fields.keys()) == ['val1', 'val2', 'desc',
                                            '_position', '_ref_node_id']

        form = form_class(instance=sorted_model.objects.get(desc='bcd'))
        assert list(form.fields.keys()) == ['val1', 'val2', 'desc',
                                            '_position', '_ref_node_id']
        assert 'id__position' in str(form)
        assert 'id__ref_node_id' in str(form)


class TestForm(TestNonEmptyTree):
    def test_form(self, model):
        form_class = movenodeform_factory(model)
        form = form_class()
        assert list(form.fields.keys()) == ['desc', '_position',
                                            '_ref_node_id']

        form = form_class(instance=model.objects.get(desc='1'))
        assert list(form.fields.keys()) == ['desc', '_position',
                                            '_ref_node_id']
        assert 'id__position' in str(form)
        assert 'id__ref_node_id' in str(form)

    def test_move_node_form(self, model):
        form_class = movenodeform_factory(model)

        bad_node = model.objects.get(desc='1').add_child(
            desc='Benign<script>alert("Compromised");</script>'
        )

        form = form_class(instance=bad_node)
        rendered_html = form.as_p()
        assert "Benign" in rendered_html
        assert "<script>" not in rendered_html
        assert "&lt;script&gt;" in rendered_html

    def test_get_position_ref_node(self, model):
        form_class = movenodeform_factory(model)

        instance_parent = model.objects.get(desc='1')
        form = form_class(instance=instance_parent)
        assert form._get_position_ref_node(instance_parent) == {
            '_position': 'first-child',
            '_ref_node_id': ''
        }

        instance_child = model.objects.get(desc='21')
        form = form_class(instance=instance_child)
        assert form._get_position_ref_node(instance_child) == {
            '_position': 'first-child',
            '_ref_node_id': model.objects.get(desc='2').pk
        }

        instance_grandchild = model.objects.get(desc='22')
        form = form_class(instance=instance_grandchild)
        assert form._get_position_ref_node(instance_grandchild) == {
            '_position': 'right',
            '_ref_node_id': model.objects.get(desc='21').pk
        }

        instance_grandchild = model.objects.get(desc='231')
        form = form_class(instance=instance_grandchild)
        assert form._get_position_ref_node(instance_grandchild) == {
            '_position': 'first-child',
            '_ref_node_id': model.objects.get(desc='23').pk
        }

    def test_clean_cleaned_data(self, model):
        instance_parent = model.objects.get(desc='1')
        _position = 'first-child'
        _ref_node_id = ''
        form_class = movenodeform_factory(model)
        form = form_class(
            instance=instance_parent,
            data={
                '_position': _position,
                '_ref_node_id': _ref_node_id,
                'desc': instance_parent.desc
            }
        )
        assert form.is_valid()
        assert form._clean_cleaned_data() == (_position, _ref_node_id)

    def test_save_edit(self, model):
        instance_parent = model.objects.get(desc='1')
        original_count = len(model.objects.all())
        form_class = movenodeform_factory(model)
        form = form_class(
            instance=instance_parent,
            data={
                '_position': 'first-child',
                '_ref_node_id': model.objects.get(desc='2').pk,
                'desc': instance_parent.desc
            }
        )
        assert form.is_valid()
        saved_instance = form.save()
        assert original_count == model.objects.all().count()
        assert saved_instance.get_children_count() == 0
        assert saved_instance.get_depth() == 2
        assert not saved_instance.is_root()
        assert saved_instance.is_leaf()

        # Return to original state
        form_class = movenodeform_factory(model)
        form = form_class(
            instance=saved_instance,
            data={
                '_position': 'first-child',
                '_ref_node_id': '',
                'desc': saved_instance.desc
            }
        )
        assert form.is_valid()
        restored_instance = form.save()
        assert original_count == model.objects.all().count()
        assert restored_instance.get_children_count() == 0
        assert restored_instance.get_depth() == 1
        assert restored_instance.is_root()
        assert restored_instance.is_leaf()

    def test_save_new(self, model):
        original_count = model.objects.all().count()
        assert original_count == 10
        _position = 'first-child'
        form_class = movenodeform_factory(model)
        form = form_class(
            data={'_position': _position, 'desc': 'New Form Test'})
        assert form.is_valid()
        assert form.save() is not None
        assert original_count < model.objects.all().count()


class TestAdminTreeTemplateTags(TestCase):
    def test_treebeard_css(self):
        template = Template("{% load admin_tree %}{% treebeard_css %}")
        context = Context()
        rendered = template.render(context)
        expected = ('<link rel="stylesheet" type="text/css" '
                    'href="/treebeard/treebeard-admin.css"/>')
        assert expected == rendered

    def test_treebeard_js(self):
        template = Template("{% load admin_tree %}{% treebeard_js %}")
        context = Context()
        rendered = template.render(context)
        expected = ('<script type="text/javascript" src="jsi18n"></script>'
                    '<script type="text/javascript" '
                    'src="/treebeard/treebeard-admin.js"></script>'
                    '<script>(function($){'
                    'jQuery = $.noConflict(true);'
                    '})(django.jQuery);</script>'
                    '<script type="text/javascript" '
                    'src="/treebeard/jquery-ui.min.js"></script>')
        assert expected == rendered

    def test_get_static_url(self):
        with self.settings(STATIC_URL=None, MEDIA_URL=None):
            assert get_static_url() == '/'
        with self.settings(STATIC_URL='/static/', MEDIA_URL=None):
            assert get_static_url() == '/static/'
        with self.settings(STATIC_URL=None, MEDIA_URL='/media/'):
            assert get_static_url() == '/media/'
        with self.settings(STATIC_URL='/static/', MEDIA_URL='/media/'):
            assert get_static_url() == '/static/'


class TestAdminTree(TestNonEmptyTree):
    template = Template('{% load admin_tree %}{% spaceless %}'
                        '{% result_tree cl request %}{% endspaceless %}')

    def test_result_tree(self, model_without_proxy):
        """
        Verifies that inclusion tag result_list generates a table when with
        default ModelAdmin settings.
        """
        model = model_without_proxy
        request = RequestFactory().get('/admin/tree/')
        request.user = AnonymousUser()
        site = AdminSite()
        form_class = movenodeform_factory(model)
        admin_class = admin_factory(form_class)
        m = admin_class(model, site)
        list_display = m.get_list_display(request)
        list_display_links = m.get_list_display_links(request, list_display)
        if DJANGO_VERSION < (2, 1):
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m)
        else:
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m, None)
        cl.formset = None
        context = Context({'cl': cl,
                           'request': request})
        table_output = self.template.render(context)
        # We have the same amount of drag handlers as objects
        drag_handler = '<td class="drag-handler"><span>&nbsp;</span></td>'
        assert table_output.count(drag_handler) == model.objects.count()
        # All nodes are in the result tree
        for object in model.objects.all():
            url = cl.url_for_result(object)
            node = '<a href="%s">Node %i</a>' % (url, object.pk)
            assert node in table_output
        # Unfiltered
        assert '<input type="hidden" id="has-filters" value="0"/>' in \
               table_output

    def test_unicode_result_tree(self, model_with_unicode):
        """
        Verifies that inclusion tag result_list generates a table when with
        default ModelAdmin settings.
        """
        model = model_with_unicode
        # Add a unicode description
        model.add_root(desc='áéîøü')
        request = RequestFactory().get('/admin/tree/')
        request.user = AnonymousUser()
        site = AdminSite()
        form_class = movenodeform_factory(model)
        ModelAdmin = admin_factory(form_class)

        class UnicodeModelAdmin(ModelAdmin):
            list_display = ('__str__', 'desc')

        m = UnicodeModelAdmin(model, site)
        list_display = m.get_list_display(request)
        list_display_links = m.get_list_display_links(request, list_display)
        if DJANGO_VERSION < (2, 1):
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m)
        else:
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m, None)
        cl.formset = None
        context = Context({'cl': cl,
                           'request': request})
        table_output = self.template.render(context)
        # We have the same amount of drag handlers as objects
        drag_handler = '<td class="drag-handler"><span>&nbsp;</span></td>'
        assert table_output.count(drag_handler) == model.objects.count()
        # All nodes are in the result tree
        for object in model.objects.all():
            url = cl.url_for_result(object)
            node = '<a href="%s">%s</a>' % (url, object.desc)
            assert node in table_output
        # Unfiltered
        assert '<input type="hidden" id="has-filters" value="0"/>' in \
               table_output

    def test_result_filtered(self, model_without_proxy):
        """ Test template changes with filters or pagination.
        """
        model = model_without_proxy
        # Filtered GET
        request = RequestFactory().get('/admin/tree/?desc=1')
        request.user = AnonymousUser()
        site = AdminSite()
        form_class = movenodeform_factory(model)
        admin_class = admin_factory(form_class)
        m = admin_class(model, site)
        list_display = m.get_list_display(request)
        list_display_links = m.get_list_display_links(request, list_display)
        if DJANGO_VERSION < (2, 1):
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m)
        else:
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m, None)
        cl.formset = None
        context = Context({'cl': cl,
                           'request': request})
        table_output = self.template.render(context)
        # Filtered
        assert '<input type="hidden" id="has-filters" value="1"/>' in \
               table_output

        # Not Filtered GET, it should ignore pagination
        request = RequestFactory().get('/admin/tree/?p=1')
        request.user = AnonymousUser()
        list_display = m.get_list_display(request)
        list_display_links = m.get_list_display_links(request, list_display)
        if DJANGO_VERSION < (2, 1):
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m)
        else:
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m, None)
        cl.formset = None
        context = Context({'cl': cl,
                           'request': request})
        table_output = self.template.render(context)
        # Not Filtered
        assert '<input type="hidden" id="has-filters" value="0"/>' in \
               table_output

        # Not Filtered GET, it should ignore all
        request = RequestFactory().get('/admin/tree/?all=1')
        request.user = AnonymousUser()
        list_display = m.get_list_display(request)
        list_display_links = m.get_list_display_links(request, list_display)
        if DJANGO_VERSION < (2, 1):
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m)
        else:
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m, None)
        cl.formset = None
        context = Context({'cl': cl,
                           'request': request})
        table_output = self.template.render(context)
        # Not Filtered
        assert '<input type="hidden" id="has-filters" value="0"/>' in \
               table_output


class TestAdminTreeList(TestNonEmptyTree):
    template = Template('{% load admin_tree_list %}{% spaceless %}'
                        '{% result_tree cl request %}{% endspaceless %}')

    def test_result_tree_list(self, model_without_proxy):
        """
        Verifies that inclusion tag result_list generates a table when with
        default ModelAdmin settings.
        """
        model = model_without_proxy
        request = RequestFactory().get('/admin/tree/')
        request.user = AnonymousUser()
        site = AdminSite()
        form_class = movenodeform_factory(model)
        admin_class = admin_factory(form_class)
        m = admin_class(model, site)
        list_display = m.get_list_display(request)
        list_display_links = m.get_list_display_links(request, list_display)
        if DJANGO_VERSION < (2, 1):
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m)
        else:
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m, None)
        cl.formset = None
        context = Context({'cl': cl,
                           'request': request})
        table_output = self.template.render(context)

        output_template = '<li><a href="%i/" >Node %i</a>'
        for object in model.objects.all():
            expected_output = output_template % (object.pk, object.pk)
            assert expected_output in table_output

    def test_result_tree_list_with_action(self, model_without_proxy):
        model = model_without_proxy
        request = RequestFactory().get('/admin/tree/')
        request.user = AnonymousUser()
        site = AdminSite()
        form_class = movenodeform_factory(model)
        admin_class = admin_factory(form_class)
        m = admin_class(model, site)
        list_display = m.get_list_display(request)
        list_display_links = m.get_list_display_links(request, list_display)
        if DJANGO_VERSION < (2, 1):
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m)
        else:
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m, None)
        cl.formset = None
        context = Context({'cl': cl,
                           'request': request,
                           'action_form': True})
        table_output = self.template.render(context)
        output_template = ('<input type="checkbox" class="action-select" '
                           'value="%i" name="_selected_action" />'
                           '<a href="%i/" >Node %i</a>')

        for object in model.objects.all():
            expected_output = output_template % (object.pk, object.pk,
                                                 object.pk)
            assert expected_output in table_output

    def test_result_tree_list_with_get(self, model_without_proxy):
        model = model_without_proxy
        # Test t GET parameter with value id
        request = RequestFactory().get(
            '/admin/tree/?{0}=id'.format(TO_FIELD_VAR))
        request.user = AnonymousUser()
        site = AdminSite()
        admin_register_all(site)
        form_class = movenodeform_factory(model)
        admin_class = admin_factory(form_class)
        m = admin_class(model, site)
        list_display = m.get_list_display(request)
        list_display_links = m.get_list_display_links(request, list_display)
        if DJANGO_VERSION < (2, 1):
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m)
        else:
            cl = ChangeList(request, model, list_display, list_display_links,
                            m.list_filter, m.date_hierarchy, m.search_fields,
                            m.list_select_related, m.list_per_page,
                            m.list_max_show_all, m.list_editable, m, None)
        cl.formset = None
        context = Context({'cl': cl,
                           'request': request})
        table_output = self.template.render(context)
        output_template = "opener.dismissRelatedLookupPopup(window, '%i');"
        for object in model.objects.all():
            expected_output = output_template % object.pk
            assert expected_output in table_output


class TestTreeAdmin(TestNonEmptyTree):
    site = AdminSite()

    def _create_superuser(self, username):
        return User.objects.create(username=username, is_superuser=True)

    def _mocked_authenticated_request(self, url, user):
        request_factory = RequestFactory()
        request = request_factory.get(url)
        request.user = user
        return request

    def _mocked_request(self, data):
        request_factory = RequestFactory()
        request = request_factory.post('/', data=data)
        setattr(request, 'session', 'session')
        messages = FallbackStorage(request)
        setattr(request, '_messages', messages)
        return request

    def _get_admin_obj(self, model_class):
        form_class = movenodeform_factory(model_class)
        admin_class = admin_factory(form_class)
        return admin_class(model_class, self.site)

    def test_changelist_view(self):
        tmp_user = self._create_superuser('changelist_tmp')
        request = self._mocked_authenticated_request('/', tmp_user)
        admin_obj = self._get_admin_obj(models.AL_TestNode)
        admin_obj.changelist_view(request)
        assert admin_obj.change_list_template == 'admin/tree_list.html'

        admin_obj = self._get_admin_obj(models.MP_TestNode)
        admin_obj.changelist_view(request)
        assert admin_obj.change_list_template != 'admin/tree_list.html'

    def test_get_node(self, model):
        admin_obj = self._get_admin_obj(model)
        target = model.objects.get(desc='2')
        assert admin_obj.get_node(target.pk) == target

    def test_move_node_validate_keyerror(self, model):
        admin_obj = self._get_admin_obj(model)
        request = self._mocked_request(data={})
        response = admin_obj.move_node(request)
        assert response.status_code == 400
        request = self._mocked_request(data={'node_id': 1})
        response = admin_obj.move_node(request)
        assert response.status_code == 400

    def test_move_node_validate_valueerror(self, model):
        admin_obj = self._get_admin_obj(model)
        request = self._mocked_request(data={'node_id': 1,
                                             'sibling_id': 2,
                                             'as_child': 'invalid'})
        response = admin_obj.move_node(request)
        assert response.status_code == 400

    def test_move_validate_missing_nodeorderby(self, model):
        node = model.objects.get(desc='231')
        admin_obj = self._get_admin_obj(model)
        request = self._mocked_request(data={})
        response = admin_obj.try_to_move_node(True, node, 'sorted-child',
                                              request, target=node)
        assert response.status_code == 400

        response = admin_obj.try_to_move_node(True, node, 'sorted-sibling',
                                              request, target=node)
        assert response.status_code == 400

    def test_move_validate_invalid_pos(self, model):
        node = model.objects.get(desc='231')
        admin_obj = self._get_admin_obj(model)
        request = self._mocked_request(data={})
        response = admin_obj.try_to_move_node(True, node, 'invalid_pos',
                                              request, target=node)
        assert response.status_code == 400

    def test_move_validate_to_descendant(self, model):
        node = model.objects.get(desc='2')
        target = model.objects.get(desc='231')
        admin_obj = self._get_admin_obj(model)
        request = self._mocked_request(data={})
        response = admin_obj.try_to_move_node(True, node, 'first-sibling',
                                              request, target)
        assert response.status_code == 400

    def test_move_left(self, model):
        node = model.objects.get(desc='231')
        target = model.objects.get(desc='2')

        admin_obj = self._get_admin_obj(model)
        request = self._mocked_request(data={'node_id': node.pk,
                                             'sibling_id': target.pk,
                                             'as_child': 0})
        response = admin_obj.move_node(request)
        assert response.status_code == 200
        expected = [('1', 1, 0),
                    ('231', 1, 0),
                    ('2', 1, 4),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected

    def test_move_last_child(self, model):
        node = model.objects.get(desc='231')
        target = model.objects.get(desc='2')

        admin_obj = self._get_admin_obj(model)
        request = self._mocked_request(data={'node_id': node.pk,
                                             'sibling_id': target.pk,
                                             'as_child': 1})
        response = admin_obj.move_node(request)
        assert response.status_code == 200
        expected = [('1', 1, 0),
                    ('2', 1, 5),
                    ('21', 2, 0),
                    ('22', 2, 0),
                    ('23', 2, 0),
                    ('24', 2, 0),
                    ('231', 2, 0),
                    ('3', 1, 0),
                    ('4', 1, 1),
                    ('41', 2, 0)]
        assert self.got(model) == expected
