diff --git a/awx/main/tests/factories/README.md b/awx/main/tests/factories/README.md new file mode 100644 index 0000000000..c451c02598 --- /dev/null +++ b/awx/main/tests/factories/README.md @@ -0,0 +1,65 @@ +factories +========= + +This is a module for defining stand-alone factories and fixtures. Ideally a fixture will implement a single item. +DO NOT decorate fixtures in this module with the @pytest.fixture. These fixtures are to be combined +with fixture factories and composition using the `conftest.py` convention. Those composed fixtures +will be decorated for usage and discovery. + +Use the fixtures directly in factory methods to build up the desired set of components and relationships. +Each fixture should create exactly one object and should support the option for that object to be persisted +or not. + +A factory should create at a minimum a single object for that factory type. The creation of any +associated objects should be explicit. For example, the `create_organization` factory when given only +a `name` parameter will create an Organization but it will not implicitly create any other objects. + +teams +----- + +There is some special handling for users when adding teams. There is a short hand that allows you to +assign a user to the member\_role of a team using the string notation of `team_name:user_name`. There is +no shortcut for adding a user to the admin\_role of a team. See the roles section for more information +about how to do that. + +roles +----- + +The roles helper allows you pass in roles to a factory. These roles assignments will happen after +the objects are created. Using the roles parameter required that persisted=True (default). + +You can use a string notation of `object_name.role_name:user` OR `object_name.role_name:object_name.child_role` + + obj.parent_role:user # This will make the user a member of parent_role + obj1.role:obj2.role # This will make obj2 a child role of obj1 + + team1.admin_role:joe + team1.admin_role:project1.admin_role + +examples +-------- + + objects = create_organization('test-org') + assert objects.organization.name == 'test-org' + + objects = create_organization('test-org', projects=['test-proj']) + assert objects.projects.test-proj.organization == objects.organization + + objects = create_organization('test-org', persisted=False) + assert not objects.organization.pk + +patterns +-------- + +`mk` functions are single object fixtures. They should create only a single object with the minimum deps. +They should also accept a `persited` flag, if they must be persisted to work, they raise an error if persisted=False + +`generate` and `apply` functions are helpers that build up the various parts of a `create` functions objects. These +should be useful for more than one create function to use and should explicitly accept all of the values needed +to execute. These functions should also be robust and have very speciifc error reporting about constraints and/or +bad values. + +`create` functions compose many of the `mk` and `generate` functions to make different object +factories. These functions when giving the minimum set of arguments should only produce a +single artifact (or the minimum needed for that object). These should be wrapped by discoverable +fixtures in various conftest.py files. diff --git a/awx/main/tests/factories/__init__.py b/awx/main/tests/factories/__init__.py new file mode 100644 index 0000000000..8c8eb326d1 --- /dev/null +++ b/awx/main/tests/factories/__init__.py @@ -0,0 +1,16 @@ +from .tower import ( + create_organization, + create_job_template, + create_notification_template, +) + +from .exc import ( + NotUnique, +) + +__all__ = [ + 'create_organization', + 'create_job_template', + 'create_notification_template', + 'NotUnique', +] diff --git a/awx/main/tests/factories/exc.py b/awx/main/tests/factories/exc.py new file mode 100644 index 0000000000..aa51de5bd3 --- /dev/null +++ b/awx/main/tests/factories/exc.py @@ -0,0 +1,5 @@ +class NotUnique(Exception): + def __init__(self, name, objects): + msg = '{} is not a unique key, found {}={}'.format(name, name, objects[name]) + super(Exception, self).__init__(msg) + diff --git a/awx/main/tests/factories/fixtures.py b/awx/main/tests/factories/fixtures.py new file mode 100644 index 0000000000..91ead460db --- /dev/null +++ b/awx/main/tests/factories/fixtures.py @@ -0,0 +1,124 @@ +from django.contrib.auth.models import User + +from awx.main.models import ( + Organization, + Project, + Team, + Instance, + JobTemplate, + NotificationTemplate, + Credential, + Inventory, + Label, +) + +# mk methods should create only a single object of a single type. +# they should also have the option of being persisted or not. +# if the object must be persisted an error should be raised when +# persisted=False +# + +def mk_instance(persisted=True): + if not persisted: + raise RuntimeError('creating an Instance requires persisted=True') + from django.conf import settings + return Instance.objects.get_or_create(uuid=settings.SYSTEM_UUID, primary=True, hostname="instance.example.org") + + +def mk_organization(name, description=None, persisted=True): + description = description or '{}-description'.format(name) + org = Organization(name=name, description=description) + if persisted: + mk_instance(persisted) + org.save() + return org + + +def mk_label(name, organization=None, description=None, persisted=True): + description = description or '{}-description'.format(name) + label = Label(name=name, description=description) + if organization is not None: + label.organization = organization + if persisted: + label.save() + return label + + +def mk_team(name, organization=None, persisted=True): + team = Team(name=name) + if organization is not None: + team.organization = organization + if persisted: + mk_instance(persisted) + team.save() + return team + + +def mk_user(name, is_superuser=False, organization=None, team=None, persisted=True): + user = User(username=name, is_superuser=is_superuser) + if persisted: + user.save() + if organization is not None: + organization.member_role.members.add(user) + if team is not None: + team.member_role.members.add(user) + return user + + +def mk_project(name, organization=None, description=None, persisted=True): + description = description or '{}-description'.format(name) + project = Project(name=name, description=description) + if organization is not None: + project.organization = organization + if persisted: + project.save() + return project + + +def mk_credential(name, cloud=False, kind='ssh', persisted=True): + cred = Credential(name=name, cloud=cloud, kind=kind) + if persisted: + cred.save() + return cred + + +def mk_notification_template(name, notification_type='webhook', configuration=None, organization=None, persisted=True): + nt = NotificationTemplate(name=name) + nt.notification_type = notification_type + nt.notification_configuration = configuration or dict(url="http://localhost", headers={"Test": "Header"}) + + if organization is not None: + nt.organization = organization + if persisted: + nt.save() + return nt + + +def mk_inventory(name, organization=None, persisted=True): + inv = Inventory(name=name) + if organization is not None: + inv.organization = organization + if persisted: + inv.save() + return inv + + +def mk_job_template(name, job_type='run', + organization=None, inventory=None, + credential=None, persisted=True, + project=None): + jt = JobTemplate(name=name, job_type=job_type) + + jt.inventory = inventory + if jt.inventory is None: + jt.ask_inventory_on_launch = True + + jt.credential = credential + if jt.credential is None: + jt.ask_credential_on_launch = True + + jt.project = project + + if persisted: + jt.save() + return jt diff --git a/awx/main/tests/factories/tower.py b/awx/main/tests/factories/tower.py new file mode 100644 index 0000000000..2a9d493efe --- /dev/null +++ b/awx/main/tests/factories/tower.py @@ -0,0 +1,280 @@ +from collections import namedtuple + +from django.contrib.auth.models import User + +from awx.main.models import ( + Organization, + Project, + Team, + NotificationTemplate, + Credential, + Inventory, + Label, +) + +from .fixtures import ( + mk_organization, + mk_team, + mk_user, + mk_job_template, + mk_credential, + mk_inventory, + mk_project, + mk_label, + mk_notification_template, +) + +from .exc import NotUnique + + +def generate_role_objects(objects): + '''generate_role_objects assembles a dictionary of all possible objects by name. + It will raise an exception if any of the objects share a name due to the fact that + it is to be used with apply_roles, which expects unique object names. + + roles share a common name e.g. admin_role, member_role. This ensures that the + roles short hand used for mapping Roles and Users in apply_roles will function as desired. + ''' + combined_objects = {} + for o in objects: + if type(o) is dict: + for k,v in o.iteritems(): + if combined_objects.get(k) is not None: + raise NotUnique(k, combined_objects) + combined_objects[k] = v + elif hasattr(o, 'name'): + if combined_objects.get(o.name) is not None: + raise NotUnique(o.name, combined_objects) + combined_objects[o.name] = o + else: + if o is not None: + raise RuntimeError('expected a list of dict or list of list, got a type {}'.format(type(o))) + return combined_objects + +def apply_roles(roles, objects, persisted): + '''apply_roles evaluates a list of Role relationships represented as strings. + The format of this string is 'role:[user|role]'. When a user is provided, they will be + made a member of the role on the LHS. When a role is provided that role will be added to + the children of the role on the LHS. + + This function assumes that objects is a dictionary that contains a unique set of key to value + mappings for all possible "Role objects". See the example below: + + Mapping Users + ------------- + roles = ['org1.admin_role:user1', 'team1.admin_role:user1'] + objects = {'org1': Organization, 'team1': Team, 'user1': User] + + Mapping Roles + ------------- + roles = ['org1.admin_role:team1.admin_role'] + objects = {'org1': Organization, 'team1': Team} + + Invalid Mapping + --------------- + roles = ['org1.admin_role:team1.admin_role'] + objects = {'org1': Organization', 'user1': User} # Exception, no team1 entry + ''' + if roles is None: + return None + + if not persisted: + raise RuntimeError('roles can not be used when persisted=False') + + for role in roles: + obj_role, sep, member_role = role.partition(':') + if not member_role: + raise RuntimeError('you must provide an assignment role, got None') + + obj_str, o_role_str = obj_role.split('.') + member_str, m_sep, m_role_str = member_role.partition('.') + + obj = objects[obj_str] + obj_role = getattr(obj, o_role_str) + + member = objects[member_str] + if m_role_str: + if hasattr(member, m_role_str): + member_role = getattr(member, m_role_str) + obj_role.children.add(member_role) + else: + raise RuntimeError('unable to find {} role for {}'.format(m_role_str, member_str)) + else: + if type(member) is User: + obj_role.members.add(member) + else: + raise RuntimeError('unable to add non-user {} for members list of {}'.format(member_str, obj_str)) + +def generate_users(organization, teams, superuser, persisted, **kwargs): + '''generate_users evaluates a mixed list of User objects and strings. + If a string is encountered a user with that username is created and added to the lookup dict. + If a User object is encountered the User.username is used as a key for the lookup dict. + + A short hand for assigning a user to a team is available in the following format: "team_name:username". + If a string in that format is encounted an attempt to lookup the team by the key team_name from the teams + argumnent is made, a KeyError will be thrown if the team does not exist in the dict. The teams argument should + be a dict of {Team.name:Team} + ''' + users = {} + key = 'superusers' if superuser else 'users' + if key in kwargs and kwargs.get(key) is not None: + for u in kwargs[key]: + if type(u) is User: + users[u.username] = u + else: + p1, sep, p2 = u.partition(':') + if p2: + t = teams[p1] + users[p2] = mk_user(p2, organization=organization, team=t, is_superuser=superuser, persisted=persisted) + else: + users[p1] = mk_user(p1, organization=organization, team=None, is_superuser=superuser, persisted=persisted) + return users + +def generate_teams(organization, persisted, **kwargs): + '''generate_teams evalutes a mixed list of Team objects and strings. + If a string is encountered a team with that string name is created and added to the lookup dict. + If a Team object is encounted the Team.name is used as a key for the lookup dict. + ''' + teams = {} + if 'teams' in kwargs and kwargs.get('teams') is not None: + for t in kwargs['teams']: + if type(t) is Team: + teams[t.name] = t + else: + teams[t] = mk_team(t, organization=organization, persisted=persisted) + return teams + + +class _Mapped(object): + '''_Mapped is a helper class that replaces spaces and dashes + in the name of an object and assigns the object as an attribute + + input: {'my org': Organization} + output: instance.my_org = Organization + ''' + def __init__(self, d): + self.d = d + for k,v in d.items(): + k = k.replace(' ', '_') + k = k.replace('-', '_') + + setattr(self, k.replace(' ','_'), v) + + def all(self): + return self.d.values() + +# create methods are intended to be called directly as needed +# or encapsulated by specific factory fixtures in a conftest +# + +def create_job_template(name, **kwargs): + Objects = namedtuple("Objects", "job_template, inventory, project, credential, job_type") + + org = None + proj = None + inv = None + cred = None + job_type = kwargs.get('job_type', 'run') + persisted = kwargs.get('persisted', True) + + if 'organization' in kwargs: + org = kwargs['organization'] + if type(org) is not Organization: + org = mk_organization(org, '%s-desc'.format(org), persisted=persisted) + + if 'credential' in kwargs: + cred = kwargs['credential'] + if type(cred) is not Credential: + cred = mk_credential(cred, persisted=persisted) + + if 'project' in kwargs: + proj = kwargs['project'] + if type(proj) is not Project: + proj = mk_project(proj, organization=org, persisted=persisted) + + if 'inventory' in kwargs: + inv = kwargs['inventory'] + if type(inv) is not Inventory: + inv = mk_inventory(inv, organization=org, persisted=persisted) + + jt = mk_job_template(name, project=proj, + inventory=inv, credential=cred, + job_type=job_type, persisted=persisted) + + role_objects = generate_role_objects([org, proj, inv, cred]) + apply_roles(kwargs.get('roles'), role_objects, persisted) + + return Objects(job_template=jt, + project=proj, + inventory=inv, + credential=cred, + job_type=job_type) + +def create_organization(name, **kwargs): + Objects = namedtuple("Objects", "organization,teams,users,superusers,projects,labels,notification_templates") + + projects = {} + labels = {} + notification_templates = {} + persisted = kwargs.get('persisted', True) + + org = mk_organization(name, '%s-desc'.format(name), persisted=persisted) + + if 'projects' in kwargs: + for p in kwargs['projects']: + if type(p) is Project: + projects[p.name] = p + else: + projects[p] = mk_project(p, organization=org, persisted=persisted) + + teams = generate_teams(org, persisted, teams=kwargs.get('teams')) + superusers = generate_users(org, teams, True, persisted, superusers=kwargs.get('superusers')) + users = generate_users(org, teams, False, persisted, users=kwargs.get('users')) + + if 'labels' in kwargs: + for l in kwargs['labels']: + if type(l) is Label: + labels[l.name] = l + else: + labels[l] = mk_label(l, organization=org, persisted=persisted) + + if 'notification_templates' in kwargs: + for nt in kwargs['notification_templates']: + if type(nt) is NotificationTemplate: + notification_templates[nt.name] = nt + else: + notification_templates[nt] = mk_notification_template(nt, organization=org, persisted=persisted) + + role_objects = generate_role_objects([org, superusers, users, teams, projects, labels, notification_templates]) + apply_roles(kwargs.get('roles'), role_objects, persisted) + return Objects(organization=org, + superusers=_Mapped(superusers), + users=_Mapped(users), + teams=_Mapped(teams), + projects=_Mapped(projects), + labels=_Mapped(labels), + notification_templates=_Mapped(notification_templates)) + +def create_notification_template(name, **kwargs): + Objects = namedtuple("Objects", "notification_template,organization,users,superusers,teams") + + organization = None + persisted = kwargs.get('persisted', True) + + if 'organization' in kwargs: + org = kwargs['organization'] + organization = mk_organization(org, '{}-desc'.format(org), persisted=persisted) + + notification_template = mk_notification_template(name, organization=organization, persisted=persisted) + + teams = generate_teams(organization, persisted, teams=kwargs.get('teams')) + superusers = generate_users(organization, teams, True, persisted, superusers=kwargs.get('superusers')) + users = generate_users(organization, teams, False, persisted, users=kwargs.get('users')) + + role_objects = generate_role_objects([organization, notification_template]) + apply_roles(kwargs.get('roles'), role_objects, persisted) + return Objects(notification_template=notification_template, + organization=organization, + users=_Mapped(users), + superusers=_Mapped(superusers), + teams=teams) diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 59d34a2832..68bd1257fd 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -38,6 +38,12 @@ from awx.main.models.organization import ( from awx.main.models.notifications import NotificationTemplate +from awx.main.tests.factories import ( + create_organization, + create_job_template, + create_notification_template, +) + ''' Disable all django model signals. ''' @@ -147,18 +153,6 @@ def instance(settings): def organization(instance): return Organization.objects.create(name="test-org", description="test-org-desc") -@pytest.fixture -def organization_factory(instance): - def factory(name): - try: - org = Organization.objects.get(name=name) - except Organization.DoesNotExist: - org = Organization.objects.create(name=name, - description="description for " + name, - ) - return org - return factory - @pytest.fixture def credential(): return Credential.objects.create(kind='aws', name='test-cred') @@ -282,21 +276,6 @@ def permissions(): 'update':False, 'delete':False, 'scm_update':False, 'execute':False, 'use':True,}, } -@pytest.fixture -def notification_template_factory(organization): - def n(name="test-notification_template"): - try: - notification_template = NotificationTemplate.objects.get(name=name) - except NotificationTemplate.DoesNotExist: - notification_template = NotificationTemplate(name=name, - organization=organization, - notification_type="webhook", - notification_configuration=dict(url="http://localhost", - headers={"Test": "Header"})) - notification_template.save() - return notification_template - return n - @pytest.fixture def post(): def rf(url, data, user=None, middleware=None, **kwargs): @@ -474,3 +453,16 @@ def job_template_labels(organization, job_template): job_template.labels.create(name="label-2", organization=organization) return job_template + +@pytest.fixture +def job_template_factory(): + return create_job_template + +@pytest.fixture +def organization_factory(): + return create_organization + +@pytest.fixture +def notification_template_factory(): + return create_notification_template + diff --git a/awx/main/tests/functional/test_fixture_factories.py b/awx/main/tests/functional/test_fixture_factories.py new file mode 100644 index 0000000000..286d375a1a --- /dev/null +++ b/awx/main/tests/functional/test_fixture_factories.py @@ -0,0 +1,85 @@ +import pytest + +from awx.main.tests.factories import NotUnique + +def test_roles_exc_not_persisted(organization_factory): + with pytest.raises(RuntimeError) as exc: + organization_factory('test-org', roles=['test-org.admin_role:user1'], persisted=False) + assert 'persisted=False' in str(exc.value) + + +@pytest.mark.django_db +def test_roles_exc_bad_object(organization_factory): + with pytest.raises(KeyError): + organization_factory('test-org', roles=['test-project.admin_role:user']) + + +@pytest.mark.django_db +def test_roles_exc_not_unique(organization_factory): + with pytest.raises(NotUnique) as exc: + organization_factory('test-org', projects=['foo'], teams=['foo'], roles=['foo.admin_role:user']) + assert 'not a unique key' in str(exc.value) + + +@pytest.mark.django_db +def test_roles_exc_not_assignment(organization_factory): + with pytest.raises(RuntimeError) as exc: + organization_factory('test-org', projects=['foo'], roles=['foo.admin_role']) + assert 'provide an assignment' in str(exc.value) + + +@pytest.mark.django_db +def test_roles_exc_not_found(organization_factory): + with pytest.raises(RuntimeError) as exc: + organization_factory('test-org', users=['user'], projects=['foo'], roles=['foo.admin_role:user.bad_role']) + assert 'unable to find' in str(exc.value) + + +@pytest.mark.django_db +def test_roles_exc_not_user(organization_factory): + with pytest.raises(RuntimeError) as exc: + organization_factory('test-org', projects=['foo'], roles=['foo.admin_role:foo']) + assert 'unable to add non-user' in str(exc.value) + + +@pytest.mark.django_db +def test_org_factory_roles(organization_factory): + objects = organization_factory('org_roles_test', + teams=['team1', 'team2'], + users=['team1:foo', 'bar'], + projects=['baz', 'bang'], + roles=['team2.member_role:foo', + 'team1.admin_role:bar', + 'team1.admin_role:team2.admin_role', + 'baz.admin_role:foo']) + + assert objects.users.bar in objects.teams.team2.admin_role + assert objects.users.foo in objects.projects.baz.admin_role + assert objects.users.foo in objects.teams.team1.member_role + assert objects.teams.team2.admin_role in objects.teams.team1.admin_role.children.all() + + +@pytest.mark.django_db +def test_org_factory(organization_factory): + objects = organization_factory('organization1', + teams=['team1'], + superusers=['superuser'], + users=['admin', 'alice', 'team1:bob'], + projects=['proj1']) + assert hasattr(objects.users, 'admin') + assert hasattr(objects.users, 'alice') + assert hasattr(objects.superusers, 'superuser') + assert objects.users.bob in objects.teams.team1.member_role.members.all() + assert objects.projects.proj1.organization == objects.organization + + +@pytest.mark.django_db +def test_job_template_factory(job_template_factory): + jt_objects = job_template_factory('testJT', organization='org1', + project='proj1', inventory='inventory1', + credential='cred1') + assert jt_objects.job_template.name == 'testJT' + assert jt_objects.project.name == 'proj1' + assert jt_objects.inventory.name == 'inventory1' + assert jt_objects.credential.name == 'cred1' + assert jt_objects.inventory.organization.name == 'org1' diff --git a/awx/main/tests/functional/test_projects.py b/awx/main/tests/functional/test_projects.py index d8bcd5d151..4c32d1dd69 100644 --- a/awx/main/tests/functional/test_projects.py +++ b/awx/main/tests/functional/test_projects.py @@ -1,7 +1,6 @@ import mock # noqa import pytest -from django.db import transaction from django.core.urlresolvers import reverse from awx.main.models import Project @@ -9,62 +8,55 @@ from awx.main.models import Project # # Project listing and visibility tests # +@pytest.fixture +def team_project_list(organization_factory): + objects = organization_factory('org-test', + superusers=['admin'], + users=['team1:alice', 'team2:bob'], + teams=['team1', 'team2'], + projects=['pteam1', 'pteam2', 'pshared'], + roles=['team1.member_role:pteam1.admin_role', + 'team2.member_role:pteam2.admin_role', + 'team1.member_role:pshared.admin_role', + 'team2.member_role:pshared.admin_role']) + return objects + @pytest.mark.django_db -def test_user_project_list(get, project_factory, organization, admin, alice, bob): +def test_user_project_list(get, organization_factory): 'List of projects a user has access to, filtered by projects you can also see' - organization.member_role.members.add(alice, bob) + objects = organization_factory('org1', + projects=['alice project', 'bob project', 'shared project'], + superusers=['admin'], + users=['alice', 'bob'], + roles=['alice project.admin_role:alice', + 'bob project.admin_role:bob', + 'shared project.admin_role:bob', + 'shared project.admin_role:alice']) - alice_project = project_factory('alice project') - alice_project.admin_role.members.add(alice) - - bob_project = project_factory('bob project') - bob_project.admin_role.members.add(bob) - - shared_project = project_factory('shared project') - shared_project.admin_role.members.add(alice) - shared_project.admin_role.members.add(bob) - - # admins can see all projects - assert get(reverse('api:user_projects_list', args=(admin.pk,)), admin).data['count'] == 3 + assert get(reverse('api:user_projects_list', args=(objects.superusers.admin.pk,)), objects.superusers.admin).data['count'] == 3 # admins can see everyones projects - assert get(reverse('api:user_projects_list', args=(alice.pk,)), admin).data['count'] == 2 - assert get(reverse('api:user_projects_list', args=(bob.pk,)), admin).data['count'] == 2 + assert get(reverse('api:user_projects_list', args=(objects.users.alice.pk,)), objects.superusers.admin).data['count'] == 2 + assert get(reverse('api:user_projects_list', args=(objects.users.bob.pk,)), objects.superusers.admin).data['count'] == 2 # users can see their own projects - assert get(reverse('api:user_projects_list', args=(alice.pk,)), alice).data['count'] == 2 + assert get(reverse('api:user_projects_list', args=(objects.users.alice.pk,)), objects.users.alice).data['count'] == 2 # alice should only be able to see the shared project when looking at bobs projects - assert get(reverse('api:user_projects_list', args=(bob.pk,)), alice).data['count'] == 1 + assert get(reverse('api:user_projects_list', args=(objects.users.bob.pk,)), objects.users.alice).data['count'] == 1 # alice should see all projects they can see when viewing an admin - assert get(reverse('api:user_projects_list', args=(admin.pk,)), alice).data['count'] == 2 + assert get(reverse('api:user_projects_list', args=(objects.superusers.admin.pk,)), objects.users.alice).data['count'] == 2 -def setup_test_team_project_list(project_factory, team_factory, admin, alice, bob): - team1 = team_factory('team1') - team2 = team_factory('team2') - - team1_project = project_factory('team1 project') - team1_project.admin_role.parents.add(team1.member_role) - - team2_project = project_factory('team2 project') - team2_project.admin_role.parents.add(team2.member_role) - - shared_project = project_factory('shared project') - shared_project.admin_role.parents.add(team1.member_role) - shared_project.admin_role.parents.add(team2.member_role) - - team1.member_role.members.add(alice) - team2.member_role.members.add(bob) - return team1, team2 - @pytest.mark.django_db -def test_team_project_list(get, project_factory, team_factory, admin, alice, bob): - 'List of projects a team has access to, filtered by projects you can also see' - team1, team2 = setup_test_team_project_list(project_factory, team_factory, admin, alice, bob) +def test_team_project_list(get, team_project_list): + objects = team_project_list + + team1, team2 = objects.teams.team1, objects.teams.team2 + alice, bob, admin = objects.users.alice, objects.users.bob, objects.superusers.admin # admins can see all projects on a team assert get(reverse('api:team_projects_list', args=(team1.pk,)), admin).data['count'] == 2 @@ -78,12 +70,6 @@ def test_team_project_list(get, project_factory, team_factory, admin, alice, bob assert get(reverse('api:team_projects_list', args=(team2.pk,)), alice).data['count'] == 1 team2.read_role.members.remove(alice) - # Test user endpoints first, very similar tests to test_user_project_list - # but permissions are being derived from team membership instead. - with transaction.atomic(): - res = get(reverse('api:user_projects_list', args=(bob.pk,)), alice) - assert res.status_code == 403 - # admins can see all projects assert get(reverse('api:user_projects_list', args=(admin.pk,)), admin).data['count'] == 3 @@ -98,17 +84,11 @@ def test_team_project_list(get, project_factory, team_factory, admin, alice, bob assert get(reverse('api:user_projects_list', args=(admin.pk,)), alice).data['count'] == 2 @pytest.mark.django_db -def test_team_project_list_fail1(get, project_factory, team_factory, admin, alice, bob): - # alice should not be able to see team2 projects because she doesn't have access to team2 - team1, team2 = setup_test_team_project_list(project_factory, team_factory, admin, alice, bob) - res = get(reverse('api:team_projects_list', args=(team2.pk,)), alice) +def test_team_project_list_fail1(get, team_project_list): + objects = team_project_list + res = get(reverse('api:team_projects_list', args=(objects.teams.team2.pk,)), objects.users.alice) assert res.status_code == 403 -@pytest.mark.django_db -def test_team_project_list_fail2(get, project_factory, team_factory, admin, alice, bob): - team1, team2 = setup_test_team_project_list(project_factory, team_factory, admin, alice, bob) - # alice should not be able to see bob - @pytest.mark.parametrize("u,expected_status_code", [ ('rando', 403), ('org_member', 403), diff --git a/awx/main/tests/functional/test_rbac_label.py b/awx/main/tests/functional/test_rbac_label.py index ec3c83f314..e425d50908 100644 --- a/awx/main/tests/functional/test_rbac_label.py +++ b/awx/main/tests/functional/test_rbac_label.py @@ -31,20 +31,22 @@ def test_label_access_superuser(label, user): assert access.can_delete(label) @pytest.mark.django_db -def test_label_access_admin(label, user, organization_factory): +def test_label_access_admin(organization_factory): '''can_change because I am an admin of that org''' - a = user('admin', False) - org_no_members = organization_factory("no_members") - org_members = organization_factory("has_members") + no_members = organization_factory("no_members") + members = organization_factory("has_members", + users=['admin'], + labels=['test']) - label.organization.admin_role.members.add(a) - org_members.admin_role.members.add(a) + label = members.labels.test + admin = members.users.admin + members.organization.admin_role.members.add(admin) - access = LabelAccess(user('admin', False)) - assert not access.can_change(label, {'organization': org_no_members.id}) + access = LabelAccess(admin) + assert not access.can_change(label, {'organization': no_members.organization.id}) assert access.can_read(label) assert access.can_change(label, None) - assert access.can_change(label, {'organization': org_members.id}) + assert access.can_change(label, {'organization': members.organization.id}) assert access.can_delete(label) @pytest.mark.django_db diff --git a/awx/main/tests/functional/test_rbac_notifications.py b/awx/main/tests/functional/test_rbac_notifications.py index 467ae8038a..9cabec6fbf 100644 --- a/awx/main/tests/functional/test_rbac_notifications.py +++ b/awx/main/tests/functional/test_rbac_notifications.py @@ -25,35 +25,44 @@ def test_notification_template_get_queryset_orgadmin(notification_template, user assert access.get_queryset().count() == 1 @pytest.mark.django_db -def test_notification_template_access_superuser(notification_template, user, notification_template_factory): - access = NotificationTemplateAccess(user('admin', True)) - assert access.can_read(notification_template) - assert access.can_change(notification_template, None) - assert access.can_delete(notification_template) - nf = notification_template_factory("test-orphaned") +def test_notification_template_access_superuser(notification_template_factory): + nf_objects = notification_template_factory('test-orphaned', organization='test', superusers=['admin']) + admin = nf_objects.superusers.admin + nf = nf_objects.notification_template + + access = NotificationTemplateAccess(admin) + assert access.can_read(nf) + assert access.can_change(nf, None) + assert access.can_delete(nf) + nf.organization = None nf.save() + assert access.can_read(nf) assert access.can_change(nf, None) assert access.can_delete(nf) @pytest.mark.django_db -def test_notification_template_access_admin(notification_template, user, organization_factory, notification_template_factory): - adm = user('admin', False) - other_org = organization_factory('other') - present_org = organization_factory('present') - notification_template.organization.admin_role.members.add(adm) - present_org.admin_role.members.add(adm) +def test_notification_template_access_admin(organization_factory, notification_template_factory): + other_objects = organization_factory('other') + present_objects = organization_factory('present', + users=['admin'], + notification_templates=['test-notification'], + roles=['present.admin_role:admin']) - access = NotificationTemplateAccess(user('admin', False)) + notification_template = present_objects.notification_templates.test_notification + other_org = other_objects.organization + present_org = present_objects.organization + admin = present_objects.users.admin + + access = NotificationTemplateAccess(admin) assert not access.can_change(notification_template, {'organization': other_org.id}) assert access.can_read(notification_template) assert access.can_change(notification_template, None) assert access.can_change(notification_template, {'organization': present_org.id}) assert access.can_delete(notification_template) + nf = notification_template_factory("test-orphaned") - nf.organization = None - nf.save() assert not access.can_read(nf) assert not access.can_change(nf, None) assert not access.can_delete(nf)