-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add tests that create, read, update, delete and search for users. Document the new tests. The new test module should be a functional superset of the tests in Pulp Automation's `tests.general_tests.test_05_user` module. Test results: $ python -m unittest2 pulp_smash.tests.platform.api_v2.test_user ............... ---------------------------------------------------------------------- Ran 15 tests in 16.291s OK
- Loading branch information
1 parent
dcd77e2
commit 1fca0c5
Showing
3 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
`pulp_smash.tests.platform.api_v2.test_user` | ||
============================================ | ||
|
||
Location: :doc:`/index` → :doc:`/api` → | ||
:doc:`pulp_smash.tests.platform.api_v2.test_user` | ||
|
||
.. automodule:: pulp_smash.tests.platform.api_v2.test_user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,298 @@ | ||
# coding=utf-8 | ||
"""Test the `user`_ API endpoints. | ||
The assumptions explored in this module have the following dependencies:: | ||
It is possible to create a user. | ||
├── It is impossible to create a duplicate user. | ||
├── It is possible to read a user. | ||
├── It is possible to update a user. | ||
│ └── It is possible to search for a (updated) user. | ||
└── It is possible to delete a user. | ||
.. _user: | ||
https://pulp.readthedocs.org/en/latest/dev-guide/integration/rest-api/user/index.html | ||
""" | ||
from __future__ import unicode_literals | ||
|
||
import requests | ||
from pulp_smash.config import get_config | ||
from random import randint | ||
from unittest2 import TestCase | ||
|
||
|
||
USER_PATH = '/pulp/api/v2/users/' | ||
|
||
|
||
def _rand_str(): | ||
"""Return a randomized string.""" | ||
return type('')(randint(-100000, 100000)) | ||
|
||
|
||
def _search_logins(response): | ||
"""Return a tuple of all logins in a search response.""" | ||
response.raise_for_status() | ||
return tuple(resp['login'] for resp in response.json()) | ||
|
||
|
||
class CreateTestCase(TestCase): | ||
"""Can we create users? No prior assumptions are made.""" | ||
|
||
@classmethod | ||
def setUpClass(cls): | ||
"""Create several users. | ||
Create one user with the minimum required attributes, and another with | ||
all available attributes. | ||
""" | ||
cls.cfg = get_config() | ||
cls.bodies = ( | ||
{'login': _rand_str()}, | ||
{key: _rand_str() for key in {'login', 'password', 'name'}}, | ||
) | ||
cls.responses = tuple(( | ||
requests.post( | ||
cls.cfg.base_url + USER_PATH, | ||
json=body, | ||
**cls.cfg.get_requests_kwargs() | ||
) | ||
for body in cls.bodies | ||
)) | ||
|
||
def test_status_code(self): | ||
"""Assert that each response has an HTTP 201 status code.""" | ||
for i, response in enumerate(self.responses): | ||
with self.subTest(self.bodies[i]): | ||
self.assertEqual(response.status_code, 201) | ||
|
||
def test_password(self): | ||
"""Assert that responses do not contain passwords.""" | ||
for i, response in enumerate(self.responses): | ||
with self.subTest(self.bodies[i]): | ||
self.assertNotIn('password', response.json()) | ||
|
||
def test_attrs(self): | ||
"""Assert that each user has the requested attributes.""" | ||
bodies = [body.copy() for body in self.bodies] | ||
for body in bodies: | ||
body.pop('password', None) | ||
for i, body in enumerate(bodies): | ||
with self.subTest(body): | ||
# First check response keys… | ||
attrs = self.responses[i].json() | ||
self.assertLessEqual(set(body.keys()), set(attrs.keys())) | ||
# …then check response values. | ||
attrs = {key: attrs[key] for key in body.keys()} | ||
self.assertEqual(body, attrs) | ||
|
||
@classmethod | ||
def tearDownClass(cls): | ||
"""Delete the created users.""" | ||
for response in cls.responses: | ||
requests.delete( | ||
cls.cfg.base_url + response.json()['_href'], | ||
**cls.cfg.get_requests_kwargs() | ||
).raise_for_status() | ||
|
||
|
||
class ReadUpdateDeleteTestCase(TestCase): | ||
"""Can we read, update and delete users? | ||
This test case assumes that the assertions in :class:`CreateTestCase` are | ||
valid. | ||
""" | ||
|
||
@classmethod | ||
def setUpClass(cls): | ||
"""Create three users and read, update and delete them respectively.""" | ||
cls.update_body = {'delta': { | ||
'name': _rand_str(), | ||
'password': _rand_str(), | ||
'roles': ['super-users'], | ||
}} | ||
cls.cfg = get_config() | ||
cls.paths = [] | ||
for _ in range(3): | ||
response = requests.post( | ||
cls.cfg.base_url + USER_PATH, | ||
json={'login': _rand_str()}, | ||
**cls.cfg.get_requests_kwargs() | ||
) | ||
response.raise_for_status() | ||
cls.paths.append(response.json()['_href']) | ||
cls.read_response = requests.get( | ||
cls.cfg.base_url + cls.paths[0], | ||
**cls.cfg.get_requests_kwargs() | ||
) | ||
cls.update_response = requests.put( | ||
cls.cfg.base_url + cls.paths[1], | ||
json=cls.update_body, | ||
**cls.cfg.get_requests_kwargs() | ||
) | ||
cls.delete_response = requests.delete( | ||
cls.cfg.base_url + cls.paths[2], | ||
**cls.cfg.get_requests_kwargs() | ||
) | ||
|
||
def test_status_codes(self): | ||
"""Do the read, update and delete responses have 200 status codes?""" | ||
for attr in ('read_response', 'update_response', 'delete_response'): | ||
with self.subTest(attr): | ||
self.assertEqual(getattr(self, attr).status_code, 200) | ||
|
||
def test_password_in_responses(self): | ||
"""Ensure read and update responses do not contain a password. | ||
Target https://bugzilla.redhat.com/show_bug.cgi?id=1020300. | ||
""" | ||
for response in (self.read_response, self.update_response): | ||
with self.subTest(response): | ||
self.assertNotIn('password', response.json()) | ||
|
||
def test_use_deleted_user(self): | ||
"""Assert that one cannot read, update or delete a deleted user.""" | ||
http_actions = ('get', 'put', 'delete') | ||
responses = tuple(( | ||
getattr(requests, http_action)( | ||
self.cfg.base_url + self.paths[-1], | ||
**self.cfg.get_requests_kwargs() | ||
) | ||
for http_action in http_actions | ||
)) | ||
for i, response in enumerate(responses): | ||
with self.subTest(http_actions[i]): | ||
self.assertEqual(response.status_code, 404) | ||
|
||
def test_updated_user(self): | ||
"""Assert that the updated user has the assigned attributes.""" | ||
attrs = self.update_response.json() | ||
for key in set(self.update_body['delta'].keys()) - {'password'}: | ||
with self.subTest(key): | ||
self.assertIn(key, attrs.keys()) | ||
self.assertEqual(self.update_body['delta'][key], attrs[key]) | ||
|
||
def test_updated_user_password(self): | ||
"""Assert that one can log in with a user with an updated password.""" | ||
login = self.update_response.json()['login'] | ||
requests.post( | ||
self.cfg.base_url + '/pulp/api/v2/actions/login/', | ||
auth=(login, self.update_body['delta']['password']), | ||
verify=self.cfg.verify, | ||
).raise_for_status() | ||
|
||
def test_create_duplicate_user(self): | ||
"""Verify that one cannot create a duplicate user.""" | ||
response = requests.post( | ||
self.cfg.base_url + USER_PATH, | ||
json={'login': self.read_response.json()['login']}, | ||
**self.cfg.get_requests_kwargs() | ||
) | ||
self.assertEqual(response.status_code, 409) | ||
|
||
@classmethod | ||
def tearDownClass(cls): | ||
"""Delete created users. | ||
:meth:`setUpClass` makes a super-user. Thus, this method tests whether | ||
it is possible to delete a super-user. | ||
""" | ||
for path in cls.paths[0:1]: | ||
requests.delete( | ||
cls.cfg.base_url + path, | ||
**cls.cfg.get_requests_kwargs() | ||
).raise_for_status() | ||
|
||
|
||
class SearchTestCase(TestCase): | ||
"""Can we search for users? | ||
This test case assumes that the assertions in | ||
:class:`ReadUpdateDeleteTestCase` are valid. | ||
""" | ||
|
||
@classmethod | ||
def setUpClass(cls): | ||
"""Create a user and add it to the 'super-users' role. | ||
Search for: | ||
* Nothing at all. | ||
* All super-users. | ||
* A user by their login. | ||
* A non-existent user by their login. | ||
""" | ||
# Create a user and note information about it. | ||
cls.cfg = get_config() | ||
cls.login = _rand_str() | ||
response = requests.post( | ||
cls.cfg.base_url + USER_PATH, | ||
json={'login': cls.login}, | ||
**cls.cfg.get_requests_kwargs() | ||
) | ||
response.raise_for_status() | ||
cls.path = response.json()['_href'] | ||
|
||
# Make user a super-user. | ||
requests.put( | ||
cls.cfg.base_url + cls.path, | ||
json={'delta': {'roles': ['super-users']}}, | ||
**cls.cfg.get_requests_kwargs() | ||
).raise_for_status() | ||
|
||
# Formulate and execute searches. Save responses. | ||
searches = tuple(( | ||
{'criteria': {}}, | ||
{'criteria': {'filters': {'roles': ['super-users']}}}, | ||
{'criteria': {'filters': {'roles': []}}}, | ||
{'criteria': {'filters': {'login': cls.login}}}, | ||
{'criteria': {'filters': {'login': _rand_str()}}}, | ||
)) | ||
cls.responses = tuple(( | ||
requests.post( | ||
cls.cfg.base_url + USER_PATH + 'search/', | ||
json=search, | ||
**cls.cfg.get_requests_kwargs() | ||
) | ||
for search in searches | ||
)) | ||
|
||
def test_status_codes(self): | ||
"""Assert that each response has an HTTP 200 status code.""" | ||
for i, response in enumerate(self.responses): | ||
with self.subTest(i): | ||
self.assertEqual(response.status_code, 200, response.json()) | ||
|
||
def test_global_search(self): | ||
"""Assert that the global search includes the user's login.""" | ||
self.assertIn(self.login, _search_logins(self.responses[0])) | ||
|
||
def test_roles_filter_inclusion(self): | ||
"""Assert that the "roles" filter can be used for inclusion.""" | ||
self.assertIn(self.login, _search_logins(self.responses[1])) | ||
|
||
def test_roles_filter_exclusion(self): | ||
"""Assert that the "roles" filter can be used for exclusion.""" | ||
self.assertNotIn(self.login, _search_logins(self.responses[2])) | ||
|
||
def test_login_filter_inclusion(self): | ||
"""Search for a user via the "login" filter.""" | ||
self.assertEqual({self.login}, set(_search_logins(self.responses[3]))) | ||
|
||
def test_login_filter_exclusion(self): | ||
"""Search for a non-existent user via the "login" filter.""" | ||
self.assertEqual(len(_search_logins(self.responses[4])), 0) | ||
|
||
@classmethod | ||
def tearDownClass(cls): | ||
"""Delete created users.""" | ||
requests.delete( | ||
cls.cfg.base_url + cls.path, | ||
**cls.cfg.get_requests_kwargs() | ||
).raise_for_status() |