218 lines
8.1 KiB
Python
218 lines
8.1 KiB
Python
|
|
# Python
|
|
import pytest
|
|
from unittest import mock
|
|
|
|
# DRF
|
|
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
from rest_framework.exceptions import PermissionDenied
|
|
|
|
# AWX
|
|
from awx.api.generics import (
|
|
ParentMixin,
|
|
SubListCreateAttachDetachAPIView, SubListAttachDetachAPIView,
|
|
ResourceAccessList,
|
|
ListAPIView
|
|
)
|
|
from awx.main.models import Organization, Credential
|
|
|
|
|
|
@pytest.fixture
|
|
def get_object_or_404(mocker):
|
|
# pytest patch without return_value generates a random value, we are counting on this
|
|
return mocker.patch('awx.api.generics.get_object_or_404')
|
|
|
|
|
|
@pytest.fixture
|
|
def get_object_or_400(mocker):
|
|
return mocker.patch('awx.api.generics.get_object_or_400')
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_organization():
|
|
return Organization(pk=4, name="Unsaved Org")
|
|
|
|
|
|
@pytest.fixture
|
|
def parent_relationship_factory(mocker):
|
|
def rf(serializer_class, relationship_name, relationship_value=mocker.Mock()):
|
|
mock_parent_relationship = mocker.MagicMock(**{'%s.add.return_value' % relationship_name: relationship_value})
|
|
mocker.patch('awx.api.generics.ParentMixin.get_parent_object', return_value=mock_parent_relationship)
|
|
|
|
serializer = serializer_class()
|
|
[setattr(serializer, x, '') for x in ['relationship', 'model', 'parent_model']]
|
|
serializer.relationship = relationship_name
|
|
|
|
return (serializer, mock_parent_relationship)
|
|
return rf
|
|
|
|
|
|
# TODO: Test create and associate failure (i.e. id doesn't exist, record already exists, permission denied)
|
|
# TODO: Mock and check return (Response)
|
|
class TestSubListCreateAttachDetachAPIView:
|
|
def test_attach_validate_ok(self, mocker):
|
|
mock_request = mocker.MagicMock(data=dict(id=1))
|
|
serializer = SubListCreateAttachDetachAPIView()
|
|
|
|
(sub_id, res) = serializer.attach_validate(mock_request)
|
|
|
|
assert sub_id == 1
|
|
assert res is None
|
|
|
|
def test_attach_validate_invalid_type(self, mocker):
|
|
mock_request = mocker.MagicMock(data=dict(id='foobar'))
|
|
serializer = SubListCreateAttachDetachAPIView()
|
|
|
|
(sub_id, res) = serializer.attach_validate(mock_request)
|
|
|
|
assert type(res) is Response
|
|
|
|
def test_attach_create_and_associate(self, mocker, get_object_or_400, parent_relationship_factory):
|
|
(serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
|
|
create_return_value = mocker.MagicMock(status_code=status.HTTP_201_CREATED)
|
|
serializer.create = mocker.Mock(return_value=create_return_value)
|
|
|
|
mock_request = mocker.MagicMock(data=dict())
|
|
serializer.attach(mock_request, None, None)
|
|
|
|
serializer.create.assert_called_with(mock_request, None, None)
|
|
mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
|
|
|
|
def test_attach_associate_only(self, mocker, get_object_or_400, parent_relationship_factory):
|
|
(serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
|
|
serializer.create = mocker.Mock(return_value=mocker.MagicMock())
|
|
|
|
mock_request = mocker.MagicMock(data=dict(id=1))
|
|
serializer.attach(mock_request, None, None)
|
|
|
|
serializer.create.assert_not_called()
|
|
mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
|
|
|
|
def test_unattach_validate_ok(self, mocker):
|
|
mock_request = mocker.MagicMock(data=dict(id=1))
|
|
serializer = SubListCreateAttachDetachAPIView()
|
|
|
|
(sub_id, res) = serializer.unattach_validate(mock_request)
|
|
|
|
assert sub_id == 1
|
|
assert res is None
|
|
|
|
def test_unattach_validate_invalid_type(self, mocker):
|
|
mock_request = mocker.MagicMock(data=dict(id='foobar'))
|
|
serializer = SubListCreateAttachDetachAPIView()
|
|
|
|
(sub_id, res) = serializer.unattach_validate(mock_request)
|
|
|
|
assert type(res) is Response
|
|
|
|
def test_unattach_validate_missing_id(self, mocker):
|
|
mock_request = mocker.MagicMock(data=dict())
|
|
serializer = SubListCreateAttachDetachAPIView()
|
|
|
|
(sub_id, res) = serializer.unattach_validate(mock_request)
|
|
|
|
assert sub_id is None
|
|
assert type(res) is Response
|
|
|
|
def test_unattach_by_id_ok(self, mocker, parent_relationship_factory, get_object_or_400):
|
|
(serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
|
|
mock_request = mocker.MagicMock()
|
|
mock_sub = mocker.MagicMock(name="object to unattach")
|
|
get_object_or_400.return_value = mock_sub
|
|
|
|
res = serializer.unattach_by_id(mock_request, 1)
|
|
|
|
assert type(res) is Response
|
|
assert res.status_code == status.HTTP_204_NO_CONTENT
|
|
mock_parent_relationship.wife.remove.assert_called_with(mock_sub)
|
|
|
|
def test_unattach_ok(self, mocker):
|
|
mock_request = mocker.MagicMock()
|
|
mock_sub_id = mocker.MagicMock()
|
|
view = SubListCreateAttachDetachAPIView()
|
|
view.unattach_validate = mocker.MagicMock()
|
|
view.unattach_by_id = mocker.MagicMock()
|
|
view.unattach_validate.return_value = (mock_sub_id, None)
|
|
|
|
view.unattach(mock_request)
|
|
|
|
view.unattach_validate.assert_called_with(mock_request)
|
|
view.unattach_by_id.assert_called_with(mock_request, mock_sub_id)
|
|
|
|
def test_unattach_invalid(self, mocker):
|
|
mock_request = mocker.MagicMock()
|
|
mock_res = mocker.MagicMock()
|
|
view = SubListCreateAttachDetachAPIView()
|
|
view.unattach_validate = mocker.MagicMock()
|
|
view.unattach_by_id = mocker.MagicMock()
|
|
view.unattach_validate.return_value = (None, mock_res)
|
|
|
|
view.unattach(mock_request)
|
|
|
|
view.unattach_validate.assert_called_with(mock_request)
|
|
view.unattach_by_id.assert_not_called()
|
|
|
|
|
|
def test_attach_detatch_only(mocker):
|
|
mock_request = mocker.MagicMock()
|
|
mock_request.data = {'name': 'name for my new model'}
|
|
view = SubListAttachDetachAPIView()
|
|
view.model = mocker.MagicMock()
|
|
view.model._meta = mocker.MagicMock()
|
|
view.model._meta.verbose_name = "Foo Bar"
|
|
|
|
resp = view.post(mock_request)
|
|
|
|
assert 'Foo Bar' in resp.data['msg']
|
|
assert 'field is missing' in resp.data['msg']
|
|
|
|
|
|
class TestParentMixin:
|
|
def test_get_parent_object(self, mocker, get_object_or_404):
|
|
parent_mixin = ParentMixin()
|
|
parent_mixin.lookup_field = 'foo'
|
|
parent_mixin.kwargs = dict(foo='bar')
|
|
parent_mixin.parent_model = 'parent_model'
|
|
mock_parent_mixin = mocker.MagicMock(wraps=parent_mixin)
|
|
|
|
return_value = mock_parent_mixin.get_parent_object()
|
|
|
|
get_object_or_404.assert_called_with(parent_mixin.parent_model, **parent_mixin.kwargs)
|
|
assert get_object_or_404.return_value == return_value
|
|
|
|
|
|
class TestResourceAccessList:
|
|
|
|
def mock_request(self):
|
|
return mock.MagicMock(
|
|
user=mock.MagicMock(is_anonymous=False, is_superuser=False),
|
|
method='GET')
|
|
|
|
def mock_view(self, parent=None):
|
|
view = ResourceAccessList()
|
|
view.parent_model = Organization
|
|
view.kwargs = {'pk': 4}
|
|
if parent:
|
|
view.get_parent_object = lambda: parent
|
|
return view
|
|
|
|
def test_parent_access_check_failed(self, mocker, mock_organization):
|
|
mock_access = mocker.MagicMock(__name__='for logger', return_value=False)
|
|
with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access):
|
|
with pytest.raises(PermissionDenied):
|
|
self.mock_view(parent=mock_organization).check_permissions(self.mock_request())
|
|
mock_access.assert_called_once_with(mock_organization)
|
|
|
|
def test_parent_access_check_worked(self, mocker, mock_organization):
|
|
mock_access = mocker.MagicMock(__name__='for logger', return_value=True)
|
|
with mocker.patch('awx.main.access.BaseAccess.can_read', mock_access):
|
|
self.mock_view(parent=mock_organization).check_permissions(self.mock_request())
|
|
mock_access.assert_called_once_with(mock_organization)
|
|
|
|
|
|
def test_related_search_reverse_FK_field():
|
|
view = ListAPIView()
|
|
view.model = Credential
|
|
assert 'unifiedjobtemplates__search' in view.related_search_fields
|