import pytest import json import os import tempfile from unittest.mock import Mock, patch, MagicMock, mock_open from pathlib import Path from datetime import datetime from app.migrate_to_v2_permissions import ( get_new_roles, add_permissions, delete_permissions, migrate_to_v2, delete_old_roles, generate_log_filename, create_migration_log_entry, write_migration_log, perform_migration, OldRole, NewRole, FeatureFlag, transition_config, ALL_ORCHARD_LABELS_UUID ) from app.dtos import User, Vendor class TestGetNewRoles: """Testing logic for determining new roles based on old permissions and feature flags""" def test_admin_with_basic_audience_app_access(self): """Admin with basic audience app access gets VIEW_FAN_DATA role""" old_roles = [OldRole.ADMIN.value] ff = [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] new_roles = get_new_roles(old_roles, ff) assert NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value in new_roles assert len(new_roles) == 1 def test_analyst_with_basic_audience_app_access(self): """Analyst with basic audience app access gets VIEW_FAN_DATA role""" old_roles = [OldRole.ANALYST.value] ff = [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] new_roles = get_new_roles(old_roles, ff) assert NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value in new_roles assert len(new_roles) == 1 def test_admin_with_meta_sharing_permission(self): """Admin with Meta audience sharing permission gets corresponding role""" old_roles = [OldRole.ADMIN.value] ff = [ FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value, FeatureFlag.AUDIENCE_ALLOW_AUDIENCE_SHARING_TO_META.value ] new_roles = get_new_roles(old_roles, ff) assert NewRole.FANSIFTER_CAN_SHARE_AD_CAMPAIGN_AUDIENCES.value in new_roles def test_audience_manager_with_meta_sharing_gets_multiple_roles(self): """Audience manager with audience sharing permission gets multiple roles""" old_roles = [OldRole.AUDIENCE_MANAGER.value] ff = [ FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value, FeatureFlag.AUDIENCE_ALLOW_AUDIENCE_SHARING_TO_META.value ] new_roles = get_new_roles(old_roles, ff) expected_roles = { NewRole.FANSIFTER_CAN_SHARE_AD_CAMPAIGN_AUDIENCES.value, NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value } assert new_roles == expected_roles def test_admin_with_ad_reporting_permission(self): """Admin with ad reporting permission gets corresponding role""" old_roles = [OldRole.ADMIN.value] ff = [ FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value, FeatureFlag.AUDIENCE_SHOW_AD_REPORTING.value ] new_roles = get_new_roles(old_roles, ff) assert NewRole.FANSIFTER_CAN_CREATE_AD_REPORTS.value in new_roles def test_email_campaigns_create_permission(self): """Test full email campaigns permissions (creation)""" old_roles = [OldRole.ADMIN.value] ff = [ FeatureFlag.AUDIENCE_SHOW_EMAIL_CAMPAIGNS_PAGE.value, FeatureFlag.AUDIENCE_ALLOW_SCHEDULE_OR_SEND_EMAIL_CAMPAIGNS.value ] new_roles = get_new_roles(old_roles, ff) assert NewRole.FANSIFTER_CAN_CREATE_EMAIL_CAMPAIGNS.value in new_roles def test_email_campaigns_create_permission_basic(self): """Test email campaigns creation permission""" old_roles = [OldRole.ADMIN.value] ff = [FeatureFlag.AUDIENCE_SHOW_EMAIL_CAMPAIGNS_PAGE.value] new_roles = get_new_roles(old_roles, ff) assert NewRole.FANSIFTER_CAN_CREATE_EMAIL_CAMPAIGNS.value in new_roles def test_app_connections_permission(self): """Test ad accounts connection permissions""" old_roles = [OldRole.ADMIN.value] ff = [FeatureFlag.AUDIENCE_SHOW_APP_CONNECTIONS.value] new_roles = get_new_roles(old_roles, ff) assert NewRole.FANSIFTER_CAN_CONNECT_AD_ACCOUNTS.value in new_roles def test_no_matching_permissions_returns_empty_set(self): """If no matching permissions found, returns empty set""" old_roles = [OldRole.CLIENT.value] # CLIENT role has no transitions ff = [FeatureFlag.SHOW_SME_DATA.value] # Non-relevant feature flag new_roles = get_new_roles(old_roles, ff) assert len(new_roles) == 0 def test_multiple_transitions_combined(self): """Test combination of multiple transitions for one user""" old_roles = [OldRole.ADMIN.value] ff = [ FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value, FeatureFlag.AUDIENCE_ALLOW_AUDIENCE_SHARING_TO_META.value, FeatureFlag.AUDIENCE_SHOW_AD_REPORTING.value, FeatureFlag.AUDIENCE_SHOW_APP_CONNECTIONS.value ] new_roles = get_new_roles(old_roles, ff) expected_roles = { NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value, # From basic ORCHARD_SUITE_SHOW_AUDIENCE_APP NewRole.FANSIFTER_CAN_SHARE_AD_CAMPAIGN_AUDIENCES.value, NewRole.FANSIFTER_CAN_CREATE_AD_REPORTS.value, NewRole.FANSIFTER_CAN_CONNECT_AD_ACCOUNTS.value } assert new_roles == expected_roles def test_absent_feature_flags_logic(self): """Test absent_ff logic - when feature flag should NOT be present""" old_roles = [OldRole.ADMIN.value] ff = [ FeatureFlag.AUDIENCE_SHOW_EMAIL_CAMPAIGNS_PAGE.value, FeatureFlag.AUDIENCE_ALLOW_SCHEDULE_OR_SEND_EMAIL_CAMPAIGNS.value # This flag is present ] new_roles = get_new_roles(old_roles, ff) # Should get CREATE, not VIEW, because ALLOW_SCHEDULE flag is present assert NewRole.FANSIFTER_CAN_CREATE_EMAIL_CAMPAIGNS.value in new_roles assert NewRole.FANSIFTER_CAN_VIEW_EMAIL_CAMPAIGNS.value not in new_roles class TestAddPermissions: """Testing adding new permissions via PDP API""" @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.PROD_BEARER_TOKEN', 'test-token') def test_add_permissions_with_ssh_client(self, mock_get_ssh): """Test adding permissions using provided SSH client""" mock_ssh = Mock() user_uuid = "test-user-uuid" tenant_uuid = "test-tenant-uuid" roles = [NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value] # Mock the exec_command to avoid actual SSH execution in tests mock_stdout = Mock() mock_stdout.read.return_value.decode.return_value.strip.return_value = '{"success": true}' mock_ssh.exec_command.return_value = (Mock(), mock_stdout, Mock()) # Should not call get_ssh_client if ssh_client is provided add_permissions(user_uuid, tenant_uuid, roles, mock_ssh) mock_get_ssh.assert_not_called() @patch('app.migrate_to_v2_permissions.get_ssh_client') def test_add_permissions_without_ssh_client(self, mock_get_ssh): """Test adding permissions without provided SSH client""" mock_ssh = Mock() mock_get_ssh.return_value = mock_ssh user_uuid = "test-user-uuid" tenant_uuid = "test-tenant-uuid" roles = [NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value] # Mock the exec_command for the SSH client mock_stdout = Mock() mock_stdout.read.return_value.decode.return_value.strip.return_value = '{"success": true}' mock_ssh.exec_command.return_value = (Mock(), mock_stdout, Mock()) add_permissions(user_uuid, tenant_uuid, roles, None) mock_get_ssh.assert_called_once() @patch('app.migrate_to_v2_permissions.get_ssh_client') def test_add_multiple_permissions(self, mock_get_ssh): """Test adding multiple permissions at once""" mock_ssh = Mock() user_uuid = "test-user-uuid" tenant_uuid = "test-tenant-uuid" roles = [ NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value, NewRole.FANSIFTER_CAN_CREATE_AD_REPORTS.value ] # Mock the exec_command for the SSH client mock_stdout = Mock() mock_stdout.read.return_value.decode.return_value.strip.return_value = '{"success": true}' mock_ssh.exec_command.return_value = (Mock(), mock_stdout, Mock()) # Should run without error add_permissions(user_uuid, tenant_uuid, roles, mock_ssh) class TestDeletePermissions: """Testing deletion of old permissions""" @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.PROD_BEARER_TOKEN', 'test-token') def test_delete_permissions(self, mock_get_ssh): """Test permissions deletion""" mock_ssh = Mock() user_uuid = "test-user-uuid" tenant_uuid = "test-tenant-uuid" roles = [OldRole.ADMIN.value] # Mock the exec_command for the SSH client mock_stdout = Mock() mock_stdout.read.return_value.decode.return_value.strip.return_value = '{"success": true}' mock_ssh.exec_command.return_value = (Mock(), mock_stdout, Mock()) # Should run without error delete_permissions(user_uuid, tenant_uuid, roles, mock_ssh) class TestMigrateToV2: """Testing complete migration process to v2 permissions""" @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.add_permissions') def test_migrate_to_v2_success(self, mock_add_permissions, mock_get_ssh): """Test successful user migration""" mock_ssh = Mock() mock_get_ssh.return_value = mock_ssh users = [ User(email="test@example.com", identity="user1", role="admin"), User(email="test2@example.com", identity="user2", role="analyst") ] users_permissions = { "user1": { "tenant1": [OldRole.ADMIN.value] }, "user2": { "tenant1": [OldRole.ANALYST.value] } } users_treatments = { "user1": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value], "user2": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] } migrate_to_v2(users, users_permissions, users_treatments) # Check that add_permissions was called for both users assert mock_add_permissions.call_count == 2 # Check that correct roles were passed calls = mock_add_permissions.call_args_list for call in calls: user_id, tenant_uuid, new_roles, ssh_client = call[0] assert user_id in ["user1", "user2"] assert tenant_uuid == "tenant1" assert NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value in new_roles assert ssh_client == mock_ssh @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.add_permissions') def test_migrate_to_v2_no_new_roles(self, mock_add_permissions, mock_get_ssh): """Test migration when there are no new roles to add""" users = [User(email="test@example.com", identity="user1", role="client")] users_permissions = { "user1": { "tenant1": [OldRole.CLIENT.value] # CLIENT has no transitions } } users_treatments = { "user1": [FeatureFlag.SHOW_SME_DATA.value] # Non-relevant flag } migrate_to_v2(users, users_permissions, users_treatments) # add_permissions should not be called mock_add_permissions.assert_not_called() @patch('builtins.print') @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.add_permissions') def test_migrate_to_v2_user_without_feature_flags(self, mock_add_permissions, mock_get_ssh, mock_print): """Test migration of user without feature flags""" users = [User(email="test@example.com", identity="user1", role="admin")] users_permissions = { "user1": { "tenant1": [OldRole.ADMIN.value] } } users_treatments = {} # User without feature flags migrate_to_v2(users, users_permissions, users_treatments) # add_permissions should not be called because no feature flags mock_add_permissions.assert_not_called() # Should print warning message mock_print.assert_called_with("Warning: User user1 has no feature flags, skipping migration") @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.add_permissions') def test_migrate_to_v2_user_with_empty_feature_flags_list(self, mock_add_permissions, mock_get_ssh): """Test migration of user with empty feature flags list""" users = [User(email="test@example.com", identity="user1", role="admin")] users_permissions = { "user1": { "tenant1": [OldRole.ADMIN.value] } } users_treatments = { "user1": [] # Empty feature flags list } migrate_to_v2(users, users_permissions, users_treatments) # add_permissions should not be called because no matching transitions mock_add_permissions.assert_not_called() class TestDeleteOldRoles: """Testing deletion of old roles""" @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.delete_permissions') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value, OldRole.ANALYST.value]) def test_delete_old_roles(self, mock_delete_permissions, mock_get_ssh): """Test deletion of old roles""" mock_ssh = Mock() mock_get_ssh.return_value = mock_ssh users = [User(email="test@example.com", identity="user1", role="admin")] users_permissions = { "user1": { "tenant1": [OldRole.ADMIN.value, "other_role"] } } delete_old_roles(users, users_permissions) mock_delete_permissions.assert_called_once_with( "user1", "tenant1", {OldRole.ADMIN.value}, # Only roles user has that are in AVAILABLE_PDP_ROLES mock_ssh ) @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.delete_permissions') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value, OldRole.ANALYST.value]) def test_delete_old_roles_no_matching_roles(self, mock_delete_permissions, mock_get_ssh): """Test deletion when user has no old roles to delete""" mock_ssh = Mock() mock_get_ssh.return_value = mock_ssh users = [User(email="test@example.com", identity="user1", role="admin")] users_permissions = { "user1": { "tenant1": ["other_role", "another_role"] # No old roles } } delete_old_roles(users, users_permissions) # delete_permissions should not be called mock_delete_permissions.assert_not_called() @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.delete_permissions') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value, OldRole.ANALYST.value]) def test_delete_old_roles_multiple_tenants(self, mock_delete_permissions, mock_get_ssh): """Test deletion across multiple tenants""" mock_ssh = Mock() mock_get_ssh.return_value = mock_ssh users = [User(email="test@example.com", identity="user1", role="admin")] users_permissions = { "user1": { "tenant1": [OldRole.ADMIN.value], "tenant2": [OldRole.ANALYST.value, "other_role"] } } delete_old_roles(users, users_permissions) # Should be called twice - once for each tenant assert mock_delete_permissions.call_count == 2 calls = mock_delete_permissions.call_args_list # Check first call assert calls[0][0] == ("user1", "tenant1", {OldRole.ADMIN.value}, mock_ssh) # Check second call assert calls[1][0] == ("user1", "tenant2", {OldRole.ANALYST.value}, mock_ssh) class TestTransitionConfig: """Testing transition configuration""" def test_transition_config_completeness(self): """Check that all possible old role combinations are covered""" # Check that transitions exist for all main roles roles_in_config = set() for transition in transition_config: roles_in_config.update(transition.old.pdp) expected_roles = { OldRole.ADMIN.value, OldRole.ANALYST.value, OldRole.AUDIENCE_MANAGER.value } assert roles_in_config.issuperset(expected_roles) def test_transition_config_validity(self): """Check validity of transition configuration""" for i, transition in enumerate(transition_config): # Check that old.pdp contains valid roles for role in transition.old.pdp: assert role in [r.value for r in OldRole], f"Invalid old role in transition {i}: {role}" # Check that new contains valid new roles for role in transition.new: assert role in [r.value for r in NewRole], f"Invalid new role in transition {i}: {role}" # Check that present_ff contains valid feature flags for ff in transition.old.present_ff: assert ff in [f.value for f in FeatureFlag], f"Invalid feature flag in transition {i}: {ff}" def test_no_duplicate_transitions(self): """Check that there are no duplicate transitions""" seen_transitions = set() for transition in transition_config: # Create unique key for transition key = ( tuple(sorted(transition.old.pdp)), tuple(sorted(transition.old.present_ff)), tuple(sorted(transition.old.absent_ff)) ) assert key not in seen_transitions, f"Duplicate transition found: {transition}" seen_transitions.add(key) @pytest.fixture def sample_user(): """Fixture for creating test user""" return User(email="test@example.com", identity="test-uuid", role="admin") @pytest.fixture def sample_permissions(): """Fixture for creating test permissions""" return { "test-uuid": { "tenant-uuid": [OldRole.ADMIN.value] } } @pytest.fixture def sample_feature_flags(): """Fixture for creating test feature flags""" return { "test-uuid": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] } class TestLoggingFunctionality: """Testing migration logging functionality""" @patch('app.migrate_to_v2_permissions.datetime') def test_generate_log_filename_creates_logs_directory(self, mock_datetime): """Test that generate_log_filename creates logs directory""" mock_datetime.now.return_value.strftime.return_value = "2024-12-03" # Use a temporary directory for testing import tempfile import os with tempfile.TemporaryDirectory() as temp_dir: with patch('app.migrate_to_v2_permissions.Path') as mock_path_cls: from pathlib import Path logs_dir = Path(temp_dir) / "logs" mock_path_cls.return_value = logs_dir result = generate_log_filename() assert logs_dir.exists() assert "migration_log_2024-12-03_001.csv" in result @patch('app.migrate_to_v2_permissions.datetime') def test_generate_log_filename_increments_sequence(self, mock_datetime): """Test that generate_log_filename increments sequence when file exists""" mock_datetime.now.return_value.strftime.return_value = "2024-12-03" # Use a temporary directory for testing import tempfile with tempfile.TemporaryDirectory() as temp_dir: with patch('app.migrate_to_v2_permissions.Path') as mock_path_cls: from pathlib import Path logs_dir = Path(temp_dir) / "logs" mock_path_cls.return_value = logs_dir # Create the first file to simulate it exists logs_dir.mkdir(exist_ok=True) (logs_dir / "migration_log_2024-12-03_001.csv").touch() result = generate_log_filename() assert "migration_log_2024-12-03_002.csv" in result def test_create_migration_log_entry(self): """Test creating migration log entry""" vendor1 = Vendor(name="Test Vendor", id=1, uuid="tenant1") user = User(email="test@example.com", identity="user-uuid", role="admin") user.name = "Test User" user.vendors = [vendor1] permissions_before = {"tenant1": ["admin"]} permissions_after = {"tenant1": ["admin", "fansifter_can_view_fan_data"]} new_roles_added = ["fansifter_can_view_fan_data"] old_roles_removed = ["admin"] feature_flags = ["orchard_suite_show_audience_app"] timestamp = "2024-12-03T10:30:00" entry = create_migration_log_entry( user, permissions_before, permissions_after, new_roles_added, old_roles_removed, feature_flags, "success", timestamp ) assert entry['email'] == "test@example.com" assert entry['identity'] == "user-uuid" assert entry['name'] == "Test User" assert entry['vendor_names'] == "Test Vendor" assert json.loads(entry['permissions_before']) == permissions_before assert json.loads(entry['permissions_after']) == permissions_after assert json.loads(entry['new_roles_added']) == new_roles_added assert json.loads(entry['old_roles_removed']) == old_roles_removed assert json.loads(entry['feature_flags']) == feature_flags assert entry['operation_status'] == "success" assert entry['timestamp'] == timestamp def test_create_migration_log_entry_multiple_vendors(self): """Test creating migration log entry with multiple vendors""" vendor1 = Vendor(name="Vendor One", id=1, uuid="tenant1") vendor2 = Vendor(name="Vendor Two", id=2, uuid="tenant2") user = User(email="test@example.com", identity="user-uuid", role="admin") user.name = "Test User" user.vendors = [vendor1, vendor2] permissions_before = {"tenant1": ["admin"], "tenant2": ["analyst"]} permissions_after = {"tenant1": ["admin", "new_role"], "tenant2": ["analyst", "another_role"]} entry = create_migration_log_entry( user, permissions_before, permissions_after, [], [], [], "success", "2024-12-03T10:30:00" ) vendor_names = entry['vendor_names'].split(', ') assert "Vendor One" in vendor_names assert "Vendor Two" in vendor_names assert len(vendor_names) == 2 def test_create_migration_log_entry_empty_values(self): """Test creating migration log entry with empty values""" user = User(email=None, identity="user-uuid", role="admin") user.name = None entry = create_migration_log_entry( user, {}, {}, [], [], [], "skipped_no_feature_flags", "2024-12-03T10:30:00" ) assert entry['email'] == "" assert entry['name'] == "" assert entry['permissions_before'] == "" assert entry['new_roles_added'] == "" assert entry['operation_status'] == "skipped_no_feature_flags" def test_create_migration_log_entry_all_labels_access(self): """Test creating migration log entry for user with ALL_ORCHARD_LABELS_UUID""" user = User(email="test@example.com", identity="user-uuid", role="admin") user.name = "Test User" user.vendors = [] # No specific vendors when user has all labels access permissions_before = {ALL_ORCHARD_LABELS_UUID: ["admin"]} permissions_after = {ALL_ORCHARD_LABELS_UUID: ["admin", "fansifter_can_view_fan_data"]} new_roles_added = ["fansifter_can_view_fan_data"] old_roles_removed = ["admin"] feature_flags = ["orchard_suite_show_audience_app"] timestamp = "2024-12-03T10:30:00" entry = create_migration_log_entry( user, permissions_before, permissions_after, new_roles_added, old_roles_removed, feature_flags, "success", timestamp ) assert entry['email'] == "test@example.com" assert entry['identity'] == "user-uuid" assert entry['name'] == "Test User" assert entry['vendor_names'] == "user with all labels access" assert json.loads(entry['permissions_before']) == permissions_before assert json.loads(entry['permissions_after']) == permissions_after assert json.loads(entry['new_roles_added']) == new_roles_added assert json.loads(entry['old_roles_removed']) == old_roles_removed assert json.loads(entry['feature_flags']) == feature_flags assert entry['operation_status'] == "success" assert entry['timestamp'] == timestamp @patch('builtins.open', new_callable=mock_open) @patch('app.migrate_to_v2_permissions.csv.DictWriter') def test_write_migration_log(self, mock_dict_writer, mock_file): """Test writing migration log to CSV""" mock_writer = Mock() mock_dict_writer.return_value = mock_writer log_entries = [ {'email': 'test@example.com', 'identity': 'uuid', 'operation_status': 'success'}, {'email': 'test2@example.com', 'identity': 'uuid2', 'operation_status': 'skipped'} ] write_migration_log(log_entries, "test_log.csv") mock_file.assert_called_once_with("test_log.csv", 'w', newline='', encoding='utf-8') mock_writer.writeheader.assert_called_once() mock_writer.writerows.assert_called_once_with(log_entries) def test_write_migration_log_empty_entries(self): """Test that write_migration_log does nothing with empty entries""" with patch('builtins.open') as mock_file: write_migration_log([], "test_log.csv") mock_file.assert_not_called() class TestPerformMigration: """Testing the main migration coordination function""" @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('builtins.print') def test_perform_migration_dry_run(self, mock_print, mock_get_new_roles, mock_get_permissions, mock_check_treatments): """Test perform_migration in dry run mode""" # Setup test data users = [User(email="test@example.com", identity="user1", role="admin")] mock_get_permissions.return_value = { "user1": {"tenant1": [OldRole.ADMIN.value]} } mock_check_treatments.return_value = { "user1": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] } mock_get_new_roles.return_value = {NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value} success, log_file = perform_migration(users, dry_run=True) assert success is True assert log_file is None # No log file in dry run mock_print.assert_any_call("Starting migration for 1 users (dry_run=True)") mock_print.assert_any_call("\nDry run completed - no changes made") # Check that detailed tenant information is shown in dry-run printed_calls = [str(call) for call in mock_print.call_args_list] tenant_details_shown = any("Tenant tenant1:" in call for call in printed_calls) assert tenant_details_shown, "Should show detailed tenant information in dry-run mode" @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value]) @patch('builtins.print') def test_perform_migration_dry_run_detailed_output(self, mock_print, mock_get_new_roles, mock_get_permissions, mock_check_treatments): """Test detailed output in dry run mode with multiple tenants""" vendor1 = Vendor(name="Test Vendor One", id=1, uuid="tenant1") vendor2 = Vendor(name="Test Vendor Two", id=2, uuid="tenant2") users = [User(email="test@example.com", identity="user1", role="admin")] users[0].vendors = [vendor1, vendor2] mock_get_permissions.return_value = { "user1": { "tenant1": [OldRole.ADMIN.value, "other_role"], "tenant2": [OldRole.ADMIN.value] } } mock_check_treatments.return_value = { "user1": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] } mock_get_new_roles.return_value = {NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value} success, log_file = perform_migration(users, dry_run=True) assert success is True # Check detailed output for each tenant printed_calls = [str(call) for call in mock_print.call_args_list] # Should show current roles current_roles_shown = any("Current roles:" in call for call in printed_calls) assert current_roles_shown, "Should show current roles for each tenant" # Should show new roles to add new_roles_shown = any("New roles to add:" in call for call in printed_calls) assert new_roles_shown, "Should show new roles to add" # Should show old roles to remove old_roles_shown = any("Old roles to remove:" in call for call in printed_calls) assert old_roles_shown, "Should show old roles to remove" # Should show final roles final_roles_shown = any("Final roles:" in call for call in printed_calls) assert final_roles_shown, "Should show final roles after migration" # Should show vendor names in tenant output vendor_names_shown = any("Test Vendor One" in call for call in printed_calls) assert vendor_names_shown, "Should show vendor names in dry-run output" @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value]) @patch('builtins.print') def test_perform_migration_dry_run_all_labels_access(self, mock_print, mock_get_new_roles, mock_get_permissions, mock_check_treatments): """Test dry run mode output for user with ALL_ORCHARD_LABELS_UUID""" users = [User(email="test@example.com", identity="user1", role="admin")] users[0].vendors = [] # No specific vendors for all labels user mock_get_permissions.return_value = { "user1": { ALL_ORCHARD_LABELS_UUID: [OldRole.ADMIN.value] } } mock_check_treatments.return_value = { "user1": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] } mock_get_new_roles.return_value = {NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value} success, log_file = perform_migration(users, dry_run=True) assert success is True # Check that "user with all labels access" appears in output printed_calls = [str(call) for call in mock_print.call_args_list] all_labels_shown = any("user with all labels access" in call for call in printed_calls) assert all_labels_shown, "Should show 'user with all labels access' in dry-run output" @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') def test_perform_migration_api_error(self, mock_get_permissions, mock_check_treatments): """Test perform_migration when API calls fail""" users = [User(email="test@example.com", identity="user1", role="admin")] mock_get_permissions.side_effect = Exception("SSH connection failed") with patch('builtins.print') as mock_print: success, log_file = perform_migration(users, dry_run=False) assert success is False assert log_file is None mock_print.assert_any_call("Error fetching data: SSH connection failed") @patch('app.migrate_to_v2_permissions.write_migration_log') @patch('app.migrate_to_v2_permissions.generate_log_filename') @patch('app.migrate_to_v2_permissions.delete_permissions') @patch('app.migrate_to_v2_permissions.add_permissions') @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value]) def test_perform_migration_success(self, mock_get_permissions, mock_check_treatments, mock_get_new_roles, mock_get_ssh, mock_add_permissions, mock_delete_permissions, mock_generate_filename, mock_write_log): """Test successful complete migration""" # Setup test data users = [User(email="test@example.com", identity="user1", role="admin")] mock_get_permissions.return_value = { "user1": {"tenant1": [OldRole.ADMIN.value]} } mock_check_treatments.return_value = { "user1": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] } mock_get_new_roles.return_value = {NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value} mock_generate_filename.return_value = "logs/migration_log_2024-12-03_001.csv" mock_ssh = Mock() mock_get_ssh.return_value = mock_ssh with patch('builtins.print') as mock_print: success, log_file = perform_migration(users, dry_run=False) assert success is True assert log_file == "logs/migration_log_2024-12-03_001.csv" # Check that roles were added and removed mock_add_permissions.assert_called_once() mock_delete_permissions.assert_called_once() mock_write_log.assert_called_once() mock_print.assert_any_call("New roles added successfully!") mock_print.assert_any_call("Old roles removed successfully!") @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.get_new_roles') def test_perform_migration_user_no_feature_flags(self, mock_get_new_roles, mock_get_permissions, mock_check_treatments): """Test perform_migration with user without feature flags""" users = [User(email="test@example.com", identity="user1", role="admin")] mock_get_permissions.return_value = { "user1": {"tenant1": [OldRole.ADMIN.value]} } mock_check_treatments.return_value = {} # No feature flags for user with patch('builtins.print') as mock_print: success, log_file = perform_migration(users, dry_run=True) assert success is True mock_print.assert_any_call("Skipping test@example.com - no feature flags") @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', []) # No old roles to remove def test_perform_migration_no_changes_needed(self, mock_get_new_roles, mock_get_permissions, mock_check_treatments): """Test perform_migration when no changes are needed""" users = [User(email="test@example.com", identity="user1", role="admin")] mock_get_permissions.return_value = { "user1": {"tenant1": ["some_other_role"]} # No old roles to remove } mock_check_treatments.return_value = { "user1": [FeatureFlag.SHOW_SME_DATA.value] # Non-relevant feature flag } mock_get_new_roles.return_value = set() # No new roles with patch('builtins.print') as mock_print: success, log_file = perform_migration(users, dry_run=False) assert success is True assert log_file is None mock_print.assert_any_call("No users to migrate") @patch('app.migrate_to_v2_permissions.add_permissions') @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') def test_perform_migration_add_permissions_error(self, mock_get_permissions, mock_check_treatments, mock_get_new_roles, mock_get_ssh, mock_add_permissions): """Test perform_migration when adding permissions fails""" users = [User(email="test@example.com", identity="user1", role="admin")] mock_get_permissions.return_value = { "user1": {"tenant1": [OldRole.ADMIN.value]} } mock_check_treatments.return_value = { "user1": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] } mock_get_new_roles.return_value = {NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value} mock_add_permissions.side_effect = Exception("Permission addition failed") with patch('builtins.print') as mock_print: success, log_file = perform_migration(users, dry_run=False) assert success is False assert log_file is None mock_print.assert_any_call("Error during role addition: Permission addition failed") @patch('app.migrate_to_v2_permissions.write_migration_log') @patch('app.migrate_to_v2_permissions.generate_log_filename') @patch('app.migrate_to_v2_permissions.delete_permissions') @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value]) def test_perform_migration_only_remove_old_roles(self, mock_get_permissions, mock_check_treatments, mock_get_new_roles, mock_get_ssh, mock_delete_permissions, mock_generate_filename, mock_write_log): """Test migration when there are no new roles to add but old roles to remove""" users = [User(email="test@example.com", identity="user1", role="admin")] mock_get_permissions.return_value = { "user1": {"tenant1": [OldRole.ADMIN.value]} # Has old role to remove } mock_check_treatments.return_value = { "user1": [FeatureFlag.SHOW_SME_DATA.value] # Non-relevant feature flag - no new roles } mock_get_new_roles.return_value = set() # No new roles to add mock_generate_filename.return_value = "logs/migration_log_2024-12-03_001.csv" mock_ssh = Mock() mock_get_ssh.return_value = mock_ssh with patch('builtins.print') as mock_print: success, log_file = perform_migration(users, dry_run=False) assert success is True assert log_file == "logs/migration_log_2024-12-03_001.csv" # Should NOT call add_permissions since no new roles # Should call delete_permissions since there are old roles to remove mock_delete_permissions.assert_called_once_with("user1", "tenant1", [OldRole.ADMIN.value], mock_ssh) mock_write_log.assert_called_once() # Check specific print messages mock_print.assert_any_call("No new roles to add - proceeding to role removal...") mock_print.assert_any_call("Old roles removed successfully!") @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value]) def test_perform_migration_dry_run_only_remove_old_roles(self, mock_get_new_roles, mock_get_permissions, mock_check_treatments): """Test dry run when there are no new roles to add but old roles to remove""" vendor1 = Vendor(name="Test Vendor", id=1, uuid="tenant1") users = [User(email="test@example.com", identity="user1", role="admin")] users[0].vendors = [vendor1] mock_get_permissions.return_value = { "user1": {"tenant1": [OldRole.ADMIN.value, "other_role"]} } mock_check_treatments.return_value = { "user1": [FeatureFlag.SHOW_SME_DATA.value] # Non-relevant feature flag } mock_get_new_roles.return_value = set() # No new roles with patch('builtins.print') as mock_print: success, log_file = perform_migration(users, dry_run=True) assert success is True assert log_file is None # Check that user is processed even with no new roles printed_calls = [str(call) for call in mock_print.call_args_list] user_processed = any("User test@example.com:" in call for call in printed_calls) assert user_processed, "User should be processed even when only removing old roles" # Should show old roles to remove but no new roles to add old_roles_shown = any("Old roles to remove:" in call for call in printed_calls) assert old_roles_shown, "Should show old roles to remove" # Should show that no new roles are being added final_roles_shown = any("Final roles:" in call for call in printed_calls) assert final_roles_shown, "Should show final roles after removal" @patch('app.migrate_to_v2_permissions.delete_permissions') @patch('app.migrate_to_v2_permissions.get_ssh_client') @patch('app.migrate_to_v2_permissions.get_new_roles') @patch('app.migrate_to_v2_permissions.check_treatments') @patch('app.migrate_to_v2_permissions.get_pdp_permissions_for_identities') @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value]) def test_perform_migration_remove_old_roles_error(self, mock_get_permissions, mock_check_treatments, mock_get_new_roles, mock_get_ssh, mock_delete_permissions): """Test perform_migration when removing old roles fails""" users = [User(email="test@example.com", identity="user1", role="admin")] mock_get_permissions.return_value = { "user1": {"tenant1": [OldRole.ADMIN.value]} } mock_check_treatments.return_value = { "user1": [FeatureFlag.SHOW_SME_DATA.value] # Non-relevant feature flag } mock_get_new_roles.return_value = set() # No new roles mock_delete_permissions.side_effect = Exception("Permission deletion failed") with patch('builtins.print') as mock_print: success, log_file = perform_migration(users, dry_run=False) assert success is False assert log_file is None mock_print.assert_any_call("Error during role removal: Permission deletion failed") class TestMigrationIntegration: """Integration tests for migration functionality""" @patch('app.migrate_to_v2_permissions.AVAILABLE_PDP_ROLES', [OldRole.ADMIN.value, OldRole.ANALYST.value]) def test_full_migration_workflow_calculation(self): """Test the full migration workflow calculations without API calls""" # Create test users users = [ User(email="admin@example.com", identity="user1", role="admin"), User(email="analyst@example.com", identity="user2", role="analyst") ] # Mock permissions data users_permissions = { "user1": {"tenant1": [OldRole.ADMIN.value]}, "user2": {"tenant1": [OldRole.ANALYST.value]} } # Mock feature flags users_treatments = { "user1": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value], "user2": [FeatureFlag.ORCHARD_SUITE_SHOW_AUDIENCE_APP.value] } # Test the calculation logic user_lookup = {user.identity: user for user in users} for user_id, user_tenants in users_permissions.items(): user = user_lookup[user_id] user_ff = users_treatments[user_id] for tenant_uuid, roles in user_tenants.items(): new_roles = get_new_roles(roles, user_ff) # Both admin and analyst should get VIEW_FAN_DATA role assert NewRole.FANSIFTER_CAN_VIEW_FAN_DATA.value in new_roles assert len(new_roles) == 1