from django.utils import timezone as tz import pytest from model_bakery import baker from aircox import models from aircox.test import interface from aircox_streamer import controllers @pytest.fixture def monitor(streamer): streamer.calls = {} return controllers.Monitor( streamer, tz.timedelta(seconds=10), cancel_timeout=tz.timedelta(minutes=10), sync_timeout=tz.timedelta(minutes=5), ) @pytest.fixture def diffusion(program, episode, sound): return baker.make( models.Diffusion, program=program, episode=episode, start=tz.now() - tz.timedelta(minutes=2), end=tz.now() + tz.timedelta(minutes=4), schedule=None, type=models.Diffusion.TYPE_ON_AIR, ) @pytest.fixture def source(monitor, streamer, sound, diffusion): source = next(monitor.streamer.playlists) source.uri = sound.file.path source.episode_id = sound.episode_id source.air_time = diffusion.start + tz.timedelta(seconds=10) return source @pytest.fixture def tracks(sound): items = [baker.prepare(models.Track, sound=sound, position=i, timestamp=i * 60) for i in range(0, 4)] models.Track.objects.bulk_create(items) return items @pytest.fixture def log(station, source, sound): return baker.make( models.Log, station=station, type=models.Log.TYPE_START, sound=sound, source=source.id, ) class TestMonitor: @pytest.mark.django_db(transaction=True) def test_last_diff_start(self, monitor, diffusion, log): log.diffusion = diffusion log.save() assert monitor.last_diff_start == log @pytest.mark.django_db(transaction=True) def test___init__(self, monitor): assert isinstance(monitor.logs, models.LogQuerySet) assert isinstance(monitor.last_sound_logs, dict) @pytest.mark.django_db(transaction=True) def test_get_logs_queryset(self, monitor, station, sounds): query = monitor.get_logs_queryset() assert all(log.station_id == station.pk for log in query) @pytest.mark.django_db(transaction=True) def test_init_last_sound_logs(self, monitor, source, log): monitor.init_last_sound_logs() assert monitor.last_sound_logs[source.id] == log @pytest.mark.django_db(transaction=True) def test_monitor(self, monitor, source, log, sound): monitor.streamer.is_ready = True monitor.streamer.source = source interface( monitor, { "trace_sound": log, "trace_tracks": None, "handle_diffusions": None, "sync": None, }, ) monitor.monitor() assert monitor.streamer.calls.get("fetch") assert monitor.calls["trace_sound"] == ((source,), {}) assert monitor.calls["trace_tracks"] == ((log,), {}) assert monitor.calls["handle_diffusions"] assert monitor.calls["sync"] @pytest.mark.django_db(transaction=True) def test_monitor_streamer_not_ready(self, monitor): monitor.streamer.is_ready = False interface( monitor, { "trace_sound": log, "trace_tracks": None, "handle_diffusions": None, "sync": None, }, ) monitor.monitor() assert not monitor.streamer.calls.get("fetch") assert monitor.calls["trace_sound"] is None assert monitor.calls["trace_tracks"] is None assert not monitor.calls["handle_diffusions"] assert not monitor.calls["sync"] @pytest.mark.django_db(transaction=True) def test_monitor_no_source_uri(self, monitor, log): source.uri = None monitor.streamer.is_ready = True monitor.streamer.source = source interface( monitor, { "trace_sound": log, "trace_tracks": None, "handle_diffusions": None, "sync": None, }, ) monitor.monitor() assert monitor.streamer.calls.get("fetch") assert monitor.calls["trace_sound"] is None assert monitor.calls["trace_tracks"] is None assert monitor.calls["handle_diffusions"] assert monitor.calls["sync"] @pytest.mark.django_db(transaction=True) def test_trace_sound(self, monitor, diffusion, source, sound): monitor.last_sound_logs[source.id] = None result = monitor.trace_sound(source) assert result.type == models.Log.TYPE_ON_AIR assert result.source == source.id assert result.sound == sound assert result.diffusion == diffusion @pytest.mark.django_db(transaction=True) def test_trace_sound_returns_last_log(self, monitor, source, sound, log): log.sound = sound monitor.last_sound_logs[source.id] = log result = monitor.trace_sound(source) assert result == log @pytest.mark.django_db(transaction=True) def test_trace_tracks(self, monitor, log, tracks): interface(monitor, {"log": None}) for track in tracks: log.date = tz.now() - tz.timedelta(seconds=track.timestamp + 5) monitor.trace_tracks(log) assert monitor.calls["log"] log_by_track = [call[1].get("track") for call in monitor.calls["log"]] # only one call of log assert all(log_by_track.count(track) for track in tracks) @pytest.mark.django_db(transaction=True) def test_trace_tracks_returns_on_log_diffusion(self, monitor, log, diffusion, tracks): log.diffusion = None monitor.trace_tracks(log) @pytest.mark.django_db(transaction=True) def test_trace_tracks_returns_on_no_tracks_exists(self, monitor, log): log.diffusion = None monitor.trace_tracks(log) @pytest.mark.django_db(transaction=True) def test_handle_diffusions(self, monitor, streamer, diffusion, sound): interface( monitor, { "start_diff": None, "cancel_diff": None, }, ) streamer.dealer.queue = [1] streamer.dealer.rid = None streamer.dealer.remaining = monitor.delay.total_seconds() - 10 monitor.handle_diffusions() assert monitor.calls["start_diff"] == ( (streamer.dealer, diffusion), {}, ) assert not monitor.calls["cancel_diff"] @pytest.mark.django_db(transaction=True) def test_handle_diffusions_returns_on_diff(self, monitor, streamer, diffusion, log): interface( monitor, { "start_diff": None, "cancel_diff": None, }, ) streamer.dealer.queue = [1] streamer.dealer.rid = None streamer.dealer.remaining = monitor.delay.total_seconds() - 10 diffusion.start = tz.now() - tz.timedelta(minutes=30) diffusion.end = tz.now() - tz.timedelta(minutes=20) diffusion.save() monitor.handle_diffusions() assert not monitor.calls["start_diff"] assert not monitor.calls["cancel_diff"] @pytest.mark.django_db(transaction=True) def test_handle_diffusions_returns_on_diff_log_exists(self, monitor, streamer, diffusion, log): interface( monitor, { "start_diff": None, "cancel_diff": None, }, ) streamer.dealer.queue = [1] streamer.dealer.rid = None streamer.dealer.remaining = monitor.delay.total_seconds() - 10 log.diffusion = diffusion log.save() monitor.handle_diffusions() assert not monitor.calls["start_diff"] assert not monitor.calls["cancel_diff"] @pytest.mark.django_db(transaction=True) def test_handle_diffusions_cancel_diff(self, monitor, streamer, diffusion): interface( monitor, { "start_diff": None, "cancel_diff": None, }, ) streamer.dealer.queue = None streamer.dealer.rid = "13" streamer.dealer.remaining = monitor.delay.total_seconds() + 10 diffusion.start = tz.now() - monitor.cancel_timeout - tz.timedelta(seconds=30) diffusion.end = tz.now() + tz.timedelta(minutes=30) diffusion.save() monitor.handle_diffusions() assert not monitor.calls["start_diff"] assert monitor.calls["cancel_diff"] == ( (streamer.dealer, diffusion), {}, ) @pytest.mark.django_db(transaction=True) def test_log(self, monitor, source): log = monitor.log("source", type=models.Log.TYPE_START, comment="test") assert log.source == "source" assert log.type == models.Log.TYPE_START assert log.comment == "test" @pytest.mark.django_db(transaction=True) def test_start_diff(self, monitor, diffusion, source, episode, sound, tracks): result = {} monitor.log = lambda **kw: result.update(kw) monitor.start_diff(source, diffusion) assert source.calls["push"] == (sound.file.path,) assert result == { "type": models.Log.TYPE_START, "source": source.id, "diffusion": diffusion, "comment": str(diffusion), } @pytest.mark.django_db(transaction=True) def test_cancel_diff(self, monitor, source, diffusion): result = {} monitor.log = lambda **kw: result.update(kw) monitor.cancel_diff(source, diffusion) assert diffusion.type == models.Log.TYPE_CANCEL assert result == { "type": models.Log.TYPE_CANCEL, "source": source.id, "diffusion": diffusion, "comment": str(diffusion), } @pytest.mark.django_db(transaction=True) def test_sync(self, monitor): now = tz.now() monitor.sync_next = now - tz.timedelta(minutes=1) monitor.sync() assert monitor.sync_next >= now + monitor.sync_timeout assert all(source.calls.get("sync") for source in monitor.streamer.playlists) @pytest.mark.django_db(transaction=True) def test_sync_timeout_not_reached_skip_sync(self, monitor): monitor.sync_next = tz.now() + tz.timedelta(seconds=monitor.sync_timeout.total_seconds() + 20) monitor.sync() assert all(not source.calls.get("sync") for source in monitor.streamer.playlists)