From cf31b15cd604fc5e889ea725108724f7b3598b7e Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Thu, 2 Sep 2021 16:12:19 +0200
Subject: [PATCH] ovh: retry on too many requests error (#44730)

---
 passerelle/apps/ovh/models.py |  4 ++++
 passerelle/sms/views.py       |  3 ++-
 tests/test_sms.py             | 35 +++++++++++++++++++++++++++++++++++
 3 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/passerelle/apps/ovh/models.py b/passerelle/apps/ovh/models.py
index d3afd58b..e94180f8 100644
--- a/passerelle/apps/ovh/models.py
+++ b/passerelle/apps/ovh/models.py
@@ -1,24 +1,26 @@
 import hashlib
 import json
+import random
 import time
 from datetime import timedelta
 from urllib.parse import urljoin
 
 import requests
 from django.conf import settings
 from django.contrib.postgres.fields import ArrayField
 from django.core.mail import send_mail
 from django.db import models
 from django.template.loader import render_to_string
 from django.utils import timezone
 from django.utils.encoding import force_text
 from django.utils.translation import ugettext_lazy as _
 
+from passerelle.base.models import SkipJob
 from passerelle.sms.models import SMSResource
 from passerelle.utils.jsonresponse import APIError
 
 
 class OVHSMSGateway(SMSResource):
     documentation_url = (
         'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/'
     )
@@ -275,10 +277,12 @@ class OVHSMSGateway(SMSResource):
                             self.slug,
                             credit_left,
                             self.credit_threshold_alert,
                         )
                     ret['credit_left'] = credit_left
                     ret['ovh_result'] = result
                     ret['sms_ids'] = result.get('SmsIds', [])
                     return ret
+                elif result['status'] == 429 and 'Please retry' in result.get('message'):
+                    raise SkipJob(after_timestamp=random.randint(4, 10))
                 else:
                     raise APIError('OVH error: %r' % result)
diff --git a/passerelle/sms/views.py b/passerelle/sms/views.py
index a818a91c..10acbe75 100644
--- a/passerelle/sms/views.py
+++ b/passerelle/sms/views.py
@@ -4,16 +4,17 @@ from django.apps import apps
 from django.contrib import messages
 from django.db.models import Count
 from django.db.models.functions import TruncDay
 from django.http import JsonResponse
 from django.utils.timezone import make_aware
 from django.utils.translation import ugettext_lazy as _
 from django.views.generic import FormView, View
 
+from passerelle.base.models import SkipJob
 from passerelle.utils.jsonresponse import APIError
 from passerelle.views import GenericConnectorMixin
 
 from .forms import SmsTestSendForm
 from .models import SMSLog
 
 
 class SmsTestSendView(GenericConnectorMixin, FormView):
@@ -33,17 +34,17 @@ class SmsTestSendView(GenericConnectorMixin, FormView):
         number = form.cleaned_data['number']
         sender = form.cleaned_data['sender']
         message = form.cleaned_data['message']
         connector = self.get_object()
         try:
             number = connector.clean_numbers([number])[0]
             number = connector.authorize_numbers([number])[0][0]
             connector.send_msg(text=message, sender=sender, destinations=[number], stop=False)
-        except APIError as exc:
+        except (APIError, SkipJob) as exc:
             messages.error(self.request, _('Sending SMS fails: %s' % exc))
         else:
             messages.success(self.request, _('An SMS was just sent'))
         return super(SmsTestSendView, self).form_valid(form)
 
 
 class SmsStatisticsView(View):
     def get(self, request, *args, **kwargs):
diff --git a/tests/test_sms.py b/tests/test_sms.py
index 334cfedf..bbe7d253 100644
--- a/tests/test_sms.py
+++ b/tests/test_sms.py
@@ -173,16 +173,51 @@ def test_connectors(app, connector, freezer):
             assert test_vector['result']['err_desc'] in job.status_details['error_summary']
             nb_failed += 1
         else:
             assert job.status == 'completed'
     assert Job.objects.filter(method_name='send_job').count() == total
     assert SMSLog.objects.count() == total - nb_failed
 
 
+@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
+def test_sms_legacy_retry_after_error(app, connector, freezer):
+    path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
+    assert not SMSLog.objects.filter(appname=connector.get_connector_slug(), slug=connector.slug).exists()
+
+    payload = {
+        'message': 'plop',
+        'from': '+33699999999',
+        'to': ['+33688888888'],
+    }
+    freezer.move_to('2019-01-01 00:00:00')
+    result = app.post_json(path, params=payload)
+    assert result.json['err'] == 0
+    assert Job.objects.exists()
+    assert Job.objects.get().status == 'registered'
+    assert not Job.objects.get().after_timestamp
+    with utils.mock_url(
+        url=connector.URL,
+        response={'status': 429, 'message': 'Too much requests.\nPlease retry in 3 seconds.'},
+    ):
+        connector.jobs()
+    assert Job.objects.get().status == 'registered'
+    assert Job.objects.get().after_timestamp
+
+    with utils.mock_url(url=connector.URL, response={'status': 100, 'credit_left': 22}):
+        connector.jobs()
+    assert Job.objects.get().status == 'registered'
+
+    freezer.move_to('2019-01-01 00:00:11')
+    with utils.mock_url(url=connector.URL, response={'status': 100, 'creditLeft': 22}):
+        connector.jobs()
+    assert Job.objects.get().status == 'completed'
+    assert SMSLog.objects.exists()
+
+
 def test_manage_views(admin_user, app, connector):
     url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
     resp = app.get(url)
     assert 'Endpoints' in resp.text
     assert not 'accessright/add' in resp.text
     app = login(app)
     resp = app.get(url)
     description_fields = [
-- 
2.30.2

