From be57512a653d3051cd29c8c63df311c172eca8f7 Mon Sep 17 00:00:00 2001 From: Dan Jones <dan.jones@noc.ac.uk> Date: Thu, 9 Feb 2023 09:55:03 +0000 Subject: [PATCH] tests: unit tests for models and api endpoints - done - client and token model - client and token api endpoints - send api endpoint - todo - notify api endpoint - receive api endpoint --- Pipfile | 24 ++ Pipfile.lock | 569 ++++++++++++++++++++++++++++ api.py | 26 +- api_client_test.py | 95 +++++ api_send_test.py | 59 +++ api_token_test.py | 39 ++ conftest.py | 92 +++++ docker/Dockerfile | 3 +- endpoints/auth_resource.py | 20 +- endpoints/client.py | 77 ++++ endpoints/notify.py | 13 +- endpoints/receive.py | 4 +- endpoints/send.py | 13 +- endpoints/token.py | 8 +- models/client_model.py | 83 ++++ models/client_model_test.py | 135 +++++++ models/{token.py => token_model.py} | 71 ++-- models/token_model_test.py | 130 +++++++ requirements-dev.txt | 5 + requirements.txt | 2 +- rmq.py | 49 ++- soar_bus.py | 14 +- 22 files changed, 1422 insertions(+), 109 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 api_client_test.py create mode 100644 api_send_test.py create mode 100644 api_token_test.py create mode 100644 conftest.py create mode 100644 endpoints/client.py create mode 100644 models/client_model.py create mode 100644 models/client_model_test.py rename models/{token.py => token_model.py} (55%) create mode 100644 models/token_model_test.py create mode 100644 requirements-dev.txt diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..cd8d354 --- /dev/null +++ b/Pipfile @@ -0,0 +1,24 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +pubsubpy = "*" +pika = "*" +pyrabbit = "*" +flask = "*" +flask-restful = "*" +marshmallow = "*" +bson = "*" +flask-cors = "*" +cryptography = "*" + +[dev-packages] +pytest = "*" +pytest-rabbitmq = "*" +pytest-mock = "*" +black = "*" + +[requires] +python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..d72d816 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,569 @@ +{ + "_meta": { + "hash": { + "sha256": "21c6756e40ab45e59ba28a311bc17728bc285e1ad8de901d4a4213355880027a" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "amqp": { + "hashes": [ + "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2", + "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359" + ], + "markers": "python_version >= '3.6'", + "version": "==5.1.1" + }, + "aniso8601": { + "hashes": [ + "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f", + "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973" + ], + "version": "==9.0.1" + }, + "bson": { + "hashes": [ + "sha256:d6511b2ab051139a9123c184de1a04227262173ad593429d21e443d6462d6590" + ], + "index": "pypi", + "version": "==0.5.10" + }, + "cffi": { + "hashes": [ + "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", + "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", + "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", + "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", + "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", + "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", + "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", + "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", + "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", + "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", + "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", + "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", + "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", + "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", + "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", + "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", + "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", + "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", + "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", + "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", + "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", + "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", + "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", + "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", + "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", + "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", + "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", + "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", + "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", + "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", + "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", + "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", + "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", + "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", + "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", + "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", + "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", + "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", + "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", + "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", + "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", + "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", + "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", + "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", + "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", + "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", + "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", + "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", + "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", + "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", + "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", + "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", + "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", + "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", + "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", + "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", + "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", + "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", + "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", + "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", + "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", + "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", + "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" + ], + "version": "==1.15.1" + }, + "click": { + "hashes": [ + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.3" + }, + "cryptography": { + "hashes": [ + "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4", + "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f", + "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502", + "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41", + "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965", + "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e", + "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc", + "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad", + "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505", + "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388", + "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6", + "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2", + "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac", + "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695", + "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6", + "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336", + "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0", + "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c", + "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106", + "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a", + "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8" + ], + "index": "pypi", + "version": "==39.0.1" + }, + "flask": { + "hashes": [ + "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", + "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" + ], + "index": "pypi", + "version": "==2.2.2" + }, + "flask-cors": { + "hashes": [ + "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438", + "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de" + ], + "index": "pypi", + "version": "==3.0.10" + }, + "flask-restful": { + "hashes": [ + "sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2", + "sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e" + ], + "index": "pypi", + "version": "==0.3.9" + }, + "future": { + "hashes": [ + "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.18.3" + }, + "httplib2": { + "hashes": [ + "sha256:987c8bb3eb82d3fa60c68699510a692aa2ad9c4bd4f123e51dfb1488c14cdd01", + "sha256:fc144f091c7286b82bec71bdbd9b27323ba709cc612568d3000893bfd9cb4b34" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.21.0" + }, + "importlib-metadata": { + "hashes": [ + "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad", + "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d" + ], + "markers": "python_version < '3.10'", + "version": "==6.0.0" + }, + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.2" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.2" + }, + "kombu": { + "hashes": [ + "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610", + "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4" + ], + "markers": "python_version >= '3.7'", + "version": "==5.2.4" + }, + "markupsafe": { + "hashes": [ + "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", + "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", + "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", + "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", + "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", + "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", + "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", + "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", + "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", + "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", + "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", + "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", + "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", + "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", + "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", + "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", + "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", + "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", + "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", + "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", + "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", + "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", + "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", + "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", + "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", + "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", + "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", + "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", + "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", + "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", + "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", + "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", + "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", + "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", + "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", + "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", + "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", + "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", + "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", + "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", + "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", + "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", + "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", + "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", + "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", + "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", + "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", + "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", + "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", + "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.2" + }, + "marshmallow": { + "hashes": [ + "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78", + "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b" + ], + "index": "pypi", + "version": "==3.19.0" + }, + "packaging": { + "hashes": [ + "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", + "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0" + }, + "pika": { + "hashes": [ + "sha256:89f5e606646caebe3c00cbdbc4c2c609834adde45d7507311807b5775edac8e0", + "sha256:beb19ff6dd1547f99a29acc2c6987ebb2ba7c44bf44a3f8e305877c5ef7d2fdc" + ], + "index": "pypi", + "version": "==1.3.1" + }, + "pubsubpy": { + "hashes": [ + "sha256:58e394d14dd172fc03caff172adf3817d980bb6b8cb46cd18a362f8aa6e530c6", + "sha256:b29fa140615935ac03801ccd1de137ce4d33b741465b9002f290538ce966f2e9" + ], + "index": "pypi", + "version": "==2.3.0" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pyparsing": { + "hashes": [ + "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", + "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" + ], + "markers": "python_version >= '3.1'", + "version": "==3.0.9" + }, + "pyrabbit": { + "hashes": [ + "sha256:50b8995fbfde14820ddc97292312c8f0c77054748c2b018138d03d94e400c39c" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, + "pytz": { + "hashes": [ + "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0", + "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a" + ], + "version": "==2022.7.1" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "vine": { + "hashes": [ + "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30", + "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e" + ], + "markers": "python_version >= '3.6'", + "version": "==5.0.0" + }, + "werkzeug": { + "hashes": [ + "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", + "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" + ], + "markers": "python_version >= '3.7'", + "version": "==2.2.2" + }, + "zipp": { + "hashes": [ + "sha256:6c4fe274b8f85ec73c37a8e4e3fa00df9fb9335da96fb789e3b96b318e5097b3", + "sha256:a3cac813d40993596b39ea9e93a18e8a2076d5c378b8bc88ec32ab264e04ad02" + ], + "markers": "python_version >= '3.7'", + "version": "==3.12.1" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", + "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" + ], + "markers": "python_version >= '3.6'", + "version": "==22.2.0" + }, + "black": { + "hashes": [ + "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd", + "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555", + "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481", + "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468", + "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9", + "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a", + "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958", + "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580", + "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26", + "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32", + "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8", + "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753", + "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b", + "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074", + "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651", + "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24", + "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6", + "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad", + "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac", + "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221", + "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06", + "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27", + "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648", + "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739", + "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104" + ], + "index": "pypi", + "version": "==23.1.0" + }, + "click": { + "hashes": [ + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.3" + }, + "exceptiongroup": { + "hashes": [ + "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e", + "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23" + ], + "markers": "python_version < '3.11'", + "version": "==1.1.0" + }, + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "mirakuru": { + "hashes": [ + "sha256:ec84d4d81b4bca96cb0e598c6b3d198a92f036a0c1223c881482c02a98508226", + "sha256:fdb67d141cc9f7abd485a515d618daf3272c3e6ff48380749997ff8e8c5f2cb2" + ], + "markers": "python_version >= '3.7'", + "version": "==2.4.2" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "packaging": { + "hashes": [ + "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", + "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" + ], + "markers": "python_version >= '3.7'", + "version": "==23.0" + }, + "pamqp": { + "hashes": [ + "sha256:2f81b5c186f668a67f165193925b6bfd83db4363a6222f599517f29ecee60b02", + "sha256:5cd0f5a85e89f20d5f8e19285a1507788031cfca4a9ea6f067e3cf18f5e294e8" + ], + "version": "==2.3.0" + }, + "pathspec": { + "hashes": [ + "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229", + "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc" + ], + "markers": "python_version >= '3.7'", + "version": "==0.11.0" + }, + "platformdirs": { + "hashes": [ + "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9", + "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567" + ], + "markers": "python_version >= '3.7'", + "version": "==3.0.0" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "port-for": { + "hashes": [ + "sha256:232bd999015b7fbdf19f90f3a9298cc742252d67650108123940bfc75c6f4d4e", + "sha256:31860afde6cb552e1830c927def3288350c8fbbe9aea8aed8150ed9d1aa0de81" + ], + "markers": "python_version >= '3.7'", + "version": "==0.6.3" + }, + "psutil": { + "hashes": [ + "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff", + "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1", + "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62", + "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549", + "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08", + "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7", + "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e", + "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe", + "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24", + "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad", + "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94", + "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8", + "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7", + "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4" + ], + "markers": "sys_platform != 'cygwin'", + "version": "==5.9.4" + }, + "pytest": { + "hashes": [ + "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5", + "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42" + ], + "index": "pypi", + "version": "==7.2.1" + }, + "pytest-mock": { + "hashes": [ + "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b", + "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f" + ], + "index": "pypi", + "version": "==3.10.0" + }, + "pytest-rabbitmq": { + "hashes": [ + "sha256:694ef26a6b85ec3b67428cd91a3abc35231c0a90adc70dbea59e94a6950dd07e", + "sha256:a9306b3e8c53440663fbc1baa7d77f574b9612403192fafa09224cd8824e19c1" + ], + "index": "pypi", + "version": "==2.2.1" + }, + "rabbitpy": { + "hashes": [ + "sha256:58be8ccef6d64010d98c77b0966be148aafb48b867359e885d9cd671361fe5ba", + "sha256:c6a6d0e8e51d0859fbb9bb78c8eeddd7b1ec79957db633673a8741d0cedf4830" + ], + "version": "==2.0.1" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version < '3.11'", + "version": "==2.0.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", + "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + ], + "markers": "python_version < '3.10'", + "version": "==4.4.0" + } + } +} diff --git a/api.py b/api.py index 72d91fe..ee83b44 100644 --- a/api.py +++ b/api.py @@ -7,25 +7,31 @@ from endpoints.notify import Notify from endpoints.receive import Receive from endpoints.send import Send from endpoints.token import Token -from models.token import TokenModel +from models.token_model import TokenModel + import os token = TokenModel() token.setSecret() -app = Flask(__name__) -api = Api(app) -CORS(app, resources={r"*": {"origins": "http://localhost:8086"}}) -api.add_resource(ClientList, "/client") -api.add_resource(Client, "/client/<client_id>") -api.add_resource(Receive, "/receive") -api.add_resource(Send, "/send") -api.add_resource(Notify, "/notify") -api.add_resource(Token, "/token") +def create_app(): + app = Flask(__name__) + api = Api(app) + CORS(app, resources={r"*": {"origins": "http://localhost:8086"}}) + + api.add_resource(ClientList, "/client") + api.add_resource(Client, "/client/<client_id>") + api.add_resource(Receive, "/receive") + api.add_resource(Send, "/send") + api.add_resource(Notify, "/notify") + api.add_resource(Token, "/token") + return app + flask_host = os.getenv("FLASK_HOST", "localhost") # Sets to whatever MQ_HOST is, or defaults to localhost if __name__ == "__main__": + app = create_app() app.run(debug=False, port=8087, host=flask_host) diff --git a/api_client_test.py b/api_client_test.py new file mode 100644 index 0000000..f7f4b78 --- /dev/null +++ b/api_client_test.py @@ -0,0 +1,95 @@ +import flask +import json +import pytest +from unittest.mock import patch, mock_open, call +from werkzeug.exceptions import HTTPException +from api import create_app + + +@pytest.mark.usefixtures("mock_clients") +def test_get_client(mock_clients): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.get("/client") + assert response.status_code == 200 + assert len(response.json.keys()) == 2 + assert "client-1" in response.json + + +@pytest.mark.usefixtures("mock_clients") +def test_get_client_1(mock_clients): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.get("/client/client-1") + assert response.status_code == 200 + assert "client_id" in response.json + assert response.json["client_id"] == "client-1" + assert "client_name" in response.json + assert "secret" not in response.json + + +@pytest.mark.usefixtures("mock_clients", "mock_new_client") +def test_post_client(mock_clients, mock_new_client): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.post("/client", json=mock_new_client) + assert response.status_code == 201 + assert "client_id" in response.json + assert response.json["client_id"] == "client-3" + assert "client_name" in response.json + assert "secret" in response.json + + +@pytest.mark.usefixtures("mock_clients") +def test_put_client_1(mock_clients): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.get("/client/client-1") + client_1 = response.json + print(client_1) + client_1["subscription"] = "soar.client-1.#" + response = app_test_client.put("/client/client-1", json=client_1) + print(response.data) + assert response.status_code == 201 + assert "client_id" in response.json + assert response.json["client_id"] == "client-1" + assert response.json["subscription"] == "soar.client-1.#" + assert "client_name" in response.json + assert "secret" in response.json + + +@pytest.mark.usefixtures("mock_clients") +def test_put_client_1(mock_clients): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.get("/client/client-1") + client_1 = response.json + print(client_1) + client_1["subscription"] = "soar.client-1.#" + response = app_test_client.put("/client/client-1", json=client_1) + assert response.status_code == 201 + assert "client_id" in response.json + assert response.json["client_id"] == "client-1" + assert response.json["subscription"] == "soar.client-1.#" + assert "client_name" in response.json + assert "secret" in response.json + + +@pytest.mark.usefixtures("mock_clients") +def test_delete_client_1(mock_clients): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.delete("/client/client-1") + assert response.status_code == 204 diff --git a/api_send_test.py b/api_send_test.py new file mode 100644 index 0000000..80db180 --- /dev/null +++ b/api_send_test.py @@ -0,0 +1,59 @@ +import flask +import json +import pytest +from unittest.mock import patch, mock_open, call +from werkzeug.exceptions import HTTPException +from api import create_app +from endpoints import send +from conftest import get_auth_header + + +@pytest.mark.usefixtures( + "mock_clients", "mock_client_credentials", "mock_post_send", "mock_message_send" +) +def test_post_send( + mock_clients, mock_client_credentials, mock_post_send, mock_message_send +): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, patch.object( + send, "write_to_queue" + ) as mock_write_to_queue, app.test_client() as app_test_client: + auth_header = get_auth_header(app_test_client, mock_client_credentials) + response = app_test_client.post( + "/send", json=mock_post_send, headers=auth_header + ) + assert response.status_code == 200 + mock_write_to_queue.assert_called_once_with( + queue_name="client-1-outbox", msg=json.dumps(mock_message_send) + ) + + +@pytest.mark.usefixtures("mock_clients", "mock_post_send") +def test_post_send_no_token(mock_clients, mock_post_send): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, patch.object( + send, "write_to_queue" + ) as mock_write_to_queue, app.test_client() as app_test_client: + response = app_test_client.post("/send", json=mock_post_send) + assert response.status_code == 403 + mock_write_to_queue.assert_not_called() + + +@pytest.mark.usefixtures("mock_clients", "mock_post_send") +def test_post_send_invalid_token(mock_clients, mock_post_send): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, patch.object( + send, "write_to_queue" + ) as mock_write_to_queue, app.test_client() as app_test_client: + auth_header = {"Authorization": "made-up-token"} + response = app_test_client.post( + "/send", json=mock_post_send, headers=auth_header + ) + assert response.status_code == 403 + mock_write_to_queue.assert_not_called() diff --git a/api_token_test.py b/api_token_test.py new file mode 100644 index 0000000..7245cec --- /dev/null +++ b/api_token_test.py @@ -0,0 +1,39 @@ +import flask +import json +import pytest +from unittest.mock import patch, mock_open, call +from werkzeug.exceptions import HTTPException +from api import create_app + + +@pytest.mark.usefixtures("mock_clients", "mock_client_credentials") +def test_get_token(mock_clients, mock_client_credentials): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.get("/token", query_string=mock_client_credentials) + assert response.status_code == 200 + assert "token" in response.json + + +@pytest.mark.usefixtures("mock_clients", "mock_invalid_credentials") +def test_get_token_invalid_credentials(mock_clients, mock_invalid_credentials): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.get("/token", query_string=mock_invalid_credentials) + assert response.status_code == 403 + assert "token" not in response.json + + +@pytest.mark.usefixtures("mock_clients", "mock_client_credentials") +def test_get_token_no_credentials(mock_clients, mock_client_credentials): + app = create_app() + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, app.test_client() as app_test_client: + response = app_test_client.get("/token") + assert response.status_code == 400 + assert "token" not in response.json diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..c6fcacd --- /dev/null +++ b/conftest.py @@ -0,0 +1,92 @@ +import pytest + + +def clients(): + return { + "client-1": { + "client_id": "client-1", + "client_name": "Client 1", + "subscription": "soar.#", + "secret": "abc123", + }, + "client-2": { + "client_id": "client-2", + "client_name": "Client 2", + "subscription": "soar.client-2.#", + "secret": "xyz789", + }, + } + + +def get_auth_header(client, credentials): + token_response = client.get("/token", query_string=credentials) + if token_response.status_code == 200: + token = token_response.json["token"] + return {"Authorization": f"Bearer {token}"} + else: + return None + + +@pytest.fixture +def mock_clients(): + return clients() + + +@pytest.fixture +def mock_new_client(): + return { + "client_id": "client-3", + "client_name": "Client 3", + "subscription": "soar.client-3.#", + } + + +@pytest.fixture +def mock_client_credentials(): + mock_clients = clients() + return { + "client_id": mock_clients["client-1"]["client_id"], + "secret": mock_clients["client-1"]["secret"], + } + + +@pytest.fixture +def mock_invalid_credentials(): + return {"client_id": "client-invalid", "secret": "fake-secret"} + + +@pytest.fixture +def mock_token_secret(): + return "2UrRyeb9c6hq8Gj9nmI5safPz9LpPeUFtifeMNx4GQo=" + + +def posts(): + return { + "send": { + "topic": "soar.client-1.message", + "body": "this is a pub/sub message from client-1", + }, + "notify": {"body": "this is a broadcast message from client-1"}, + } + + +@pytest.fixture +def mock_post_send(): + return posts()["send"] + + +@pytest.fixture +def mock_message_send(): + post = posts()["send"] + return {"topic": post["topic"], "message": post["body"]} + + +@pytest.fixture +def mock_post_notify(): + return posts()["notify"] + + +@pytest.fixture +def mock_message_notify(): + post = posts()["send"] + return {"message": post["body"]} diff --git a/docker/Dockerfile b/docker/Dockerfile index 606b939..f4b5099 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,6 +3,7 @@ FROM python:alpine3.17 WORKDIR /app COPY requirements.txt requirements.txt -RUN pip install -r requirements.txt +COPY requirements-dev.txt requirements-dev.txt +RUN pip install -r requirements-dev.txt ENTRYPOINT [ "python" ] \ No newline at end of file diff --git a/endpoints/auth_resource.py b/endpoints/auth_resource.py index 6429a20..b970928 100644 --- a/endpoints/auth_resource.py +++ b/endpoints/auth_resource.py @@ -2,28 +2,26 @@ import json import os from flask_restful import Resource, abort - -from models.token import TokenModel +from models.token_model import TokenModel class AuthResource(Resource): - - def __init__(self): + def __init__(self): self.token = TokenModel() with open("./data/clients.json", "r") as clients_file: self.clients = json.load(clients_file) - def auth(self, request): + def auth(self, request): allow = False - auth = request.headers.get('Authorization', False) + auth = request.headers.get("Authorization", False) if auth: - token = auth.split(' ').pop() + token = auth.split(" ").pop() parsed = self.token.validate(token) - if parsed['valid']: - client = self.clients.get(parsed['client_id']) - if client: + if parsed["valid"]: + client = self.clients.get(parsed["client_id"]) + if client: self.client = client allow = True if not allow: abort(403, message="Invalid token") - return allow \ No newline at end of file + return allow diff --git a/endpoints/client.py b/endpoints/client.py new file mode 100644 index 0000000..60fca56 --- /dev/null +++ b/endpoints/client.py @@ -0,0 +1,77 @@ +from flask_restful import Resource, request, abort +from marshmallow import Schema, fields +import json +import os +import random +import string +from models.client_model import ClientModel + + +class ClientSchema(Schema): + client_id = fields.Str(required=True) + client_name = fields.Str(required=True) + subscription = fields.Str(required=True) + + +# Client +class Client(Resource): + clients_file = None + + def __init__(self): + self.schema = ClientSchema() + self.clients_file = ClientModel() + + def get(self, client_id): + client = self.clients_file.find(client_id) + del client["secret"] + if not client: + abort(404, message="No client with id: {}".format(client_id)) + return client + + def delete(self, client_id): + client = self.clients_file.find(client_id) + if not client: + abort(404, message="No client with id: {}".format(client_id)) + else: + self.clients_file.remove(client) + return client, 204 + + def put(self, client_id): + args = request.get_json() + errors = self.schema.validate(args) + if errors: + abort(400, message=str(errors)) + + client = self.clients_file.find(client_id) + if not client: + abort(404, message="No client with id: {}".format(client_id)) + else: + client = self.clients_file.update(args) + return client, 201 + + +# ClientList +class ClientList(Resource): + def __init__(self): + self.schema = ClientSchema() + self.clients_file = ClientModel() + + def get(self): + return { + client_id: (client, client.pop("secret", None))[0] + for client_id, client in self.clients_file.get().items() + } + + def post(self): + args = request.get_json() + + errors = self.schema.validate(args) + if errors: + abort(400, message=str(errors)) + + client = self.clients_file.find(args["client_id"]) + if client: + abort(403, message="Duplicate client id: {}".format(client_id)) + else: + client = self.clients_file.add(args) + return client, 201 diff --git a/endpoints/notify.py b/endpoints/notify.py index 22bcb8d..dff460e 100644 --- a/endpoints/notify.py +++ b/endpoints/notify.py @@ -10,6 +10,7 @@ from rmq import write_to_queue class NotifySchema(Schema): body = fields.Str(required=True) + class Notify(AuthResource): clients = None schema = None @@ -17,7 +18,7 @@ class Notify(AuthResource): def __init__(self): super().__init__() self.schema = NotifySchema() - + def post(self): args = request.get_json() errors = self.schema.validate(args) @@ -27,12 +28,12 @@ class Notify(AuthResource): allow = False body = args.get("body") message = { - 'topic': 'broadcast', - 'message': body, + "topic": "broadcast", + "message": body, } allow = self.auth(request) - + if allow: - notify_queue = self.client['client_id'] + "-broadcast" - write_to_queue(queue_name=notify_queue, msg=json.dumps(message)) \ No newline at end of file + notify_queue = self.client["client_id"] + "-broadcast" + write_to_queue(queue_name=notify_queue, msg=json.dumps(message)) diff --git a/endpoints/receive.py b/endpoints/receive.py index 5c1f99c..bc299fa 100644 --- a/endpoints/receive.py +++ b/endpoints/receive.py @@ -26,5 +26,5 @@ class Receive(AuthResource): allow = self.auth(request) if allow: - inbox_queue = self.client['client_id'] + "-inbox" - return read_from_queue(queue_name=inbox_queue, max_msgs=max_messages) \ No newline at end of file + inbox_queue = self.client["client_id"] + "-inbox" + return read_from_queue(queue_name=inbox_queue, max_msgs=max_messages) diff --git a/endpoints/send.py b/endpoints/send.py index 9fe91ca..cb64c4a 100644 --- a/endpoints/send.py +++ b/endpoints/send.py @@ -11,6 +11,7 @@ class SendSchema(Schema): body = fields.Str(required=True) topic = fields.Str(required=True) + class Send(AuthResource): clients = None schema = None @@ -18,7 +19,7 @@ class Send(AuthResource): def __init__(self): super().__init__() self.schema = SendSchema() - + def post(self): args = request.get_json() errors = self.schema.validate(args) @@ -26,14 +27,14 @@ class Send(AuthResource): abort(400, message=str(errors)) allow = self.auth(request) - + if allow: body = args.get("body") topic = args.get("topic") - outbox_queue = self.client['client_id'] + "-outbox" + outbox_queue = self.client["client_id"] + "-outbox" message = { - 'topic': topic, - 'message': body, + "topic": topic, + "message": body, } - write_to_queue(queue_name=outbox_queue, msg=json.dumps(message)) \ No newline at end of file + write_to_queue(queue_name=outbox_queue, msg=json.dumps(message)) diff --git a/endpoints/token.py b/endpoints/token.py index cbc1ec5..7d1f28e 100644 --- a/endpoints/token.py +++ b/endpoints/token.py @@ -1,7 +1,7 @@ -import json +import json from flask_restful import Resource, request, abort from marshmallow import Schema, fields -from models.token import TokenModel +from models.token_model import TokenModel class TokenQuerySchema(Schema): client_id = fields.Str(required=True) @@ -18,7 +18,7 @@ class Token(Resource): self.model = TokenModel() with open("./data/clients.json", "r") as clients_file: self.clients = json.load(clients_file) - + def get(self): errors = self.schema.validate(request.args) if errors: @@ -38,4 +38,4 @@ class Token(Resource): else: abort(403, message="Invalid client credentials") - return token \ No newline at end of file + return token diff --git a/models/client_model.py b/models/client_model.py new file mode 100644 index 0000000..ec52219 --- /dev/null +++ b/models/client_model.py @@ -0,0 +1,83 @@ +""" +The backbone doesn't have a relational database or other backend state provider +It just saves its configuration to the local filesystem +In docker data is saved to a mounted volume for persistence + +POSTS to /client create entries in clients.json. The return from the post +contains a generated secret but subsequent calls to GET /client or +GET /client/{id} do not return the secret. + +Each time .find is called the .get method is called which checks the +mtime of the file and reloads it if newer. This means that new clients +should be returned +""" +import json +import os +import random +import string + + +class ClientModel: + file = "clients.json" + mtime = 0 + clients = {} + parser = None + + def __init__(self): + self.get() + + def get(self): + try: + mtime = os.path.getmtime(self.file) + if mtime > self.mtime: + with open(self.file, "r") as client_file: + self.clients = json.load(client_file) + self.mtime = mtime + except FileNotFoundError as error: + self.clients = {} + self.save() + self.mtime = os.path.getmtime(self.file) + + return self.clients + + def find(self, client_id): + self.get() + if client_id in self.clients: + client = self.clients[client_id] + else: + client = None + return client + + def add(self, client): + client["secret"] = self.secret() + self.clients[client["client_id"]] = client + self.save() + return client + + def remove(self, client): + del self.clients[client["client_id"]] + self.save() + + def update(self, client_updates): + client = self.find(client_updates["client_id"]) + client.update(client_updates) + self.clients[client["client_id"]] = client + self.save() + return client + + def save(self): + try: + with open(self.file, "w") as client_file: + client_file.write(json.dumps(self.clients, indent=2)) + return True + except OSError as error: + print(str(error)) + return False + + def secret(self, chars=36): + res = "".join( + random.choices( + string.ascii_lowercase + string.ascii_uppercase + string.digits, k=chars + ) + ) + return str(res) diff --git a/models/client_model_test.py b/models/client_model_test.py new file mode 100644 index 0000000..2791092 --- /dev/null +++ b/models/client_model_test.py @@ -0,0 +1,135 @@ +import json +import os +import pytest +import re +from unittest.mock import patch, mock_open, call +from client_model import ClientModel + + +@pytest.mark.usefixtures("mock_clients") +def test_init_with_clients(mock_clients): + # File is read on __init__ + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open: + clients_file = ClientModel() + assert len(clients_file.clients.keys()) == 2 + assert "client-1" in clients_file.clients.keys() + + +def test_init_no_clients_file(): + # Handles FileNotFound on __init__ + with patch("builtins.open", side_effect=FileNotFoundError()) as mock_file_open: + clients_file = ClientModel() + assert len(clients_file.clients.keys()) == 0 + assert "client-1" not in clients_file.clients.keys() + + +@pytest.mark.usefixtures("mock_clients") +def test_get_unmodified(mock_clients): + # If modtime is unchanged file is not read + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, patch("os.path.getmtime", return_value=3) as mock_getmtime: + clients_file = ClientModel() + clients = clients_file.get() + assert mock_file_open.call_count == 1 + + +@pytest.mark.usefixtures("mock_clients") +def test_get_modified(mock_clients): + # If modtime has changed file is re-read + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open, patch("os.path.getmtime", return_value=3) as mock_getmtime: + print(os.path.getmtime("clients.json")) + clients_file = ClientModel() + mock_getmtime.return_value = 4 + print(os.path.getmtime("clients.json")) + clients = clients_file.get() + assert mock_file_open.call_count == 2 + + +@pytest.mark.usefixtures("mock_clients") +def test_find_existing(mock_clients): + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open: + clients_file = ClientModel() + client = clients_file.find("client-1") + assert client is not None + assert client.get("subscription") == "soar.#" + + +@pytest.mark.usefixtures("mock_clients") +def test_find_nonexisting(mock_clients): + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open: + clients_file = ClientModel() + client = clients_file.find("client-3") + assert client is None + + +@pytest.mark.usefixtures("mock_clients", "mock_new_client") +def test_add(mock_clients, mock_new_client): + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open: + clients_file = ClientModel() + client = clients_file.add(mock_new_client) + assert client["client_id"] in clients_file.clients + assert "secret" in client + + +@pytest.mark.usefixtures("mock_clients") +def test_remove(mock_clients): + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open: + clients_file = ClientModel() + client = clients_file.find("client-2") + clients_file.remove(client) + assert len(clients_file.clients) == 1 + assert "client-2" not in clients_file.clients + + +@pytest.mark.usefixtures("mock_clients") +def test_update(mock_clients): + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open: + clients_file = ClientModel() + client = clients_file.find("client-1") + assert client["subscription"] == "soar.#" + client["subscription"] = "soar.client-1.#" + clients_file.update(client) + client = clients_file.find("client-1") + assert len(clients_file.clients) == 2 + assert client["subscription"] == "soar.client-1.#" + + +@pytest.mark.usefixtures("mock_clients") +def test_save(mock_clients): + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open: + clients_file = ClientModel() + mock_file_open.assert_any_call("clients.json", "r") + clients_file.save() + mock_file_open.assert_any_call("clients.json", "w") + mock_file_open().write.has_any_call(json.dumps(mock_clients)) + + +@pytest.mark.usefixtures("mock_clients") +def test_secret(mock_clients): + with patch( + "builtins.open", mock_open(read_data=json.dumps(mock_clients)) + ) as mock_file_open: + secret_pattern = "^[A-Za-z0-9]+$" + clients_file = ClientModel() + secret = clients_file.secret() + assert len(secret) == 36 + assert re.match(secret_pattern, secret) + secret = clients_file.secret(48) + assert len(secret) == 48 diff --git a/models/token.py b/models/token_model.py similarity index 55% rename from models/token.py rename to models/token_model.py index 6721a99..9782085 100644 --- a/models/token.py +++ b/models/token_model.py @@ -1,4 +1,4 @@ -from cryptography.fernet import Fernet,InvalidToken +from cryptography.fernet import Fernet, InvalidToken import datetime import os import json @@ -7,84 +7,73 @@ import json TOKENS = {} -class TokenModel(): +class TokenModel: clients = None schema = None key = None fernet = None token_lifetime_hours = None - env_lifetime = 'SOAR_TOKEN_LIFETIME' - env_secret = 'SOAR_TOKEN_SECRET' + env_lifetime = "SOAR_TOKEN_LIFETIME" + env_secret = "SOAR_TOKEN_SECRET" def __init__(self): self.getFernet() - self.token_lifetime_hours = os.getenv(self.env_lifetime, 24) - - def getFernet(self): - self.fernet = Fernet(self.getKey().encode()) + self.token_lifetime_hours = int(os.getenv(self.env_lifetime, 24)) - def getKey(self): + def getFernet(self): + self.fernet = Fernet(self.getKey().encode()) + + def getKey(self): key = os.getenv(self.env_secret) print(key) - if not key: + if not key: key = Fernet.generate_key().decode() os.environ[self.env_secret] = key - self.key = key + self.key = key return self.key - def setSecret(self): + def setSecret(self): if not os.getenv(self.env_secret): - os.environ[self.env_secret] = self.getKey() + os.environ[self.env_secret] = self.getKey() - def getExpiry(self): + def getExpiry(self): now = datetime.datetime.utcnow() expires = now + datetime.timedelta(hours=self.token_lifetime_hours) return expires.isoformat() - + def encrypt(self, client_id): try: expiry = self.getExpiry() - token_content = { - 'client_id': client_id, - 'expiry': expiry - } + token_content = {"client_id": client_id, "expiry": expiry} token = self.fernet.encrypt(json.dumps(token_content).encode()).decode() - return { - 'token': token, - 'expiry': expiry - } - except KeyError as e: + return {"token": token, "expiry": expiry} + except KeyError as e: return None - + def decrypt(self, token): - try: + try: content = json.loads(self.fernet.decrypt(token.encode()).decode()) - return content - except (InvalidToken,KeyError) as e: + return content + except (InvalidToken, KeyError) as e: return None def get(self, client_id): response = self.encrypt(client_id) - TOKENS[response['token']] = client_id - return response + TOKENS[response["token"]] = client_id + return response def validate(self, token): - response = { - 'valid': False - } + response = {"valid": False} if token in TOKENS: content = self.decrypt(token) if content: now = datetime.datetime.utcnow() - expires = datetime.datetime.fromisoformat(content['expiry']) - response['valid'] = expires > now - if response['valid']: + expires = datetime.datetime.fromisoformat(content["expiry"]) + response["valid"] = expires > now + if response["valid"]: response.update(content) - else: + else: del TOKENS[token] else: del TOKENS[token] - return response - - - + return response diff --git a/models/token_model_test.py b/models/token_model_test.py new file mode 100644 index 0000000..e8748c9 --- /dev/null +++ b/models/token_model_test.py @@ -0,0 +1,130 @@ +import cryptography +from datetime import datetime, timedelta +import json +import math +import os +import pytest +import re +from unittest.mock import patch, mock_open, call +from token_model import TokenModel + + +def test_init(): + token = TokenModel() + assert type(token.fernet) == cryptography.fernet.Fernet + assert token.token_lifetime_hours == 24 + + +def test_get_fernet_no_env(): + token = TokenModel() + token.getFernet() + assert type(token.fernet) == cryptography.fernet.Fernet + + +@pytest.mark.usefixtures("mock_token_secret") +def test_get_fernet_env(mock_token_secret): + with patch.dict(os.environ, {"SOAR_TOKEN_SECRET": mock_token_secret}): + token = TokenModel() + token.getFernet() + assert type(token.fernet) == cryptography.fernet.Fernet + assert token.key == mock_token_secret + + +def test_get_key_no_env(): + token = TokenModel() + secret = token.getKey() + assert secret is not None + assert token.key is not None + assert secret == token.key + + +@pytest.mark.usefixtures("mock_token_secret") +def test_get_key_env(mock_token_secret): + with patch.dict(os.environ, {"SOAR_TOKEN_SECRET": mock_token_secret}): + token = TokenModel() + secret = token.getKey() + assert secret == mock_token_secret + assert token.key == mock_token_secret + + +def test_set_secret(): + with patch.dict(os.environ): + token = TokenModel() + token.setSecret() + assert os.getenv(token.env_secret) == token.key + + +def test_get_expiry_no_env(): + token = TokenModel() + now = datetime.utcnow() + expected = now + timedelta(hours=token.token_lifetime_hours) + returned = datetime.strptime(token.getExpiry(), "%Y-%m-%dT%H:%M:%S.%f") + diff_seconds = abs(int((returned - expected).total_seconds())) + # Although the expected and returned are calculated separately + # the difference is generally a few microseconds. + assert diff_seconds == 0 + lifetime = math.ceil((returned - now).total_seconds()) + assert lifetime >= 60 * 60 * 24 + + +def test_get_expiry_env(): + with patch.dict(os.environ, {"SOAR_TOKEN_LIFETIME": "1"}): + token = TokenModel() + now = datetime.utcnow() + expected = now + timedelta(hours=token.token_lifetime_hours) + returned = datetime.fromisoformat(token.getExpiry()) + diff_seconds = abs(int((returned - expected).total_seconds())) + + assert diff_seconds == 0 + lifetime = math.ceil((returned - now).total_seconds()) + assert lifetime >= 60 * 60 + + +def test_encrypt_decrypt_no_env(): + token = TokenModel() + clear = "test" + cipher = token.encrypt(clear) + decoded = token.decrypt(cipher["token"]) + assert decoded["client_id"] == clear + assert decoded["expiry"] == cipher["expiry"] + + +@pytest.mark.usefixtures("mock_token_secret") +def test_encrypt_decrypt_env(mock_token_secret): + with patch.dict(os.environ, {"SOAR_TOKEN_SECRET": mock_token_secret}): + token = TokenModel() + clear = "test" + cipher = token.encrypt(clear) + decoded = token.decrypt(cipher["token"]) + assert decoded["client_id"] == clear + assert decoded["expiry"] == cipher["expiry"] + # A separate instance with the same secret + # is able to decode existing encrypted data + token = TokenModel() + decoded = token.decrypt(cipher["token"]) + assert decoded["client_id"] == clear + assert decoded["expiry"] == cipher["expiry"] + + +def test_get(): + # get returns an encrypted token + token = TokenModel() + response = token.get("test") + assert "token" in response + assert "expiry" in response + decoded = token.decrypt(response["token"]) + assert "client_id" in decoded + assert decoded["client_id"] == "test" + + +def test_validate(): + token = TokenModel() + valid_response = token.get("test") + valid_token = valid_response["token"] + validation = token.validate(valid_token) + assert validation["valid"] + assert "client_id" in validation + invalid_token = "abc" + valid_token[3:] + validation = token.validate(invalid_token) + assert not validation["valid"] + assert "client_id" not in validation diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..018e083 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +-r requirements.txt +black==23.1.0 +pytest==7.2.1 +pytest-mock==3.10.0 +pytest-rabbitmq==2.2.1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 6cd0750..7715a39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ jinja2==3.1.2 ; python_version >= '3.7' kombu==5.2.4 ; python_version >= '3.7' markupsafe==2.1.1 ; python_version >= '3.7' marshmallow==3.19.0 -packaging==21.3 ; python_version >= '3.6' +packaging>=22.0 ; python_version >= '3.6' pika==1.3.1 pubsubpy==2.3.0 pycparser==2.21 diff --git a/rmq.py b/rmq.py index ae9656b..719835e 100644 --- a/rmq.py +++ b/rmq.py @@ -7,6 +7,7 @@ host = os.getenv("MQ_HOST", "localhost") # Sets to whatever MQ_HOST is, or defau # ------------------------------------------------------------------------------------------------------------------------------------------------------------- + def pika_connect(host): try: connection = pika.BlockingConnection(pika.ConnectionParameters(host)) @@ -22,16 +23,22 @@ def pika_connect(host): return connection, channel -def setup_queue(channel, queue_name=''): - channel.queue_declare(queue=queue_name, exclusive=False, durable=True) # exclusive means the queue can only be used by the connection that created it +def setup_queue(channel, queue_name=""): + channel.queue_declare( + queue=queue_name, exclusive=False, durable=True + ) # exclusive means the queue can only be used by the connection that created it def fanout_exchange(channel, exchange_name): - channel.exchange_declare(exchange=exchange_name, exchange_type='fanout', durable=True) + channel.exchange_declare( + exchange=exchange_name, exchange_type="fanout", durable=True + ) def topic_exchange(channel, exchange_name): - channel.exchange_declare(exchange=exchange_name, exchange_type='topic', durable=True) + channel.exchange_declare( + exchange=exchange_name, exchange_type="topic", durable=True + ) def deliver_to_exchange(channel, body, exchange_name, topic=None): @@ -39,37 +46,39 @@ def deliver_to_exchange(channel, body, exchange_name, topic=None): fanout_exchange(channel=channel, exchange_name=exchange_name) channel.basic_publish( exchange=exchange_name, - routing_key='', - body=body, + routing_key="", + body=body, properties=pika.BasicProperties( delivery_mode=pika.spec.PERSISTENT_DELIVERY_MODE - ) + ), ) else: topic_exchange(channel=channel, exchange_name=exchange_name) channel.basic_publish( exchange=exchange_name, - routing_key=topic, - body=body, + routing_key=topic, + body=body, properties=pika.BasicProperties( delivery_mode=pika.spec.PERSISTENT_DELIVERY_MODE - ) + ), ) + # ------------------------------------------------------------------------------------------------------------------------------------------------------------- + def write_to_queue(queue_name, msg): # write a single message to a queue connection, channel = pika_connect(host=host) setup_queue(channel=channel, queue_name=queue_name) channel.basic_publish( - exchange='', - routing_key=queue_name, + exchange="", + routing_key=queue_name, body=msg, properties=pika.BasicProperties( delivery_mode=pika.spec.PERSISTENT_DELIVERY_MODE - ) + ), ) connection.close() @@ -125,12 +134,12 @@ def forward(from_queue, to_queue): def forward_callback(ch, method, properties, body): channel.basic_publish( - exchange='', - routing_key=to_queue, + exchange="", + routing_key=to_queue, body=body, properties=pika.BasicProperties( delivery_mode=pika.spec.PERSISTENT_DELIVERY_MODE - ) + ), ) ch.basic_ack(delivery_tag=method.delivery_tag) @@ -150,7 +159,9 @@ def publish(queue_name, exchange_name): def publish_callback(ch, method, properties, body): message = json.loads(body.decode()) topic = message["topic"] - deliver_to_exchange(channel=ch, body=body, exchange_name=exchange_name, topic=topic) + deliver_to_exchange( + channel=ch, body=body, exchange_name=exchange_name, topic=topic + ) ch.basic_ack(delivery_tag=method.delivery_tag) try: @@ -161,7 +172,7 @@ def publish(queue_name, exchange_name): def subscribe(queue_name, exchange_name, topic=None): - # setup bindings between queue and exchange, + # setup bindings between queue and exchange, # exchange_type is either 'fanout' or 'topic' based on if the topic arg is passed connection, channel = pika_connect(host=host) setup_queue(channel=channel, queue_name=queue_name) @@ -183,4 +194,4 @@ def listen(queue_name, callback): setup_queue(channel=channel, queue_name=queue_name) channel.basic_consume(queue=queue_name, on_message_callback=callback) - channel.start_consuming() \ No newline at end of file + channel.start_consuming() diff --git a/soar_bus.py b/soar_bus.py index f236322..741f6af 100644 --- a/soar_bus.py +++ b/soar_bus.py @@ -11,8 +11,8 @@ import concurrent.futures -from endpoints.clients import ClientsFile from rmq import broadcast, forward, publish, subscribe +from models.client_model import ClientModel THREADS = [] EXCHANGES = { @@ -23,7 +23,7 @@ EXCHANGES = { def main(): print("Starting SOAR bus...") - clients_file = ClientsFile() + clients_file = ClientModel() clients = clients_file.get() with concurrent.futures.ProcessPoolExecutor() as executor: @@ -31,7 +31,7 @@ def main(): thread = executor.submit(publish, "soar-publish", EXCHANGES.get("publish")) THREADS.append(thread) - for (id, client) in clients.items(): + for id, client in clients.items(): # forward thread = executor.submit(forward, f"{id}-outbox", "soar-publish") THREADS.append(thread) @@ -42,16 +42,14 @@ def main(): THREADS.append(thread) # subscribe thread = executor.submit( - subscribe, + subscribe, f"{id}-inbox", EXCHANGES.get("publish"), - client["subscription"] # topic + client["subscription"], # topic ) THREADS.append(thread) thread = executor.submit( - subscribe, - f"{id}-inbox", - EXCHANGES.get("broadcast") + subscribe, f"{id}-inbox", EXCHANGES.get("broadcast") ) THREADS.append(thread) # push -- GitLab