# -*- coding: utf-8; -*- from unittest.mock import patch from sqlalchemy import orm import colander from wuttaweb.grids import Grid from wuttaweb.views import users as mod from wuttaweb.testing import WebTestCase, FunctionalTestCase class TestUserView(WebTestCase): def make_view(self): return mod.UserView(self.request) def test_includeme(self): self.pyramid_config.include("wuttaweb.views.users") def test_get_query(self): view = self.make_view() query = view.get_query(session=self.session) self.assertIsInstance(query, orm.Query) def test_configure_grid(self): model = self.app.model view = self.make_view() grid = view.make_grid(model_class=model.User) self.assertFalse(grid.is_linked("person")) view.configure_grid(grid) self.assertTrue(grid.is_linked("person")) def test_grid_row_class(self): model = self.app.model user = model.User(username="barney", active=True) data = dict(user) view = self.make_view() self.assertIsNone(view.grid_row_class(user, data, 1)) user.active = False self.assertEqual(view.grid_row_class(user, data, 1), "has-background-warning") def test_is_editable(self): model = self.app.model view = self.make_view() # active user is editable user = model.User(username="barney", active=True) self.assertTrue(view.is_editable(user)) # inactive also editable user = model.User(username="barney", active=False) self.assertTrue(view.is_editable(user)) # but not if prevent_edit flag is set user = model.User(username="barney", prevent_edit=True) self.assertFalse(view.is_editable(user)) # unless request user is root self.request.is_root = True self.assertTrue(view.is_editable(user)) def test_configure_form(self): model = self.app.model person = model.Person( first_name="Barney", last_name="Rubble", full_name="Barney Rubble" ) barney = model.User(username="barney", person=person) self.session.add(barney) self.session.commit() view = self.make_view() # person replaced with first/last name when creating or editing with patch.object(view, "viewing", new=True): form = view.make_form(model_instance=barney) self.assertIn("person", form) self.assertNotIn("first_name", form) self.assertNotIn("last_name", form) view.configure_form(form) self.assertIn("person", form) self.assertNotIn("first_name", form) self.assertNotIn("last_name", form) with patch.object(view, "creating", new=True): form = view.make_form(model_instance=barney) self.assertIn("person", form) self.assertNotIn("first_name", form) self.assertNotIn("last_name", form) view.configure_form(form) self.assertNotIn("person", form) self.assertIn("first_name", form) self.assertIn("last_name", form) with patch.object(view, "editing", new=True): form = view.make_form(model_instance=barney) self.assertIn("person", form) self.assertNotIn("first_name", form) self.assertNotIn("last_name", form) view.configure_form(form) self.assertNotIn("person", form) self.assertIn("first_name", form) self.assertIn("last_name", form) # first/last name have default values when editing with patch.object(view, "editing", new=True): form = view.make_form(model_instance=barney) self.assertNotIn("first_name", form.defaults) self.assertNotIn("last_name", form.defaults) view.configure_form(form) self.assertIn("first_name", form.defaults) self.assertEqual(form.defaults["first_name"], "Barney") self.assertIn("last_name", form.defaults) self.assertEqual(form.defaults["last_name"], "Rubble") # password removed (always, for now) with patch.object(view, "viewing", new=True): form = view.make_form(model_instance=barney) self.assertIn("password", form) view.configure_form(form) self.assertNotIn("password", form) with patch.object(view, "editing", new=True): form = view.make_form(model_instance=barney) self.assertIn("password", form) view.configure_form(form) self.assertNotIn("password", form) # api tokens grid shown only if current user has perm with patch.object(view, "viewing", new=True): form = view.make_form(model_instance=barney) self.assertIn("api_tokens", form) view.configure_form(form) self.assertNotIn("api_tokens", form) with patch.object(self.request, "is_root", new=True): form = view.make_form(model_instance=barney) self.assertIn("api_tokens", form) view.configure_form(form) self.assertIn("api_tokens", form) def test_unique_username(self): model = self.app.model view = self.make_view() user = model.User(username="foo") self.session.add(user) self.session.commit() with patch.object(view, "Session", return_value=self.session): # invalid if same username in data node = colander.SchemaNode(colander.String(), name="username") self.assertRaises(colander.Invalid, view.unique_username, node, "foo") # but not if username belongs to current user view.editing = True self.request.matchdict = {"uuid": user.uuid} node = colander.SchemaNode(colander.String(), name="username") self.assertIsNone(view.unique_username(node, "foo")) def test_objectify(self): model = self.app.model auth = self.app.get_auth_handler() view = self.make_view() blokes = model.Role(name="Blokes") self.session.add(blokes) others = model.Role(name="Others") self.session.add(others) barney = model.User(username="barney") auth.set_user_password(barney, "testpass") barney.roles.append(blokes) self.session.add(barney) self.session.commit() with patch.object(self.request, "matchdict", new={"uuid": barney.uuid}): with patch.object(view, "editing", new=True): # sanity check, user is just in 'blokes' role self.session.refresh(barney) self.assertEqual(len(barney.roles), 1) self.assertEqual(barney.roles[0].name, "Blokes") # form can update user password self.assertTrue(auth.check_user_password(barney, "testpass")) form = view.make_model_form(model_instance=barney) form.validated = {"username": "barney", "set_password": "testpass2"} with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIs(user, barney) self.assertTrue(auth.check_user_password(barney, "testpass2")) # form can update user roles form = view.make_model_form(model_instance=barney) form.validated = {"username": "barney", "roles": {others.uuid}} with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIs(user, barney) self.assertEqual(len(user.roles), 1) self.assertEqual(user.roles[0].name, "Others") # person is auto-created self.assertIsNone(barney.person) form = view.make_model_form(model_instance=barney) form.validated = { "username": "barney", "first_name": "Barney", "last_name": "Rubble", } with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIsNotNone(barney.person) self.assertEqual(barney.person.first_name, "Barney") self.assertEqual(barney.person.last_name, "Rubble") self.assertEqual(barney.person.full_name, "Barney Rubble") # person is auto-removed self.assertIsNotNone(barney.person) form = view.make_model_form(model_instance=barney) form.validated = { "username": "barney", "first_name": "", "last_name": "", } with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIsNone(barney.person) # nb. re-attach the person barney.person = self.session.query(model.Person).one() # person name is updated self.assertEqual(barney.person.first_name, "Barney") self.assertEqual(barney.person.last_name, "Rubble") self.assertEqual(barney.person.full_name, "Barney Rubble") form = view.make_model_form(model_instance=barney) form.validated = { "username": "barney", "first_name": "Fred", "last_name": "Flintstone", } with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIsNotNone(barney.person) self.assertEqual(barney.person.first_name, "Fred") self.assertEqual(barney.person.last_name, "Flintstone") self.assertEqual(barney.person.full_name, "Fred Flintstone") with patch.object(view, "creating", new=True): # person is auto-created when making new user form = view.make_model_form() form.validated = { "username": "betty", "first_name": "Betty", "last_name": "Boop", } with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIsNotNone(user.person) self.assertEqual(user.person.first_name, "Betty") self.assertEqual(user.person.last_name, "Boop") self.assertEqual(user.person.full_name, "Betty Boop") # nb. keep ref to last user last_user = user # person is *not* auto-created if no name provided form = view.make_model_form() form.validated = {"username": "betty", "first_name": "", "last_name": ""} with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIsNone(user.person) self.assertIsNot(user, last_user) def test_update_roles(self): model = self.app.model auth = self.app.get_auth_handler() admin = auth.get_role_administrator(self.session) authed = auth.get_role_authenticated(self.session) anon = auth.get_role_anonymous(self.session) blokes = model.Role(name="Blokes") self.session.add(blokes) others = model.Role(name="Others") self.session.add(others) barney = model.User(username="barney") barney.roles.append(blokes) self.session.add(barney) self.session.commit() view = self.make_view() view.editing = True self.request.matchdict = {"uuid": barney.uuid} # no error if data is missing roles form = view.make_model_form(model_instance=barney) form.validated = {"username": "barneyx"} user = view.objectify(form) self.assertIs(user, barney) self.assertEqual(barney.username, "barneyx") # sanity check, user is just in 'blokes' role self.session.refresh(barney) self.assertEqual(len(barney.roles), 1) self.assertEqual(barney.roles[0].name, "Blokes") # let's test a bunch at once to ensure: # - user roles are updated # - authed / anon roles are not added # - admin role not added if current user is not root form = view.make_model_form(model_instance=barney) form.validated = { "username": "barney", "roles": {admin.uuid, authed.uuid, anon.uuid, others.uuid}, } with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIs(user, barney) self.assertEqual(len(user.roles), 1) self.assertEqual(user.roles[0].name, "Others") # let's test a bunch at once to ensure: # - user roles are updated # - admin role is added if current user is root self.request.is_root = True form = view.make_model_form(model_instance=barney) form.validated = { "username": "barney", "roles": {admin.uuid, blokes.uuid, others.uuid}, } with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIs(user, barney) self.assertEqual(len(user.roles), 3) role_uuids = set([role.uuid for role in user.roles]) self.assertEqual(role_uuids, {admin.uuid, blokes.uuid, others.uuid}) # admin role not removed if current user is not root self.request.is_root = False form = view.make_model_form(model_instance=barney) form.validated = {"username": "barney", "roles": {blokes.uuid, others.uuid}} with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIs(user, barney) self.assertEqual(len(user.roles), 3) # admin role is removed if current user is root self.request.is_root = True form = view.make_model_form(model_instance=barney) form.validated = {"username": "barney", "roles": {blokes.uuid, others.uuid}} with patch.object(view, "Session", return_value=self.session): user = view.objectify(form) self.assertIs(user, barney) self.assertEqual(len(user.roles), 2) def test_normalize_api_token(self): model = self.app.model auth = self.app.get_auth_handler() view = self.make_view() user = model.User(username="foo") self.session.add(user) token = auth.add_api_token(user, "test token") self.session.commit() normal = view.normalize_api_token(token) self.assertIn("uuid", normal) self.assertEqual(normal["uuid"], token.uuid.hex) self.assertIn("description", normal) self.assertEqual(normal["description"], "test token") self.assertIn("created", normal) def test_make_api_tokens_grid(self): model = self.app.model auth = self.app.get_auth_handler() view = self.make_view() user = model.User(username="foo") self.session.add(user) token1 = auth.add_api_token(user, "test1") token2 = auth.add_api_token(user, "test2") self.session.commit() # grid should have 2 records but no tools/actions grid = view.make_api_tokens_grid(user) self.assertIsInstance(grid, Grid) self.assertEqual(len(grid.data), 2) self.assertEqual(len(grid.tools), 0) self.assertEqual(len(grid.actions), 0) # create + delete allowed with patch.object(self.request, "is_root", new=True): grid = view.make_api_tokens_grid(user) self.assertEqual(len(grid.tools), 1) self.assertIn("create", grid.tools) self.assertEqual(len(grid.actions), 1) self.assertEqual(grid.actions[0].key, "delete") def test_add_api_token(self): model = self.app.model view = self.make_view() user = model.User(username="foo") self.session.add(user) self.session.commit() self.session.refresh(user) self.assertEqual(len(user.api_tokens), 0) with patch.object(view, "Session", return_value=self.session): with patch.object(self.request, "matchdict", new={"uuid": user.uuid}): with patch.object( self.request, "json_body", create=True, new={"description": "testing"}, ): result = view.add_api_token() self.assertEqual(len(user.api_tokens), 1) token = user.api_tokens[0] self.assertEqual(result["token_string"], token.token_string) self.assertEqual(result["description"], "testing") def test_delete_api_token(self): model = self.app.model auth = self.app.get_auth_handler() view = self.make_view() user = model.User(username="foo") self.session.add(user) token1 = auth.add_api_token(user, "test1") token2 = auth.add_api_token(user, "test2") self.session.commit() self.session.refresh(user) self.assertEqual(len(user.api_tokens), 2) with patch.object(view, "Session", return_value=self.session): with patch.object(self.request, "matchdict", new={"uuid": user.uuid}): # normal behavior with patch.object( self.request, "json_body", create=True, new={"uuid": token1.uuid.hex}, ): result = view.delete_api_token() self.assertEqual(result, {}) self.session.refresh(user) self.assertEqual(len(user.api_tokens), 1) token = user.api_tokens[0] self.assertIs(token, token2) # token for wrong user user2 = model.User(username="bar") self.session.add(user2) token3 = auth.add_api_token(user2, "test3") self.session.commit() with patch.object( self.request, "json_body", create=True, new={"uuid": token3.uuid.hex}, ): result = view.delete_api_token() self.assertEqual(result, {"error": "API token not found"}) # token not found with patch.object( self.request, "json_body", create=True, new={"uuid": self.app.make_true_uuid().hex}, ): result = view.delete_api_token() self.assertEqual(result, {"error": "API token not found"}) # TODO: this test seems to work fine on its own, but not in conjunction # with the next class below. will have to sort this out before adding # anymore functional tests probably. but it can wait for the moment. # class TestListUsers(FunctionalTestCase): # def setUp(self): # super().setUp() # model = self.app.model # auth = self.app.get_auth_handler() # # add 'fred' user # self.fred = model.User(username="fred") # auth.set_user_password(self.fred, "fredpass") # self.session.add(self.fred) # # add 'managers' role # self.managers = model.Role(name="Managers") # self.fred.roles.append(self.managers) # self.session.add(self.managers) # self.session.commit() # def test_index(self): # model = self.app.model # auth = self.app.get_auth_handler() # testapp = self.make_webtest() # csrf = self.get_csrf_token(testapp) # # cannot list users if not logged in # res = testapp.get("/users/") # self.assertEqual(res.status_code, 200) # self.assertIn("Access Denied", res.text) # self.assertIn("Login", res.text) # self.assertNotIn("fred", res.text) # # so we login # res = testapp.post( # "/login", # params={ # "_csrf": csrf, # "username": "fred", # "password": "fredpass", # }, # ) # self.assertEqual(res.status_code, 302) # self.assertEqual(res.location, "http://localhost/") # res = res.follow() # self.assertEqual(res.status_code, 200) # self.assertNotIn("Login", res.text) # self.assertIn("fred", res.text) # perms = self.session.query(model.Permission).all() # self.assertEqual(len(perms), 0) # self.assertFalse(auth.has_permission(self.session, self.fred, "users.list")) # # but we still cannot list users, b/c no perm # res = testapp.get("/users/") # self.assertEqual(res.status_code, 200) # self.assertIn("Access Denied", res.text) # self.assertNotIn("Login", res.text) # self.assertIn("fred", res.text) # # so we grant the perm # auth.grant_permission(self.managers, "users.list") # self.session.commit() # perms = self.session.query(model.Permission).all() # # now we can list users # res = testapp.get("/users/") # self.assertEqual(res.status_code, 200) # self.assertNotIn("Access Denied", res.text) # self.assertNotIn("Login", res.text) # self.assertIn("fred", res.text) # testapp.get("/logout") class TestCreateUser(FunctionalTestCase): def setUp(self): super().setUp() model = self.app.model auth = self.app.get_auth_handler() # add 'fred' user self.fred = model.User(username="fred") auth.set_user_password(self.fred, "fredpass") self.session.add(self.fred) # add 'managers' role self.managers = model.Role(name="Managers") self.fred.roles.append(self.managers) self.session.add(self.managers) self.session.commit() def test_create(self): model = self.app.model auth = self.app.get_auth_handler() testapp = self.make_webtest() csrf = self.get_csrf_token(testapp) # cannot create user if not logged in res = testapp.get("/users/new") self.assertEqual(res.status_code, 200) self.assertIn("Access Denied", res.text) self.assertIn("Login", res.text) self.assertNotIn("fred", res.text) # so we login res = testapp.post( "/login", params={ "_csrf": csrf, "username": "fred", "password": "fredpass", }, ) self.assertEqual(res.status_code, 302) self.assertEqual(res.location, "http://localhost/") res = res.follow() self.assertEqual(res.status_code, 200) self.assertNotIn("Login", res.text) self.assertIn("fred", res.text) # but we still cannot create user, b/c no perm res = testapp.get("/users/new") self.assertEqual(res.status_code, 200) self.assertIn("Access Denied", res.text) self.assertNotIn("Login", res.text) self.assertIn("fred", res.text) # so we grant the perm; then we can create user auth.grant_permission(self.managers, "users.list") auth.grant_permission(self.managers, "users.create") auth.grant_permission(self.managers, "users.view") self.session.commit() self.assertTrue(auth.has_permission(self.session, self.fred, "users.create")) # first get the form res = testapp.get("/users/new") self.assertEqual(res.status_code, 200) self.assertNotIn("Access Denied", res.text) self.assertNotIn("Login", res.text) self.assertIn("fred", res.text) self.assertIn("Username", res.text) # then post the form; user should be created res = testapp.post( "/users/new", [ ("_csrf", csrf), ("username", "barney"), ("__start__", "set_password:mapping"), ("set_password", "barneypass"), ("set_password-confirm", "barneypass"), ("__end__", "set_password:mapping"), ("first_name", "Barney"), ("last_name", "Rubble"), ("__start__", "roles:sequence"), ("checkbox", str(self.managers.uuid)), ("__end__", "roles:sequence"), ], ) barney = self.session.query(model.User).filter_by(username="barney").first() self.assertTrue(barney) self.assertEqual(res.status_code, 302) self.assertEqual(res.location, f"http://localhost/users/{barney.uuid}") res = res.follow() self.assertEqual(res.status_code, 200) self.assertNotIn("Login", res.text) self.assertIn("fred", res.text) self.assertIn("Barney Rubble", res.text) testapp.get("/logout")