summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2015-12-03 08:15:21 +0000
committerGerrit Code Review <review@openstack.org>2015-12-03 08:15:22 +0000
commitb06b36fcd08ebb73db8b92b8fa28bf1f87b0fd0a (patch)
tree3cc06034e694a7b0498152d4c2463c06baa0992a
parentaa38180cf531d1c536cc8a3097eb2cb3be49bb02 (diff)
parentd76a61346ebc0d7eb860fe72230ea3a9d1c88022 (diff)
Merge "Make unit testing less reliant on HTML fragments"
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/floating_ips/tests.py83
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/tests.py82
-rw-r--r--openstack_dashboard/dashboards/project/instances/tests.py78
-rw-r--r--openstack_dashboard/dashboards/project/networks/tests.py163
-rw-r--r--openstack_dashboard/dashboards/project/routers/tests.py64
-rw-r--r--openstack_dashboard/dashboards/project/volumes/volumes/tests.py120
-rw-r--r--openstack_dashboard/test/helpers.py38
7 files changed, 463 insertions, 165 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 b189195..e6c8521 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
25import six 25import six
26 26
27from openstack_dashboard import api 27from openstack_dashboard import api
28from openstack_dashboard.dashboards.project.access_and_security \
29 .floating_ips import tables
30from openstack_dashboard.test import helpers as test 28from openstack_dashboard.test import helpers as test
31from openstack_dashboard.usage import quotas 29from openstack_dashboard.usage import quotas
32 30
@@ -222,6 +220,68 @@ class FloatingIpViewTests(test.TestCase):
222 'server_list',), 220 'server_list',),
223 quotas: ('tenant_quota_usages',), 221 quotas: ('tenant_quota_usages',),
224 api.base: ('is_service_enabled',)}) 222 api.base: ('is_service_enabled',)})
223 def test_allocate_button_attributes(self):
224 keypairs = self.keypairs.list()
225 floating_ips = self.floating_ips.list()
226 floating_pools = self.pools.list()
227 quota_data = self.quota_usages.first()
228 quota_data['floating_ips']['available'] = 10
229 sec_groups = self.security_groups.list()
230
231 api.network.floating_ip_supported(
232 IsA(http.HttpRequest)) \
233 .AndReturn(True)
234 api.network.tenant_floating_ip_list(
235 IsA(http.HttpRequest)) \
236 .AndReturn(floating_ips)
237 api.network.security_group_list(
238 IsA(http.HttpRequest)).MultipleTimes()\
239 .AndReturn(sec_groups)
240 api.network.floating_ip_pools_list(
241 IsA(http.HttpRequest)) \
242 .AndReturn(floating_pools)
243 api.nova.keypair_list(
244 IsA(http.HttpRequest)) \
245 .AndReturn(keypairs)
246 api.nova.server_list(
247 IsA(http.HttpRequest)) \
248 .AndReturn([self.servers.list(), False])
249 quotas.tenant_quota_usages(
250 IsA(http.HttpRequest)).MultipleTimes() \
251 .AndReturn(quota_data)
252
253 api.base.is_service_enabled(
254 IsA(http.HttpRequest),
255 'network').MultipleTimes() \
256 .AndReturn(True)
257 api.base.is_service_enabled(
258 IsA(http.HttpRequest),
259 'ec2').MultipleTimes() \
260 .AndReturn(False)
261
262 self.mox.ReplayAll()
263
264 res = self.client.get(INDEX_URL +
265 "?tab=access_security_tabs__floating_ips_tab")
266
267 allocate_action = self.getAndAssertTableAction(res, 'floating_ips',
268 'allocate')
269 self.assertEqual(set(['ajax-modal']), set(allocate_action.classes))
270 self.assertEqual('Allocate IP To Project',
271 six.text_type(allocate_action.verbose_name))
272 self.assertEqual(None, allocate_action.policy_rules)
273
274 url = 'horizon:project:access_and_security:floating_ips:allocate'
275 self.assertEqual(url, allocate_action.url)
276
277 @test.create_stubs({api.network: ('floating_ip_supported',
278 'tenant_floating_ip_list',
279 'security_group_list',
280 'floating_ip_pools_list',),
281 api.nova: ('keypair_list',
282 'server_list',),
283 quotas: ('tenant_quota_usages',),
284 api.base: ('is_service_enabled',)})
225 def test_allocate_button_disabled_when_quota_exceeded(self): 285 def test_allocate_button_disabled_when_quota_exceeded(self):
226 keypairs = self.keypairs.list() 286 keypairs = self.keypairs.list()
227 floating_ips = self.floating_ips.list() 287 floating_ips = self.floating_ips.list()
@@ -266,19 +326,12 @@ class FloatingIpViewTests(test.TestCase):
266 res = self.client.get(INDEX_URL + 326 res = self.client.get(INDEX_URL +
267 "?tab=access_security_tabs__floating_ips_tab") 327 "?tab=access_security_tabs__floating_ips_tab")
268 328
269 allocate_link = tables.AllocateIP() 329 allocate_action = self.getAndAssertTableAction(res, 'floating_ips',
270 url = allocate_link.get_link_url() 330 'allocate')
271 classes = (list(allocate_link.get_default_classes()) 331 self.assertTrue('disabled' in allocate_action.classes,
272 + list(allocate_link.classes)) 332 'The create button should be disabled')
273 link_name = "%s (%s)" % (six.text_type(allocate_link.verbose_name), 333 self.assertEqual('Allocate IP To Project (Quota exceeded)',
274 "Quota exceeded") 334 six.text_type(allocate_action.verbose_name))
275 expected_string = ("<a href='%s' title='%s' class='%s disabled' "
276 "id='floating_ips__action_allocate'>"
277 "<span class='fa fa-link'>"
278 "</span>%s</a>"
279 % (url, link_name, " ".join(classes), link_name))
280 self.assertContains(res, expected_string, html=True,
281 msg_prefix="The create button is not disabled")
282 335
283 336
284class FloatingIpNeutronViewTests(FloatingIpViewTests): 337class 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 f6f374a..046c74f 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
27from openstack_dashboard import api 27from openstack_dashboard import api
28from openstack_dashboard.dashboards.project.access_and_security \ 28from openstack_dashboard.dashboards.project.access_and_security \
29 import api_access 29 import api_access
30from openstack_dashboard.dashboards.project.access_and_security \
31 .security_groups import tables
32from openstack_dashboard.test import helpers as test 30from openstack_dashboard.test import helpers as test
33from openstack_dashboard.usage import quotas 31from openstack_dashboard.usage import quotas
34 32
@@ -171,6 +169,70 @@ class SecurityGroupTabTests(test.TestCase):
171 'server_list',), 169 'server_list',),
172 quotas: ('tenant_quota_usages',), 170 quotas: ('tenant_quota_usages',),
173 api.base: ('is_service_enabled',)}) 171 api.base: ('is_service_enabled',)})
172 def test_create_button_attributes(self):
173 keypairs = self.keypairs.list()
174 floating_ips = self.floating_ips.list()
175 floating_pools = self.pools.list()
176 sec_groups = self.security_groups.list()
177 quota_data = self.quota_usages.first()
178 quota_data['security_groups']['available'] = 10
179
180 api.network.floating_ip_supported(
181 IsA(http.HttpRequest)) \
182 .AndReturn(True)
183 api.network.tenant_floating_ip_list(
184 IsA(http.HttpRequest)) \
185 .AndReturn(floating_ips)
186 api.network.floating_ip_pools_list(
187 IsA(http.HttpRequest)) \
188 .AndReturn(floating_pools)
189 api.network.security_group_list(
190 IsA(http.HttpRequest)) \
191 .AndReturn(sec_groups)
192 api.nova.keypair_list(
193 IsA(http.HttpRequest)) \
194 .AndReturn(keypairs)
195 api.nova.server_list(
196 IsA(http.HttpRequest)) \
197 .AndReturn([self.servers.list(), False])
198 quotas.tenant_quota_usages(
199 IsA(http.HttpRequest)).MultipleTimes() \
200 .AndReturn(quota_data)
201
202 api.base.is_service_enabled(
203 IsA(http.HttpRequest), 'network').MultipleTimes() \
204 .AndReturn(True)
205 api.base.is_service_enabled(
206 IsA(http.HttpRequest), 'ec2').MultipleTimes() \
207 .AndReturn(False)
208
209 self.mox.ReplayAll()
210
211 res = self.client.get(INDEX_URL +
212 "?tab=access_security_tabs__security_groups_tab")
213
214 security_groups = res.context['security_groups_table'].data
215 self.assertItemsEqual(security_groups, self.security_groups.list())
216
217 create_action = self.getAndAssertTableAction(res, 'security_groups',
218 'create')
219
220 self.assertEqual('Create Security Group',
221 six.text_type(create_action.verbose_name))
222 self.assertEqual(None, create_action.policy_rules)
223 self.assertEqual(set(['ajax-modal']), set(create_action.classes))
224
225 url = 'horizon:project:access_and_security:security_groups:create'
226 self.assertEqual(url, create_action.url)
227
228 @test.create_stubs({api.network: ('floating_ip_supported',
229 'tenant_floating_ip_list',
230 'security_group_list',
231 'floating_ip_pools_list',),
232 api.nova: ('keypair_list',
233 'server_list',),
234 quotas: ('tenant_quota_usages',),
235 api.base: ('is_service_enabled',)})
174 def _test_create_button_disabled_when_quota_exceeded(self, 236 def _test_create_button_disabled_when_quota_exceeded(self,
175 network_enabled): 237 network_enabled):
176 keypairs = self.keypairs.list() 238 keypairs = self.keypairs.list()
@@ -217,18 +279,10 @@ class SecurityGroupTabTests(test.TestCase):
217 security_groups = res.context['security_groups_table'].data 279 security_groups = res.context['security_groups_table'].data
218 self.assertItemsEqual(security_groups, self.security_groups.list()) 280 self.assertItemsEqual(security_groups, self.security_groups.list())
219 281
220 create_link = tables.CreateGroup() 282 create_action = self.getAndAssertTableAction(res, 'security_groups',
221 url = create_link.get_link_url() 283 'create')
222 classes = (list(create_link.get_default_classes()) 284 self.assertTrue('disabled' in create_action.classes,
223 + list(create_link.classes)) 285 'The create button should be disabled')
224 link_name = "%s (%s)" % (six.text_type(create_link.verbose_name),
225 "Quota exceeded")
226 expected_string = "<a href='%s' title='%s' class='%s disabled' "\
227 "id='security_groups__action_create'>" \
228 "<span class='fa fa-plus'></span>%s</a>" \
229 % (url, link_name, " ".join(classes), link_name)
230 self.assertContains(res, expected_string, html=True,
231 msg_prefix="The create button is not disabled")
232 286
233 def test_create_button_disabled_when_quota_exceeded_neutron_disabled(self): 287 def test_create_button_disabled_when_quota_exceeded_neutron_disabled(self):
234 self._test_create_button_disabled_when_quota_exceeded(False) 288 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 2708e59..4c99966 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
28from django.forms import widgets 28from django.forms import widgets
29from django import http 29from django import http
30import django.test 30import django.test
31from django.utils import encoding
32from django.utils.http import urlencode 31from django.utils.http import urlencode
33from mox3.mox import IgnoreArg # noqa 32from mox3.mox import IgnoreArg # noqa
34from mox3.mox import IsA # noqa 33from mox3.mox import IsA # noqa
@@ -3595,6 +3594,54 @@ class InstanceTests(helpers.TestCase):
3595 'floating_ip_supported', 3594 'floating_ip_supported',
3596 'servers_update_addresses',), 3595 'servers_update_addresses',),
3597 }) 3596 })
3597 def test_launch_button_attributes(self):
3598 servers = self.servers.list()
3599 limits = self.limits['absolute']
3600 limits['totalInstancesUsed'] = 0
3601
3602 api.nova.extension_supported('AdminActions',
3603 IsA(http.HttpRequest)) \
3604 .MultipleTimes().AndReturn(True)
3605 api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
3606 .MultipleTimes().AndReturn(True)
3607 api.nova.flavor_list(IsA(http.HttpRequest)) \
3608 .AndReturn(self.flavors.list())
3609 api.glance.image_list_detailed(IgnoreArg()) \
3610 .AndReturn((self.images.list(), False, False))
3611 search_opts = {'marker': None, 'paginate': True}
3612 api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \
3613 .AndReturn([servers, False])
3614 api.network.servers_update_addresses(IsA(http.HttpRequest), servers)
3615 api.nova.tenant_absolute_limits(IsA(http.HttpRequest), reserved=True) \
3616 .MultipleTimes().AndReturn(limits)
3617 api.network.floating_ip_supported(IsA(http.HttpRequest)) \
3618 .MultipleTimes().AndReturn(True)
3619 api.network.floating_ip_simple_associate_supported(
3620 IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
3621
3622 self.mox.ReplayAll()
3623
3624 tables.LaunchLink()
3625 res = self.client.get(INDEX_URL)
3626
3627 launch_action = self.getAndAssertTableAction(res, 'instances',
3628 'launch')
3629
3630 self.assertEqual(set(['ajax-modal', 'ajax-update', 'btn-launch']),
3631 set(launch_action.classes))
3632 self.assertEqual('Launch Instance', launch_action.verbose_name)
3633 self.assertEqual('horizon:project:instances:launch', launch_action.url)
3634 self.assertEqual((('compute', 'compute:create'),),
3635 launch_action.policy_rules)
3636
3637 @helpers.create_stubs({
3638 api.nova: ('flavor_list', 'server_list', 'tenant_absolute_limits',
3639 'extension_supported',),
3640 api.glance: ('image_list_detailed',),
3641 api.network: ('floating_ip_simple_associate_supported',
3642 'floating_ip_supported',
3643 'servers_update_addresses',),
3644 })
3598 def test_launch_button_disabled_when_quota_exceeded(self): 3645 def test_launch_button_disabled_when_quota_exceeded(self):
3599 servers = self.servers.list() 3646 servers = self.servers.list()
3600 limits = self.limits['absolute'] 3647 limits = self.limits['absolute']
@@ -3622,27 +3669,16 @@ class InstanceTests(helpers.TestCase):
3622 3669
3623 self.mox.ReplayAll() 3670 self.mox.ReplayAll()
3624 3671
3625 launch = tables.LaunchLink() 3672 tables.LaunchLink()
3626 url = launch.get_link_url()
3627 classes = list(launch.get_default_classes()) + list(launch.classes)
3628 link_name = "%s (%s)" % (six.text_type(launch.verbose_name),
3629 "Quota exceeded")
3630
3631 res = self.client.get(INDEX_URL) 3673 res = self.client.get(INDEX_URL)
3632 if django.VERSION < (1, 8, 0): 3674
3633 resp_charset = res._charset 3675 launch_action = self.getAndAssertTableAction(
3634 else: 3676 res, 'instances', 'launch')
3635 resp_charset = res.charset 3677
3636 expected_string = encoding.smart_str(u''' 3678 self.assertTrue('disabled' in launch_action.classes,
3637 <a href="%s" title="%s" class="%s disabled" 3679 'The launch button should be disabled')
3638 data-update-url= 3680 self.assertEqual('Launch Instance (Quota exceeded)',
3639 "/project/instances/?action=launch&amp;table=instances" 3681 six.text_type(launch_action.verbose_name))
3640 id="instances__action_launch">
3641 <span class="fa fa-cloud-upload"></span>%s</a>
3642 ''' % (url, link_name, " ".join(classes), link_name), resp_charset)
3643
3644 self.assertContains(res, expected_string, html=True,
3645 msg_prefix="The launch button is not disabled")
3646 3682
3647 @helpers.create_stubs({api.glance: ('image_list_detailed',), 3683 @helpers.create_stubs({api.glance: ('image_list_detailed',),
3648 api.neutron: ('network_list',), 3684 api.neutron: ('network_list',),
diff --git a/openstack_dashboard/dashboards/project/networks/tests.py b/openstack_dashboard/dashboards/project/networks/tests.py
index 4b50302..7e433bd 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
19from horizon.workflows import views 19from horizon.workflows import views
20 20
21from mox3.mox import IsA # noqa 21from mox3.mox import IsA # noqa
22import six
22 23
23from openstack_dashboard import api 24from openstack_dashboard import api
24from openstack_dashboard.dashboards.project.networks.subnets import tables\
25 as subnets_tables
26from openstack_dashboard.dashboards.project.networks import tables\ 25from openstack_dashboard.dashboards.project.networks import tables\
27 as networks_tables 26 as networks_tables
28from openstack_dashboard.dashboards.project.networks import workflows 27from openstack_dashboard.dashboards.project.networks import workflows
@@ -2013,7 +2012,8 @@ class NetworkSubnetTests(test.TestCase):
2013class NetworkViewTests(test.TestCase, NetworkStubMixin): 2012class NetworkViewTests(test.TestCase, NetworkStubMixin):
2014 2013
2015 def _test_create_button_shown_when_quota_disabled( 2014 def _test_create_button_shown_when_quota_disabled(
2016 self, expected_string): 2015 self,
2016 find_button_fn):
2017 # if quota_data doesnt contain a networks|subnets|routers key or 2017 # if quota_data doesnt contain a networks|subnets|routers key or
2018 # these keys are empty dicts, its disabled 2018 # these keys are empty dicts, its disabled
2019 quota_data = self.neutron_quota_usages.first() 2019 quota_data = self.neutron_quota_usages.first()
@@ -2033,11 +2033,14 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin):
2033 2033
2034 networks = res.context['networks_table'].data 2034 networks = res.context['networks_table'].data
2035 self.assertItemsEqual(networks, self.networks.list()) 2035 self.assertItemsEqual(networks, self.networks.list())
2036 self.assertContains(res, expected_string, True, html=True, 2036
2037 msg_prefix="The enabled create button not shown") 2037 button = find_button_fn(res)
2038 self.assertFalse('disabled' in button.classes,
2039 "The create button should not be disabled")
2040 return button
2038 2041
2039 def _test_create_button_disabled_when_quota_exceeded( 2042 def _test_create_button_disabled_when_quota_exceeded(
2040 self, expected_string, network_quota=5, subnet_quota=5): 2043 self, find_button_fn, network_quota=5, subnet_quota=5, ):
2041 2044
2042 quota_data = self.neutron_quota_usages.first() 2045 quota_data = self.neutron_quota_usages.first()
2043 2046
@@ -2056,69 +2059,55 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin):
2056 2059
2057 networks = res.context['networks_table'].data 2060 networks = res.context['networks_table'].data
2058 self.assertItemsEqual(networks, self.networks.list()) 2061 self.assertItemsEqual(networks, self.networks.list())
2059 self.assertContains(res, expected_string, True, html=True, 2062
2060 msg_prefix="The create button is not disabled") 2063 button = find_button_fn(res)
2064 self.assertTrue('disabled' in button.classes,
2065 "The create button should be disabled")
2066 return button
2061 2067
2062 @test.create_stubs({api.neutron: ('network_list',), 2068 @test.create_stubs({api.neutron: ('network_list',),
2063 quotas: ('tenant_quota_usages',)}) 2069 quotas: ('tenant_quota_usages',)})
2064 def test_network_create_button_disabled_when_quota_exceeded_index(self): 2070 def test_network_create_button_disabled_when_quota_exceeded_index(self):
2065 create_link = networks_tables.CreateNetwork() 2071 networks_tables.CreateNetwork()
2066 url = create_link.get_link_url() 2072
2067 classes = (list(create_link.get_default_classes()) 2073 def _find_net_button(res):
2068 + list(create_link.classes)) 2074 return self.getAndAssertTableAction(res, 'networks', 'create')
2069 link_name = "%s (%s)" % (create_link.verbose_name, "Quota exceeded") 2075 self._test_create_button_disabled_when_quota_exceeded(_find_net_button,
2070 expected_string = "<a href='%s' title='%s' class='%s disabled' "\ 2076 network_quota=0)
2071 "id='networks__action_create'>" \
2072 "<span class='fa fa-plus'></span>%s</a>" \
2073 % (url, link_name, " ".join(classes), link_name)
2074 self._test_create_button_disabled_when_quota_exceeded(expected_string,
2075 network_quota=0
2076 )
2077 2077
2078 @test.create_stubs({api.neutron: ('network_list',), 2078 @test.create_stubs({api.neutron: ('network_list',),
2079 quotas: ('tenant_quota_usages',)}) 2079 quotas: ('tenant_quota_usages',)})
2080 def test_subnet_create_button_disabled_when_quota_exceeded_index(self): 2080 def test_subnet_create_button_disabled_when_quota_exceeded_index(self):
2081 network_id = self.networks.first().id 2081 network_id = self.networks.first().id
2082 create_link = networks_tables.CreateSubnet() 2082 networks_tables.CreateSubnet()
2083 url = reverse(create_link.get_link_url(), args=[network_id]) 2083
2084 classes = (list(create_link.get_default_classes()) 2084 def _find_subnet_button(res):
2085 + list(create_link.classes)) 2085 return self.getAndAssertTableRowAction(res, 'networks',
2086 link_name = "%s (%s)" % (create_link.verbose_name, "Quota exceeded") 2086 'subnet', network_id)
2087 expected_string = "<a href='%s' class='%s disabled' " \ 2087
2088 "id='networks__row_%s__action_subnet'>%s</a>" \ 2088 self._test_create_button_disabled_when_quota_exceeded(
2089 % (url, " ".join(classes), network_id, link_name) 2089 _find_subnet_button, subnet_quota=0)
2090 self._test_create_button_disabled_when_quota_exceeded(expected_string,
2091 subnet_quota=0
2092 )
2093 2090
2094 @test.create_stubs({api.neutron: ('network_list',), 2091 @test.create_stubs({api.neutron: ('network_list',),
2095 quotas: ('tenant_quota_usages',)}) 2092 quotas: ('tenant_quota_usages',)})
2096 def test_network_create_button_shown_when_quota_disabled_index(self): 2093 def test_network_create_button_shown_when_quota_disabled_index(self):
2097 # if quota_data doesnt contain a networks["available"] key its disabled 2094 # if quota_data doesnt contain a networks["available"] key its disabled
2098 create_link = networks_tables.CreateNetwork() 2095 networks_tables.CreateNetwork()
2099 url = create_link.get_link_url() 2096 self._test_create_button_shown_when_quota_disabled(
2100 classes = (list(create_link.get_default_classes()) 2097 lambda res: self.getAndAssertTableAction(res, 'networks', 'create')
2101 + list(create_link.classes)) 2098 )
2102 expected_string = "<a href='%s' title='%s' class='%s' "\
2103 "id='networks__action_create'>" \
2104 "<span class='fa fa-plus'></span>%s</a>" \
2105 % (url, create_link.verbose_name, " ".join(classes),
2106 create_link.verbose_name)
2107 self._test_create_button_shown_when_quota_disabled(expected_string)
2108 2099
2109 @test.create_stubs({api.neutron: ('network_list',), 2100 @test.create_stubs({api.neutron: ('network_list',),
2110 quotas: ('tenant_quota_usages',)}) 2101 quotas: ('tenant_quota_usages',)})
2111 def test_subnet_create_button_shown_when_quota_disabled_index(self): 2102 def test_subnet_create_button_shown_when_quota_disabled_index(self):
2112 # if quota_data doesnt contain a subnets["available"] key, its disabled 2103 # if quota_data doesnt contain a subnets["available"] key, its disabled
2113 network_id = self.networks.first().id 2104 network_id = self.networks.first().id
2114 create_link = networks_tables.CreateSubnet() 2105
2115 url = reverse(create_link.get_link_url(), args=[network_id]) 2106 def _find_subnet_button(res):
2116 classes = (list(create_link.get_default_classes()) 2107 return self.getAndAssertTableRowAction(res, 'networks',
2117 + list(create_link.classes)) 2108 'subnet', network_id)
2118 expected_string = "<a href='%s' class='%s' "\ 2109
2119 "id='networks__row_%s__action_subnet'>%s</a>" \ 2110 self._test_create_button_shown_when_quota_disabled(_find_subnet_button)
2120 % (url, " ".join(classes), network_id, create_link.verbose_name)
2121 self._test_create_button_shown_when_quota_disabled(expected_string)
2122 2111
2123 @test.create_stubs({api.neutron: ('network_get', 2112 @test.create_stubs({api.neutron: ('network_get',
2124 'subnet_list', 2113 'subnet_list',
@@ -2155,17 +2144,65 @@ class NetworkViewTests(test.TestCase, NetworkStubMixin):
2155 subnets = res.context['subnets_table'].data 2144 subnets = res.context['subnets_table'].data
2156 self.assertItemsEqual(subnets, self.subnets.list()) 2145 self.assertItemsEqual(subnets, self.subnets.list())
2157 2146
2158 class FakeTable(object): 2147 create_action = self.getAndAssertTableAction(res, 'subnets', 'create')
2159 kwargs = {'network_id': network_id} 2148 self.assertTrue('disabled' in create_action.classes,
2160 create_link = subnets_tables.CreateSubnet() 2149 'The create button should be disabled')
2161 create_link.table = FakeTable() 2150
2162 url = create_link.get_link_url() 2151 @test.create_stubs({api.neutron: ('network_list',),
2163 classes = (list(create_link.get_default_classes()) 2152 quotas: ('tenant_quota_usages',)})
2164 + list(create_link.classes)) 2153 def test_create_button_attributes(self):
2165 link_name = "%s (%s)" % (create_link.verbose_name, "Quota exceeded") 2154 create_action = self._test_create_button_shown_when_quota_disabled(
2166 expected_string = "<a href='%s' title='%s' class='%s disabled' "\ 2155 lambda res: self.getAndAssertTableAction(res, 'networks', 'create')
2167 "id='subnets__action_create'>" \ 2156 )
2168 "<span class='fa fa-plus'></span>%s</a>" \ 2157
2169 % (url, link_name, " ".join(classes), link_name) 2158 self.assertEqual(set(['ajax-modal']), set(create_action.classes))
2170 self.assertContains(res, expected_string, html=True, 2159 self.assertEqual('horizon:project:networks:create', create_action.url)
2171 msg_prefix="The create button is not disabled") 2160 self.assertEqual('Create Network',
2161 six.text_type(create_action.verbose_name))
2162 self.assertEqual((('network', 'create_network'),),
2163 create_action.policy_rules)
2164
2165 @test.create_stubs({api.neutron: ('network_get',
2166 'subnet_list',
2167 'port_list',
2168 'is_extension_supported',),
2169 quotas: ('tenant_quota_usages',)})
2170 def test_create_subnet_button_attributes(self):
2171 network_id = self.networks.first().id
2172 quota_data = self.neutron_quota_usages.first()
2173 quota_data['subnets']['available'] = 1
2174
2175 api.neutron.network_get(
2176 IsA(http.HttpRequest), network_id)\
2177 .MultipleTimes().AndReturn(self.networks.first())
2178 api.neutron.subnet_list(
2179 IsA(http.HttpRequest), network_id=network_id)\
2180 .AndReturn(self.subnets.list())
2181 api.neutron.port_list(
2182 IsA(http.HttpRequest), network_id=network_id)\
2183 .AndReturn([self.ports.first()])
2184 api.neutron.is_extension_supported(
2185 IsA(http.HttpRequest), 'mac-learning')\
2186 .AndReturn(False)
2187 quotas.tenant_quota_usages(
2188 IsA(http.HttpRequest)) \
2189 .MultipleTimes().AndReturn(quota_data)
2190
2191 self.mox.ReplayAll()
2192
2193 res = self.client.get(reverse('horizon:project:networks:detail',
2194 args=[network_id]))
2195 self.assertTemplateUsed(res, 'project/networks/detail.html')
2196
2197 subnets = res.context['subnets_table'].data
2198 self.assertItemsEqual(subnets, self.subnets.list())
2199
2200 create_action = self.getAndAssertTableAction(res, 'subnets', 'create')
2201
2202 self.assertEqual(set(['ajax-modal']), set(create_action.classes))
2203 self.assertEqual('horizon:project:networks:addsubnet',
2204 create_action.url)
2205 self.assertEqual('Create Subnet',
2206 six.text_type(create_action.verbose_name))
2207 self.assertEqual((('network', 'create_subnet'),),
2208 create_action.policy_rules)
diff --git a/openstack_dashboard/dashboards/project/routers/tests.py b/openstack_dashboard/dashboards/project/routers/tests.py
index 5aad93d..ce0116c 100644
--- a/openstack_dashboard/dashboards/project/routers/tests.py
+++ b/openstack_dashboard/dashboards/project/routers/tests.py
@@ -23,7 +23,6 @@ import six
23from openstack_dashboard import api 23from openstack_dashboard import api
24from openstack_dashboard.dashboards.project.routers.extensions.routerrules\ 24from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
25 import rulemanager 25 import rulemanager
26from openstack_dashboard.dashboards.project.routers import tables
27from openstack_dashboard.test import helpers as test 26from openstack_dashboard.test import helpers as test
28from openstack_dashboard.usage import quotas 27from openstack_dashboard.usage import quotas
29 28
@@ -938,18 +937,11 @@ class RouterViewTests(RouterMixin, test.TestCase):
938 routers = res.context['Routers_table'].data 937 routers = res.context['Routers_table'].data
939 self.assertItemsEqual(routers, self.routers.list()) 938 self.assertItemsEqual(routers, self.routers.list())
940 939
941 create_link = tables.CreateRouter() 940 create_action = self.getAndAssertTableAction(res, 'Routers', 'create')
942 url = create_link.get_link_url() 941 self.assertTrue('disabled' in create_action.classes,
943 classes = (list(create_link.get_default_classes()) 942 'Create button is not disabled')
944 + list(create_link.classes)) 943 self.assertEqual('Create Router (Quota exceeded)',
945 link_name = "%s (%s)" % (six.text_type(create_link.verbose_name), 944 create_action.verbose_name)
946 "Quota exceeded")
947 expected_string = "<a href='%s' title='%s' class='%s disabled' "\
948 "id='Routers__action_create'>" \
949 "<span class='fa fa-plus'></span>%s</a>" \
950 % (url, link_name, " ".join(classes), link_name)
951 self.assertContains(res, expected_string, html=True,
952 msg_prefix="The create button is not disabled")
953 945
954 @test.create_stubs({api.neutron: ('router_list', 'network_list'), 946 @test.create_stubs({api.neutron: ('router_list', 'network_list'),
955 quotas: ('tenant_quota_usages',)}) 947 quotas: ('tenant_quota_usages',)})
@@ -973,14 +965,38 @@ class RouterViewTests(RouterMixin, test.TestCase):
973 routers = res.context['Routers_table'].data 965 routers = res.context['Routers_table'].data
974 self.assertItemsEqual(routers, self.routers.list()) 966 self.assertItemsEqual(routers, self.routers.list())
975 967
976 create_link = tables.CreateRouter() 968 create_action = self.getAndAssertTableAction(res, 'Routers', 'create')
977 url = create_link.get_link_url() 969 self.assertFalse('disabled' in create_action.classes,
978 classes = (list(create_link.get_default_classes()) 970 'Create button should not be disabled')
979 + list(create_link.classes)) 971 self.assertEqual('Create Router',
980 link_name = "%s" % (six.text_type(create_link.verbose_name)) 972 create_action.verbose_name)
981 expected_string = "<a href='%s' title='%s' class='%s' "\ 973
982 "id='Routers__action_create'>" \ 974 @test.create_stubs({api.neutron: ('router_list', 'network_list'),
983 "<span class='fa fa-plus'></span>%s</a>" \ 975 quotas: ('tenant_quota_usages',)})
984 % (url, link_name, " ".join(classes), link_name) 976 def test_create_button_attributes(self):
985 self.assertContains(res, expected_string, html=True, 977 quota_data = self.neutron_quota_usages.first()
986 msg_prefix="The create button is not displayed") 978 quota_data['routers']['available'] = 10
979 api.neutron.router_list(
980 IsA(http.HttpRequest),
981 tenant_id=self.tenant.id,
982 search_opts=None).AndReturn(self.routers.list())
983 quotas.tenant_quota_usages(
984 IsA(http.HttpRequest)) \
985 .MultipleTimes().AndReturn(quota_data)
986
987 self._mock_external_network_list()
988 self.mox.ReplayAll()
989
990 res = self.client.get(self.INDEX_URL)
991 self.assertTemplateUsed(res, 'project/routers/index.html')
992
993 routers = res.context['Routers_table'].data
994 self.assertItemsEqual(routers, self.routers.list())
995
996 create_action = self.getAndAssertTableAction(res, 'Routers', 'create')
997 self.assertEqual(set(['ajax-modal']), set(create_action.classes))
998 self.assertEqual('Create Router',
999 six.text_type(create_action.verbose_name))
1000 self.assertEqual('horizon:project:routers:create', create_action.url)
1001 self.assertEqual((('network', 'create_router'),),
1002 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 ab7fbb6..b6a70d0 100644
--- a/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
+++ b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py
@@ -15,7 +15,6 @@
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations 16# License for the specific language governing permissions and limitations
17# under the License. 17# under the License.
18
19import django 18import django
20from django.core.urlresolvers import reverse 19from django.core.urlresolvers import reverse
21from django.forms import widgets 20from django.forms import widgets
@@ -24,11 +23,10 @@ from django.test.utils import override_settings
24 23
25from mox3.mox import IsA # noqa 24from mox3.mox import IsA # noqa
26import six 25import six
26from six import moves
27 27
28from openstack_dashboard import api 28from openstack_dashboard import api
29from openstack_dashboard.api import cinder 29from openstack_dashboard.api import cinder
30from openstack_dashboard.dashboards.project.volumes \
31 .volumes import tables
32from openstack_dashboard.test import helpers as test 30from openstack_dashboard.test import helpers as test
33from openstack_dashboard.usage import quotas 31from openstack_dashboard.usage import quotas
34 32
@@ -1021,6 +1019,50 @@ class VolumeViewTests(test.TestCase):
1021 server.id) 1019 server.id)
1022 self.assertEqual(res.status_code, 200) 1020 self.assertEqual(res.status_code, 200)
1023 1021
1022 def _get_volume_row_action_from_ajax(self, res, action_name, row_id):
1023 def _matches_row_id(context_row):
1024 return (len(context_row.dicts) > 1 and
1025 isinstance(context_row.dicts[1], dict) and
1026 context_row.dicts[1].get('row_id', None) == row_id)
1027
1028 matching = list(moves.filter(lambda r: _matches_row_id(r),
1029 res.context))
1030 self.assertTrue(len(matching) > 1,
1031 "Expected at least one row matching %s" % row_id)
1032 row = matching[-1].dicts[1]
1033 matching_actions = list(moves.filter(lambda a: a.name == action_name,
1034 row['row_actions']))
1035 self.assertEqual(1, len(matching_actions),
1036 "Expected one row action named '%s'" % action_name)
1037 return matching_actions[0]
1038
1039 @test.create_stubs({cinder: ('tenant_absolute_limits',
1040 'volume_get',)})
1041 def test_create_snapshot_button_attributes(self):
1042 limits = {'maxTotalSnapshots': 2}
1043 limits['totalSnapshotsUsed'] = 1
1044 volume = self.cinder_volumes.first()
1045
1046 cinder.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
1047 cinder.tenant_absolute_limits(IsA(http.HttpRequest)).AndReturn(limits)
1048 self.mox.ReplayAll()
1049
1050 res_url = (VOLUME_INDEX_URL +
1051 "?action=row_update&table=volumes&obj_id=" + volume.id)
1052
1053 res = self.client.get(res_url, {},
1054 HTTP_X_REQUESTED_WITH='XMLHttpRequest')
1055
1056 snapshot_action = self._get_volume_row_action_from_ajax(
1057 res, 'snapshots', volume.id)
1058 self.assertEqual('horizon:project:volumes:volumes:create_snapshot',
1059 snapshot_action.url)
1060 self.assertEqual(set(['ajax-modal']), set(snapshot_action.classes))
1061 self.assertEqual('Create Snapshot',
1062 six.text_type(snapshot_action.verbose_name))
1063 self.assertEqual((('volume', 'volume:create_snapshot'),),
1064 snapshot_action.policy_rules)
1065
1024 @test.create_stubs({cinder: ('tenant_absolute_limits', 1066 @test.create_stubs({cinder: ('tenant_absolute_limits',
1025 'volume_get',)}) 1067 'volume_get',)})
1026 def test_create_snapshot_button_disabled_when_quota_exceeded(self): 1068 def test_create_snapshot_button_disabled_when_quota_exceeded(self):
@@ -1032,25 +1074,57 @@ class VolumeViewTests(test.TestCase):
1032 cinder.tenant_absolute_limits(IsA(http.HttpRequest)).AndReturn(limits) 1074 cinder.tenant_absolute_limits(IsA(http.HttpRequest)).AndReturn(limits)
1033 self.mox.ReplayAll() 1075 self.mox.ReplayAll()
1034 1076
1035 create_link = tables.CreateSnapshot()
1036 url = reverse(create_link.get_link_url(), args=[volume.id])
1037 res_url = (VOLUME_INDEX_URL + 1077 res_url = (VOLUME_INDEX_URL +
1038 "?action=row_update&table=volumes&obj_id=" + volume.id) 1078 "?action=row_update&table=volumes&obj_id=" + volume.id)
1039 1079
1040 res = self.client.get(res_url, {}, 1080 res = self.client.get(res_url, {},
1041 HTTP_X_REQUESTED_WITH='XMLHttpRequest') 1081 HTTP_X_REQUESTED_WITH='XMLHttpRequest')
1042 1082
1043 classes = (list(create_link.get_default_classes()) 1083 snapshot_action = self._get_volume_row_action_from_ajax(
1044 + list(create_link.classes)) 1084 res, 'snapshots', volume.id)
1045 link_name = "%s (%s)" % (six.text_type(create_link.verbose_name), 1085 self.assertTrue('disabled' in snapshot_action.classes,
1046 "Quota exceeded") 1086 'The create snapshot button should be disabled')
1047 expected_string = "<a href='%s' class=\"%s disabled\" "\ 1087
1048 "id=\"volumes__row_%s__action_snapshots\">%s</a>" \ 1088 @test.create_stubs({cinder: ('tenant_absolute_limits',
1049 % (url, " ".join(classes), volume.id, link_name) 1089 'volume_list',
1090 'volume_snapshot_list',
1091 'volume_backup_supported',),
1092 api.nova: ('server_list',)})
1093 def test_create_button_attributes(self):
1094 limits = self.cinder_limits['absolute']
1095 limits['maxTotalVolumes'] = 10
1096 limits['totalVolumesUsed'] = 1
1097 volumes = self.cinder_volumes.list()
1098
1099 api.cinder.volume_backup_supported(IsA(http.HttpRequest)). \
1100 MultipleTimes().AndReturn(True)
1101 cinder.volume_list(IsA(http.HttpRequest), search_opts=None)\
1102 .AndReturn(volumes)
1103 cinder.volume_snapshot_list(IsA(http.HttpRequest),
1104 search_opts=None).\
1105 AndReturn([])
1106 api.nova.server_list(IsA(http.HttpRequest), search_opts=None)\
1107 .AndReturn([self.servers.list(), False])
1108 cinder.tenant_absolute_limits(IsA(http.HttpRequest))\
1109 .MultipleTimes().AndReturn(limits)
1110 self.mox.ReplayAll()
1111
1112 res = self.client.get(VOLUME_INDEX_URL)
1113 self.assertTemplateUsed(res, 'project/volumes/index.html')
1114
1115 volumes = res.context['volumes_table'].data
1116 self.assertItemsEqual(volumes, self.cinder_volumes.list())
1117
1118 create_action = self.getAndAssertTableAction(res, 'volumes', 'create')
1050 1119
1051 self.assertContains( 1120 self.assertEqual(set(['ajax-modal', 'ajax-update', 'btn-create']),
1052 res, expected_string, html=True, 1121 set(create_action.classes))
1053 msg_prefix="The create snapshot button is not disabled") 1122 self.assertEqual('Create Volume',
1123 six.text_type(create_action.verbose_name))
1124 self.assertEqual('horizon:project:volumes:volumes:create',
1125 create_action.url)
1126 self.assertEqual((('volume', 'volume:create'),),
1127 create_action.policy_rules)
1054 1128
1055 @test.create_stubs({cinder: ('tenant_absolute_limits', 1129 @test.create_stubs({cinder: ('tenant_absolute_limits',
1056 'volume_list', 1130 'volume_list',
@@ -1081,19 +1155,9 @@ class VolumeViewTests(test.TestCase):
1081 volumes = res.context['volumes_table'].data 1155 volumes = res.context['volumes_table'].data
1082 self.assertItemsEqual(volumes, self.cinder_volumes.list()) 1156 self.assertItemsEqual(volumes, self.cinder_volumes.list())
1083 1157
1084 create_link = tables.CreateVolume() 1158 create_action = self.getAndAssertTableAction(res, 'volumes', 'create')
1085 url = create_link.get_link_url() 1159 self.assertTrue('disabled' in create_action.classes,
1086 classes = (list(create_link.get_default_classes()) 1160 'The create button should be disabled')
1087 + list(create_link.classes))
1088 link_name = "%s (%s)" % (six.text_type(create_link.verbose_name),
1089 "Quota exceeded")
1090 expected_string = "<a href='%s' title='%s' class='%s disabled' "\
1091 "id='volumes__action_create' data-update-url=" \
1092 "'/project/volumes/?action=create&amp;table=volumes'> "\
1093 "<span class='fa fa-plus'></span>%s</a>" \
1094 % (url, link_name, " ".join(classes), link_name)
1095 self.assertContains(res, expected_string, html=True,
1096 msg_prefix="The create button is not disabled")
1097 1161
1098 @test.create_stubs({cinder: ('tenant_absolute_limits', 1162 @test.create_stubs({cinder: ('tenant_absolute_limits',
1099 'volume_get',), 1163 'volume_get',),
diff --git a/openstack_dashboard/test/helpers.py b/openstack_dashboard/test/helpers.py
index b108d6b..b01216b 100644
--- a/openstack_dashboard/test/helpers.py
+++ b/openstack_dashboard/test/helpers.py
@@ -280,6 +280,44 @@ class TestCase(horizon_helpers.TestCase):
280 def assertItemsCollectionEqual(self, response, items_list): 280 def assertItemsCollectionEqual(self, response, items_list):
281 self.assertEqual(response.json, {"items": items_list}) 281 self.assertEqual(response.json, {"items": items_list})
282 282
283 def getAndAssertTableRowAction(self, response, table_name,
284 action_name, row_id):
285 table = response.context[table_name + '_table']
286 full_row_id = '%s__row__%s' % (table_name, row_id)
287 rows = list(moves.filter(lambda x: x.id == full_row_id,
288 table.get_rows()))
289 self.assertEqual(1, len(rows),
290 "Did not find a row matching id '%s'" % row_id)
291 row_actions = table.get_row_actions(rows[0])
292
293 msg_args = (table_name, action_name, row_id)
294 self.assertTrue(
295 len(row_actions) > 0,
296 "No action named '%s' found in table '%s' row '%s'" % msg_args)
297
298 self.assertEqual(
299 1, len(row_actions),
300 "Multiple actions '%s' found in table '%s' row '%s'" % msg_args)
301
302 return row_actions[0]
303
304 def getAndAssertTableAction(self, response, table_name, action_name):
305
306 table = response.context[table_name + '_table']
307 table_actions = table.get_table_actions()
308 actions = list(moves.filter(lambda x: x.name == action_name,
309 table_actions))
310 msg_args = (table_name, action_name)
311 self.assertTrue(
312 len(actions) > 0,
313 "No action named '%s' found in table '%s'" % msg_args)
314
315 self.assertEqual(
316 1, len(actions),
317 "More than one action named '%s' found in table '%s'" % msg_args)
318
319 return actions[0]
320
283 @staticmethod 321 @staticmethod
284 def mock_rest_request(**args): 322 def mock_rest_request(**args):
285 mock_args = { 323 mock_args = {