"""Unit tests for split_handlers.py.""" from unittest.mock import MagicMock import pytest from owsrequest import error_response from features import server from features import config from features.connectors import redis from features.constants import header from features.utils import parser from features.models import ows_users from features.models import split_feature client = server.app.test_client() def all_split_feature_data(): """Return data for multiple split features.""" return { 'feature_1': 'variant_1', 'feature_2': 'variant_2', 'feature_3': 'variant_2', 'feature_4': 'variant_1', } def forced_all_split_feature_data(): """Return forced rule data for multiple split features.""" return { 'feature_1': 'variant_1', 'feature_2': 'variant_1', 'feature_3': 'variant_2', 'feature_4': 'variant_1', } def all_profile_split_feature_data(): """Return data for multiple split features.""" return { 'feature_3': 'variant_2', 'feature_4': 'variant_1', } def users_data(): """Return data for multiple split features.""" user_response_message = { 'type': 'alw', 'user_id': 'alw:16088', 'account': {'vendor_id': 7123, 'subaccount_id': None}, } mock_response = MagicMock() mock_response.status = 200 mock_response.json = MagicMock(return_value=user_response_message) return mock_response def test_get_split_features_without_headers(monkeypatch): """Verify that get_feature returns feature without user context.""" monkeypatch.setattr( split_feature, 'get_all_split_features', MagicMock(return_value=all_split_feature_data()), ) resp = client.get('/features?splitioEnabled=true', headers={}) assert resp.status_code == 200 assert resp.get_json() == all_split_feature_data() @pytest.mark.parametrize( ('headers', 'expected_status_code', 'expected_json'), [ # only profile type, profile id ( {header.ORCHARD_PROFILE_TYPE: 'ArtistProfile'}, 400, error_response.create_error_incomplete_profile_headers().errors, ), # only profile id ( {header.ORCHARD_PROFILE_ID: 555}, 400, error_response.create_error_incomplete_profile_headers().errors, ), # account type ( {header.GRASS_ACCOUNT_TYPE: 'subaccount'}, 400, error_response.create_error_incomplete_profile_headers().errors, ), # account id ( {header.GRASS_ACCOUNT_ID: 71223}, 400, error_response.create_error_incomplete_profile_headers().errors, ), ], ) def test_get_split_features_with_invalid_headers( headers, expected_status_code, expected_json ): """Verify that get_features returns error.""" resp = client.get( '/features?splitioEnabled=true', headers={'Grass-Account-Type': 'vendor'} ) assert resp.status_code == 400 assert resp.json == ( error_response.create_error_incomplete_profile_headers().errors ) def test_get_split_features_with_vendor_headers(monkeypatch): """Verify that get_features returns features with vendor context.""" monkeypatch.setattr( split_feature, 'get_all_split_features', MagicMock(return_value=forced_all_split_feature_data()), ) monkeypatch.setattr( ows_users, 'get_user_details_by_user_id', MagicMock(return_value=users_data()) ) resp = client.get( '/features?splitioEnabled=true', headers={ 'Grass-Account-Type': 'vendor', 'Grass-Account-Id': 7123, 'Orchard-User-Id': 'alw:16888', }, ) assert resp.status_code == 200 assert resp.json == forced_all_split_feature_data() def test_get_split_features_with_subaccount_headers(monkeypatch): """Verify that get_features returns features with vendor context.""" monkeypatch.setattr( split_feature, 'get_all_split_features', MagicMock(return_value=all_split_feature_data()), ) monkeypatch.setattr(redis.client, 'get', MagicMock(return_value=None)) monkeypatch.setattr(redis.client, 'set', MagicMock()) mock_response = MagicMock() mock_response.status_code = 200 mock_response.json = MagicMock( return_value={ 'type': 'alw', 'user_id': 'alw:16092', 'account': {'vendor_id': 21989, 'subaccount_id': 1}, } ) monkeypatch.setattr( 'features.models.ows_users.requests.get', MagicMock(return_value=mock_response) ) resp = client.get( '/features?splitioEnabled=true', headers={ 'Grass-Account-Type': 'subaccount', 'Grass-Account-Id': 1, 'Orchard-User-Id': 'alw:16092', }, ) assert resp.status_code == 200 assert resp.json == all_split_feature_data() @pytest.mark.parametrize( ('headers', 'split_logic_response', 'expected_status_code', 'expected_json'), [ # no headers ( None, MagicMock(return_value=all_profile_split_feature_data()), 200, { 'feature_1': 'variant_2', 'feature_2': 'variant_2', 'feature_3': 'variant_2', 'feature_4': 'variant_1', }, ), # profile type, profile id ( { header.ORCHARD_PROFILE_TYPE: 'ArtistProfile', header.ORCHARD_PROFILE_ID: 555, }, MagicMock(return_value=all_profile_split_feature_data()), 200, { 'feature_1': 'variant_1', 'feature_2': 'variant_2', 'feature_3': 'variant_2', 'feature_4': 'variant_1', }, ), # identity id ( {header.ORCHARD_IDENTITY_ID: '5d542f411234a40db1f64010'}, MagicMock(return_value=all_profile_split_feature_data()), 200, { 'feature_1': 'variant_2', 'feature_2': 'variant_1', 'feature_3': 'variant_2', 'feature_4': 'variant_1', }, ), # all 3, empty split response ( { header.ORCHARD_PROFILE_TYPE: 'ArtistProfile', header.ORCHARD_PROFILE_ID: 555, header.ORCHARD_IDENTITY_ID: '5d542f411234a40db1f64010', }, MagicMock(return_value={}), 200, {'feature_1': 'variant_1', 'feature_2': 'variant_1'}, ), ], ) def test_handler_get_all_features_with_profile_context( monkeypatch, headers, split_logic_response, expected_status_code, expected_json ): """Test handler.get_features() with profile context.""" monkeypatch.setattr(split_feature, 'get_all_split_features', split_logic_response) server.features.clear() server.features.update( parser.parse_features_from_yaml_file(config.PROFILE_FEATURES_YML_FILE) ) resp = client.get('/features?splitioEnabled=true', headers=headers) assert resp.status_code == expected_status_code assert resp.get_json() == expected_json