<reponame>ProzorroUKR/prozorro_chronograph<gh_stars>0
from uuid import uuid4
from datetime import datetime, timedelta
from unittest.mock import patch, MagicMock, AsyncMock
from freezegun import freeze_time
import json

from prozorro_chronograph.utils import get_now
from prozorro_chronograph.scheduler import recheck_tender, push
from prozorro_chronograph.settings import TZ, URL_SUFFIX, BASE_URL, SMOOTHING_MAX, SMOOTHING_MIN, INVALID_STATUSES

from .base import BaseTenderTest


@freeze_time("2012-01-14")
class TestRecheckTender(BaseTenderTest):
    @patch("prozorro_chronograph.scheduler.randint", return_value=2)
    @patch("prozorro_chronograph.scheduler.scheduler.add_job")
    async def test_recheck_tender_with_patch_200_next_check_past(self,  mock_add_job, mock_randint):
        tender_id = uuid4().hex
        now_minus_1s = (get_now() - timedelta(seconds=1)).isoformat()
        response = MagicMock(
            status=200,
            text=AsyncMock(return_value=json.dumps({"data": {"next_check": now_minus_1s}}))
        )
        with patch("prozorro_chronograph.scheduler.SESSION.patch", AsyncMock(return_value=response)):
            recheck_tender_return = await recheck_tender(tender_id)
        mock_randint.assert_called_once_with(SMOOTHING_MIN, SMOOTHING_MAX)
        mock_add_job.assert_called_once_with(
            push,
            "date",
            run_date=get_now() + timedelta(seconds=2),
            timezone=TZ,
            id=f"recheck_{tender_id}",
            name=f"Recheck {tender_id}",
            misfire_grace_time=60 * 60,
            replace_existing=True,
            args=["recheck", tender_id],
        )
        assert recheck_tender_return == now_minus_1s

    @patch("prozorro_chronograph.scheduler.randint", return_value=2)
    @patch("prozorro_chronograph.scheduler.scheduler.add_job")
    async def test_recheck_tender_with_patch_200_next_check_future(self, mock_add_job, mock_randint):
        tender_id = uuid4().hex
        now_plus_1s = (datetime.now(TZ) + timedelta(seconds=1)).isoformat()
        response = MagicMock(
            status=200,
            text=AsyncMock(return_value=json.dumps({"data": {"next_check": now_plus_1s}}))
        )
        with patch("prozorro_chronograph.scheduler.SESSION.patch", AsyncMock(return_value=response)):
            recheck_tender_return = await recheck_tender(tender_id)
        mock_randint.assert_called_once_with(SMOOTHING_MIN, SMOOTHING_MAX)
        mock_add_job.assert_called_once_with(
            push,
            "date",
            run_date=get_now() + timedelta(seconds=1) + timedelta(seconds=2),
            timezone=TZ,
            id=f"recheck_{tender_id}",
            name=f"Recheck {tender_id}",
            misfire_grace_time=60 * 60,
            replace_existing=True,
            args=["recheck", tender_id],
        )
        assert recheck_tender_return == now_plus_1s

    @patch("prozorro_chronograph.scheduler.randint", return_value=2)
    @patch("prozorro_chronograph.scheduler.scheduler.add_job")
    @patch("prozorro_chronograph.scheduler.LOGGER.error")
    async def test_recheck_tender_with_patch_429(self, mock_logger_error, mock_add_job, mock_randint):
        tender_id = uuid4().hex
        url = f"{BASE_URL}/{tender_id}{URL_SUFFIX}"
        response = MagicMock(status=429, text=AsyncMock(return_value="text_patch_response"))
        with patch("prozorro_chronograph.scheduler.SESSION.patch", AsyncMock(return_value=response)):
            recheck_tender_return = await recheck_tender(tender_id)
        mock_logger_error.assert_called_once_with(
            f"Error too many requests {response.status} on getting tender '{url}': text_patch_response")
        mock_randint.assert_called_once_with(SMOOTHING_MIN, SMOOTHING_MAX)
        mock_add_job.assert_called_once_with(
            push,
            "date",
            run_date=get_now() + timedelta(minutes=1) + timedelta(seconds=2),
            timezone=TZ,
            id=f"recheck_{tender_id}",
            name=f"Recheck {tender_id}",
            misfire_grace_time=60 * 60,
            replace_existing=True,
            args=["recheck", tender_id],
        )
        assert recheck_tender_return == (get_now() + timedelta(minutes=1)).isoformat()

    @patch("prozorro_chronograph.scheduler.randint", return_value=2)
    @patch("prozorro_chronograph.scheduler.scheduler.add_job")
    @patch("prozorro_chronograph.scheduler.LOGGER.error")
    async def test_recheck_tender_with_patch_409(self, mock_logger_error, mock_add_job, mock_randint):
        tender_id = uuid4().hex
        url = f"{BASE_URL}/{tender_id}{URL_SUFFIX}"
        response = MagicMock(status=409, text=AsyncMock(return_value="text_patch_response"))
        with patch("prozorro_chronograph.scheduler.SESSION.patch", AsyncMock(return_value=response)):
            recheck_tender_return = await recheck_tender(tender_id)
        mock_logger_error.assert_called_once_with(
            f"Error {response.status} on checking tender '{url}': text_patch_response")
        mock_randint.assert_called_once_with(SMOOTHING_MIN, SMOOTHING_MAX)
        mock_add_job.assert_called_once_with(
            push,
            "date",
            run_date=get_now() + timedelta(minutes=1) + timedelta(seconds=2),
            timezone=TZ,
            id=f"recheck_{tender_id}",
            name=f"Recheck {tender_id}",
            misfire_grace_time=60 * 60,
            replace_existing=True,
            args=["recheck", tender_id],
        )
        assert recheck_tender_return == (get_now() + timedelta(minutes=1)).isoformat()

    @patch("prozorro_chronograph.scheduler.LOGGER")
    async def test_recheck_tender_with_patch_422_and_get_200_with_invalid_statuses(self, mock_logger):
        tender_id = uuid4().hex
        url = f"{BASE_URL}/{tender_id}{URL_SUFFIX}"
        patch_response = MagicMock(status=422, text=AsyncMock(return_value="text_patch_response"))
        with patch("prozorro_chronograph.scheduler.SESSION.patch", AsyncMock(return_value=patch_response)):
            get_response = MagicMock(
                status=200,
                text=AsyncMock(return_value=json.dumps(
                    {"data": {
                        "id": tender_id,
                        "status": INVALID_STATUSES[0]
                    }})))
            with patch("prozorro_chronograph.scheduler.SESSION.get", AsyncMock(return_value=get_response)):
                recheck_tender_return = await recheck_tender(tender_id)
        mock_logger.error.assert_called_once_with(
            f"Error {patch_response.status} on checking tender '{url}': text_patch_response")
        mock_logger.info.assert_called_once_with(
            f"Next check won't be set for tender {tender_id} with status {INVALID_STATUSES[0]}")
        assert recheck_tender_return is None

    @patch("prozorro_chronograph.scheduler.randint", return_value=2)
    @patch("prozorro_chronograph.scheduler.scheduler.add_job")
    @patch("prozorro_chronograph.scheduler.LOGGER.error")
    async def test_recheck_tender_with_patch_422_and_get_200_with_not_invalid_status(self, mock_logger_error, mock_add_job, mock_randint):
        tender_id = uuid4().hex
        url = f"{BASE_URL}/{tender_id}{URL_SUFFIX}"
        patch_response = MagicMock(status=422, text=AsyncMock(return_value="text_patch_response"))
        with patch("prozorro_chronograph.scheduler.SESSION.patch", AsyncMock(return_value=patch_response)):
            get_response = MagicMock(
                status=200,
                text=AsyncMock(return_value=json.dumps(
                    {"data": {
                        "id": tender_id,
                        "status": "successful"
                    }})))
            with patch("prozorro_chronograph.scheduler.SESSION.get", AsyncMock(return_value=get_response)):
                recheck_tender_return = await recheck_tender(tender_id)
        mock_logger_error.assert_called_once_with(f"Error {patch_response.status} on checking tender '{url}': text_patch_response")
        mock_randint.assert_called_once_with(SMOOTHING_MIN, SMOOTHING_MAX)
        mock_add_job.assert_called_once_with(
            push,
            "date",
            run_date=get_now() + timedelta(minutes=1) + timedelta(seconds=2),
            timezone=TZ,
            id=f"recheck_{tender_id}",
            name=f"Recheck {tender_id}",
            misfire_grace_time=60 * 60,
            replace_existing=True,
            args=["recheck", tender_id], )
        assert recheck_tender_return == (get_now() + timedelta(minutes=1)).isoformat()

    @patch("prozorro_chronograph.scheduler.LOGGER.error")
    async def test_recheck_tender_with_patch_422_and_get_404(self, mock_logger_error):
        tender_id = uuid4().hex
        url = f"{BASE_URL}/{tender_id}{URL_SUFFIX}"
        patch_response = MagicMock(status=422, text=AsyncMock(return_value="text_patch_response"))
        with patch("prozorro_chronograph.scheduler.SESSION.patch", AsyncMock(return_value=patch_response)):
            get_response = MagicMock(status=404, text=AsyncMock(return_value="text_get_response"))
            with patch("prozorro_chronograph.scheduler.SESSION.get", AsyncMock(return_value=get_response)):
                recheck_tender_return = await recheck_tender(tender_id)
        mock_logger_error.assert_called_once_with(
            f"Error {patch_response.status} on checking tender '{url}': text_patch_response")
        assert recheck_tender_return == (get_now() + timedelta(minutes=1)).isoformat()
