From d76a61346ebc0d7eb860fe72230ea3a9d1c88022 Mon Sep 17 00:00:00 2001 From: Steve McLellan Date: Tue, 17 Nov 2015 12:36:31 -0600 Subject: [PATCH] Make unit testing less reliant on HTML fragments Some tests check for equality with quite large HTML fragments, when the test is only interested in certain specific things (and not style changes etc). This patch alters some of those tests (specifically ones that were tripping up a dependent patch, but can be expanded) to check the context data of the response, since the actual rendering is tested by horizon's tests. It also adds tests to check context data in 'normal' circumstances so that no test coverage should be lost by this. Change-Id: I09be82058edc923521ffb4c022515845a18b158d Closes-Bug: #1517084 --- .../access_and_security/floating_ips/tests.py | 83 +++++++-- .../project/access_and_security/tests.py | 82 +++++++-- .../dashboards/project/instances/tests.py | 76 +++++--- .../dashboards/project/networks/tests.py | 163 +++++++++++------- .../dashboards/project/routers/tests.py | 64 ++++--- .../project/volumes/volumes/tests.py | 120 ++++++++++--- openstack_dashboard/test/helpers.py | 38 ++++ 7 files changed, 462 insertions(+), 164 deletions(-) diff --git a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/tests.py b/openstack_dashboard/dashboards/project/access_and_security/floating_ips/tests.py index b189195c58..e6c85215cc 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/tests.py +++ b/openstack_dashboard/dashboards/project/access_and_security/floating_ips/tests.py @@ -25,8 +25,6 @@ from mox3.mox import IsA # noqa import six from openstack_dashboard import api -from openstack_dashboard.dashboards.project.access_and_security \ - .floating_ips import tables from openstack_dashboard.test import helpers as test from openstack_dashboard.usage import quotas @@ -214,6 +212,68 @@ class FloatingIpViewTests(test.TestCase): res = self.client.post(INDEX_URL, {"action": action}) self.assertRedirectsNoFollow(res, INDEX_URL) + @test.create_stubs({api.network: ('floating_ip_supported', + 'tenant_floating_ip_list', + 'security_group_list', + 'floating_ip_pools_list',), + api.nova: ('keypair_list', + 'server_list',), + quotas: ('tenant_quota_usages',), + api.base: ('is_service_enabled',)}) + def test_allocate_button_attributes(self): + keypairs = self.keypairs.list() + floating_ips = self.floating_ips.list() + floating_pools = self.pools.list() + quota_data = self.quota_usages.first() + quota_data['floating_ips']['available'] = 10 + sec_groups = self.security_groups.list() + + api.network.floating_ip_supported( + IsA(http.HttpRequest)) \ + .AndReturn(True) + api.network.tenant_floating_ip_list( + IsA(http.HttpRequest)) \ + .AndReturn(floating_ips) + api.network.security_group_list( + IsA(http.HttpRequest)).MultipleTimes()\ + .AndReturn(sec_groups) + api.network.floating_ip_pools_list( + IsA(http.HttpRequest)) \ + .AndReturn(floating_pools) + api.nova.keypair_list( + IsA(http.HttpRequest)) \ + .AndReturn(keypairs) + api.nova.server_list( + IsA(http.HttpRequest)) \ + .AndReturn([self.servers.list(), False]) + quotas.tenant_quota_usages( + IsA(http.HttpRequest)).MultipleTimes() \ + .AndReturn(quota_data) + + api.base.is_service_enabled( + IsA(http.HttpRequest), + 'network').MultipleTimes() \ + .AndReturn(True) + api.base.is_service_enabled( + IsA(http.HttpRequest), + 'ec2').MultipleTimes() \ + .AndReturn(False) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL + + "?tab=access_security_tabs__floating_ips_tab") + + allocate_action = self.getAndAssertTableAction(res, 'floating_ips', + 'allocate') + self.assertEqual(set(['ajax-modal']), set(allocate_action.classes)) + self.assertEqual('Allocate IP To Project', + six.text_type(allocate_action.verbose_name)) + self.assertEqual(None, allocate_action.policy_rules) + + url = 'horizon:project:access_and_security:floating_ips:allocate' + self.assertEqual(url, allocate_action.url) + @test.create_stubs({api.network: ('floating_ip_supported', 'tenant_floating_ip_list', 'security_group_list', @@ -266,19 +326,12 @@ class FloatingIpViewTests(test.TestCase): res = self.client.get(INDEX_URL + "?tab=access_security_tabs__floating_ips_tab") - allocate_link = tables.AllocateIP() - url = allocate_link.get_link_url() - classes = (list(allocate_link.get_default_classes()) - + list(allocate_link.classes)) - link_name = "%s (%s)" % (six.text_type(allocate_link.verbose_name), - "Quota exceeded") - expected_string = ("" - "" - "%s" - % (url, link_name, " ".join(classes), link_name)) - self.assertContains(res, expected_string, html=True, - msg_prefix="The create button is not disabled") + allocate_action = self.getAndAssertTableAction(res, 'floating_ips', + 'allocate') + self.assertTrue('disabled' in allocate_action.classes, + 'The create button should be disabled') + self.assertEqual('Allocate IP To Project (Quota exceeded)', + six.text_type(allocate_action.verbose_name)) class FloatingIpNeutronViewTests(FloatingIpViewTests): diff --git a/openstack_dashboard/dashboards/project/access_and_security/tests.py b/openstack_dashboard/dashboards/project/access_and_security/tests.py index f6f374a76b..046c74fd1e 100644 --- a/openstack_dashboard/dashboards/project/access_and_security/tests.py +++ b/openstack_dashboard/dashboards/project/access_and_security/tests.py @@ -27,8 +27,6 @@ from horizon.workflows import views from openstack_dashboard import api from openstack_dashboard.dashboards.project.access_and_security \ import api_access -from openstack_dashboard.dashboards.project.access_and_security \ - .security_groups import tables from openstack_dashboard.test import helpers as test from openstack_dashboard.usage import quotas @@ -163,6 +161,70 @@ class SecurityGroupTabTests(test.TestCase): def setUp(self): super(SecurityGroupTabTests, self).setUp() + @test.create_stubs({api.network: ('floating_ip_supported', + 'tenant_floating_ip_list', + 'security_group_list', + 'floating_ip_pools_list',), + api.nova: ('keypair_list', + 'server_list',), + quotas: ('tenant_quota_usages',), + api.base: ('is_service_enabled',)}) + def test_create_button_attributes(self): + keypairs = self.keypairs.list() + floating_ips = self.floating_ips.list() + floating_pools = self.pools.list() + sec_groups = self.security_groups.list() + quota_data = self.quota_usages.first() + quota_data['security_groups']['available'] = 10 + + api.network.floating_ip_supported( + IsA(http.HttpRequest)) \ + .AndReturn(True) + api.network.tenant_floating_ip_list( + IsA(http.HttpRequest)) \ + .AndReturn(floating_ips) + api.network.floating_ip_pools_list( + IsA(http.HttpRequest)) \ + .AndReturn(floating_pools) + api.network.security_group_list( + IsA(http.HttpRequest)) \ + .AndReturn(sec_groups) + api.nova.keypair_list( + IsA(http.HttpRequest)) \ + .AndReturn(keypairs) + api.nova.server_list( + IsA(http.HttpRequest)) \ + .AndReturn([self.servers.list(), False]) + quotas.tenant_quota_usages( + IsA(http.HttpRequest)).MultipleTimes() \ + .AndReturn(quota_data) + + api.base.is_service_enabled( + IsA(http.HttpRequest), 'network').MultipleTimes() \ + .AndReturn(True) + api.base.is_service_enabled( + IsA(http.HttpRequest), 'ec2').MultipleTimes() \ + .AndReturn(False) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL + + "?tab=access_security_tabs__security_groups_tab") + + security_groups = res.context['security_groups_table'].data + self.assertItemsEqual(security_groups, self.security_groups.list()) + + create_action = self.getAndAssertTableAction(res, 'security_groups', + 'create') + + self.assertEqual('Create Security Group', + six.text_type(create_action.verbose_name)) + self.assertEqual(None, create_action.policy_rules) + self.assertEqual(set(['ajax-modal']), set(create_action.classes)) + + url = 'horizon:project:access_and_security:security_groups:create' + self.assertEqual(url, create_action.url) + @test.create_stubs({api.network: ('floating_ip_supported', 'tenant_floating_ip_list', 'security_group_list', @@ -217,18 +279,10 @@ class SecurityGroupTabTests(test.TestCase): security_groups = res.context['security_groups_table'].data self.assertItemsEqual(security_groups, self.security_groups.list()) - create_link = tables.CreateGroup() - url = create_link.get_link_url() - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - link_name = "%s (%s)" % (six.text_type(create_link.verbose_name), - "Quota exceeded") - expected_string = "" \ - "%s" \ - % (url, link_name, " ".join(classes), link_name) - self.assertContains(res, expected_string, html=True, - msg_prefix="The create button is not disabled") + create_action = self.getAndAssertTableAction(res, 'security_groups', + 'create') + self.assertTrue('disabled' in create_action.classes, + 'The create button should be disabled') def test_create_button_disabled_when_quota_exceeded_neutron_disabled(self): self._test_create_button_disabled_when_quota_exceeded(False) diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 637b6dfe4c..7da47eda41 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -28,7 +28,6 @@ from django.core.urlresolvers import reverse from django.forms import widgets from django import http import django.test -from django.utils import encoding from django.utils.http import urlencode from mox3.mox import IgnoreArg # noqa from mox3.mox import IsA # noqa @@ -3587,6 +3586,54 @@ class InstanceTests(helpers.TestCase): self.test_launch_form_instance_non_int_volume_size( test_with_profile=True) + @helpers.create_stubs({ + api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('floating_ip_simple_associate_supported', + 'floating_ip_supported', + 'servers_update_addresses',), + }) + def test_launch_button_attributes(self): + servers = self.servers.list() + limits = self.limits['absolute'] + limits['totalInstancesUsed'] = 0 + + api.nova.extension_supported('AdminActions', + IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) + api.nova.flavor_list(IsA(http.HttpRequest)) \ + .AndReturn(self.flavors.list()) + api.glance.image_list_detailed(IgnoreArg()) \ + .AndReturn((self.images.list(), False, False)) + search_opts = {'marker': None, 'paginate': True} + api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \ + .AndReturn([servers, False]) + api.network.servers_update_addresses(IsA(http.HttpRequest), servers) + api.nova.tenant_absolute_limits(IsA(http.HttpRequest), reserved=True) \ + .MultipleTimes().AndReturn(limits) + api.network.floating_ip_supported(IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) + api.network.floating_ip_simple_associate_supported( + IsA(http.HttpRequest)).MultipleTimes().AndReturn(True) + + self.mox.ReplayAll() + + tables.LaunchLink() + res = self.client.get(INDEX_URL) + + launch_action = self.getAndAssertTableAction(res, 'instances', + 'launch') + + self.assertEqual(set(['ajax-modal', 'ajax-update', 'btn-launch']), + set(launch_action.classes)) + self.assertEqual('Launch Instance', launch_action.verbose_name) + self.assertEqual('horizon:project:instances:launch', launch_action.url) + self.assertEqual((('compute', 'compute:create'),), + launch_action.policy_rules) + @helpers.create_stubs({ api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits', 'extension_supported',), @@ -3622,27 +3669,16 @@ class InstanceTests(helpers.TestCase): self.mox.ReplayAll() - launch = tables.LaunchLink() - url = launch.get_link_url() - classes = list(launch.get_default_classes()) + list(launch.classes) - link_name = "%s (%s)" % (six.text_type(launch.verbose_name), - "Quota exceeded") - + tables.LaunchLink() res = self.client.get(INDEX_URL) - if django.VERSION < (1, 8, 0): - resp_charset = res._charset - else: - resp_charset = res.charset - expected_string = encoding.smart_str(u''' - - %s - ''' % (url, link_name, " ".join(classes), link_name), resp_charset) - self.assertContains(res, expected_string, html=True, - msg_prefix="The launch button is not disabled") + launch_action = self.getAndAssertTableAction( + res, 'instances', 'launch') + + self.assertTrue('disabled' in launch_action.classes, + 'The launch button should be disabled') + self.assertEqual('Launch Instance (Quota exceeded)', + six.text_type(launch_action.verbose_name)) @helpers.create_stubs({api.glance: ('image_list_detailed',), api.neutron: ('network_list',), diff --git a/openstack_dashboard/dashboards/project/networks/tests.py b/openstack_dashboard/dashboards/project/networks/tests.py index 4b50302d85..7e433bd371 100644 --- a/openstack_dashboard/dashboards/project/networks/tests.py +++ b/openstack_dashboard/dashboards/project/networks/tests.py @@ -19,10 +19,9 @@ from django.utils.html import escape from horizon.workflows import views from mox3.mox import IsA # noqa +import six from openstack_dashboard import api -from openstack_dashboard.dashboards.project.networks.subnets import tables\ - as subnets_tables from openstack_dashboard.dashboards.project.networks import tables\ as networks_tables from openstack_dashboard.dashboards.project.networks import workflows @@ -2013,7 +2012,8 @@ class NetworkSubnetTests(test.TestCase): class NetworkViewTests(test.TestCase, NetworkStubMixin): def _test_create_button_shown_when_quota_disabled( - self, expected_string): + self, + find_button_fn): # if quota_data doesnt contain a networks|subnets|routers key or # these keys are empty dicts, its disabled quota_data = self.neutron_quota_usages.first() @@ -2033,11 +2033,14 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin): networks = res.context['networks_table'].data self.assertItemsEqual(networks, self.networks.list()) - self.assertContains(res, expected_string, True, html=True, - msg_prefix="The enabled create button not shown") + + button = find_button_fn(res) + self.assertFalse('disabled' in button.classes, + "The create button should not be disabled") + return button def _test_create_button_disabled_when_quota_exceeded( - self, expected_string, network_quota=5, subnet_quota=5): + self, find_button_fn, network_quota=5, subnet_quota=5, ): quota_data = self.neutron_quota_usages.first() @@ -2056,69 +2059,55 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin): networks = res.context['networks_table'].data self.assertItemsEqual(networks, self.networks.list()) - self.assertContains(res, expected_string, True, html=True, - msg_prefix="The create button is not disabled") + + button = find_button_fn(res) + self.assertTrue('disabled' in button.classes, + "The create button should be disabled") + return button @test.create_stubs({api.neutron: ('network_list',), quotas: ('tenant_quota_usages',)}) def test_network_create_button_disabled_when_quota_exceeded_index(self): - create_link = networks_tables.CreateNetwork() - url = create_link.get_link_url() - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - link_name = "%s (%s)" % (create_link.verbose_name, "Quota exceeded") - expected_string = "" \ - "%s" \ - % (url, link_name, " ".join(classes), link_name) - self._test_create_button_disabled_when_quota_exceeded(expected_string, - network_quota=0 - ) + networks_tables.CreateNetwork() + + def _find_net_button(res): + return self.getAndAssertTableAction(res, 'networks', 'create') + self._test_create_button_disabled_when_quota_exceeded(_find_net_button, + network_quota=0) @test.create_stubs({api.neutron: ('network_list',), quotas: ('tenant_quota_usages',)}) def test_subnet_create_button_disabled_when_quota_exceeded_index(self): network_id = self.networks.first().id - create_link = networks_tables.CreateSubnet() - url = reverse(create_link.get_link_url(), args=[network_id]) - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - link_name = "%s (%s)" % (create_link.verbose_name, "Quota exceeded") - expected_string = "%s" \ - % (url, " ".join(classes), network_id, link_name) - self._test_create_button_disabled_when_quota_exceeded(expected_string, - subnet_quota=0 - ) + networks_tables.CreateSubnet() + + def _find_subnet_button(res): + return self.getAndAssertTableRowAction(res, 'networks', + 'subnet', network_id) + + self._test_create_button_disabled_when_quota_exceeded( + _find_subnet_button, subnet_quota=0) @test.create_stubs({api.neutron: ('network_list',), quotas: ('tenant_quota_usages',)}) def test_network_create_button_shown_when_quota_disabled_index(self): # if quota_data doesnt contain a networks["available"] key its disabled - create_link = networks_tables.CreateNetwork() - url = create_link.get_link_url() - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - expected_string = "" \ - "%s" \ - % (url, create_link.verbose_name, " ".join(classes), - create_link.verbose_name) - self._test_create_button_shown_when_quota_disabled(expected_string) + networks_tables.CreateNetwork() + self._test_create_button_shown_when_quota_disabled( + lambda res: self.getAndAssertTableAction(res, 'networks', 'create') + ) @test.create_stubs({api.neutron: ('network_list',), quotas: ('tenant_quota_usages',)}) def test_subnet_create_button_shown_when_quota_disabled_index(self): # if quota_data doesnt contain a subnets["available"] key, its disabled network_id = self.networks.first().id - create_link = networks_tables.CreateSubnet() - url = reverse(create_link.get_link_url(), args=[network_id]) - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - expected_string = "%s" \ - % (url, " ".join(classes), network_id, create_link.verbose_name) - self._test_create_button_shown_when_quota_disabled(expected_string) + + def _find_subnet_button(res): + return self.getAndAssertTableRowAction(res, 'networks', + 'subnet', network_id) + + self._test_create_button_shown_when_quota_disabled(_find_subnet_button) @test.create_stubs({api.neutron: ('network_get', 'subnet_list', @@ -2155,17 +2144,65 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin): subnets = res.context['subnets_table'].data self.assertItemsEqual(subnets, self.subnets.list()) - class FakeTable(object): - kwargs = {'network_id': network_id} - create_link = subnets_tables.CreateSubnet() - create_link.table = FakeTable() - url = create_link.get_link_url() - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - link_name = "%s (%s)" % (create_link.verbose_name, "Quota exceeded") - expected_string = "" \ - "%s" \ - % (url, link_name, " ".join(classes), link_name) - self.assertContains(res, expected_string, html=True, - msg_prefix="The create button is not disabled") + create_action = self.getAndAssertTableAction(res, 'subnets', 'create') + self.assertTrue('disabled' in create_action.classes, + 'The create button should be disabled') + + @test.create_stubs({api.neutron: ('network_list',), + quotas: ('tenant_quota_usages',)}) + def test_create_button_attributes(self): + create_action = self._test_create_button_shown_when_quota_disabled( + lambda res: self.getAndAssertTableAction(res, 'networks', 'create') + ) + + self.assertEqual(set(['ajax-modal']), set(create_action.classes)) + self.assertEqual('horizon:project:networks:create', create_action.url) + self.assertEqual('Create Network', + six.text_type(create_action.verbose_name)) + self.assertEqual((('network', 'create_network'),), + create_action.policy_rules) + + @test.create_stubs({api.neutron: ('network_get', + 'subnet_list', + 'port_list', + 'is_extension_supported',), + quotas: ('tenant_quota_usages',)}) + def test_create_subnet_button_attributes(self): + network_id = self.networks.first().id + quota_data = self.neutron_quota_usages.first() + quota_data['subnets']['available'] = 1 + + api.neutron.network_get( + IsA(http.HttpRequest), network_id)\ + .MultipleTimes().AndReturn(self.networks.first()) + api.neutron.subnet_list( + IsA(http.HttpRequest), network_id=network_id)\ + .AndReturn(self.subnets.list()) + api.neutron.port_list( + IsA(http.HttpRequest), network_id=network_id)\ + .AndReturn([self.ports.first()]) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), 'mac-learning')\ + .AndReturn(False) + quotas.tenant_quota_usages( + IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(quota_data) + + self.mox.ReplayAll() + + res = self.client.get(reverse('horizon:project:networks:detail', + args=[network_id])) + self.assertTemplateUsed(res, 'project/networks/detail.html') + + subnets = res.context['subnets_table'].data + self.assertItemsEqual(subnets, self.subnets.list()) + + create_action = self.getAndAssertTableAction(res, 'subnets', 'create') + + self.assertEqual(set(['ajax-modal']), set(create_action.classes)) + self.assertEqual('horizon:project:networks:addsubnet', + create_action.url) + self.assertEqual('Create Subnet', + six.text_type(create_action.verbose_name)) + self.assertEqual((('network', 'create_subnet'),), + create_action.policy_rules) diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py index 5aad93ddb5..ce0116c123 100644 --- a/openstack_dashboard/dashboards/project/routers/tests.py +++ b/openstack_dashboard/dashboards/project/routers/tests.py @@ -23,7 +23,6 @@ import six from openstack_dashboard import api from openstack_dashboard.dashboards.project.routers.extensions.routerrules\ import rulemanager -from openstack_dashboard.dashboards.project.routers import tables from openstack_dashboard.test import helpers as test from openstack_dashboard.usage import quotas @@ -938,18 +937,11 @@ class RouterViewTests(RouterMixin, test.TestCase): routers = res.context['Routers_table'].data self.assertItemsEqual(routers, self.routers.list()) - create_link = tables.CreateRouter() - url = create_link.get_link_url() - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - link_name = "%s (%s)" % (six.text_type(create_link.verbose_name), - "Quota exceeded") - expected_string = "" \ - "%s" \ - % (url, link_name, " ".join(classes), link_name) - self.assertContains(res, expected_string, html=True, - msg_prefix="The create button is not disabled") + create_action = self.getAndAssertTableAction(res, 'Routers', 'create') + self.assertTrue('disabled' in create_action.classes, + 'Create button is not disabled') + self.assertEqual('Create Router (Quota exceeded)', + create_action.verbose_name) @test.create_stubs({api.neutron: ('router_list', 'network_list'), quotas: ('tenant_quota_usages',)}) @@ -973,14 +965,38 @@ class RouterViewTests(RouterMixin, test.TestCase): routers = res.context['Routers_table'].data self.assertItemsEqual(routers, self.routers.list()) - create_link = tables.CreateRouter() - url = create_link.get_link_url() - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - link_name = "%s" % (six.text_type(create_link.verbose_name)) - expected_string = "" \ - "%s" \ - % (url, link_name, " ".join(classes), link_name) - self.assertContains(res, expected_string, html=True, - msg_prefix="The create button is not displayed") + create_action = self.getAndAssertTableAction(res, 'Routers', 'create') + self.assertFalse('disabled' in create_action.classes, + 'Create button should not be disabled') + self.assertEqual('Create Router', + create_action.verbose_name) + + @test.create_stubs({api.neutron: ('router_list', 'network_list'), + quotas: ('tenant_quota_usages',)}) + def test_create_button_attributes(self): + quota_data = self.neutron_quota_usages.first() + quota_data['routers']['available'] = 10 + api.neutron.router_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id, + search_opts=None).AndReturn(self.routers.list()) + quotas.tenant_quota_usages( + IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(quota_data) + + self._mock_external_network_list() + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + self.assertTemplateUsed(res, 'project/routers/index.html') + + routers = res.context['Routers_table'].data + self.assertItemsEqual(routers, self.routers.list()) + + create_action = self.getAndAssertTableAction(res, 'Routers', 'create') + self.assertEqual(set(['ajax-modal']), set(create_action.classes)) + self.assertEqual('Create Router', + six.text_type(create_action.verbose_name)) + self.assertEqual('horizon:project:routers:create', create_action.url) + self.assertEqual((('network', 'create_router'),), + create_action.policy_rules) diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py index ab7fbb641f..b6a70d0554 100644 --- a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py +++ b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py @@ -15,7 +15,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - import django from django.core.urlresolvers import reverse from django.forms import widgets @@ -24,11 +23,10 @@ from django.test.utils import override_settings from mox3.mox import IsA # noqa import six +from six import moves from openstack_dashboard import api from openstack_dashboard.api import cinder -from openstack_dashboard.dashboards.project.volumes \ - .volumes import tables from openstack_dashboard.test import helpers as test from openstack_dashboard.usage import quotas @@ -1021,6 +1019,50 @@ class VolumeViewTests(test.TestCase): server.id) self.assertEqual(res.status_code, 200) + def _get_volume_row_action_from_ajax(self, res, action_name, row_id): + def _matches_row_id(context_row): + return (len(context_row.dicts) > 1 and + isinstance(context_row.dicts[1], dict) and + context_row.dicts[1].get('row_id', None) == row_id) + + matching = list(moves.filter(lambda r: _matches_row_id(r), + res.context)) + self.assertTrue(len(matching) > 1, + "Expected at least one row matching %s" % row_id) + row = matching[-1].dicts[1] + matching_actions = list(moves.filter(lambda a: a.name == action_name, + row['row_actions'])) + self.assertEqual(1, len(matching_actions), + "Expected one row action named '%s'" % action_name) + return matching_actions[0] + + @test.create_stubs({cinder: ('tenant_absolute_limits', + 'volume_get',)}) + def test_create_snapshot_button_attributes(self): + limits = {'maxTotalSnapshots': 2} + limits['totalSnapshotsUsed'] = 1 + volume = self.cinder_volumes.first() + + cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume) + cinder.tenant_absolute_limits(IsA(http.HttpRequest)).AndReturn(limits) + self.mox.ReplayAll() + + res_url = (VOLUME_INDEX_URL + + "?action=row_update&table=volumes&obj_id=" + volume.id) + + res = self.client.get(res_url, {}, + HTTP_X_REQUESTED_WITH='XMLHttpRequest') + + snapshot_action = self._get_volume_row_action_from_ajax( + res, 'snapshots', volume.id) + self.assertEqual('horizon:project:volumes:volumes:create_snapshot', + snapshot_action.url) + self.assertEqual(set(['ajax-modal']), set(snapshot_action.classes)) + self.assertEqual('Create Snapshot', + six.text_type(snapshot_action.verbose_name)) + self.assertEqual((('volume', 'volume:create_snapshot'),), + snapshot_action.policy_rules) + @test.create_stubs({cinder: ('tenant_absolute_limits', 'volume_get',)}) def test_create_snapshot_button_disabled_when_quota_exceeded(self): @@ -1032,25 +1074,57 @@ class VolumeViewTests(test.TestCase): cinder.tenant_absolute_limits(IsA(http.HttpRequest)).AndReturn(limits) self.mox.ReplayAll() - create_link = tables.CreateSnapshot() - url = reverse(create_link.get_link_url(), args=[volume.id]) res_url = (VOLUME_INDEX_URL + "?action=row_update&table=volumes&obj_id=" + volume.id) res = self.client.get(res_url, {}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - link_name = "%s (%s)" % (six.text_type(create_link.verbose_name), - "Quota exceeded") - expected_string = "%s" \ - % (url, " ".join(classes), volume.id, link_name) + snapshot_action = self._get_volume_row_action_from_ajax( + res, 'snapshots', volume.id) + self.assertTrue('disabled' in snapshot_action.classes, + 'The create snapshot button should be disabled') - self.assertContains( - res, expected_string, html=True, - msg_prefix="The create snapshot button is not disabled") + @test.create_stubs({cinder: ('tenant_absolute_limits', + 'volume_list', + 'volume_snapshot_list', + 'volume_backup_supported',), + api.nova: ('server_list',)}) + def test_create_button_attributes(self): + limits = self.cinder_limits['absolute'] + limits['maxTotalVolumes'] = 10 + limits['totalVolumesUsed'] = 1 + volumes = self.cinder_volumes.list() + + api.cinder.volume_backup_supported(IsA(http.HttpRequest)). \ + MultipleTimes().AndReturn(True) + cinder.volume_list(IsA(http.HttpRequest), search_opts=None)\ + .AndReturn(volumes) + cinder.volume_snapshot_list(IsA(http.HttpRequest), + search_opts=None).\ + AndReturn([]) + api.nova.server_list(IsA(http.HttpRequest), search_opts=None)\ + .AndReturn([self.servers.list(), False]) + cinder.tenant_absolute_limits(IsA(http.HttpRequest))\ + .MultipleTimes().AndReturn(limits) + self.mox.ReplayAll() + + res = self.client.get(VOLUME_INDEX_URL) + self.assertTemplateUsed(res, 'project/volumes/index.html') + + volumes = res.context['volumes_table'].data + self.assertItemsEqual(volumes, self.cinder_volumes.list()) + + create_action = self.getAndAssertTableAction(res, 'volumes', 'create') + + self.assertEqual(set(['ajax-modal', 'ajax-update', 'btn-create']), + set(create_action.classes)) + self.assertEqual('Create Volume', + six.text_type(create_action.verbose_name)) + self.assertEqual('horizon:project:volumes:volumes:create', + create_action.url) + self.assertEqual((('volume', 'volume:create'),), + create_action.policy_rules) @test.create_stubs({cinder: ('tenant_absolute_limits', 'volume_list', @@ -1081,19 +1155,9 @@ class VolumeViewTests(test.TestCase): volumes = res.context['volumes_table'].data self.assertItemsEqual(volumes, self.cinder_volumes.list()) - create_link = tables.CreateVolume() - url = create_link.get_link_url() - classes = (list(create_link.get_default_classes()) - + list(create_link.classes)) - link_name = "%s (%s)" % (six.text_type(create_link.verbose_name), - "Quota exceeded") - expected_string = " "\ - "%s" \ - % (url, link_name, " ".join(classes), link_name) - self.assertContains(res, expected_string, html=True, - msg_prefix="The create button is not disabled") + create_action = self.getAndAssertTableAction(res, 'volumes', 'create') + self.assertTrue('disabled' in create_action.classes, + 'The create button should be disabled') @test.create_stubs({cinder: ('tenant_absolute_limits', 'volume_get',), diff --git a/openstack_dashboard/test/helpers.py b/openstack_dashboard/test/helpers.py index b108d6be0e..b01216b0d0 100644 --- a/openstack_dashboard/test/helpers.py +++ b/openstack_dashboard/test/helpers.py @@ -280,6 +280,44 @@ class TestCase(horizon_helpers.TestCase): def assertItemsCollectionEqual(self, response, items_list): self.assertEqual(response.json, {"items": items_list}) + def getAndAssertTableRowAction(self, response, table_name, + action_name, row_id): + table = response.context[table_name + '_table'] + full_row_id = '%s__row__%s' % (table_name, row_id) + rows = list(moves.filter(lambda x: x.id == full_row_id, + table.get_rows())) + self.assertEqual(1, len(rows), + "Did not find a row matching id '%s'" % row_id) + row_actions = table.get_row_actions(rows[0]) + + msg_args = (table_name, action_name, row_id) + self.assertTrue( + len(row_actions) > 0, + "No action named '%s' found in table '%s' row '%s'" % msg_args) + + self.assertEqual( + 1, len(row_actions), + "Multiple actions '%s' found in table '%s' row '%s'" % msg_args) + + return row_actions[0] + + def getAndAssertTableAction(self, response, table_name, action_name): + + table = response.context[table_name + '_table'] + table_actions = table.get_table_actions() + actions = list(moves.filter(lambda x: x.name == action_name, + table_actions)) + msg_args = (table_name, action_name) + self.assertTrue( + len(actions) > 0, + "No action named '%s' found in table '%s'" % msg_args) + + self.assertEqual( + 1, len(actions), + "More than one action named '%s' found in table '%s'" % msg_args) + + return actions[0] + @staticmethod def mock_rest_request(**args): mock_args = {