def migration_tree():
    """
      _____(01)_____
     V      V       V
    (02)   (03)    (04)__
     |      |      V     V
      \     /    (05)  (06)
       \   /_____/  \  /
        V VV         VV
        (07)        (08)
           \___  ___/
               VV
              (09)
               V
              (10)
    """
    return [
        Migration(name='01', dependencies=[]),
        Migration(name='02', dependencies=['01']),
        Migration(name='03', dependencies=['01']),
        Migration(name='04', dependencies=['01']),
        Migration(name='05', dependencies=['04']),
        Migration(name='06', dependencies=['04']),
        Migration(name='07', dependencies=['02', '03', '05']),
        Migration(name='08', dependencies=['05', '06']),
        Migration(name='09', dependencies=['07', '08']),
        Migration(name='10', dependencies=['09']),
    ]
    def test_add__if_migration_already_in_graph__should_rebuild_children_links(self, migration_tree):
        random.shuffle(migration_tree)
        migrations = {m.name: m for m in migration_tree}
        expect_children = {
            '01': [migrations['02'], migrations['03'], migrations['04']],
            '02': [migrations['07']],
            '03': [migrations['07']],
            '04': [migrations['05'], migrations['06']],
            '05': [migrations['07'], migrations['08']],
            '06': [migrations['08']],
            '07': [migrations['09']],
            '08': [migrations['09']],
            '09': [migrations['10']],
            '10': []
        }
        another_migration = Migration(name='05', dependencies=['04', '03'], applied=True)

        for m in migration_tree:
            self.obj.add(m)
        self.obj.add(another_migration)

        assert self.obj._children.keys() == expect_children.keys()
        assert all(e in self.obj._children[k]
                   for k in expect_children.keys()
                   for e in expect_children[k])
    def test_verify__if_graph_has_disconnects__should_raise_migration_error(self, migration_tree):
        migration_tree[8] = Migration(name='09', dependencies=[])
        for m in migration_tree:
            self.obj.add(m)

        with pytest.raises(MongoengineMigrateError) as e:
            self.obj.verify()

        assert 'Migrations graph is disconnected' in str(e)
        assert '09' in str(e)
    def test_verify__if_wrong_dependency__should_raise_migration_error(self, migration_tree):
        migration_tree[4] = Migration(name='05', dependencies=['wrong_dep'])
        for m in migration_tree:
            self.obj.add(m)

        with pytest.raises(MongoengineMigrateError) as e:
            self.obj.verify()

        assert 'Unknown dependencies' in str(e)
        assert '05' in str(e)
    def test_add__if_migration_already_in_graph__should_replace_migration_object(self,
                                                                                 migration_tree):
        random.shuffle(migration_tree)
        another_migration = Migration(name='05', dependencies=['04', '03'], applied=True)

        for m in migration_tree:
            self.obj.add(m)
        self.obj.add(another_migration)

        assert self.obj.migrations['05'] is another_migration
    def test_verify__if_migration_is_dependent_on_itself__should_raise_migration_error(self,
                                                                                       migration_tree):
        migration_tree[4] = Migration(name='05', dependencies=['05'])
        for m in migration_tree:
            self.obj.add(m)

        with pytest.raises(MongoengineMigrateError) as e:
            self.obj.verify()

        assert 'dependent on itself' in str(e)
        assert '05' in str(e)
    def test_verify__if_graph_has_no_initial_or_last_migration__should_raise_migration_error(
            self,
            migration_tree
    ):
        migration_tree[0] = Migration(name='01', dependencies=['10'])
        for m in migration_tree:
            self.obj.add(m)

        with pytest.raises(MongoengineMigrateError) as e:
            self.obj.verify()

        assert 'No initial or last children' in str(e)
    def test_eq_ne__if_extra_migration_in_second_graph__graphs_should_be_not_equal(self,
                                                                                   migration_tree):
        obj = MigrationsGraph()
        test_migration05 = Migration(name="test_name", dependencies=['04'])
        for m in migration_tree:
            self.obj.add(m)
            obj.add(m)
        obj.add(test_migration05)

        assert not self.obj == obj
        assert not obj == self.obj
        assert self.obj != obj
        assert obj != self.obj
    def test_verify__if_graph_has_several_last_migrations__should_raise_migration_error(
            self,
            migration_tree
    ):
        migration_tree.append(Migration(name='11', dependencies=['09']))
        for m in migration_tree:
            self.obj.add(m)

        with pytest.raises(MongoengineMigrateError) as e:
            self.obj.verify()

        assert 'Several last migrations' in str(e)
        assert '11' in str(e)
    def test_verify__if_graph_has_several_initial_migrations__should_raise_migration_error(
            self,
            migration_tree
    ):
        migration_tree[4] = Migration(name='05', dependencies=[])
        for m in migration_tree:
            self.obj.add(m)

        with pytest.raises(MongoengineMigrateError) as e:
            self.obj.verify()

        assert 'Several initial migrations' in str(e)
        assert '05' in str(e)
    def test_eq_ne__if_two_migrations_in_graphs_are_differ__graphs_should_be_not_equal(
            self,
            migration_tree
    ):
        obj = MigrationsGraph()
        test_migration05 = Migration(name="test_name", dependencies=['04'])
        for m in migration_tree:
            self.obj.add(m)
            obj.add(m)
        obj.migrations['05'] = test_migration05

        assert not self.obj == obj
        assert not obj == self.obj
        assert self.obj != obj
        assert obj != self.obj