0001-sms-raise-on-recoverable-errors-42230.patch
passerelle/apps/choosit/models.py | ||
---|---|---|
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 |
import json |
3 | 3 |
import requests |
4 | 4 | |
5 | 5 |
from django.utils.six import string_types |
6 | 6 |
from django.utils.translation import ugettext_lazy as _ |
7 | 7 |
from django.db import models |
8 | 8 | |
9 |
from passerelle.utils.jsonresponse import APIError |
|
9 |
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
|
|
10 | 10 |
from passerelle.base.models import SMSResource |
11 | 11 | |
12 | 12 | |
13 | 13 |
class ChoositSMSGateway(SMSResource): |
14 | 14 |
key = models.CharField(verbose_name=_('Key'), max_length=64) |
15 | 15 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
16 | 16 |
default=u'33') |
17 | 17 |
default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2, |
... | ... | |
86 | 86 |
'key': self.key, |
87 | 87 |
'recipient': dest, |
88 | 88 |
'content': text[:160], |
89 | 89 |
} |
90 | 90 |
data = {'data': json.dumps(params)} |
91 | 91 |
try: |
92 | 92 |
r = self.requests.post(self.URL, data=data) |
93 | 93 |
except requests.RequestException as e: |
94 |
results.append('Choosit error: %s' % e)
|
|
94 |
raise APIRecoverableError('Choosit error: POST failed, %s' % e)
|
|
95 | 95 |
else: |
96 | 96 |
try: |
97 | 97 |
output = r.json() |
98 | 98 |
except ValueError as e: |
99 | 99 |
results.append('Choosit error: bad JSON response') |
100 | 100 |
else: |
101 | 101 |
if not isinstance(output, dict): |
102 | 102 |
results.append('Choosit error: JSON response is not a dict %r' % output) |
passerelle/apps/mobyt/models.py | ||
---|---|---|
1 | 1 |
from django.utils.translation import ugettext_lazy as _ |
2 | 2 |
from django.db import models |
3 | 3 | |
4 | 4 |
import requests |
5 | 5 | |
6 |
from passerelle.utils.jsonresponse import APIError |
|
6 |
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
|
|
7 | 7 |
from passerelle.base.models import SMSResource |
8 | 8 | |
9 | 9 | |
10 | 10 |
class MobytSMSGateway(SMSResource): |
11 | 11 |
URL = 'http://multilevel.mobyt.fr/sms/batch.php' |
12 | 12 |
MESSAGES_QUALITIES = ( |
13 | 13 |
('l', _('sms direct')), |
14 | 14 |
('ll', _('sms low-cost')), |
... | ... | |
61 | 61 |
'rcpt': rcpt, |
62 | 62 |
'data': text, |
63 | 63 |
'sender': sender, |
64 | 64 |
'qty': self.quality, |
65 | 65 |
} |
66 | 66 |
try: |
67 | 67 |
r = self.requests.post(self.URL, data=params) |
68 | 68 |
except requests.RequestException as e: |
69 |
raise APIError('MobyT error: POST failed, %s' % e) |
|
69 |
raise APIRecoverableError('MobyT error: POST failed, %s' % e)
|
|
70 | 70 |
if r.content[:2] != "OK": |
71 | 71 |
raise APIError('MobyT error: response is not "OK"') |
72 | 72 |
return None |
passerelle/apps/orange/models.py | ||
---|---|---|
14 | 14 |
# |
15 | 15 |
# This program is distributed in the hope that it will be useful, |
16 | 16 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | 17 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | 18 |
# GNU Affero General Public License for more details. |
19 | 19 |
# |
20 | 20 |
# You should have received a copy of the GNU Affero General Public License |
21 | 21 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
22 |
import requests |
|
23 | ||
22 | 24 |
from django.db import models |
23 | 25 |
from django.utils.translation import ugettext_lazy as _ |
24 | 26 | |
25 | 27 |
from passerelle.base.models import SMSResource |
26 |
from passerelle.utils.jsonresponse import APIError |
|
28 |
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
|
|
27 | 29 | |
28 | 30 |
BASE_API = 'https://contact-everyone.orange-business.com/api/v1.2/' |
29 | 31 |
URL_TOKEN = BASE_API + 'oauth/token' |
30 | 32 |
URL_GROUPS = BASE_API + 'groups' |
31 | 33 |
URL_DIFFUSION = BASE_API + 'groups/%s/diffusion-requests' |
32 | 34 | |
33 | 35 | |
34 | 36 |
class OrangeError(APIError): |
... | ... | |
105 | 107 |
raise OrangeError('Orange fails to send SMS: %s, %s' % ( |
106 | 108 |
response.status_code, response.text)) |
107 | 109 |
return get_json(response) |
108 | 110 | |
109 | 111 |
def send_msg(self, text, sender, destinations, **kwargs): |
110 | 112 |
'''Send a SMS using the Orange provider''' |
111 | 113 |
destinations = self.clean_numbers( |
112 | 114 |
destinations, self.default_country_code, self.default_trunk_prefix) |
113 |
access_token = self.get_access_token() |
|
114 |
group_id = self.group_id_from_name(access_token) |
|
115 |
response = self.diffusion(access_token, group_id, destinations, text) |
|
115 |
try: |
|
116 |
access_token = self.get_access_token() |
|
117 |
group_id = self.group_id_from_name(access_token) |
|
118 |
response = self.diffusion(access_token, group_id, destinations, text) |
|
119 |
except requests.RequestException as exc: |
|
120 |
raise APIRecoverableError('Orange error: POST failed, %s' % exc) |
|
116 | 121 |
return response |
passerelle/apps/ovh/models.py | ||
---|---|---|
1 | 1 |
import requests |
2 | 2 | |
3 | 3 |
from django.utils.translation import ugettext_lazy as _ |
4 | 4 |
from django.db import models |
5 | 5 |
from django.utils.encoding import force_text |
6 | 6 | |
7 |
from passerelle.utils.jsonresponse import APIError |
|
7 |
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
|
|
8 | 8 |
from passerelle.base.models import SMSResource |
9 | 9 | |
10 | 10 | |
11 | 11 |
class OVHSMSGateway(SMSResource): |
12 | 12 |
URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi' |
13 | 13 |
MESSAGES_CLASSES = ( |
14 | 14 |
(0, _('Message are directly shown to users on phone screen ' |
15 | 15 |
'at reception. The message is never stored, neither in the ' |
... | ... | |
98 | 98 |
'contentType': 'text/json', |
99 | 99 |
'class': self.msg_class, |
100 | 100 |
} |
101 | 101 |
if not kwargs['stop']: |
102 | 102 |
params.update({'noStop': 1}) |
103 | 103 |
try: |
104 | 104 |
response = self.requests.post(self.URL, data=params) |
105 | 105 |
except requests.RequestException as e: |
106 |
raise APIError('OVH error: POST failed, %s' % e) |
|
106 |
raise APIRecoverableError('OVH error: POST failed, %s' % e)
|
|
107 | 107 |
else: |
108 | 108 |
try: |
109 | 109 |
result = response.json() |
110 | 110 |
except ValueError as e: |
111 | 111 |
raise APIError('OVH error: bad JSON response') |
112 | 112 |
else: |
113 | 113 |
if not isinstance(result, dict): |
114 | 114 |
raise APIError('OVH error: bad JSON response %r, it should be a dictionnary' % |
passerelle/apps/oxyd/models.py | ||
---|---|---|
1 | 1 |
import requests |
2 | 2 | |
3 | 3 |
from django.db import models |
4 | 4 |
from django.utils.encoding import force_text |
5 | 5 | |
6 |
from passerelle.utils.jsonresponse import APIError |
|
6 |
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
|
|
7 | 7 |
from passerelle.base.models import SMSResource |
8 | 8 |
from django.utils.translation import ugettext_lazy as _ |
9 | 9 | |
10 | 10 | |
11 | 11 |
class OxydSMSGateway(SMSResource): |
12 | 12 |
username = models.CharField(verbose_name=_('Username'), max_length=64) |
13 | 13 |
password = models.CharField(verbose_name=_('Password'), max_length=64) |
14 | 14 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
... | ... | |
64 | 64 |
'pass': self.password, |
65 | 65 |
'num': dest, |
66 | 66 |
'sms': text.encode('utf-8'), |
67 | 67 |
'flash': '0', |
68 | 68 |
} |
69 | 69 |
try: |
70 | 70 |
r = self.requests.post(self.URL, params) |
71 | 71 |
except requests.RequestException as e: |
72 |
results.append('OXYD error: POST failed, %s' % e)
|
|
72 |
raise APIRecoverableError('OXYD error: POST failed, %s' % e)
|
|
73 | 73 |
else: |
74 | 74 |
code = r.content and r.content.split()[0] |
75 | 75 |
if force_text(code) != '200': |
76 | 76 |
results.append('OXYD error: response is not 200') |
77 | 77 |
else: |
78 | 78 |
results.append(0) |
79 | 79 |
if any(results): |
80 | 80 |
raise APIError( |
passerelle/utils/jsonresponse.py | ||
---|---|---|
35 | 35 |
log_error = False |
36 | 36 |
http_status = 200 |
37 | 37 | |
38 | 38 |
def __init__(self, *args, **kwargs): |
39 | 39 |
self.__dict__.update(kwargs) |
40 | 40 |
super(APIError, self).__init__(*args) |
41 | 41 | |
42 | 42 | |
43 |
class APIRecoverableError(APIError): |
|
44 |
''''Exception to raise when call on remote application should be retry later''' |
|
45 |
err = 2 |
|
46 | ||
47 | ||
43 | 48 |
class JSONEncoder(DjangoJSONEncoder): |
44 | 49 |
def default(self, o): |
45 | 50 |
if isinstance(o, time.struct_time): |
46 | 51 |
o = datetime.datetime(*tuple(o)[:6]) |
47 | 52 |
return super(JSONEncoder, self).default(o) |
48 | 53 | |
49 | 54 | |
50 | 55 |
class to_json(object): |
tests/test_orange.py | ||
---|---|---|
11 | 11 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | 12 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | 13 |
# GNU Affero General Public License for more details. |
14 | 14 |
# |
15 | 15 |
# You should have received a copy of the GNU Affero General Public License |
16 | 16 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | 17 | |
18 | 18 |
import json |
19 |
import requests |
|
19 | 20 | |
20 | 21 |
import httmock |
21 | 22 |
import pytest |
22 | 23 | |
23 | 24 |
from django.contrib.contenttypes.models import ContentType |
24 | 25 |
from django.utils.encoding import force_text |
25 | 26 | |
26 | 27 |
from passerelle.apps.orange.models import OrangeSMSGateway, OrangeError |
27 | 28 |
from passerelle.base.models import ApiUser, AccessRight |
28 |
from passerelle.utils.jsonresponse import APIError |
|
29 |
from passerelle.utils.jsonresponse import APIError, APIRecoverableError
|
|
29 | 30 | |
30 | 31 | |
31 | 32 |
NETLOC = 'contact-everyone.orange-business.com' |
32 | 33 |
JSON_HEADERS = {'content-type': 'application/json'} |
33 | 34 |
PAYLOAD = { |
34 | 35 |
'message': 'hello', |
35 | 36 |
'from': '+33699999999', |
36 | 37 |
'to': ['+33688888888', '+33677777777'], |
... | ... | |
65 | 66 |
@httmock.urlmatch(netloc=NETLOC) |
66 | 67 |
def response_500(url, request): |
67 | 68 |
return httmock.response(500, 'my_error') |
68 | 69 | |
69 | 70 |
@httmock.urlmatch(netloc=NETLOC) |
70 | 71 |
def response_invalid_json(url, request): |
71 | 72 |
return httmock.response(200, 'not a JSON content') |
72 | 73 | |
74 |
@httmock.urlmatch(netloc=NETLOC) |
|
75 |
def request_exception(url, request): |
|
76 |
raise requests.RequestException('my_error') |
|
77 | ||
73 | 78 |
def setup_access_rights(obj): |
74 | 79 |
api = ApiUser.objects.create(username='all', |
75 | 80 |
keytype='', key='') |
76 | 81 |
obj_type = ContentType.objects.get_for_model(obj) |
77 | 82 |
AccessRight.objects.create(codename='can_send_messages', apiuser=api, |
78 | 83 |
resource_type=obj_type, resource_pk=obj.pk) |
79 | 84 |
return obj |
80 | 85 | |
... | ... | |
168 | 173 |
assert not resp.json['err'] |
169 | 174 |
assert resp.json['data']['status'] == "I'm ok" |
170 | 175 | |
171 | 176 |
# not 201 |
172 | 177 |
with httmock.HTTMock(response_token_ok, response_group_ok, response_500): |
173 | 178 |
resp = app.post_json(url, params=PAYLOAD, status=200) |
174 | 179 |
assert resp.json['err'] |
175 | 180 |
assert resp.json['err_desc'] == 'Orange fails to send SMS: 500, my_error' |
181 | ||
182 |
# RequestException |
|
183 |
with httmock.HTTMock(request_exception): |
|
184 |
resp = app.post_json(url, params=PAYLOAD, status=200) |
|
185 |
assert resp.json['err'] |
|
186 |
assert resp.json['err_desc'] == 'Orange error: POST failed, my_error' |
|
187 |
assert 'APIRecoverableError' in resp.json['err_class'] |
|
176 |
- |