RAHHH
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,238 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
import platform
|
||||
|
||||
import re
|
||||
import json
|
||||
import binascii
|
||||
|
||||
from autobahn.wamp import auth
|
||||
from autobahn.wamp import types
|
||||
|
||||
|
||||
# these test vectors are all for HMAC-SHA1
|
||||
PBKDF2_TEST_VECTORS = [
|
||||
# From RFC 6070
|
||||
(b'password', b'salt', 1, 20, '0c60c80f961f0e71f3a9b524af6012062fe037a6'),
|
||||
(b'password', b'salt', 2, 20, 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957'),
|
||||
|
||||
# From Crypt-PBKDF2
|
||||
(b'password', b'ATHENA.MIT.EDUraeburn', 1, 16, 'cdedb5281bb2f801565a1122b2563515'),
|
||||
(b'password', b'ATHENA.MIT.EDUraeburn', 1, 32, 'cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837'),
|
||||
(b'password', b'ATHENA.MIT.EDUraeburn', 2, 16, '01dbee7f4a9e243e988b62c73cda935d'),
|
||||
(b'password', b'ATHENA.MIT.EDUraeburn', 2, 32, '01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86'),
|
||||
(b'password', b'ATHENA.MIT.EDUraeburn', 1200, 32, '5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13'),
|
||||
(b'X' * 64, b'pass phrase equals block size', 1200, 32, '139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1'),
|
||||
(b'X' * 65, b'pass phrase exceeds block size', 1200, 32, '9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a'),
|
||||
]
|
||||
|
||||
if platform.python_implementation() != 'PyPy':
|
||||
|
||||
# the following fails on PyPy: "RuntimeError: maximum recursion depth exceeded"
|
||||
PBKDF2_TEST_VECTORS.extend(
|
||||
[
|
||||
# From RFC 6070
|
||||
(b'password', b'salt', 4096, 20, '4b007901b765489abead49d926f721d065a429c1'),
|
||||
(b'passwordPASSWORDpassword', b'saltSALTsaltSALTsaltSALTsaltSALTsalt', 4096, 25, '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038'),
|
||||
(b'pass\x00word', b'sa\x00lt', 4096, 16, '56fa6aa75548099dcc37d7f03425e0c3'),
|
||||
|
||||
# This one is from the RFC but it just takes for ages
|
||||
# (b'password', b'salt', 16777216, 20, 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984'),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class TestWampAuthHelpers(unittest.TestCase):
|
||||
|
||||
def test_pbkdf2(self):
|
||||
for tv in PBKDF2_TEST_VECTORS:
|
||||
result = auth.pbkdf2(tv[0], tv[1], tv[2], tv[3], 'sha1')
|
||||
self.assertEqual(type(result), bytes)
|
||||
self.assertEqual(binascii.hexlify(result).decode('ascii'), tv[4])
|
||||
|
||||
def test_generate_totp_secret_default(self):
|
||||
secret = auth.generate_totp_secret()
|
||||
self.assertEqual(type(secret), str)
|
||||
self.assertEqual(len(secret), 10 * 8 / 5)
|
||||
|
||||
def test_generate_totp_secret_length(self):
|
||||
for length in [5, 10, 20, 30, 40, 50]:
|
||||
secret = auth.generate_totp_secret(length)
|
||||
self.assertEqual(type(secret), str)
|
||||
self.assertEqual(len(secret), length * 8 / 5)
|
||||
|
||||
def test_compute_totp(self):
|
||||
pat = re.compile(r"\d\d\d\d\d\d")
|
||||
secret = "MFRGGZDFMZTWQ2LK"
|
||||
signature = auth.compute_totp(secret)
|
||||
self.assertEqual(type(signature), str)
|
||||
self.assertTrue(pat.match(signature) is not None)
|
||||
|
||||
def test_compute_totp_offset(self):
|
||||
pat = re.compile(r"\d\d\d\d\d\d")
|
||||
secret = "MFRGGZDFMZTWQ2LK"
|
||||
for offset in range(-10, 10):
|
||||
signature = auth.compute_totp(secret, offset)
|
||||
self.assertEqual(type(signature), str)
|
||||
self.assertTrue(pat.match(signature) is not None)
|
||||
|
||||
def test_derive_key(self):
|
||||
secret = 'L3L1YUE8Txlw'
|
||||
salt = 'salt123'
|
||||
key = auth.derive_key(secret.encode('utf8'), salt.encode('utf8'))
|
||||
self.assertEqual(type(key), bytes)
|
||||
self.assertEqual(key, b"qzcdsr9uu/L5hnss3kjNTRe490ETgA70ZBaB5rvnJ5Y=")
|
||||
|
||||
def test_generate_wcs_default(self):
|
||||
secret = auth.generate_wcs()
|
||||
self.assertEqual(type(secret), bytes)
|
||||
self.assertEqual(len(secret), 14)
|
||||
|
||||
def test_generate_wcs_length(self):
|
||||
for length in [5, 10, 20, 30, 40, 50]:
|
||||
secret = auth.generate_wcs(length)
|
||||
self.assertEqual(type(secret), bytes)
|
||||
self.assertEqual(len(secret), length)
|
||||
|
||||
def test_compute_wcs(self):
|
||||
secret = 'L3L1YUE8Txlw'
|
||||
challenge = json.dumps([1, 2, 3], ensure_ascii=False).encode('utf8')
|
||||
signature = auth.compute_wcs(secret.encode('utf8'), challenge)
|
||||
self.assertEqual(type(signature), bytes)
|
||||
self.assertEqual(signature, b"1njQtmmeYO41N5EWEzD2kAjjEKRZ5kPZt/TzpYXOzR0=")
|
||||
|
||||
|
||||
@unittest.skipIf(not auth.HAS_ARGON, 'no Argon2 library')
|
||||
class TestScram(unittest.TestCase):
|
||||
|
||||
def test_argon2id_static(self):
|
||||
# re-generate from the official argon2 tools:
|
||||
# echo -n "p4ssw0rd" | argon2 '1234567890abcdef' -id -t 32 -m 9 -p 1 -l 32
|
||||
expected = binascii.unhexlify('ee4a8acf9d5958354fb79a95ae20692d05e42591ba49fae85eb6700e8b0ed293')
|
||||
raw_hash = auth._hash_argon2id13_secret(
|
||||
b'p4ssw0rd',
|
||||
binascii.b2a_base64(b'1234567890abcdef'), # ours takes base64-encoded salt
|
||||
32, # this is WAY TOO SMALL; for production, use 4096 or higher
|
||||
512, # note that the argon2 utility takes a "power of 2", so "-m 9" above == 512
|
||||
)
|
||||
decoded_hash = binascii.a2b_base64(raw_hash + b'==\n')
|
||||
self.assertEqual(expected, decoded_hash)
|
||||
|
||||
def test_pbkdf2_static(self):
|
||||
expected = binascii.unhexlify('f6991a28c75f43751e0d75499fd7b8649f659118ddc1d61cee5883af547d15f5')
|
||||
# 8 iterations is WAY TOO FEW for production; this is a test
|
||||
raw_hash = auth._hash_pbkdf2_secret(b'p4ssw0rd', b'1234567890abcdef', 8)
|
||||
self.assertEqual(raw_hash, expected)
|
||||
|
||||
def test_basic(self):
|
||||
scram = auth.AuthScram(
|
||||
nonce='1234567890abcdef',
|
||||
kdf='argon2id13',
|
||||
salt=binascii.b2a_hex(b'1234567890abcdef').decode('ascii'),
|
||||
iterations=32, # far too few; use 4096 or more for production
|
||||
memory=512,
|
||||
password='p4ssw0rd',
|
||||
authid='username',
|
||||
)
|
||||
# thought: if we could import crossbar code here, we could
|
||||
# test the "other side" of this with fewer mocks
|
||||
# (i.e. hard-coding the client nonce)
|
||||
scram._client_nonce = binascii.b2a_hex(b'1234567890abcdef').decode('ascii')
|
||||
self.assertEqual(
|
||||
{'nonce': '31323334353637383930616263646566'},
|
||||
scram.authextra,
|
||||
)
|
||||
|
||||
challenge = types.Challenge('scram', {
|
||||
'nonce': '1234567890abcdeffedcba0987654321',
|
||||
'kdf': 'argon2id-13',
|
||||
'salt': binascii.b2a_hex(b'1234567890abcdef').decode('ascii'),
|
||||
'iterations': 32,
|
||||
'memory': 512,
|
||||
})
|
||||
reply = scram.on_challenge(Mock(), challenge)
|
||||
self.assertEqual(
|
||||
b'f5r3loERzGVSuimE+lvO0bWna2zyswBo0HrZkaaEy38=',
|
||||
reply,
|
||||
)
|
||||
|
||||
authextra = dict(
|
||||
scram_server_signature=b'f5r3loERzGVSuimE+lvO0bWna2zyswBo0HrZkaaEy38=',
|
||||
)
|
||||
scram.on_welcome(Mock(), authextra)
|
||||
|
||||
def test_no_memory_arg(self):
|
||||
scram = auth.AuthScram(
|
||||
nonce='1234567890abcdef',
|
||||
kdf='argon2id13',
|
||||
salt=binascii.b2a_hex(b'1234567890abcdef').decode('ascii'),
|
||||
iterations=4096,
|
||||
memory=512,
|
||||
password='p4ssw0rd',
|
||||
authid='username',
|
||||
)
|
||||
scram.authextra
|
||||
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
challenge = types.Challenge('scram', {
|
||||
'nonce': '1234567890abcdeffedcba0987654321',
|
||||
'kdf': 'argon2id-13',
|
||||
'salt': binascii.b2a_hex(b'1234567890abcdef'),
|
||||
'iterations': 4096,
|
||||
# no 'memory' key
|
||||
})
|
||||
scram.on_challenge(Mock(), challenge)
|
||||
self.assertIn(
|
||||
"requires 'memory' parameter",
|
||||
str(ctx.exception)
|
||||
)
|
||||
|
||||
def test_unknown_arg(self):
|
||||
scram = auth.AuthScram(
|
||||
nonce='1234567890abcdef',
|
||||
kdf='argon2id13',
|
||||
salt=binascii.b2a_hex(b'1234567890abcdef'),
|
||||
iterations=4096,
|
||||
memory=512,
|
||||
password='p4ssw0rd',
|
||||
authid='username',
|
||||
)
|
||||
scram.authextra
|
||||
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
challenge = types.Challenge('scram', {
|
||||
'nonce': '1234567890abcdeffedcba0987654321',
|
||||
'kdf': 'argon2id-13',
|
||||
'salt': binascii.b2a_hex(b'1234567890abcdef'),
|
||||
'iterations': 4096,
|
||||
'memory': 512,
|
||||
'an_invalid_key': None
|
||||
})
|
||||
scram.on_challenge(Mock(), challenge)
|
||||
self.assertIn("an_invalid_key", str(ctx.exception))
|
||||
@@ -0,0 +1,219 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
|
||||
if os.environ.get('USE_TWISTED', False):
|
||||
from autobahn.twisted.util import sleep
|
||||
from autobahn.twisted import wamp
|
||||
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet import defer
|
||||
from twisted.application import service
|
||||
|
||||
class CaseComponent(wamp.ApplicationSession):
|
||||
"""
|
||||
Application code goes here. This is an example component that calls
|
||||
a remote procedure on a WAMP peer, subscribes to a topic to receive
|
||||
events, and then stops the world after some events.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
wamp.ApplicationSession.__init__(self, config)
|
||||
self.test = config.extra['test']
|
||||
self.stop = False
|
||||
self._logline = 1
|
||||
self.finished = False
|
||||
|
||||
def log(self, *args):
|
||||
if len(args) > 1:
|
||||
sargs = ", ".join(str(s) for s in args)
|
||||
elif len(args) == 1:
|
||||
sargs = args[0]
|
||||
else:
|
||||
sargs = "-"
|
||||
|
||||
msg = '= : {0:>3} : {1:<20} : {2}'.format(self._logline, self.__class__.__name__, sargs)
|
||||
self._logline += 1
|
||||
print(msg)
|
||||
|
||||
def finish(self):
|
||||
if not self.finished:
|
||||
self.test.deferred.callback(None)
|
||||
self.finished = True
|
||||
else:
|
||||
print("already finished")
|
||||
|
||||
class Case1_Backend(CaseComponent):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
|
||||
self.log("joined")
|
||||
|
||||
def add2(x, y):
|
||||
self.log("add2 invoked: {0}, {1}".format(x, y))
|
||||
return x + y
|
||||
|
||||
yield self.register(add2, 'com.mathservice.add2')
|
||||
self.log("add2 registered")
|
||||
|
||||
self.finish()
|
||||
|
||||
class Case1_Frontend(CaseComponent):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
|
||||
self.log("joined")
|
||||
|
||||
try:
|
||||
res = yield self.call('com.mathservice.add2', 2, 3)
|
||||
except Exception as e:
|
||||
self.log("call error: {0}".format(e))
|
||||
else:
|
||||
self.log("call result: {0}".format(res))
|
||||
|
||||
self.finish()
|
||||
|
||||
class Case2_Backend(CaseComponent):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
|
||||
self.log("joined")
|
||||
|
||||
def ping():
|
||||
self.log("ping() is invoked")
|
||||
return
|
||||
|
||||
def add2(a, b):
|
||||
self.log("add2() is invoked", a, b)
|
||||
return a + b
|
||||
|
||||
def stars(nick="somebody", stars=0):
|
||||
self.log("stars() is invoked", nick, stars)
|
||||
return "{0} starred {1}x".format(nick, stars)
|
||||
|
||||
def orders(product, limit=5):
|
||||
self.log("orders() is invoked", product, limit)
|
||||
return ["Product {0}".format(i) for i in range(50)][:limit]
|
||||
|
||||
def arglen(*args, **kwargs):
|
||||
self.log("arglen() is invoked", args, kwargs)
|
||||
return [len(args), len(kwargs)]
|
||||
|
||||
yield self.register(ping, 'com.arguments.ping')
|
||||
yield self.register(add2, 'com.arguments.add2')
|
||||
yield self.register(stars, 'com.arguments.stars')
|
||||
yield self.register(orders, 'com.arguments.orders')
|
||||
yield self.register(arglen, 'com.arguments.arglen')
|
||||
|
||||
self.log("procedures registered")
|
||||
|
||||
class Case2_Frontend(CaseComponent):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def onJoin(self, details):
|
||||
|
||||
self.log("joined")
|
||||
|
||||
yield sleep(1)
|
||||
|
||||
yield self.call('com.arguments.ping')
|
||||
self.log("Pinged!")
|
||||
|
||||
res = yield self.call('com.arguments.add2', 2, 3)
|
||||
self.log("Add2: {0}".format(res))
|
||||
|
||||
starred = yield self.call('com.arguments.stars')
|
||||
self.log("Starred 1: {0}".format(starred))
|
||||
|
||||
starred = yield self.call('com.arguments.stars', nick='Homer')
|
||||
self.log("Starred 2: {0}".format(starred))
|
||||
|
||||
starred = yield self.call('com.arguments.stars', stars=5)
|
||||
self.log("Starred 3: {0}".format(starred))
|
||||
|
||||
starred = yield self.call('com.arguments.stars', nick='Homer', stars=5)
|
||||
self.log("Starred 4: {0}".format(starred))
|
||||
|
||||
orders = yield self.call('com.arguments.orders', 'coffee')
|
||||
self.log("Orders 1: {0}".format(orders))
|
||||
|
||||
orders = yield self.call('com.arguments.orders', 'coffee', limit=10)
|
||||
self.log("Orders 2: {0}".format(orders))
|
||||
|
||||
arglengths = yield self.call('com.arguments.arglen')
|
||||
self.log("Arglen 1: {0}".format(arglengths))
|
||||
|
||||
arglengths = yield self.call('com.arguments.arglen', 1, 2, 3)
|
||||
self.log("Arglen 1: {0}".format(arglengths))
|
||||
|
||||
arglengths = yield self.call('com.arguments.arglen', a=1, b=2, c=3)
|
||||
self.log("Arglen 2: {0}".format(arglengths))
|
||||
|
||||
arglengths = yield self.call('com.arguments.arglen', 1, 2, 3, a=1, b=2, c=3)
|
||||
self.log("Arglen 3: {0}".format(arglengths))
|
||||
|
||||
self.log("finishing")
|
||||
|
||||
self.finish()
|
||||
|
||||
class TestRpc(unittest.TestCase):
|
||||
|
||||
if os.environ.get("WAMP_ROUTER_URL") is None:
|
||||
skip = ("Please provide WAMP_ROUTER_URL environment with url to "
|
||||
"WAMP router to run WAMP integration tests")
|
||||
|
||||
def setUp(self):
|
||||
self.url = os.environ.get("WAMP_ROUTER_URL")
|
||||
self.realm = "realm1"
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def runOneTest(self, components):
|
||||
self.deferred = defer.Deferred()
|
||||
app = service.MultiService()
|
||||
for component in components:
|
||||
c = wamp.Service(
|
||||
url=self.url,
|
||||
extra=dict(test=self),
|
||||
realm=self.realm,
|
||||
make=component,
|
||||
)
|
||||
c.setServiceParent(app)
|
||||
|
||||
app.startService()
|
||||
yield self.deferred
|
||||
app.stopService()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_case1(self):
|
||||
yield self.runOneTest([Case1_Backend, Case1_Frontend])
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_case2(self):
|
||||
yield self.runOneTest([Case2_Backend, Case2_Frontend])
|
||||
@@ -0,0 +1,154 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest.mock as mock
|
||||
import pytest
|
||||
import txaio
|
||||
|
||||
if os.environ.get('USE_ASYNCIO', False):
|
||||
from autobahn.asyncio.component import Component
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 5), reason="requires Python 3.5+")
|
||||
@pytest.mark.asyncio(forbid_global_loop=True)
|
||||
async def test_asyncio_component(event_loop):
|
||||
orig_loop = txaio.config.loop
|
||||
txaio.config.loop = event_loop
|
||||
|
||||
comp = Component(
|
||||
transports=[
|
||||
{
|
||||
"url": "ws://localhost:12/bogus",
|
||||
"max_retries": 1,
|
||||
"max_retry_delay": 0.1,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
# if having trouble, try starting some logging (and use
|
||||
# "py.test -s" to get real-time output)
|
||||
# txaio.start_logging(level="debug")
|
||||
f = comp.start(loop=event_loop)
|
||||
txaio.config.loop = event_loop
|
||||
finished = txaio.create_future()
|
||||
|
||||
def fail():
|
||||
finished.set_exception(AssertionError("timed out"))
|
||||
txaio.config.loop = orig_loop
|
||||
txaio.call_later(4.0, fail)
|
||||
|
||||
def done(f):
|
||||
try:
|
||||
f.result()
|
||||
finished.set_exception(AssertionError("should get an error"))
|
||||
except RuntimeError as e:
|
||||
if 'Exhausted all transport connect attempts' not in str(e):
|
||||
finished.set_exception(AssertionError("wrong exception caught"))
|
||||
finished.set_result(None)
|
||||
txaio.config.loop = orig_loop
|
||||
assert comp._done_f is None
|
||||
f.add_done_callback(done)
|
||||
await finished
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 5), reason="requires Python 3.5+")
|
||||
@pytest.mark.asyncio(forbid_global_loop=True)
|
||||
async def test_asyncio_component_404(event_loop):
|
||||
"""
|
||||
If something connects but then gets aborted, it should still try
|
||||
to re-connect (in real cases this could be e.g. wrong path,
|
||||
TLS failure, WebSocket handshake failure, etc)
|
||||
"""
|
||||
orig_loop = txaio.config.loop
|
||||
txaio.config.loop = event_loop
|
||||
|
||||
class FakeTransport(object):
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def write(self, data):
|
||||
pass
|
||||
|
||||
fake_transport = FakeTransport()
|
||||
actual_protocol = [None] # set in a closure below
|
||||
|
||||
def create_connection(protocol_factory=None, server_hostname=None, host=None, port=None, ssl=False):
|
||||
if actual_protocol[0] is None:
|
||||
protocol = protocol_factory()
|
||||
actual_protocol[0] = protocol
|
||||
protocol.connection_made(fake_transport)
|
||||
return txaio.create_future_success((fake_transport, protocol))
|
||||
else:
|
||||
return txaio.create_future_error(RuntimeError("second connection fails completely"))
|
||||
|
||||
with mock.patch.object(event_loop, 'create_connection', create_connection):
|
||||
event_loop.create_connection = create_connection
|
||||
|
||||
comp = Component(
|
||||
transports=[
|
||||
{
|
||||
"url": "ws://localhost:12/bogus",
|
||||
"max_retries": 1,
|
||||
"max_retry_delay": 0.1,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
# if having trouble, try starting some logging (and use
|
||||
# "py.test -s" to get real-time output)
|
||||
# txaio.start_logging(level="debug")
|
||||
f = comp.start(loop=event_loop)
|
||||
txaio.config.loop = event_loop
|
||||
|
||||
# now that we've started connecting, we *should* be able
|
||||
# to connetion_lost our transport .. but we do a
|
||||
# call-later to ensure we're after the setup stuff in the
|
||||
# event-loop (because asyncio doesn't synchronously
|
||||
# process already-completed Futures like Twisted does)
|
||||
|
||||
def nuke_transport():
|
||||
if actual_protocol[0] is not None:
|
||||
actual_protocol[0].connection_lost(None) # asyncio can call this with None
|
||||
txaio.call_later(0.1, nuke_transport)
|
||||
|
||||
finished = txaio.create_future()
|
||||
|
||||
def fail():
|
||||
finished.set_exception(AssertionError("timed out"))
|
||||
txaio.config.loop = orig_loop
|
||||
txaio.call_later(1.0, fail)
|
||||
|
||||
def done(f):
|
||||
try:
|
||||
f.result()
|
||||
finished.set_exception(AssertionError("should get an error"))
|
||||
except RuntimeError as e:
|
||||
if 'Exhausted all transport connect attempts' not in str(e):
|
||||
finished.set_exception(AssertionError("wrong exception caught"))
|
||||
finished.set_result(None)
|
||||
txaio.config.loop = orig_loop
|
||||
f.add_done_callback(done)
|
||||
await finished
|
||||
@@ -0,0 +1,37 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from autobahn.wamp import cryptobox
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
@unittest.skipIf(not cryptobox.HAS_CRYPTOBOX, 'no cryptobox support present')
|
||||
class TestCryptoBox(unittest.TestCase):
|
||||
|
||||
def test_create_keyring(self):
|
||||
kr = cryptobox.KeyRing()
|
||||
assert kr
|
||||
@@ -0,0 +1,295 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
import binascii
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
|
||||
import txaio
|
||||
|
||||
if os.environ.get('USE_TWISTED', None):
|
||||
txaio.use_twisted()
|
||||
elif os.environ.get('USE_ASYNCIO', None):
|
||||
txaio.use_asyncio()
|
||||
else:
|
||||
raise RuntimeError('need either USE_TWISTED=1 or USE_ASYNCIO=1')
|
||||
|
||||
from autobahn.wamp import types
|
||||
from autobahn.wamp.auth import create_authenticator
|
||||
from autobahn.wamp.cryptosign import _makepad, HAS_CRYPTOSIGN, CryptosignAuthextra
|
||||
|
||||
if HAS_CRYPTOSIGN:
|
||||
from autobahn.wamp.cryptosign import CryptosignKey
|
||||
from nacl.encoding import HexEncoder
|
||||
|
||||
|
||||
import tempfile
|
||||
|
||||
|
||||
keybody = '''-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACAa38i/4dNWFuZN/72QAJbyOwZvkUyML/u2b2B1uW4RbQAAAJj4FLyB+BS8
|
||||
gQAAAAtzc2gtZWQyNTUxOQAAACAa38i/4dNWFuZN/72QAJbyOwZvkUyML/u2b2B1uW4RbQ
|
||||
AAAEBNV9l6aPVVaWYgpthJwM5YJWhRjXKet1PcfHMt4oBFEBrfyL/h01YW5k3/vZAAlvI7
|
||||
Bm+RTIwv+7ZvYHW5bhFtAAAAFXNvbWV1c2VyQGZ1bmt0aGF0LmNvbQ==
|
||||
-----END OPENSSH PRIVATE KEY-----'''
|
||||
|
||||
pubkey = '''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJVp3hjHwIQyEladzd8mFcf0YSXcmyKS3qMLB7VqTQKm someuser@example.com
|
||||
'''
|
||||
|
||||
# valid test vectors for WAMP-cryptosign signature testing
|
||||
test_vectors_1 = [
|
||||
# _WITHOUT_ channel_id
|
||||
{
|
||||
'channel_id': None,
|
||||
'private_key': '4d57d97a68f555696620a6d849c0ce582568518d729eb753dc7c732de2804510',
|
||||
'challenge': 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||
'signature': 'b32675b221f08593213737bef8240e7c15228b07028e19595294678c90d11c0cae80a357331bfc5cc9fb71081464e6e75013517c2cf067ad566a6b7b728e5d03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
||||
},
|
||||
{
|
||||
'channel_id': None,
|
||||
'private_key': 'd511fe78e23934b3dadb52fcd022974b80bd92bccc7c5cf404e46cc0a8a2f5cd',
|
||||
'challenge': 'b26c1f87c13fc1da14997f1b5a71995dff8fbe0a62fae8473c7bdbd05bfb607d',
|
||||
'signature': 'd4209ad10d5aff6bfbc009d7e924795de138a63515efc7afc6b01b7fe5201372190374886a70207b042294af5bd64ce725cd8dceb344e6d11c09d1aaaf4d660fb26c1f87c13fc1da14997f1b5a71995dff8fbe0a62fae8473c7bdbd05bfb607d'
|
||||
},
|
||||
{
|
||||
'channel_id': None,
|
||||
'private_key': '6e1fde9cf9e2359a87420b65a87dc0c66136e66945196ba2475990d8a0c3a25b',
|
||||
'challenge': 'b05e6b8ad4d69abf74aa3be3c0ee40ae07d66e1895b9ab09285a2f1192d562d2',
|
||||
'signature': '7beb282184baadd08f166f16dd683b39cab53816ed81e6955def951cb2ddad1ec184e206746fd82bda075af03711d3d5658fc84a76196b0fa8d1ebc92ef9f30bb05e6b8ad4d69abf74aa3be3c0ee40ae07d66e1895b9ab09285a2f1192d562d2'
|
||||
},
|
||||
|
||||
# _WITH_ channel_id
|
||||
{
|
||||
'channel_id': '62e935ae755f3d48f80d4d59f6121358c435722a67e859cc0caa8b539027f2ff',
|
||||
'private_key': '4d57d97a68f555696620a6d849c0ce582568518d729eb753dc7c732de2804510',
|
||||
'challenge': 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||
'signature': '9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00'
|
||||
},
|
||||
{
|
||||
'channel_id': '62e935ae755f3d48f80d4d59f6121358c435722a67e859cc0caa8b539027f2ff',
|
||||
'private_key': 'd511fe78e23934b3dadb52fcd022974b80bd92bccc7c5cf404e46cc0a8a2f5cd',
|
||||
'challenge': 'b26c1f87c13fc1da14997f1b5a71995dff8fbe0a62fae8473c7bdbd05bfb607d',
|
||||
'signature': '305aaa3ac25e98f651427688b3fc43fe7d8a68a7ec1d7d61c61517c519bd4a427c3015599d83ca28b4c652333920223844ef0725eb5dc2febfd6af7677b73f01d0852a29b460fc92ec943242ac638a053bbacc200512b18b30d15083cbdc9282'
|
||||
},
|
||||
{
|
||||
'channel_id': '62e935ae755f3d48f80d4d59f6121358c435722a67e859cc0caa8b539027f2ff',
|
||||
'private_key': '6e1fde9cf9e2359a87420b65a87dc0c66136e66945196ba2475990d8a0c3a25b',
|
||||
'challenge': 'b05e6b8ad4d69abf74aa3be3c0ee40ae07d66e1895b9ab09285a2f1192d562d2',
|
||||
'signature': 'ee3c7644fd8070532bc1fde3d70d742267da545d8c8f03e63bda63f1ad4214f4d2c4bfdb4eb9526def42deeb7e31602a6ff99eba893e0a4ad4d45892ca75e608d2b75e24a189a7f78ca776ba36fc53f6c3e31c32f251f2c524f0a44202f2902d'
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class TestSigVectors(unittest.TestCase):
|
||||
|
||||
def test_vectors(self):
|
||||
session = Mock()
|
||||
|
||||
for testvec in test_vectors_1:
|
||||
# setup fake transport details including fake channel_id
|
||||
if testvec['channel_id']:
|
||||
channel_id = binascii.a2b_hex(testvec['channel_id'])
|
||||
channel_id_type = 'tls-unique'
|
||||
session._transport.transport_details = types.TransportDetails(channel_id={'tls-unique': channel_id})
|
||||
else:
|
||||
channel_id = None
|
||||
channel_id_type = None
|
||||
session._transport.transport_details = types.TransportDetails(channel_id=None)
|
||||
|
||||
# private signing key (the seed for it)
|
||||
private_key = CryptosignKey.from_bytes(binascii.a2b_hex(testvec['private_key']))
|
||||
|
||||
# the fake challenge we've received
|
||||
challenge = types.Challenge("cryptosign", dict(challenge=testvec['challenge']))
|
||||
|
||||
# ok, now sign the challenge
|
||||
f_signed = private_key.sign_challenge(challenge,
|
||||
channel_id=channel_id,
|
||||
channel_id_type=channel_id_type)
|
||||
|
||||
def success(signed):
|
||||
# the signature returned is a Hex encoded string
|
||||
self.assertTrue(type(signed) == str)
|
||||
|
||||
# we return the concatenation of the signature and the message signed (96 bytes)
|
||||
self.assertEqual(
|
||||
192,
|
||||
len(signed),
|
||||
)
|
||||
|
||||
# must match the expected value in our test vector
|
||||
self.assertEqual(
|
||||
testvec['signature'],
|
||||
signed,
|
||||
)
|
||||
|
||||
def failed(err):
|
||||
self.fail(str(err))
|
||||
|
||||
txaio.add_callbacks(f_signed, success, failed)
|
||||
|
||||
|
||||
class TestAuth(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.key = CryptosignKey.from_ssh_bytes(keybody)
|
||||
self.privkey_hex = self.key._key.encode(encoder=HexEncoder)
|
||||
|
||||
# all tests here fake the use of channel_id_type='tls-unique' with the following channel_id
|
||||
m = hashlib.sha256()
|
||||
m.update("some TLS message".encode())
|
||||
|
||||
# 62e935ae755f3d48f80d4d59f6121358c435722a67e859cc0caa8b539027f2ff
|
||||
channel_id = m.digest()
|
||||
|
||||
self.transport_details = types.TransportDetails(channel_id={'tls-unique': channel_id})
|
||||
|
||||
def test_public_key(self):
|
||||
self.assertEqual(self.key.public_key(binary=False), '1adfc8bfe1d35616e64dffbd900096f23b066f914c8c2ffbb66f6075b96e116d')
|
||||
|
||||
def test_valid(self):
|
||||
session = Mock()
|
||||
session._transport.transport_details = self.transport_details
|
||||
|
||||
challenge = types.Challenge("cryptosign", dict(challenge="ff" * 32))
|
||||
f_signed = self.key.sign_challenge(challenge,
|
||||
channel_id=self.transport_details.channel_id['tls-unique'],
|
||||
channel_id_type='tls-unique')
|
||||
|
||||
def success(signed):
|
||||
self.assertEqual(
|
||||
192,
|
||||
len(signed),
|
||||
)
|
||||
self.assertEqual(
|
||||
'9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00',
|
||||
signed,
|
||||
)
|
||||
|
||||
def failed(err):
|
||||
self.fail(str(err))
|
||||
|
||||
txaio.add_callbacks(f_signed, success, failed)
|
||||
|
||||
def test_authenticator(self):
|
||||
authenticator = create_authenticator(
|
||||
"cryptosign",
|
||||
authid="someone",
|
||||
authextra={'channel_binding': 'tls-unique'},
|
||||
privkey=self.privkey_hex,
|
||||
)
|
||||
session = Mock()
|
||||
session._transport.transport_details = self.transport_details
|
||||
challenge = types.Challenge("cryptosign", dict(challenge="ff" * 32))
|
||||
f_reply = authenticator.on_challenge(session, challenge)
|
||||
|
||||
def success(reply):
|
||||
self.assertEqual(
|
||||
reply,
|
||||
'9b6f41540c9b95b4b7b281c3042fa9c54cef43c842d62ea3fd6030fcb66e70b3e80d49d44c29d1635da9348d02ec93f3ed1ef227dfb59a07b580095c2b82f80f9d16ca518aa0c2b707f2b2a609edeca73bca8dd59817a633f35574ac6fd80d00',
|
||||
)
|
||||
|
||||
def failed(err):
|
||||
self.fail(str(err))
|
||||
|
||||
txaio.add_callbacks(f_reply, success, failed)
|
||||
|
||||
|
||||
class TestKey(unittest.TestCase):
|
||||
|
||||
def test_pad(self):
|
||||
self.assertEqual(_makepad(0), b'')
|
||||
self.assertEqual(_makepad(2), b'\x01\x02')
|
||||
self.assertEqual(_makepad(3), b'\x01\x02\x03')
|
||||
self.assertEqual(_makepad(30), b'\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e')
|
||||
self.assertEqual(binascii.b2a_hex(_makepad(30)).decode(), '0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e')
|
||||
|
||||
def test_key(self):
|
||||
with tempfile.NamedTemporaryFile('w+t') as fp:
|
||||
fp.write(keybody)
|
||||
fp.seek(0)
|
||||
|
||||
key = CryptosignKey.from_ssh_file(fp.name)
|
||||
self.assertEqual(key.public_key(), '1adfc8bfe1d35616e64dffbd900096f23b066f914c8c2ffbb66f6075b96e116d')
|
||||
|
||||
def test_pubkey(self):
|
||||
with tempfile.NamedTemporaryFile('w+t') as fp:
|
||||
fp.write(pubkey)
|
||||
fp.seek(0)
|
||||
|
||||
key = CryptosignKey.from_ssh_file(fp.name)
|
||||
self.assertEqual(key.public_key(binary=False), '9569de18c7c0843212569dcddf2615c7f46125dc9b2292dea30b07b56a4d02a6')
|
||||
self.assertEqual(key.comment, 'someuser@example.com')
|
||||
|
||||
|
||||
class TestAuthExtra(unittest.TestCase):
|
||||
def test_default_ctor(self):
|
||||
ae = CryptosignAuthextra()
|
||||
self.assertEqual(ae.marshal(), {})
|
||||
|
||||
def test_ctor(self):
|
||||
ae1 = CryptosignAuthextra(pubkey=b'\xff' * 32)
|
||||
self.assertEqual(ae1.marshal(), {
|
||||
'pubkey': 'ff' * 32
|
||||
})
|
||||
|
||||
ae1 = CryptosignAuthextra(pubkey=b'\xff' * 32, bandwidth=200)
|
||||
self.assertEqual(ae1.marshal(), {
|
||||
'pubkey': 'ff' * 32,
|
||||
'reservation': {
|
||||
'bandwidth': 200
|
||||
}
|
||||
})
|
||||
|
||||
def test_parse(self):
|
||||
data_original = {
|
||||
'pubkey': '9019a424b040859c108edee02e64c1dcb32b253686d7b5db56c306e9bdb2fe7e',
|
||||
'challenge': 'fe81c84e94a75a357c259d6b37361e43966a45f57dff181bb61b2f91a0f4ac88',
|
||||
'channel_binding': 'tls-unique',
|
||||
'channel_id': '2e642bf991f48ece9133a0a32d15550921dda12bfebfbc941571d4b2960540bc',
|
||||
'trustroot': '0xe78ea2fE1533D4beD9A10d91934e109A130D0ad8',
|
||||
'reservation': {
|
||||
'chain_id': 999,
|
||||
'block_no': 123456789,
|
||||
'realm': '0x163D58cE482560B7826b4612f40aa2A7d53310C4',
|
||||
'delegate': '0x72b3486d38E9f49215b487CeAaDF27D6acf22115',
|
||||
'seeder': '0x52d66f36A7927cF9612e1b40bD6549d08E0513Ff',
|
||||
'bandwidth': 200
|
||||
},
|
||||
'signature': '747763c69394270603f64af5be3f8256a14b41ff51027e583ee81db9f1f15a01cc8e55218a76139f26dbaaa78d8a537d80d248b3fc6245ecf4602cc5fbb0f6452e',
|
||||
}
|
||||
ae1 = CryptosignAuthextra.parse(data_original)
|
||||
data_marshalled = ae1.marshal()
|
||||
|
||||
# FIXME: marshal check-summed eth addresses
|
||||
data_original['trustroot'] = data_original['trustroot'].lower()
|
||||
for k in ['realm', 'delegate', 'seeder']:
|
||||
data_original['reservation'][k] = data_original['reservation'][k].lower()
|
||||
|
||||
self.assertEqual(data_marshalled, data_original)
|
||||
@@ -0,0 +1,50 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from autobahn.wamp.exception import ApplicationError
|
||||
|
||||
|
||||
class ApplicationErrorTestCase(TestCase):
|
||||
|
||||
def test_unicode_str(self):
|
||||
"""
|
||||
Unicode arguments in ApplicationError will not raise an exception when
|
||||
str()'d.
|
||||
"""
|
||||
error = ApplicationError("some.url", "\u2603")
|
||||
self.assertIn("\u2603", str(error))
|
||||
|
||||
def test_unicode_errormessage(self):
|
||||
"""
|
||||
Unicode arguments in ApplicationError will not raise an exception when
|
||||
the error_message method is called.
|
||||
"""
|
||||
error = ApplicationError("some.url", "\u2603")
|
||||
# on py27-tw189: exceptions.UnicodeEncodeError: 'ascii' codec can't encode character '\u2603' in position 10: ordinal not in
|
||||
print(error.error_message())
|
||||
self.assertIn("\u2603", error.error_message())
|
||||
@@ -0,0 +1,98 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from autobahn.wamp.message import check_or_raise_realm_name, identify_realm_name_category
|
||||
from autobahn.wamp.exception import InvalidUriError
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestWampIdentifiers(unittest.TestCase):
|
||||
|
||||
def test_valid_realm_names(self):
|
||||
for name in [
|
||||
'realm1',
|
||||
'com.example.myapp1',
|
||||
'myapp1.example.com',
|
||||
'eth.wamp-proto',
|
||||
'wamp-proto.eth',
|
||||
'eth.wamp-proto.myapp1',
|
||||
'myapp1.wamp-proto.eth',
|
||||
'aaa',
|
||||
'Abc',
|
||||
'a00',
|
||||
'A00',
|
||||
'0x0000000000000000000000000000000000000000',
|
||||
'0xe59C7418403CF1D973485B36660728a5f4A8fF9c',
|
||||
]:
|
||||
self.assertEqual(name, check_or_raise_realm_name(name))
|
||||
|
||||
def test_invalid_realm_names(self):
|
||||
for name in [
|
||||
None,
|
||||
23,
|
||||
{},
|
||||
'',
|
||||
'.realm1',
|
||||
'123realm',
|
||||
'0x' + '00' * 64,
|
||||
'0x' + '00' * 32,
|
||||
'0x' + 'zz' * 40,
|
||||
'rlm$test',
|
||||
'a' * 256,
|
||||
]:
|
||||
self.assertRaises(InvalidUriError, check_or_raise_realm_name, name)
|
||||
|
||||
def test_realm_name_categories(self):
|
||||
for name, category in [
|
||||
# valid
|
||||
('realm1', 'standalone'),
|
||||
('com.example.myapp1', 'standalone'),
|
||||
('myapp1.example.com', 'standalone'),
|
||||
('eth.wamp-proto', 'reverse_ens'),
|
||||
('wamp-proto.eth', 'ens'),
|
||||
('eth.wamp-proto.myapp1', 'reverse_ens'),
|
||||
('myapp1.wamp-proto.eth', 'ens'),
|
||||
('aaa', 'standalone'),
|
||||
('Abc', 'standalone'),
|
||||
('a00', 'standalone'),
|
||||
('A00', 'standalone'),
|
||||
('0x0000000000000000000000000000000000000000', 'eth'),
|
||||
('0xe59C7418403CF1D973485B36660728a5f4A8fF9c', 'eth'),
|
||||
# invalid
|
||||
(None, None),
|
||||
(23, None),
|
||||
({}, None),
|
||||
('', None),
|
||||
('.realm1', None),
|
||||
('123realm', None),
|
||||
('0x' + '00' * 64, None),
|
||||
('0x' + '00' * 32, None),
|
||||
('0x' + 'zz' * 40, None),
|
||||
('rlm$test', None),
|
||||
('a' * 256, None),
|
||||
]:
|
||||
self.assertEqual(category, identify_realm_name_category(name))
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,104 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
# we need to select a txaio subsystem because we're importing the base
|
||||
# protocol classes here for testing purposes. "normally" yo'd import
|
||||
# from autobahn.twisted.wamp or autobahn.asyncio.wamp explicitly.
|
||||
from autobahn import wamp
|
||||
from autobahn.wamp import message
|
||||
from autobahn.wamp import exception
|
||||
from autobahn.wamp import protocol
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestPeerExceptions(unittest.TestCase):
|
||||
|
||||
def test_exception_from_message(self):
|
||||
session = protocol.BaseSession()
|
||||
|
||||
@wamp.error("com.myapp.error1")
|
||||
class AppError1(Exception):
|
||||
pass
|
||||
|
||||
@wamp.error("com.myapp.error2")
|
||||
class AppError2(Exception):
|
||||
pass
|
||||
|
||||
session.define(AppError1)
|
||||
session.define(AppError2)
|
||||
|
||||
# map defined errors to user exceptions
|
||||
emsg = message.Error(message.Call.MESSAGE_TYPE, 123456, 'com.myapp.error1')
|
||||
exc = session._exception_from_message(emsg)
|
||||
self.assertIsInstance(exc, AppError1)
|
||||
self.assertEqual(exc.args, ())
|
||||
|
||||
emsg = message.Error(message.Call.MESSAGE_TYPE, 123456, 'com.myapp.error2')
|
||||
exc = session._exception_from_message(emsg)
|
||||
self.assertIsInstance(exc, AppError2)
|
||||
self.assertEqual(exc.args, ())
|
||||
|
||||
# map undefined error to (generic) exception
|
||||
emsg = message.Error(message.Call.MESSAGE_TYPE, 123456, 'com.myapp.error3')
|
||||
exc = session._exception_from_message(emsg)
|
||||
self.assertIsInstance(exc, exception.ApplicationError)
|
||||
self.assertEqual(exc.error, 'com.myapp.error3')
|
||||
self.assertEqual(exc.args, ())
|
||||
self.assertEqual(exc.kwargs, {})
|
||||
|
||||
emsg = message.Error(message.Call.MESSAGE_TYPE, 123456, 'com.myapp.error3', args=[1, 2, 'hello'])
|
||||
exc = session._exception_from_message(emsg)
|
||||
self.assertIsInstance(exc, exception.ApplicationError)
|
||||
self.assertEqual(exc.error, 'com.myapp.error3')
|
||||
self.assertEqual(exc.args, (1, 2, 'hello'))
|
||||
self.assertEqual(exc.kwargs, {})
|
||||
|
||||
emsg = message.Error(message.Call.MESSAGE_TYPE, 123456, 'com.myapp.error3', args=[1, 2, 'hello'], kwargs={'foo': 23, 'bar': 'baz'})
|
||||
exc = session._exception_from_message(emsg)
|
||||
self.assertIsInstance(exc, exception.ApplicationError)
|
||||
self.assertEqual(exc.error, 'com.myapp.error3')
|
||||
self.assertEqual(exc.args, (1, 2, 'hello'))
|
||||
self.assertEqual(exc.kwargs, {'foo': 23, 'bar': 'baz'})
|
||||
|
||||
def test_message_from_exception(self):
|
||||
session = protocol.BaseSession()
|
||||
|
||||
@wamp.error("com.myapp.error1")
|
||||
class AppError1(Exception):
|
||||
pass
|
||||
|
||||
@wamp.error("com.myapp.error2")
|
||||
class AppError2(Exception):
|
||||
pass
|
||||
|
||||
session.define(AppError1)
|
||||
session.define(AppError2)
|
||||
|
||||
exc = AppError1()
|
||||
msg = session._message_from_exception(message.Call.MESSAGE_TYPE, 123456, exc)
|
||||
|
||||
self.assertEqual(msg.marshal(), [message.Error.MESSAGE_TYPE, message.Call.MESSAGE_TYPE, 123456, {}, "com.myapp.error1"])
|
||||
@@ -0,0 +1,72 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import unittest
|
||||
from binascii import a2b_hex
|
||||
|
||||
from autobahn.wamp.auth import derive_scram_credential
|
||||
|
||||
|
||||
TEST_VECTORS = [
|
||||
{
|
||||
'email': 'foobar@example.com',
|
||||
'password': 'secret123',
|
||||
'salt': None,
|
||||
'expected': {
|
||||
'iterations': 4096,
|
||||
'kdf': 'argon2id-13',
|
||||
'memory': 512,
|
||||
'salt': '3bc3ca01dd1d501ca1c22e1c5d7d16fe',
|
||||
'server-key': '8de7864c316f3c2356fd76cfdab696db55bc70e680fe5180e2f731e2345acca2',
|
||||
'stored-key': 'e796c2f0a51770303ee4616bc630a66774d51a55003154aff2a54ec7c4ac0e38'
|
||||
}
|
||||
},
|
||||
{
|
||||
'email': 'foobar@example.com',
|
||||
'password': 'secret123',
|
||||
'salt': a2b_hex('ae1f0d2f422757809077785e660b62c6'),
|
||||
'expected': {
|
||||
'iterations': 4096,
|
||||
'kdf': 'argon2id-13',
|
||||
'memory': 512,
|
||||
'salt': 'ae1f0d2f422757809077785e660b62c6',
|
||||
'server-key': '0d8e7e9222a7c0e54c9e979aa342115699ff5696c45dc379b5ee241338a5861d',
|
||||
'stored-key': '5f19358ff6f38e267b6ef1ea1d862514ec4e8745a84682259fd3894be09febb5'
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class TestKey(unittest.TestCase):
|
||||
|
||||
def test_derive_scram_credential(self):
|
||||
for tv in TEST_VECTORS:
|
||||
email = tv['email']
|
||||
password = tv['password']
|
||||
salt = tv['salt']
|
||||
expected = tv['expected']
|
||||
credential = derive_scram_credential(email, password, salt)
|
||||
self.assertEqual(credential, expected)
|
||||
@@ -0,0 +1,546 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import random
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
|
||||
from autobahn.wamp import message
|
||||
from autobahn.wamp import role
|
||||
from autobahn.wamp import serializer
|
||||
|
||||
|
||||
def generate_test_messages():
|
||||
"""
|
||||
List of WAMP test message used for serializers. Expand this if you add more
|
||||
options or messages.
|
||||
|
||||
This list of WAMP message does not contain any binary app payloads!
|
||||
"""
|
||||
some_bytes = os.urandom(32)
|
||||
some_unicode = '\u3053\u3093\u306b\u3061\u306f\u4e16\u754c'
|
||||
|
||||
some_uri = 'com.myapp.foobar'
|
||||
some_unicode_uri = 'com.myapp.\u4f60\u597d\u4e16\u754c.baz'
|
||||
|
||||
some_args = [1, 2, 3, 'hello', some_bytes, some_unicode, {'foo': 23, 'bar': 'hello', 'baz': some_bytes, 'moo': some_unicode}]
|
||||
some_kwargs = {'foo': 23, 'bar': 'hello', 'baz': some_bytes, 'moo': some_unicode, 'arr': some_args}
|
||||
|
||||
msgs = [
|
||||
message.Hello("realm1", {'subscriber': role.RoleSubscriberFeatures()}),
|
||||
message.Hello("realm1", {'publisher': role.RolePublisherFeatures()}),
|
||||
message.Hello("realm1", {'caller': role.RoleCallerFeatures()}),
|
||||
message.Hello("realm1", {'callee': role.RoleCalleeFeatures()}),
|
||||
message.Hello("realm1", {
|
||||
'subscriber': role.RoleSubscriberFeatures(),
|
||||
'publisher': role.RolePublisherFeatures(),
|
||||
'caller': role.RoleCallerFeatures(),
|
||||
'callee': role.RoleCalleeFeatures(),
|
||||
}),
|
||||
message.Goodbye(),
|
||||
message.Yield(123456),
|
||||
message.Yield(123456, args=some_args),
|
||||
message.Yield(123456, args=[], kwargs=some_kwargs),
|
||||
message.Yield(123456, args=some_args, kwargs=some_kwargs),
|
||||
message.Yield(123456, progress=True),
|
||||
message.Interrupt(123456),
|
||||
message.Interrupt(123456, mode=message.Interrupt.KILL),
|
||||
message.Invocation(123456, 789123),
|
||||
message.Invocation(123456, 789123, args=some_args),
|
||||
message.Invocation(123456, 789123, args=[], kwargs=some_kwargs),
|
||||
message.Invocation(123456, 789123, args=some_args, kwargs=some_kwargs),
|
||||
message.Invocation(123456, 789123, timeout=10000),
|
||||
message.Result(123456),
|
||||
message.Result(123456, args=some_args),
|
||||
message.Result(123456, args=[], kwargs=some_kwargs),
|
||||
message.Result(123456, args=some_args, kwargs=some_kwargs),
|
||||
message.Result(123456, progress=True),
|
||||
message.Cancel(123456),
|
||||
message.Cancel(123456, mode=message.Cancel.KILL),
|
||||
message.Call(123456, some_uri),
|
||||
message.Call(123456, some_uri, args=some_args),
|
||||
message.Call(123456, some_uri, args=[], kwargs=some_kwargs),
|
||||
message.Call(123456, some_uri, args=some_args, kwargs=some_kwargs),
|
||||
message.Call(123456, some_uri, timeout=10000),
|
||||
message.Call(123456, some_unicode_uri),
|
||||
message.Call(123456, some_unicode_uri, args=some_args),
|
||||
message.Call(123456, some_unicode_uri, args=[], kwargs=some_kwargs),
|
||||
message.Call(123456, some_unicode_uri, args=some_args, kwargs=some_kwargs),
|
||||
message.Call(123456, some_unicode_uri, timeout=10000),
|
||||
message.Unregistered(123456),
|
||||
message.Unregister(123456, 789123),
|
||||
message.Registered(123456, 789123),
|
||||
message.Register(123456, some_uri),
|
||||
message.Register(123456, some_uri, match='prefix'),
|
||||
message.Register(123456, some_uri, invoke='roundrobin'),
|
||||
message.Register(123456, some_unicode_uri),
|
||||
message.Register(123456, some_unicode_uri, match='prefix'),
|
||||
message.Register(123456, some_unicode_uri, invoke='roundrobin'),
|
||||
message.Event(123456, 789123),
|
||||
message.Event(123456, 789123, args=some_args),
|
||||
message.Event(123456, 789123, args=[], kwargs=some_kwargs),
|
||||
message.Event(123456, 789123, args=some_args, kwargs=some_kwargs),
|
||||
message.Event(123456, 789123, publisher=300),
|
||||
message.Published(123456, 789123),
|
||||
message.Publish(123456, some_uri),
|
||||
message.Publish(123456, some_uri, args=some_args),
|
||||
message.Publish(123456, some_uri, args=[], kwargs=some_kwargs),
|
||||
message.Publish(123456, some_uri, args=some_args, kwargs=some_kwargs),
|
||||
message.Publish(123456, some_uri, exclude_me=False, exclude=[300], eligible=[100, 200, 300]),
|
||||
message.Publish(123456, some_unicode_uri),
|
||||
message.Publish(123456, some_unicode_uri, args=some_args),
|
||||
message.Publish(123456, some_unicode_uri, args=[], kwargs=some_kwargs),
|
||||
message.Publish(123456, some_unicode_uri, args=some_args, kwargs=some_kwargs),
|
||||
message.Publish(123456, some_unicode_uri, exclude_me=False, exclude=[300], eligible=[100, 200, 300]),
|
||||
message.Unsubscribed(123456),
|
||||
message.Unsubscribe(123456, 789123),
|
||||
message.Subscribed(123456, 789123),
|
||||
message.Subscribe(123456, some_uri),
|
||||
message.Subscribe(123456, some_uri, match=message.Subscribe.MATCH_PREFIX),
|
||||
message.Subscribe(123456, some_unicode_uri),
|
||||
message.Subscribe(123456, some_unicode_uri, match=message.Subscribe.MATCH_PREFIX),
|
||||
message.Error(message.Call.MESSAGE_TYPE, 123456, some_uri),
|
||||
message.Error(message.Call.MESSAGE_TYPE, 123456, some_uri, args=some_args),
|
||||
message.Error(message.Call.MESSAGE_TYPE, 123456, some_uri, args=[], kwargs=some_kwargs),
|
||||
message.Error(message.Call.MESSAGE_TYPE, 123456, some_uri, args=some_args, kwargs=some_kwargs),
|
||||
message.Error(message.Call.MESSAGE_TYPE, 123456, some_unicode_uri),
|
||||
message.Error(message.Call.MESSAGE_TYPE, 123456, some_unicode_uri, args=some_args),
|
||||
message.Error(message.Call.MESSAGE_TYPE, 123456, some_unicode_uri, args=[], kwargs=some_kwargs),
|
||||
message.Error(message.Call.MESSAGE_TYPE, 123456, some_unicode_uri, args=some_args, kwargs=some_kwargs),
|
||||
message.Result(123456),
|
||||
message.Result(123456, args=some_args),
|
||||
message.Result(123456, args=some_args, kwargs=some_kwargs),
|
||||
]
|
||||
return [(False, msg) for msg in msgs]
|
||||
|
||||
|
||||
def generate_test_messages_binary():
|
||||
"""
|
||||
Generate WAMP test messages which contain binary app payloads.
|
||||
|
||||
With the JSON serializer, this currently only works on Python 3 (both CPython3 and PyPy3),
|
||||
because even on Python 3, we need to patch the stdlib JSON, and on Python 2, the patching
|
||||
would be even hackier.
|
||||
"""
|
||||
msgs = []
|
||||
for binary in [b'',
|
||||
b'\x00',
|
||||
b'\30',
|
||||
os.urandom(4),
|
||||
os.urandom(16),
|
||||
os.urandom(128),
|
||||
os.urandom(256),
|
||||
os.urandom(512),
|
||||
os.urandom(1024)]:
|
||||
msgs.append(message.Event(123456, 789123, args=[binary]))
|
||||
msgs.append(message.Event(123456, 789123, args=[binary], kwargs={'foo': binary}))
|
||||
return [(True, msg) for msg in msgs]
|
||||
|
||||
|
||||
def create_serializers(decimal_support=False):
|
||||
_serializers = []
|
||||
|
||||
_serializers.append(serializer.JsonSerializer(use_decimal_from_str=decimal_support))
|
||||
_serializers.append(serializer.JsonSerializer(batched=True, use_decimal_from_str=decimal_support))
|
||||
|
||||
_serializers.append(serializer.CBORSerializer())
|
||||
_serializers.append(serializer.CBORSerializer(batched=True))
|
||||
|
||||
if not decimal_support:
|
||||
# builtins.OverflowError: Integer value out of range
|
||||
_serializers.append(serializer.MsgPackSerializer())
|
||||
_serializers.append(serializer.MsgPackSerializer(batched=True))
|
||||
|
||||
# roundtrip error
|
||||
_serializers.append(serializer.UBJSONSerializer())
|
||||
_serializers.append(serializer.UBJSONSerializer(batched=True))
|
||||
|
||||
# FIXME: implement full FlatBuffers serializer for WAMP
|
||||
# WAMP-FlatBuffers currently only supports Python 3
|
||||
# _serializers.append(serializer.FlatBuffersSerializer())
|
||||
# _serializers.append(serializer.FlatBuffersSerializer(batched=True))
|
||||
|
||||
return _serializers
|
||||
|
||||
|
||||
class TestFlatBuffersSerializer(unittest.TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
messages = [
|
||||
message.Event(123456,
|
||||
789123,
|
||||
args=[1, 2, 3],
|
||||
kwargs={'foo': 23, 'bar': 'hello'},
|
||||
publisher=666,
|
||||
retained=True),
|
||||
message.Publish(123456,
|
||||
'com.example.topic1',
|
||||
args=[1, 2, 3],
|
||||
kwargs={'foo': 23, 'bar': 'hello'},
|
||||
retain=True)
|
||||
]
|
||||
|
||||
ser = serializer.FlatBuffersSerializer()
|
||||
|
||||
# from pprint import pprint
|
||||
|
||||
for msg in messages:
|
||||
|
||||
# serialize message
|
||||
payload, binary = ser.serialize(msg)
|
||||
|
||||
# unserialize message again
|
||||
msg2 = ser.unserialize(payload, binary)[0]
|
||||
|
||||
# pprint(msg.marshal())
|
||||
# pprint(msg2.marshal())
|
||||
|
||||
# must be equal: message roundtrips via the serializer
|
||||
self.assertEqual(msg, msg2)
|
||||
# self.assertEqual(msg.subscription, msg2.subscription)
|
||||
# self.assertEqual(msg.publication, msg2.publication)
|
||||
|
||||
|
||||
class TestDecimalSerializer(unittest.TestCase):
|
||||
"""
|
||||
binary fixed-point
|
||||
binary floating-point: float (float32), double (float64)
|
||||
decimal floating-point: decimal128, decimal256
|
||||
decimal fixed-point: NUMERIC(precision, scale)
|
||||
decimal arbitrary precision: NUMERIC, decimal.Decimal
|
||||
|
||||
https://developer.nvidia.com/blog/implementing-high-precision-decimal-arithmetic-with-cuda-int128/
|
||||
https://github.com/johnmcfarlane/cnl
|
||||
"""
|
||||
def setUp(self) -> None:
|
||||
self._test_serializers = create_serializers(decimal_support=True)
|
||||
|
||||
# enough for decimal256 precision arithmetic (76 significand decimal digits)
|
||||
decimal.getcontext().prec = 76
|
||||
|
||||
self._test_messages_no_dec = [
|
||||
(True,
|
||||
{
|
||||
'a': random.random(),
|
||||
'b': random.randint(0, 2 ** 53),
|
||||
'c': random.randint(0, 2 ** 64),
|
||||
'd': random.randint(0, 2 ** 128),
|
||||
'e': random.randint(0, 2 ** 256),
|
||||
# float64: 52 binary digits, precision of 15-17 significant decimal digits
|
||||
'f': 0.12345678901234567,
|
||||
'g': 0.8765432109876545,
|
||||
'y': os.urandom(8),
|
||||
'z': [
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
True,
|
||||
None,
|
||||
0.12345678901234567,
|
||||
0.8765432109876545,
|
||||
os.urandom(8)
|
||||
]
|
||||
})
|
||||
]
|
||||
self._test_messages_dec = [
|
||||
(True,
|
||||
{
|
||||
'a': random.random(),
|
||||
'b': random.randint(0, 2 ** 53),
|
||||
'c': random.randint(0, 2 ** 64),
|
||||
'd': random.randint(0, 2 ** 128),
|
||||
'e': random.randint(0, 2 ** 256),
|
||||
# float64: 52 binary digits, precision of 15-17 significant decimal digits
|
||||
'f': 0.12345678901234567,
|
||||
'g': 0.8765432109876545,
|
||||
# decimal128: precision of 38 significant decimal digits
|
||||
'h': Decimal('0.1234567890123456789012345678901234567'),
|
||||
'i': Decimal('0.8765432109876543210987654321098765434'),
|
||||
# decimal256: precision of 76 significant decimal digits
|
||||
'j': Decimal('0.123456789012345678901234567890123456701234567890123456789012345678901234567'),
|
||||
'k': Decimal('0.876543210987654321098765432109876543298765432109876543210987654321098765434'),
|
||||
'y': os.urandom(8),
|
||||
'z': [
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
True,
|
||||
None,
|
||||
0.12345678901234567,
|
||||
0.8765432109876545,
|
||||
Decimal('0.1234567890123456789012345678901234567'),
|
||||
Decimal('0.8765432109876543210987654321098765434'),
|
||||
Decimal('0.123456789012345678901234567890123456701234567890123456789012345678901234567'),
|
||||
Decimal('0.876543210987654321098765432109876543298765432109876543210987654321098765434'),
|
||||
os.urandom(8)
|
||||
]
|
||||
})
|
||||
]
|
||||
|
||||
def test_json_no_decimal(self):
|
||||
"""
|
||||
Test without ``use_decimal_from_str`` feature of JSON object serializer.
|
||||
"""
|
||||
ser = serializer.JsonObjectSerializer(use_decimal_from_str=False)
|
||||
for contains_binary, obj in self._test_messages_no_dec:
|
||||
_obj = ser.unserialize(ser.serialize(obj))[0]
|
||||
self.assertEqual(obj, _obj)
|
||||
self.assertEqual(1.0000000000000002, _obj['f'] + _obj['g'])
|
||||
|
||||
def test_json_decimal(self):
|
||||
"""
|
||||
Test ``use_decimal_from_str`` feature of JSON object serializer.
|
||||
"""
|
||||
ser = serializer.JsonObjectSerializer(use_decimal_from_str=True)
|
||||
for contains_binary, obj in self._test_messages_dec:
|
||||
_obj = ser.unserialize(ser.serialize(obj))[0]
|
||||
self.assertEqual(obj, _obj)
|
||||
self.assertEqual(1.0000000000000002, _obj['f'] + _obj['g'])
|
||||
self.assertEqual(Decimal('1.0000000000000000000000000000000000001'), _obj['h'] + _obj['i'])
|
||||
self.assertEqual(Decimal('1.000000000000000000000000000000000000000000000000000000000000000000000000001'), _obj['j'] + _obj['k'])
|
||||
|
||||
def test_roundtrip_msg(self):
|
||||
for wamp_ser in self._test_serializers:
|
||||
ser = wamp_ser._serializer
|
||||
for contains_binary, msg in self._test_messages_no_dec + self._test_messages_dec:
|
||||
payload = ser.serialize(msg)
|
||||
msg2 = ser.unserialize(payload)
|
||||
self.assertEqual(msg, msg2[0])
|
||||
|
||||
def test_crosstrip_msg(self):
|
||||
for wamp_ser1 in self._test_serializers:
|
||||
ser1 = wamp_ser1._serializer
|
||||
for contains_binary, msg in self._test_messages_no_dec + self._test_messages_dec:
|
||||
payload1 = ser1.serialize(msg)
|
||||
msg1 = ser1.unserialize(payload1)
|
||||
msg1 = msg1[0]
|
||||
for wamp_ser2 in self._test_serializers:
|
||||
ser2 = wamp_ser2._serializer
|
||||
payload2 = ser2.serialize(msg1)
|
||||
msg2 = ser2.unserialize(payload2)
|
||||
msg2 = msg2[0]
|
||||
self.assertEqual(msg, msg2)
|
||||
# print(ser1, len(payload1), ser2, len(payload2))
|
||||
|
||||
|
||||
class TestSerializer(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._test_messages = generate_test_messages() + generate_test_messages_binary()
|
||||
self._test_serializers = create_serializers()
|
||||
# print('Testing WAMP serializers {} with {} WAMP test messages'.format([ser.SERIALIZER_ID for ser in self._test_serializers], len(self._test_messages)))
|
||||
|
||||
def test_deep_equal_msg(self):
|
||||
"""
|
||||
Test deep object equality assert (because I am paranoid).
|
||||
"""
|
||||
v = os.urandom(10)
|
||||
o1 = [1, 2, {'foo': 'bar', 'bar': v, 'baz': [9, 3, 2], 'goo': {'moo': [1, 2, 3]}}, v]
|
||||
o2 = [1, 2, {'goo': {'moo': [1, 2, 3]}, 'bar': v, 'baz': [9, 3, 2], 'foo': 'bar'}, v]
|
||||
self.assertEqual(o1, o2)
|
||||
|
||||
def test_roundtrip_msg(self):
|
||||
"""
|
||||
Test round-tripping over each serializer.
|
||||
"""
|
||||
for ser in self._test_serializers:
|
||||
|
||||
for contains_binary, msg in self._test_messages:
|
||||
|
||||
# serialize message
|
||||
payload, binary = ser.serialize(msg)
|
||||
|
||||
# unserialize message again
|
||||
msg2 = ser.unserialize(payload, binary)
|
||||
|
||||
# must be equal: message roundtrips via the serializer
|
||||
self.assertEqual([msg], msg2)
|
||||
|
||||
def test_crosstrip_msg(self):
|
||||
"""
|
||||
Test cross-tripping over 2 serializers (as is done by WAMP routers).
|
||||
"""
|
||||
for ser1 in self._test_serializers:
|
||||
|
||||
for contains_binary, msg in self._test_messages:
|
||||
|
||||
# serialize message
|
||||
payload, binary = ser1.serialize(msg)
|
||||
|
||||
# unserialize message again
|
||||
msg1 = ser1.unserialize(payload, binary)
|
||||
msg1 = msg1[0]
|
||||
|
||||
for ser2 in self._test_serializers:
|
||||
|
||||
# serialize message
|
||||
payload, binary = ser2.serialize(msg1)
|
||||
|
||||
# unserialize message again
|
||||
msg2 = ser2.unserialize(payload, binary)
|
||||
|
||||
# must be equal: message crosstrips via
|
||||
# the serializers ser1 -> ser2
|
||||
self.assertEqual([msg], msg2)
|
||||
|
||||
def test_cache_msg(self):
|
||||
"""
|
||||
Test message serialization caching.
|
||||
"""
|
||||
for contains_binary, msg in self._test_messages:
|
||||
|
||||
# message serialization cache is initially empty
|
||||
self.assertEqual(msg._serialized, {})
|
||||
|
||||
for ser in self._test_serializers:
|
||||
|
||||
# verify message serialization is not yet cached
|
||||
self.assertFalse(ser._serializer in msg._serialized)
|
||||
payload, binary = ser.serialize(msg)
|
||||
|
||||
# now the message serialization must be cached
|
||||
self.assertTrue(ser._serializer in msg._serialized)
|
||||
self.assertEqual(msg._serialized[ser._serializer], payload)
|
||||
|
||||
# and after resetting the serialization cache, message
|
||||
# serialization is gone
|
||||
msg.uncache()
|
||||
self.assertFalse(ser._serializer in msg._serialized)
|
||||
|
||||
def test_initial_stats(self):
|
||||
"""
|
||||
Test initial serializer stats are indeed empty.
|
||||
"""
|
||||
for ser in self._test_serializers:
|
||||
|
||||
stats = ser.stats(details=True)
|
||||
|
||||
self.assertEqual(stats['serialized']['bytes'], 0)
|
||||
self.assertEqual(stats['serialized']['messages'], 0)
|
||||
self.assertEqual(stats['serialized']['rated_messages'], 0)
|
||||
|
||||
self.assertEqual(stats['unserialized']['bytes'], 0)
|
||||
self.assertEqual(stats['unserialized']['messages'], 0)
|
||||
self.assertEqual(stats['unserialized']['rated_messages'], 0)
|
||||
|
||||
def test_serialize_stats(self):
|
||||
"""
|
||||
Test serializer stats are non-empty after serializing/unserializing messages.
|
||||
"""
|
||||
for ser in self._test_serializers:
|
||||
|
||||
for contains_binary, msg in self._test_messages:
|
||||
|
||||
# serialize message
|
||||
payload, binary = ser.serialize(msg)
|
||||
|
||||
# unserialize message again
|
||||
ser.unserialize(payload, binary)
|
||||
|
||||
stats = ser.stats(details=False)
|
||||
|
||||
self.assertTrue(stats['bytes'] > 0)
|
||||
self.assertTrue(stats['messages'] > 0)
|
||||
self.assertTrue(stats['rated_messages'] > 0)
|
||||
|
||||
def test_serialize_stats_with_details(self):
|
||||
"""
|
||||
Test serializer stats - with details - are non-empty after serializing/unserializing messages.
|
||||
"""
|
||||
for ser in self._test_serializers:
|
||||
|
||||
for contains_binary, msg in self._test_messages:
|
||||
|
||||
# serialize message
|
||||
payload, binary = ser.serialize(msg)
|
||||
|
||||
# unserialize message again
|
||||
ser.unserialize(payload, binary)
|
||||
|
||||
stats = ser.stats(details=True)
|
||||
|
||||
# {'serialized': {'bytes': 7923, 'messages': 59, 'rated_messages': 69}, 'unserialized': {'bytes': 7923, 'messages': 59, 'rated_messages': 69}}
|
||||
# print(stats)
|
||||
|
||||
self.assertTrue(stats['serialized']['bytes'] > 0)
|
||||
self.assertTrue(stats['serialized']['messages'] > 0)
|
||||
self.assertTrue(stats['serialized']['rated_messages'] > 0)
|
||||
|
||||
self.assertTrue(stats['unserialized']['bytes'] > 0)
|
||||
self.assertTrue(stats['unserialized']['messages'] > 0)
|
||||
self.assertTrue(stats['unserialized']['rated_messages'] > 0)
|
||||
|
||||
self.assertEqual(stats['serialized']['bytes'], stats['unserialized']['bytes'])
|
||||
self.assertEqual(stats['serialized']['messages'], stats['unserialized']['messages'])
|
||||
self.assertEqual(stats['serialized']['rated_messages'], stats['unserialized']['rated_messages'])
|
||||
|
||||
def test_reset_stats(self):
|
||||
"""
|
||||
Test serializer stats are reset after fetching stats - depending on option.
|
||||
"""
|
||||
for ser in self._test_serializers:
|
||||
|
||||
for contains_binary, msg in self._test_messages:
|
||||
|
||||
# serialize message
|
||||
payload, binary = ser.serialize(msg)
|
||||
|
||||
# unserialize message again
|
||||
ser.unserialize(payload, binary)
|
||||
|
||||
ser.stats()
|
||||
stats = ser.stats(details=True)
|
||||
|
||||
self.assertEqual(stats['serialized']['bytes'], 0)
|
||||
self.assertEqual(stats['serialized']['messages'], 0)
|
||||
self.assertEqual(stats['serialized']['rated_messages'], 0)
|
||||
|
||||
self.assertEqual(stats['unserialized']['bytes'], 0)
|
||||
self.assertEqual(stats['unserialized']['messages'], 0)
|
||||
self.assertEqual(stats['unserialized']['rated_messages'], 0)
|
||||
|
||||
def test_auto_stats(self):
|
||||
"""
|
||||
Test serializer stats are non-empty after serializing/unserializing messages.
|
||||
"""
|
||||
for ser in self._test_serializers:
|
||||
|
||||
def on_stats(stats):
|
||||
self.assertTrue(stats['bytes'] > 0)
|
||||
self.assertTrue(stats['messages'] > 0)
|
||||
self.assertTrue(stats['rated_messages'] > 0)
|
||||
|
||||
ser.set_stats_autoreset(10, 0, on_stats)
|
||||
|
||||
for contains_binary, msg in self._test_messages:
|
||||
|
||||
# serialize message
|
||||
payload, binary = ser.serialize(msg)
|
||||
|
||||
# unserialize message again
|
||||
ser.unserialize(payload, binary)
|
||||
@@ -0,0 +1,88 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from autobahn.wamp.types import TransportDetails, SessionDetails
|
||||
|
||||
import unittest
|
||||
|
||||
# from .test_wamp_transport_details import TRANSPORT_DETAILS_1
|
||||
from autobahn.wamp.test.test_wamp_transport_details import TRANSPORT_DETAILS_1
|
||||
|
||||
|
||||
class TestSessionDetails(unittest.TestCase):
|
||||
|
||||
def test_empty(self):
|
||||
sd1 = SessionDetails()
|
||||
data = sd1.marshal()
|
||||
self.assertEqual(data, {
|
||||
'realm': None,
|
||||
'session': None,
|
||||
'authid': None,
|
||||
'authrole': None,
|
||||
'authmethod': None,
|
||||
'authprovider': None,
|
||||
'authextra': None,
|
||||
'serializer': None,
|
||||
'transport': None,
|
||||
'resumed': None,
|
||||
'resumable': None,
|
||||
'resume_token': None,
|
||||
})
|
||||
sd2 = SessionDetails.parse(data)
|
||||
self.assertEqual(sd2, sd1)
|
||||
|
||||
def test_attributes(self):
|
||||
sd1 = SessionDetails()
|
||||
td1 = TransportDetails.parse(TRANSPORT_DETAILS_1)
|
||||
sd1.realm = 'realm1'
|
||||
sd1.session = 666
|
||||
sd1.authid = 'homer'
|
||||
sd1.authrole = 'user'
|
||||
sd1.authmethod = 'wampcra'
|
||||
sd1.authprovider = 'static'
|
||||
sd1.authextra = {'foo': 'bar', 'baz': [1, 2, 3]}
|
||||
sd1.serializer = 'json'
|
||||
sd1.transport = td1
|
||||
sd1.resumed = False
|
||||
sd1.resumable = True
|
||||
sd1.resume_token = '8713e25a-d4f5-48b7-9d6d-eda66603a1ab'
|
||||
data = sd1.marshal()
|
||||
self.assertEqual(data, {
|
||||
'realm': sd1.realm,
|
||||
'session': sd1.session,
|
||||
'authid': sd1.authid,
|
||||
'authrole': sd1.authrole,
|
||||
'authmethod': sd1.authmethod,
|
||||
'authprovider': sd1.authprovider,
|
||||
'authextra': sd1.authextra,
|
||||
'serializer': sd1.serializer,
|
||||
'transport': sd1.transport.marshal(),
|
||||
'resumed': sd1.resumed,
|
||||
'resumable': sd1.resumable,
|
||||
'resume_token': sd1.resume_token,
|
||||
})
|
||||
sd2 = SessionDetails.parse(data)
|
||||
self.assertEqual(sd2, sd1)
|
||||
@@ -0,0 +1,138 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
|
||||
import unittest
|
||||
|
||||
TRANSPORT_DETAILS_1 = {
|
||||
# TransportDetails.CHANNEL_TYPE_TO_STR[TransportDetails.CHANNEL_TYPE_TCP]
|
||||
'channel_type': 'tcp',
|
||||
|
||||
# TransportDetails.CHANNEL_FRAMING_TO_STR[TransportDetails.CHANNEL_FRAMING_WEBSOCKET]
|
||||
'channel_framing': 'websocket',
|
||||
|
||||
# TransportDetails.CHANNEL_SERIALIZER_TO_STR[TransportDetails.CHANNEL_SERIALIZER_CBOR]
|
||||
'channel_serializer': 'cbor',
|
||||
|
||||
# This end of the connection
|
||||
'own': 'ws://localhost:8080/ws',
|
||||
'own_pid': 9182731,
|
||||
'own_tid': 7563483,
|
||||
'own_fd': 20914571,
|
||||
|
||||
# Peer of the connection
|
||||
'peer': 'tcp4:127.0.0.1:48576',
|
||||
'is_server': True,
|
||||
|
||||
# TLS
|
||||
'is_secure': False,
|
||||
'channel_id': None,
|
||||
'peer_cert': None,
|
||||
|
||||
# only filled when using WebSocket
|
||||
'websocket_protocol': 'wamp.2.cbor.batched',
|
||||
'websocket_extensions_in_use': None,
|
||||
|
||||
# only filled when using HTTP (including regular WebSocket)
|
||||
'http_headers_received': {'cache-control': 'no-cache',
|
||||
'connection': 'Upgrade',
|
||||
'host': 'localhost:8080',
|
||||
'pragma': 'no-cache',
|
||||
'sec-websocket-extensions': 'permessage-deflate; '
|
||||
'client_no_context_takeover; '
|
||||
'client_max_window_bits',
|
||||
'sec-websocket-key': 'Q+t++aGQJPaFLzDW7LktEQ==',
|
||||
'sec-websocket-protocol': 'wamp.2.cbor.batched,wamp.2.cbor,wamp.2.msgpack.batched,wamp.2.msgpack,wamp.2.ubjson.batched,wamp.2.ubjson,wamp.2.json.batched,wamp.2.json',
|
||||
'sec-websocket-version': '13',
|
||||
'upgrade': 'WebSocket',
|
||||
'user-agent': 'AutobahnPython/22.4.1.dev5'},
|
||||
'http_headers_sent': {'Set-Cookie': 'cbtid=JD27oZC18xS+O4VE9+x5iyKR;max-age=604800'},
|
||||
'http_cbtid': 'JD27oZC18xS+O4VE9+x5iyKR',
|
||||
}
|
||||
|
||||
|
||||
class TestTransportDetails(unittest.TestCase):
|
||||
|
||||
def test_ctor_empty(self):
|
||||
td = TransportDetails()
|
||||
data = td.marshal()
|
||||
self.assertEqual(data, {
|
||||
'channel_type': None,
|
||||
'channel_framing': None,
|
||||
'channel_serializer': None,
|
||||
'own': None,
|
||||
'peer': None,
|
||||
'is_server': None,
|
||||
'own_pid': None,
|
||||
'own_tid': None,
|
||||
'own_fd': None,
|
||||
'is_secure': None,
|
||||
'channel_id': None,
|
||||
'peer_cert': None,
|
||||
'websocket_protocol': None,
|
||||
'websocket_extensions_in_use': None,
|
||||
'http_headers_received': None,
|
||||
'http_headers_sent': None,
|
||||
'http_cbtid': None,
|
||||
})
|
||||
td2 = TransportDetails.parse(td.marshal())
|
||||
self.assertEqual(td2, td)
|
||||
|
||||
def test_attributes(self):
|
||||
td = TransportDetails()
|
||||
for channel_type in TransportDetails.CHANNEL_TYPE_TO_STR:
|
||||
td.channel_type = channel_type
|
||||
self.assertEqual(td.channel_type, channel_type)
|
||||
|
||||
for channel_framing in TransportDetails.CHANNEL_FRAMING_TO_STR:
|
||||
td.channel_framing = channel_framing
|
||||
self.assertEqual(td.channel_framing, channel_framing)
|
||||
|
||||
for channel_serializer in TransportDetails.CHANNEL_SERIALIZER_TO_STR:
|
||||
td.channel_serializer = channel_serializer
|
||||
self.assertEqual(td.channel_serializer, channel_serializer)
|
||||
|
||||
def test_parse(self):
|
||||
td = TransportDetails.parse(TRANSPORT_DETAILS_1)
|
||||
data2 = td.marshal()
|
||||
self.maxDiff = None
|
||||
self.assertEqual(data2, TRANSPORT_DETAILS_1)
|
||||
|
||||
def test_channel_typeid(self):
|
||||
# test empty
|
||||
td = TransportDetails()
|
||||
self.assertEqual(td.channel_typeid, 'null-null-null')
|
||||
|
||||
# test all combinations
|
||||
for channel_type in TransportDetails.CHANNEL_TYPE_TO_STR:
|
||||
for channel_framing in TransportDetails.CHANNEL_FRAMING_TO_STR:
|
||||
for channel_serializer in TransportDetails.CHANNEL_SERIALIZER_TO_STR:
|
||||
td = TransportDetails(channel_type=channel_type, channel_framing=channel_framing, channel_serializer=channel_serializer)
|
||||
channel_typeid = '{}-{}-{}'.format(TransportDetails.CHANNEL_TYPE_TO_STR[channel_type],
|
||||
TransportDetails.CHANNEL_FRAMING_TO_STR[channel_framing],
|
||||
TransportDetails.CHANNEL_SERIALIZER_TO_STR[channel_serializer])
|
||||
self.assertEqual(td.channel_typeid, channel_typeid)
|
||||
@@ -0,0 +1,573 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from autobahn import wamp
|
||||
from autobahn.wamp.uri import Pattern, RegisterOptions, SubscribeOptions
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class TestUris(unittest.TestCase):
|
||||
|
||||
def test_invalid_uris(self):
|
||||
for u in ["",
|
||||
"com.myapp.<product:foo>.update",
|
||||
"com.myapp.<123:int>.update",
|
||||
"com.myapp.<:product>.update",
|
||||
"com.myapp.<product:>.update",
|
||||
"com.myapp.<int:>.update",
|
||||
]:
|
||||
self.assertRaises(Exception, Pattern, u, Pattern.URI_TARGET_ENDPOINT)
|
||||
|
||||
def test_valid_uris(self):
|
||||
for u in ["com.myapp.proc1",
|
||||
"123",
|
||||
"com.myapp.<product:int>.update",
|
||||
"com.myapp.<category:string>.<subcategory>.list"
|
||||
"com.myapp.something..update"
|
||||
]:
|
||||
p = Pattern(u, Pattern.URI_TARGET_ENDPOINT)
|
||||
self.assertIsInstance(p, Pattern)
|
||||
|
||||
def test_parse_uris(self):
|
||||
tests = [
|
||||
("com.myapp.<product:int>.update", [
|
||||
("com.myapp.0.update", {'product': 0}),
|
||||
("com.myapp.123456.update", {'product': 123456}),
|
||||
("com.myapp.aaa.update", None),
|
||||
("com.myapp..update", None),
|
||||
("com.myapp.0.delete", None),
|
||||
]),
|
||||
("com.myapp.<product:string>.update", [
|
||||
("com.myapp.box.update", {'product': 'box'}),
|
||||
("com.myapp.123456.update", {'product': '123456'}),
|
||||
("com.myapp..update", None),
|
||||
]),
|
||||
("com.myapp.<product>.update", [
|
||||
("com.myapp.0.update", {'product': '0'}),
|
||||
("com.myapp.abc.update", {'product': 'abc'}),
|
||||
("com.myapp..update", None),
|
||||
]),
|
||||
("com.myapp.<category:string>.<subcategory:string>.list", [
|
||||
("com.myapp.cosmetic.shampoo.list", {'category': 'cosmetic', 'subcategory': 'shampoo'}),
|
||||
("com.myapp...list", None),
|
||||
("com.myapp.cosmetic..list", None),
|
||||
("com.myapp..shampoo.list", None),
|
||||
]),
|
||||
("eth.pydefi.tradeclock.<clock_oid:str>.get_clock_info", [
|
||||
("eth.pydefi.tradeclock.ba3b1e9f-3006-4eae-ae88-cf5896b36342.get_clock_info",
|
||||
{"clock_oid": "ba3b1e9f-3006-4eae-ae88-cf5896b36342"}),
|
||||
]),
|
||||
("eth.wamp.network.catalog.<catalog_adr:str>.owner", [
|
||||
("eth.wamp.network.catalog.0xAA8Cc377db31a354137d8Bb86D0E38495dbD5266.owner",
|
||||
{"catalog_adr": "0xAA8Cc377db31a354137d8Bb86D0E38495dbD5266"}),
|
||||
]),
|
||||
]
|
||||
for test in tests:
|
||||
pat = Pattern(test[0], Pattern.URI_TARGET_ENDPOINT)
|
||||
for ptest in test[1]:
|
||||
uri = ptest[0]
|
||||
kwargs_should = ptest[1]
|
||||
if kwargs_should is not None:
|
||||
args_is, kwargs_is = pat.match(uri)
|
||||
self.assertEqual(kwargs_is, kwargs_should)
|
||||
else:
|
||||
self.assertRaises(Exception, pat.match, uri)
|
||||
|
||||
|
||||
class TestDecorators(unittest.TestCase):
|
||||
|
||||
def test_decorate_endpoint(self):
|
||||
@wamp.register("com.calculator.square")
|
||||
def square(_):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(square, '_wampuris'))
|
||||
self.assertTrue(type(square._wampuris) == list)
|
||||
self.assertEqual(len(square._wampuris), 1)
|
||||
self.assertIsInstance(square._wampuris[0], Pattern)
|
||||
self.assertTrue(square._wampuris[0].is_endpoint())
|
||||
self.assertFalse(square._wampuris[0].is_handler())
|
||||
self.assertFalse(square._wampuris[0].is_exception())
|
||||
self.assertEqual(square._wampuris[0].uri(), "com.calculator.square")
|
||||
self.assertEqual(square._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
|
||||
|
||||
@wamp.register("com.myapp.product.<product:int>.update")
|
||||
def update_product(product=None, label=None):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(update_product, '_wampuris'))
|
||||
self.assertTrue(type(update_product._wampuris) == list)
|
||||
self.assertEqual(len(update_product._wampuris), 1)
|
||||
self.assertIsInstance(update_product._wampuris[0], Pattern)
|
||||
self.assertTrue(update_product._wampuris[0].is_endpoint())
|
||||
self.assertFalse(update_product._wampuris[0].is_handler())
|
||||
self.assertFalse(update_product._wampuris[0].is_exception())
|
||||
self.assertEqual(update_product._wampuris[0].uri(), "com.myapp.product.<product:int>.update")
|
||||
self.assertEqual(update_product._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.register("com.myapp.<category:string>.<cid:int>.update")
|
||||
def update(category=None, cid=None):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(update, '_wampuris'))
|
||||
self.assertTrue(type(update._wampuris) == list)
|
||||
self.assertEqual(len(update._wampuris), 1)
|
||||
self.assertIsInstance(update._wampuris[0], Pattern)
|
||||
self.assertTrue(update._wampuris[0].is_endpoint())
|
||||
self.assertFalse(update._wampuris[0].is_handler())
|
||||
self.assertFalse(update._wampuris[0].is_exception())
|
||||
self.assertEqual(update._wampuris[0].uri(), "com.myapp.<category:string>.<cid:int>.update")
|
||||
self.assertEqual(update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.register("com.myapp.circle.<name:string>",
|
||||
RegisterOptions(match="wildcard", details_arg="details"))
|
||||
def circle(name=None, details=None):
|
||||
""" Do nothing. """
|
||||
|
||||
self.assertTrue(hasattr(circle, '_wampuris'))
|
||||
self.assertTrue(type(circle._wampuris) == list)
|
||||
self.assertEqual(len(circle._wampuris), 1)
|
||||
self.assertIsInstance(circle._wampuris[0], Pattern)
|
||||
self.assertIsInstance(circle._wampuris[0].options, RegisterOptions)
|
||||
self.assertEqual(circle._wampuris[0].options.match, "wildcard")
|
||||
self.assertEqual(circle._wampuris[0].options.details_arg, "details")
|
||||
self.assertTrue(circle._wampuris[0].is_endpoint())
|
||||
self.assertFalse(circle._wampuris[0].is_handler())
|
||||
self.assertFalse(circle._wampuris[0].is_exception())
|
||||
self.assertEqual(circle._wampuris[0].uri(), "com.myapp.circle.<name:string>")
|
||||
self.assertEqual(circle._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.register("com.myapp.something..update",
|
||||
RegisterOptions(match="wildcard", details_arg="details"))
|
||||
def something(dynamic=None, details=None):
|
||||
""" Do nothing. """
|
||||
|
||||
self.assertTrue(hasattr(something, '_wampuris'))
|
||||
self.assertTrue(type(something._wampuris) == list)
|
||||
self.assertEqual(len(something._wampuris), 1)
|
||||
self.assertIsInstance(something._wampuris[0], Pattern)
|
||||
self.assertIsInstance(something._wampuris[0].options, RegisterOptions)
|
||||
self.assertEqual(something._wampuris[0].options.match, "wildcard")
|
||||
self.assertEqual(something._wampuris[0].options.details_arg, "details")
|
||||
self.assertTrue(something._wampuris[0].is_endpoint())
|
||||
self.assertFalse(something._wampuris[0].is_handler())
|
||||
self.assertFalse(something._wampuris[0].is_exception())
|
||||
self.assertEqual(something._wampuris[0].uri(), "com.myapp.something..update")
|
||||
self.assertEqual(something._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
def test_decorate_handler(self):
|
||||
@wamp.subscribe("com.myapp.on_shutdown")
|
||||
def on_shutdown():
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(on_shutdown, '_wampuris'))
|
||||
self.assertTrue(type(on_shutdown._wampuris) == list)
|
||||
self.assertEqual(len(on_shutdown._wampuris), 1)
|
||||
self.assertIsInstance(on_shutdown._wampuris[0], Pattern)
|
||||
self.assertFalse(on_shutdown._wampuris[0].is_endpoint())
|
||||
self.assertTrue(on_shutdown._wampuris[0].is_handler())
|
||||
self.assertFalse(on_shutdown._wampuris[0].is_exception())
|
||||
self.assertEqual(on_shutdown._wampuris[0].uri(), "com.myapp.on_shutdown")
|
||||
self.assertEqual(on_shutdown._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
|
||||
|
||||
@wamp.subscribe("com.myapp.product.<product:int>.on_update")
|
||||
def on_product_update(product=None, label=None):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(on_product_update, '_wampuris'))
|
||||
self.assertTrue(type(on_product_update._wampuris) == list)
|
||||
self.assertEqual(len(on_product_update._wampuris), 1)
|
||||
self.assertIsInstance(on_product_update._wampuris[0], Pattern)
|
||||
self.assertFalse(on_product_update._wampuris[0].is_endpoint())
|
||||
self.assertTrue(on_product_update._wampuris[0].is_handler())
|
||||
self.assertFalse(on_product_update._wampuris[0].is_exception())
|
||||
self.assertEqual(on_product_update._wampuris[0].uri(), "com.myapp.product.<product:int>.on_update")
|
||||
self.assertEqual(on_product_update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.subscribe("com.myapp.<category:string>.<cid:int>.on_update")
|
||||
def on_update(category=None, cid=None, label=None):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(on_update, '_wampuris'))
|
||||
self.assertTrue(type(on_update._wampuris) == list)
|
||||
self.assertEqual(len(on_update._wampuris), 1)
|
||||
self.assertIsInstance(on_update._wampuris[0], Pattern)
|
||||
self.assertFalse(on_update._wampuris[0].is_endpoint())
|
||||
self.assertTrue(on_update._wampuris[0].is_handler())
|
||||
self.assertFalse(on_update._wampuris[0].is_exception())
|
||||
self.assertEqual(on_update._wampuris[0].uri(), "com.myapp.<category:string>.<cid:int>.on_update")
|
||||
self.assertEqual(on_update._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.subscribe("com.myapp.on.<event:string>",
|
||||
SubscribeOptions(match="wildcard", details_arg="details"))
|
||||
def on_event(event=None, details=None):
|
||||
""" Do nothing. """
|
||||
|
||||
self.assertTrue(hasattr(on_event, '_wampuris'))
|
||||
self.assertTrue(type(on_event._wampuris) == list)
|
||||
self.assertEqual(len(on_event._wampuris), 1)
|
||||
self.assertIsInstance(on_event._wampuris[0], Pattern)
|
||||
self.assertIsInstance(on_event._wampuris[0].options, SubscribeOptions)
|
||||
self.assertEqual(on_event._wampuris[0].options.match, "wildcard")
|
||||
self.assertEqual(on_event._wampuris[0].options.details_arg, "details")
|
||||
self.assertFalse(on_event._wampuris[0].is_endpoint())
|
||||
self.assertTrue(on_event._wampuris[0].is_handler())
|
||||
self.assertFalse(on_event._wampuris[0].is_exception())
|
||||
self.assertEqual(on_event._wampuris[0].uri(), "com.myapp.on.<event:string>")
|
||||
self.assertEqual(on_event._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
def test_decorate_exception(self):
|
||||
@wamp.error("com.myapp.error")
|
||||
class AppError(Exception):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(AppError, '_wampuris'))
|
||||
self.assertTrue(type(AppError._wampuris) == list)
|
||||
self.assertEqual(len(AppError._wampuris), 1)
|
||||
self.assertIsInstance(AppError._wampuris[0], Pattern)
|
||||
self.assertFalse(AppError._wampuris[0].is_endpoint())
|
||||
self.assertFalse(AppError._wampuris[0].is_handler())
|
||||
self.assertTrue(AppError._wampuris[0].is_exception())
|
||||
self.assertEqual(AppError._wampuris[0].uri(), "com.myapp.error")
|
||||
self.assertEqual(AppError._wampuris[0]._type, Pattern.URI_TYPE_EXACT)
|
||||
|
||||
@wamp.error("com.myapp.product.<product:int>.product_inactive")
|
||||
class ProductInactiveError(Exception):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(ProductInactiveError, '_wampuris'))
|
||||
self.assertTrue(type(ProductInactiveError._wampuris) == list)
|
||||
self.assertEqual(len(ProductInactiveError._wampuris), 1)
|
||||
self.assertIsInstance(ProductInactiveError._wampuris[0], Pattern)
|
||||
self.assertFalse(ProductInactiveError._wampuris[0].is_endpoint())
|
||||
self.assertFalse(ProductInactiveError._wampuris[0].is_handler())
|
||||
self.assertTrue(ProductInactiveError._wampuris[0].is_exception())
|
||||
self.assertEqual(ProductInactiveError._wampuris[0].uri(), "com.myapp.product.<product:int>.product_inactive")
|
||||
self.assertEqual(ProductInactiveError._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
@wamp.error("com.myapp.<category:string>.<product:int>.inactive")
|
||||
class ObjectInactiveError(Exception):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(ObjectInactiveError, '_wampuris'))
|
||||
self.assertTrue(type(ObjectInactiveError._wampuris) == list)
|
||||
self.assertEqual(len(ObjectInactiveError._wampuris), 1)
|
||||
self.assertIsInstance(ObjectInactiveError._wampuris[0], Pattern)
|
||||
self.assertFalse(ObjectInactiveError._wampuris[0].is_endpoint())
|
||||
self.assertFalse(ObjectInactiveError._wampuris[0].is_handler())
|
||||
self.assertTrue(ObjectInactiveError._wampuris[0].is_exception())
|
||||
self.assertEqual(ObjectInactiveError._wampuris[0].uri(), "com.myapp.<category:string>.<product:int>.inactive")
|
||||
self.assertEqual(ObjectInactiveError._wampuris[0]._type, Pattern.URI_TYPE_WILDCARD)
|
||||
|
||||
def test_match_decorated_endpoint(self):
|
||||
@wamp.register("com.calculator.square")
|
||||
def square(x):
|
||||
return x
|
||||
|
||||
args, kwargs = square._wampuris[0].match("com.calculator.square")
|
||||
self.assertEqual(square(666, **kwargs), 666)
|
||||
|
||||
@wamp.register("com.myapp.product.<product:int>.update")
|
||||
def update_product(product=None, label=None):
|
||||
return product, label
|
||||
|
||||
args, kwargs = update_product._wampuris[0].match("com.myapp.product.123456.update")
|
||||
kwargs['label'] = "foobar"
|
||||
self.assertEqual(update_product(**kwargs), (123456, "foobar"))
|
||||
|
||||
@wamp.register("com.myapp.<category:string>.<cid:int>.update")
|
||||
def update(category=None, cid=None, label=None):
|
||||
return category, cid, label
|
||||
|
||||
args, kwargs = update._wampuris[0].match("com.myapp.product.123456.update")
|
||||
kwargs['label'] = "foobar"
|
||||
self.assertEqual(update(**kwargs), ("product", 123456, "foobar"))
|
||||
|
||||
def test_match_decorated_handler(self):
|
||||
@wamp.subscribe("com.myapp.on_shutdown")
|
||||
def on_shutdown():
|
||||
pass
|
||||
|
||||
args, kwargs = on_shutdown._wampuris[0].match("com.myapp.on_shutdown")
|
||||
self.assertEqual(on_shutdown(**kwargs), None)
|
||||
|
||||
@wamp.subscribe("com.myapp.product.<product:int>.on_update")
|
||||
def on_product_update(product=None, label=None):
|
||||
return product, label
|
||||
|
||||
args, kwargs = on_product_update._wampuris[0].match("com.myapp.product.123456.on_update")
|
||||
kwargs['label'] = "foobar"
|
||||
self.assertEqual(on_product_update(**kwargs), (123456, "foobar"))
|
||||
|
||||
@wamp.subscribe("com.myapp.<category:string>.<cid:int>.on_update")
|
||||
def on_update(category=None, cid=None, label=None):
|
||||
return category, cid, label
|
||||
|
||||
args, kwargs = on_update._wampuris[0].match("com.myapp.product.123456.on_update")
|
||||
kwargs['label'] = "foobar"
|
||||
self.assertEqual(on_update(**kwargs), ("product", 123456, "foobar"))
|
||||
|
||||
def test_match_decorated_exception(self):
|
||||
@wamp.error("com.myapp.error")
|
||||
class AppError(Exception):
|
||||
|
||||
def __init__(self, msg):
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__ == other.__class__ and self.args == other.args
|
||||
|
||||
args, kwargs = AppError._wampuris[0].match("com.myapp.error")
|
||||
# noinspection PyArgumentList
|
||||
self.assertEqual(AppError("fuck", **kwargs), AppError("fuck"))
|
||||
|
||||
@wamp.error("com.myapp.product.<product:int>.product_inactive")
|
||||
class ProductInactiveError(Exception):
|
||||
|
||||
def __init__(self, msg, product=None):
|
||||
Exception.__init__(self, msg)
|
||||
self.product = product
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__ == other.__class__ and self.args == other.args and self.product == other.product
|
||||
|
||||
args, kwargs = ProductInactiveError._wampuris[0].match("com.myapp.product.123456.product_inactive")
|
||||
self.assertEqual(ProductInactiveError("fuck", **kwargs), ProductInactiveError("fuck", 123456))
|
||||
|
||||
@wamp.error("com.myapp.<category:string>.<product:int>.inactive")
|
||||
class ObjectInactiveError(Exception):
|
||||
|
||||
def __init__(self, msg, category=None, product=None):
|
||||
Exception.__init__(self, msg)
|
||||
self.category = category
|
||||
self.product = product
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__ == other.__class__ and self.args == other.args and \
|
||||
self.category == other.category and self.product == other.product
|
||||
|
||||
args, kwargs = ObjectInactiveError._wampuris[0].match("com.myapp.product.123456.inactive")
|
||||
self.assertEqual(ObjectInactiveError("fuck", **kwargs), ObjectInactiveError("fuck", "product", 123456))
|
||||
|
||||
|
||||
class KwException(Exception):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Exception.__init__(self, *args)
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
# what if the WAMP error message received
|
||||
# contains args/kwargs that cannot be
|
||||
# consumed by the constructor of the exception
|
||||
# class defined for the WAMP error URI?
|
||||
|
||||
# 1. we can bail out (but we are already signaling an error)
|
||||
# 2. we can require a generic constructor
|
||||
# 3. we can map only unconsumed args/kwargs to generic attributes
|
||||
# 4. we can silently drop unconsumed args/kwargs
|
||||
|
||||
|
||||
class MockSession(object):
|
||||
|
||||
def __init__(self):
|
||||
self._ecls_to_uri_pat = {}
|
||||
self._uri_to_ecls = {}
|
||||
|
||||
def define(self, exception, error=None):
|
||||
if error is None:
|
||||
assert (hasattr(exception, '_wampuris'))
|
||||
self._ecls_to_uri_pat[exception] = exception._wampuris
|
||||
self._uri_to_ecls[exception._wampuris[0].uri()] = exception
|
||||
else:
|
||||
assert (not hasattr(exception, '_wampuris'))
|
||||
self._ecls_to_uri_pat[exception] = [Pattern(error, Pattern.URI_TARGET_HANDLER)]
|
||||
self._uri_to_ecls[error] = exception
|
||||
|
||||
def map_error(self, error, args=None, kwargs=None):
|
||||
|
||||
# FIXME:
|
||||
# 1. map to ecls based on error URI wildcard/prefix
|
||||
# 2. extract additional args/kwargs from error URI
|
||||
|
||||
if error in self._uri_to_ecls:
|
||||
ecls = self._uri_to_ecls[error]
|
||||
try:
|
||||
# the following might fail, eg. TypeError when
|
||||
# signature of exception constructor is incompatible
|
||||
# with args/kwargs or when the exception constructor raises
|
||||
if kwargs:
|
||||
if args:
|
||||
exc = ecls(*args, **kwargs)
|
||||
else:
|
||||
exc = ecls(**kwargs)
|
||||
else:
|
||||
if args:
|
||||
exc = ecls(*args)
|
||||
else:
|
||||
exc = ecls()
|
||||
except Exception:
|
||||
# FIXME: log e
|
||||
exc = KwException(error, *args, **kwargs)
|
||||
else:
|
||||
# this never fails
|
||||
args = args or []
|
||||
kwargs = kwargs or {}
|
||||
exc = KwException(error, *args, **kwargs)
|
||||
return exc
|
||||
|
||||
|
||||
class TestDecoratorsAdvanced(unittest.TestCase):
|
||||
|
||||
def test_decorate_exception_non_exception(self):
|
||||
|
||||
def test():
|
||||
# noinspection PyUnusedLocal
|
||||
@wamp.error("com.test.error")
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
self.assertRaises(Exception, test)
|
||||
|
||||
def test_decorate_endpoint_multiple(self):
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@wamp.register("com.oldapp.oldproc")
|
||||
@wamp.register("com.calculator.square")
|
||||
def square(x):
|
||||
"""Do nothing."""
|
||||
|
||||
self.assertTrue(hasattr(square, '_wampuris'))
|
||||
self.assertTrue(type(square._wampuris) == list)
|
||||
self.assertEqual(len(square._wampuris), 2)
|
||||
|
||||
for i in range(2):
|
||||
self.assertIsInstance(square._wampuris[i], Pattern)
|
||||
self.assertTrue(square._wampuris[i].is_endpoint())
|
||||
self.assertFalse(square._wampuris[i].is_handler())
|
||||
self.assertFalse(square._wampuris[i].is_exception())
|
||||
self.assertEqual(square._wampuris[i]._type, Pattern.URI_TYPE_EXACT)
|
||||
|
||||
self.assertEqual(square._wampuris[0].uri(), "com.calculator.square")
|
||||
self.assertEqual(square._wampuris[1].uri(), "com.oldapp.oldproc")
|
||||
|
||||
def test_marshal_decorated_exception(self):
|
||||
|
||||
@wamp.error("com.myapp.error")
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
try:
|
||||
raise AppError("fuck")
|
||||
except Exception as e:
|
||||
self.assertEqual(e._wampuris[0].uri(), "com.myapp.error")
|
||||
|
||||
@wamp.error("com.myapp.product.<product:int>.product_inactive")
|
||||
class ProductInactiveError(Exception):
|
||||
|
||||
def __init__(self, msg, product=None):
|
||||
Exception.__init__(self, msg)
|
||||
self.product = product
|
||||
|
||||
try:
|
||||
raise ProductInactiveError("fuck", 123456)
|
||||
except Exception as e:
|
||||
self.assertEqual(e._wampuris[0].uri(), "com.myapp.product.<product:int>.product_inactive")
|
||||
|
||||
session = MockSession()
|
||||
session.define(AppError)
|
||||
|
||||
def test_define_exception_undecorated(self):
|
||||
|
||||
session = MockSession()
|
||||
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
# defining an undecorated exception requires
|
||||
# an URI to be provided
|
||||
self.assertRaises(Exception, session.define, AppError)
|
||||
|
||||
session.define(AppError, "com.myapp.error")
|
||||
|
||||
exc = session.map_error("com.myapp.error")
|
||||
self.assertIsInstance(exc, AppError)
|
||||
|
||||
def test_define_exception_decorated(self):
|
||||
|
||||
session = MockSession()
|
||||
|
||||
@wamp.error("com.myapp.error")
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
# when defining a decorated exception
|
||||
# an URI must not be provided
|
||||
self.assertRaises(Exception, session.define, AppError, "com.myapp.error")
|
||||
|
||||
session.define(AppError)
|
||||
|
||||
exc = session.map_error("com.myapp.error")
|
||||
self.assertIsInstance(exc, AppError)
|
||||
|
||||
def test_map_exception_undefined(self):
|
||||
|
||||
session = MockSession()
|
||||
|
||||
exc = session.map_error("com.myapp.error")
|
||||
self.assertIsInstance(exc, Exception)
|
||||
|
||||
def test_map_exception_args(self):
|
||||
|
||||
session = MockSession()
|
||||
|
||||
@wamp.error("com.myapp.error")
|
||||
class AppError(Exception):
|
||||
pass
|
||||
|
||||
@wamp.error("com.myapp.error.product_inactive")
|
||||
class ProductInactiveError(Exception):
|
||||
def __init__(self, product=None):
|
||||
self.product = product
|
||||
|
||||
# define exceptions in mock session
|
||||
session.define(AppError)
|
||||
session.define(ProductInactiveError)
|
||||
|
||||
for test in [
|
||||
# ("com.myapp.foo.error", [], {}, KwException),
|
||||
("com.myapp.error", [], {}, AppError),
|
||||
("com.myapp.error", ["you are doing it wrong"], {}, AppError),
|
||||
("com.myapp.error", ["you are doing it wrong", 1, 2, 3], {}, AppError),
|
||||
|
||||
("com.myapp.error.product_inactive", [], {}, ProductInactiveError),
|
||||
("com.myapp.error.product_inactive", [], {"product": 123456}, ProductInactiveError),
|
||||
]:
|
||||
error, args, kwargs, ecls = test
|
||||
exc = session.map_error(error, args, kwargs)
|
||||
|
||||
self.assertIsInstance(exc, ecls)
|
||||
self.assertEqual(list(exc.args), args)
|
||||
@@ -0,0 +1,406 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
|
||||
if os.environ.get('USE_TWISTED', False):
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet import defer
|
||||
from twisted.python.failure import Failure
|
||||
from autobahn.wamp import message, role
|
||||
from autobahn.wamp.exception import ProtocolError
|
||||
from autobahn.twisted.wamp import ApplicationSession
|
||||
from autobahn.wamp.types import TransportDetails
|
||||
|
||||
class MockTransport:
|
||||
def __init__(self):
|
||||
self.messages = []
|
||||
self._transport_details = TransportDetails()
|
||||
|
||||
def transport_details(self):
|
||||
return self._transport_details
|
||||
|
||||
def send(self, msg):
|
||||
self.messages.append(msg)
|
||||
|
||||
def close(self, *args, **kw):
|
||||
pass
|
||||
|
||||
class MockApplicationSession(ApplicationSession):
|
||||
'''
|
||||
This is used by tests, which typically attach their own handler to
|
||||
on*() methods. This just collects any errors from onUserError
|
||||
'''
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
ApplicationSession.__init__(self, *args, **kw)
|
||||
self.errors = []
|
||||
self._realm = 'dummy'
|
||||
self._transport = MockTransport()
|
||||
|
||||
def onUserError(self, e, msg):
|
||||
self.errors.append((e.value, msg))
|
||||
|
||||
def exception_raiser(exc):
|
||||
'''
|
||||
Create a method that takes any args and always raises the given
|
||||
Exception instance.
|
||||
'''
|
||||
assert isinstance(exc, Exception), "Must derive from Exception"
|
||||
|
||||
def method(*args, **kw):
|
||||
raise exc
|
||||
return method
|
||||
|
||||
def async_exception_raiser(exc):
|
||||
'''
|
||||
Create a method that takes any args, and always returns a Deferred
|
||||
that has failed.
|
||||
'''
|
||||
assert isinstance(exc, Exception), "Must derive from Exception"
|
||||
|
||||
def method(*args, **kw):
|
||||
try:
|
||||
raise exc
|
||||
except:
|
||||
return defer.fail(Failure())
|
||||
return method
|
||||
|
||||
def create_mock_welcome():
|
||||
return message.Welcome(
|
||||
1234,
|
||||
{
|
||||
'broker': role.RoleBrokerFeatures(),
|
||||
},
|
||||
)
|
||||
|
||||
class TestSessionCallbacks(unittest.TestCase):
|
||||
'''
|
||||
These test that callbacks on user-overridden ApplicationSession
|
||||
methods that produce errors are handled correctly.
|
||||
|
||||
XXX should do state-diagram documenting where we are when each
|
||||
of these cases arises :/
|
||||
'''
|
||||
|
||||
# XXX sure would be nice to use py.test @fixture to do the
|
||||
# async/sync exception-raising stuff (i.e. make each test run
|
||||
# twice)...but that would mean switching all test-running over
|
||||
# to py-test
|
||||
|
||||
# the whole variable must not be defined to deactivate (!)
|
||||
skip = True
|
||||
|
||||
def test_on_join(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("blammo")
|
||||
session.onJoin = exception_raiser(exception)
|
||||
msg = create_mock_welcome()
|
||||
|
||||
# give the sesion a WELCOME, from which it should call onJoin
|
||||
session.onMessage(msg)
|
||||
|
||||
# make sure we got the right error out of onUserError
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_join_deferred(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("blammo")
|
||||
session.onJoin = async_exception_raiser(exception)
|
||||
msg = create_mock_welcome()
|
||||
|
||||
# give the sesion a WELCOME, from which it should call onJoin
|
||||
session.onMessage(msg)
|
||||
|
||||
# make sure we got the right error out of onUserError
|
||||
# import traceback
|
||||
# traceback.print_exception(*session.errors[0][:3])
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_leave(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("boom")
|
||||
session.onLeave = exception_raiser(exception)
|
||||
msg = message.Abort("testing")
|
||||
|
||||
# we haven't done anything, so this is "abort before we've
|
||||
# connected"
|
||||
session.onMessage(msg)
|
||||
|
||||
# make sure we got the right error out of onUserError
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_leave_deferred(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("boom")
|
||||
session.onLeave = async_exception_raiser(exception)
|
||||
msg = message.Abort("testing")
|
||||
|
||||
# we haven't done anything, so this is "abort before we've
|
||||
# connected"
|
||||
session.onMessage(msg)
|
||||
|
||||
# make sure we got the right error out of onUserError
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_leave_valid_session(self):
|
||||
'''
|
||||
cover when onLeave called after we have a valid session
|
||||
'''
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("such challenge")
|
||||
session.onLeave = exception_raiser(exception)
|
||||
# we have to get to an established connection first...
|
||||
session.onMessage(create_mock_welcome())
|
||||
self.assertTrue(session._session_id is not None)
|
||||
|
||||
# okay we have a session ("because ._session_id is not None")
|
||||
msg = message.Goodbye()
|
||||
session.onMessage(msg)
|
||||
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_leave_valid_session_deferred(self):
|
||||
'''
|
||||
cover when onLeave called after we have a valid session
|
||||
'''
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("such challenge")
|
||||
session.onLeave = async_exception_raiser(exception)
|
||||
# we have to get to an established connection first...
|
||||
session.onMessage(create_mock_welcome())
|
||||
self.assertTrue(session._session_id is not None)
|
||||
|
||||
# okay we have a session ("because ._session_id is not None")
|
||||
msg = message.Goodbye()
|
||||
session.onMessage(msg)
|
||||
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_leave_after_bad_challenge(self):
|
||||
'''
|
||||
onLeave raises error after onChallenge fails
|
||||
'''
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("such challenge")
|
||||
session.onLeave = exception_raiser(exception)
|
||||
session.onChallenge = exception_raiser(exception)
|
||||
# make a challenge (which will fail, and then the
|
||||
# subsequent onLeave will also fail)
|
||||
msg = message.Challenge("foo")
|
||||
session.onMessage(msg)
|
||||
|
||||
self.assertEqual(2, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_disconnect_via_close(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("sideways")
|
||||
session.onDisconnect = exception_raiser(exception)
|
||||
# we short-cut the whole state-machine traversal here by
|
||||
# just calling onClose directly, which would normally be
|
||||
# called via a Protocol, e.g.,
|
||||
# autobahn.wamp.websocket.WampWebSocketProtocol
|
||||
session.onClose(False)
|
||||
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_disconnect_via_close_deferred(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("sideways")
|
||||
session.onDisconnect = async_exception_raiser(exception)
|
||||
# we short-cut the whole state-machine traversal here by
|
||||
# just calling onClose directly, which would normally be
|
||||
# called via a Protocol, e.g.,
|
||||
# autobahn.wamp.websocket.WampWebSocketProtocol
|
||||
session.onClose(False)
|
||||
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
# XXX FIXME Probably more ways to call onLeave!
|
||||
|
||||
def test_on_challenge(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("such challenge")
|
||||
session.onChallenge = exception_raiser(exception)
|
||||
msg = message.Challenge("foo")
|
||||
|
||||
# execute
|
||||
session.onMessage(msg)
|
||||
|
||||
# we already handle any onChallenge errors as "abort the
|
||||
# connection". So make sure our error showed up in the
|
||||
# fake-transport.
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
self.assertEqual(1, len(session._transport.messages))
|
||||
reply = session._transport.messages[0]
|
||||
self.assertIsInstance(reply, message.Abort)
|
||||
self.assertEqual("such challenge", reply.message)
|
||||
|
||||
def test_on_challenge_deferred(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("such challenge")
|
||||
session.onChallenge = async_exception_raiser(exception)
|
||||
msg = message.Challenge("foo")
|
||||
|
||||
# execute
|
||||
session.onMessage(msg)
|
||||
|
||||
# we already handle any onChallenge errors as "abort the
|
||||
# connection". So make sure our error showed up in the
|
||||
# fake-transport.
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(session.errors[0][0], exception)
|
||||
self.assertEqual(1, len(session._transport.messages))
|
||||
reply = session._transport.messages[0]
|
||||
self.assertIsInstance(reply, message.Abort)
|
||||
self.assertEqual("such challenge", reply.message)
|
||||
|
||||
def test_no_session(self):
|
||||
'''
|
||||
test "all other cases" when we don't yet have a session
|
||||
established, which should all raise ProtocolErrors and
|
||||
*not* go through the onUserError handler. We cheat and
|
||||
just test one.
|
||||
'''
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("such challenge")
|
||||
session.onConnect = exception_raiser(exception)
|
||||
|
||||
for msg in [message.Goodbye()]:
|
||||
self.assertRaises(ProtocolError, session.onMessage, (msg,))
|
||||
self.assertEqual(0, len(session.errors))
|
||||
|
||||
def test_on_disconnect(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("oh sadness")
|
||||
session.onDisconnect = exception_raiser(exception)
|
||||
# we short-cut the whole state-machine traversal here by
|
||||
# just calling onClose directly, which would normally be
|
||||
# called via a Protocol, e.g.,
|
||||
# autobahn.wamp.websocket.WampWebSocketProtocol
|
||||
session.onClose(False)
|
||||
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_disconnect_deferred(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("oh sadness")
|
||||
session.onDisconnect = async_exception_raiser(exception)
|
||||
# we short-cut the whole state-machine traversal here by
|
||||
# just calling onClose directly, which would normally be
|
||||
# called via a Protocol, e.g.,
|
||||
# autobahn.wamp.websocket.WampWebSocketProtocol
|
||||
session.onClose(False)
|
||||
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_disconnect_with_session(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("the pain runs deep")
|
||||
session.onDisconnect = exception_raiser(exception)
|
||||
# create a valid session
|
||||
session.onMessage(create_mock_welcome())
|
||||
|
||||
# we short-cut the whole state-machine traversal here by
|
||||
# just calling onClose directly, which would normally be
|
||||
# called via a Protocol, e.g.,
|
||||
# autobahn.wamp.websocket.WampWebSocketProtocol
|
||||
session.onClose(False)
|
||||
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_disconnect_with_session_deferred(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("the pain runs deep")
|
||||
session.onDisconnect = async_exception_raiser(exception)
|
||||
# create a valid session
|
||||
session.onMessage(create_mock_welcome())
|
||||
|
||||
# we short-cut the whole state-machine traversal here by
|
||||
# just calling onClose directly, which would normally be
|
||||
# called via a Protocol, e.g.,
|
||||
# autobahn.wamp.websocket.WampWebSocketProtocol
|
||||
session.onClose(False)
|
||||
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_connect(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("the pain runs deep")
|
||||
session.onConnect = exception_raiser(exception)
|
||||
trans = MockTransport()
|
||||
|
||||
# normally would be called from a Protocol?
|
||||
session.onOpen(trans)
|
||||
|
||||
# shouldn't have done the .join()
|
||||
self.assertEqual(0, len(trans.messages))
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
def test_on_connect_deferred(self):
|
||||
session = MockApplicationSession()
|
||||
exception = RuntimeError("the pain runs deep")
|
||||
session.onConnect = async_exception_raiser(exception)
|
||||
trans = MockTransport()
|
||||
|
||||
# normally would be called from a Protocol?
|
||||
session.onOpen(trans)
|
||||
|
||||
# shouldn't have done the .join()
|
||||
self.assertEqual(0, len(trans.messages))
|
||||
self.assertEqual(1, len(session.errors))
|
||||
self.assertEqual(exception, session.errors[0][0])
|
||||
|
||||
# XXX likely missing other ways to invoke the above. need to
|
||||
# cover, for sure:
|
||||
#
|
||||
# onChallenge
|
||||
# onJoin
|
||||
# onLeave
|
||||
# onDisconnect
|
||||
#
|
||||
# what about other ISession ones?
|
||||
# onConnect
|
||||
# onDisconnect
|
||||
|
||||
# NOTE: for Event stuff, that is publish() handlers,
|
||||
# test_publish_callback_exception in test_protocol.py already
|
||||
# covers exceptions coming from user-code.
|
||||
@@ -0,0 +1,40 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
|
||||
if os.environ.get('USE_TWISTED', False):
|
||||
from twisted.trial import unittest
|
||||
|
||||
from autobahn.wamp.websocket import WampWebSocketProtocol
|
||||
|
||||
class TestWebsocketProtocol(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.protocol = WampWebSocketProtocol()
|
||||
|
||||
def test_close_before_open(self):
|
||||
# just checking this doesn't throw an exception...
|
||||
self.protocol.onClose(True, 1, "just testing")
|
||||
Reference in New Issue
Block a user