Merge "Deduplicate client unit tests"
diff --git a/tempest/api/compute/test_tenant_networks.py b/tempest/api/compute/test_tenant_networks.py
index 80691a8..96b7ef6 100644
--- a/tempest/api/compute/test_tenant_networks.py
+++ b/tempest/api/compute/test_tenant_networks.py
@@ -22,13 +22,23 @@
     def resource_setup(cls):
         super(ComputeTenantNetworksTest, cls).resource_setup()
         cls.client = cls.os.tenant_networks_client
+        cls.network = cls.get_tenant_network()
+
+    @classmethod
+    def setup_credentials(cls):
+        cls.set_network_resources(network=True)
+        super(ComputeTenantNetworksTest, cls).setup_credentials()
 
     @test.idempotent_id('edfea98e-bbe3-4c7a-9739-87b986baff26')
+    @test.services('network')
     def test_list_show_tenant_networks(self):
-        tenant_networks = self.client.list_tenant_networks()['networks']
-        self.assertNotEmpty(tenant_networks, "No tenant networks found.")
+        # Fetch all networks that are visible to the tenant: this may include
+        # shared and external networks
+        tenant_networks = [
+            n['id'] for n in self.client.list_tenant_networks()['networks']
+        ]
+        self.assertIn(self.network['id'], tenant_networks,
+                      "No tenant networks found.")
 
-        for net in tenant_networks:
-            tenant_network = (self.client.show_tenant_network(net['id'])
-                              ['network'])
-            self.assertEqual(net['id'], tenant_network['id'])
+        net = self.client.show_tenant_network(self.network['id'])
+        self.assertEqual(self.network['id'], net['network']['id'])
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
new file mode 100644
index 0000000..4e5b41d
--- /dev/null
+++ b/tempest/api/identity/v2/test_users.py
@@ -0,0 +1,78 @@
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    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 copy
+
+from tempest_lib.common.utils import data_utils
+from tempest_lib import exceptions
+
+from tempest.api.identity import base
+from tempest import manager
+from tempest import test
+
+
+class IdentityUsersTest(base.BaseIdentityV2Test):
+
+    @classmethod
+    def resource_setup(cls):
+        super(IdentityUsersTest, cls).resource_setup()
+        cls.creds = cls.os.credentials
+        cls.username = cls.creds.username
+        cls.password = cls.creds.password
+        cls.tenant_name = cls.creds.tenant_name
+
+    @test.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
+    def test_user_update_own_password(self):
+        self.new_creds = copy.copy(self.creds.credentials)
+        self.new_creds.password = data_utils.rand_password()
+        # we need new non-admin Identity Client with new credentials, since
+        # current non_admin_client token will be revoked after updating
+        # password
+        self.non_admin_client_for_cleanup = copy.copy(self.non_admin_client)
+        self.non_admin_client_for_cleanup.auth_provider = (
+            manager.get_auth_provider(self.new_creds))
+        user_id = self.creds.credentials.user_id
+        old_pass = self.creds.credentials.password
+        new_pass = self.new_creds.password
+
+        # to change password back. important for allow_tenant_isolation = false
+        self.addCleanup(
+            self.non_admin_client_for_cleanup.update_user_own_password,
+            user_id=user_id,
+            new_pass=old_pass,
+            old_pass=new_pass)
+
+        # user updates own password
+        resp = self.non_admin_client.update_user_own_password(
+            user_id=user_id, new_pass=new_pass, old_pass=old_pass)
+
+        # check authorization with new token
+        self.non_admin_token_client.auth_token(resp['token']['id'])
+        # check authorization with new password
+        self.non_admin_token_client.auth(self.username,
+                                         new_pass,
+                                         self.tenant_name)
+
+        # authorize with old token should lead to Unauthorized
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_token_client.auth_token,
+                          self.non_admin_client.token)
+
+        # authorize with old password should lead to Unauthorized
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_token_client.auth,
+                          self.username,
+                          old_pass,
+                          self.tenant_name)
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
new file mode 100644
index 0000000..a1f664f
--- /dev/null
+++ b/tempest/api/identity/v3/test_users.py
@@ -0,0 +1,72 @@
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    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 copy
+
+from tempest_lib.common.utils import data_utils
+from tempest_lib import exceptions
+
+from tempest.api.identity import base
+from tempest import manager
+from tempest import test
+
+
+class IdentityV3UsersTest(base.BaseIdentityV3Test):
+
+    @classmethod
+    def resource_setup(cls):
+        super(IdentityV3UsersTest, cls).resource_setup()
+        cls.creds = cls.os.credentials
+        cls.user_id = cls.creds.user_id
+        cls.username = cls.creds.username
+        cls.password = cls.creds.password
+
+    @test.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
+    def test_user_update_own_password(self):
+        self.new_creds = copy.copy(self.creds.credentials)
+        self.new_creds.password = data_utils.rand_password()
+        # we need new non-admin Identity V3 Client with new credentials, since
+        # current non_admin_client token will be revoked after updating
+        # password
+        self.non_admin_client_for_cleanup = copy.copy(self.non_admin_client)
+        self.non_admin_client_for_cleanup.auth_provider = (
+            manager.get_auth_provider(self.new_creds))
+        user_id = self.creds.credentials.user_id
+        old_pass = self.creds.credentials.password
+        new_pass = self.new_creds.password
+        # to change password back. important for allow_tenant_isolation = false
+        self.addCleanup(
+            self.non_admin_client_for_cleanup.update_user_password,
+            user_id=user_id,
+            password=old_pass,
+            original_password=new_pass)
+
+        # user updates own password
+        self.non_admin_client.update_user_password(
+            user_id=user_id, password=new_pass, original_password=old_pass)
+
+        # check authorization with new password
+        self.non_admin_token.auth(user_id=self.user_id, password=new_pass)
+
+        # authorize with old token should lead to IdentityError (404 code)
+        self.assertRaises(exceptions.IdentityError,
+                          self.non_admin_token.auth,
+                          token=self.non_admin_client.token)
+
+        # authorize with old password should lead to Unauthorized
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_token.auth,
+                          user_id=self.user_id,
+                          password=old_pass)
diff --git a/tempest/services/identity/v2/json/identity_client.py b/tempest/services/identity/v2/json/identity_client.py
index e6416d6..8eeefe7 100644
--- a/tempest/services/identity/v2/json/identity_client.py
+++ b/tempest/services/identity/v2/json/identity_client.py
@@ -297,6 +297,17 @@
         self.expected_success(200, resp.status)
         return service_client.ResponseBody(resp, self._parse_resp(body))
 
+    def update_user_own_password(self, user_id, new_pass, old_pass):
+        """User updates own password"""
+        patch_body = {
+            "password": new_pass,
+            "original_password": old_pass
+        }
+        patch_body = json.dumps({'user': patch_body})
+        resp, body = self.patch('OS-KSCRUD/users/%s' % user_id, patch_body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, self._parse_resp(body))
+
     def list_extensions(self):
         """List all the extensions."""
         resp, body = self.get('/extensions')