# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Tests for gitweb views.
"""

import json
from unittest.mock import patch

import pytest
from django import urls
from django.contrib.messages.storage.fallback import FallbackStorage
from django.http.response import Http404

from plinth import module_loader
from plinth.errors import ActionError
from plinth.modules.gitweb import views

# For all tests, use plinth.urls instead of urls configured for testing
pytestmark = pytest.mark.urls('plinth.urls')

EXISTING_REPOS = [
    {
        'name': 'something',
        'description': '',
        'owner': '',
        'access': 'public',
        'is_private': False,
        'default_branch': 'master',
    },
    {
        'name': 'something2',
        'description': '',
        'owner': '',
        'access': 'private',
        'is_private': True,
        'default_branch': 'master',
    },
]


@pytest.fixture(autouse=True, scope='module')
def fixture_gitweb_urls():
    """Make sure gitweb app's URLs are part of plinth.urls."""
    with patch('plinth.module_loader._modules_to_load', new=[]) as modules, \
            patch('plinth.urls.urlpatterns', new=[]):
        modules.append('plinth.modules.gitweb')
        module_loader.include_urls()
        yield


def action_run(*args, **kwargs):
    """Action return values."""
    subcommand = args[1][0]
    if subcommand == 'repo-info':
        return json.dumps(EXISTING_REPOS[0])

    elif subcommand == 'check-repo-exists':
        return True

    elif subcommand == 'get-branches':
        return json.dumps({
            "default_branch": "master",
            "branches": ["master", "branch1"]
        })

    return None


@pytest.fixture(autouse=True)
def gitweb_patch():
    """Patch gitweb."""
    with patch('plinth.modules.gitweb.get_repo_list') as get_repo_list, \
            patch('plinth.modules.gitweb.app') as gitweb_app, \
            patch('plinth.actions.superuser_run', side_effect=action_run), \
            patch('plinth.actions.run', side_effect=action_run):
        get_repo_list.return_value = [{
            'name': EXISTING_REPOS[0]['name']
        }, {
            'name': EXISTING_REPOS[1]['name']
        }]
        gitweb_app.update_service_access.return_value = None

        yield


def make_request(request, view, **kwargs):
    """Make request with a message storage."""
    setattr(request, 'session', 'session')
    messages = FallbackStorage(request)
    setattr(request, '_messages', messages)
    response = view(request, **kwargs)

    return response, messages


def test_repos_view(rf):
    """Test that a repo list has correct view data."""
    with patch('plinth.views.AppView.get_context_data',
               return_value={'is_enabled': True}):
        view = views.GitwebAppView.as_view()
        response, _ = make_request(rf.get(''), view)

        assert response.context_data['repos'] == [{
            'name': EXISTING_REPOS[0]['name']
        }, {
            'name': EXISTING_REPOS[1]['name']
        }]
        assert response.status_code == 200


def test_create_repo_view(rf):
    """Test that repo create view sends correct success message."""
    form_data = {
        'gitweb-name': 'something_other',
        'gitweb-description': 'test description',
        'gitweb-owner': 'test owner',
        'gitweb-is_private': True
    }
    request = rf.post(urls.reverse('gitweb:create'), data=form_data)
    view = views.CreateRepoView.as_view()
    response, messages = make_request(request, view)

    assert list(messages)[0].message == 'Repository created.'
    assert response.status_code == 302


def test_create_repo_duplicate_name_view(rf):
    """Test that repo create view shows correct error message."""
    form_data = {
        'gitweb-name': EXISTING_REPOS[0]['name'],
        'gitweb-description': '',
        'gitweb-owner': ''
    }
    request = rf.post(urls.reverse('gitweb:create'), data=form_data)
    view = views.CreateRepoView.as_view()
    response, _ = make_request(request, view)

    assert response.context_data['form'].errors['name'][
        0] == 'A repository with this name already exists.'
    assert response.status_code == 200


def test_create_repo_invalid_name_view(rf):
    """Test that repo create view shows correct error message."""
    form_data = {
        'gitweb-name': '.something_other',
        'gitweb-description': '',
        'gitweb-owner': ''
    }
    request = rf.post(urls.reverse('gitweb:create'), data=form_data)
    view = views.CreateRepoView.as_view()
    response, _ = make_request(request, view)

    assert response.context_data['form'].errors['name'][
        0] == 'Invalid repository name.'
    assert response.status_code == 200


def test_create_repo_failed_view(rf):
    """Test that repo creation failure sends correct error message."""
    general_error_message = "An error occurred while creating the repository."
    error_description = 'some error'
    with patch('plinth.modules.gitweb.create_repo',
               side_effect=ActionError('gitweb', '', error_description)):
        form_data = {
            'gitweb-name': 'something_other',
            'gitweb-description': '',
            'gitweb-owner': ''
        }
        request = rf.post(urls.reverse('gitweb:create'), data=form_data)
        view = views.CreateRepoView.as_view()
        response, messages = make_request(request, view)

        assert list(messages)[0].message == '{0} {1}'.format(
            general_error_message, error_description)
        assert response.status_code == 302


def test_clone_repo_view(rf):
    """Test that cloning repo sends correct succcess message."""
    form_data = {
        'gitweb-name': 'https://example.com/test.git',
        'gitweb-description': '',
        'gitweb-owner': ''
    }
    request = rf.post(urls.reverse('gitweb:create'), data=form_data)
    view = views.CreateRepoView.as_view()
    response, messages = make_request(request, view)

    with pytest.raises(AttributeError):
        getattr(response.context_data['form'], 'errors')

    assert list(messages)[0].message == 'Repository created.'
    assert response.status_code == 302


def test_clone_repo_missing_remote_view(rf):
    """Test that cloning non-existing repo shows correct error message."""
    with patch('plinth.modules.gitweb.repo_exists', return_value=False):
        form_data = {
            'gitweb-name': 'https://example.com/test.git',
            'gitweb-description': '',
            'gitweb-owner': ''
        }
        request = rf.post(urls.reverse('gitweb:create'), data=form_data)
        view = views.CreateRepoView.as_view()
        response, _ = make_request(request, view)

        assert response.context_data['form'].errors['name'][
            0] == 'Remote repository is not available.'
        assert response.status_code == 200


def test_edit_repository_view(rf):
    """Test that editing repo sends correct success message."""
    form_data = {
        'gitweb-name': 'something_other.git',
        'gitweb-description': 'test-description',
        'gitweb-owner': 'test-owner',
        'gitweb-is_private': True,
        'gitweb-default_branch': 'branch1',
    }
    url = urls.reverse('gitweb:edit',
                       kwargs={'name': EXISTING_REPOS[0]['name']})
    request = rf.post(url, data=form_data)
    view = views.EditRepoView.as_view()
    response, messages = make_request(request, view,
                                      name=EXISTING_REPOS[0]['name'])

    assert list(messages)[0].message == 'Repository edited.'
    assert response.status_code == 302


def test_edit_nonexisting_repository_view(rf):
    """Test that trying to edit non-existing repository raises 404."""
    non_existing_repo = {'name': 'something_other'}
    request = rf.post(urls.reverse('gitweb:edit', kwargs=non_existing_repo),
                      data={})
    view = views.EditRepoView.as_view()

    with pytest.raises(Http404):
        make_request(request, view, **non_existing_repo)


def test_edit_repository_duplicate_name_view(rf):
    """Test that renaming to already existing repo name shows correct error
    message.

    """
    form_data = {
        'gitweb-name': EXISTING_REPOS[1]['name'],
        'gitweb-description': 'test-description',
        'gitweb-owner': 'test-owner'
    }
    url = urls.reverse('gitweb:edit',
                       kwargs={'name': EXISTING_REPOS[0]['name']})
    request = rf.post(url, data=form_data)
    view = views.EditRepoView.as_view()
    response, _ = make_request(request, view, **EXISTING_REPOS[0])

    assert response.context_data['form'].errors['name'][
        0] == 'A repository with this name already exists.'
    assert response.status_code == 200


def test_edit_repository_invalid_name_view(rf):
    """Test that renaming repo to invalid name shows correct error message."""
    form_data = {
        'gitweb-name': '.something',
        'gitweb-description': 'test-description',
        'gitweb-owner': 'test-owner'
    }
    url = urls.reverse('gitweb:edit',
                       kwargs={'name': EXISTING_REPOS[0]['name']})
    request = rf.post(url, data=form_data)
    view = views.EditRepoView.as_view()
    response, _ = make_request(request, view, **EXISTING_REPOS[0])

    assert response.context_data['form'].errors['name'][
        0] == 'Invalid repository name.'
    assert response.status_code == 200


def test_edit_repository_no_change_view(rf):
    """Test that not changing any values don't edit the repo."""
    with patch('plinth.modules.gitweb.edit_repo') as edit_repo:
        form_data = {
            'gitweb-name': EXISTING_REPOS[0]['name'],
            'gitweb-description': EXISTING_REPOS[0]['description'],
            'gitweb-owner': EXISTING_REPOS[0]['owner'],
            'gitweb-default_branch': EXISTING_REPOS[0]['default_branch'],
        }
        request = rf.post(
            urls.reverse('gitweb:edit',
                         kwargs={'name': EXISTING_REPOS[0]['name']}),
            data=form_data)
        view = views.EditRepoView.as_view()
        response, _ = make_request(request, view, **EXISTING_REPOS[0])

        assert not edit_repo.called, 'method should not have been called'
        assert response.status_code == 302


def test_edit_repository_failed_view(rf):
    """Test that failed repo editing sends correct error message."""
    with patch('plinth.modules.gitweb.edit_repo',
               side_effect=ActionError('Error')):
        form_data = {
            'gitweb-name': 'something_other',
            'gitweb-description': 'test-description',
            'gitweb-owner': 'test-owner',
            'gitweb-default_branch': 'master',
        }
        request = rf.post(
            urls.reverse('gitweb:edit',
                         kwargs={'name': EXISTING_REPOS[0]['name']}),
            data=form_data)
        view = views.EditRepoView.as_view()
        response, messages = make_request(request, view, **EXISTING_REPOS[0])

        assert list(
            messages)[0].message == 'An error occurred during configuration.'
        assert response.status_code == 302


def test_delete_repository_confirmation_view(rf):
    """Test that repo deletion confirmation shows correct repo name."""
    response, _ = make_request(rf.get(''), views.delete,
                               name=EXISTING_REPOS[0]['name'])

    assert response.context_data['name'] == EXISTING_REPOS[0]['name']
    assert response.status_code == 200


def test_delete_repository_view(rf):
    """Test that repo deletion sends correct success message."""
    response, messages = make_request(rf.post(''), views.delete,
                                      name=EXISTING_REPOS[0]['name'])

    assert list(messages)[0].message == '{} deleted.'.format(
        EXISTING_REPOS[0]['name'])
    assert response.status_code == 302


def test_delete_repository_fail_view(rf):
    """Test that failed repository deletion sends correct error message."""

    with patch('plinth.modules.gitweb.delete_repo',
               side_effect=ActionError('Error')):
        response, messages = make_request(rf.post(''), views.delete,
                                          name=EXISTING_REPOS[0]['name'])

        assert list(
            messages)[0].message == 'Could not delete {}: Error'.format(
                EXISTING_REPOS[0]['name'])
        assert response.status_code == 302


def test_delete_non_existing_repository_view(rf):
    """Test that deleting non-existing repository raises 404."""
    with pytest.raises(Http404):
        make_request(rf.post(''), views.delete, name='unknown_repo')
