finish writing controllers.monitor tests
This commit is contained in:
		@ -66,7 +66,7 @@ class Monitor:
 | 
			
		||||
            "diffusion", "sound", "track"
 | 
			
		||||
        ).order_by("-pk")
 | 
			
		||||
 | 
			
		||||
    def init_last_sound_logs(self, key=None):
 | 
			
		||||
    def init_last_sound_logs(self):
 | 
			
		||||
        """Retrieve last logs and initialize `last_sound_logs`"""
 | 
			
		||||
        logs = {}
 | 
			
		||||
        for source in self.streamer.sources:
 | 
			
		||||
@ -159,7 +159,7 @@ class Monitor:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        tracks = Track.objects.filter(
 | 
			
		||||
            sound__id=log.sound_id, timestamp__isnull=False
 | 
			
		||||
            sound_id=log.sound_id, timestamp__isnull=False
 | 
			
		||||
        ).order_by("timestamp")
 | 
			
		||||
        if not tracks.exists():
 | 
			
		||||
            return
 | 
			
		||||
@ -169,15 +169,14 @@ class Monitor:
 | 
			
		||||
        now = tz.now()
 | 
			
		||||
        for track in tracks:
 | 
			
		||||
            pos = log.date + tz.timedelta(seconds=track.timestamp)
 | 
			
		||||
            if pos > now:
 | 
			
		||||
                break
 | 
			
		||||
            self.log(
 | 
			
		||||
                type=Log.TYPE_ON_AIR,
 | 
			
		||||
                date=pos,
 | 
			
		||||
                source=log.source,
 | 
			
		||||
                track=track,
 | 
			
		||||
                comment=track,
 | 
			
		||||
            )
 | 
			
		||||
            if pos <= now:
 | 
			
		||||
                self.log(
 | 
			
		||||
                    type=Log.TYPE_ON_AIR,
 | 
			
		||||
                    date=pos,
 | 
			
		||||
                    source=log.source,
 | 
			
		||||
                    track=track,
 | 
			
		||||
                    comment=track,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    def handle_diffusions(self):
 | 
			
		||||
        """Handle scheduled diffusion, trigger if needed, preload playlists and
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,25 @@ local_tz = tzlocal.get_localzone()
 | 
			
		||||
working_dir = os.path.join(os.path.dirname(__file__), "working_dir")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def interface(self, obj, funcs):
 | 
			
		||||
def interface_wrap(obj, attr, value):
 | 
			
		||||
    if not isinstance(getattr(obj, "calls", None), dict):
 | 
			
		||||
        obj.calls = {}
 | 
			
		||||
    obj.calls[attr] = None
 | 
			
		||||
 | 
			
		||||
    def wrapper(*a, **kw):
 | 
			
		||||
        call = obj.calls.get(attr)
 | 
			
		||||
        if call is None:
 | 
			
		||||
            obj.calls[attr] = (a, kw)
 | 
			
		||||
        elif isinstance(call, tuple):
 | 
			
		||||
            obj.calls[attr] = [call, (a, kw)]
 | 
			
		||||
        else:
 | 
			
		||||
            call.append((a, kw))
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
    setattr(obj, attr, wrapper)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def interface(obj, funcs):
 | 
			
		||||
    """Override provided object's functions using dict of funcs, as ``{
 | 
			
		||||
    func_name: return_value}``.
 | 
			
		||||
 | 
			
		||||
@ -26,23 +44,8 @@ def interface(self, obj, funcs):
 | 
			
		||||
    with all call done using those methods, as
 | 
			
		||||
    ``{func_name: (args, kwargs)}``.
 | 
			
		||||
    """
 | 
			
		||||
    if not isinstance(getattr(obj, "calls", None), dict):
 | 
			
		||||
        obj.calls = {}
 | 
			
		||||
 | 
			
		||||
    for attr, value in funcs.items():
 | 
			
		||||
 | 
			
		||||
        def func(*a, **kw):
 | 
			
		||||
            call = obj.calls.get(attr)
 | 
			
		||||
            if call is None:
 | 
			
		||||
                obj.calls[attr] = (a, kw)
 | 
			
		||||
            elif isinstance(call, tuple):
 | 
			
		||||
                obj.calls[attr] = [call, (a, kw)]
 | 
			
		||||
            else:
 | 
			
		||||
                call.append((a, kw))
 | 
			
		||||
            return value
 | 
			
		||||
 | 
			
		||||
        obj.calls[attr] = None
 | 
			
		||||
        setattr(obj, attr, func)
 | 
			
		||||
        interface_wrap(obj, attr, value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeSocket:
 | 
			
		||||
@ -172,15 +175,12 @@ def stream(program):
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def episode(program):
 | 
			
		||||
    episode = baker.make(models.Episode, title="test episode", program=program)
 | 
			
		||||
    episode.playlist = lambda: ["/tmp/a", "/tmp/b"]
 | 
			
		||||
    return episode
 | 
			
		||||
    return baker.make(models.Episode, title="test episode", program=program)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def sound(program, episode):
 | 
			
		||||
    return baker.make(
 | 
			
		||||
        models.Sound,
 | 
			
		||||
    sound = models.Sound(
 | 
			
		||||
        program=program,
 | 
			
		||||
        episode=episode,
 | 
			
		||||
        name="sound",
 | 
			
		||||
@ -188,6 +188,8 @@ def sound(program, episode):
 | 
			
		||||
        position=0,
 | 
			
		||||
        file="sound.mp3",
 | 
			
		||||
    )
 | 
			
		||||
    sound.save(check=False)
 | 
			
		||||
    return sound
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
@ -266,6 +268,7 @@ def metadata_string(metadata_data):
 | 
			
		||||
# -- streamers
 | 
			
		||||
class FakeStreamer(controllers.Streamer):
 | 
			
		||||
    calls = {}
 | 
			
		||||
    is_ready = False
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        self.__dict__.update(**kwargs)
 | 
			
		||||
@ -311,7 +314,7 @@ class FakeQueueSource(FakeSource, controllers.QueueSource):
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def streamer(station, station_ports):
 | 
			
		||||
    streamer = FakeStreamer(station)
 | 
			
		||||
    streamer = FakeStreamer(station=station)
 | 
			
		||||
    streamer.sources = [
 | 
			
		||||
        FakePlaylist(i, uri=f"source-{i}") for i in range(0, 3)
 | 
			
		||||
    ]
 | 
			
		||||
@ -324,7 +327,7 @@ def streamers(stations, stations_ports):
 | 
			
		||||
    streamers = controllers.Streamers(streamer_class=FakeStreamer)
 | 
			
		||||
    # avoid unecessary db calls
 | 
			
		||||
    streamers.streamers = {
 | 
			
		||||
        station.pk: FakeStreamer(station) for station in stations
 | 
			
		||||
        station.pk: FakeStreamer(station=station) for station in stations
 | 
			
		||||
    }
 | 
			
		||||
    for j, streamer in enumerate(streamers.values()):
 | 
			
		||||
        streamer.sources = [
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										251
									
								
								aircox_streamer/tests/test_controllers_monitor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								aircox_streamer/tests/test_controllers_monitor.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,251 @@
 | 
			
		||||
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):
 | 
			
		||||
    return baker.make(
 | 
			
		||||
        models.Diffusion,
 | 
			
		||||
        program=program,
 | 
			
		||||
        episode=episode,
 | 
			
		||||
        start=tz.now() - tz.timedelta(minutes=10),
 | 
			
		||||
        end=tz.now() + tz.timedelta(minutes=30),
 | 
			
		||||
        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):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @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):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @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
 | 
			
		||||
        )
 | 
			
		||||
		Reference in New Issue
	
	Block a user