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

"""
 (c) 2017 - Copyright Red Hat Inc

 Authors:
   Pierre-Yves Chibon <pingou@pingoured.fr>

"""

from __future__ import unicode_literals, absolute_import

import datetime
import os
import shutil
import sys
import tempfile
import time
import unittest
from io import open

import pygit2
from mock import patch, MagicMock

sys.path.insert(
    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
)

import pagure.lib.git
import pagure.lib.query
import tests
from pagure.lib.repo import PagureRepo


CORE_CONFIG = """repo test
  R   = @all
  RW+ = pingou

repo docs/test
  R   = @all
  RW+ = pingou

repo tickets/test
  RW+ = pingou

repo requests/test
  RW+ = pingou

repo test2
  R   = @all
  RW+ = pingou

repo docs/test2
  R   = @all
  RW+ = pingou

repo tickets/test2
  RW+ = pingou

repo requests/test2
  RW+ = pingou

repo somenamespace/test3
  R   = @all
  RW+ = pingou

repo docs/somenamespace/test3
  R   = @all
  RW+ = pingou

repo tickets/somenamespace/test3
  RW+ = pingou

repo requests/somenamespace/test3
  RW+ = pingou"""


class PagureLibGitoliteConfigtests(tests.Modeltests):
    """ Tests for pagure.lib.git """

    maxDiff = None

    def setUp(self):
        """ Set up the environnment, ran before every tests. """
        super(PagureLibGitoliteConfigtests, self).setUp()

        tests.create_projects(self.session)

        self.outputconf = os.path.join(self.path, "test_gitolite.conf")

        self.preconf = os.path.join(self.path, "header_gitolite")
        with open(self.preconf, "w") as stream:
            stream.write("# this is a header that is manually added\n")
            stream.write("\n")
            stream.write("@group1 = foo bar baz\n")
            stream.write("@group2 = threebean puiterwijk kevin pingou\n")

        self.postconf = os.path.join(self.path, "footer_gitolite")
        with open(self.postconf, "w", encoding="utf-8") as stream:
            stream.write("# end of generated configuration\n")
            stream.write(r"# \ó/")
            stream.write("\n# end of footer\n")

    def tearDown(self):
        """ Tearn down the environnment, ran before every tests. """
        super(PagureLibGitoliteConfigtests, self).tearDown()

        if os.path.exists(self.outputconf):
            os.unlink(self.outputconf)
        self.assertFalse(os.path.exists(self.outputconf))

    def test_write_gitolite_pre_post_projectNone(self):
        """ Test the write_gitolite_acls function of pagure.lib.git with
        a postconf set """

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=None,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf, "r") as stream:
            data = stream.read()

        exp = (
            r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
%s

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
            % CORE_CONFIG
        )
        # print data
        self.assertEqual(data, exp)

    def test_write_gitolite_pre_post_projectNone_to_existing_file(self):
        """ Test the write_gitolite_acls function of pagure.lib.git with
        a postconf set with existing output file """

        with open(self.outputconf, "w") as stream:
            pass

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=None,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()
        self.assertEqual(data, "")

    def test_write_gitolite_pre_post_project_1(self):
        """ Test the write_gitolite_acls function of pagure.lib.git with
        a postconf set """

        with open(self.outputconf, "w") as stream:
            pass

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=-1,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = (
            r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
%s

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
            % CORE_CONFIG
        )

        # print data
        self.assertEqual(data, exp)

    def test_write_gitolite_pre_post_project_test(self):
        """ Test the write_gitolite_acls function of pagure.lib.git with
        a postconf set """

        with open(self.outputconf, "w") as stream:
            pass

        project = pagure.lib.query._get_project(self.session, "test")

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=project,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
repo test
  R   = @all
  RW+ = pingou

repo docs/test
  R   = @all
  RW+ = pingou

repo tickets/test
  RW+ = pingou

repo requests/test
  RW+ = pingou

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
        # print data
        self.assertEqual(data, exp)

    def test_write_gitolite_pre_post_project_test_full_file(self):
        """ Test the write_gitolite_acls function of pagure.lib.git with
        a postconf set """

        # Re-generate the gitolite config for all the projects
        self.test_write_gitolite_pre_post_project_1()
        self.assertTrue(os.path.exists(self.outputconf))

        project = pagure.lib.query._get_project(self.session, "test")
        project.user_id = 2
        self.session.add(project)
        self.session.commit()

        project = pagure.lib.query._get_project(self.session, "test")
        msg = pagure.lib.query.add_user_to_project(
            self.session,
            project=project,
            new_user="pingou",
            user="foo",
            access="commit",
        )
        self.assertEqual(msg, "User added")
        self.session.commit()

        project = pagure.lib.query._get_project(self.session, "test")
        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=project,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
repo test2
  R   = @all
  RW+ = pingou

repo docs/test2
  R   = @all
  RW+ = pingou

repo tickets/test2
  RW+ = pingou

repo requests/test2
  RW+ = pingou

repo somenamespace/test3
  R   = @all
  RW+ = pingou

repo docs/somenamespace/test3
  R   = @all
  RW+ = pingou

repo tickets/somenamespace/test3
  RW+ = pingou

repo requests/somenamespace/test3
  RW+ = pingou

repo test
  R   = @all
  RW+ = foo
  RW+ = pingou

repo docs/test
  R   = @all
  RW+ = foo
  RW+ = pingou

repo tickets/test
  RW+ = foo
  RW+ = pingou

repo requests/test
  RW+ = foo
  RW+ = pingou

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
        # print data
        self.assertEqual(data, exp)

    @patch.dict(
        "pagure.config.config", {"ENABLE_DOCS": False, "ENABLE_TICKETS": False}
    )
    def test_write_gitolite_disabled_docs_tickets(self):
        """ Test the write_gitolite_acls function when docs and tickets
        are disabled """

        # Re-generate the gitolite config for all the projects
        project = pagure.lib.query._get_project(self.session, "test")
        project.user_id = 2
        self.session.add(project)
        self.session.commit()

        project = pagure.lib.query._get_project(self.session, "test")
        msg = pagure.lib.query.add_user_to_project(
            self.session,
            project=project,
            new_user="pingou",
            user="foo",
            access="commit",
        )
        self.assertEqual(msg, "User added")
        self.session.commit()

        project = pagure.lib.query._get_project(self.session, "test")
        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session, self.outputconf, project=project
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = """repo test
  R   = @all
  RW+ = foo
  RW+ = pingou

repo requests/test
  RW+ = foo
  RW+ = pingou

repo test2
  R   = @all
  RW+ = pingou

repo requests/test2
  RW+ = pingou

repo somenamespace/test3
  R   = @all
  RW+ = pingou

repo requests/somenamespace/test3
  RW+ = pingou

# end of body
"""
        self.assertEqual(data, exp)


class PagureLibGitoliteGroupConfigtests(tests.Modeltests):
    """ Tests for generating the gitolite configuration file for a group
    change

    """

    maxDiff = None

    def setUp(self):
        """ Set up the environnment, ran before every tests. """
        super(PagureLibGitoliteGroupConfigtests, self).setUp()

        tests.create_projects(self.session)

        pagure.lib.query.add_group(
            self.session,
            group_name="grp",
            display_name="grp group",
            description=None,
            group_type="user",
            user="pingou",
            is_admin=False,
            blacklist=[],
        )
        pagure.lib.query.add_group(
            self.session,
            group_name="grp2",
            display_name="grp2 group",
            description=None,
            group_type="user",
            user="foo",
            is_admin=False,
            blacklist=[],
        )
        self.session.commit()

        self.outputconf = os.path.join(self.path, "test_gitolite.conf")

        self.preconf = os.path.join(self.path, "header_gitolite")
        with open(self.preconf, "w") as stream:
            stream.write("# this is a header that is manually added\n")
            stream.write("\n")
            stream.write("@group1 = foo bar baz\n")
            stream.write("@group2 = threebean puiterwijk kevin pingou\n")

        self.postconf = os.path.join(self.path, "footer_gitolite")
        with open(self.postconf, "w") as stream:
            stream.write("# end of generated configuration\n")
            stream.write(r"# \ó/")
            stream.write("\n# end of footer\n")

    def tearDown(self):
        """ Tearn down the environnment, ran before every tests. """
        super(PagureLibGitoliteGroupConfigtests, self).tearDown()

        if os.path.exists(self.outputconf):
            os.unlink(self.outputconf)
        self.assertFalse(os.path.exists(self.outputconf))

    def test_write_gitolite_project_test_group(self):
        """ Test the write_gitolite_acls when updating a single group. """

        with open(self.outputconf, "w") as stream:
            pass

        project = pagure.lib.query._get_project(self.session, "test")
        group = pagure.lib.query.search_groups(self.session, group_name="grp")

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=project,
            preconf=self.preconf,
            postconf=self.postconf,
            group=group,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
@grp  = pingou

repo test
  R   = @all
  RW+ = pingou

repo docs/test
  R   = @all
  RW+ = pingou

repo tickets/test
  RW+ = pingou

repo requests/test
  RW+ = pingou

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
        # print data
        self.assertEqual(data, exp)

    def test_write_gitolite_project_test_all_groups(self):
        """ Test the write_gitolite_acls when updating all groups. """

        with open(self.outputconf, "w") as stream:
            pass

        project = pagure.lib.query._get_project(self.session, "test")

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=project,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
@grp  = pingou
@grp2  = foo
# end of groups

repo test
  R   = @all
  RW+ = pingou

repo docs/test
  R   = @all
  RW+ = pingou

repo tickets/test
  RW+ = pingou

repo requests/test
  RW+ = pingou

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
        # print data
        self.assertEqual(data, exp)

    def test_write_gitolite_project_all_projects_groups(self):
        """ Test the generating the entire gitolite config. """

        with open(self.outputconf, "w") as stream:
            pass

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=-1,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = (
            r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
@grp  = pingou
@grp2  = foo
# end of groups

%s

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
            % CORE_CONFIG
        )
        # print data
        self.assertEqual(data, exp)

    def test_write_gitolite_project_all_projects_one_group(self):
        """ Test the generating the entire gitolite config. """

        # Generate the full gitolite config that we will update
        self.test_write_gitolite_project_all_projects_groups()

        project = pagure.lib.query._get_project(self.session, "test")
        group = pagure.lib.query.search_groups(self.session, group_name="grp")

        # Let's add `foo` to `grp` so something changes
        msg = pagure.lib.query.add_user_to_group(
            self.session,
            username="foo",
            group=group,
            user="pingou",
            is_admin=False,
        )
        self.session.commit()
        self.assertEqual(msg, "User `foo` added to the group `grp`.")

        # Let's add `foo` to `test` so the project changes as well
        msg = pagure.lib.query.add_user_to_project(
            self.session,
            project=project,
            new_user="foo",
            user="pingou",
            access="commit",
        )
        self.assertEqual(msg, "User added")
        self.session.commit()

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=project,
            group=group,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
@grp  = foo pingou
@grp2  = foo
# end of groups

repo test2
  R   = @all
  RW+ = pingou

repo docs/test2
  R   = @all
  RW+ = pingou

repo tickets/test2
  RW+ = pingou

repo requests/test2
  RW+ = pingou

repo somenamespace/test3
  R   = @all
  RW+ = pingou

repo docs/somenamespace/test3
  R   = @all
  RW+ = pingou

repo tickets/somenamespace/test3
  RW+ = pingou

repo requests/somenamespace/test3
  RW+ = pingou

repo test
  R   = @all
  RW+ = pingou
  RW+ = foo

repo docs/test
  R   = @all
  RW+ = pingou
  RW+ = foo

repo tickets/test
  RW+ = pingou
  RW+ = foo

repo requests/test
  RW+ = pingou
  RW+ = foo

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
        # print data
        self.assertEqual(data, exp)

    def test_write_gitolite_delete_group(self):
        """ Test the updating the gitolite config after having
        deleted a group.
        """

        # Generate the full gitolite config that we will update
        self.test_write_gitolite_project_all_projects_groups()

        # Delete the group `grp`
        self.assertEqual(len(pagure.lib.query.search_groups(self.session)), 2)
        group = pagure.lib.query.search_groups(self.session, group_name="grp")
        self.session.delete(group)
        self.session.commit()
        self.assertEqual(len(pagure.lib.query.search_groups(self.session)), 1)

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(
            self.session,
            self.outputconf,
            project=None,
            preconf=self.preconf,
            postconf=self.postconf,
        )
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = (
            r"""# this is a header that is manually added

@group1 = foo bar baz
@group2 = threebean puiterwijk kevin pingou

# end of header
@grp2  = foo
# end of groups

%s

# end of body
# end of generated configuration
# \ó/
# end of footer

"""
            % CORE_CONFIG
        )
        # print data
        self.assertEqual(data, exp)

    @patch("pagure.lib.git_auth.get_git_auth_helper")
    def test_task_generate_gitolite_acls_one_group(self, get_helper):
        """ Test the generate_gitolite_acls task to ensure if group is None
        then None is passed to the helper. """
        helper = MagicMock()
        get_helper.return_value = helper
        pagure.lib.query.SESSIONMAKER = self.session.session_factory

        pagure.lib.tasks.generate_gitolite_acls(
            namespace=None, name="test", user=None, group=None
        )

        get_helper.assert_called_with()
        args = helper.generate_acls.call_args
        self.assertIsNone(args[1].get("group"))
        self.assertIsNotNone(args[1].get("project"))

    def test_write_gitolite_project_test_private(self):
        """ Test the write_gitolite_acls function of pagure.lib.git with
        a postconf set """

        # Make the test project private
        project = pagure.lib.query._get_project(self.session, "test")
        project.private = True
        self.session.add(project)
        self.session.commit()

        # Re-generate the gitolite config just for this project
        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(self.session, self.outputconf, project=None)
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = """@grp  = pingou
@grp2  = foo
# end of groups

repo test
  RW+ = pingou

repo docs/test
  RW+ = pingou

repo tickets/test
  RW+ = pingou

repo requests/test
  RW+ = pingou

repo test2
  R   = @all
  RW+ = pingou

repo docs/test2
  R   = @all
  RW+ = pingou

repo tickets/test2
  RW+ = pingou

repo requests/test2
  RW+ = pingou

repo somenamespace/test3
  R   = @all
  RW+ = pingou

repo docs/somenamespace/test3
  R   = @all
  RW+ = pingou

repo tickets/somenamespace/test3
  RW+ = pingou

repo requests/somenamespace/test3
  RW+ = pingou

# end of body
"""
        # print data
        self.assertEqual(data, exp)

    def test_remove_acls(self):
        """ Test the remove_acls function of pagure.lib.git when deleting
        a project """
        pagure.config.config["GITOLITE_CONFIG"] = self.outputconf

        with open(self.outputconf, "w") as stream:
            pass

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(self.session, self.outputconf, project=-1)
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = (
            """@grp  = pingou
@grp2  = foo
# end of groups

%s

# end of body
"""
            % CORE_CONFIG
        )

        # print data
        self.assertEqual(data, exp)

        # Test removing a project from the existing config
        project = pagure.lib.query.get_authorized_project(
            self.session, project_name="test"
        )

        helper.remove_acls(self.session, project=project)

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = """@grp  = pingou
@grp2  = foo
# end of groups

repo test2
  R   = @all
  RW+ = pingou

repo docs/test2
  R   = @all
  RW+ = pingou

repo tickets/test2
  RW+ = pingou

repo requests/test2
  RW+ = pingou

repo somenamespace/test3
  R   = @all
  RW+ = pingou

repo docs/somenamespace/test3
  R   = @all
  RW+ = pingou

repo tickets/somenamespace/test3
  RW+ = pingou

repo requests/somenamespace/test3
  RW+ = pingou

# end of body
"""

        # print data
        self.assertEqual(data, exp)

    def test_remove_acls_no_project(self):
        """ Test the remove_acls function of pagure.lib.git when no project
        is specified """
        pagure.config.config["GITOLITE_CONFIG"] = self.outputconf

        with open(self.outputconf, "w") as stream:
            pass

        helper = pagure.lib.git_auth.get_git_auth_helper("gitolite3")
        helper.write_gitolite_acls(self.session, self.outputconf, project=-1)
        self.assertTrue(os.path.exists(self.outputconf))

        with open(self.outputconf) as stream:
            data = stream.read()

        exp = (
            """@grp  = pingou
@grp2  = foo
# end of groups

%s

# end of body
"""
            % CORE_CONFIG
        )

        # print data
        self.assertEqual(data, exp)

        # Test nothing changes if no project is specified

        self.assertRaises(
            RuntimeError, helper.remove_acls, self.session, project=None
        )

        with open(self.outputconf) as stream:
            data = stream.read()

        self.assertEqual(data, exp)


if __name__ == "__main__":
    unittest.main(verbosity=2)
