mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 23:51:09 +03:00
Merge branch 'test-labels' into devel
This commit is contained in:
commit
ff5935ccfd
@ -1565,6 +1565,14 @@ class JobOptionsSerializer(BaseSerializer):
|
||||
args=(obj.cloud_credential.pk,))
|
||||
return res
|
||||
|
||||
def _summary_field_labels(self, obj):
|
||||
return [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('-name')[:10]]
|
||||
|
||||
def get_summary_fields(self, obj):
|
||||
res = super(JobOptionsSerializer, self).get_summary_fields(obj)
|
||||
res['labels'] = self._summary_field_labels(obj)
|
||||
return res
|
||||
|
||||
def to_representation(self, obj):
|
||||
ret = super(JobOptionsSerializer, self).to_representation(obj)
|
||||
if obj is None:
|
||||
@ -1622,6 +1630,9 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
|
||||
res['callback'] = reverse('api:job_template_callback', args=(obj.pk,))
|
||||
return res
|
||||
|
||||
def _recent_jobs(self, obj):
|
||||
return [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in obj.jobs.filter(active=True).order_by('-created')[:10]]
|
||||
|
||||
def get_summary_fields(self, obj):
|
||||
d = super(JobTemplateSerializer, self).get_summary_fields(obj)
|
||||
if obj.survey_spec is not None and ('name' in obj.survey_spec and 'description' in obj.survey_spec):
|
||||
@ -1640,8 +1651,7 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
|
||||
else:
|
||||
d['can_copy'] = False
|
||||
d['can_edit'] = False
|
||||
d['recent_jobs'] = [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in obj.jobs.order_by('-created')[:10]]
|
||||
d['labels'] = [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('-name')[:10]]
|
||||
d['recent_jobs'] = self._recent_jobs(obj)
|
||||
return d
|
||||
|
||||
def validate(self, attrs):
|
||||
@ -1683,11 +1693,6 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
||||
res['relaunch'] = reverse('api:job_relaunch', args=(obj.pk,))
|
||||
return res
|
||||
|
||||
def get_summary_fields(self, obj):
|
||||
d = super(JobSerializer, self).get_summary_fields(obj)
|
||||
d['labels'] = [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('-name')[:10]]
|
||||
return d
|
||||
|
||||
def to_internal_value(self, data):
|
||||
# When creating a new job and a job template is specified, populate any
|
||||
# fields not provided in data from the job template.
|
||||
|
@ -400,14 +400,16 @@ def fact_services_json():
|
||||
def permission_inv_read(organization, inventory, team):
|
||||
return Permission.objects.create(inventory=inventory, team=team, permission_type=PERM_INVENTORY_READ)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def job_template_labels(organization):
|
||||
def job_template(organization):
|
||||
jt = JobTemplate(name='test-job_template')
|
||||
jt.save()
|
||||
|
||||
jt.labels.create(name="label-1", organization=organization)
|
||||
jt.labels.create(name="label-2", organization=organization)
|
||||
|
||||
return jt
|
||||
|
||||
@pytest.fixture
|
||||
def job_template_labels(organization, job_template):
|
||||
job_template.labels.create(name="label-1", organization=organization)
|
||||
job_template.labels.create(name="label-2", organization=organization)
|
||||
|
||||
return job_template
|
||||
|
80
awx/main/tests/unit/api/test_generics.py
Normal file
80
awx/main/tests/unit/api/test_generics.py
Normal file
@ -0,0 +1,80 @@
|
||||
|
||||
# Python
|
||||
import pytest
|
||||
|
||||
# DRF
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
# AWX
|
||||
from awx.api.generics import ParentMixin, SubListCreateAttachDetachAPIView
|
||||
|
||||
@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 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 or record already exists)
|
||||
# TODO: Mock and check return (Response)
|
||||
class TestSubListCreateAttachDetachAPIView:
|
||||
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)
|
||||
|
||||
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
|
||||
|
156
awx/main/tests/unit/api/test_serializers.py
Normal file
156
awx/main/tests/unit/api/test_serializers.py
Normal file
@ -0,0 +1,156 @@
|
||||
# Python
|
||||
import pytest
|
||||
import mock
|
||||
|
||||
# AWX
|
||||
from awx.api.serializers import JobTemplateSerializer, JobSerializer, JobOptionsSerializer
|
||||
from awx.main.models import Label, Job
|
||||
|
||||
@pytest.fixture
|
||||
def job_template(mocker):
|
||||
return mocker.MagicMock(pk=5)
|
||||
|
||||
@pytest.fixture
|
||||
def job(mocker, job_template):
|
||||
return mocker.MagicMock(pk=5, job_template=job_template)
|
||||
|
||||
@pytest.fixture
|
||||
def labels(mocker):
|
||||
return [Label(id=x, name='label-%d' % x) for x in xrange(0, 25)]
|
||||
|
||||
@pytest.fixture
|
||||
def jobs(mocker):
|
||||
return [Job(id=x, name='job-%d' % x) for x in xrange(0, 25)]
|
||||
|
||||
class GetRelatedMixin:
|
||||
def _assert(self, model_obj, related, resource_name, related_resource_name):
|
||||
assert related_resource_name in related
|
||||
assert related[related_resource_name] == '/api/v1/%s/%d/%s/' % (resource_name, model_obj.pk, related_resource_name)
|
||||
|
||||
def _mock_and_run(self, serializer_class, model_obj):
|
||||
serializer = serializer_class()
|
||||
related = serializer.get_related(model_obj)
|
||||
return related
|
||||
|
||||
def _test_get_related(self, serializer_class, model_obj, resource_name, related_resource_name):
|
||||
related = self._mock_and_run(serializer_class, model_obj)
|
||||
self._assert(model_obj, related, resource_name, related_resource_name)
|
||||
return related
|
||||
|
||||
class GetSummaryFieldsMixin:
|
||||
def _assert(self, summary, summary_field_name):
|
||||
assert summary_field_name in summary
|
||||
|
||||
def _mock_and_run(self, serializer_class, model_obj):
|
||||
serializer = serializer_class()
|
||||
return serializer.get_summary_fields(model_obj)
|
||||
|
||||
def _test_get_summary_fields(self, serializer_class, model_obj, summary_field_name):
|
||||
summary = self._mock_and_run(serializer_class, model_obj)
|
||||
self._assert(summary, summary_field_name)
|
||||
return summary
|
||||
|
||||
@mock.patch('awx.api.serializers.UnifiedJobTemplateSerializer.get_related', lambda x,y: {})
|
||||
@mock.patch('awx.api.serializers.JobOptionsSerializer.get_related', lambda x,y: {})
|
||||
class TestJobTemplateSerializerGetRelated(GetRelatedMixin):
|
||||
@pytest.mark.parametrize("related_resource_name", [
|
||||
'jobs',
|
||||
'schedules',
|
||||
'activity_stream',
|
||||
'launch',
|
||||
'notifiers_any',
|
||||
'notifiers_success',
|
||||
'notifiers_error',
|
||||
'survey_spec',
|
||||
'labels',
|
||||
'callback',
|
||||
])
|
||||
def test_get_related(self, job_template, related_resource_name):
|
||||
self._test_get_related(JobTemplateSerializer, job_template, 'job_templates', related_resource_name)
|
||||
|
||||
def test_callback_absent(self, job_template):
|
||||
job_template.host_config_key = None
|
||||
related = self._mock_and_run(JobTemplateSerializer, job_template)
|
||||
assert 'callback' not in related
|
||||
|
||||
class TestJobTemplateSerializerGetSummaryFields(GetSummaryFieldsMixin):
|
||||
def test__recent_jobs(self, mocker, job_template, jobs):
|
||||
|
||||
job_template.jobs.filter = mocker.MagicMock(**{'order_by.return_value': jobs})
|
||||
job_template.jobs.filter.return_value = job_template.jobs.filter
|
||||
|
||||
serializer = JobTemplateSerializer()
|
||||
recent_jobs = serializer._recent_jobs(job_template)
|
||||
|
||||
job_template.jobs.filter.assert_called_with(active=True)
|
||||
job_template.jobs.filter.order_by.assert_called_with('-created')
|
||||
assert len(recent_jobs) == 10
|
||||
for x in jobs[:10]:
|
||||
assert recent_jobs == [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in jobs[:10]]
|
||||
|
||||
def test_survey_spec_exists(self, mocker, job_template):
|
||||
job_template.survey_spec = {'name': 'blah', 'description': 'blah blah'}
|
||||
self._test_get_summary_fields(JobTemplateSerializer, job_template, 'survey')
|
||||
|
||||
def test_survey_spec_absent(self, mocker, job_template):
|
||||
job_template.survey_spec = None
|
||||
summary = self._mock_and_run(JobTemplateSerializer, job_template)
|
||||
assert 'survey' not in summary
|
||||
|
||||
@pytest.mark.skip(reason="RBAC needs to land")
|
||||
def test_can_copy_true(self, mocker, job_template):
|
||||
pass
|
||||
|
||||
@pytest.mark.skip(reason="RBAC needs to land")
|
||||
def test_can_copy_false(self, mocker, job_template):
|
||||
pass
|
||||
|
||||
@pytest.mark.skip(reason="RBAC needs to land")
|
||||
def test_can_edit_true(self, mocker, job_template):
|
||||
pass
|
||||
|
||||
@pytest.mark.skip(reason="RBAC needs to land")
|
||||
def test_can_edit_false(self, mocker, job_template):
|
||||
pass
|
||||
|
||||
@mock.patch('awx.api.serializers.UnifiedJobTemplateSerializer.get_related', lambda x,y: {})
|
||||
@mock.patch('awx.api.serializers.JobOptionsSerializer.get_related', lambda x,y: {})
|
||||
class TestJobSerializerGetRelated(GetRelatedMixin):
|
||||
@pytest.mark.parametrize("related_resource_name", [
|
||||
'job_events',
|
||||
'job_plays',
|
||||
'job_tasks',
|
||||
'relaunch',
|
||||
'labels',
|
||||
])
|
||||
def test_get_related(self, mocker, job, related_resource_name):
|
||||
self._test_get_related(JobSerializer, job, 'jobs', related_resource_name)
|
||||
|
||||
def test_job_template_present(self, job):
|
||||
job.job_template.active = True
|
||||
serializer = JobSerializer()
|
||||
related = serializer.get_related(job)
|
||||
assert 'job_template' in related
|
||||
|
||||
def test_job_template_absent(self, job):
|
||||
job.job_template.active = False
|
||||
serializer = JobSerializer()
|
||||
related = serializer.get_related(job)
|
||||
assert 'job_template' not in related
|
||||
|
||||
@mock.patch('awx.api.serializers.BaseSerializer.get_summary_fields', lambda x,y: {})
|
||||
class TestJobOptionsSerializerGetSummaryFields(GetSummaryFieldsMixin):
|
||||
def test__summary_field_labels_10_max(self, mocker, job_template, labels):
|
||||
job_template.labels.all = mocker.MagicMock(**{'order_by.return_value': labels})
|
||||
job_template.labels.all.return_value = job_template.labels.all
|
||||
|
||||
serializer = JobOptionsSerializer()
|
||||
summary_labels = serializer._summary_field_labels(job_template)
|
||||
|
||||
job_template.labels.all.order_by.assert_called_with('-name')
|
||||
assert len(summary_labels) == 10
|
||||
assert summary_labels == [{'id': x.id, 'name': x.name} for x in labels[:10]]
|
||||
|
||||
def test_labels_exists(self, mocker, job_template):
|
||||
self._test_get_summary_fields(JobOptionsSerializer, job_template, 'labels')
|
||||
|
52
awx/main/tests/unit/api/test_views.py
Normal file
52
awx/main/tests/unit/api/test_views.py
Normal file
@ -0,0 +1,52 @@
|
||||
# Python
|
||||
import pytest
|
||||
|
||||
# AWX
|
||||
from awx.api.views import ApiV1RootView
|
||||
|
||||
@pytest.fixture
|
||||
def mock_response_new(mocker):
|
||||
m = mocker.patch('awx.api.views.Response.__new__')
|
||||
m.return_value = m
|
||||
return m
|
||||
|
||||
class TestApiV1RootView:
|
||||
def test_get_endpoints(self, mocker, mock_response_new):
|
||||
endpoints = [
|
||||
'authtoken',
|
||||
'ping',
|
||||
'config',
|
||||
'settings',
|
||||
'me',
|
||||
'dashboard',
|
||||
'organizations',
|
||||
'users',
|
||||
'projects',
|
||||
'teams',
|
||||
'credentials',
|
||||
'inventory',
|
||||
'inventory_scripts',
|
||||
'inventory_sources',
|
||||
'groups',
|
||||
'hosts',
|
||||
'job_templates',
|
||||
'jobs',
|
||||
'ad_hoc_commands',
|
||||
'system_job_templates',
|
||||
'system_jobs',
|
||||
'schedules',
|
||||
'notifiers',
|
||||
'notifications',
|
||||
'labels',
|
||||
'unified_job_templates',
|
||||
'unified_jobs',
|
||||
'activity_stream',
|
||||
]
|
||||
view = ApiV1RootView()
|
||||
ret = view.get(mocker.MagicMock())
|
||||
|
||||
assert ret == mock_response_new
|
||||
data_arg = mock_response_new.mock_calls[0][1][1]
|
||||
for endpoint in endpoints:
|
||||
assert endpoint in data_arg
|
||||
|
Loading…
Reference in New Issue
Block a user