import unittest from types import SimpleNamespace from unittest.mock import AsyncMock, patch from backend.app import db from backend.app.services import jellyfin_sync from backend.app.services.jellyfin_identity import link_user, user_for_identity from backend.app.routers import admin from backend.tests.test_backend_quality import TempDatabaseMixin class IdentitySyncTests(TempDatabaseMixin, unittest.IsolatedAsyncioTestCase): async def test_sync_reuses_id_when_names_differ_and_preserves_settings(self): db.create_user('old@example.test', 'Password-123456!', auth_provider='jellyseerr', jellyseerr_user_id=42, auto_search_enabled=False, email='kept@example.test') original = db.get_user_by_username('old@example.test') runtime = SimpleNamespace(jellyfin_base_url='http://jf', jellyfin_api_key='test') jf = SimpleNamespace(configured=lambda: True, get_users=AsyncMock(return_value=[{'Id': 'a' * 32, 'Name': 'NewName'}])) with patch.object(jellyfin_sync, 'get_runtime_settings', return_value=runtime), \ patch.object(jellyfin_sync, 'JellyfinClient', return_value=jf), \ patch.object(jellyfin_sync, 'get_cached_jellyseerr_users', return_value=[{'id': 42, 'jellyfinUserId': 'a' * 32, 'email': 'upstream@example.test'}]), \ patch.object(jellyfin_sync, 'save_jellyfin_users_cache'): self.assertEqual(await jellyfin_sync.sync_jellyfin_users(), 0) self.assertEqual(await jellyfin_sync.sync_jellyfin_users(), 0) kept = user_for_identity('a' * 32, 'http://jf') self.assertEqual(kept['id'], original['id']) self.assertFalse(kept['auto_search_enabled']) self.assertEqual(kept['email'], 'kept@example.test') self.assertIsNone(db.get_user_by_username('NewName')) self.assertEqual(kept['auth_provider'], 'jellyfin') async def test_resync_no_longer_deletes_accounts(self): db.create_user('Keep', 'Password-123456!') runtime = SimpleNamespace(jellyseerr_base_url='http://seer', jellyseerr_api_key='test') with patch.object(admin, 'get_runtime_settings', return_value=runtime), \ patch.object(admin, '_fetch_all_jellyseerr_users', new=AsyncMock(return_value=[{'id': 42}])), \ patch.object(jellyfin_sync, 'sync_jellyfin_users', new=AsyncMock(return_value=0)), \ patch.object(admin, 'delete_non_admin_users') as delete: result = await admin.jellyseerr_users_resync() self.assertEqual(result['cleared'], 0) delete.assert_not_called() self.assertIsNotNone(db.get_user_by_username('Keep')) def test_jellyfin_lookup_is_scoped_to_server(self): db.create_user('Viewer', 'Password-123456!', auth_provider='jellyfin') link_user('Viewer', 'a' * 32, 'http://jf') self.assertIsNotNone(user_for_identity('a' * 32, 'http://jf')) self.assertIsNone(user_for_identity('a' * 32, 'http://other-server'))