From 758bef9b8eb0274dd69d2900e3a374af5182480b Mon Sep 17 00:00:00 2001 From: Rich Braun Date: Thu, 29 Apr 2021 08:15:14 -0700 Subject: [PATCH] SYS-539 bump apicrud to 0.0.65 --- .gitlab-ci.yml | 2 +- Makefile | 10 +- Pipfile | 7 +- Pipfile.lock | 214 ++++++++++++++++------------- media/_version.py | 2 +- media/controllers/__init__.py | 4 +- media/controllers/auth.py | 41 ------ media/controllers/metric.py | 38 ----- media/main.py | 54 ++------ media/models/api.py | 19 ++- media/models/base.py | 5 + media/models/media.py | 15 -- media/openapi/account.path.yaml | 4 +- media/openapi/api.yaml | 4 +- media/openapi/health.path.yaml | 4 +- requirements-dev.txt | 48 +++---- tests/data/db_fixture.yaml | 50 +++++++ tests/data/schema-cac2000912a5.sql | 31 +++-- tests/test_albums.py | 4 +- tests/test_base.py | 171 ++++------------------- 20 files changed, 286 insertions(+), 441 deletions(-) delete mode 100644 media/controllers/auth.py delete mode 100644 media/controllers/metric.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e85d038..19ae3f5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ stages: - Publish Packages - Clean -image: instantlinux/python-builder:3.8.7-r1 +image: instantlinux/python-builder:3.8.8-r0 before_script: - export TAG=bld_$CI_PIPELINE_IID_${CI_COMMIT_SHA:0:7} diff --git a/Makefile b/Makefile index 4d93759..266edb4 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,12 @@ export APP_ENV ?= local include Makefile.vars include Makefile.i18n +VENV=python_env +VDIR=$(PWD)/$(VENV) + # Local dev -run_media: py_requirements +run_media: dev_requirements . $(VDIR)/bin/activate && \ AMQ_HOST=$(RABBITMQ_IP) REDIS_HOST=$(REDIS_IP) \ PUBLIC_URL=http://$(FQDN):$(APP_PORT) \ @@ -26,9 +29,6 @@ media_worker: celery -A media_worker worker -Q media_$(APP_ENV) \ -n media1@%h --loglevel=INFO -VENV=python_env -VDIR=$(PWD)/$(VENV) - .PHONY: qemu analysis: flake8 @@ -78,7 +78,7 @@ test: dev_requirements media/.proto.sqlite \ --cov-report html \ --cov-report xml \ --cov-report term-missing \ - --cov ../python_env/lib/python*/site-packages/apicrud/media \ + --cov ~/.local/lib/python*/site-packages/apicrud/media \ --cov .) media/.proto.sqlite: diff --git a/Pipfile b/Pipfile index 1a09957..2f2159e 100644 --- a/Pipfile +++ b/Pipfile @@ -1,6 +1,6 @@ [packages] -apicrud = "==0.0.56" -Pillow = "==7.2.0" +apicrud = "==0.0.66" +Pillow = "==8.1.2" # for faster builds, keep these versions in sync with images # instantlinux/python-builder and instantlinux/python-uwsgi @@ -9,7 +9,8 @@ celery = "==4.4.7" connexion = "*" "connexion[swagger-ui]" = "*" dollar-ref = "*" -SQLAlchemy = "*" +# 1.4.1 throws shitloads of warnings +SQLAlchemy = "==1.3.23" SQLAlchemy-Utils = "*" swagger-ui-bundle = "*" diff --git a/Pipfile.lock b/Pipfile.lock index ff42282..d865654 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "49e33a434de5636b0aa5f29cfafc05b15cbb87f326aaca7f2c016c62a37c7808" + "sha256": "0b0537a3bd93f71af02f5f6a52aa154f4146afa8affc3a2030f5b5f54041cc7f" }, "pipfile-spec": 6, "requires": {}, @@ -32,11 +32,11 @@ }, "apicrud": { "hashes": [ - "sha256:2c598840de746e7d7b0f93d287c371a6b50f6d39a16d8adf233431fe51fd51e9", - "sha256:e0545ff49a73e8282539996bf40a90e2c6d9be582891339a3ab2bb895eed1b0e" + "sha256:8c549f79f940334c9fe40762f8debf1aba552843ad77f16911ca37f67251974e", + "sha256:f369104a36aee950e8d4196a3d82bf1f0a0187d6847b8ab12a2120cb66915984" ], "index": "pypi", - "version": "==0.0.56" + "version": "==0.0.66" }, "arrow": { "hashes": [ @@ -54,6 +54,13 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.3.0" }, + "authlib": { + "hashes": [ + "sha256:0f6af3a38d37dd77361808dd3f2e258b647668dac6d2cefcefc4c4ebc3c7d2b2", + "sha256:7dde11ba45db51e97169c261362fab3193073100b7387e60c159db1eec470bbc" + ], + "version": "==0.15.3" + }, "b2sdk": { "hashes": [ "sha256:af53e2449414b772a0bd6fad311d98a171c3693f77ebca933499946e786715ae" @@ -63,18 +70,18 @@ }, "babel": { "hashes": [ - "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5", - "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05" + "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9", + "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.9.0" + "version": "==2.9.1" }, "billiard": { "hashes": [ - "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", - "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" + "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547", + "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b" ], - "version": "==3.6.3.0" + "version": "==3.6.4.0" }, "boto3": { "hashes": [ @@ -92,6 +99,14 @@ "index": "pypi", "version": "==1.19.47" }, + "cachetools": { + "hashes": [ + "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001", + "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff" + ], + "markers": "python_version ~= '3.5'", + "version": "==4.2.2" + }, "celery": { "hashes": [ "sha256:a92e1d56e650781fb747032a3997d16236d037c8199eacd5217d1a72893bca45", @@ -206,10 +221,11 @@ }, "decorator": { "hashes": [ - "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", - "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" + "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060", + "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98" ], - "version": "==4.4.2" + "markers": "python_version >= '3.5'", + "version": "==5.0.7" }, "dollar-ref": { "hashes": [ @@ -251,7 +267,7 @@ "hashes": [ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.18.2" }, "geocoder": { @@ -298,7 +314,7 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.0" }, "jsonschema": { @@ -407,37 +423,42 @@ }, "pillow": { "hashes": [ - "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f", - "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8", - "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad", - "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f", - "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae", - "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d", - "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5", - "sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b", - "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8", - "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233", - "sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6", - "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727", - "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f", - "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38", - "sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4", - "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626", - "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d", - "sha256:9c87ef410a58dd54b92424ffd7e28fd2ec65d2f7fc02b76f5e9b2067e355ebf6", - "sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6", - "sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63", - "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f", - "sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41", - "sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1", - "sha256:e901964262a56d9ea3c2693df68bc9860b8bdda2b04768821e4c44ae797de117", - "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d", - "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9", - "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a", - "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce" + "sha256:15306d71a1e96d7e271fd2a0737038b5a92ca2978d2e38b6ced7966583e3d5af", + "sha256:1940fc4d361f9cc7e558d6f56ff38d7351b53052fd7911f4b60cd7bc091ea3b1", + "sha256:1f93f2fe211f1ef75e6f589327f4d4f8545d5c8e826231b042b483d8383e8a7c", + "sha256:30d33a1a6400132e6f521640dd3f64578ac9bfb79a619416d7e8802b4ce1dd55", + "sha256:328240f7dddf77783e72d5ed79899a6b48bc6681f8d1f6001f55933cb4905060", + "sha256:46c2bcf8e1e75d154e78417b3e3c64e96def738c2a25435e74909e127a8cba5e", + "sha256:5762ebb4436f46b566fc6351d67a9b5386b5e5de4e58fdaa18a1c83e0e20f1a8", + "sha256:5a2d957eb4aba9d48170b8fe6538ec1fbc2119ffe6373782c03d8acad3323f2e", + "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0", + "sha256:5daba2b40782c1c5157a788ec4454067c6616f5a0c1b70e26ac326a880c2d328", + "sha256:63cd413ac52ee3f67057223d363f4f82ce966e64906aea046daf46695e3c8238", + "sha256:6efac40344d8f668b6c4533ae02a48d52fd852ef0654cc6f19f6ac146399c733", + "sha256:71b01ee69e7df527439d7752a2ce8fb89e19a32df484a308eca3e81f673d3a03", + "sha256:71f31ee4df3d5e0b366dd362007740106d3210fb6a56ec4b581a5324ba254f06", + "sha256:72027ebf682abc9bafd93b43edc44279f641e8996fb2945104471419113cfc71", + "sha256:74cd9aa648ed6dd25e572453eb09b08817a1e3d9f8d1bd4d8403d99e42ea790b", + "sha256:81b3716cc9744ffdf76b39afb6247eae754186838cedad0b0ac63b2571253fe6", + "sha256:8565355a29655b28fdc2c666fd9a3890fe5edc6639d128814fafecfae2d70910", + "sha256:87f42c976f91ca2fc21a3293e25bd3cd895918597db1b95b93cbd949f7d019ce", + "sha256:89e4c757a91b8c55d97c91fa09c69b3677c227b942fa749e9a66eef602f59c28", + "sha256:8c4e32218c764bc27fe49b7328195579581aa419920edcc321c4cb877c65258d", + "sha256:903293320efe2466c1ab3509a33d6b866dc850cfd0c5d9cc92632014cec185fb", + "sha256:90882c6f084ef68b71bba190209a734bf90abb82ab5e8f64444c71d5974008c6", + "sha256:98afcac3205d31ab6a10c5006b0cf040d0026a68ec051edd3517b776c1d78b09", + "sha256:a01da2c266d9868c4f91a9c6faf47a251f23b9a862dce81d2ff583135206f5be", + "sha256:aeab4cd016e11e7aa5cfc49dcff8e51561fa64818a0be86efa82c7038e9369d0", + "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44", + "sha256:bead24c0ae3f1f6afcb915a057943ccf65fc755d11a1410a909c1fefb6c06ad1", + "sha256:d1d6bca39bb6dd94fba23cdb3eeaea5e30c7717c5343004d900e2a63b132c341", + "sha256:e2cd8ac157c1e5ae88b6dd790648ee5d2777e76f1e5c7d184eaddb2938594f34", + "sha256:e5739ae63636a52b706a0facec77b2b58e485637e1638202556156e424a02dc2", + "sha256:f36c3ff63d6fc509ce599a2f5b0d0732189eed653420e7294c039d342c6e204a", + "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5" ], "index": "pypi", - "version": "==7.2.0" + "version": "==8.1.2" }, "pycparser": { "hashes": [ @@ -504,6 +525,13 @@ "markers": "python_version >= '3.6'", "version": "==1.0.2" }, + "pyotp": { + "hashes": [ + "sha256:9d144de0f8a601d6869abe1409f4a3f75f097c37b50a36a3bf165810a6e23f28", + "sha256:d28ddfd40e0c1b6a6b9da961c7d47a10261fb58f378cb00f05ce88b26df9c432" + ], + "version": "==2.6.0" + }, "pyrsistent": { "hashes": [ "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" @@ -516,7 +544,7 @@ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.1" }, "python-editor": { @@ -596,17 +624,17 @@ }, "s3transfer": { "hashes": [ - "sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed", - "sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2" + "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994", + "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246" ], - "version": "==0.3.4" + "version": "==0.3.7" }, "six": { "hashes": [ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "sqlalchemy": { @@ -655,10 +683,10 @@ }, "sqlalchemy-utils": { "hashes": [ - "sha256:fb66e9956e41340011b70b80f898fde6064ec1817af77199ee21ace71d7d6ab0" + "sha256:c7bec2c982b31ec6133ba519f73f07653bbb7e7b3c23836bb8d9133045386b68" ], "index": "pypi", - "version": "==0.36.8" + "version": "==0.37.0" }, "swagger-ui-bundle": { "hashes": [ @@ -676,19 +704,19 @@ }, "tqdm": { "hashes": [ - "sha256:9fdf349068d047d4cfbe24862c425883af1db29bcddf4b0eeb2524f6fbdb23c7", - "sha256:d666ae29164da3e517fcf125e41d4fe96e5bb375cd87ff9763f6b38b5592fe33" + "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3", + "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.59.0" + "version": "==4.60.0" }, "urllib3": { "hashes": [ - "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", - "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" + "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", + "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" ], "markers": "python_version != '3.4'", - "version": "==1.26.3" + "version": "==1.26.4" }, "vine": { "hashes": [ @@ -867,19 +895,19 @@ }, "fakeredis": { "hashes": [ - "sha256:01cb47d2286825a171fb49c0e445b1fa9307087e07cbb3d027ea10dbff108b6a", - "sha256:2c6041cf0225889bc403f3949838b2c53470a95a9e2d4272422937786f5f8f73" + "sha256:1ac0cef767c37f51718874a33afb5413e69d132988cb6a80c6e6dbeddf8c7623", + "sha256:e0416e4941cecd3089b0d901e60c8dc3c944f6384f5e29e2261c0d3c5fa99669" ], "index": "pypi", - "version": "==1.4.5" + "version": "==1.5.0" }, "flake8": { "hashes": [ - "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", - "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" + "sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378", + "sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a" ], "index": "pypi", - "version": "==3.8.4" + "version": "==3.9.1" }, "idna": { "hashes": [ @@ -909,7 +937,7 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.0" }, "markupsafe": { @@ -977,14 +1005,6 @@ ], "version": "==0.6.1" }, - "mock": { - "hashes": [ - "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62", - "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc" - ], - "markers": "python_version >= '3.6'", - "version": "==4.0.3" - }, "more-itertools": { "hashes": [ "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", @@ -995,11 +1015,11 @@ }, "moto": { "hashes": [ - "sha256:5c729d67bd879f3874d772cb486da7788e8c9820f49d676b9cb839333ff40bb9", - "sha256:b0761ad0d73eaaf92324a190f780bb482dfed57c235c1cada1f5cfbf84ae6feb" + "sha256:17219c9e8d79bc7450615896b37e5bd639c2038d4672bcab114ac4661e27f8ae", + "sha256:467f25a734a04c552aa0fb09bcffd5e7e5d17b056178c065493c2eb0fe49891c" ], "index": "pypi", - "version": "==2.0.1" + "version": "==2.0.5" }, "packaging": { "hashes": [ @@ -1027,11 +1047,11 @@ }, "pycodestyle": { "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" + "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", + "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.6.0" + "version": "==2.7.0" }, "pycparser": { "hashes": [ @@ -1043,27 +1063,27 @@ }, "pyflakes": { "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" + "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", + "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.2.0" + "version": "==2.3.1" }, "pyparsing": { "hashes": [ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.4.7" }, "pytest": { "hashes": [ - "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9", - "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839" + "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", + "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" ], "index": "pypi", - "version": "==6.2.2" + "version": "==6.2.3" }, "pytest-cov": { "hashes": [ @@ -1078,7 +1098,7 @@ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.1" }, "pytz": { @@ -1106,25 +1126,25 @@ }, "responses": { "hashes": [ - "sha256:2e5764325c6b624e42b428688f2111fea166af46623cb0127c05f6afb14d3457", - "sha256:ef265bd3200bdef5ec17912fc64a23570ba23597fd54ca75c18650fa1699213d" + "sha256:18a5b88eb24143adbf2b4100f328a2f5bfa72fbdacf12d97d41f07c26c45553d", + "sha256:b54067596f331786f5ed094ff21e8d79e6a1c68ef625180a7d34808d6f36c11b" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.12.1" + "version": "==0.13.3" }, "s3transfer": { "hashes": [ - "sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed", - "sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2" + "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994", + "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246" ], - "version": "==0.3.4" + "version": "==0.3.7" }, "six": { "hashes": [ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "sortedcontainers": { @@ -1139,16 +1159,16 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "urllib3": { "hashes": [ - "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", - "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" + "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", + "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" ], "markers": "python_version != '3.4'", - "version": "==1.26.3" + "version": "==1.26.4" }, "werkzeug": { "hashes": [ diff --git a/media/_version.py b/media/_version.py index 3a6ec64..7df0c96 100644 --- a/media/_version.py +++ b/media/_version.py @@ -1,3 +1,3 @@ -__version__ = '0.0.30' +__version__ = '0.0.31' vcs_ref = 'unset' build_date = 'unset' diff --git a/media/controllers/__init__.py b/media/controllers/__init__.py index 41dc287..51810f7 100644 --- a/media/controllers/__init__.py +++ b/media/controllers/__init__.py @@ -5,7 +5,9 @@ created 14-jan-2020 by richb@instantlinux.net """ -from . import album, file, metric, picture, storage +from apicrud.controllers import metric + +from . import album, file, picture, storage def resources(): diff --git a/media/controllers/auth.py b/media/controllers/auth.py deleted file mode 100644 index f475e4d..0000000 --- a/media/controllers/auth.py +++ /dev/null @@ -1,41 +0,0 @@ -"""auth stub for microservice unit-tests - -See example/controllers/auth.py in apicrud repo for full implementation -This is temporary, until API-key authentication is added - -created 17-may-2020 by richb@instantlinux.net -""" - -from flask_babel import _ - -from apicrud import SessionAuth -import models - - -class AuthController(object): - - def __init__(self): - self.resource = 'auth' - - @staticmethod - def login(body): - """Login a new session - - Args: - body (dict): specify username and password - Returns: - dict: - Fields include jwt_token (contains uid / account ID), - ID of entry in settings database, and a sub-dictionary - with mapping of endpoints registered to microservices - """ - return SessionAuth().account_login( - body['username'], body['password'], roles_from=models.List) - - def logout(): - """Logout - - Returns: - tuple: dict with message and http status code 200 - """ - return dict(message=_(u'logged out')), 200 diff --git a/media/controllers/metric.py b/media/controllers/metric.py deleted file mode 100644 index 2be9739..0000000 --- a/media/controllers/metric.py +++ /dev/null @@ -1,38 +0,0 @@ -"""metric controller - -created 27-may-2019 by richb@instantlinux.net -""" - -from apicrud import BasicCRUD, Metrics - - -class MetricController(BasicCRUD): - def __init__(self): - self.resource = 'metric' - - @staticmethod - def get(id): - """Get one metric - - Args: - id (str): Database or hybrid metric ID - """ - return Metrics().get(id) - - @staticmethod - def find(**kwargs): - """Find multiple metrics - - Args: - kwargs: as defined in openapi.yaml - """ - return Metrics().find(**kwargs) - - @staticmethod - def collect(**kwargs): - """Collect metrics in prometheus-compatible format - - Args: - kwargs: as defined in openapi.yaml - """ - return Metrics().collect(**kwargs) diff --git a/media/main.py b/media/main.py index 791316d..df4d9bf 100755 --- a/media/main.py +++ b/media/main.py @@ -4,62 +4,29 @@ created 9-dec-2019 by richb@instantlinux.net """ - import connexion -from datetime import datetime -from flask import g, request +from flask import g from flask_babel import Babel import os -from apicrud import AccessControl, AccountSettings, Metrics, ServiceConfig, \ - ServiceRegistry, SessionManager, database, initialize +from apicrud import ServiceConfig, initialize import controllers from messaging import send_contact import models application = connexion.FlaskApp(__name__) -path = os.path.dirname(os.path.abspath(__file__)) -config = ServiceConfig( - babel_translation_directories='i18n;%s' % os.path.join(path, 'i18n'), - reset=True, file=os.path.join(path, 'config.yaml'), models=models).config -initialize.app(application) babel = Babel(application.app) -@application.app.before_first_request -def setup_db(db_url=None, redis_conn=None): - """Database setup - - Args: - db_url (str): URL with db host, credentials and db name - redis_conn (obj): connection to redis - """ - db_url = db_url or config.DB_URL - ServiceRegistry().register(controllers.resources()) - if __name__ in ['__main__', 'main', 'uwsgi_file_main']: - database.initialize_db(db_url=db_url, redis_conn=redis_conn) - Metrics(redis_conn=redis_conn, func_send=send_contact.delay).store( - 'api_start_timestamp', value=int(datetime.now().timestamp())) - - @application.app.before_request def before_request(): - """Request-setup function - get sessions to database and auth - """ - g.db = database.get_session() - g.session = SessionManager() - g.request_start_time = datetime.utcnow() + initialize.before_request() @application.app.after_request def add_header(response): - """All responses get a cache-control header""" - response.cache_control.max_age = config.HTTP_RESPONSE_CACHE_MAX_AGE - Metrics().store( - 'api_request_seconds_total', value=datetime.utcnow().timestamp() - - g.request_start_time.timestamp()) - return response + return initialize.after_request(response) @application.app.teardown_appcontext @@ -71,14 +38,11 @@ def cleanup(resp_or_exc): @babel.localeselector def get_locale(): - acc = AccessControl() - if acc.auth and acc.uid: - locale = AccountSettings(acc.account_id, - uid=acc.uid, db_session=g.db).locale - if locale: - return locale - return request.accept_languages.best_match(config.LANGUAGES) + return initialize.get_locale() +if __name__ in ('__main__', 'uwsgi_file_main', 'media.main'): + initialize.app(application, controllers, models, os.path.dirname( + os.path.abspath(__file__)), func_send=send_contact.delay) if __name__ == '__main__': - application.run(port=config.APP_PORT) + application.run(port=ServiceConfig().config.APP_PORT) diff --git a/media/models/api.py b/media/models/api.py index 278141f..de10e06 100644 --- a/media/models/api.py +++ b/media/models/api.py @@ -8,9 +8,10 @@ # coding: utf-8 # from geoalchemy2 import Geometry -from sqlalchemy import BOOLEAN, Column, Enum, ForeignKey, INTEGER, String, \ - TEXT, TIMESTAMP, Unicode, UniqueConstraint +from sqlalchemy import BOOLEAN, Column, Enum, ForeignKey, INTEGER, \ + LargeBinary, String, TEXT, TIMESTAMP, Unicode, UniqueConstraint from sqlalchemy import func +from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import relationship, backref from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine, \ StringEncryptedType @@ -24,8 +25,9 @@ class Account(AsDictMixin, Base): __table_args__ = ( UniqueConstraint(u'id', u'uid', name='uniq_account_user'), ) - __rest_exclude__ = ('password', 'totp_secret', 'invalid_attempts', - 'last_invalid_attempt') + __rest_exclude__ = ('backup_codes', 'password', 'totp_secret', + 'invalid_attempts', 'last_invalid_attempt') + __rest_hybrid__ = ('totp',) id = Column(String(16), primary_key=True, unique=True) name = Column(String(32), nullable=False, unique=True) @@ -36,7 +38,8 @@ class Account(AsDictMixin, Base): password_must_change = Column(BOOLEAN, nullable=False, server_default="False") totp_secret = Column(StringEncryptedType(Unicode, aes_secret, AesEngine, - 'pkcs5', length=48)) + 'pkcs5', length=64)) + backup_codes = Column(LargeBinary(256)) is_admin = Column(BOOLEAN, nullable=False, server_default="False") settings_id = Column(ForeignKey(u'settings.id'), nullable=False) last_login = Column(TIMESTAMP) @@ -50,6 +53,10 @@ class Account(AsDictMixin, Base): 'account_uid', cascade='all, delete-orphan')) settings = relationship('Settings') + @hybrid_property + def totp(self): + return True if self.totp_secret else False + class APIkey(AsDictMixin, Base): __tablename__ = 'apikeys' @@ -361,7 +368,7 @@ class Profile(AsDictMixin, Base): id = Column(String(16), primary_key=True, unique=True) uid = Column(ForeignKey(u'people.id', ondelete='CASCADE'), nullable=False) item = Column(String(32), nullable=False) - value = Column(String(32)) + value = Column(String(96)) location_id = Column(ForeignKey(u'locations.id')) tz_id = Column(ForeignKey(u'time_zone_name.id')) privacy = Column(String(8), nullable=False, server_default=u'public') diff --git a/media/models/base.py b/media/models/base.py index e92bb54..6262d39 100644 --- a/media/models/base.py +++ b/media/models/base.py @@ -10,10 +10,12 @@ from apicrud import ServiceConfig Base = declarative_base() +# TODO set this value after initialize.app not at import time aes_secret = ServiceConfig().config.DB_AES_SECRET class AsDictMixin(object): + def as_dict(self): """Returns a serializable dict from an instance of the model @@ -31,5 +33,8 @@ def as_dict(self): if hasattr(self, '__rest_related__'): for key in self.__rest_related__: retval[key] = [rec.id for rec in getattr(self, key)] + if hasattr(self, '__rest_hybrid__'): + for key in self.__rest_hybrid__: + retval[key] = getattr(self, key) retval.pop('_sa_instance_state', None) return retval diff --git a/media/models/media.py b/media/models/media.py index 4ef4fc5..326e353 100644 --- a/media/models/media.py +++ b/media/models/media.py @@ -70,11 +70,6 @@ class AlbumContent(Base): rank = Column(Float) created = Column(TIMESTAMP, nullable=False, server_default=func.now()) - album = relationship('Album', foreign_keys=[album_id], backref=backref( - 'albumcontents', cascade='all, delete-orphan')) - picture = relationship('Picture', backref=backref( - 'albumcontents', cascade='all, delete-orphan')) - class File(AsDictMixin, Base): __tablename__ = 'files' @@ -114,11 +109,6 @@ class ListFile(Base): nullable=False, index=True) created = Column(TIMESTAMP, nullable=False, server_default=func.now()) - file = relationship('File', foreign_keys=[file_id], backref=backref( - 'listfiles', cascade='all, delete-orphan')) - list = relationship('List', backref=backref( - 'listfiles', cascade='all, delete-orphan')) - # Message attachments class MessageFile(Base): @@ -133,11 +123,6 @@ class MessageFile(Base): nullable=False, index=True) created = Column(TIMESTAMP, nullable=False, server_default=func.now()) - file = relationship('File', foreign_keys=[file_id], backref=backref( - 'messagefiles', cascade='all, delete-orphan')) - message = relationship('Message', backref=backref( - 'messagefiles', cascade='all, delete-orphan')) - class Picture(Base): __tablename__ = 'pictures' diff --git a/media/openapi/account.path.yaml b/media/openapi/account.path.yaml index c64b6b3..d18034f 100644 --- a/media/openapi/account.path.yaml +++ b/media/openapi/account.path.yaml @@ -15,7 +15,7 @@ auth: tags: - auth x-codegen-request-body-name: body - x-openapi-router-controller: controllers.auth + x-openapi-router-controller: apicrud.controllers.auth logout: get: summary: Log out @@ -29,4 +29,4 @@ logout: description: Invalid input tags: - auth - x-openapi-router-controller: controllers.auth + x-openapi-router-controller: apicrud.controllers.auth diff --git a/media/openapi/api.yaml b/media/openapi/api.yaml index 6f0ce44..f7e542b 100644 --- a/media/openapi/api.yaml +++ b/media/openapi/api.yaml @@ -36,12 +36,12 @@ components: basic: type: http scheme: basic - x-basicInfoFunc: apicrud.session_auth.basic + x-basicInfoFunc: apicrud.auth.local_user.basic apikey: in: header name: X-API-KEY type: apiKey - x-apikeyInfoFunc: apicrud.session_auth.api_key + x-apikeyInfoFunc: apicrud.auth.apikey.auth paths: /album: diff --git a/media/openapi/health.path.yaml b/media/openapi/health.path.yaml index 8cf52d0..8a7b42d 100644 --- a/media/openapi/health.path.yaml +++ b/media/openapi/health.path.yaml @@ -1,5 +1,6 @@ health: get: + summary: Health Check description: Status of API operationId: HealthController.get parameters: @@ -9,8 +10,8 @@ health: required: false schema: items: - maxLength: 10 type: string + maxLength: 10 type: array responses: 200: @@ -19,7 +20,6 @@ health: 503: content: {} description: Service unhealthy - summary: Health Check tags: - health x-openapi-router-controller: controllers.health diff --git a/requirements-dev.txt b/requirements-dev.txt index 488912f..de13ea6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,14 +11,16 @@ -i https://pypi.org/simple alembic==1.4.3 amqp==2.6.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -apicrud==0.0.56 +apicrud==0.0.66 arrow==1.0.1 attrs==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +authlib==0.15.3 b2sdk==1.3.0 -babel==2.9.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -billiard==3.6.3.0 +babel==2.9.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +billiard==3.6.4.0 boto3==1.16.47 botocore==1.19.47 +cachetools==4.2.2; python_version ~= '3.5' celery==4.4.7 certifi==2020.12.5 cffi==1.14.4 @@ -28,65 +30,65 @@ clickclick==20.10.2 connexion[swagger-ui]==2.7.0 coverage==5.4 cryptography==3.3.2 -decorator==4.4.2 +decorator==5.0.7; python_version >= '3.5' dollar-ref==0.1.3 -fakeredis==1.4.5 -flake8==3.8.4 +fakeredis==1.5.0 +flake8==3.9.1 flask-babel==2.0.0 flask-cors==3.0.10 flask==1.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' funcsigs==1.0.2 -future==0.18.2; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2' +future==0.18.2; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' geocoder==1.38.1 idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' inflection==0.5.1; python_version >= '3.5' iniconfig==1.1.1 itsdangerous==1.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' jinja2==2.11.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -jmespath==0.10.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2' +jmespath==0.10.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' jsonschema==3.2.0 kombu==4.6.11; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' logfury==0.1.2 mako==1.1.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' markupsafe==1.1.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' mccabe==0.6.1 -mock==4.0.3; python_version >= '3.6' more-itertools==8.7.0; python_version >= '3.5' -moto==2.0.1 +moto==2.0.5 openapi-spec-validator==0.2.9 packaging==20.9; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' passlib==1.7.4 -pillow==7.2.0 +pillow==8.1.2 pluggy==0.13.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' py==1.10.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -pycodestyle==2.6.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +pycodestyle==2.7.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' pycryptodomex==3.9.9 -pyflakes==2.2.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +pyflakes==2.3.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' pyjwt==1.7.1 pymysql==1.0.2; python_version >= '3.6' -pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2' +pyotp==2.6.0 +pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' pyrsistent==0.17.3; python_version >= '3.5' pytest-cov==2.11.1 -pytest==6.2.2 -python-dateutil==2.8.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' +pytest==6.2.3 +python-dateutil==2.8.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' python-editor==1.0.4 pytz==2021.1 pyyaml==5.4.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' ratelim==0.1.6 redis==3.5.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' requests==2.25.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -responses==0.12.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -s3transfer==0.3.4 -six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' +responses==0.13.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +s3transfer==0.3.7 +six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' sortedcontainers==2.3.0 -sqlalchemy-utils==0.36.8 +sqlalchemy-utils==0.37.0 sqlalchemy==1.3.23 swagger-ui-bundle==0.0.8 termcolor==1.1.0 -toml==0.10.2; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2' -tqdm==4.59.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -urllib3==1.26.3; python_version != '3.4' +toml==0.10.2; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' +tqdm==4.60.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +urllib3==1.26.4; python_version != '3.4' vine==1.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' xmltodict==0.12.0 diff --git a/tests/data/db_fixture.yaml b/tests/data/db_fixture.yaml index 001eaf5..c9a061e 100644 --- a/tests/data/db_fixture.yaml +++ b/tests/data/db_fixture.yaml @@ -1,9 +1,39 @@ --- # Seed records for test fixture +_constants: + access_key_id: AKIAP0XS5P8E4XXX1234 + account_id: x-TpP43xS9 + adm_account_id: x-6hj1BXEH + adm_cat_id: x-1xDjMu6M + adm_contact_2: x-P703gZkJ + adm_contact_id: x-6b1AUgmt + adm_person_name: Test Admin + admin_email: admin@test.conclave.events + admin_name: testadmin + admin_pw: opens3crEt + admin_uid: u-tMY1b862 + bucket: apicrud-test + cat_id: x-Jiqag482 + contact_id: x-E97yKy73 + default_storage_id: x-RwgSVd5G + location_id: x-67673434 + password: t0ps3crEt + settings_id: x-75023275 + test_email: testuser@test.conclave.events + test_person_name: Conclave Tester + test_uid: u-B0miQweE + theme_id: x-05a720bf + username: testr category: - id: x-3423ceaf name: default uid: x-23450001 +- id: x-Jiqag482 + name: default + uid: u-B0miQweE +- id: x-1xDjMu6M + name: default + uid: u-tMY1b862 person: - id: x-23450001 name: Example User @@ -15,6 +45,11 @@ person: identity: admin@test.conclave.events privacy: public status: active +- id: u-B0miQweE + name: Conclave Tester + identity: testuser@test.conclave.events + privacy: public + status: active contact: - id: x-4566a239 uid: x-23450001 @@ -23,6 +58,13 @@ contact: muted: True privacy: public status: active +- id: x-E97yKy73 + info: testuser@test.conclave.events + muted: False + privacy: public + type: email + uid: u-B0miQweE + status: active - id: x-6b1AUgmt info: admin@test.conclave.events muted: False @@ -46,6 +88,14 @@ account: password_must_change: False password: $5$rounds=535000$YPzbABo4IekfjkMO$mWiN711ak8D16YNyc/x.K8FBfQBp1J3q8yrokRheSy7 status: active +- id: x-TpP43xS9 + name: testr + is_admin: False + uid: u-B0miQweE + settings_id: x-75023275 + password_must_change: False + password: $5$rounds=535000$HGVkMUlpoWPlgbsN$BqsTgmTmiUXaSLoaiQgEY5IWxNLrA3Y1D2qlzm57hz/ + status: active - id: x-6hj1BXEH name: testadmin is_admin: True diff --git a/tests/data/schema-cac2000912a5.sql b/tests/data/schema-cac2000912a5.sql index 0f65fec..7a2a25a 100644 --- a/tests/data/schema-cac2000912a5.sql +++ b/tests/data/schema-cac2000912a5.sql @@ -158,8 +158,8 @@ CREATE TABLE IF NOT EXISTS "listmembers" ( created TIMESTAMP DEFAULT (CURRENT_TIMESTAMP) NOT NULL, PRIMARY KEY (uid, list_id), CONSTRAINT uniq_listmember UNIQUE (list_id, uid), - FOREIGN KEY(list_id) REFERENCES lists (id) ON DELETE CASCADE, - FOREIGN KEY(uid) REFERENCES people (id) ON DELETE CASCADE + FOREIGN KEY(uid) REFERENCES people (id) ON DELETE CASCADE, + FOREIGN KEY(list_id) REFERENCES lists (id) ON DELETE CASCADE ); CREATE INDEX ix_listmembers_list_id ON listmembers (list_id); CREATE TABLE IF NOT EXISTS "listmessages" ( @@ -168,8 +168,8 @@ CREATE TABLE IF NOT EXISTS "listmessages" ( created TIMESTAMP DEFAULT (CURRENT_TIMESTAMP) NOT NULL, PRIMARY KEY (message_id, list_id), CONSTRAINT uniq_listmessage UNIQUE (list_id, message_id), - FOREIGN KEY(message_id) REFERENCES messages (id) ON DELETE CASCADE, - FOREIGN KEY(list_id) REFERENCES lists (id) ON DELETE CASCADE + FOREIGN KEY(list_id) REFERENCES lists (id) ON DELETE CASCADE, + FOREIGN KEY(message_id) REFERENCES messages (id) ON DELETE CASCADE ); CREATE INDEX ix_listmessages_list_id ON listmessages (list_id); CREATE TABLE scopes ( @@ -200,7 +200,8 @@ CREATE TABLE accounts ( uid VARCHAR(16) NOT NULL, password VARCHAR(128) NOT NULL, password_must_change BOOLEAN DEFAULT 0 NOT NULL, - totp_secret VARCHAR(48), + totp_secret VARCHAR(64), + backup_codes BLOB, is_admin BOOLEAN DEFAULT 0 NOT NULL, settings_id VARCHAR(16) NOT NULL, last_login TIMESTAMP, @@ -344,16 +345,16 @@ CREATE TABLE IF NOT EXISTS "messages" ( PRIMARY KEY (id), CHECK (published IN (0, 1)), CONSTRAINT messages_fk1 FOREIGN KEY(album_id) REFERENCES albums (id), - UNIQUE (id), - FOREIGN KEY(sender_id) REFERENCES people (id), FOREIGN KEY(recipient_id) REFERENCES people (id), + FOREIGN KEY(sender_id) REFERENCES people (id), + UNIQUE (id), FOREIGN KEY(uid) REFERENCES people (id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS "profileitems" ( id VARCHAR(16) NOT NULL, uid VARCHAR(16) NOT NULL, item VARCHAR(16) NOT NULL, - value VARCHAR(32), + value VARCHAR(96), location_id VARCHAR(16), tz_id INTEGER, privacy VARCHAR(8) DEFAULT 'public' NOT NULL, @@ -364,10 +365,10 @@ CREATE TABLE IF NOT EXISTS "profileitems" ( PRIMARY KEY (id), CONSTRAINT uniq_itemuid UNIQUE (uid, item), CONSTRAINT albums_fk1 FOREIGN KEY(album_id) REFERENCES albums (id), - UNIQUE (id), - FOREIGN KEY(uid) REFERENCES people (id) ON DELETE CASCADE, FOREIGN KEY(tz_id) REFERENCES time_zone_name (id), - FOREIGN KEY(location_id) REFERENCES locations (id) + FOREIGN KEY(uid) REFERENCES people (id) ON DELETE CASCADE, + FOREIGN KEY(location_id) REFERENCES locations (id), + UNIQUE (id) ); CREATE TABLE albumcontents ( album_id VARCHAR(16) NOT NULL, @@ -433,11 +434,11 @@ CREATE TABLE IF NOT EXISTS "settings" ( PRIMARY KEY (id), CONSTRAINT settings_fk1 FOREIGN KEY(smtp_credential_id) REFERENCES credentials (id), CONSTRAINT settings_fk2 FOREIGN KEY(default_storage_id) REFERENCES storageitems (id), - FOREIGN KEY(default_cat_id) REFERENCES categories (id), - FOREIGN KEY(administrator_id) REFERENCES people (id), FOREIGN KEY(default_hostlist_id) REFERENCES lists (id), - UNIQUE (id), - FOREIGN KEY(tz_id) REFERENCES time_zone_name (id) + FOREIGN KEY(tz_id) REFERENCES time_zone_name (id), + FOREIGN KEY(administrator_id) REFERENCES people (id), + FOREIGN KEY(default_cat_id) REFERENCES categories (id), + UNIQUE (id) ); CREATE TABLE events ( id VARCHAR(16) NOT NULL, diff --git a/tests/test_albums.py b/tests/test_albums.py index 4023886..c20a851 100644 --- a/tests/test_albums.py +++ b/tests/test_albums.py @@ -12,7 +12,9 @@ class TestAlbums(test_base.TestBase): def setUp(self): - self.authorize(username=self.admin_name, password=self.admin_pw) + self.assertEqual( + self.authorize(username=self.admin_name, password=self.admin_pw), + 201, 'Check database, admin account not found') def test_add_and_fetch_album(self): record = dict(name='my album', sizes=[240, 1024]) diff --git a/tests/test_base.py b/tests/test_base.py index 0c250bc..64d0890 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -5,171 +5,56 @@ created 10-oct-2019 by richb@instantlinux.net """ -import base64 import fakeredis -from flask import g -import json -import jwt import os import pytest -from sqlalchemy.exc import IntegrityError import subprocess import tempfile import unittest -import unittest.mock -from apicrud import database -from apicrud.service_config import ServiceConfig -from apicrud.session_manager import SessionManager -from apicrud.media.storage import StorageAPI +from apicrud import database, initialize, state +from apicrud.test.base import TestBaseMixin, test_globals +import controllers +from main import application +import models -from main import application, setup_db -from models import Account, Category, Contact, Person - -global_fixture = {} unittest.util._MAX_LENGTH = 2000 -class TestBase(unittest.TestCase): +class TestBase(TestBaseMixin, unittest.TestCase): @pytest.fixture(scope='session', autouse=True) def initial_setup(self): - if not global_fixture: - global_fixture['app'] = application.app - global_fixture['dbfile'] = tempfile.mkstemp(prefix='_db')[1] - subprocess.Popen(['cp', '.proto.sqlite', global_fixture['dbfile']]) + if not test_globals: + test_globals['app'] = application.app + test_globals['dbfile'] = tempfile.mkstemp(prefix='_db')[1] db_url = os.environ.get( - 'DB_URL', 'sqlite:///%s' % global_fixture['dbfile']) - global_fixture['config'] = ServiceConfig( - db_url=db_url, db_seed_file=os.path.join(os.path.dirname( - __file__), 'data', 'db_fixture.yaml')).config - global_fixture['flask'] = global_fixture['app'].test_client() - global_fixture['redis'] = fakeredis.FakeStrictRedis( + 'DB_URL', 'sqlite:///%s' % test_globals['dbfile']) + subprocess.Popen(['cp', '.proto.sqlite', test_globals['dbfile']]) + path = os.path.join(os.path.dirname( + os.path.abspath(__file__)), '..', 'media') + db_seed_file = os.path.join(path, '..', 'tests', 'data', + 'db_fixture.yaml') + test_globals['redis'] = redis_conn = fakeredis.FakeStrictRedis( server=fakeredis.FakeServer()) - redis_conn = global_fixture['redis'] - unittest.mock.patch( - 'apicrud.service_registry.ServiceRegistry.update').start() - setup_db(db_url=db_url, redis_conn=redis_conn) + initialize.app( + application, controllers, models, path, + db_seed_file=db_seed_file, db_url=db_url, + redis_conn=redis_conn) db_session = database.get_session(db_url=db_url) database.seed_new_db(db_session) db_session.remove() - yield global_fixture + self.baseSetup() + yield test_globals try: if os.environ.get('DBCLEAN', None) != '0': - os.remove(global_fixture['dbfile']) + os.remove(test_globals['dbfile']) except FileNotFoundError: + # TODO this is getting called for each test class + # (thought scope='session' would invoke only once) pass @classmethod - def setUpClass(self): - self.app = global_fixture['app'] - self.config = global_fixture['config'] - self.flask = global_fixture['flask'] - self.redis = global_fixture['redis'] - self.maxDiff = None - - self.base_url = self.config.BASE_URL - self.theme_id = 'x-05a720bf' - self.credentials = {} - self.authuser = None - self.settings_id = 'x-75023275' - self.location_id = 'x-67673434' - self.default_storage_id = 'x-RwgSVd5G' - - self.username = 'testr' - self.password = 't0ps3crEt' - self.access_key_id = 'AKIAP0XS5P8E4XXX1234' - self.account_id = 'x-TpP43xS9' - self.bucket = 'apicrud-test' - self.cat_id = 'x-Jiqag482' - self.contact_id = 'x-E97yKy73' - self.test_uid = 'u-B0miQweE' - self.test_person_name = 'Conclave Tester' - self.test_email = 'testuser@test.conclave.events' - db_session = database.get_session(db_url=self.config.DB_URL) - record = Person(id=self.test_uid, name=self.test_person_name, - identity=self.test_email, privacy='public', - status='active') - db_session.add(record) - record = Contact(id=self.contact_id, uid=self.test_uid, - type='email', info=self.test_email, - privacy='public', status='active') - db_session.add(record) - record = Account(id=self.account_id, name=self.username, - uid=self.test_uid, status='active', - settings_id=self.settings_id, - password=('$5$rounds=535000$HGVkMUlpoWPlgbsN$BqsTg' - 'mTmiUXaSLoaiQgEY5IWxNLrA3Y1D2qlzm57hz/')) - db_session.add(record) - record = Category(id=self.cat_id, uid=self.test_uid, - name='default') - db_session.add(record) - - self.admin_name = 'testadmin' - self.admin_pw = 'opens3crEt' - self.adm_account_id = 'x-6hj1BXEH' - self.adm_contact_id = 'x-6b1AUgmt' - self.adm_contact_2 = 'x-P703gZkJ' - self.adm_cat_id = 'x-1xDjMu6M' - self.admin_uid = 'u-tMY1b862' - self.adm_person_name = 'Test Admin' - self.admin_email = 'admin@test.conclave.events' - record = Category(id=self.adm_cat_id, uid=self.admin_uid, - name='default') - db_session.add(record) - try: - db_session.commit() - except IntegrityError: - db_session.rollback() - StorageAPI(db_session=db_session, redis_conn=self.redis, - uid=self.test_uid) - db_session.remove() - - def authorize(self, username=None, password=None, new_session=False): - if not username: - username = self.username - password = self.password - if username not in self.credentials or new_session: - response = self.call_endpoint('/auth', 'post', data=dict( - username=username, password=password)) - if response.status_code != 201: - return response.status_code - tok = jwt.decode(response.get_json()['jwt_token'], - self.config.JWT_SECRET, algorithms=['HS256']) - self.credentials[username] = dict( - auth='Basic ' + base64.b64encode( - bytes(tok['sub'] + ':' + tok['jti'], 'utf-8') - ).decode('utf-8')) - self.authuser = username - return 201 - - def call_endpoint(self, endpoint, method, data=None): - """call an endpoint with flask.g context - - params: - endpoint(str): endpoint path (after base_url) - method(str): get, post, put etc - returns: - http response - """ - - with self.app.test_request_context(): - g.db = database.get_session() - g.session = SessionManager(self.config, redis_conn=self.redis) - headers = {} - if self.authuser: - headers['Authorization'] = self.credentials[ - self.authuser]['auth'] - headers['Accept'] = 'application/json' - headers['Content-Type'] = 'application/json' - if data: - response = getattr(self.flask, method)( - self.base_url + endpoint, data=json.dumps(data), - headers=headers, - content_type='application/json') - else: - response = getattr(self.flask, method)( - self.base_url + endpoint, - headers=headers) - g.db.remove() - return response + def setUpClass(cls): + cls.mock_messaging = state.func_send = unittest.mock.Mock() + cls.classSetup(cls)