from unittest.mock import Mock, patch import pytest from flask import g from owsrequest.utils import mock_request from requests.structures import CaseInsensitiveDict from playlist import api from playlist.connectors.redis import redis_client from playlist.services import ows_permissions from playlist.services.ows_permissions import ( OWS_PERMISSIONS_PROFILE_URL, OWS_PERMISSIONS_SERVICE_NAME, get_permissions, ) @pytest.fixture def client(): """Return test client.""" test_client = api.app.test_client() class Ows: def __init__(self): self.correlation_id = "1" class RequestContext: def __init__(self): self.authorization = True with test_client.application.app_context(): g.ows = Ows() g.request_context = RequestContext() yield OWS_PERMISSIONS_ALL_ACCESS_RESPONSE = { "items": [ { "name": "All Orchard Labels", "vendorId": "*", "id": "*", "uuid": "053a1a75-acc5-4cd8-9206-a194335d2afa", "type": "Vendor", "roles": ["analytics"], } ] } OWS_PERMISSIONS_ALL_ACCESS_PARSED = { "permission_label_ids": [], "permission_label_participant_ids": [], "permission_subaccount_ids": [], } PERMISSION_RESOURCES = [ { "name": "Test Vendor", "id": "1", "vendorId": "1-1", "uuid": "1-2", "type": "Vendor", "roles": ["analytics"], }, { "name": "Test Subaccount", "id": "2", "vendorId": "2-1", "uuid": "2-2", "type": "Subaccount", "roles": ["analytics"], }, { "name": "Test Label Participant", "id": 3, "vendorId": "3-1", "uuid": "3-2", "type": "LabelParticipant", "roles": ["analytics"], }, ] MOCK_PROFILE_ID = "1" MOCK_PROFILE_TYPE = "InsightsProfile" URL = OWS_PERMISSIONS_PROFILE_URL.format( profile_id=MOCK_PROFILE_ID, profile_type=MOCK_PROFILE_TYPE ) MOCK_HEADERS = { "Orchard-Profile-Id": MOCK_PROFILE_ID, "Orchard-Profile-Type": MOCK_PROFILE_TYPE, } MOCK_FLASK_REQUEST = Mock(headers=CaseInsensitiveDict(MOCK_HEADERS)) def test_get_accessible_resources_not_authorized(client): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=401, ) with patch("playlist.services.ows_permissions.abort", return_value=None) as abort: get_permissions(MOCK_FLASK_REQUEST) abort.assert_called_with(401) def test_get_accessible_resources_raises_value_error(client): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=500, ) with pytest.raises(ValueError): get_permissions(MOCK_FLASK_REQUEST) def test_get_accessible_resources_returns_resources(client): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=200, response=OWS_PERMISSIONS_ALL_ACCESS_RESPONSE, ) resources = get_permissions(MOCK_FLASK_REQUEST) assert resources is not None assert resources == OWS_PERMISSIONS_ALL_ACCESS_PARSED def test_get_accessible_resources_returns_empty_resources(client): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=200, response={"items": []}, ) with patch("playlist.services.ows_permissions.abort") as abort: get_permissions(MOCK_FLASK_REQUEST) abort.assert_called_with(401) def test_get_accessible_resources_returns_invalid_resources( client, ): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=200, response={ "items": [ { "name": "Test Artist", "vendorId": "3434", "id": "2323", "uuid": "11111111-acc5-4cd8-9206-a194335d2afa", "type": "ArtistInfo", "roles": ["analytics"], } ] }, ) with patch("playlist.services.ows_permissions.abort") as abort: get_permissions(MOCK_FLASK_REQUEST) abort.assert_called_with(403) def test_get_accessible_resources_returns_ids(client): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=200, response={"items": PERMISSION_RESOURCES}, ) resources = get_permissions(MOCK_FLASK_REQUEST) ids = [id for id_list in resources.values() for id in id_list] assert len(ids) == 3 assert set(ids) == {"1", "2", 3} def test_get_accessible_resources_returns_ids_non_standard(client): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=200, response={ "items": [ { "name": "Test Subaccount 2", "id": "4", "vendorId": "4-1", "uuid": "4-2", "type": "SubAccount", "roles": ["analytics"], }, ] + PERMISSION_RESOURCES }, ) resources = get_permissions(MOCK_FLASK_REQUEST) ids = [id for id_list in resources.values() for id in id_list] assert len(ids) == 4 assert set(ids) == {"1", "2", 3, "4"} def test_get_accessible_resources_returns_ids_with_different_role(client): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=200, response={ "items": [ { "name": "Test Subaccount 2", "id": "4", "vendorId": "4-1", "uuid": "4-2", "type": "Vendor", "roles": ["administrator"], }, ] + PERMISSION_RESOURCES }, ) resources = get_permissions(MOCK_FLASK_REQUEST) ids = [id for id_list in resources.values() for id in id_list] assert len(ids) == 4 assert set(ids) == {"1", "2", 3, "4"} def test_get_accessible_resources_returns_ids_with_role_not_allowed(client): mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=200, response={ "items": [ { "name": "Test Subaccount 2", "id": "4", "vendorId": "4-1", "uuid": "4-2", "type": "Vendor", "roles": ["xyz"], }, ] + PERMISSION_RESOURCES }, ) resources = get_permissions(MOCK_FLASK_REQUEST) ids = [id for id_list in resources.values() for id in id_list] assert len(ids) == 3 assert set(ids) == {"1", "2", 3} @pytest.mark.disable_mock_cache def test_cached_permissions(client): redis_client.flushall() mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=200, response=OWS_PERMISSIONS_ALL_ACCESS_RESPONSE, ) with ( patch.object( ows_permissions.redis_client, "set", wraps=redis_client.set ) as redis_set, patch.object( ows_permissions.redis_client, "get", wraps=redis_client.get ) as redis_get, ): assert redis_get.call_count == 0 assert redis_set.call_count == 0 resources = get_permissions(MOCK_FLASK_REQUEST) assert resources is not None assert resources == OWS_PERMISSIONS_ALL_ACCESS_PARSED assert redis_get.call_count == 1 assert redis_set.call_count == 1 resources = get_permissions(MOCK_FLASK_REQUEST) assert resources is not None assert resources == OWS_PERMISSIONS_ALL_ACCESS_PARSED assert redis_get.call_count == 2 assert redis_set.call_count == 1 @pytest.mark.disable_mock_cache def test_errors_not_cached(client): redis_client.flushall() mock_request.get( OWS_PERMISSIONS_SERVICE_NAME, URL, status=500, ) with ( patch.object( ows_permissions.redis_client, "set", wraps=redis_client.set ) as redis_set, patch.object( ows_permissions.redis_client, "get", wraps=redis_client.get ) as redis_get, ): assert redis_get.call_count == 0 assert redis_set.call_count == 0 with pytest.raises(ValueError): get_permissions(MOCK_FLASK_REQUEST) assert redis_get.call_count == 1 assert redis_set.call_count == 0 with pytest.raises(ValueError): get_permissions(MOCK_FLASK_REQUEST) assert redis_get.call_count == 2 assert redis_set.call_count == 0 @pytest.mark.disable_mock_cache def test_empty_results_not_cached(client): redis_client.flushall() mock_request.get(OWS_PERMISSIONS_SERVICE_NAME, URL, status=400, response={}) with ( patch("playlist.services.ows_permissions.abort", return_value=None) as abort, patch.object( ows_permissions.redis_client, "set", wraps=redis_client.set ) as redis_set, patch.object( ows_permissions.redis_client, "get", wraps=redis_client.get ) as redis_get, ): assert redis_get.call_count == 0 assert redis_set.call_count == 0 get_permissions(MOCK_FLASK_REQUEST) abort.assert_called_with(401) assert redis_get.call_count == 1 assert redis_set.call_count == 0 get_permissions(MOCK_FLASK_REQUEST) abort.assert_called_with(401) assert redis_get.call_count == 2 assert redis_set.call_count == 0