Skip to content

Commit

Permalink
Add CRUD tests for users
Browse files Browse the repository at this point in the history
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
Ichimonji10 committed Oct 21, 2015
1 parent dcd77e2 commit 1fca0c5
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ developers, not a gospel.
api/pulp_smash.tests.platform.api_v2
api/pulp_smash.tests.platform.api_v2.test_content_applicability
api/pulp_smash.tests.platform.api_v2.test_login
api/pulp_smash.tests.platform.api_v2.test_user
api/tests
api/tests.test_config
7 changes: 7 additions & 0 deletions docs/api/pulp_smash.tests.platform.api_v2.test_user.rst
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
298 changes: 298 additions & 0 deletions pulp_smash/tests/platform/api_v2/test_user.py
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()

0 comments on commit 1fca0c5

Please sign in to comment.