"""Tests for watcher event loop logic.""" from unittest.mock import AsyncMock, patch import pytest from watcher.event_loop import ( extract_nc_file_path, is_audio_file, is_band_audio_path, poll_once, ) def test_is_audio_file_matches_extensions(): extensions = [".wav", ".mp3", ".flac"] assert is_audio_file("/bands/foo/songs/bar/take1.wav", extensions) assert is_audio_file("/bands/foo/songs/bar/take1.MP3", extensions) assert not is_audio_file("/bands/foo/songs/bar/cover.jpg", extensions) assert not is_audio_file("/bands/foo/songs/bar/notes.txt", extensions) def test_is_band_audio_path(): assert is_band_audio_path("/bands/myband/songs/mysong/take.wav") assert is_band_audio_path("bands/slug/songs/") assert not is_band_audio_path("/nextcloud/files/random.wav") assert not is_band_audio_path("/") def test_extract_nc_file_path_from_objects(): activity = {"objects": {"42": "/bands/foo/songs/bar/take.wav"}} path = extract_nc_file_path(activity) assert path == "/bands/foo/songs/bar/take.wav" def test_extract_nc_file_path_from_object_name(): activity = {"objects": {}, "object_name": "/bands/foo/songs/bar/take.wav"} path = extract_nc_file_path(activity) assert path == "/bands/foo/songs/bar/take.wav" def test_extract_nc_file_path_returns_none_when_missing(): activity = {"objects": {}} path = extract_nc_file_path(activity) assert path is None @pytest.mark.asyncio async def test_poll_once_ignores_non_audio_files(settings): from watcher.nc_client import NextcloudWatcherClient nc = AsyncMock(spec=NextcloudWatcherClient) nc.get_activities.return_value = [ { "activity_id": 1, "subject": "file_created", "objects": {"1": "/bands/foo/songs/bar/image.jpg"}, } ] with patch("watcher.event_loop.register_version_with_api") as mock_register: await poll_once(nc, settings) mock_register.assert_not_called() @pytest.mark.asyncio async def test_poll_once_registers_audio_upload(settings): from watcher.nc_client import NextcloudWatcherClient nc = AsyncMock(spec=NextcloudWatcherClient) nc.get_activities.return_value = [ { "activity_id": 5, "subject": "file_created", "objects": {"10": "/bands/myband/songs/mysong/take1.wav"}, } ] nc.get_file_etag.return_value = "abc123" with patch( "watcher.event_loop.register_version_with_api", new_callable=AsyncMock, return_value=True ) as mock_register: await poll_once(nc, settings) mock_register.assert_called_once_with( "/bands/myband/songs/mysong/take1.wav", "abc123", settings.api_url, ) @pytest.mark.asyncio async def test_poll_once_ignores_non_file_events(settings): from watcher.nc_client import NextcloudWatcherClient nc = AsyncMock(spec=NextcloudWatcherClient) nc.get_activities.return_value = [ { "activity_id": 2, "subject": "shared", # not file_created or file_changed "objects": {"5": "/bands/foo/songs/bar/take.wav"}, } ] with patch("watcher.event_loop.register_version_with_api") as mock_register: await poll_once(nc, settings) mock_register.assert_not_called() @pytest.mark.asyncio async def test_poll_once_empty_activities_does_nothing(settings): from watcher.nc_client import NextcloudWatcherClient nc = AsyncMock(spec=NextcloudWatcherClient) nc.get_activities.return_value = [] with patch("watcher.event_loop.register_version_with_api") as mock_register: await poll_once(nc, settings) mock_register.assert_not_called()