def test_finish_date_frame_with_longer_than_specified_duration(self, mock_timezone, tested_date_frame,
                                                                   expected_error_code, tested_date_frame_length):
        mock_timezone.now.return_value = tested_date_frame_length

        with pytest.raises(ValidationError) as exc:
            finish_date_frame(date_frame_id=tested_date_frame.id)
        assert exc.value.messages[0] == DateFrameException.messages[expected_error_code]
    def test_finish_date_frame_with_start_greater_than_end(self, mock_datetime_now, tested_date_frame, task_instance):
        mock_datetime_now.now.return_value = get_time_delta({'minutes': 20}, ahead=False)

        with pytest.raises(ValidationError) as exc:
            finish_date_frame(date_frame_id=tested_date_frame.id)

        assert exc.value.messages[0] == DateFrameException.messages[DateFrameException.start_greater_than_end]
    def test_finish_date_frame_with_breaks_and_pauses_saves_proper_duration(self, mock_timezone,
                                                                            pomodoro_in_progress_with_breaks_and_pauses):
        mock_timezone.now.return_value = get_time_delta({'minutes': 25}, ahead=True)

        finish_date_frame(date_frame_id=pomodoro_in_progress_with_breaks_and_pauses.id)

        pomodoro_in_progress_with_breaks_and_pauses.refresh_from_db()

        break_frames = get_breaks_inside_date_frame(
            date_frame_object=pomodoro_in_progress_with_breaks_and_pauses,
            end=pomodoro_in_progress_with_breaks_and_pauses.end).values('start', 'end')

        breaks_duration = reduce(operator.add,
                                 (break_frame['end'] - break_frame['start'] for break_frame in break_frames),
                                 timedelta(0))

        pause_frames = get_pauses_inside_date_frame(
            date_frame_object=pomodoro_in_progress_with_breaks_and_pauses,
            end=pomodoro_in_progress_with_breaks_and_pauses.end).values('start', 'end')

        pauses_duration = reduce(operator.add,
                                 (pause_frame['end'] - pause_frame['start'] for pause_frame in pause_frames),
                                 timedelta(0))

        breaks_and_pauses_duration = breaks_duration + pauses_duration
        expected_duration = math.trunc(
            (pomodoro_in_progress_with_breaks_and_pauses.end - pomodoro_in_progress_with_breaks_and_pauses.start -
             breaks_and_pauses_duration).seconds / 60)

        assert round(breaks_and_pauses_duration.seconds) // 60 == 10
        assert pomodoro_in_progress_with_breaks_and_pauses.end is not None
        assert pomodoro_in_progress_with_breaks_and_pauses.duration == timedelta(minutes=expected_duration)
    def test_finish_date_frame_on_completed_task(self, tested_date_frame, task_instance):
        task_instance.status = 1
        task_instance.save()

        with pytest.raises(ValidationError) as exc:
            finish_date_frame(date_frame_id=tested_date_frame.id)
        assert exc.value.messages[0] == DateFrameException.messages[DateFrameException.task_already_completed]
    def test_finish_date_frame_fits_within_length_error_margin(self, mock_timezone, tested_date_frame,
                                                               normalized_date_frame_duration,
                                                               tested_date_frame_length):
        mock_timezone.now.return_value = tested_date_frame_length
        finish_date_frame(date_frame_id=tested_date_frame.id)
        tested_date_frame.refresh_from_db()

        assert tested_date_frame.end is not None
        assert tested_date_frame.duration == normalized_date_frame_duration
    def frame_finish(self, event):
        """
        Called in order to finish a date frame. If there are any colliding date frames, they will be
        finished immediately.
        In order to call the handler, the following data is expected by the main receive handler:

            - type: str pointing to frame_start handler,
            - date_frame_id: int corresponding to the date frame that was currently being processed

        Valid frame_type values:

            - 0 corresponds to pomodoro
            - 1 corresponds to break
            - 2 corresponds to pause
        """
        try:
            current_date_frame_id = event['content']['date_frame_id']
        except KeyError:
            self.send(text_data=json.dumps({
                'level':
                statuses.MESSAGE_LEVEL_CHOICES[statuses.LEVEL_TYPE_ERROR],
                'code':
                statuses.LEVEL_TYPE_ERROR,
                'action':
                statuses.MESSAGE_FRAME_ACTION_CHOICES[
                    statuses.FRAME_ACTION_ABORTED],
                'errors': {
                    'non_field_errors':
                    [statuses.ERROR_MESSAGES[statuses.ERROR_INCOMPLETE_DATA]]
                }
            }))
        else:
            finish_date_frame(date_frame_id=current_date_frame_id)

            self.send(text_data=json.dumps({
                'level':
                statuses.MESSAGE_LEVEL_CHOICES[statuses.LEVEL_TYPE_SUCCESS],
                'code':
                statuses.LEVEL_TYPE_SUCCESS,
                'action':
                statuses.MESSAGE_FRAME_ACTION_CHOICES[
                    statuses.FRAME_ACTION_FINISHED],
                'data': {
                    'date_frame_id': current_date_frame_id
                }
            }))
    def test_finish_date_frame_with_valid_end_datetime(self, tested_date_frame):
        finish_date_frame(date_frame_id=tested_date_frame.id)

        tested_date_frame.refresh_from_db()
        assert tested_date_frame.end is not None and tested_date_frame.duration is not None