|
1 # Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net> |
|
2 # |
|
3 # This file is part of Iro. |
|
4 # |
|
5 # Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
6 # this software and associated documentation files (the "Software"), to deal in |
|
7 # the Software without restriction, including without limitation the rights to use, |
|
8 # copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the |
|
9 # #Software, and to permit persons to whom the Software is furnished to do so, |
|
10 # subject to the following conditions: |
|
11 # |
|
12 # The above copyright notice and this permission notice shall be included in |
|
13 # all copies or substantial portions of the Software. |
|
14 # |
|
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
16 # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
|
17 # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
|
18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|
19 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
20 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
21 from twisted.trial import unittest |
|
22 |
|
23 from twisted.web.client import Agent, ResponseDone |
|
24 from twisted.internet import reactor |
|
25 from twisted.web.http_headers import Headers |
|
26 from twisted.python.failure import Failure |
|
27 from twisted.internet.protocol import Protocol |
|
28 from twisted.internet import defer |
|
29 |
|
30 from zope.interface import implements |
|
31 |
|
32 from twisted.internet.defer import succeed |
|
33 from twisted.web.iweb import IBodyProducer |
|
34 |
|
35 import json |
|
36 |
|
37 from twisted.web import server |
|
38 from txjsonrpc.web.jsonrpc import Fault |
|
39 |
|
40 from datetime import datetime |
|
41 |
|
42 from iro.model.schema import User, Offer, Userright, Job, Message |
|
43 |
|
44 from iro.view import jsonresource |
|
45 import iro.error as IroError |
|
46 |
|
47 from iro.test_helpers.dbtestcase import DBTestCase |
|
48 |
|
49 from iro.model import setEngine, setPool |
|
50 from iro.controller.pool import startPool, dbPool |
|
51 |
|
52 |
|
53 class StringProducer(object): |
|
54 implements(IBodyProducer) |
|
55 |
|
56 def __init__(self, body): |
|
57 self.body = json.dumps(body) |
|
58 self.length = len(self.body) |
|
59 |
|
60 def startProducing(self, consumer): |
|
61 consumer.write(self.body) |
|
62 return succeed(None) |
|
63 |
|
64 def pauseProducing(self): |
|
65 pass |
|
66 |
|
67 def stopProducing(self): |
|
68 pass |
|
69 |
|
70 class SimpleReceiver(Protocol): |
|
71 def __init__(self, d): |
|
72 self.d = d |
|
73 self.data = "" |
|
74 |
|
75 def dataReceived(self, data): |
|
76 self.data += data |
|
77 |
|
78 def connectionLost(self, reason): |
|
79 try: |
|
80 if reason.check(ResponseDone): |
|
81 self.d.callback(json.loads(self.data)) |
|
82 else: |
|
83 self.d.errback(reason) |
|
84 except: |
|
85 self.d.errback(Failure()) |
|
86 |
|
87 class JSONRPCTest(DBTestCase): |
|
88 """tests for the jsonrpc interface""" |
|
89 ContentType = 'application/json' |
|
90 StringProducer = StringProducer |
|
91 def setUp(self): |
|
92 DBTestCase.setUp(self) |
|
93 |
|
94 setEngine(self.engine) |
|
95 startPool(reactor) |
|
96 setPool(dbPool) |
|
97 self.p = reactor.listenTCP(0, server.Site(jsonresource.JSONFactory()), |
|
98 interface="127.0.0.1") |
|
99 self.port = self.p.getHost().port |
|
100 self.agent = Agent(reactor) |
|
101 |
|
102 def tearDown(self): |
|
103 DBTestCase.tearDown(self) |
|
104 return self.p.stopListening() |
|
105 |
|
106 def proxy(self, method, data=None, **kargs): |
|
107 d = self.agent.request( |
|
108 'GET', |
|
109 'http://localhost:%d/%s'%(self.port, method), |
|
110 Headers({'Content-Type':[self.ContentType]}), |
|
111 self.StringProducer(data) if data else None, |
|
112 ) |
|
113 |
|
114 def cbResponse(response): |
|
115 if kargs.has_key('code'): |
|
116 self.failUnlessEqual(response.code, kargs["code"]) |
|
117 if response.code not in [200,400,500]: |
|
118 raise Fault(response.code,'') |
|
119 self.failUnlessEqual(response.headers.getRawHeaders('Content-Type'), ['application/json']) |
|
120 d = defer.Deferred() |
|
121 response.deliverBody(SimpleReceiver(d)) |
|
122 def _(data): |
|
123 if data["status"]: |
|
124 return data["result"] |
|
125 else: |
|
126 raise Fault(data["error"]["code"],data["error"]["msg"]) |
|
127 |
|
128 d.addCallback(_) |
|
129 return d |
|
130 d.addCallback(cbResponse) |
|
131 return d |
|
132 |
|
133 |
|
134 def testListMethods(self): |
|
135 '''list of all offical Methods, that can be executed''' |
|
136 |
|
137 def cbMethods(ret): |
|
138 ret.sort() |
|
139 self.failUnlessEqual(ret, ['bill', 'defaultRoute', 'email', 'fax', 'listMethods', 'mail', 'routes', 'sms', 'status', 'telnumber']) |
|
140 |
|
141 d=self.proxy("listMethods", code=200) |
|
142 d.addCallback(cbMethods) |
|
143 return d |
|
144 |
|
145 def testApikey(self): |
|
146 ''' test apikey''' |
|
147 with self.session() as session: |
|
148 session.add(User(name='test',apikey='abcdef123456789')) |
|
149 |
|
150 d = self.proxy('status', ['abcdef123456789'], code=200) |
|
151 d.addCallback(lambda ret: self.failUnlessEqual(ret,{})) |
|
152 |
|
153 return d |
|
154 |
|
155 def testStatus(self): |
|
156 ''' test the status function''' |
|
157 with self.session() as session: |
|
158 u = User(name='test',apikey='abcdef123456789') |
|
159 session.add(u) |
|
160 j = Job(info='info', status="started") |
|
161 j.user=u |
|
162 session.add(j) |
|
163 session.commit() |
|
164 jid=j.id |
|
165 status = {str(jid):{"status":"started"}} |
|
166 args=[('abcdef123456789',jid), |
|
167 ('abcdef123456789',), |
|
168 ('abcdef123456789', '', 'false'), |
|
169 ('abcdef123456789', '', 0), |
|
170 ('abcdef123456789', '', 0), |
|
171 {"id":jid, "user":'abcdef123456789'}, |
|
172 ] |
|
173 dl=[] |
|
174 for a in args: |
|
175 d = self.proxy('status', a, code=200) |
|
176 d.addCallback(lambda ret: self.failUnlessEqual(ret,status)) |
|
177 dl.append(d) |
|
178 |
|
179 def f(exc): |
|
180 jnf = IroError.JobNotFound() |
|
181 self.failUnlessEqual(exc.faultCode, jnf.code) |
|
182 self.failUnlessEqual(exc.faultString, jnf.msg) |
|
183 d = self.proxy('status', ['abcdef123456789', jid+1], code=500) |
|
184 d = self.assertFailure(d, Fault) |
|
185 d.addCallback(f) |
|
186 dl.append(d) |
|
187 |
|
188 return defer.DeferredList(dl, fireOnOneErrback=True) |
|
189 |
|
190 def testNoSuchUser(self): |
|
191 '''a unknown user should raise a UserNotNound Exception |
|
192 bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception''' |
|
193 def f(exc): |
|
194 unf = IroError.UserNotFound() |
|
195 self.failUnlessEqual(exc.faultCode, unf.code) |
|
196 self.failUnlessEqual(exc.faultString, unf.msg) |
|
197 d = self.proxy('status', ['abcdef123456789'], code=500) |
|
198 d = self.assertFailure(d, Fault) |
|
199 d.addCallback(f) |
|
200 return d |
|
201 |
|
202 def testNoSuchMethod(self): |
|
203 '''a unknown mothod should raise a Exception ''' |
|
204 def f(exc): |
|
205 self.failUnlessEqual(exc.faultCode, 404) |
|
206 self.failUnlessEqual(exc.faultString, '') |
|
207 d = self.proxy('nosuchmethod', code=404) |
|
208 d = self.assertFailure(d, Fault) |
|
209 d.addCallback(f) |
|
210 return d |
|
211 |
|
212 def testValidationFault(self): |
|
213 '''a validate Exception should be translated to a xmlrpclib.Fault.''' |
|
214 def f(exc): |
|
215 self.failUnlessEqual(exc.faultCode, 700) |
|
216 self.failUnlessEqual(exc.faultString, "Validation of 'apikey' failed.") |
|
217 d = self.proxy('status',{'user':'xxx'}, code=400) |
|
218 d = self.assertFailure(d, Fault) |
|
219 d.addCallback(f) |
|
220 return d |
|
221 |
|
222 @defer.inlineCallbacks |
|
223 def testRoutes(self): |
|
224 '''test the route function''' |
|
225 with self.session() as session: |
|
226 u=User(name='test',apikey='abcdef123456789') |
|
227 o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") |
|
228 u.rights.append(Userright(o)) |
|
229 session.add(u) |
|
230 |
|
231 x = yield self.proxy('routes',['abcdef123456789','sms'], code=200) |
|
232 self.failUnlessEqual(x,['sipgate_basic']) |
|
233 |
|
234 def f(exc): |
|
235 self.failUnlessEqual(exc.faultCode, 700) |
|
236 self.failUnlessEqual(exc.faultString, "Typ fax is not valid.") |
|
237 d = self.proxy('routes',['abcdef123456789','fax'], code=400) |
|
238 d = self.assertFailure(d, Fault) |
|
239 d.addCallback(f) |
|
240 yield d |
|
241 |
|
242 with self.session() as session: |
|
243 o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") |
|
244 u = session.query(User).filter_by(name="test").first() |
|
245 u.rights.append(Userright(o)) |
|
246 o=Offer(name="faxde", provider="faxde", route="", typ="fax") |
|
247 session.add(o) |
|
248 session.commit() |
|
249 |
|
250 x = yield self.proxy('routes',['abcdef123456789','sms'], code=200) |
|
251 self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus']) |
|
252 x = yield self.proxy('routes', ['abcdef123456789','fax'], code=200) |
|
253 self.failUnlessEqual(x, []) |
|
254 |
|
255 with self.session() as session: |
|
256 u = session.query(User).filter_by(name="test").first() |
|
257 u.rights.append(Userright(o)) |
|
258 |
|
259 x = yield self.proxy('routes',['abcdef123456789','sms'], code=200) |
|
260 self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus']) |
|
261 x = yield self.proxy('routes', ['abcdef123456789','fax'], code=200) |
|
262 self.failUnlessEqual(x, ['faxde']) |
|
263 |
|
264 def testDefaultRoutes(self): |
|
265 '''test the defaultRoute function''' |
|
266 with self.session() as session: |
|
267 u=User(name='test',apikey='abcdef123456789') |
|
268 o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") |
|
269 u.rights.append(Userright(o,True)) |
|
270 o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") |
|
271 u.rights.append(Userright(o)) |
|
272 session.add(u) |
|
273 d = self.proxy('defaultRoute', ['abcdef123456789','sms'], code=200) |
|
274 d.addCallback(lambda x: self.failUnlessEqual(x,['sipgate_basic'])) |
|
275 |
|
276 return d |
|
277 |
|
278 def testTelnumbers(self): |
|
279 '''test the telefon validator''' |
|
280 dl = [] |
|
281 d = self.proxy('telnumber',[["0123/456(78)","+4912346785433","00123435456-658"]], code=200) |
|
282 d.addCallback(lambda x: self.failUnlessEqual(x,True)) |
|
283 dl.append(d) |
|
284 |
|
285 invalid=['xa','+1','1-23',';:+0','0123'] |
|
286 def f(exc): |
|
287 self.failUnlessEqual(exc.faultCode, 701) |
|
288 self.failUnlessEqual(exc.faultString, "No valid telnumber: '%s'" % invalid[0]) |
|
289 d = self.proxy('telnumber',[['01234']+invalid], code=400) |
|
290 d = self.assertFailure(d, Fault) |
|
291 d.addCallback(f) |
|
292 dl.append(d) |
|
293 |
|
294 return defer.DeferredList(dl, fireOnOneErrback=True) |
|
295 |
|
296 def testVaildEmail(self): |
|
297 '''test vaild email adresses (got from wikipedia)''' |
|
298 validmails=["niceandsimple@example.com"] |
|
299 d = self.proxy('email', {"recipients":validmails}, code=200) |
|
300 d.addCallback(lambda x: self.failUnlessEqual(x,True)) |
|
301 return d |
|
302 |
|
303 def testInvaildEmail(self): |
|
304 '''test invaild email adresses (got from wikipedia)''' |
|
305 invalid=["Abc.example.com",'foo@t.de'] |
|
306 def f(exc): |
|
307 self.failUnlessEqual(exc.faultCode, 702) |
|
308 self.failUnlessEqual(exc.faultString, "No valid email: '%s'" % invalid[0]) |
|
309 d = self.proxy('email', [invalid], code=400) |
|
310 d = self.assertFailure(d, Fault) |
|
311 d.addCallback(f) |
|
312 return d |
|
313 |
|
314 def testBill(self): |
|
315 '''test bill function''' |
|
316 apikey='abcdef123456789' |
|
317 with self.session() as session: |
|
318 u=User(name='test',apikey=apikey) |
|
319 session.add(u) |
|
320 d = self.proxy('bill', {"user":apikey}, code=200) |
|
321 d.addCallback(lambda x: self.failUnlessEqual(x,{'total':{'price':0.0,'anz':0}})) |
|
322 |
|
323 return d |
|
324 |
|
325 def testBillWithPrice(self): |
|
326 apikey='abcdef123456789' |
|
327 with self.session() as session: |
|
328 u=User(name='test',apikey=apikey) |
|
329 session.add(u) |
|
330 o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") |
|
331 u.rights.append(Userright(o)) |
|
332 j = Job(info='i',status='sended') |
|
333 j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o)) |
|
334 u.jobs.append(j) |
|
335 |
|
336 j = Job(info='a',status='sended') |
|
337 j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o)) |
|
338 u.jobs.append(j) |
|
339 |
|
340 def f(ret): |
|
341 self.failUnlessEqual(ret['total'],{'price':0.8,'anz':2}) |
|
342 self.failUnlessEqual(ret['sipgate_basic'], |
|
343 {'price':0.8,'anz':2, |
|
344 'info':{'i':{'price':0.4,'anz':1}, |
|
345 'a':{'price':0.4,'anz':1}, |
|
346 } |
|
347 }) |
|
348 |
|
349 d = self.proxy('bill', [apikey], code=200) |
|
350 d.addCallback(f) |
|
351 return d |
|
352 |
|
353 if __name__ == '__main__': |
|
354 unittest.main() |