diff --git a/plinth/modules/gitweb/__init__.py b/plinth/modules/gitweb/__init__.py index ed9b201f1..2f8b15408 100644 --- a/plinth/modules/gitweb/__init__.py +++ b/plinth/modules/gitweb/__init__.py @@ -238,6 +238,7 @@ def repo_info(repo): info['is_private'] = True else: info['is_private'] = False + del info['access'] return info diff --git a/plinth/modules/gitweb/tests/test_gitweb.py b/plinth/modules/gitweb/tests/test_actions.py similarity index 100% rename from plinth/modules/gitweb/tests/test_gitweb.py rename to plinth/modules/gitweb/tests/test_actions.py diff --git a/plinth/modules/gitweb/tests/test_views.py b/plinth/modules/gitweb/tests/test_views.py new file mode 100644 index 000000000..6ddab24e7 --- /dev/null +++ b/plinth/modules/gitweb/tests/test_views.py @@ -0,0 +1,360 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +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 + }, + { + 'name': 'something2', + 'description': '', + 'owner': '', + 'access': 'private', + 'is_private': True + }, +] + + +@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.""" + if args[1][0] == 'repo-info': + return json.dumps(EXISTING_REPOS[0]) + + if args[1][0] == 'check-repo-exists': + return True + + return None + + +@pytest.fixture(autouse=True) +def gitweb_patch(): + """Patch gitweb.""" + with patch('plinth.modules.gitweb.app') as app_patch, \ + patch('plinth.actions.superuser_run', side_effect=action_run), \ + patch('plinth.actions.run', side_effect=action_run): + app_patch.get_repo_list.return_value = [{ + 'name': EXISTING_REPOS[0]['name'] + }, { + 'name': EXISTING_REPOS[1]['name'] + }] + 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.""" + with patch('plinth.modules.gitweb.create_repo', + side_effect=ActionError('Error')): + 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 == 'An error occurred while creating the repository.' + 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 + } + 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'] + } + 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' + } + 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')