mirror of
https://github.com/ansible/awx.git
synced 2026-08-04 20:10:01 -02:30
* AAP-83773 — Use unfiltered count for activity stream pagination Forward-port of tower#7606 (stable-2.6). The RBAC-filtered COUNT(*) on activity_stream takes ~36 min per call on large tables (713K rows) due to the pk__in subquery shape introduced by the AAP-81860 LEFT JOIN fix. Replace with an unfiltered table count for pagination -- an approximate over-count is harmless for UI page navigation while the actual page results remain RBAC-filtered. * AAP-83773 — Add unit tests for ActivityStreamPagination Cover ActivityStreamPaginator and ActivityStreamPagination to satisfy SonarCloud coverage gate on new lines in awx/api/pagination.py. * AAP-83773 — Save/restore django_paginator_class in base Pagination Address review feedback from lallen92: use a save/restore pattern in Pagination.paginate_queryset() so subclasses only need to set the class attribute. Remove the now-redundant paginate_queryset override from ActivityStreamPagination. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Liam Allen <lallen@redhat.com>
180 lines
7.4 KiB
Python
180 lines
7.4 KiB
Python
import pytest
|
|
|
|
from awx.api.versioning import reverse
|
|
from awx.main.models.activity_stream import ActivityStream
|
|
from awx.main.access import ActivityStreamAccess
|
|
from awx.conf.models import Setting
|
|
|
|
|
|
@pytest.fixture
|
|
def activity_stream_entry(organization, org_admin):
|
|
return ActivityStream.objects.filter(organization__pk=organization.pk, user=org_admin, operation='associate').first()
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_get_activity_stream_list(monkeypatch, organization, get, user, settings):
|
|
settings.ACTIVITY_STREAM_ENABLED = True
|
|
url = reverse('api:activity_stream_list')
|
|
response = get(url, user('admin', True))
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_basic_fields(monkeypatch, organization, get, user, settings):
|
|
settings.ACTIVITY_STREAM_ENABLED = True
|
|
u = user('admin', True)
|
|
activity_stream = ActivityStream.objects.filter(organization=organization).latest('pk')
|
|
activity_stream.actor = u
|
|
activity_stream.save()
|
|
|
|
aspk = activity_stream.pk
|
|
url = reverse('api:activity_stream_detail', kwargs={'pk': aspk})
|
|
response = get(url, user('admin', True))
|
|
|
|
assert response.status_code == 200
|
|
assert 'related' in response.data
|
|
assert 'organization' in response.data['related']
|
|
assert 'summary_fields' in response.data
|
|
assert 'organization' in response.data['summary_fields']
|
|
assert response.data['summary_fields']['organization'][0]['name'] == 'test-org'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_ctint_activity_stream(monkeypatch, get, user, settings):
|
|
Setting.objects.create(key="FOO", value="bar")
|
|
settings.ACTIVITY_STREAM_ENABLED = True
|
|
u = user('admin', True)
|
|
activity_stream = ActivityStream.objects.filter(setting__icontains="FOO").latest('pk')
|
|
activity_stream.actor = u
|
|
activity_stream.save()
|
|
|
|
aspk = activity_stream.pk
|
|
url = reverse('api:activity_stream_detail', kwargs={'pk': aspk})
|
|
response = get(url, user('admin', True))
|
|
|
|
assert response.status_code == 200
|
|
assert 'summary_fields' in response.data
|
|
assert 'setting' in response.data['summary_fields']
|
|
assert response.data['summary_fields']['setting'][0]['name'] == 'FOO'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_rbac_stream_resource_roles(activity_stream_entry, organization, org_admin, settings):
|
|
settings.ACTIVITY_STREAM_ENABLED = True
|
|
assert activity_stream_entry.user.first() == org_admin
|
|
assert activity_stream_entry.organization.first() == organization
|
|
assert activity_stream_entry.role.first() == organization.admin_role
|
|
assert activity_stream_entry.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_rbac_stream_user_roles(activity_stream_entry, organization, org_admin, settings):
|
|
settings.ACTIVITY_STREAM_ENABLED = True
|
|
assert activity_stream_entry.user.first() == org_admin
|
|
assert activity_stream_entry.organization.first() == organization
|
|
assert activity_stream_entry.role.first() == organization.admin_role
|
|
assert activity_stream_entry.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.activity_stream_access
|
|
def test_stream_access_cant_change(activity_stream_entry, organization, org_admin, settings):
|
|
settings.ACTIVITY_STREAM_ENABLED = True
|
|
access = ActivityStreamAccess(org_admin)
|
|
# These should always return false because the activity stream cannot be edited
|
|
assert not access.can_add(activity_stream_entry)
|
|
assert not access.can_change(activity_stream_entry, {'organization': None})
|
|
assert not access.can_delete(activity_stream_entry)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.activity_stream_access
|
|
def test_stream_queryset_hides_shows_items(
|
|
activity_stream_entry,
|
|
organization,
|
|
user,
|
|
org_admin,
|
|
project,
|
|
org_credential,
|
|
inventory,
|
|
label,
|
|
deploy_jobtemplate,
|
|
notification_template,
|
|
group,
|
|
host,
|
|
team,
|
|
settings,
|
|
):
|
|
settings.ACTIVITY_STREAM_ENABLED = True
|
|
# this user is not in any organizations and should not see any resource activity
|
|
no_access_user = user('no-access-user', False)
|
|
access = ActivityStreamAccess(no_access_user)
|
|
queryset = access.get_queryset()
|
|
|
|
assert not queryset.filter(project__pk=project.pk)
|
|
assert not queryset.filter(credential__pk=org_credential.pk)
|
|
assert not queryset.filter(inventory__pk=inventory.pk)
|
|
assert not queryset.filter(label__pk=label.pk)
|
|
assert not queryset.filter(job_template__pk=deploy_jobtemplate.pk)
|
|
assert not queryset.filter(group__pk=group.pk)
|
|
assert not queryset.filter(host__pk=host.pk)
|
|
assert not queryset.filter(team__pk=team.pk)
|
|
assert not queryset.filter(notification_template__pk=notification_template.pk)
|
|
assert not access.can_read(activity_stream_entry)
|
|
|
|
# Organization admin should be able to see most things in the ActivityStream
|
|
access = ActivityStreamAccess(org_admin)
|
|
queryset = access.get_queryset()
|
|
|
|
assert queryset.filter(project__pk=project.pk, operation='create').count() == 1
|
|
assert queryset.filter(credential__pk=org_credential.pk, operation='create').count() == 1
|
|
assert queryset.filter(inventory__pk=inventory.pk, operation='create').count() == 1
|
|
assert queryset.filter(label__pk=label.pk, operation='create').count() == 1
|
|
assert queryset.filter(job_template__pk=deploy_jobtemplate.pk, operation='create').count() == 1
|
|
assert queryset.filter(group__pk=group.pk, operation='create').count() == 1
|
|
assert queryset.filter(host__pk=host.pk, operation='create').count() == 1
|
|
assert queryset.filter(team__pk=team.pk, operation='create').count() == 1
|
|
assert queryset.filter(notification_template__pk=notification_template.pk, operation='create').count() == 1
|
|
assert access.can_read(activity_stream_entry)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_activity_stream_pagination_uses_unfiltered_count(get, organization, project, user, settings):
|
|
"""The pagination count should reflect total activity stream rows, not
|
|
the RBAC-filtered subset. The RBAC-filtered COUNT is catastrophically
|
|
slow on large tables (AAP-83773); an approximate over-count from an
|
|
unfiltered SELECT COUNT(*) is acceptable for pagination UI."""
|
|
settings.ACTIVITY_STREAM_ENABLED = True
|
|
|
|
no_access_user = user('no-access-user', False)
|
|
|
|
total_entries = ActivityStream.objects.count()
|
|
assert total_entries > 0
|
|
|
|
url = reverse('api:activity_stream_list')
|
|
response = get(url, no_access_user)
|
|
|
|
assert response.status_code == 200
|
|
visible_results = len(response.data['results'])
|
|
pagination_count = response.data['count']
|
|
assert pagination_count == total_entries
|
|
assert visible_results < pagination_count
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_stream_user_direct_role_updates(get, post, organization_factory):
|
|
objects = organization_factory('test_org', superusers=['admin'], users=['test'], inventories=['inv1'])
|
|
|
|
url = reverse('api:user_roles_list', kwargs={'pk': objects.users.test.pk})
|
|
post(url, dict(id=objects.inventories.inv1.read_role.pk), objects.superusers.admin)
|
|
|
|
activity_stream = ActivityStream.objects.filter(
|
|
inventory__pk=objects.inventories.inv1.pk, user__pk=objects.users.test.pk, role__pk=objects.inventories.inv1.read_role.pk
|
|
).first()
|
|
url = reverse('api:activity_stream_detail', kwargs={'pk': activity_stream.pk})
|
|
response = get(url, objects.users.test)
|
|
|
|
assert response.data['object1'] == 'user'
|
|
assert response.data['object2'] == 'inventory'
|