Add unit tests #39
14 changed files with 1959 additions and 542 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,6 +3,7 @@ __pycache__
|
||||||
.vscode/
|
.vscode/
|
||||||
.coverage
|
.coverage
|
||||||
.noseids
|
.noseids
|
||||||
|
.pytest_cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
tests/report
|
tests/report
|
||||||
tests/tmp.db
|
tests/tmp.db
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ install:
|
||||||
- pip install pipenv
|
- pip install pipenv
|
||||||
- pipenv install --dev
|
- pipenv install --dev
|
||||||
|
|
||||||
script: nosetests
|
script: pipenv run python -m pytest
|
||||||
|
|
|
||||||
5
Pipfile
5
Pipfile
|
|
@ -11,11 +11,14 @@ pyyaml = ">=4.2b1"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
black = "*"
|
black = "*"
|
||||||
nose = "*"
|
|
||||||
coverage = "*"
|
coverage = "*"
|
||||||
mock = "*"
|
mock = "*"
|
||||||
pylint = "*"
|
pylint = "*"
|
||||||
pbr = "*"
|
pbr = "*"
|
||||||
|
pytest-asyncio = "*"
|
||||||
|
pytest-aiohttp = "*"
|
||||||
|
pytest-cov = "*"
|
||||||
|
testfixtures = "*"
|
||||||
|
|
||||||
[pipenv]
|
[pipenv]
|
||||||
allow_prereleases = true
|
allow_prereleases = true
|
||||||
|
|
|
||||||
207
Pipfile.lock
generated
207
Pipfile.lock
generated
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "6e5b4ccc879dfdf82f53f7966eac54af90f9b9a2ffd1da6f1ce126d0ca125fc9"
|
"sha256": "ce534b7ec3bc05783e99636abeb05165298c96698d1c6e1288595a1d3174168b"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
|
|
@ -208,6 +208,34 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
"aiohttp": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02396865118790ebb0bd14d6935e9e4fa80d87683240618894e585048a8b83ec",
|
||||||
|
"sha256:0f448b5d5cd45e642b9fcf4798e48cca4149ae479022f59bd67a47caed44657e",
|
||||||
|
"sha256:1af72f53ccead6d161296e576aa2b5d0c6c7403a389601dd7aa7a7a30a9f41c3",
|
||||||
|
"sha256:32eeef64a5bcf6dc652f19e020b16f36ec66d291957f62ffad18ecfc58695966",
|
||||||
|
"sha256:36c4e234a85a81e325d8b1d7fdeb696a3d51bb8eb09a8c8e69c7532b421e8a29",
|
||||||
|
"sha256:371a5f1bb604fc262ba32893931f5aad3bb3e797dbebcf2b5b2c36f97603e4c7",
|
||||||
|
"sha256:3f5a30f67e4152d4075063ae9f2286313af2e39b950869edba76232569e08793",
|
||||||
|
"sha256:48c65d65d0de79f1a4391bc29c8c8ec7655671a03ad7902d5e66fd735531893e",
|
||||||
|
"sha256:4ede22808195126d55879f1a14aaa3d1004f188c341f92ec3b9ca5ea13e695b9",
|
||||||
|
"sha256:53228028648b40f59fc941c0cd67b7899bce52f35eb66aeb9e33b75bac0aac0a",
|
||||||
|
"sha256:67cb8e71c043686f806cfdb4189774c57ef929d32890024c3775d72b54381797",
|
||||||
|
"sha256:6b1e99dc838c28f14e45bffef6fb77bf5b54bd2bfb1a475c776da5ba8a9fec2d",
|
||||||
|
"sha256:6c30eca95e7d60fbf13e6ebcaf62b7c0f3f93ec5de8862919aa5ede3720b1c86",
|
||||||
|
"sha256:6f2c905ab82aa0ee8a06210d5a6c1359c79f3f2088cc1b53c8758cae1dbc0d55",
|
||||||
|
"sha256:746b73eff86a1618025093b9c86ff4d642cff71a7ac7244f7fd8a186967cee22",
|
||||||
|
"sha256:9ebf518c7bc08e65b5396f80e8155c7bb1380b921b84b510570e62196c9fca56",
|
||||||
|
"sha256:9efc80ced0936f3fb2774e1bc981006324c74d1e58fc7f7164ecd98ede727c7d",
|
||||||
|
"sha256:abc43651f2c6d70b812cb874fc8caa00eb69fac11930cd2640d32a11ee7beef0",
|
||||||
|
"sha256:b4c6e1f5a591511537d4ccd4c807f6c8bb8d1b8a5043395950ba339086380904",
|
||||||
|
"sha256:bee80820a8ef5f6bddd58d762fc9f981ef1041a464d382d7d9a992bb94cd23c6",
|
||||||
|
"sha256:f0c304dcc1494dbc3fc492ebc2e61d0db323be9756a1bb14f407097d8adf82ec",
|
||||||
|
"sha256:fb9c2f27d2db6d709a02421733bb6ad22e16104beb2cf403e1120c24f3cc8668"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.0.0a0"
|
||||||
|
},
|
||||||
"appdirs": {
|
"appdirs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||||
|
|
@ -222,6 +250,20 @@
|
||||||
],
|
],
|
||||||
"version": "==2.2.5"
|
"version": "==2.2.5"
|
||||||
},
|
},
|
||||||
|
"async-timeout": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
|
||||||
|
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
|
||||||
|
],
|
||||||
|
"version": "==3.0.1"
|
||||||
|
},
|
||||||
|
"atomicwrites": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
|
||||||
|
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
||||||
|
|
@ -237,6 +279,13 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==19.3b0"
|
"version": "==19.3b0"
|
||||||
},
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
|
},
|
||||||
"click": {
|
"click": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||||
|
|
@ -277,6 +326,20 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==5.0a5"
|
"version": "==5.0a5"
|
||||||
},
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
|
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||||
|
],
|
||||||
|
"version": "==2.8"
|
||||||
|
},
|
||||||
|
"importlib-metadata": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a9f185022cfa69e9ca5f7eabfd5a58b689894cb78a11e3c8c89398a8ccbb8e7f",
|
||||||
|
"sha256:df1403cd3aebeb2b1dcd3515ca062eecb5bd3ea7611f18cba81130c68707e879"
|
||||||
|
],
|
||||||
|
"version": "==0.17"
|
||||||
|
},
|
||||||
"isort": {
|
"isort": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c40744b6bc5162bbb39c1257fe298b7a393861d50978b565f3ccd9cb9de0182a",
|
"sha256:c40744b6bc5162bbb39c1257fe298b7a393861d50978b565f3ccd9cb9de0182a",
|
||||||
|
|
@ -322,14 +385,54 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.0.5"
|
"version": "==3.0.5"
|
||||||
},
|
},
|
||||||
"nose": {
|
"more-itertools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac",
|
"sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
|
||||||
"sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
|
"sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
|
||||||
"sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"markers": "python_version > '2.7'",
|
||||||
"version": "==1.3.7"
|
"version": "==7.0.0"
|
||||||
|
},
|
||||||
|
"multidict": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f",
|
||||||
|
"sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3",
|
||||||
|
"sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef",
|
||||||
|
"sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b",
|
||||||
|
"sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73",
|
||||||
|
"sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc",
|
||||||
|
"sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3",
|
||||||
|
"sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd",
|
||||||
|
"sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351",
|
||||||
|
"sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941",
|
||||||
|
"sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d",
|
||||||
|
"sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1",
|
||||||
|
"sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b",
|
||||||
|
"sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a",
|
||||||
|
"sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3",
|
||||||
|
"sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7",
|
||||||
|
"sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0",
|
||||||
|
"sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0",
|
||||||
|
"sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014",
|
||||||
|
"sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5",
|
||||||
|
"sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036",
|
||||||
|
"sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d",
|
||||||
|
"sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a",
|
||||||
|
"sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce",
|
||||||
|
"sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1",
|
||||||
|
"sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a",
|
||||||
|
"sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9",
|
||||||
|
"sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7",
|
||||||
|
"sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b"
|
||||||
|
],
|
||||||
|
"version": "==4.5.2"
|
||||||
|
},
|
||||||
|
"packaging": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
|
||||||
|
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
|
||||||
|
],
|
||||||
|
"version": "==19.0"
|
||||||
},
|
},
|
||||||
"pbr": {
|
"pbr": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -339,6 +442,20 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==5.2.1"
|
"version": "==5.2.1"
|
||||||
},
|
},
|
||||||
|
"pluggy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc",
|
||||||
|
"sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"
|
||||||
|
],
|
||||||
|
"version": "==0.12.0"
|
||||||
|
},
|
||||||
|
"py": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
|
||||||
|
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
|
||||||
|
],
|
||||||
|
"version": "==1.8.0"
|
||||||
|
},
|
||||||
"pylint": {
|
"pylint": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
|
"sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
|
||||||
|
|
@ -347,6 +464,44 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.3.1"
|
"version": "==2.3.1"
|
||||||
},
|
},
|
||||||
|
"pyparsing": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a",
|
||||||
|
"sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03"
|
||||||
|
],
|
||||||
|
"version": "==2.4.0"
|
||||||
|
},
|
||||||
|
"pytest": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6032845e68a17a96e8da3088037f899b56357769a724122056265ca2ea1890ee",
|
||||||
|
"sha256:bea27a646a3d74cbbcf8d3d4a06b2dfc336baf3dc2cc85cf70ad0157e73e8322"
|
||||||
|
],
|
||||||
|
"version": "==4.6.2"
|
||||||
|
},
|
||||||
|
"pytest-aiohttp": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0b9b660b146a65e1313e2083d0d2e1f63047797354af9a28d6b7c9f0726fa33d",
|
||||||
|
"sha256:c929854339637977375838703b62fef63528598bc0a9d451639eba95f4aaa44f"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.3.0"
|
||||||
|
},
|
||||||
|
"pytest-asyncio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf",
|
||||||
|
"sha256:d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.10.0"
|
||||||
|
},
|
||||||
|
"pytest-cov": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6",
|
||||||
|
"sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.7.1"
|
||||||
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||||
|
|
@ -354,6 +509,14 @@
|
||||||
],
|
],
|
||||||
"version": "==1.12.0"
|
"version": "==1.12.0"
|
||||||
},
|
},
|
||||||
|
"testfixtures": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:819e9090bf3cce5dfaf56817f55ede76b3ae490ad0e9ffe3060a6618d11894c3",
|
||||||
|
"sha256:a6e95634a80bd6e5adc7e861729f4abd84433e91b4215d48a2098a096c0261b7"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==6.8.2"
|
||||||
|
},
|
||||||
"toml": {
|
"toml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||||
|
|
@ -386,11 +549,41 @@
|
||||||
"markers": "implementation_name == 'cpython'",
|
"markers": "implementation_name == 'cpython'",
|
||||||
"version": "==1.3.5"
|
"version": "==1.3.5"
|
||||||
},
|
},
|
||||||
|
"wcwidth": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
||||||
|
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
|
||||||
|
],
|
||||||
|
"version": "==0.1.7"
|
||||||
|
},
|
||||||
"wrapt": {
|
"wrapt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533"
|
"sha256:4aea003270831cceb8a90ff27c4031da6ead7ec1886023b80ce0dfe0adf61533"
|
||||||
],
|
],
|
||||||
"version": "==1.11.1"
|
"version": "==1.11.1"
|
||||||
|
},
|
||||||
|
"yarl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9",
|
||||||
|
"sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f",
|
||||||
|
"sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb",
|
||||||
|
"sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320",
|
||||||
|
"sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842",
|
||||||
|
"sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0",
|
||||||
|
"sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829",
|
||||||
|
"sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310",
|
||||||
|
"sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4",
|
||||||
|
"sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8",
|
||||||
|
"sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
|
"zipp": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d",
|
||||||
|
"sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3"
|
||||||
|
],
|
||||||
|
"version": "==0.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,4 @@ test_script:
|
||||||
# Note that you must use the environment variable %PYTHON% to refer to
|
# Note that you must use the environment variable %PYTHON% to refer to
|
||||||
# the interpreter you're using - Appveyor does not do anything special
|
# the interpreter you're using - Appveyor does not do anything special
|
||||||
# to put the Python version you want to use on PATH.
|
# to put the Python version you want to use on PATH.
|
||||||
- "pipenv run nosetests"
|
- "pipenv run python -m pytest"
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class ConfServer:
|
||||||
self.app = None
|
self.app = None
|
||||||
|
|
||||||
def confserver_app(self):
|
def confserver_app(self):
|
||||||
self.app = web.Application()
|
self.app = web.Application(loop=asyncio.get_event_loop())
|
||||||
|
|
||||||
self.app.add_routes(
|
self.app.add_routes(
|
||||||
[
|
[
|
||||||
|
|
@ -125,7 +125,7 @@ class ConfServer:
|
||||||
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/hasUnreadMsg",
|
"/{apiversion}/private/{country}/{language}/{devid}/{apptype}/{appversion}/{devtype}/{aid}/message/hasUnreadMsg",
|
||||||
self.handle_hasUnreadMessage,
|
self.handle_hasUnreadMessage,
|
||||||
),
|
),
|
||||||
web.get( # EcoVacs Home neng message hasUnreadMsg
|
web.post( # EcoVacs Home neng message hasUnreadMsg
|
||||||
"/api/neng/message/hasUnreadMsg", self.handle_neng_hasUnreadMessage
|
"/api/neng/message/hasUnreadMsg", self.handle_neng_hasUnreadMessage
|
||||||
),
|
),
|
||||||
web.get( # EcoVacs Home message getMsgList
|
web.get( # EcoVacs Home message getMsgList
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ logging.getLogger("hbmqtt.client").setLevel(logging.CRITICAL + 1) # Ignore this
|
||||||
class MQTTHelperBot:
|
class MQTTHelperBot:
|
||||||
|
|
||||||
Client = MQTTClient()
|
Client = MQTTClient()
|
||||||
|
wait_resp_timeout_seconds = 10
|
||||||
|
expire_msg_seconds = 10
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(self, address):
|
||||||
self.address = address
|
self.address = address
|
||||||
|
|
@ -105,12 +107,13 @@ class MQTTHelperBot:
|
||||||
# Cleanup "expired messages" > 60 seconds from time
|
# Cleanup "expired messages" > 60 seconds from time
|
||||||
for msg in self.command_responses:
|
for msg in self.command_responses:
|
||||||
expire_time = (
|
expire_time = (
|
||||||
datetime.fromtimestamp(msg["time"]) + timedelta(seconds=10)
|
datetime.fromtimestamp(msg["time"])
|
||||||
|
+ timedelta(seconds=self.expire_msg_seconds)
|
||||||
).timestamp()
|
).timestamp()
|
||||||
if time.time() > expire_time:
|
if time.time() > expire_time:
|
||||||
helperbotlog.debug(
|
helperbotlog.debug(
|
||||||
"Pruning Message Time: {}, MsgTime: {}, MsgTime+60: {}".format(
|
"Pruning Message Due To Expiration - Message Topic: {}".format(
|
||||||
time.time(), msg["time"], expire_time
|
msg["topic"]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.command_responses.remove(msg)
|
self.command_responses.remove(msg)
|
||||||
|
|
@ -118,7 +121,9 @@ class MQTTHelperBot:
|
||||||
async def wait_for_resp(self, requestid):
|
async def wait_for_resp(self, requestid):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
t_end = (datetime.now() + timedelta(seconds=10)).timestamp()
|
t_end = (
|
||||||
|
datetime.now() + timedelta(seconds=self.wait_resp_timeout_seconds)
|
||||||
|
).timestamp()
|
||||||
|
|
||||||
while time.time() < t_end:
|
while time.time() < t_end:
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
@ -186,14 +191,15 @@ class MQTTHelperBot:
|
||||||
|
|
||||||
class MQTTServer:
|
class MQTTServer:
|
||||||
default_config = {}
|
default_config = {}
|
||||||
|
broker = None
|
||||||
|
|
||||||
async def broker_coro(self):
|
async def broker_coro(self):
|
||||||
try:
|
try:
|
||||||
mqttserverlog.info(
|
mqttserverlog.info(
|
||||||
"Starting MQTT Server at {}:{}".format(self.address[0], self.address[1])
|
"Starting MQTT Server at {}:{}".format(self.address[0], self.address[1])
|
||||||
)
|
)
|
||||||
broker = hbmqtt.broker.Broker(config=self.default_config)
|
self.broker = hbmqtt.broker.Broker(config=self.default_config)
|
||||||
await broker.start()
|
await self.broker.start()
|
||||||
|
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
if "bind" in e.strerror:
|
if "bind" in e.strerror:
|
||||||
|
|
@ -360,12 +366,14 @@ class BumperMQTTServer_Plugin:
|
||||||
bot = bumper.bot_get(didsplit[0])
|
bot = bumper.bot_get(didsplit[0])
|
||||||
if bot:
|
if bot:
|
||||||
bumper.bot_set_mqtt(bot["did"], False)
|
bumper.bot_set_mqtt(bot["did"], False)
|
||||||
|
return
|
||||||
|
|
||||||
# clientuserid = didsplit[0]
|
# clientuserid = didsplit[0]
|
||||||
clientresource = didsplit[1].split("/")[1]
|
clientresource = didsplit[1].split("/")[1]
|
||||||
client = bumper.client_get(clientresource)
|
client = bumper.client_get(clientresource)
|
||||||
if client:
|
if client:
|
||||||
bumper.client_set_mqtt(client["resource"], False)
|
bumper.client_set_mqtt(client["resource"], False)
|
||||||
|
return
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mqttserverlog.exception("{}".format(e))
|
mqttserverlog.exception("{}".format(e))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class XMPPServer:
|
||||||
server_id = "ecouser.net"
|
server_id = "ecouser.net"
|
||||||
clients = []
|
clients = []
|
||||||
exit_flag = False
|
exit_flag = False
|
||||||
|
server = None
|
||||||
|
|
||||||
def __init__(self, address):
|
def __init__(self, address):
|
||||||
# Initialize bot server
|
# Initialize bot server
|
||||||
|
|
@ -27,12 +28,11 @@ class XMPPServer:
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
server = await loop.create_server(
|
self.server = await loop.create_server(
|
||||||
self.xmpp_protocol, host=self.address[0], port=self.address[1]
|
self.xmpp_protocol, host=self.address[0], port=self.address[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
async with server:
|
self.server_coro = loop.create_task(self.server.serve_forever())
|
||||||
await server.serve_forever()
|
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -42,6 +42,7 @@ class XMPPServer:
|
||||||
|
|
||||||
self.exit_flag = True
|
self.exit_flag = True
|
||||||
xmppserverlog.debug("shutting down")
|
xmppserverlog.debug("shutting down")
|
||||||
|
self.server_coro.cancel()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
xmppserverlog.error("{}".format(e))
|
xmppserverlog.error("{}".format(e))
|
||||||
|
|
@ -402,22 +403,20 @@ class XMPPAsyncClient:
|
||||||
if self.TLSUpgraded == False:
|
if self.TLSUpgraded == False:
|
||||||
# With STARTTLS #https://xmpp.org/rfcs/rfc3920.html
|
# With STARTTLS #https://xmpp.org/rfcs/rfc3920.html
|
||||||
self.send(
|
self.send(
|
||||||
'<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><auth xmlns="http://jabber.org/features/iq-auth"/><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
'<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Already using TLS send authentication support for iq-auth (fallback) and SASL
|
# Already using TLS send authentication support for SASL
|
||||||
self.send(
|
self.send(
|
||||||
'<stream:features><auth xmlns="http://jabber.org/features/iq-auth"/><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
'<stream:features><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.send("</stream>")
|
self.send("</stream>")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if "jabber:iq:auth" in xml.tag: # Handle iq-auth
|
if (
|
||||||
self._handle_iq_auth(xml)
|
|
||||||
elif (
|
|
||||||
"urn:ietf:params:xml:ns:xmpp-sasl" in xml.tag
|
"urn:ietf:params:xml:ns:xmpp-sasl" in xml.tag
|
||||||
): # Handle SASL Auth
|
): # Handle SASL Auth
|
||||||
self._handle_sasl_auth(xml)
|
self._handle_sasl_auth(xml)
|
||||||
|
|
@ -486,98 +485,6 @@ class XMPPAsyncClient:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
xmppserverlog.exception("{}".format(e))
|
xmppserverlog.exception("{}".format(e))
|
||||||
|
|
||||||
def _handle_iq_auth(self, data):
|
|
||||||
try:
|
|
||||||
xml = ET.fromstring(data.decode("utf-8"))
|
|
||||||
ctl = xml[0][0]
|
|
||||||
xmppserverlog.info("IQ AUTH XML: {}".format(xml))
|
|
||||||
# Received username and auth tag, send username/password requirement
|
|
||||||
if (
|
|
||||||
xml.get("type") == "get"
|
|
||||||
and "auth}username" in ctl.tag
|
|
||||||
and self.type == self.UNKNOWN
|
|
||||||
):
|
|
||||||
self.send(
|
|
||||||
'<iq type="result" id="{}"><query xmlns="jabber:iq:auth"><username/><password/></query></iq>'.format(
|
|
||||||
xml.get("id")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Received username, password, resource - Handle auth here and return pass or fail
|
|
||||||
if (
|
|
||||||
xml.get("type") == "set"
|
|
||||||
and "auth}username" in ctl.tag
|
|
||||||
and self.type == self.UNKNOWN
|
|
||||||
):
|
|
||||||
xmlauth = xml[0].getchildren()
|
|
||||||
# uid = ""
|
|
||||||
password = ""
|
|
||||||
authcode = ""
|
|
||||||
resource = ""
|
|
||||||
for aitem in xmlauth:
|
|
||||||
if "username" in aitem.tag:
|
|
||||||
self.uid = aitem.text
|
|
||||||
|
|
||||||
elif "password" in aitem.tag:
|
|
||||||
password = aitem.text.split("/")[2]
|
|
||||||
authcode = password
|
|
||||||
|
|
||||||
elif "resource" in aitem.tag:
|
|
||||||
self.clientresource = aitem.text
|
|
||||||
resource = self.clientresource
|
|
||||||
|
|
||||||
if self.devclass: # if there is a devclass it is a bot
|
|
||||||
bumper.bot_add("", self.uid, "", resource, "eco-legacy")
|
|
||||||
xmppserverlog.debug("bot authenticated {}".format(self.uid))
|
|
||||||
|
|
||||||
# Client authenticated, move to next state
|
|
||||||
self._set_state("INIT")
|
|
||||||
|
|
||||||
# Successful auth
|
|
||||||
self.send('<iq type="result" id="{}"/>'.format(xml.get("id")))
|
|
||||||
|
|
||||||
else:
|
|
||||||
auth = False
|
|
||||||
if bumper.check_authcode(self.uid, authcode):
|
|
||||||
auth = True
|
|
||||||
elif bumper.use_auth == False:
|
|
||||||
auth = True
|
|
||||||
|
|
||||||
if auth:
|
|
||||||
bumper.client_add(self.uid, "bumper", self.clientresource)
|
|
||||||
xmppserverlog.debug("client authenticated {}".format(self.uid))
|
|
||||||
|
|
||||||
# Client authenticated, move to next state
|
|
||||||
self._set_state("INIT")
|
|
||||||
|
|
||||||
# Successful auth
|
|
||||||
self.send('<iq type="result" id="{}"/>'.format(xml.get("id")))
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Failed auth
|
|
||||||
self.send(
|
|
||||||
'<iq type="error" id="{}"><error code="401" type="auth"><not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>'.format(
|
|
||||||
xml.get("id")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
except ET.ParseError as e:
|
|
||||||
if "no element found" in e.msg:
|
|
||||||
xmppserverlog.debug(
|
|
||||||
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
|
||||||
)
|
|
||||||
elif "not well-formed (invalid token)" in e.msg:
|
|
||||||
xmppserverlog.debug(
|
|
||||||
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
xmppserverlog.debug(
|
|
||||||
"xml parse error - {} - {}".format(data.decode("utf-8"), e)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
xmppserverlog.exception("{}".format(e))
|
|
||||||
|
|
||||||
def _handle_sasl_auth(self, xml):
|
def _handle_sasl_auth(self, xml):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ async def main():
|
||||||
xmpp_address = (listen_host, 5223)
|
xmpp_address = (listen_host, 5223)
|
||||||
mqtt_address = (listen_host, 8883)
|
mqtt_address = (listen_host, 8883)
|
||||||
|
|
||||||
xmpp_server = bumper.XMPPServer(xmpp_address)
|
|
||||||
mqtt_server = bumper.MQTTServer(mqtt_address)
|
mqtt_server = bumper.MQTTServer(mqtt_address)
|
||||||
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
conf_server = bumper.ConfServer(
|
conf_server = bumper.ConfServer(
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,5 @@
|
||||||
from nose.tools import *
|
|
||||||
import nose
|
|
||||||
import mock
|
import mock
|
||||||
|
import pytest
|
||||||
from tinydb.storages import MemoryStorage
|
from tinydb.storages import MemoryStorage
|
||||||
from tinydb import TinyDB, Query
|
from tinydb import TinyDB, Query
|
||||||
import bumper
|
import bumper
|
||||||
|
|
@ -10,13 +9,13 @@ import platform
|
||||||
|
|
||||||
|
|
||||||
def test_get_milli_time():
|
def test_get_milli_time():
|
||||||
assert_equals(
|
assert (
|
||||||
bumper.get_milli_time(
|
bumper.get_milli_time(
|
||||||
datetime.datetime(
|
datetime.datetime(
|
||||||
2018, 1, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc
|
2018, 1, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc
|
||||||
).timestamp()
|
).timestamp()
|
||||||
),
|
)
|
||||||
1514768400000,
|
== 1514768400000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -27,80 +26,73 @@ def test_user_db():
|
||||||
# Test os_db_path
|
# Test os_db_path
|
||||||
platform.system = mock.MagicMock(return_value="Windows")
|
platform.system = mock.MagicMock(return_value="Windows")
|
||||||
p = platform.system()
|
p = platform.system()
|
||||||
os.getenv = mock.MagicMock(return_value="C:\AppData")
|
os.getenv = mock.MagicMock(return_value="C:\\AppData")
|
||||||
o = os.getenv("APPDATA")
|
o = os.getenv("APPDATA")
|
||||||
assert_equals(bumper.os_db_path(createdir=False), os.path.join(os.getenv("APPDATA"), "bumper.db"))
|
assert bumper.os_db_path(createdir=False) == os.path.join(
|
||||||
|
os.getenv("APPDATA"), "bumper.db"
|
||||||
|
)
|
||||||
|
|
||||||
platform.system = mock.MagicMock(return_value="Linux")
|
platform.system = mock.MagicMock(return_value="Linux")
|
||||||
assert_equals(bumper.os_db_path(createdir=False), os.path.expanduser("~/.config/bumper.db"))
|
assert bumper.os_db_path(createdir=False) == os.path.expanduser(
|
||||||
|
"~/.config/bumper.db"
|
||||||
|
)
|
||||||
|
|
||||||
bumper.db = "tests/tmp.db" # Set db location for testing
|
bumper.db = "tests/tmp.db" # Set db location for testing
|
||||||
bumper.user_add("testuser") # Add testuser
|
bumper.user_add("testuser") # Add testuser
|
||||||
|
|
||||||
assert_equals(
|
assert (
|
||||||
bumper.user_get("testuser")["userid"], "testuser"
|
bumper.user_get("testuser")["userid"] == "testuser"
|
||||||
) # Test that testuser was created and returned
|
) # Test that testuser was created and returned
|
||||||
|
|
||||||
bumper.user_add_device("testuser", "dev_1234") # Add device to testuser
|
bumper.user_add_device("testuser", "dev_1234") # Add device to testuser
|
||||||
|
|
||||||
assert_equals(
|
assert (
|
||||||
bumper.user_by_deviceid("dev_1234")["userid"], "testuser"
|
bumper.user_by_deviceid("dev_1234")["userid"] == "testuser"
|
||||||
) # Test that testuser was found by deviceid
|
) # Test that testuser was found by deviceid
|
||||||
|
|
||||||
bumper.user_remove_device("testuser", "dev_1234") # Remove device from testuser
|
bumper.user_remove_device("testuser", "dev_1234") # Remove device from testuser
|
||||||
|
|
||||||
assert_true(
|
assert "dev_1234" not in bumper.user_get("testuser")["devices"]
|
||||||
"dev_1234" not in bumper.user_get("testuser")["devices"]
|
# Test that dev_1234 was not found in testuser devices
|
||||||
) # Test that dev_1234 was not found in testuser devices
|
|
||||||
|
|
||||||
bumper.user_add_bot("testuser", "bot_1234") # Add bot did to testuser
|
bumper.user_add_bot("testuser", "bot_1234") # Add bot did to testuser
|
||||||
|
|
||||||
assert_true(
|
assert "bot_1234" in bumper.user_get("testuser")["bots"]
|
||||||
"bot_1234" in bumper.user_get("testuser")["bots"]
|
# Test that bot was found in testuser's bot list
|
||||||
) # Test that bot was found in testuser's bot list
|
|
||||||
|
|
||||||
bumper.user_remove_bot("testuser", "bot_1234") # Remove bot did from testuser
|
bumper.user_remove_bot("testuser", "bot_1234") # Remove bot did from testuser
|
||||||
|
|
||||||
assert_true(
|
assert "bot_1234" not in bumper.user_get("testuser")["bots"]
|
||||||
"bot_1234" not in bumper.user_get("testuser")["bots"]
|
# Test that bot was not found in testuser's bot list
|
||||||
) # Test that bot was not found in testuser's bot list
|
|
||||||
|
|
||||||
bumper.user_add_token("testuser", "token_1234") # Add token to testuser
|
bumper.user_add_token("testuser", "token_1234") # Add token to testuser
|
||||||
|
|
||||||
assert_true(
|
assert bumper.check_token("testuser", "token_1234")
|
||||||
bumper.check_token("testuser", "token_1234")
|
# Test that token was found for testuser
|
||||||
) # Test that token was found for testuser
|
|
||||||
|
|
||||||
assert_true(
|
assert bumper.user_get_token("testuser", "token_1234")
|
||||||
bumper.user_get_token("testuser", "token_1234")
|
# Test that token was returned for testuser
|
||||||
) # Test that token was returned for testuser
|
|
||||||
|
|
||||||
bumper.user_add_authcode(
|
bumper.user_add_authcode(
|
||||||
"testuser", "token_1234", "auth_1234"
|
"testuser", "token_1234", "auth_1234"
|
||||||
) # Add authcode to token_1234 for testuser
|
) # Add authcode to token_1234 for testuser
|
||||||
assert_true(
|
assert bumper.check_authcode("testuser", "auth_1234")
|
||||||
bumper.check_authcode("testuser", "auth_1234")
|
# Test that authcode was found for testuser
|
||||||
) # Test that authcode was found for testuser
|
|
||||||
|
|
||||||
bumper.user_revoke_authcode(
|
bumper.user_revoke_authcode(
|
||||||
"testuser", "token_1234", "auth_1234"
|
"testuser", "token_1234", "auth_1234"
|
||||||
) # Remove authcode from testuser
|
) # Remove authcode from testuser
|
||||||
assert_false(
|
assert bumper.check_authcode("testuser", "auth_1234") == False
|
||||||
bumper.check_authcode("testuser", "auth_1234")
|
# Test that authcode was not found for testuser
|
||||||
) # Test that authcode was not found for testuser
|
|
||||||
bumper.user_revoke_token("testuser", "token_1234") # Remove token from testuser
|
bumper.user_revoke_token("testuser", "token_1234") # Remove token from testuser
|
||||||
assert_false(
|
assert (
|
||||||
bumper.check_token("testuser", "token_1234")
|
bumper.check_token("testuser", "token_1234") == False
|
||||||
) # Test that token was not found for testuser
|
) # Test that token was not found for testuser
|
||||||
bumper.user_add_token("testuser", "token_1234") # Add token_1234
|
bumper.user_add_token("testuser", "token_1234") # Add token_1234
|
||||||
bumper.user_add_token("testuser", "token_4321") # Add token_4321
|
bumper.user_add_token("testuser", "token_4321") # Add token_4321
|
||||||
assert_equals(
|
assert len(bumper.user_get_tokens("testuser")) == 2 # Test 2 tokens are available
|
||||||
len(bumper.user_get_tokens("testuser")), 2
|
|
||||||
) # Test 2 tokens are available
|
|
||||||
bumper.user_revoke_all_tokens("testuser") # Revoke all tokens
|
bumper.user_revoke_all_tokens("testuser") # Revoke all tokens
|
||||||
assert_equals(
|
assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available
|
||||||
len(bumper.user_get_tokens("testuser")), 0
|
|
||||||
) # Test 0 tokens are available
|
|
||||||
|
|
||||||
db = TinyDB("tests/tmp.db")
|
db = TinyDB("tests/tmp.db")
|
||||||
tokens = db.table("tokens")
|
tokens = db.table("tokens")
|
||||||
|
|
@ -114,13 +106,9 @@ def test_user_db():
|
||||||
}
|
}
|
||||||
) # Add expired token
|
) # Add expired token
|
||||||
db.close()
|
db.close()
|
||||||
assert_equals(
|
assert len(bumper.user_get_tokens("testuser")) == 1 # Test 1 tokens are available
|
||||||
len(bumper.user_get_tokens("testuser")), 1
|
|
||||||
) # Test 1 tokens are available
|
|
||||||
bumper.user_revoke_expired_tokens("testuser") # Revoke expired tokens
|
bumper.user_revoke_expired_tokens("testuser") # Revoke expired tokens
|
||||||
assert_equals(
|
assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available
|
||||||
len(bumper.user_get_tokens("testuser")), 0
|
|
||||||
) # Test 0 tokens are available
|
|
||||||
|
|
||||||
db = TinyDB("tests/tmp.db")
|
db = TinyDB("tests/tmp.db")
|
||||||
tokens = db.table("tokens")
|
tokens = db.table("tokens")
|
||||||
|
|
@ -134,53 +122,49 @@ def test_user_db():
|
||||||
}
|
}
|
||||||
) # Add expired token
|
) # Add expired token
|
||||||
db.close()
|
db.close()
|
||||||
assert_equals(
|
assert len(bumper.user_get_tokens("testuser")) == 1 # Test 1 tokens are available
|
||||||
len(bumper.user_get_tokens("testuser")), 1
|
|
||||||
) # Test 1 tokens are available
|
|
||||||
bumper.revoke_expired_tokens() # Revoke expired tokens
|
bumper.revoke_expired_tokens() # Revoke expired tokens
|
||||||
assert_equals(
|
assert len(bumper.user_get_tokens("testuser")) == 0 # Test 0 tokens are available
|
||||||
len(bumper.user_get_tokens("testuser")), 0
|
|
||||||
) # Test 0 tokens are available
|
|
||||||
|
|
||||||
|
|
||||||
def test_bot_db():
|
def test_bot_db():
|
||||||
bumper.db = "tests/tmp.db" # Set db location for testing
|
bumper.db = "tests/tmp.db" # Set db location for testing
|
||||||
bumper.bot_add("sn_123", "did_123", "dev_123", "res_123", "co_123")
|
bumper.bot_add("sn_123", "did_123", "dev_123", "res_123", "co_123")
|
||||||
assert_true(bumper.bot_get("did_123")) # Test that bot was added to db
|
assert bumper.bot_get("did_123") # Test that bot was added to db
|
||||||
|
|
||||||
bumper.bot_set_nick("did_123", "nick_123")
|
bumper.bot_set_nick("did_123", "nick_123")
|
||||||
assert_equals(
|
assert (
|
||||||
bumper.bot_get("did_123")["nick"], "nick_123"
|
bumper.bot_get("did_123")["nick"] == "nick_123"
|
||||||
) # Test that nick was added to bot
|
) # Test that nick was added to bot
|
||||||
|
|
||||||
bumper.bot_set_mqtt("did_123", True)
|
bumper.bot_set_mqtt("did_123", True)
|
||||||
assert_true(
|
assert bumper.bot_get("did_123")[
|
||||||
bumper.bot_get("did_123")["mqtt_connection"]
|
"mqtt_connection"
|
||||||
) # Test that mqtt was set True for bot
|
] # Test that mqtt was set True for bot
|
||||||
|
|
||||||
bumper.bot_set_xmpp("did_123", True)
|
bumper.bot_set_xmpp("did_123", True)
|
||||||
assert_true(
|
assert bumper.bot_get("did_123")[
|
||||||
bumper.bot_get("did_123")["xmpp_connection"]
|
"xmpp_connection"
|
||||||
) # Test that xmpp was set True for bot
|
] # Test that xmpp was set True for bot
|
||||||
|
|
||||||
bumper.bot_remove("did_123")
|
bumper.bot_remove("did_123")
|
||||||
assert_false(bumper.bot_get("did_123")) # Test that bot is no longer in db
|
assert bumper.bot_get("did_123") == None # Test that bot is no longer in db
|
||||||
|
|
||||||
|
|
||||||
def test_client_db():
|
def test_client_db():
|
||||||
bumper.db = "tests/tmp.db" # Set db location for testing
|
bumper.db = "tests/tmp.db" # Set db location for testing
|
||||||
bumper.client_add("user_123", "realm_123", "resource_123")
|
bumper.client_add("user_123", "realm_123", "resource_123")
|
||||||
assert_true(bumper.client_get("resource_123")) # Test client was added
|
assert bumper.client_get("resource_123") # Test client was added
|
||||||
|
|
||||||
bumper.client_set_mqtt("resource_123", True)
|
bumper.client_set_mqtt("resource_123", True)
|
||||||
assert_true(
|
assert bumper.client_get("resource_123")[
|
||||||
bumper.client_get("resource_123")["mqtt_connection"]
|
"mqtt_connection"
|
||||||
) # Test that mqtt was set True for client
|
] # Test that mqtt was set True for client
|
||||||
|
|
||||||
bumper.client_set_xmpp("resource_123", False)
|
bumper.client_set_xmpp("resource_123", False)
|
||||||
assert_false(
|
assert (
|
||||||
bumper.client_get("resource_123")["xmpp_connection"]
|
bumper.client_get("resource_123")["xmpp_connection"] == False
|
||||||
) # Test that xmpp was set False for client
|
) # Test that xmpp was set False for client
|
||||||
assert_equals(
|
assert (
|
||||||
len(bumper.get_disconnected_xmpp_clients()), 1
|
len(bumper.get_disconnected_xmpp_clients()) == 1
|
||||||
) # Test len of connected xmpp clients is 1
|
) # Test len of connected xmpp clients is 1
|
||||||
371
tests/test_mqttserver.py
Normal file
371
tests/test_mqttserver.py
Normal file
|
|
@ -0,0 +1,371 @@
|
||||||
|
import mock
|
||||||
|
import bumper
|
||||||
|
import asyncio
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import tinydb
|
||||||
|
import pytest_asyncio
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import hbmqtt
|
||||||
|
import logging
|
||||||
|
from testfixtures import LogCapture
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
async def test_helperbot_message():
|
||||||
|
with LogCapture("helperbot") as l:
|
||||||
|
mqtt_address = ("127.0.0.1", 8883)
|
||||||
|
mqtt_server = bumper.MQTTServer(mqtt_address)
|
||||||
|
await mqtt_server.broker_coro()
|
||||||
|
#broker = mqtt_server.broker
|
||||||
|
#mqtt_address = ("127.0.0.1", 8883)
|
||||||
|
#mqtt_server = bumper.MQTTServer(mqtt_address)
|
||||||
|
#broker = hbmqtt.broker.Broker(
|
||||||
|
# mqtt_server.default_config, plugin_namespace="hbmqtt.test.plugins"
|
||||||
|
#)
|
||||||
|
#await broker.start()
|
||||||
|
|
||||||
|
# Test broadcast message
|
||||||
|
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
await mqtt_helperbot.start_helper_bot()
|
||||||
|
assert (
|
||||||
|
mqtt_helperbot.Client._connected_state._value == True
|
||||||
|
) # Check helperbot is connected
|
||||||
|
msg_payload = "<ctl ts='1547822804960' td='DustCaseST' st='0'/>"
|
||||||
|
msg_topic_name = "iot/atr/DustCaseST/bot_serial/ls1ok3/wC3g/x"
|
||||||
|
await mqtt_helperbot.Client.publish(
|
||||||
|
msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
l.check_present(
|
||||||
|
(
|
||||||
|
"helperbot",
|
||||||
|
"DEBUG",
|
||||||
|
"Received Broadcast - Topic: iot/atr/DustCaseST/bot_serial/ls1ok3/wC3g/x - Message: <ctl ts='1547822804960' td='DustCaseST' st='0'/>",
|
||||||
|
)
|
||||||
|
) # Check broadcast message was logged
|
||||||
|
l.clear()
|
||||||
|
mqtt_helperbot.Client.disconnect()
|
||||||
|
|
||||||
|
# Send command to bot
|
||||||
|
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
await mqtt_helperbot.start_helper_bot()
|
||||||
|
assert (
|
||||||
|
mqtt_helperbot.Client._connected_state._value == True
|
||||||
|
) # Check helperbot is connected
|
||||||
|
msg_payload = "{}"
|
||||||
|
msg_topic_name = (
|
||||||
|
"iot/p2p/GetWKVer/helper1/bumper/helper1/bot_serial/ls1ok3/wC3g/q/iCmuqp/j"
|
||||||
|
)
|
||||||
|
await mqtt_helperbot.Client.publish(
|
||||||
|
msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
l.check_present(
|
||||||
|
(
|
||||||
|
"helperbot",
|
||||||
|
"DEBUG",
|
||||||
|
"Send Command - Topic: iot/p2p/GetWKVer/helper1/bumper/helper1/bot_serial/ls1ok3/wC3g/q/iCmuqp/j - Message: {}",
|
||||||
|
)
|
||||||
|
) # Check send command message was logged
|
||||||
|
l.clear()
|
||||||
|
mqtt_helperbot.Client.disconnect()
|
||||||
|
|
||||||
|
# Received response to command
|
||||||
|
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
await mqtt_helperbot.start_helper_bot()
|
||||||
|
assert (
|
||||||
|
mqtt_helperbot.Client._connected_state._value == True
|
||||||
|
) # Check helperbot is connected
|
||||||
|
msg_payload = '{"ret":"ok","ver":"0.13.5"}'
|
||||||
|
msg_topic_name = (
|
||||||
|
"iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/iCmuqp/j"
|
||||||
|
)
|
||||||
|
await mqtt_helperbot.Client.publish(
|
||||||
|
msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
l.check_present(
|
||||||
|
(
|
||||||
|
"helperbot",
|
||||||
|
"DEBUG",
|
||||||
|
'Received Response - Topic: iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/iCmuqp/j - Message: {"ret":"ok","ver":"0.13.5"}',
|
||||||
|
)
|
||||||
|
) # Check received response message was logged
|
||||||
|
l.clear()
|
||||||
|
mqtt_helperbot.Client.disconnect()
|
||||||
|
|
||||||
|
# Received unknown message
|
||||||
|
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
await mqtt_helperbot.start_helper_bot()
|
||||||
|
assert (
|
||||||
|
mqtt_helperbot.Client._connected_state._value == True
|
||||||
|
) # Check helperbot is connected
|
||||||
|
msg_payload = "test"
|
||||||
|
msg_topic_name = (
|
||||||
|
"iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helper1/p/iCmuqp/j"
|
||||||
|
)
|
||||||
|
await mqtt_helperbot.Client.publish(
|
||||||
|
msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
l.check_present(
|
||||||
|
(
|
||||||
|
"helperbot",
|
||||||
|
"DEBUG",
|
||||||
|
"Received Message - Topic: iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/TESTBAD/bumper/helper1/p/iCmuqp/j - Message: test",
|
||||||
|
)
|
||||||
|
) # Check received message was logged
|
||||||
|
l.clear()
|
||||||
|
mqtt_helperbot.Client.disconnect()
|
||||||
|
await mqtt_server.broker.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_helperbot_expire_message():
|
||||||
|
with LogCapture("helperbot") as l:
|
||||||
|
mqtt_address = ("127.0.0.1", 8883)
|
||||||
|
mqtt_server = bumper.MQTTServer(mqtt_address)
|
||||||
|
await mqtt_server.broker_coro()
|
||||||
|
#mqtt_address = ("127.0.0.1", 8883)
|
||||||
|
#mqtt_server = bumper.MQTTServer(mqtt_address)
|
||||||
|
#broker = hbmqtt.broker.Broker(
|
||||||
|
# mqtt_server.default_config, plugin_namespace="hbmqtt.test.plugins"
|
||||||
|
#)
|
||||||
|
#await broker.start()
|
||||||
|
|
||||||
|
# Test broadcast message
|
||||||
|
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
await mqtt_helperbot.start_helper_bot()
|
||||||
|
assert (
|
||||||
|
mqtt_helperbot.Client._connected_state._value == True
|
||||||
|
) # Check helperbot is connected
|
||||||
|
|
||||||
|
expire_msg_payload = '{"ret":"ok","ver":"0.13.5"}'
|
||||||
|
expire_msg_topic_name = "iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/testgood/j"
|
||||||
|
currenttime = time.time()
|
||||||
|
mqtt_helperbot.command_responses.append(
|
||||||
|
{
|
||||||
|
"time": currenttime,
|
||||||
|
"topic": expire_msg_topic_name,
|
||||||
|
"payload": expire_msg_payload,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {
|
||||||
|
"time": currenttime,
|
||||||
|
"topic": expire_msg_topic_name,
|
||||||
|
"payload": expire_msg_payload,
|
||||||
|
} in mqtt_helperbot.command_responses # check message is in command_responses
|
||||||
|
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
mqtt_helperbot.expire_msg_seconds = (
|
||||||
|
0.1
|
||||||
|
) # Set expire message seconds to 0.1 so we don't wait 10 seconds
|
||||||
|
msg_payload = "<ctl ts='1547822804960' td='DustCaseST' st='0'/>"
|
||||||
|
msg_topic_name = "iot/atr/DustCaseST/bot_serial/ls1ok3/wC3g/x"
|
||||||
|
await mqtt_helperbot.Client.publish(
|
||||||
|
msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0
|
||||||
|
) # Send another message to force get_msg
|
||||||
|
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(mqtt_helperbot.Client.deliver_message(), timeout=0.1)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert {
|
||||||
|
"time": currenttime,
|
||||||
|
"topic": expire_msg_topic_name,
|
||||||
|
"payload": expire_msg_payload,
|
||||||
|
} not in mqtt_helperbot.command_responses # check message was expired and removed from command_responses
|
||||||
|
|
||||||
|
l.check_present(
|
||||||
|
(
|
||||||
|
"helperbot",
|
||||||
|
"DEBUG",
|
||||||
|
"Pruning Message Due To Expiration - Message Topic: {}".format(
|
||||||
|
expire_msg_topic_name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) # Check received message was logged
|
||||||
|
mqtt_helperbot.Client.disconnect()
|
||||||
|
await mqtt_server.broker.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_helperbot_sendcommand():
|
||||||
|
mqtt_address = ("127.0.0.1", 8883)
|
||||||
|
mqtt_server = bumper.MQTTServer(mqtt_address)
|
||||||
|
await mqtt_server.broker_coro()
|
||||||
|
#mqtt_address = ("127.0.0.1", 8883)
|
||||||
|
#mqtt_server = bumper.MQTTServer(mqtt_address)
|
||||||
|
#broker = hbmqtt.broker.Broker(
|
||||||
|
# mqtt_server.default_config, plugin_namespace="hbmqtt.test.plugins"
|
||||||
|
#)
|
||||||
|
#await broker.start()
|
||||||
|
|
||||||
|
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
await mqtt_helperbot.start_helper_bot()
|
||||||
|
assert (
|
||||||
|
mqtt_helperbot.Client._connected_state._value == True
|
||||||
|
) # Check helperbot is connected
|
||||||
|
|
||||||
|
cmdjson = {
|
||||||
|
"toType": "ls1ok3",
|
||||||
|
"payloadType": "j",
|
||||||
|
"toRes": "wC3g",
|
||||||
|
"payload": {},
|
||||||
|
"td": "q",
|
||||||
|
"toId": "bot_serial",
|
||||||
|
"cmdName": "GetWKVer",
|
||||||
|
"auth": {
|
||||||
|
"token": "us_52cb21fef8e547f38f4ec9a699a5d77e",
|
||||||
|
"resource": "IOSF53D07BA",
|
||||||
|
"userid": "fuid_tmpuser",
|
||||||
|
"with": "users",
|
||||||
|
"realm": "ecouser.net",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mqtt_helperbot.wait_resp_timeout_seconds = (
|
||||||
|
0.1
|
||||||
|
) # Override wait_resp_timeout (so we don't wait 10 seconds for timeout)
|
||||||
|
commandresult = await mqtt_helperbot.send_command(cmdjson, "testfail")
|
||||||
|
# Don't send a response, ensure timeout
|
||||||
|
assert commandresult == {
|
||||||
|
"debug": "wait for response timed out",
|
||||||
|
"errno": 500,
|
||||||
|
"id": "testfail",
|
||||||
|
"ret": "fail",
|
||||||
|
} # Check timeout
|
||||||
|
|
||||||
|
mqtt_helperbot.wait_resp_timeout_seconds = (
|
||||||
|
0.2
|
||||||
|
) # Override wait_resp_timeout (so we don't wait 10 seconds for timeout)
|
||||||
|
# Send response beforehand
|
||||||
|
msg_payload = '{"ret":"ok","ver":"0.13.5"}'
|
||||||
|
msg_topic_name = (
|
||||||
|
"iot/p2p/GetWKVer/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/testgood/j"
|
||||||
|
)
|
||||||
|
await mqtt_helperbot.Client.publish(
|
||||||
|
msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0
|
||||||
|
)
|
||||||
|
|
||||||
|
commandresult = await mqtt_helperbot.send_command(cmdjson, "testgood")
|
||||||
|
assert commandresult == {
|
||||||
|
"id": "testgood",
|
||||||
|
"resp": {"ret": "ok", "ver": "0.13.5"},
|
||||||
|
"ret": "ok",
|
||||||
|
}
|
||||||
|
|
||||||
|
mqtt_helperbot.Client.disconnect()
|
||||||
|
|
||||||
|
cmdjson = {
|
||||||
|
"toType": "ls1ok3",
|
||||||
|
"payloadType": "x",
|
||||||
|
"toRes": "wC3g",
|
||||||
|
"payload": '<ctl type="Brush"/>',
|
||||||
|
"td": "q",
|
||||||
|
"toId": "bot_serial",
|
||||||
|
"cmdName": "GetLifeSpan",
|
||||||
|
"auth": {
|
||||||
|
"token": "us_52cb21fef8e547f38f4ec9a699a5d77e",
|
||||||
|
"resource": "IOSF53D07BA",
|
||||||
|
"userid": "fuid_tmpuser",
|
||||||
|
"with": "users",
|
||||||
|
"realm": "ecouser.net",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mqtt_helperbot.wait_resp_timeout_seconds = (
|
||||||
|
0.2
|
||||||
|
) # Override wait_resp_timeout (so we don't wait 10 seconds for timeout)
|
||||||
|
# Send response beforehand
|
||||||
|
msg_payload = (
|
||||||
|
"{'id': 'testx', 'ret': 'ok', 'resp': "
|
||||||
|
"<ctl ret='ok' type='Brush' left='4142' total='18000'/>"
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
msg_topic_name = (
|
||||||
|
"iot/p2p/GetLifeSpan/bot_serial/ls1ok3/wC3g/helper1/bumper/helper1/p/testx/q"
|
||||||
|
)
|
||||||
|
await mqtt_helperbot.Client.publish(
|
||||||
|
msg_topic_name, msg_payload.encode(), hbmqtt.client.QOS_0
|
||||||
|
)
|
||||||
|
|
||||||
|
commandresult = await mqtt_helperbot.send_command(cmdjson, "testx")
|
||||||
|
assert commandresult == {
|
||||||
|
"id": "testx",
|
||||||
|
"resp": "{'id': 'testx', 'ret': 'ok', 'resp': <ctl ret='ok' type='Brush' left='4142' total='18000'/>}",
|
||||||
|
"ret": "ok",
|
||||||
|
}
|
||||||
|
|
||||||
|
mqtt_helperbot.Client.disconnect()
|
||||||
|
|
||||||
|
await mqtt_server.broker.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mqttserver():
|
||||||
|
if os.path.exists("tests/tmp.db"):
|
||||||
|
os.remove("tests/tmp.db") # Remove existing db
|
||||||
|
|
||||||
|
bumper.db = "tests/tmp.db" # Set db location for testing
|
||||||
|
|
||||||
|
mqtt_address = ("127.0.0.1", 8883)
|
||||||
|
|
||||||
|
mqtt_server = bumper.MQTTServer(mqtt_address)
|
||||||
|
await mqtt_server.broker_coro()
|
||||||
|
|
||||||
|
# Test helperbot connect
|
||||||
|
mqtt_helperbot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
await mqtt_helperbot.start_helper_bot()
|
||||||
|
assert (
|
||||||
|
mqtt_helperbot.Client._connected_state._value == True
|
||||||
|
) # Check helperbot is connected
|
||||||
|
await mqtt_helperbot.Client.disconnect()
|
||||||
|
|
||||||
|
# Test client connect
|
||||||
|
bumper.user_add("user_123") # Add user to db
|
||||||
|
bumper.client_add("user_123", "ecouser.net", "resource_123") # Add client to db
|
||||||
|
test_client = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
test_client.client_id = "user_123@ecouser.net/resource_123"
|
||||||
|
# await test_client.start_helper_bot()
|
||||||
|
test_client.Client = hbmqtt.client.MQTTClient(
|
||||||
|
client_id=test_client.client_id, config={"check_hostname": False}
|
||||||
|
)
|
||||||
|
|
||||||
|
await test_client.Client.connect(
|
||||||
|
"mqtts://{}:{}/".format(test_client.address[0], test_client.address[1]),
|
||||||
|
cafile=bumper.ca_cert,
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
test_client.Client._connected_state._value == True
|
||||||
|
) # Check client is connected
|
||||||
|
await test_client.Client.disconnect()
|
||||||
|
assert (
|
||||||
|
test_client.Client._connected_state._value == False
|
||||||
|
) # Check client is disconnected
|
||||||
|
|
||||||
|
# Test fake_bot connect
|
||||||
|
fake_bot = bumper.MQTTHelperBot(mqtt_address)
|
||||||
|
fake_bot.client_id = "bot_serial@ls1ok3/wC3g"
|
||||||
|
await fake_bot.start_helper_bot()
|
||||||
|
assert (
|
||||||
|
fake_bot.Client._connected_state._value == True
|
||||||
|
) # Check fake_bot is connected
|
||||||
|
await fake_bot.Client.disconnect()
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
await mqtt_server.broker.shutdown()
|
||||||
622
tests/test_xmppserver.py
Normal file
622
tests/test_xmppserver.py
Normal file
|
|
@ -0,0 +1,622 @@
|
||||||
|
import mock
|
||||||
|
import bumper
|
||||||
|
import asyncio
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import tinydb
|
||||||
|
import pytest_asyncio
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import socket
|
||||||
|
from testfixtures import LogCapture
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
|
||||||
|
def return_send_data(data, *args, **kwargs):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def mock_transport_extra_info(*args, **kwargs):
|
||||||
|
return ("127.0.0.1", 5223)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_xmpp_server():
|
||||||
|
with LogCapture("xmppserver") as l:
|
||||||
|
xmpp_address = ("127.0.0.1", 5223)
|
||||||
|
xmpp_server = bumper.XMPPServer(xmpp_address)
|
||||||
|
await xmpp_server.start_async_server()
|
||||||
|
|
||||||
|
reader, writer = await asyncio.open_connection("127.0.0.1", 5223)
|
||||||
|
|
||||||
|
writer.write(b"<stream:stream />") # Start stream
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
assert len(xmpp_server.clients) == 1 # Client count increased
|
||||||
|
assert (
|
||||||
|
xmpp_server.clients[0].address[1]
|
||||||
|
== writer.transport.get_extra_info("sockname")[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
writer.close() # Close connection
|
||||||
|
await writer.wait_closed()
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
assert len(xmpp_server.clients) == 0 # Client count decreased
|
||||||
|
|
||||||
|
reader, writer = await asyncio.open_connection("127.0.0.1", 5223)
|
||||||
|
|
||||||
|
writer.write(b"<stream:stream />") # Start stream
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
xmpp_server.disconnect()
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
assert len(xmpp_server.clients) == 0 # Client count decreased
|
||||||
|
print(l)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_client_connect_no_starttls(*args, **kwargs):
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.CONNECT # Set client state to CONNECT
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
# Send connect stream from "client"
|
||||||
|
test_data = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# Expect 2 calls to send
|
||||||
|
assert mock_send.call_count == 2
|
||||||
|
# Server opens stream
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
|
||||||
|
)
|
||||||
|
# Server tells client available features
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[1].args[0]
|
||||||
|
== '<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Client sendss auth - Ignoring the starttls, we don't force this with bumper
|
||||||
|
test_data = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl</auth>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
|
||||||
|
) # Client successfully authenticated
|
||||||
|
assert xmppclient.state == xmppclient.INIT # Client moved to INIT state
|
||||||
|
|
||||||
|
|
||||||
|
async def test_client_end_stream(*args, **kwargs):
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.CONNECT # Set client state to CONNECT
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
# Send end stream from "client"
|
||||||
|
test_data = "</stream:stream>".encode("utf-8")
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# Expect 2 calls to send
|
||||||
|
assert mock_send.call_count == 1
|
||||||
|
# Server opens stream
|
||||||
|
assert mock_send.mock_calls[0].args[0] == "</stream:stream>"
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send abnormal stream from "client"
|
||||||
|
test_data = "<badstr />".encode("utf-8")
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send blank from "client"
|
||||||
|
test_data = "".encode("utf-8")
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_client_connect_starttls_called(*args, **kwargs):
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.CONNECT # Set client state to CONNECT
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
# Send connect stream from "client"
|
||||||
|
test_data = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# Expect 2 calls to send
|
||||||
|
assert mock_send.call_count == 2
|
||||||
|
# Server opens stream
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
|
||||||
|
)
|
||||||
|
# Server tells client available features
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[1].args[0]
|
||||||
|
== '<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
mock_tls = xmppclient._handle_starttls = mock.Mock()
|
||||||
|
|
||||||
|
# Send start tls from "client"
|
||||||
|
test_data = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>".encode("utf-8")
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# After upgrading connection, server tells client to proceed with auth again
|
||||||
|
assert mock_tls.called
|
||||||
|
xmppclient.TLSUpgraded = True
|
||||||
|
|
||||||
|
# After TLS is upgraded, Client establishes session again and will auth this time
|
||||||
|
# Send connect stream from "client"
|
||||||
|
test_data = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# Expect 2 calls to send
|
||||||
|
assert mock_send.call_count == 2
|
||||||
|
# Server opens stream
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
|
||||||
|
)
|
||||||
|
# Server tells client available features (without STARTTLS)
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[1].args[0]
|
||||||
|
== '<stream:features><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
||||||
|
)
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Client sends auth
|
||||||
|
test_data = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl</auth>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
|
||||||
|
) # Client successfully authenticated
|
||||||
|
assert xmppclient.state == xmppclient.INIT # Client moved to INIT state
|
||||||
|
|
||||||
|
|
||||||
|
async def test_xmpp_server_client_tls():
|
||||||
|
with LogCapture("xmppserver") as l:
|
||||||
|
|
||||||
|
async def do_stuff_after_start_tls(
|
||||||
|
ssl_reader, ssl_writer
|
||||||
|
): # Used after starttls
|
||||||
|
|
||||||
|
writer.write(
|
||||||
|
b"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>"
|
||||||
|
) # New Stream
|
||||||
|
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
writer.write(
|
||||||
|
b'<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">AGZ1aWRfdG1wdXNlcgAwL0lPU0Y1M0QwN0JBL3VzXzg5ODgwMmZkYmM0NDQxYjBiYzgxNWIxZDFjNjgzMDJl</auth>'
|
||||||
|
) # Send Auth
|
||||||
|
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
xmpp_address = ("127.0.0.1", 5223)
|
||||||
|
xmpp_server = bumper.XMPPServer(xmpp_address)
|
||||||
|
await xmpp_server.start_async_server()
|
||||||
|
|
||||||
|
reader, writer = await asyncio.open_connection("127.0.0.1", 5223)
|
||||||
|
|
||||||
|
writer.write(
|
||||||
|
b"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>"
|
||||||
|
) # Start stream
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
writer.write(
|
||||||
|
b"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
|
||||||
|
) # Send StartTLS
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
# Below will upgrade connection to TLS then callback to "do_stuff_after_start_tls"
|
||||||
|
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||||
|
ssl_context.check_hostname = False
|
||||||
|
ssl_context.load_verify_locations(cafile=bumper.ca_cert)
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
transport = writer.transport
|
||||||
|
protocol = writer.transport.get_protocol()
|
||||||
|
new_transport = await loop.start_tls(
|
||||||
|
transport, protocol, ssl_context, server_side=False
|
||||||
|
)
|
||||||
|
protocol._stream_reader = asyncio.StreamReader(loop=loop)
|
||||||
|
protocol._client_connected_cb = do_stuff_after_start_tls
|
||||||
|
protocol.connection_made(new_transport)
|
||||||
|
|
||||||
|
print(l)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_client_init(*args, **kwargs):
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.INIT # Set client state to INIT
|
||||||
|
xmppclient.uid = "fuid_tmpuser"
|
||||||
|
xmppclient.resource = "IOSF53D07BA"
|
||||||
|
xmppclient.bumper_jid = "fuid_tmpuser@ecouser.net/IOSF53D07BA"
|
||||||
|
xmppclient.type = xmppclient.CONTROLLER
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
# Send connect stream from "client"
|
||||||
|
test_data = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='ecouser.net'>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# Expect 2 calls to send
|
||||||
|
assert mock_send.call_count == 2
|
||||||
|
# Server opens stream
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
|
||||||
|
)
|
||||||
|
# Server tells client binds
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[1].args[0]
|
||||||
|
== '<stream:features><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send bind from "client"
|
||||||
|
test_data = '<iq type="set" id="5E9872D5-547E-49AF-AE51-9EFAA282F952"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>IOSF53D07BA</resource></bind></iq>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq type="result" id="5E9872D5-547E-49AF-AE51-9EFAA282F952"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>fuid_tmpuser@ecouser.net/IOSF53D07BA</jid></bind></iq>'
|
||||||
|
) # client successfully binded
|
||||||
|
assert xmppclient.state == xmppclient.BIND # client moved to BIND state
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send set session from client
|
||||||
|
test_data = '<iq type="set" id="FA1041E7-AA27-43DD-BAA3-64DE2DE56AA3"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert xmppclient.state == xmppclient.READY # client moved to READY state
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq type="result" id="FA1041E7-AA27-43DD-BAA3-64DE2DE56AA3" />'
|
||||||
|
) # client ready
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send presense from client
|
||||||
|
test_data = '<presence type="available"/>'.encode("utf-8")
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<presence to="fuid_tmpuser@ecouser.net/IOSF53D07BA"> dummy </presence>'
|
||||||
|
) # client presence - dummy response
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bot_connect(*args, **kwargs):
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.CONNECT # Set client state to CONNECT
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
# Send connect stream from "bot"
|
||||||
|
test_data = "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='159.ecorobot.net' version='1.0'>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# Expect 2 calls to send
|
||||||
|
assert mock_send.call_count == 2
|
||||||
|
# Server opens stream
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
|
||||||
|
)
|
||||||
|
# Server tells client available features
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[1].args[0]
|
||||||
|
== '<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>PLAIN</mechanism></mechanisms></stream:features>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send auth from "bot"
|
||||||
|
test_data = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AEUwMDAwMDAwMDAwMDAwMDAxMjM0AGVuY3J5cHRlZF9wYXNz</auth>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>'
|
||||||
|
) # Bot successfully authenticated
|
||||||
|
assert xmppclient.state == xmppclient.INIT # Bot moved to INIT state
|
||||||
|
assert xmppclient.type == xmppclient.BOT # Client type is now bot
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bot_init(*args, **kwargs):
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.INIT # Set client state to INIT
|
||||||
|
xmppclient.uid = "E0000000000000001234"
|
||||||
|
xmppclient.devclass = "159"
|
||||||
|
xmppclient.type = xmppclient.BOT
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
# Send connect stream from "bot"
|
||||||
|
test_data = "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' to='159.ecorobot.net' version='1.0'>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
# Expect 2 calls to send
|
||||||
|
assert mock_send.call_count == 2
|
||||||
|
# Server opens stream
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0" id="1" from="ecouser.net">'
|
||||||
|
)
|
||||||
|
# Server tells client binds
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[1].args[0]
|
||||||
|
== '<stream:features><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send bind from "bot"
|
||||||
|
test_data = "<iq type='set' id='2521'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>atom</resource></bind></iq>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq type="result" id="2521"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>E0000000000000001234@159.ecorobot.net/atom</jid></bind></iq>'
|
||||||
|
) # Bot successfully binded
|
||||||
|
assert xmppclient.state == xmppclient.BIND # Bot moved to BIND state
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send set session from bot
|
||||||
|
test_data = "<iq type='set' id='2522'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert xmppclient.state == xmppclient.READY # Bot moved to READY state
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0] == '<iq type="result" id="2522" />'
|
||||||
|
) # Bot ready
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Send presense from bot
|
||||||
|
test_data = "<presence><status>hello world</status></presence><iq type='result' from='E0000000000000001234@159.ecorobot.net/atom' to='ecouser.net' id='s2c1'/>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<presence to="E0000000000000001234@159.ecorobot.net/atom"> dummy </presence>'
|
||||||
|
) # bot presence - dummy response
|
||||||
|
|
||||||
|
|
||||||
|
async def test_ping_server(*args, **kwargs):
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.READY # Set client state to READY
|
||||||
|
xmppclient.uid = "E0000000000000001234"
|
||||||
|
xmppclient.devclass = "159"
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
# Ping from bot
|
||||||
|
test_data = '<iq xmlns:ns0="urn:xmpp:ping" from="E000BVTNX18700260382@159.ecorobot.net/atom" id="2542" to="159.ecorobot.net" type="get"><ping /></iq>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq type="result" id="2542" from="159.ecorobot.net" />'
|
||||||
|
) # ping response
|
||||||
|
|
||||||
|
|
||||||
|
async def test_ping_client_to_client(*args, **kwargs):
|
||||||
|
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.READY # Set client state to READY
|
||||||
|
xmppclient.uid = "E0000000000000001234"
|
||||||
|
xmppclient.devclass = "159"
|
||||||
|
xmppclient.bumper_jid = "E0000000000000001234@159.ecorobot.net/atom"
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
xmppclient2 = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient2.state = xmppclient.READY # Set client state to READY
|
||||||
|
xmppclient2.uid = "fuid_tmpuser"
|
||||||
|
xmppclient2.resource = "IOSF53D07BA"
|
||||||
|
xmppclient2.bumper_jid = "fuid_tmpuser@ecouser.net/IOSF53D07BA"
|
||||||
|
mock_send2 = xmppclient2.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
bumper.xmppserver.XMPPServer.clients.append(xmppclient)
|
||||||
|
bumper.xmppserver.XMPPServer.clients.append(xmppclient2)
|
||||||
|
|
||||||
|
# Ping from user to bot
|
||||||
|
test_data = '<iq id="104934615" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="get"><ping xmlns="urn:xmpp:ping" /></iq>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send2.mock_calls[0].args[0]
|
||||||
|
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="104934615" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="get"><ping xmlns="urn:xmpp:ping" /></iq>'
|
||||||
|
) # ping response
|
||||||
|
|
||||||
|
# Ping response from bot to user
|
||||||
|
test_data = "<iq type='result' to='E0000000000000001234@159.ecorobot.net/atom' id='104934615'/>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient2._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq from="fuid_tmpuser@ecouser.net/IOSF53D07BA" id="104934615" to="E0000000000000001234@159.ecorobot.net/atom" type="result" />'
|
||||||
|
) # ping response
|
||||||
|
|
||||||
|
|
||||||
|
async def test_client_send_iq(*args, **kwargs):
|
||||||
|
test_transport = asyncio.Transport()
|
||||||
|
test_transport.get_extra_info = mock.Mock(return_value=mock_transport_extra_info())
|
||||||
|
test_transport.write = mock.Mock(return_value=return_send_data)
|
||||||
|
xmppclient = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient.state = xmppclient.READY # Set client state to READY
|
||||||
|
xmppclient.uid = "fuid_tmpuser"
|
||||||
|
xmppclient.resource = "IOSF53D07BA"
|
||||||
|
xmppclient.bumper_jid = "fuid_tmpuser@ecouser.net/IOSF53D07BA"
|
||||||
|
xmppclient.type - xmppclient.CONTROLLER
|
||||||
|
mock_send = xmppclient.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
bumper.xmppserver.XMPPServer.clients.append(xmppclient)
|
||||||
|
|
||||||
|
xmppclient2 = bumper.xmppserver.XMPPAsyncClient(test_transport)
|
||||||
|
xmppclient2.state = xmppclient.READY # Set client state to READY
|
||||||
|
xmppclient2.uid = "E0000000000000001234"
|
||||||
|
xmppclient2.devclass = "159"
|
||||||
|
xmppclient2.bumper_jid = "E0000000000000001234@159.ecorobot.net/atom"
|
||||||
|
xmppclient2.type = xmppclient2.BOT
|
||||||
|
mock_send2 = xmppclient2.send = mock.Mock(side_effect=return_send_data)
|
||||||
|
|
||||||
|
bumper.xmppserver.XMPPServer.clients.append(xmppclient2)
|
||||||
|
|
||||||
|
# Roster IQ - Only seen from Android app so far
|
||||||
|
test_data = '<iq id="EE0XQ-2" type="get"><query xmlns="jabber:iq:roster" ></query></iq>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq type="error" id="EE0XQ-2"><error type="cancel" code="501"><feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/></error></iq>'
|
||||||
|
) # feature not implemented response
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Bot Command
|
||||||
|
test_data = '<iq id="7" to="E0000000000000001234@159.ecorobot.net/atom" type="set"><query xmlns="com:ctl"><ctl id="72107787" td="GetCleanState" /></query></iq>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send2.mock_calls[0].args[0]
|
||||||
|
== '<iq from="fuid_tmpuser@ecouser.net/IOSF53D07BA" id="7" to="E0000000000000001234@159.ecorobot.net/atom" type="set"><query xmlns="com:ctl"><ctl id="72107787" td="GetCleanState" /></query></iq>'
|
||||||
|
) # command was sent to bot
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Bot response to query
|
||||||
|
test_data = '<iq xmlns:ns0="com:ctl" id="2679" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="set"><query><ctl td="ChargeState"><charge h="0" r="a" type="Going" /></ctl></query></iq>'.encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient2._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="2679" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="set"><query xmlns="com:ctl"><ctl td="ChargeState"><charge h="0" r="a" type="Going" /></ctl></query></iq>'
|
||||||
|
) # result sent to client
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Bot result
|
||||||
|
test_data = "<iq type='result' from='E0000000000000001234@159.ecorobot.net/atom' to='ecouser.net' id='s2c1'/>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient2._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="s2c1" to="ecouser.net" type="result" />'
|
||||||
|
) # result sent to ecouser.net
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
# Bot iq set
|
||||||
|
test_data = "<iq to='fuid_tmpuser@ecouser.net/IOSF53D07BA' type='set' id='2700'><query xmlns='com:ctl'><ctl td='BatteryInfo'><battery power='100'/></ctl></query></iq>".encode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
xmppclient2._parse_data(test_data)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mock_send.mock_calls[0].args[0]
|
||||||
|
== '<iq from="E0000000000000001234@159.ecorobot.net/atom" id="2700" to="fuid_tmpuser@ecouser.net/IOSF53D07BA" type="set"><query xmlns="com:ctl"><ctl td="BatteryInfo"><battery power="100" /></ctl></query></iq>'
|
||||||
|
) # result sent to ecouser.net
|
||||||
|
|
||||||
|
# Reset mock calls
|
||||||
|
mock_send.reset_mock()
|
||||||
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
# Bumper tests
|
# Bumper tests
|
||||||
Bumper uses nosetests for the majority of test cases. Install requirements using `pipenv install --dev`
|
Bumper uses pytest for the majority of test cases. Install requirements using `pipenv install --dev`
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
Enter pipenv shell `pipenv shell`
|
Enter pipenv shell `pipenv shell`
|
||||||
|
|
||||||
### Run tests
|
### Run tests
|
||||||
`nosetests`
|
`python -m pytest`
|
||||||
|
|
||||||
### Run tests with coverage
|
### Run tests with coverage
|
||||||
`nosetests --cover-package bumper --with-coverage`
|
`python -m pytest --cov=bumper tests/`
|
||||||
|
|
||||||
### Run tests with coverage html report
|
### Run tests with coverage html report
|
||||||
`nosetests --cover-package bumper --with-coverage --cover-html-dir="tests/report" --cover-html`
|
`python -m pytest --cov=bumper tests/ --cov-report html:tests/report`
|
||||||
|
|
||||||
The report will be output into tests/report/index.html for further analysis.
|
The report will be output into tests/report/index.html for further analysis.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue