mirror of
https://github.com/ansible/awx.git
synced 2026-01-27 00:21:30 -03:30
This avoids re-loading objects from the database in our chain of permission checking, wherever possible. access.py is equiped to handle object references instead of pk ints, and permissions.py is changed to pass those refs.
273 lines
10 KiB
Python
273 lines
10 KiB
Python
|
|
# Python
|
|
import pytest
|
|
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,
|
|
DeleteLastUnattachLabelMixin,
|
|
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_response_new(mocker):
|
|
m = mocker.patch('awx.api.generics.Response.__new__')
|
|
m.return_value = m
|
|
return m
|
|
|
|
|
|
@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, mock_response_new):
|
|
(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())
|
|
ret = serializer.attach(mock_request, None, None)
|
|
|
|
assert ret == mock_response_new
|
|
serializer.create.assert_called_with(mock_request, None, None)
|
|
mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
|
|
mock_response_new.assert_called_with(Response, create_return_value.data, status=status.HTTP_201_CREATED, headers={'Location': create_return_value['Location']})
|
|
|
|
def test_attach_associate_only(self, mocker, get_object_or_400, parent_relationship_factory, mock_response_new):
|
|
(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))
|
|
ret = serializer.attach(mock_request, None, None)
|
|
|
|
assert ret == mock_response_new
|
|
serializer.create.assert_not_called()
|
|
mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
|
|
mock_response_new.assert_called_with(Response, status=status.HTTP_204_NO_CONTENT)
|
|
|
|
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 TestDeleteLastUnattachLabelMixin:
|
|
@mock.patch('__builtin__.super')
|
|
def test_unattach_ok(self, super, mocker):
|
|
mock_request = mocker.MagicMock()
|
|
mock_sub_id = mocker.MagicMock()
|
|
super.return_value = super
|
|
super.unattach_validate = mocker.MagicMock(return_value=(mock_sub_id, None))
|
|
super.unattach_by_id = mocker.MagicMock()
|
|
|
|
mock_model = mocker.MagicMock()
|
|
mock_model.objects.get.return_value = mock_model
|
|
mock_model.is_detached.return_value = True
|
|
|
|
view = DeleteLastUnattachLabelMixin()
|
|
view.model = mock_model
|
|
|
|
view.unattach(mock_request, None, None)
|
|
|
|
super.unattach_validate.assert_called_with(mock_request)
|
|
super.unattach_by_id.assert_called_with(mock_request, mock_sub_id)
|
|
mock_model.is_detached.assert_called_with()
|
|
mock_model.objects.get.assert_called_with(id=mock_sub_id)
|
|
mock_model.delete.assert_called_with()
|
|
|
|
@mock.patch('__builtin__.super')
|
|
def test_unattach_fail(self, super, mocker):
|
|
mock_request = mocker.MagicMock()
|
|
mock_response = mocker.MagicMock()
|
|
super.return_value = super
|
|
super.unattach_validate = mocker.MagicMock(return_value=(None, mock_response))
|
|
view = DeleteLastUnattachLabelMixin()
|
|
|
|
res = view.unattach(mock_request, None, None)
|
|
|
|
super.unattach_validate.assert_called_with(mock_request)
|
|
assert mock_response == res
|
|
|
|
|
|
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=mock.MagicMock(return_value=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 'jobtemplates__search' in view.related_search_fields
|