auth unit tests & ruff visual fix

This commit is contained in:
2026-07-28 01:42:29 +03:00
parent 1d25b0edc3
commit 5522447b07
22 changed files with 411 additions and 54 deletions

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ Thumbs.db
#Примеры документов #Примеры документов
input/ input/
output/ output/
allure-results/

View File

@@ -1,7 +1,9 @@
VENV=source .venv/bin/activate; VENV=source .venv/bin/activate;
ALLURE=.venv/allure-2.44.0/bin/allure #linux&macos
#ALLURE=.venv\allure-2.44.0\bin\allure #Windows
.PHONY: .PHONY:
run, m_gen, m_up run, m_gen, m_up, allure, coverage
run: run:
${VENV} python3 main.py ${VENV} python3 main.py
@@ -9,3 +11,7 @@ m_gen:
${VENV} alembic revision --autogenerate ${VENV} alembic revision --autogenerate
m_up: m_up:
${VENV} alembic upgrade head ${VENV} alembic upgrade head
allure:
${VENV} ${ALLURE} generate tests/allure-results/reports --single-file -o tests/allure-results/html --clean
coverage:
${VENV} pytest --cov=src tests/

170
poetry.lock generated
View File

@@ -20,6 +20,38 @@ typing-extensions = ">=4.12"
[package.extras] [package.extras]
tz = ["tzdata"] tz = ["tzdata"]
[[package]]
name = "allure-pytest"
version = "2.16.0"
description = "Allure pytest integration"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "allure_pytest-2.16.0-py3-none-any.whl", hash = "sha256:e5035a3b1f541b0c2ee566822df6fec20e0628b8536780e5bdfe39060ac0c71b"},
{file = "allure_pytest-2.16.0.tar.gz", hash = "sha256:3cc883595b1ce4280b0b9a5fdaa0ce3bb5cdbaa1b43c7269d8b82e825e6e107c"},
]
[package.dependencies]
allure-python-commons = "2.16.0"
pytest = ">=4.5.0"
[[package]]
name = "allure-python-commons"
version = "2.16.0"
description = "Contains the API for end users as well as helper functions and classes to build Allure adapters for Python test frameworks"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "allure_python_commons-2.16.0-py3-none-any.whl", hash = "sha256:6d42a500078aca8a2e68823075c1ffc2396987bb268d62b19af82390b205ce88"},
{file = "allure_python_commons-2.16.0.tar.gz", hash = "sha256:ecdc92bafea074bab96b5f2c4eb3100825340188f5aece608ae80eced709b36f"},
]
[package.dependencies]
attrs = ">=16.0.0"
pluggy = ">=0.4.0"
[[package]] [[package]]
name = "annotated-doc" name = "annotated-doc"
version = "0.0.4" version = "0.0.4"
@@ -78,6 +110,18 @@ files = [
astroid = ["astroid (>=2,<5)"] astroid = ["astroid (>=2,<5)"]
test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"]
[[package]]
name = "attrs"
version = "26.1.0"
description = "Classes Without Boilerplate"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"},
{file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"},
]
[[package]] [[package]]
name = "bcrypt" name = "bcrypt"
version = "5.0.0" version = "5.0.0"
@@ -298,6 +342,110 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
] ]
[[package]]
name = "coverage"
version = "7.15.2"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "coverage-7.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d"},
{file = "coverage-7.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846"},
{file = "coverage-7.15.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09f5c6ec5901f667bd97dd140b5b9a2586b10efec66f46fb1e6d8135f8b95bdf"},
{file = "coverage-7.15.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d16e3a7104ea84f03e614611b3edbf6fb6892554b3ab0fe7fbb3f2b2ef04376"},
{file = "coverage-7.15.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d46e62cb35d91e6e2589fda6d28074426b0e276422b5d2ebef2c6b11dc60dbfd"},
{file = "coverage-7.15.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dfd3db045e95960ae3683059571e597fda7cc610106a8916f77c5839048c1deb"},
{file = "coverage-7.15.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:affd532502d34c0472d0cdb181325c89f1d2c44992fef0c17e88e7b1576259a1"},
{file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d17d7512151fedfcc64c1821a8977fc9be0dbf495754669afcab7b57abc98ae9"},
{file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e26ff680768b8095e8874aabe0e9d3a47a2a9f176a8340d05f8604c56457c23a"},
{file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e8f27131dc7cd53de2c137dd207b3720919320b3c20d499dc30aa9ee6173287"},
{file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:728a33676d4c3f0db977990a4bd421dcaa3be3e53b5b6273036fff6666008e89"},
{file = "coverage-7.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29c052f7c83ccfcc5c577eaae025d2e4a9bb80daf03c0ac31c996e83b000ce88"},
{file = "coverage-7.15.2-cp310-cp310-win32.whl", hash = "sha256:1268ac8fb9ddcd783d3948dbabaf80a5d53bfdaa0575e873e2139a692f797443"},
{file = "coverage-7.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:9f4432898c4bf2fba0435bbe35dd4437d7264565e5a88a21f5b49d8662a6b629"},
{file = "coverage-7.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036"},
{file = "coverage-7.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660"},
{file = "coverage-7.15.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589"},
{file = "coverage-7.15.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee"},
{file = "coverage-7.15.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0"},
{file = "coverage-7.15.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487"},
{file = "coverage-7.15.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f"},
{file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1"},
{file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5"},
{file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0"},
{file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad"},
{file = "coverage-7.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db"},
{file = "coverage-7.15.2-cp311-cp311-win32.whl", hash = "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9"},
{file = "coverage-7.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688"},
{file = "coverage-7.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934"},
{file = "coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9"},
{file = "coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73"},
{file = "coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d"},
{file = "coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b"},
{file = "coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296"},
{file = "coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6"},
{file = "coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098"},
{file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a"},
{file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b"},
{file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2"},
{file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440"},
{file = "coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e"},
{file = "coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd"},
{file = "coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40"},
{file = "coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3"},
{file = "coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8"},
{file = "coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1"},
{file = "coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578"},
{file = "coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1"},
{file = "coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6"},
{file = "coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7"},
{file = "coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d"},
{file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026"},
{file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa"},
{file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d"},
{file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b"},
{file = "coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188"},
{file = "coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050"},
{file = "coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c"},
{file = "coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b"},
{file = "coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a"},
{file = "coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2"},
{file = "coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138"},
{file = "coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f"},
{file = "coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984"},
{file = "coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7"},
{file = "coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3"},
{file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee"},
{file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1"},
{file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a"},
{file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145"},
{file = "coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446"},
{file = "coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243"},
{file = "coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc"},
{file = "coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635"},
{file = "coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be"},
{file = "coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072"},
{file = "coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328"},
{file = "coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a"},
{file = "coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0"},
{file = "coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5"},
{file = "coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743"},
{file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c"},
{file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071"},
{file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a"},
{file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d"},
{file = "coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c"},
{file = "coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688"},
{file = "coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199"},
{file = "coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658"},
{file = "coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c"},
{file = "coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d"},
]
[package.extras]
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
[[package]] [[package]]
name = "decorator" name = "decorator"
version = "5.3.1" version = "5.3.1"
@@ -1536,6 +1684,26 @@ pygments = ">=2.7.2"
[package.extras] [package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "7.1.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"},
{file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"},
]
[package.dependencies]
coverage = {version = ">=7.10.6", extras = ["toml"]}
pluggy = ">=1.2"
pytest = ">=7"
[package.extras]
testing = ["process-tests", "pytest-xdist", "virtualenv"]
[[package]] [[package]]
name = "python-dateutil" name = "python-dateutil"
version = "2.9.0.post0" version = "2.9.0.post0"
@@ -1965,4 +2133,4 @@ files = [
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.13" python-versions = ">=3.13"
content-hash = "22aa39f5ef7ad5d2415b44f3908af10f00430ba0b9be5071910d691fa9a9cb34" content-hash = "472523567b581bc4046c5b926ff842e7ade6b230dc96649cf6899f59841d38c1"

View File

@@ -22,7 +22,9 @@ dependencies = [
"python-jose (>=3.5.0,<4.0.0)", "python-jose (>=3.5.0,<4.0.0)",
"python-multipart (>=0.0.32,<0.0.33)", "python-multipart (>=0.0.32,<0.0.33)",
"ipython (>=9.15.0,<10.0.0)", "ipython (>=9.15.0,<10.0.0)",
"httpie (>=3.2.4,<4.0.0)" "httpie (>=3.2.4,<4.0.0)",
"pytest-cov (>=7.1.0,<8.0.0)",
"allure-pytest (>=2.16.0,<3.0.0)"
] ]

View File

@@ -0,0 +1,11 @@
[pytest]
addopts =
-l
--alluredir=tests/allure-results/reports/
testpaths =
tests
markers=
unit: unit tests
integra: integrations test
e2e: e2e tests
smoke: smoke tests

View File

@@ -1,16 +1,19 @@
from uuid import UUID from uuid import UUID
from src.models.database_models.model import engine, RefreshTokens
from sqlalchemy import and_, not_, select from sqlalchemy import and_, not_, select
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from src.models.database_models.model import RefreshTokens, engine
from src.models.pydantic_models.model import RefreshTokensOut from src.models.pydantic_models.model import RefreshTokensOut
class JwtCrudActions: class JwtCrudActions:
def __init__(self) -> None: def __init__(self) -> None:
self.Session=sessionmaker(bind=engine) self.Session=sessionmaker(bind=engine)
def get_token_by_user_id(self, user_id:UUID)->RefreshTokensOut|None: def get_token_by_user_id(self, user_id:UUID)->RefreshTokensOut|None:
with self.Session() as session: with self.Session() as session: # noqa: SIM117
with session.begin(): with session.begin():
query=select(RefreshTokens).where(and_(RefreshTokens.user_id==user_id, not_(RefreshTokens.is_revoked))) query=select(RefreshTokens).where(and_(RefreshTokens.user_id==user_id, not_(RefreshTokens.is_revoked)))
response=session.scalars(query).one_or_none() response=session.scalars(query).one_or_none()
@@ -19,7 +22,7 @@ class JwtCrudActions:
return RefreshTokensOut.model_validate(response) return RefreshTokensOut.model_validate(response)
def get_token_by_id(self, id:UUID)->RefreshTokensOut|None: def get_token_by_id(self, id:UUID)->RefreshTokensOut|None:
with self.Session() as session: with self.Session() as session: # noqa: SIM117
with session.begin(): with session.begin():
query=select(RefreshTokens).where(RefreshTokens.id==id) query=select(RefreshTokens).where(RefreshTokens.id==id)
response=session.scalars(query).one_or_none() response=session.scalars(query).one_or_none()
@@ -28,14 +31,14 @@ class JwtCrudActions:
return RefreshTokensOut.model_validate(response) return RefreshTokensOut.model_validate(response)
def create_token(self, data:dict)->None: def create_token(self, data:dict)->None:
with self.Session() as session: with self.Session() as session: # noqa: SIM117
with session.begin(): with session.begin():
new_token=RefreshTokens(**data) new_token=RefreshTokens(**data)
response=session.add(new_token) response=session.add(new_token)
return response return response
def update_token(self, old_jti:UUID, new_jti:UUID)->bool: def update_token(self, old_jti:UUID, new_jti:UUID)->bool:
with self.Session() as session: with self.Session() as session: # noqa: SIM117
with session.begin(): with session.begin():
query=select(RefreshTokens).where(RefreshTokens.id==old_jti) query=select(RefreshTokens).where(RefreshTokens.id==old_jti)
response=session.scalars(query).one() response=session.scalars(query).one()
@@ -44,7 +47,7 @@ class JwtCrudActions:
return True return True
def revoke_all(self, user_id:UUID)->bool: def revoke_all(self, user_id:UUID)->bool:
with self.Session() as session: with self.Session() as session: # noqa: SIM117
with session.begin(): with session.begin():
query=select(RefreshTokens).where(RefreshTokens.user_id==user_id) query=select(RefreshTokens).where(RefreshTokens.user_id==user_id)
response=session.scalars(query).all() response=session.scalars(query).all()
@@ -53,7 +56,7 @@ class JwtCrudActions:
return True return True
def logout(self,id:UUID)->bool: def logout(self,id:UUID)->bool:
with self.Session() as session: with self.Session() as session: # noqa: SIM117
with session.begin(): with session.begin():
query=select(RefreshTokens).where(RefreshTokens.id == id) query=select(RefreshTokens).where(RefreshTokens.id == id)
response=session.scalars(query).one_or_none() response=session.scalars(query).one_or_none()

View File

@@ -1,14 +1,18 @@
from uuid import UUID
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import sessionmaker
from src.models.database_models.model import User, engine from src.models.database_models.model import User, engine
from src.models.pydantic_models.model import UserOutDB from src.models.pydantic_models.model import UserOutDB
from sqlalchemy.orm import sessionmaker
from uuid import UUID
class UsersCrudActions: class UsersCrudActions:
def __init__(self) -> None: def __init__(self) -> None:
self.Session=sessionmaker(bind=engine) self.Session=sessionmaker(bind=engine)
def get_user_by_email(self, email:str)->UserOutDB|None: def get_user_by_email(self, email:str)->UserOutDB|None:
with self.Session() as session: with self.Session() as session: # noqa: SIM117
with session.begin(): with session.begin():
query=select(User).where(User.email==email) query=select(User).where(User.email==email)
response=session.scalars(query).one_or_none() response=session.scalars(query).one_or_none()
@@ -17,7 +21,7 @@ class UsersCrudActions:
return UserOutDB.model_validate(response) return UserOutDB.model_validate(response)
def get_user_by_id(self, id:UUID)->UserOutDB|None: def get_user_by_id(self, id:UUID)->UserOutDB|None:
with self.Session() as session: with self.Session() as session: # noqa: SIM117
with session.begin(): with session.begin():
query=select(User).where(User.id==id) query=select(User).where(User.id==id)
response=session.scalars(query).one_or_none() response=session.scalars(query).one_or_none()

View File

@@ -1,5 +1,6 @@
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
class Base(BaseSettings): class Base(BaseSettings):
pass pass

View File

@@ -1,7 +1,10 @@
from src.models.database_models.model import Model from uuid import UUID, uuid1
from sqlalchemy import String from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from uuid import UUID, uuid1
from src.models.database_models.model import Model
class AccountantSettings(Model): class AccountantSettings(Model):
__tablename__="accountant_settings" __tablename__="accountant_settings"

View File

@@ -1,9 +1,11 @@
from datetime import datetime from datetime import datetime
from sqlalchemy import TIMESTAMP, String, func, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.database_models.model import Model
from uuid import UUID, uuid1 from uuid import UUID, uuid1
from sqlalchemy import TIMESTAMP, ForeignKey, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.database_models.model import Model
class Stored(Model): class Stored(Model):
__tablename__="reports" __tablename__="reports"

View File

@@ -1,8 +1,11 @@
from decimal import Decimal from decimal import Decimal
from src.models.database_models.model import Model
from sqlalchemy import Boolean, ForeignKey, Integer, Numeric, String from sqlalchemy import Boolean, ForeignKey, Integer, Numeric, String
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.database_models.model import Model
class Nomenclature(Model): class Nomenclature(Model):
__tablename__="goods" __tablename__="goods"

View File

@@ -1,7 +1,20 @@
from sqlalchemy import TIMESTAMP, Table, create_engine, String, Boolean, MetaData, Column, ForeignKey, func, Uuid
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, relationship
from uuid import UUID, uuid4
from datetime import datetime from datetime import datetime
from uuid import UUID, uuid4
from sqlalchemy import (
TIMESTAMP,
Boolean,
Column,
ForeignKey,
MetaData,
String,
Table,
Uuid,
create_engine,
func,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
engine = create_engine("sqlite:///DB/database.db", echo=True) engine = create_engine("sqlite:///DB/database.db", echo=True)
'''remember as a boilerplate, or just cp/pst''' '''remember as a boilerplate, or just cp/pst'''

View File

@@ -1,7 +1,11 @@
from typing import Annotated from typing import Annotated
from src.models.pydantic_models.model import Base
from pydantic import Field
from uuid import UUID from uuid import UUID
from pydantic import Field
from src.models.pydantic_models.model import Base
class AccountantCreate(Base): class AccountantCreate(Base):
name:Annotated[str, Field(...,max_length=64,description="name of the accountant setting")] name:Annotated[str, Field(...,max_length=64,description="name of the accountant setting")]

View File

@@ -1,9 +1,12 @@
from datetime import datetime from datetime import datetime
from typing import Annotated from typing import Annotated
from uuid import UUID from uuid import UUID
from src.models.pydantic_models.model import Base
from pydantic import Field from pydantic import Field
from src.models.pydantic_models.model import Base
class ReportCreate(Base): class ReportCreate(Base):
filename:Annotated[str,Field(..., min_length=2, max_length=255, description="name of the report")] filename:Annotated[str,Field(..., min_length=2, max_length=255, description="name of the report")]
doc_date:Annotated[datetime, Field(..., description="ts of the report")] doc_date:Annotated[datetime, Field(..., description="ts of the report")]

View File

@@ -1,8 +1,11 @@
from decimal import Decimal from decimal import Decimal
from src.models.pydantic_models.model import Base
from pydantic import Field
from typing import Annotated from typing import Annotated
from pydantic import Field
from src.models.pydantic_models.model import Base
class NomenclatureCreate(Base): class NomenclatureCreate(Base):
article:Annotated[str, Field(...,min_length=5,max_length=16, description="name of the article")] article:Annotated[str, Field(...,min_length=5,max_length=16, description="name of the article")]

View File

@@ -1,8 +1,9 @@
from datetime import datetime from datetime import datetime
from typing import Annotated from typing import Annotated
from pydantic import BaseModel, EmailStr, Field
from uuid import UUID from uuid import UUID
from pydantic import BaseModel, EmailStr, Field
class Base(BaseModel): class Base(BaseModel):
model_config = {"from_attributes": True} model_config = {"from_attributes": True}

View File

@@ -1,13 +1,15 @@
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from uuid import UUID from uuid import UUID
from fastapi import Request from fastapi import Request
from .jwt import Jwt, Hashes
from src.database.users.crud import UsersCrudActions
from src.database.auth.refresh_tokens import JwtCrudActions from src.database.auth.refresh_tokens import JwtCrudActions
from src.database.users.crud import UsersCrudActions
from src.errors.http_errors.errors import Errors from src.errors.http_errors.errors import Errors
from src.models.pydantic_models.model import RefreshTokensCreate, UserOut
from src.models.configs_read.env import env_settings from src.models.configs_read.env import env_settings
from src.models.pydantic_models.model import RefreshTokensCreate, UserOut
from .jwt import Hashes, Jwt
class CurrentUser: class CurrentUser:
@@ -78,7 +80,7 @@ class CurrentUser:
token_hash=self.hash.token_to_hash(token), token_hash=self.hash.token_to_hash(token),
device_info=request.headers.get("user-agent", "unknown"), device_info=request.headers.get("user-agent", "unknown"),
ip_address=request.headers.get("x-forwarded-for", "").split(",")[0].strip() or (request.client.host if request.client else "unknown"), ip_address=request.headers.get("x-forwarded-for", "").split(",")[0].strip() or (request.client.host if request.client else "unknown"),
expires_at=datetime.now(timezone.utc)+timedelta(days=env_settings.REFRESH_TOKEN_EXPIRE_DAYS) expires_at=datetime.now(UTC)+timedelta(days=env_settings.REFRESH_TOKEN_EXPIRE_DAYS)
) )
self.jwt_db_actions.create_token(RefreshTokensCreate.model_dump(token_record)) self.jwt_db_actions.create_token(RefreshTokensCreate.model_dump(token_record))
@@ -113,8 +115,8 @@ class CurrentUser:
'''sqlite constraints about timezone''' '''sqlite constraints about timezone'''
expires_at=old_record.expires_at expires_at=old_record.expires_at
if expires_at.tzinfo is None: if expires_at.tzinfo is None:
expires_at = expires_at.replace(tzinfo=timezone.utc) expires_at = expires_at.replace(tzinfo=UTC)
if expires_at<datetime.now(timezone.utc): if expires_at<datetime.now(UTC):
raise self.error.credentials_error(detail="Token expired") raise self.error.credentials_error(detail="Token expired")
'''user check''' '''user check'''
@@ -142,7 +144,7 @@ class CurrentUser:
token_hash=self.hash.token_to_hash(new_refresh_token), token_hash=self.hash.token_to_hash(new_refresh_token),
device_info=request.headers.get("user-agent", "unknown"), device_info=request.headers.get("user-agent", "unknown"),
ip_address=request.headers.get("x-forwarded-for", "").split(",")[0].strip() or (request.client.host if request.client else "unknown"), ip_address=request.headers.get("x-forwarded-for", "").split(",")[0].strip() or (request.client.host if request.client else "unknown"),
expires_at=datetime.now(timezone.utc)+timedelta(days=env_settings.REFRESH_TOKEN_EXPIRE_DAYS), expires_at=datetime.now(UTC)+timedelta(days=env_settings.REFRESH_TOKEN_EXPIRE_DAYS),
) )
self.jwt_db_actions.create_token(RefreshTokensCreate.model_dump(new_token_record)) self.jwt_db_actions.create_token(RefreshTokensCreate.model_dump(new_token_record))

View File

@@ -1,10 +1,12 @@
from jose import JWTError, jwt
import bcrypt
from src.errors.http_errors.errors import Errors
from datetime import datetime, timedelta, timezone
from src.models.configs_read.env import env_settings
from uuid import uuid4
import hashlib import hashlib
from datetime import UTC, datetime, timedelta
from uuid import uuid4
import bcrypt
from jose import JWTError, jwt
from src.errors.http_errors.errors import Errors
from src.models.configs_read.env import env_settings
'''Hash/Check hash''' '''Hash/Check hash'''
class Hashes: class Hashes:
@@ -32,7 +34,7 @@ class Jwt:
def create_access_token(self, data:dict)->str: def create_access_token(self, data:dict)->str:
user_info=data.copy() user_info=data.copy()
user_info.update({"exp": datetime.now(timezone.utc)+timedelta(minutes=env_settings.ACCESS_TOKEN_EXPIRE_MINUTES), user_info.update({"exp": datetime.now(UTC)+timedelta(minutes=env_settings.ACCESS_TOKEN_EXPIRE_MINUTES),
"token_type":"access"}) "token_type":"access"})
return jwt.encode(user_info, env_settings.SECRET_KEY, env_settings.ALGORITHM) return jwt.encode(user_info, env_settings.SECRET_KEY, env_settings.ALGORITHM)
@@ -41,7 +43,7 @@ class Jwt:
user_info=data.copy() user_info=data.copy()
jti=str(uuid4()) jti=str(uuid4())
user_info.update({"exp":datetime.now(timezone.utc)+timedelta(days=env_settings.REFRESH_TOKEN_EXPIRE_DAYS), user_info.update({"exp":datetime.now(UTC)+timedelta(days=env_settings.REFRESH_TOKEN_EXPIRE_DAYS),
"token_type":"refresh", "token_type":"refresh",
"jti":jti "jti":jti
}) })
@@ -53,9 +55,9 @@ class Jwt:
def jwt_decode(self, token:str)->dict: def jwt_decode(self, token:str)->dict:
try: try:
payload=jwt.decode(token, env_settings.SECRET_KEY, algorithms=[env_settings.ALGORITHM]) payload=jwt.decode(token, env_settings.SECRET_KEY, algorithms=[env_settings.ALGORITHM], options={"require_exp": True} )
if (payload.get("sub")) is None: if not (payload.get("sub")) or not (payload.get("token_type")):
raise self.error.credentials_error(detail="Jwt token is incorrect") raise self.error.credentials_error(detail="Jwt token is incorrect")
except JWTError as e: except JWTError as e:

View File

@@ -1,15 +1,15 @@
from fastapi import APIRouter, Depends, Request, Response, Cookie from fastapi import APIRouter, Cookie, Depends, Request, Response
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from src.models.configs_read.env import env_settings from src.models.configs_read.env import env_settings
from src.models.pydantic_models.model import UserOut from src.models.pydantic_models.model import UserOut
from src.service.auth.auth import auth from src.service.auth.auth import auth
router=APIRouter(prefix="/protected") router=APIRouter(prefix="/protected")
oauth2_schema=OAuth2PasswordBearer(tokenUrl="/protected/token", refreshUrl="/protected/refresh") oauth2_schema=OAuth2PasswordBearer(tokenUrl="/protected/token", refreshUrl="/protected/refresh")
@router.post("/token") @router.post("/token")
async def get_access_token(request: Request,response:Response, form_data:OAuth2PasswordRequestForm=Depends())->dict: async def get_access_token(request: Request,response:Response, form_data:OAuth2PasswordRequestForm=Depends())->dict: # noqa: B008
access_token, refresh_token=auth.login(form_data_email=form_data.username, form_data_password=form_data.password, request=request) access_token, refresh_token=auth.login(form_data_email=form_data.username, form_data_password=form_data.password, request=request)
@@ -46,10 +46,10 @@ async def get_current_user(token:str = Depends(oauth2_schema)) -> UserOut:
@router.get("/logout") @router.get("/logout")
async def logout(response:Response,refresh_token: str = Cookie(),current_user:UserOut=Depends(get_current_user))->bool: async def logout(response:Response,refresh_token: str = Cookie(),current_user:UserOut=Depends(get_current_user))->bool: # noqa: B008
response.delete_cookie("refresh_token") response.delete_cookie("refresh_token")
return auth.logout(refresh_token) return auth.logout(refresh_token)
@router.get("") @router.get("")
async def protected(current_user:UserOut=Depends(get_current_user))->dict: async def protected(current_user:UserOut=Depends(get_current_user))->dict: # noqa: B008
return {"protected router": "Hello, this is a protected router"} return {"protected router": "Hello, this is a protected router"}

View File

@@ -1 +1 @@
'''unit, integr, e2e tests''' '''unit, integr, e2e tests'''

View File

@@ -0,0 +1,7 @@
import pytest
from src.service.auth.jwt import Jwt
@pytest.fixture
def jwt_service():
jwt_service=Jwt()
return jwt_service

118
tests/unit/test_auth.py Normal file
View File

@@ -0,0 +1,118 @@
from datetime import UTC, datetime, timedelta
import allure
import pytest
from fastapi import HTTPException
from jose import jwt
from src.models.configs_read.env import env_settings
@pytest.mark.unit
class TestAuth:
@pytest.mark.parametrize("data", [
({"sub": "123"}),
({"sub":""}),
({"":""})
])
def test_access_create_positive(self, jwt_service, data:dict):
with allure.step("create correct access token"):
token = jwt_service.create_access_token(data)
parts=token.split(".")
assert isinstance(token, str)
assert len(parts)==3
@pytest.mark.parametrize("data",[
("")
])
def test_access_create_negative(self, jwt_service, data:dict):
with allure.step("create invalid access token"),pytest.raises(AttributeError):
jwt_service.create_access_token(data)
@pytest.mark.parametrize("data", [
({"sub": "123"}),
({"sub":""}),
({"":""})
])
def test_refresh_create_positive(self, jwt_service, data:dict):
with allure.step("create correct access token"):
token = jwt_service.create_refresh_token(data)
parts=token[0].split(".")
assert isinstance(token[0], str)
assert isinstance(token[1], str)
assert len(parts)==3
@pytest.mark.parametrize("data",[
("")
])
def test_refresh_create_negative(self, jwt_service, data):
with allure.step("create invalid access token"), pytest.raises(AttributeError):
jwt_service.create_refresh_token(data)
@pytest.mark.parametrize("data", [
({"sub": "123", "exp":datetime.now(UTC)+timedelta(minutes=15), "token_type":"access"}),
])
def test_jwt_decode_positive(self, data, monkeypatch, jwt_service):
with allure.step("patch a create token function"):
def fake_create_access_token(data:dict)->str:
return jwt.encode(data, env_settings.SECRET_KEY, env_settings.ALGORITHM)
monkeypatch.setattr(jwt_service, "create_access_token", fake_create_access_token)
with allure.step("create and decode correct token"):
fake_token = jwt_service.create_access_token(data)
payload=jwt_service.jwt_decode(fake_token)
assert payload.get("sub")
assert payload.get("exp")
assert payload.get("token_type")
@pytest.mark.parametrize("data, expected_exception", [
({"sub": "123", "exp":datetime.now(UTC)-timedelta(minutes=15), "token_type":"access"}, HTTPException),
({"sub": "123", "exp":datetime.now(UTC)+timedelta(minutes=15)}, HTTPException),
({"sub": "123", "token_type":"access"}, HTTPException),
({}, HTTPException),
("", AttributeError)
])
def test_jwt_decode_invalid(self,jwt_service, expected_exception, data, monkeypatch):
with allure.step("patch a create token function"):
def fake_create_access_token(data:dict)->str:
return jwt.encode(data, env_settings.SECRET_KEY, env_settings.ALGORITHM)
monkeypatch.setattr(jwt_service, "create_access_token", fake_create_access_token)
with allure.step("create and decode invalid token"), pytest.raises(expected_exception):
fake_token=jwt_service.create_access_token(data)
jwt_service.jwt_decode(fake_token)
@pytest.mark.parametrize("time, key, algorithm", [
(15, "wrong_key", "HS256"),
(-15, "correct_key", "HS256"),
(15, "correct_key", "HS512"),
])
def test_jwt_decode_wrong_env(self, monkeypatch, time:int, key:str, algorithm:str, jwt_service):
with allure.step("patch a create token function"):
def fake_create_access_token(data:dict, key:str, algorithm:str)->str:
data.update({"exp":datetime.now(UTC)+timedelta(minutes=time)})
return jwt.encode(data, key, algorithm)
monkeypatch.setattr(jwt_service, "create_access_token", fake_create_access_token)
with allure.step("create and decode token with wrong data inside"), pytest.raises(HTTPException):
fake_token=jwt_service.create_access_token({"sub": "123", "token_type":"access"}, key, algorithm)
jwt_service.jwt_decode(fake_token)