0001-ovh-update-credit-left-42921.patch
passerelle/apps/ovh/migrations/0010_auto_20201008_1126.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2020-10-08 09:26 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('ovh', '0009_auto_20200730_1047'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AlterField( |
|
16 |
model_name='ovhsmsgateway', |
|
17 |
name='credit_left', |
|
18 |
field=models.PositiveIntegerField(default=0, editable=False, verbose_name='Credit left'), |
|
19 |
), |
|
20 |
] |
passerelle/apps/ovh/models.py | ||
---|---|---|
2 | 2 |
import json |
3 | 3 |
import requests |
4 | 4 |
import time |
5 |
from urllib.parse import urljoin |
|
5 | 6 | |
6 | 7 |
from django.db import models |
7 | 8 |
from django.utils.encoding import force_text |
... | ... | |
13 | 14 | |
14 | 15 |
class OVHSMSGateway(SMSResource): |
15 | 16 |
documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/' |
16 |
hide_description_fields = ['account'] |
|
17 |
API_URL = 'https://eu.api.ovh.com/1.0/sms/%(serviceName)s/users/%(login)s/jobs/'
|
|
17 |
hide_description_fields = ['account', 'credit_left']
|
|
18 |
API_URL = 'https://eu.api.ovh.com/1.0/sms/%(serviceName)s/' |
|
18 | 19 |
URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi' |
19 | 20 |
MESSAGES_CLASSES = ( |
20 | 21 |
(0, _('Message are directly shown to users on phone screen ' |
... | ... | |
69 | 70 |
verbose_name=_('Message class')) |
70 | 71 |
credit_threshold_alert = models.PositiveIntegerField(verbose_name=_('Credit alert threshold'), |
71 | 72 |
default=100) |
72 |
credit_left = models.PositiveIntegerField(verbose_name=_('Credit left'), default=0) |
|
73 |
credit_left = models.PositiveIntegerField(verbose_name=_('Credit left'), default=0, editable=False)
|
|
73 | 74 | |
74 | 75 |
TEST_DEFAULTS = { |
75 | 76 |
'create_kwargs': { |
... | ... | |
114 | 115 |
verbose_name = 'OVH' |
115 | 116 |
db_table = 'sms_ovh' |
116 | 117 | |
117 |
def send_msg(self, text, sender, destinations, **kwargs):
|
|
118 |
if not (self.application_key and self.consumer_key and self.application_secret):
|
|
119 |
return self.send_msg_legacy(text, sender, destinations, **kwargs)
|
|
118 |
@property
|
|
119 |
def uses_new_api(self):
|
|
120 |
return self.application_key and self.consumer_key and self.application_secret
|
|
120 | 121 | |
122 |
def request(self, method, endpoint, **kwargs): |
|
121 | 123 |
url = self.API_URL % {'serviceName': self.account, 'login': self.username} |
122 |
body = { |
|
123 |
'sender': sender, |
|
124 |
'receivers': destinations, |
|
125 |
'message': text, |
|
126 |
'class': self.NEW_MESSAGES_CLASSES[self.msg_class], |
|
127 |
} |
|
128 |
if not kwargs['stop']: |
|
129 |
body.update({'noStopClause': 1}) |
|
124 |
url = urljoin(url, endpoint) |
|
130 | 125 | |
131 | 126 |
# sign request |
127 |
body = json.dumps(kwargs['json']) if 'json' in kwargs else '' |
|
132 | 128 |
now = str(int(time.time())) |
133 | 129 |
signature = hashlib.sha1() |
134 |
to_sign = "+".join((self.application_secret, self.consumer_key, 'POST', url, json.dumps(body), now))
|
|
130 |
to_sign = "+".join((self.application_secret, self.consumer_key, method.upper(), url, body, now))
|
|
135 | 131 |
signature.update(to_sign.encode()) |
136 | 132 | |
137 | 133 |
headers = { |
... | ... | |
142 | 138 |
} |
143 | 139 | |
144 | 140 |
try: |
145 |
response = self.requests.post(url, headers=headers, json=body)
|
|
141 |
response = self.requests.request(method, url, headers=headers, **kwargs)
|
|
146 | 142 |
except requests.RequestException as e: |
147 | 143 |
raise APIError('OVH error: POST failed, %s' % e) |
148 | 144 |
else: |
... | ... | |
154 | 150 |
response.raise_for_status() |
155 | 151 |
except requests.RequestException as e: |
156 | 152 |
raise APIError('OVH error: %s "%s"' % (e, result)) |
153 |
return result |
|
154 | ||
155 |
def send_msg(self, text, sender, destinations, **kwargs): |
|
156 |
if not self.uses_new_api: |
|
157 |
return self.send_msg_legacy(text, sender, destinations, **kwargs) |
|
158 | ||
159 |
body = { |
|
160 |
'sender': sender, |
|
161 |
'receivers': destinations, |
|
162 |
'message': text, |
|
163 |
'class': self.NEW_MESSAGES_CLASSES[self.msg_class], |
|
164 |
} |
|
165 |
if not kwargs['stop']: |
|
166 |
body.update({'noStopClause': 1}) |
|
167 | ||
168 |
result = self.request('post', 'jobs/', json=body) |
|
157 | 169 | |
158 | 170 |
ret = {} |
159 | 171 |
credits_removed = result['totalCreditsRemoved'] |
... | ... | |
161 | 173 |
self.credit_left -= credits_removed |
162 | 174 |
if self.credit_left < 0: |
163 | 175 |
self.credit_left = 0 |
164 |
self.save() |
|
176 |
self.save(update_credit=False)
|
|
165 | 177 |
if self.credit_left < self.credit_threshold_alert: |
166 | 178 |
ret['warning'] = 'credit level too low for %s: %s (threshold %s)' % ( |
167 | 179 |
self.slug, |
... | ... | |
174 | 186 | |
175 | 187 |
return ret |
176 | 188 | |
189 |
def update_credit_left(self): |
|
190 |
result = self.request('get', endpoint='') |
|
191 |
self.credit_left = result['creditsLeft'] |
|
192 |
self.save(update_credit=False) |
|
193 | ||
194 |
def hourly(self): |
|
195 |
super().hourly() |
|
196 |
self.update_credit_left() |
|
197 | ||
198 |
def save(self, *args, update_credit=True, **kwargs): |
|
199 |
super().save(*args, **kwargs) |
|
200 |
if update_credit: |
|
201 |
self.add_job('update_credit_left') |
|
202 | ||
177 | 203 |
def send_msg_legacy(self, text, sender, destinations, **kwargs): |
178 | 204 |
"""Send a SMS using the HTTP2 endpoint""" |
179 | 205 |
if not self.password: |
passerelle/apps/ovh/templates/ovh/ovhsmsgateway_detail.html | ||
---|---|---|
1 |
{% extends "passerelle/manage/messages_service_view.html" %} |
|
2 |
{% load i18n passerelle %} |
|
3 | ||
4 |
{% block description %} |
|
5 |
{{ block.super }} |
|
6 |
{% if object.uses_new_api %} |
|
7 |
<p> |
|
8 |
{% if object.credit_left %} |
|
9 |
<b>{% trans "Credit left:" %}</b> {{ object.credit_left }} |
|
10 |
{% else %} |
|
11 |
<b>{% trans "There is no credit left." %}</b> |
|
12 |
{% endif %} |
|
13 |
</p> |
|
14 |
{% endif %} |
|
15 |
{% endblock %} |
tests/test_sms.py | ||
---|---|---|
68 | 68 |
test_vectors = getattr(connector, 'TEST_DEFAULTS', {}).get('test_vectors', []) |
69 | 69 |
total = len(test_vectors) |
70 | 70 |
nb_failed = 0 |
71 |
assert Job.objects.count() == 0 |
|
71 |
assert Job.objects.filter(method_name='send_job').count() == 0
|
|
72 | 72 |
for test_vector in test_vectors: |
73 | 73 | |
74 | 74 |
# register job |
75 | 75 |
freezer.move_to('2019-01-01 00:00:00') |
76 | 76 |
result = app.post_json(path, params=payload) |
77 | 77 |
assert result.json['err'] == 0 |
78 |
job_id = Job.objects.get(status='registered').id |
|
78 |
job_id = Job.objects.get(method_name='send_job', status='registered').id
|
|
79 | 79 | |
80 | 80 |
# perform job |
81 | 81 |
freezer.move_to('2019-01-01 01:00:03') |
... | ... | |
91 | 91 |
nb_failed += 1 |
92 | 92 |
else: |
93 | 93 |
assert job.status == 'completed' |
94 |
assert Job.objects.count() == total |
|
94 |
assert Job.objects.filter(method_name='send_job').count() == total
|
|
95 | 95 |
assert SMSLog.objects.count() == total - nb_failed |
96 | 96 | |
97 | 97 | |
... | ... | |
185 | 185 | |
186 | 186 |
def test_ovh_new_api(app, freezer): |
187 | 187 |
connector = OVHSMSGateway.objects.create( |
188 |
slug='ovh', account='sms-test42', username='john',
|
|
188 |
slug='ovh', account='sms-test42', |
|
189 | 189 |
application_key='RHrTdU2oTsrVC0pu', |
190 | 190 |
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa', |
191 | 191 |
consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz' |
... | ... | |
207 | 207 |
path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug) |
208 | 208 |
result = app.post_json(path, params=payload) |
209 | 209 |
assert result.json['err'] == 0 |
210 |
job_id = Job.objects.get(status='registered').id |
|
210 |
job_id = Job.objects.get(method_name='send_job', status='registered').id
|
|
211 | 211 | |
212 | 212 |
# perform job |
213 | 213 |
freezer.move_to('2019-01-01 01:00:03') |
... | ... | |
217 | 217 |
'ids': [241615100], |
218 | 218 |
'invalidReceivers': [] |
219 | 219 |
} |
220 |
url = connector.API_URL % {'serviceName': 'sms-test42', 'login': 'john'} |
|
220 |
base_url = connector.API_URL % {'serviceName': 'sms-test42'} |
|
221 |
url = base_url + 'jobs/' |
|
221 | 222 |
with utils.mock_url(url, resp, 200) as mocked: |
222 | 223 |
connector.jobs() |
223 | 224 |
job = Job.objects.get(id=job_id) |
... | ... | |
225 | 226 | |
226 | 227 |
request = mocked.handlers[0].call['requests'][0] |
227 | 228 |
assert 'X-Ovh-Signature' in request.headers |
229 | ||
230 | ||
231 |
def test_ovh_new_api_credit(app, freezer): |
|
232 |
connector = OVHSMSGateway.objects.create( |
|
233 |
slug='ovh', account='sms-test42', |
|
234 |
application_key='RHrTdU2oTsrVC0pu', |
|
235 |
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa', |
|
236 |
consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz' |
|
237 |
) |
|
238 | ||
239 |
manager_url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug) |
|
240 |
resp = app.get(manager_url) |
|
241 |
assert 'no credit left' in resp.text |
|
242 | ||
243 |
# a job to update credit was added on connector creation |
|
244 |
resp = { |
|
245 |
'creditsLeft': 123, |
|
246 |
} |
|
247 |
ovh_url = connector.API_URL % {'serviceName': 'sms-test42'} |
|
248 |
with utils.mock_url(ovh_url, resp, 200) as mocked: |
|
249 |
connector.jobs() |
|
250 |
assert connector.credit_left == 123 |
|
251 | ||
252 |
resp = app.get(manager_url) |
|
253 |
assert '123' in resp.text |
|
254 | ||
255 |
# hourly update |
|
256 |
resp = { |
|
257 |
'creditsLeft': 456, |
|
258 |
} |
|
259 |
with utils.mock_url(ovh_url, resp, 200) as mocked: |
|
260 |
connector.hourly() |
|
261 |
assert connector.credit_left == 456 |
|
228 |
- |