From ea9e4120f5dc2af9b51d60257ea581ae632d3d06 Mon Sep 17 00:00:00 2001 From: mellowCaribou Date: Sun, 5 Oct 2025 11:21:47 +0300 Subject: [PATCH 1/8] Refactor management tests for async support Updated management test cases to use IsolatedAsyncioTestCase and async test methods. Added async_mode support. Created testing utils to unify the mocking and asserting between async and sync modes. --- .gitleaksignore | 369 +++- pyproject.toml | 3 + requirements.txt | 389 ++-- tests/common.py | 6 +- tests/management/test_access_key.py | 175 +- tests/management/test_audit.py | 51 +- tests/management/test_authz.py | 331 ++-- tests/management/test_fga.py | 152 +- tests/management/test_flow.py | 116 +- tests/management/test_group.py | 75 +- tests/management/test_jwt.py | 121 +- tests/management/test_outbound_application.py | 644 ++++--- tests/management/test_permission.py | 87 +- tests/management/test_project.py | 133 +- tests/management/test_role.py | 117 +- tests/management/test_sso_application.py | 329 ++-- tests/management/test_sso_settings.py | 460 ++--- tests/management/test_tenant.py | 165 +- tests/management/test_user.py | 1589 +++++++++-------- tests/test_auth.py | 437 +++-- tests/test_descope_client.py | 526 +++--- tests/test_enchantedlink.py | 264 +-- tests/test_flask.py | 50 +- tests/test_magiclink.py | 438 +++-- tests/test_oauth.py | 96 +- tests/test_otp.py | 526 +++--- tests/test_password.py | 515 +++--- tests/test_saml.py | 115 +- tests/test_sso.py | 101 +- tests/test_totp.py | 158 +- tests/test_webauthn.py | 384 ++-- tests/testutils.py | 71 + 32 files changed, 5291 insertions(+), 3702 deletions(-) diff --git a/.gitleaksignore b/.gitleaksignore index 90de41c16..3442da66d 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -1,115 +1,330 @@ -04146e7a83d6212e407c5a46008324d646878fc1:tests/test_auth.py:jwt:257 +3ce967512fed2bae5e89dc9ff973e67ff9bc3084:README.md:hashicorp-tf-password:248 +# false positive | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_oauth.py:jwt:204 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:99 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:194 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:403 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:516 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_sso.py:jwt:228 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_totp.py:jwt:140 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_totp.py:jwt:309 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:211 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:419 +# key for testing purposes | lioriE | 2025-09-15 + +7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:654 +# key for testing purposes | lioriE | 2025-09-15 + +3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_totp.py:jwt:132 +# key for testing purposes | lioriE | 2025-09-15 + +3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_totp.py:jwt:289 +# key for testing purposes | lioriE | 2025-09-15 + +3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:144 +# key for testing purposes | lioriE | 2025-09-15 + +3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:312 +# key for testing purposes | lioriE | 2025-09-15 + +3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:482 +# key for testing purposes | lioriE | 2025-09-15 + +cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_oauth.py:jwt:217 +# key for testing purposes | lioriE | 2025-09-15 + +cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:104 +# key for testing purposes | lioriE | 2025-09-15 + +cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:205 +# key for testing purposes | lioriE | 2025-09-15 + +cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:426 +# key for testing purposes | lioriE | 2025-09-15 + +cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:547 +# key for testing purposes | lioriE | 2025-09-15 + +cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_sso.py:jwt:238 +# key for testing purposes | lioriE | 2025-09-15 + +e5d19171d5d9e188f1d99369b408388c1032583f:tests/test_descope_client.py:jwt:700 +# key for testing purposes | lioriE | 2025-09-15 + +bc5a7d8928ba27f9eeb2e0e5ddfe925eb5fda5a7:tests/test_sso.py:jwt:128 +# key for testing purposes | lioriE | 2025-09-15 + 1c0c3ca7205fa8fa481b2bc90933d7d1f0400253:tests/test_descope_client.py:jwt:563 +# key for testing purposes | lioriE | 2025-09-15 + 1c0c3ca7205fa8fa481b2bc90933d7d1f0400253:tests/test_descope_client.py:jwt:571 +# key for testing purposes | lioriE | 2025-09-15 + +a753d819979a4864cf4df0226fa9c9a5d06424c9:tests/test_password.py:jwt:399 +# key for testing purposes | lioriE | 2025-09-15 + +f3ec873c83a7067a1226d8b712b756b1b599fb3b:tests/test_descope_client.py:jwt:519 +# key for testing purposes | lioriE | 2025-09-15 + +e7f5ad4253ad82236a5cff5f8c06878bfb190b00:tests/test_descope_client.py:jwt:171 +# key for testing purposes | lioriE | 2025-09-15 + +e7f5ad4253ad82236a5cff5f8c06878bfb190b00:tests/test_descope_client.py:jwt:185 +# key for testing purposes | lioriE | 2025-09-15 + +e7f5ad4253ad82236a5cff5f8c06878bfb190b00:tests/test_descope_client.py:jwt:197 +# key for testing purposes | lioriE | 2025-09-15 + +3ce967512fed2bae5e89dc9ff973e67ff9bc3084:tests/test_password.py:jwt:87 +# key for testing purposes | lioriE | 2025-09-15 + 3ce967512fed2bae5e89dc9ff973e67ff9bc3084:tests/test_password.py:jwt:167 +# key for testing purposes | lioriE | 2025-09-15 + 3ce967512fed2bae5e89dc9ff973e67ff9bc3084:tests/test_password.py:jwt:313 -3ce967512fed2bae5e89dc9ff973e67ff9bc3084:tests/test_password.py:jwt:87 +# key for testing purposes | lioriE | 2025-09-15 + +84adfdb469398f4b2263d0e57f8a16389227629d:tests/test_totp.py:jwt:106 +# key for testing purposes | lioriE | 2025-09-15 + +8e5937752bd25ae682e56f9f8e31ee537ea5479d:tests/test_auth.py:jwt:285 +# key for testing purposes | lioriE | 2025-09-15 + +8e5937752bd25ae682e56f9f8e31ee537ea5479d:tests/test_auth.py:jwt:312 +# key for testing purposes | lioriE | 2025-09-15 + +ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_oauth.py:jwt:137 +# key for testing purposes | lioriE | 2025-09-15 + +ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_totp.py:jwt:87 +# key for testing purposes | lioriE | 2025-09-15 + +ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_saml.py:jwt:131 +# key for testing purposes | lioriE | 2025-09-15 + +ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_webauthn.py:jwt:317 +# key for testing purposes | lioriE | 2025-09-15 + +ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_webauthn.py:jwt:483 +# key for testing purposes | lioriE | 2025-09-15 + +621149e12da145ea6c65ca5a0246ce5f93f9124e:tests/test_magiclink.py:jwt:253 +# key for testing purposes | lioriE | 2025-09-15 + +6d4b1f4dbcbef45e4a5bdce952d8bc2bf5a8dd72:tests/test_enchantedlink.py:jwt:246 +# key for testing purposes | lioriE | 2025-09-15 + +6d4b1f4dbcbef45e4a5bdce952d8bc2bf5a8dd72:tests/test_enchantedlink.py:jwt:261 +# key for testing purposes | lioriE | 2025-09-15 + +04146e7a83d6212e407c5a46008324d646878fc1:tests/test_auth.py:jwt:257 +# key for testing purposes | lioriE | 2025-09-15 + +71ba33c612675bd34fa7830c6b884f1f01efda9b:tests/test_oauth.py:jwt:102 +# key for testing purposes | lioriE | 2025-09-15 + +71ba33c612675bd34fa7830c6b884f1f01efda9b:tests/test_saml.py:jwt:84 +# key for testing purposes | lioriE | 2025-09-15 + +602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_magiclink.py:jwt:341 +# key for testing purposes | lioriE | 2025-09-15 + +602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_webauthn.py:jwt:127 +# key for testing purposes | lioriE | 2025-09-15 + +602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_webauthn.py:jwt:220 +# key for testing purposes | lioriE | 2025-09-15 + +602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_magiclink.py:jwt:356 +# key for testing purposes | lioriE | 2025-09-15 + +602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_webauthn.py:jwt:326 +# key for testing purposes | lioriE | 2025-09-15 + +602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_otp.py:jwt:246 +# key for testing purposes | lioriE | 2025-09-15 + 602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_descope_client.py:jwt:122 +# key for testing purposes | lioriE | 2025-09-15 + 602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_descope_client.py:jwt:178 +# key for testing purposes | lioriE | 2025-09-15 + 602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_descope_client.py:jwt:248 +# key for testing purposes | lioriE | 2025-09-15 + 602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_descope_client.py:jwt:249 +# key for testing purposes | lioriE | 2025-09-15 + 602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_descope_client.py:jwt:250 +# key for testing purposes | lioriE | 2025-09-15 + 602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_descope_client.py:jwt:266 +# key for testing purposes | lioriE | 2025-09-15 + 602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_descope_client.py:jwt:267 -602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_magiclink.py:jwt:341 -602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_magiclink.py:jwt:356 -602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_otp.py:jwt:246 -602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_webauthn.py:jwt:127 -602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_webauthn.py:jwt:220 -602af9d1f4ce24799cfdcb06fc76eaedb39ba31c:tests/test_webauthn.py:jwt:326 -621149e12da145ea6c65ca5a0246ce5f93f9124e:tests/test_magiclink.py:jwt:253 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_exchanger.py:jwt:45 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_totp.py:jwt:85 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_totp.py:jwt:109 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_otp.py:jwt:245 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_magiclink.py:jwt:339 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_magiclink.py:jwt:351 +# key for testing purposes | lioriE | 2025-09-15 + 66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:606 +# key for testing purposes | lioriE | 2025-09-15 + 66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:607 +# key for testing purposes | lioriE | 2025-09-15 + 66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:632 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:648 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:649 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:723 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:724 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:742 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:779 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:780 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:781 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:796 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:797 -66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:814 -6d4b1f4dbcbef45e4a5bdce952d8bc2bf5a8dd72:tests/test_enchantedlink.py:jwt:246 -6d4b1f4dbcbef45e4a5bdce952d8bc2bf5a8dd72:tests/test_enchantedlink.py:jwt:261 -71ba33c612675bd34fa7830c6b884f1f01efda9b:tests/test_oauth.py:jwt:102 -71ba33c612675bd34fa7830c6b884f1f01efda9b:tests/test_saml.py:jwt:84 -84adfdb469398f4b2263d0e57f8a16389227629d:tests/test_totp.py:jwt:106 -8e5937752bd25ae682e56f9f8e31ee537ea5479d:tests/test_auth.py:jwt:285 -8e5937752bd25ae682e56f9f8e31ee537ea5479d:tests/test_auth.py:jwt:312 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:76 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:77 +# key for testing purposes | lioriE | 2025-09-15 + +910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:78 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:152 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:153 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:174 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:211 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:212 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:213 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:228 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:229 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:76 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:77 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_descope_client.py:jwt:78 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_exchanger.py:jwt:45 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_magiclink.py:jwt:339 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_magiclink.py:jwt:351 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_otp.py:jwt:245 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_totp.py:jwt:109 -910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_totp.py:jwt:85 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:648 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:649 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:723 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:724 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:742 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_webauthn.py:jwt:127 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_webauthn.py:jwt:212 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_webauthn.py:jwt:230 +# key for testing purposes | lioriE | 2025-09-15 + 910e8e7d14b8c53e566a06c55abfb04b5a16cb72:tests/test_webauthn.py:jwt:318 -a753d819979a4864cf4df0226fa9c9a5d06424c9:tests/test_password.py:jwt:399 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:779 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:780 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:781 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:796 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:797 +# key for testing purposes | lioriE | 2025-09-15 + +66ec16354a25b8d8d22de85ed823c5151fda0940:tests/test_auth.py:jwt:814 +# key for testing purposes | lioriE | 2025-09-15 + +ece761372c78a9ad8a57da5f6d13431d298a99db:tests/test_auth.py:jwt:562 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:385 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:402 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:403 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:404 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:478 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:479 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:497 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:534 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:535 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:549 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:550 +# key for testing purposes | lioriE | 2025-09-15 + a76d544894ddac33781df1f9d7e73de4200d177f:tests/test_auth.py:jwt:567 -ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_oauth.py:jwt:137 -ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_saml.py:jwt:131 -ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_totp.py:jwt:87 -ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_webauthn.py:jwt:317 -ad14b0225444f72e8c46527d10acfd19dd6e9352:tests/test_webauthn.py:jwt:483 -bc5a7d8928ba27f9eeb2e0e5ddfe925eb5fda5a7:tests/test_sso.py:jwt:128 -e5d19171d5d9e188f1d99369b408388c1032583f:tests/test_descope_client.py:jwt:700 -e7f5ad4253ad82236a5cff5f8c06878bfb190b00:tests/test_descope_client.py:jwt:171 -e7f5ad4253ad82236a5cff5f8c06878bfb190b00:tests/test_descope_client.py:jwt:185 -e7f5ad4253ad82236a5cff5f8c06878bfb190b00:tests/test_descope_client.py:jwt:197 -ece761372c78a9ad8a57da5f6d13431d298a99db:tests/test_auth.py:jwt:562 -f3ec873c83a7067a1226d8b712b756b1b599fb3b:tests/test_descope_client.py:jwt:519 -b6a2e217be5dceb6c85332d2e193619894d3a36e:README.md:generic-api-key:1349 -b6a2e217be5dceb6c85332d2e193619894d3a36e:README.md:generic-api-key:1372 -c751b1e9df5dcb6e6b3a62601174065ebf03144f:README.md:generic-api-key:1358 -c751b1e9df5dcb6e6b3a62601174065ebf03144f:README.md:generic-api-key:1381 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_oauth.py:jwt:204 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:99 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:194 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_sso.py:jwt:228 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:403 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_password.py:jwt:516 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_totp.py:jwt:140 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_totp.py:jwt:309 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:211 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:419 -7c7b8abe49e30e1aed5f72fb59353dc187c9375a:tests/test_webauthn.py:jwt:654 -3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_totp.py:jwt:132 -3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_totp.py:jwt:289 -3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:144 -3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:312 -3630de2df01d6c4fd7b0c6cf8c8fd835df220918:tests/test_webauthn.py:jwt:482 -cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:104 -cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_oauth.py:jwt:217 -cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:205 -cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:426 -cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_password.py:jwt:547 -cc88fb407157fa23a672e03ec07e26bdce6fe170:tests/test_sso.py:jwt:238 -3ce967512fed2bae5e89dc9ff973e67ff9bc3084:README.md:hashicorp-tf-password:248 -04146e7a83d6212e407c5a46008324d646878fc1:tests/test_auth.py:jwt:257 \ No newline at end of file +# key for testing purposes | lioriE | 2025-09-15 + diff --git a/pyproject.toml b/pyproject.toml index f7dd694b9..818051c8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,3 +89,6 @@ profile = "black" per-file-ignores = "__init__.py:F401" ignore = "E501,N818,W503" max-line-length = 120 + +[tool.pytest.ini_options] +asyncio_default_fixture_loop_scope = "function" diff --git a/requirements.txt b/requirements.txt index ab248ae47..12ae7c482 100644 --- a/requirements.txt +++ b/requirements.txt @@ -72,237 +72,176 @@ cffi==1.17.1 ; python_full_version >= "3.8.1" and python_version < "4.0" and pla --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b -charset-normalizer==3.4.3; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91 \ - --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \ - --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \ - --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \ - --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \ - --hash=sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07 \ - --hash=sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c \ - --hash=sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64 \ - --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ - --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \ - --hash=sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432 \ - --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ - --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ - --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ - --hash=sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae \ - --hash=sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19 \ - --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ - --hash=sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e \ - --hash=sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4 \ - --hash=sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7 \ - --hash=sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312 \ - --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ - --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ - --hash=sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c \ - --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \ - --hash=sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99 \ - --hash=sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b \ - --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ - --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \ - --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ - --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ - --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \ - --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \ - --hash=sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc \ - --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ - --hash=sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f \ - --hash=sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a \ - --hash=sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40 \ - --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ - --hash=sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849 \ - --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ - --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ - --hash=sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05 \ - --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ - --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \ - --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \ - --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \ - --hash=sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34 \ - --hash=sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9 \ - --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ - --hash=sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14 \ - --hash=sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30 \ - --hash=sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b \ - --hash=sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b \ - --hash=sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942 \ - --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ - --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ - --hash=sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b \ - --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ - --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \ - --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \ - --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \ - --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \ - --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \ - --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ - --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ - --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ - --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \ - --hash=sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca \ - --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ - --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \ - --hash=sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb \ - --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \ - --hash=sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557 \ - --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \ - --hash=sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7 \ - --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72 \ - --hash=sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c \ - --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 +charset-normalizer==3.4.2; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4 \ + --hash=sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45 \ + --hash=sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7 \ + --hash=sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0 \ + --hash=sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7 \ + --hash=sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d \ + --hash=sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d \ + --hash=sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0 \ + --hash=sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184 \ + --hash=sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db \ + --hash=sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b \ + --hash=sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64 \ + --hash=sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b \ + --hash=sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8 \ + --hash=sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff \ + --hash=sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344 \ + --hash=sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58 \ + --hash=sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e \ + --hash=sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471 \ + --hash=sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148 \ + --hash=sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a \ + --hash=sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836 \ + --hash=sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e \ + --hash=sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63 \ + --hash=sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c \ + --hash=sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1 \ + --hash=sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01 \ + --hash=sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366 \ + --hash=sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58 \ + --hash=sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5 \ + --hash=sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c \ + --hash=sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2 \ + --hash=sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a \ + --hash=sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597 \ + --hash=sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b \ + --hash=sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5 \ + --hash=sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb \ + --hash=sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f \ + --hash=sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0 \ + --hash=sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941 \ + --hash=sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0 \ + --hash=sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86 \ + --hash=sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7 \ + --hash=sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7 \ + --hash=sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455 \ + --hash=sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6 \ + --hash=sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4 \ + --hash=sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0 \ + --hash=sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3 \ + --hash=sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1 \ + --hash=sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6 \ + --hash=sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981 \ + --hash=sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c \ + --hash=sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980 \ + --hash=sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645 \ + --hash=sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7 \ + --hash=sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12 \ + --hash=sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa \ + --hash=sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd \ + --hash=sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef \ + --hash=sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f \ + --hash=sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2 \ + --hash=sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d \ + --hash=sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5 \ + --hash=sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02 \ + --hash=sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3 \ + --hash=sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd \ + --hash=sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e \ + --hash=sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214 \ + --hash=sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd \ + --hash=sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a \ + --hash=sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c \ + --hash=sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681 \ + --hash=sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba \ + --hash=sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f \ + --hash=sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a \ + --hash=sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28 \ + --hash=sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691 \ + --hash=sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82 \ + --hash=sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a \ + --hash=sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027 \ + --hash=sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7 \ + --hash=sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518 \ + --hash=sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf \ + --hash=sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b \ + --hash=sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9 \ + --hash=sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544 \ + --hash=sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da \ + --hash=sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509 \ + --hash=sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f \ + --hash=sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a \ + --hash=sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f click==8.2.1; python_full_version >= "3.8.1" and python_version < "4.0" \ --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b colorama==0.4.6 ; python_full_version >= "3.8.1" and python_version < "4.0" and platform_system == "Windows" \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 -cryptography==45.0.7; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34 \ - --hash=sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513 \ - --hash=sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5 \ - --hash=sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c \ - --hash=sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63 \ - --hash=sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130 \ - --hash=sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae \ - --hash=sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443 \ - --hash=sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59 \ - --hash=sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee \ - --hash=sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf \ - --hash=sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27 \ - --hash=sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde \ - --hash=sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971 \ - --hash=sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8 \ - --hash=sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339 \ - --hash=sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6 \ - --hash=sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90 \ - --hash=sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691 \ - --hash=sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3 \ - --hash=sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083 \ - --hash=sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6 \ - --hash=sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1 \ - --hash=sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3 \ - --hash=sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8 \ - --hash=sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2 \ - --hash=sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7 \ - --hash=sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141 \ - --hash=sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3 \ - --hash=sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9 \ - --hash=sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4 \ - --hash=sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4 \ - --hash=sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b \ - --hash=sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252 \ - --hash=sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17 \ - --hash=sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b \ - --hash=sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd -dnspython==2.8.0; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af \ - --hash=sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f -email-validator==2.3.0; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 \ - --hash=sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426 -Flask==3.1.2; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87 \ - --hash=sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c +cryptography==45.0.5; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7 \ + --hash=sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8 \ + --hash=sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463 \ + --hash=sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30 \ + --hash=sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f \ + --hash=sha256:206210d03c1193f4e1ff681d22885181d47efa1ab3018766a7b32a7b3d6e6afd \ + --hash=sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135 \ + --hash=sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d \ + --hash=sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57 \ + --hash=sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd \ + --hash=sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e \ + --hash=sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e \ + --hash=sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1 \ + --hash=sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0 \ + --hash=sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a \ + --hash=sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a \ + --hash=sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492 \ + --hash=sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e \ + --hash=sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e \ + --hash=sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97 \ + --hash=sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174 \ + --hash=sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9 \ + --hash=sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18 \ + --hash=sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0 \ + --hash=sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27 \ + --hash=sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63 \ + --hash=sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e \ + --hash=sha256:b9e38e0a83cd51e07f5a48ff9691cae95a79bea28fe4ded168a8e5c6c77e819d \ + --hash=sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6 \ + --hash=sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42 \ + --hash=sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097 \ + --hash=sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9 \ + --hash=sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d \ + --hash=sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f \ + --hash=sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0 \ + --hash=sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5 \ + --hash=sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8 +dnspython==2.7.0; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86 \ + --hash=sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1 +email-validator==2.2.0 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631 \ + --hash=sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7 +exceptiongroup==1.3.0 ; python_full_version >= "3.8.1" and python_version < "3.11" \ + --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \ + --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88 +h11==0.16.0 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 +httpcore==1.0.9 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 +httpx==0.27.2 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0 \ + --hash=sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2 idna==3.10 ; python_full_version >= "3.8.1" and python_version < "4.0" \ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 -importlib-metadata==8.7.0; python_full_version >= "3.8.1" and python_version < "3.10" \ - --hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \ - --hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd -itsdangerous==2.2.0 ; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \ - --hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173 -Jinja2==3.1.6; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ - --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 -liccheck==0.9.2 ; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:15cbedd042515945fe9d58b62e0a5af2f2a7795def216f163bb35b3016a16637 \ - --hash=sha256:bdc2190f8e95af3c8f9c19edb784ba7d41ecb2bf9189422eae6112bf84c08cd5 -MarkupSafe==3.0.2; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ - --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ - --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ - --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ - --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ - --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ - --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ - --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ - --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ - --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ - --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ - --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ - --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ - --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ - --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ - --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ - --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ - --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ - --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ - --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ - --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ - --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ - --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ - --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ - --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ - --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ - --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ - --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ - --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ - --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ - --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ - --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ - --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ - --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ - --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ - --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ - --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ - --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ - --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ - --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ - --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ - --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ - --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ - --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ - --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ - --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ - --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ - --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ - --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ - --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ - --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ - --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ - --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ - --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ - --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ - --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ - --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ - --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ - --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ - --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ - --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 -pycparser==2.23; python_full_version >= "3.8.1" and python_version < "4.0" and platform_python_implementation != "PyPy" \ - --hash=sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2 \ - --hash=sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 -PyJWT[crypto]==2.10.1; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953 \ - --hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb -requests==2.32.5; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ - --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf -semantic-version==2.10.0 ; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c \ - --hash=sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177 -toml==0.10.2 ; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f -urllib3==2.5.0; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ - --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc -Werkzeug==3.1.3; python_full_version >= "3.8.1" and python_version < "4.0" \ - --hash=sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e \ - --hash=sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746 -zipp==3.23.0; python_full_version >= "3.8.1" and python_version < "3.10" \ - --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \ - --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166 +pycparser==2.22 ; python_full_version >= "3.8.1" and python_version < "4.0" and platform_python_implementation != "PyPy" \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc +pyjwt==2.4.0 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf \ + --hash=sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba +sniffio==1.3.1 ; python_full_version >= "3.8.1" and python_version < "4.0" \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc +typing-extensions==4.13.2 ; python_full_version >= "3.8.1" and python_version < "3.9" \ + --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ + --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef +typing-extensions==4.14.0 ; python_version >= "3.9" and python_version < "3.13" \ + --hash=sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4 \ + --hash=sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af diff --git a/tests/common.py b/tests/common.py index 4c9f4345c..6ca1a38ea 100644 --- a/tests/common.py +++ b/tests/common.py @@ -25,8 +25,12 @@ def sdk_version(): } -class DescopeTest(unittest.TestCase): +class DescopeTest(unittest.IsolatedAsyncioTestCase): + async_test = False + def setUp(self) -> None: os.environ["DESCOPE_BASE_URI"] = ( DEFAULT_BASE_URL # Make sure tests always running against localhost ) + + self.async_test = os.environ.get("ASYNC_MODE", "False").lower() == "true" diff --git a/tests/management/test_access_key.py b/tests/management/test_access_key.py index ced57f793..24c49992d 100644 --- a/tests/management/test_access_key.py +++ b/tests/management/test_access_key.py @@ -4,9 +4,10 @@ from descope import AssociatedTenant, AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -25,42 +26,46 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_create(self): + async def test_create(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.access_key.create, - "key-name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.access_key.create( + "key-name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"key": {"id": "ak1"}, "cleartext": "abc"}""" ) mock_post.return_value = network_resp - resp = client.mgmt.access_key.create( - name="key-name", - expire_time=123456789, - key_tenants=[ - AssociatedTenant("tenant1"), - AssociatedTenant("tenant2", ["role1", "role2"]), - ], - user_id="userid", - custom_claims={"k1": "v1"}, - description="this is my access key", - permitted_ips=["10.0.0.1", "192.168.1.0/24"], + resp = await futu_await( + client.mgmt.access_key.create( + name="key-name", + expire_time=123456789, + key_tenants=[ + AssociatedTenant("tenant1"), + AssociatedTenant("tenant2", ["role1", "role2"]), + ], + user_id="userid", + custom_claims={"k1": "v1"}, + description="this is my access key", + permitted_ips=["10.0.0.1", "192.168.1.0/24"], + ) ) access_key = resp["key"] self.assertEqual(access_key["id"], "ak1") @@ -90,30 +95,32 @@ def test_create(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load(self): + async def test_load(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.access_key.load, - "key-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.access_key.load( + "key-id", + ) + ) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"key": {"id": "ak1"}}""") mock_get.return_value = network_resp - resp = client.mgmt.access_key.load("key-id") + resp = await futu_await(client.mgmt.access_key.load("key-id")) access_key = resp["key"] self.assertEqual(access_key["id"], "ak1") mock_get.assert_called_with( @@ -129,32 +136,36 @@ def test_load(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_search_all_users(self): + async def test_search_all_users(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.access_key.search_all_access_keys, - ["t1, t2"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.access_key.search_all_access_keys( + ["t1, t2"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"keys": [{"id": "ak1"}, {"id": "ak2"}]}""" ) mock_post.return_value = network_resp - resp = client.mgmt.access_key.search_all_access_keys(["t1, t2"]) + resp = await futu_await( + client.mgmt.access_key.search_all_access_keys(["t1, t2"]) + ) keys = resp["keys"] self.assertEqual(len(keys), 2) self.assertEqual(keys[0]["id"], "ak1") @@ -175,30 +186,34 @@ def test_search_all_users(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update(self): + async def test_update(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.access_key.update, - "key-id", - "new-name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.access_key.update( + "key-id", + "new-name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.access_key.update( - "key-id", name="new-name", description=None + await futu_await( + client.mgmt.access_key.update( + "key-id", name="new-name", description=None + ) ) ) mock_post.assert_called_with( @@ -219,27 +234,31 @@ def test_update(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_deactivate(self): + async def test_deactivate(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.access_key.deactivate, - "key-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.access_key.deactivate( + "key-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.access_key.deactivate("ak1")) + self.assertIsNone( + await futu_await(client.mgmt.access_key.deactivate("ak1")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.access_key_deactivate_path}", headers={ @@ -256,27 +275,29 @@ def test_deactivate(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_activate(self): + async def test_activate(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.access_key.activate, - "key-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.access_key.activate( + "key-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.access_key.activate("ak1")) + self.assertIsNone(await futu_await(client.mgmt.access_key.activate("ak1"))) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.access_key_activate_path}", headers={ @@ -293,27 +314,29 @@ def test_activate(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete(self): + async def test_delete(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.access_key.delete, - "key-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.access_key.delete( + "key-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.access_key.delete("ak1")) + self.assertIsNone(await futu_await(client.mgmt.access_key.delete("ak1"))) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.access_key_delete_path}", headers={ diff --git a/tests/management/test_audit.py b/tests/management/test_audit.py index 8cae98209..b7d2b8179 100644 --- a/tests/management/test_audit.py +++ b/tests/management/test_audit.py @@ -4,9 +4,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -25,25 +26,27 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_search(self): + async def test_search(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed search - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.audit.search, - "data", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.audit.search( + "data", + ) + ) # Test success search - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -65,7 +68,7 @@ def test_search(self): ] } mock_post.return_value = network_resp - resp = client.mgmt.audit.search() + resp = await futu_await(client.mgmt.audit.search()) audits = resp["audits"] self.assertEqual(len(audits), 2) self.assertEqual(audits[0]["loginIds"][0], "e1") @@ -83,34 +86,36 @@ def test_search(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_create_event(self): + async def test_create_event(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed search - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.audit.create_event, "a", "b", "c", "d" - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.audit.create_event("a", "b", "c", "d")) # Test success search - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = {} mock_post.return_value = network_resp - client.mgmt.audit.create_event( - action="pencil.created", - user_id="user-id", - actor_id="actor-id", - tenant_id="tenant-id", - type="info", - data={"some": "data"}, + await futu_await( + client.mgmt.audit.create_event( + action="pencil.created", + user_id="user-id", + actor_id="actor-id", + tenant_id="tenant-id", + type="info", + data={"some": "data"}, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.audit_create_event}", diff --git a/tests/management/test_authz.py b/tests/management/test_authz.py index 5f405502b..367fd2b56 100644 --- a/tests/management/test_authz.py +++ b/tests/management/test_authz.py @@ -2,9 +2,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -23,23 +24,27 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_save_schema(self): + async def test_save_schema(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed save_schema - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.save_schema, {}, True) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.save_schema({}, True)) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.authz.save_schema({"name": "kuku"}, True)) + self.assertIsNone( + await futu_await(client.mgmt.authz.save_schema({"name": "kuku"}, True)) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_schema_save}", headers={ @@ -54,23 +59,25 @@ def test_save_schema(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_schema(self): + async def test_delete_schema(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed delete_schema - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.delete_schema) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.delete_schema()) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.authz.delete_schema()) + self.assertIsNone(await futu_await(client.mgmt.authz.delete_schema())) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_schema_delete}", headers={ @@ -85,23 +92,26 @@ def test_delete_schema(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_schema(self): + async def test_load_schema(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed load_schema - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.load_schema) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.load_schema()) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.authz.load_schema()) + mock_post.return_value.json.return_value = {"schema": {"name": "test"}} + self.assertIsNotNone(await futu_await(client.mgmt.authz.load_schema())) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_schema_load}", headers={ @@ -116,24 +126,28 @@ def test_load_schema(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_save_namespace(self): + async def test_save_namespace(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed save_namespace - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.save_namespace, {}) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.save_namespace({})) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.authz.save_namespace({"name": "kuku"}, "old", "v1") + await futu_await( + client.mgmt.authz.save_namespace({"name": "kuku"}, "old", "v1") + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_ns_save}", @@ -153,23 +167,27 @@ def test_save_namespace(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_namespace(self): + async def test_delete_namespace(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed delete_namespace - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.delete_namespace, "a") + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.delete_namespace("a")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.authz.delete_namespace("a", "b")) + self.assertIsNone( + await futu_await(client.mgmt.authz.delete_namespace("a", "b")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_ns_delete}", headers={ @@ -184,27 +202,29 @@ def test_delete_namespace(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_save_relation_definition(self): + async def test_save_relation_definition(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed save_relation_definition - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.authz.save_relation_definition, {}, "a" - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.save_relation_definition({}, "a")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.authz.save_relation_definition( - {"name": "kuku"}, "a", "old", "v1" + await futu_await( + client.mgmt.authz.save_relation_definition( + {"name": "kuku"}, "a", "old", "v1" + ) ) ) mock_post.assert_called_with( @@ -226,26 +246,28 @@ def test_save_relation_definition(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_relation_definition(self): + async def test_delete_relation_definition(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed delete_relation_definition - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.authz.delete_relation_definition, "a", "b" - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.delete_relation_definition("a", "b")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.authz.delete_relation_definition("a", "b", "c") + await futu_await( + client.mgmt.authz.delete_relation_definition("a", "b", "c") + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_rd_delete}", @@ -261,32 +283,36 @@ def test_delete_relation_definition(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_create_relations(self): + async def test_create_relations(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed create_relations - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.create_relations, []) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.create_relations([])) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.authz.create_relations( - [ - { - "resource": "r", - "relationDefinition": "rd", - "namespace": "ns", - "target": "u", - } - ] + await futu_await( + client.mgmt.authz.create_relations( + [ + { + "resource": "r", + "relationDefinition": "rd", + "namespace": "ns", + "target": "u", + } + ] + ) ) ) mock_post.assert_called_with( @@ -312,32 +338,36 @@ def test_create_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_relations(self): + async def test_delete_relations(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed delete_relations - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.delete_relations, []) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.delete_relations([])) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.authz.delete_relations( - [ - { - "resource": "r", - "relationDefinition": "rd", - "namespace": "ns", - "target": "u", - } - ] + await futu_await( + client.mgmt.authz.delete_relations( + [ + { + "resource": "r", + "relationDefinition": "rd", + "namespace": "ns", + "target": "u", + } + ] + ) ) ) mock_post.assert_called_with( @@ -363,25 +393,29 @@ def test_delete_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_relations_for_resources(self): + async def test_delete_relations_for_resources(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed delete_relations_for_resources - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.authz.delete_relations_for_resources, [] - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.delete_relations_for_resources([])) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.authz.delete_relations_for_resources(["r"])) + self.assertIsNone( + await futu_await( + client.mgmt.authz.delete_relations_for_resources(["r"]) + ) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_re_delete_resources}", headers={ @@ -396,32 +430,39 @@ def test_delete_relations_for_resources(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_has_relations(self): + async def test_has_relations(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed has_relations - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.has_relations, []) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.has_relations([])) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True + mock_post.return_value.json.return_value = { + "relationQueries": [{"hasRelation": True}] + } self.assertIsNotNone( - client.mgmt.authz.has_relations( - [ - { - "resource": "r", - "relationDefinition": "rd", - "namespace": "ns", - "target": "u", - } - ] + await futu_await( + client.mgmt.authz.has_relations( + [ + { + "resource": "r", + "relationDefinition": "rd", + "namespace": "ns", + "target": "u", + } + ] + ) ) ) mock_post.assert_called_with( @@ -447,25 +488,28 @@ def test_has_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_who_can_access(self): + async def test_who_can_access(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed who_can_access - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.authz.who_can_access, "a", "b", "c" - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.who_can_access("a", "b", "c")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.authz.who_can_access("a", "b", "c")) + mock_post.return_value.json.return_value = {"targets": ["user1", "user2"]} + self.assertIsNotNone( + await futu_await(client.mgmt.authz.who_can_access("a", "b", "c")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_re_who}", headers={ @@ -480,23 +524,30 @@ def test_who_can_access(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_resource_relations(self): + async def test_resource_relations(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed resource_relations - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.resource_relations, "a") + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.resource_relations("a")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.authz.resource_relations("a")) + mock_post.return_value.json.return_value = { + "relations": [{"resource": "a", "target": "b"}] + } + self.assertIsNotNone( + await futu_await(client.mgmt.authz.resource_relations("a")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_re_resource}", headers={ @@ -511,23 +562,30 @@ def test_resource_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_targets_relations(self): + async def test_targets_relations(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed targets_relations - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.targets_relations, ["a"]) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.targets_relations(["a"])) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.authz.targets_relations(["a"])) + mock_post.return_value.json.return_value = { + "relations": [{"target": "a", "resource": "b"}] + } + self.assertIsNotNone( + await futu_await(client.mgmt.authz.targets_relations(["a"])) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_re_targets}", headers={ @@ -542,25 +600,30 @@ def test_targets_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_what_can_target_access(self): + async def test_what_can_target_access(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed what_can_target_access - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.authz.what_can_target_access, "a" - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.what_can_target_access("a")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.authz.what_can_target_access("a")) + mock_post.return_value.json.return_value = { + "relations": [{"target": "a", "resource": "b"}] + } + self.assertIsNotNone( + await futu_await(client.mgmt.authz.what_can_target_access("a")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_re_target_all}", headers={ @@ -575,30 +638,39 @@ def test_what_can_target_access(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_what_can_target_access_with_relation(self): + async def test_what_can_target_access_with_relation(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed what_can_target_access_with_relation - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.authz.what_can_target_access_with_relation, - "a", - "b", - "c", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.authz.what_can_target_access_with_relation( + "a", + "b", + "c", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True + mock_post.return_value.json.return_value = { + "relations": [{"target": "a", "resource": "b"}] + } self.assertIsNotNone( - client.mgmt.authz.what_can_target_access_with_relation("a", "b", "c") + await futu_await( + client.mgmt.authz.what_can_target_access_with_relation( + "a", "b", "c" + ) + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_re_target_with_relation}", @@ -614,23 +686,28 @@ def test_what_can_target_access_with_relation(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_get_modified(self): + async def test_get_modified(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed get_modified - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.authz.get_modified) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.authz.get_modified()) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.authz.get_modified()) + mock_post.return_value.json.return_value = { + "relations": {"resources": ["r1"], "targets": ["t1"]} + } + self.assertIsNotNone(await futu_await(client.mgmt.authz.get_modified())) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.authz_get_modified}", headers={ diff --git a/tests/management/test_fga.py b/tests/management/test_fga.py index a77f31192..3e4c567d1 100644 --- a/tests/management/test_fga.py +++ b/tests/management/test_fga.py @@ -2,9 +2,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -23,23 +24,26 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_save_schema(self): + async def test_save_schema(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed save_schema - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.fga.save_schema, "") + with self.assertRaises(AuthException): + await futu_await(client.mgmt.fga.save_schema("")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.fga.save_schema("model AuthZ 1.0")) + result = await futu_await(client.mgmt.fga.save_schema("model AuthZ 1.0")) + self.assertIsNone(result) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_save_schema}", headers={ @@ -54,23 +58,25 @@ def test_save_schema(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_create_relations(self): + async def test_create_relations(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed create_relations - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.fga.create_relations, []) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.fga.create_relations([])) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone( + result = await futu_await( client.mgmt.fga.create_relations( [ { @@ -83,6 +89,7 @@ def test_create_relations(self): ] ) ) + self.assertIsNone(result) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_create_relations}", headers={ @@ -107,23 +114,25 @@ def test_create_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_relations(self): + async def test_delete_relations(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed delete_relations - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.fga.delete_relations, []) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.fga.delete_relations([])) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone( + result = await futu_await( client.mgmt.fga.delete_relations( [ { @@ -136,6 +145,7 @@ def test_delete_relations(self): ] ) ) + self.assertIsNone(result) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_delete_relations}", headers={ @@ -160,23 +170,39 @@ def test_delete_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_check(self): + async def test_check(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed has_relations - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.fga.check, []) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.fga.check([])) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone( + mock_post.return_value.json.return_value = { + "tuples": [ + { + "tuple": { + "resource": "r", + "resourceType": "rt", + "relation": "rel", + "target": "u", + "targetType": "ty", + }, + "allowed": True, + } + ] + } + result = await futu_await( client.mgmt.fga.check( [ { @@ -189,6 +215,7 @@ def test_check(self): ] ) ) + self.assertIsNotNone(result) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_check}", headers={ @@ -213,12 +240,13 @@ def test_check(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_resources_details_success(self): + async def test_load_resources_details_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) response_body = { "resourcesDetails": [ @@ -226,14 +254,14 @@ def test_load_resources_details_success(self): {"resourceId": "r2", "resourceType": "type2", "displayName": "Name2"}, ] } - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True mock_post.return_value.json.return_value = response_body ids = [ {"resourceId": "r1", "resourceType": "type1"}, {"resourceId": "r2", "resourceType": "type2"}, ] - details = client.mgmt.fga.load_resources_details(ids) + details = await futu_await(client.mgmt.fga.load_resources_details(ids)) self.assertEqual(details, response_body["resourcesDetails"]) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_resources_load}", @@ -249,35 +277,39 @@ def test_load_resources_details_success(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_resources_details_error(self): + async def test_load_resources_details_error(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False ids = [{"resourceId": "r1", "resourceType": "type1"}] - self.assertRaises( - AuthException, - client.mgmt.fga.load_resources_details, - ids, - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.fga.load_resources_details( + ids, + ) + ) - def test_save_resources_details_success(self): + async def test_save_resources_details_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) details = [ {"resourceId": "r1", "resourceType": "type1", "displayName": "Name1"} ] - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - client.mgmt.fga.save_resources_details(details) + result = await futu_await(client.mgmt.fga.save_resources_details(details)) + self.assertIsNone(result) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_resources_save}", headers={ @@ -292,25 +324,27 @@ def test_save_resources_details_success(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_save_resources_details_error(self): + async def test_save_resources_details_error(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) details = [ {"resourceId": "r1", "resourceType": "type1", "displayName": "Name1"} ] - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.fga.save_resources_details, - details, - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.fga.save_resources_details( + details, + ) + ) - def test_fga_cache_url_save_schema(self): + async def test_fga_cache_url_save_schema(self): # Test FGA cache URL functionality for save_schema fga_cache_url = "https://my-fga-cache.example.com" client = DescopeClient( @@ -319,11 +353,12 @@ def test_fga_cache_url_save_schema(self): False, self.dummy_management_key, fga_cache_url=fga_cache_url, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - client.mgmt.fga.save_schema("model AuthZ 1.0") + await futu_await(client.mgmt.fga.save_schema("model AuthZ 1.0")) mock_post.assert_called_with( f"{fga_cache_url}{MgmtV1.fga_save_schema}", headers={ @@ -338,7 +373,7 @@ def test_fga_cache_url_save_schema(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_fga_cache_url_create_relations(self): + async def test_fga_cache_url_create_relations(self): # Test FGA cache URL functionality for create_relations fga_cache_url = "https://my-fga-cache.example.com" client = DescopeClient( @@ -347,6 +382,7 @@ def test_fga_cache_url_create_relations(self): False, self.dummy_management_key, fga_cache_url=fga_cache_url, + async_mode=self.async_test, ) relations = [ @@ -359,9 +395,9 @@ def test_fga_cache_url_create_relations(self): } ] - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - client.mgmt.fga.create_relations(relations) + await futu_await(client.mgmt.fga.create_relations(relations)) mock_post.assert_called_with( f"{fga_cache_url}{MgmtV1.fga_create_relations}", headers={ @@ -376,7 +412,7 @@ def test_fga_cache_url_create_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_fga_cache_url_delete_relations(self): + async def test_fga_cache_url_delete_relations(self): # Test FGA cache URL functionality for delete_relations fga_cache_url = "https://my-fga-cache.example.com" client = DescopeClient( @@ -385,6 +421,7 @@ def test_fga_cache_url_delete_relations(self): False, self.dummy_management_key, fga_cache_url=fga_cache_url, + async_mode=self.async_test, ) relations = [ @@ -397,9 +434,9 @@ def test_fga_cache_url_delete_relations(self): } ] - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - client.mgmt.fga.delete_relations(relations) + await futu_await(client.mgmt.fga.delete_relations(relations)) mock_post.assert_called_with( f"{fga_cache_url}{MgmtV1.fga_delete_relations}", headers={ @@ -414,7 +451,7 @@ def test_fga_cache_url_delete_relations(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_fga_cache_url_check(self): + async def test_fga_cache_url_check(self): # Test FGA cache URL functionality for check fga_cache_url = "https://my-fga-cache.example.com" client = DescopeClient( @@ -423,6 +460,7 @@ def test_fga_cache_url_check(self): False, self.dummy_management_key, fga_cache_url=fga_cache_url, + async_mode=self.async_test, ) relations = [ @@ -435,7 +473,7 @@ def test_fga_cache_url_check(self): } ] - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True mock_post.return_value.json.return_value = { "tuples": [ @@ -445,7 +483,7 @@ def test_fga_cache_url_check(self): } ] } - result = client.mgmt.fga.check(relations) + result = await futu_await(client.mgmt.fga.check(relations)) mock_post.assert_called_with( f"{fga_cache_url}{MgmtV1.fga_check}", headers={ @@ -463,20 +501,20 @@ def test_fga_cache_url_check(self): self.assertTrue(result[0]["allowed"]) self.assertEqual(result[0]["relation"], relations[0]) - def test_fga_without_cache_url_uses_default_base_url(self): + async def test_fga_without_cache_url_uses_default_base_url(self): # Test that FGA methods use default base URL when cache URL is not provided client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, # No fga_cache_url provided ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - client.mgmt.fga.save_schema("model AuthZ 1.0") - # Should use default base URL + await futu_await(client.mgmt.fga.save_schema("model AuthZ 1.0")) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_save_schema}", headers={ diff --git a/tests/management/test_flow.py b/tests/management/test_flow.py index 7f585b5b0..fc7d04d4d 100644 --- a/tests/management/test_flow.py +++ b/tests/management/test_flow.py @@ -2,9 +2,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -23,26 +24,25 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_list_flows(self): + async def test_list_flows(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.flow.list_flows, - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.flow.list_flows()) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.flow.list_flows()) + self.assertIsNotNone(await futu_await(client.mgmt.flow.list_flows())) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.flow_list_path}", headers={ @@ -57,27 +57,31 @@ def test_list_flows(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_flows(self): + async def test_delete_flows(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed delete flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.flow.delete_flows, - ["flow-1", "flow-2"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.flow.delete_flows( + ["flow-1", "flow-2"], + ) + ) # Test success delete flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.flow.delete_flows(["flow-1", "flow-2"])) + self.assertIsNotNone( + await futu_await(client.mgmt.flow.delete_flows(["flow-1", "flow-2"])) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.flow_delete_path}", headers={ @@ -92,27 +96,29 @@ def test_delete_flows(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_export_flow(self): + async def test_export_flow(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.flow.export_flow, - "name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.flow.export_flow( + "name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.flow.export_flow("test")) + self.assertIsNotNone(await futu_await(client.mgmt.flow.export_flow("test"))) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.flow_export_path}", headers={ @@ -129,30 +135,36 @@ def test_export_flow(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_import_flow(self): + async def test_import_flow(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.flow.import_flow, - "name", - {"name": "test"}, - [{"id": "test"}], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.flow.import_flow( + "name", + {"name": "test"}, + [{"id": "test"}], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNotNone( - client.mgmt.flow.import_flow("name", {"name": "test"}, [{"id": "test"}]) + await futu_await( + client.mgmt.flow.import_flow( + "name", {"name": "test"}, [{"id": "test"}] + ) + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.flow_import_path}", @@ -172,23 +184,25 @@ def test_import_flow(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_export_theme(self): + async def test_export_theme(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.flow.export_theme) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.flow.export_theme()) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.flow.export_theme()) + self.assertIsNotNone(await futu_await(client.mgmt.flow.export_theme())) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.theme_export_path}", headers={ @@ -203,25 +217,27 @@ def test_export_theme(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_import_theme(self): + async def test_import_theme(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.flow.import_theme, {"id": "test"} - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.flow.import_theme({"id": "test"})) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.flow.import_theme({"id": "test"})) + self.assertIsNotNone( + await futu_await(client.mgmt.flow.import_theme({"id": "test"})) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.theme_import_path}", headers={ diff --git a/tests/management/test_group.py b/tests/management/test_group.py index 93a13ebf3..0ab242bda 100644 --- a/tests/management/test_group.py +++ b/tests/management/test_group.py @@ -2,9 +2,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -23,27 +24,31 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_load_all_groups(self): + async def test_load_all_groups(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.group.load_all_groups, - "tenant_id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.group.load_all_groups( + "tenant_id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.mgmt.group.load_all_groups("someTenantId")) + self.assertIsNotNone( + await futu_await(client.mgmt.group.load_all_groups("someTenantId")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.group_load_all_path}", headers={ @@ -60,29 +65,33 @@ def test_load_all_groups(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_all_groups_for_members(self): + async def test_load_all_groups_for_members(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.group.load_all_groups_for_members, - "tenant_id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.group.load_all_groups_for_members( + "tenant_id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNotNone( - client.mgmt.group.load_all_groups_for_members( - "someTenantId", ["one", "two"], ["three", "four"] + await futu_await( + client.mgmt.group.load_all_groups_for_members( + "someTenantId", ["one", "two"], ["three", "four"] + ) ) ) mock_post.assert_called_with( @@ -103,29 +112,35 @@ def test_load_all_groups_for_members(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_all_group_members(self): + async def test_load_all_group_members(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.group.load_all_group_members, - "tenant_id", - "group_id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.group.load_all_group_members( + "tenant_id", + "group_id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNotNone( - client.mgmt.group.load_all_group_members("someTenantId", "someGroupId") + await futu_await( + client.mgmt.group.load_all_group_members( + "someTenantId", "someGroupId" + ) + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.group_load_all_group_members_path}", diff --git a/tests/management/test_jwt.py b/tests/management/test_jwt.py index 536b9f5e2..85097b8eb 100644 --- a/tests/management/test_jwt.py +++ b/tests/management/test_jwt.py @@ -4,9 +4,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtLoginOptions, MgmtV1 from tests import common -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call class TestUser(common.DescopeTest): @@ -24,32 +25,34 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_update_jwt(self): + async def test_update_jwt(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.jwt.update_jwt, "jwt", {"k1": "v1"}, 0 - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.jwt.update_jwt("jwt", {"k1": "v1"}, 0)) - self.assertRaises( - AuthException, client.mgmt.jwt.update_jwt, "", {"k1": "v1"}, 0 - ) + with self.assertRaises(AuthException): + + await futu_await(client.mgmt.jwt.update_jwt("", {"k1": "v1"}, 0)) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"jwt": "response"}""") mock_post.return_value = network_resp - resp = client.mgmt.jwt.update_jwt("test", {"k1": "v1"}, 40) + resp = await futu_await( + client.mgmt.jwt.update_jwt("test", {"k1": "v1"}, 40) + ) self.assertEqual(resp, "response") expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.update_jwt_path}" mock_post.assert_called_with( @@ -70,7 +73,7 @@ def test_update_jwt(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - resp = client.mgmt.jwt.update_jwt("test", {"k1": "v1"}) + resp = await futu_await(client.mgmt.jwt.update_jwt("test", {"k1": "v1"})) self.assertEqual(resp, "response") expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.update_jwt_path}" mock_post.assert_called_with( @@ -91,36 +94,36 @@ def test_update_jwt(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_impersonate(self): + async def test_impersonate(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, client.mgmt.jwt.impersonate, "imp1", "imp2", False - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.jwt.impersonate("imp1", "imp2", False)) - self.assertRaises( - AuthException, client.mgmt.jwt.impersonate, "", "imp2", False - ) + with self.assertRaises(AuthException): - self.assertRaises( - AuthException, client.mgmt.jwt.impersonate, "imp1", "", False - ) + await futu_await(client.mgmt.jwt.impersonate("", "imp2", False)) + + with self.assertRaises(AuthException): + + await futu_await(client.mgmt.jwt.impersonate("imp1", "", False)) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"jwt": "response"}""") mock_post.return_value = network_resp - resp = client.mgmt.jwt.impersonate("imp1", "imp2", True) + resp = await futu_await(client.mgmt.jwt.impersonate("imp1", "imp2", True)) self.assertEqual(resp, "response") expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.impersonate_path}" mock_post.assert_called_with( @@ -144,30 +147,32 @@ def test_impersonate(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_stop_impersonation(self): + async def test_stop_impersonation(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.jwt.stop_impersonation, - "", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.jwt.stop_impersonation( + "", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"jwt": "response"}""") mock_post.return_value = network_resp - resp = client.mgmt.jwt.stop_impersonation("jwtstr") + resp = await futu_await(client.mgmt.jwt.stop_impersonation("jwtstr")) self.assertEqual(resp, "response") expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.stop_impersonation_path}" mock_post.assert_called_with( @@ -189,31 +194,32 @@ def test_stop_impersonation(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_in(self): + async def test_sign_in(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - self.assertRaises(AuthException, client.mgmt.jwt.sign_in, "") + with self.assertRaises(AuthException): + await futu_await(client.mgmt.jwt.sign_in("")) - self.assertRaises( - AuthException, - client.mgmt.jwt.sign_in, - "loginId", - MgmtLoginOptions(mfa=True), - ) + with self.assertRaises(AuthException): + + await futu_await( + client.mgmt.jwt.sign_in("loginId", MgmtLoginOptions(mfa=True)), + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"jwt": "response"}""") mock_post.return_value = network_resp - client.mgmt.jwt.sign_in("loginId") + await futu_await(client.mgmt.jwt.sign_in("loginId")) expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.mgmt_sign_in_path}" mock_post.assert_called_with( expected_uri, @@ -237,24 +243,26 @@ def test_sign_in(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_up(self): + async def test_sign_up(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - self.assertRaises(AuthException, client.mgmt.jwt.sign_up, "") + with self.assertRaises(AuthException): + await futu_await(client.mgmt.jwt.sign_up("")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"jwt": "response"}""") mock_post.return_value = network_resp - client.mgmt.jwt.sign_up("loginId") + await futu_await(client.mgmt.jwt.sign_up("loginId")) expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.mgmt_sign_up_path}" mock_post.assert_called_with( expected_uri, @@ -288,24 +296,26 @@ def test_sign_up(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_up_or_in(self): + async def test_sign_up_or_in(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - self.assertRaises(AuthException, client.mgmt.jwt.sign_up_or_in, "") + with self.assertRaises(AuthException): + await futu_await(client.mgmt.jwt.sign_up_or_in("")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"jwt": "response"}""") mock_post.return_value = network_resp - client.mgmt.jwt.sign_up_or_in("loginId") + await futu_await(client.mgmt.jwt.sign_up_or_in("loginId")) expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.mgmt_sign_up_or_in_path}" mock_post.assert_called_with( expected_uri, @@ -339,21 +349,22 @@ def test_sign_up_or_in(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_anonymous(self): + async def test_anonymous(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"jwt": "response"}""") mock_post.return_value = network_resp - client.mgmt.jwt.anonymous({"k1": "v1"}, "id") + await futu_await(client.mgmt.jwt.anonymous({"k1": "v1"}, "id")) expected_uri = f"{common.DEFAULT_BASE_URL}{MgmtV1.anonymous_path}" mock_post.assert_called_with( expected_uri, diff --git a/tests/management/test_outbound_application.py b/tests/management/test_outbound_application.py index 4161c52a0..d340062e4 100644 --- a/tests/management/test_outbound_application.py +++ b/tests/management/test_outbound_application.py @@ -2,8 +2,10 @@ from unittest.mock import patch from descope import AuthException, DescopeClient +from descope.future_utils import futu_await from descope.management.common import AccessType, PromptType, URLParam from descope.management.outbound_application import OutboundApplication +from tests.testutils import mock_http_call from .. import common @@ -23,15 +25,16 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_create_application_success(self): + async def test_create_application_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -42,8 +45,10 @@ def test_create_application_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application.create_application( - "Test App", description="Test Description", client_secret="secret" + response = await futu_await( + client.mgmt.outbound_application.create_application( + "Test App", description="Test Description", client_secret="secret" + ) ) assert response == { @@ -54,12 +59,13 @@ def test_create_application_success(self): } } - def test_create_application_with_all_parameters_success(self): + async def test_create_application_with_all_parameters_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Create test data for all new parameters @@ -70,7 +76,7 @@ def test_create_application_with_all_parameters_success(self): token_params = [URLParam("grant_type", "authorization_code")] prompts = [PromptType.LOGIN, PromptType.CONSENT] - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -81,25 +87,27 @@ def test_create_application_with_all_parameters_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application.create_application( - name="Test OAuth App", - description="Test Description", - logo="https://example.com/logo.png", - id="app123", - client_secret="secret", - client_id="test-client-id", - discovery_url="https://accounts.google.com/.well-known/openid_configuration", - authorization_url="https://accounts.google.com/o/oauth2/v2/auth", - authorization_url_params=auth_params, - token_url="https://oauth2.googleapis.com/token", - token_url_params=token_params, - revocation_url="https://oauth2.googleapis.com/revoke", - default_scopes=["https://www.googleapis.com/auth/userinfo.profile"], - default_redirect_url="https://myapp.com/callback", - callback_domain="myapp.com", - pkce=True, - access_type=AccessType.OFFLINE, - prompt=prompts, + response = await futu_await( + client.mgmt.outbound_application.create_application( + name="Test OAuth App", + description="Test Description", + logo="https://example.com/logo.png", + id="app123", + client_secret="secret", + client_id="test-client-id", + discovery_url="https://accounts.google.com/.well-known/openid_configuration", + authorization_url="https://accounts.google.com/o/oauth2/v2/auth", + authorization_url_params=auth_params, + token_url="https://oauth2.googleapis.com/token", + token_url_params=token_params, + revocation_url="https://oauth2.googleapis.com/revoke", + default_scopes=["https://www.googleapis.com/auth/userinfo.profile"], + default_redirect_url="https://myapp.com/callback", + callback_domain="myapp.com", + pkce=True, + access_type=AccessType.OFFLINE, + prompt=prompts, + ) ) assert response == { @@ -110,31 +118,34 @@ def test_create_application_with_all_parameters_success(self): } } - def test_create_application_failure(self): + async def test_create_application_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.create_application, - "Test App", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.create_application( + "Test App", + ) + ) - def test_update_application_success(self): + async def test_update_application_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -145,11 +156,13 @@ def test_update_application_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application.update_application( - "app123", - "Updated App", - description="Updated Description", - client_secret="new-secret", + response = await futu_await( + client.mgmt.outbound_application.update_application( + "app123", + "Updated App", + description="Updated Description", + client_secret="new-secret", + ) ) assert response == { @@ -160,12 +173,13 @@ def test_update_application_success(self): } } - def test_update_application_with_all_parameters_success(self): + async def test_update_application_with_all_parameters_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Create test data for all new parameters @@ -176,7 +190,7 @@ def test_update_application_with_all_parameters_success(self): token_params = [URLParam("grant_type", "authorization_code")] prompts = [PromptType.LOGIN, PromptType.CONSENT, PromptType.SELECT_ACCOUNT] - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -187,28 +201,30 @@ def test_update_application_with_all_parameters_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application.update_application( - id="app123", - name="Updated OAuth App", - description="Updated Description", - logo="https://example.com/new-logo.png", - client_secret="new-secret", - client_id="new-client-id", - discovery_url="https://accounts.google.com/.well-known/openid_configuration", - authorization_url="https://accounts.google.com/o/oauth2/v2/auth", - authorization_url_params=auth_params, - token_url="https://oauth2.googleapis.com/token", - token_url_params=token_params, - revocation_url="https://oauth2.googleapis.com/revoke", - default_scopes=[ - "https://www.googleapis.com/auth/userinfo.profile", - "https://www.googleapis.com/auth/userinfo.email", - ], - default_redirect_url="https://myapp.com/updated-callback", - callback_domain="myapp.com", - pkce=True, - access_type=AccessType.OFFLINE, - prompt=prompts, + response = await futu_await( + client.mgmt.outbound_application.update_application( + id="app123", + name="Updated OAuth App", + description="Updated Description", + logo="https://example.com/new-logo.png", + client_secret="new-secret", + client_id="new-client-id", + discovery_url="https://accounts.google.com/.well-known/openid_configuration", + authorization_url="https://accounts.google.com/o/oauth2/v2/auth", + authorization_url_params=auth_params, + token_url="https://oauth2.googleapis.com/token", + token_url_params=token_params, + revocation_url="https://oauth2.googleapis.com/revoke", + default_scopes=[ + "https://www.googleapis.com/auth/userinfo.profile", + "https://www.googleapis.com/auth/userinfo.email", + ], + default_redirect_url="https://myapp.com/updated-callback", + callback_domain="myapp.com", + pkce=True, + access_type=AccessType.OFFLINE, + prompt=prompts, + ) ) assert response == { @@ -219,62 +235,70 @@ def test_update_application_with_all_parameters_success(self): } } - def test_update_application_failure(self): + async def test_update_application_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.update_application, - "app123", - "Updated App", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.update_application( + "app123", + "Updated App", + ) + ) - def test_delete_application_success(self): + async def test_delete_application_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True mock_post.return_value = network_resp - client.mgmt.outbound_application.delete_application("app123") + await futu_await( + client.mgmt.outbound_application.delete_application("app123") + ) - def test_delete_application_failure(self): + async def test_delete_application_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.delete_application, - "app123", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.delete_application( + "app123", + ) + ) - def test_load_application_success(self): + async def test_load_application_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -285,7 +309,9 @@ def test_load_application_success(self): } } mock_get.return_value = network_resp - response = client.mgmt.outbound_application.load_application("app123") + response = await futu_await( + client.mgmt.outbound_application.load_application("app123") + ) assert response == { "app": { @@ -295,31 +321,34 @@ def test_load_application_success(self): } } - def test_load_application_failure(self): + async def test_load_application_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.load_application, - "app123", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.load_application( + "app123", + ) + ) - def test_load_all_applications_success(self): + async def test_load_all_applications_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -329,7 +358,9 @@ def test_load_all_applications_success(self): ] } mock_get.return_value = network_resp - response = client.mgmt.outbound_application.load_all_applications() + response = await futu_await( + client.mgmt.outbound_application.load_all_applications() + ) assert response == { "apps": [ @@ -338,30 +369,32 @@ def test_load_all_applications_success(self): ] } - def test_load_all_applications_failure(self): + async def test_load_all_applications_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.load_all_applications, - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.load_all_applications() + ) - def test_fetch_token_by_scopes_success(self): + async def test_fetch_token_by_scopes_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -374,12 +407,14 @@ def test_fetch_token_by_scopes_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application.fetch_token_by_scopes( - "app123", - "user456", - ["read", "write"], - {"refreshToken": True}, - "tenant789", + response = await futu_await( + client.mgmt.outbound_application.fetch_token_by_scopes( + "app123", + "user456", + ["read", "write"], + {"refreshToken": True}, + "tenant789", + ) ) assert response == { @@ -392,33 +427,36 @@ def test_fetch_token_by_scopes_success(self): } } - def test_fetch_token_by_scopes_failure(self): + async def test_fetch_token_by_scopes_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.fetch_token_by_scopes, - "app123", - "user456", - ["read"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.fetch_token_by_scopes( + "app123", + "user456", + ["read"], + ) + ) - def test_fetch_token_success(self): + async def test_fetch_token_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -431,8 +469,10 @@ def test_fetch_token_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application.fetch_token( - "app123", "user456", "tenant789", {"forceRefresh": True} + response = await futu_await( + client.mgmt.outbound_application.fetch_token( + "app123", "user456", "tenant789", {"forceRefresh": True} + ) ) assert response == { @@ -445,32 +485,35 @@ def test_fetch_token_success(self): } } - def test_fetch_token_failure(self): + async def test_fetch_token_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.fetch_token, - "app123", - "user456", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.fetch_token( + "app123", + "user456", + ) + ) - def test_fetch_tenant_token_by_scopes_success(self): + async def test_fetch_tenant_token_by_scopes_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -483,8 +526,10 @@ def test_fetch_tenant_token_by_scopes_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application.fetch_tenant_token_by_scopes( - "app123", "tenant789", ["read", "write"], {"refreshToken": True} + response = await futu_await( + client.mgmt.outbound_application.fetch_tenant_token_by_scopes( + "app123", "tenant789", ["read", "write"], {"refreshToken": True} + ) ) assert response == { @@ -497,33 +542,36 @@ def test_fetch_tenant_token_by_scopes_success(self): } } - def test_fetch_tenant_token_by_scopes_failure(self): + async def test_fetch_tenant_token_by_scopes_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.fetch_tenant_token_by_scopes, - "app123", - "tenant789", - ["read"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.fetch_tenant_token_by_scopes( + "app123", + "tenant789", + ["read"], + ) + ) - def test_fetch_tenant_token_success(self): + async def test_fetch_tenant_token_success(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -536,8 +584,10 @@ def test_fetch_tenant_token_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application.fetch_tenant_token( - "app123", "tenant789", {"forceRefresh": True} + response = await futu_await( + client.mgmt.outbound_application.fetch_tenant_token( + "app123", "tenant789", {"forceRefresh": True} + ) ) assert response == { @@ -550,24 +600,26 @@ def test_fetch_tenant_token_success(self): } } - def test_fetch_tenant_token_failure(self): + async def test_fetch_tenant_token_failure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application.fetch_tenant_token, - "app123", - "tenant789", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application.fetch_tenant_token( + "app123", + "tenant789", + ) + ) - def test_compose_create_update_body(self): + async def test_compose_create_update_body(self): body = OutboundApplication._compose_create_update_body( "Test App", "Test Description", @@ -586,7 +638,7 @@ def test_compose_create_update_body(self): assert body == expected_body - def test_compose_create_update_body_without_client_secret(self): + async def test_compose_create_update_body_without_client_secret(self): body = OutboundApplication._compose_create_update_body( "Test App", "Test Description", "https://example.com/logo.png", "app123" ) @@ -600,7 +652,7 @@ def test_compose_create_update_body_without_client_secret(self): assert body == expected_body - def test_compose_create_update_body_with_all_new_parameters(self): + async def test_compose_create_update_body_with_all_new_parameters(self): # Create test data for all new parameters auth_params = [ URLParam("response_type", "code"), @@ -656,7 +708,7 @@ def test_compose_create_update_body_with_all_new_parameters(self): assert body == expected_body - def test_compose_create_update_body_with_partial_new_parameters(self): + async def test_compose_create_update_body_with_partial_new_parameters(self): # Test with only some of the new parameters body = OutboundApplication._compose_create_update_body( name="Test App", @@ -684,7 +736,7 @@ def test_compose_create_update_body_with_partial_new_parameters(self): assert body == expected_body - def test_compose_create_update_body_with_url_params_only(self): + async def test_compose_create_update_body_with_url_params_only(self): # Test with only URL parameters auth_params = [URLParam("response_type", "code")] token_params = [URLParam("grant_type", "authorization_code")] @@ -707,7 +759,7 @@ def test_compose_create_update_body_with_url_params_only(self): assert body == expected_body - def test_compose_create_update_body_with_prompt_types(self): + async def test_compose_create_update_body_with_prompt_types(self): # Test with different prompt type combinations prompts = [PromptType.LOGIN, PromptType.CONSENT, PromptType.SELECT_ACCOUNT] @@ -725,7 +777,7 @@ def test_compose_create_update_body_with_prompt_types(self): assert body == expected_body - def test_compose_create_update_body_with_none_values(self): + async def test_compose_create_update_body_with_none_values(self): # Test that None values are handled correctly body = OutboundApplication._compose_create_update_body( name="Test App", @@ -744,7 +796,7 @@ def test_compose_create_update_body_with_none_values(self): assert body == expected_body - def test_compose_create_update_body_with_empty_lists(self): + async def test_compose_create_update_body_with_empty_lists(self): # Test with empty lists for URL parameters and prompts body = OutboundApplication._compose_create_update_body( name="Test App", @@ -768,7 +820,7 @@ def test_compose_create_update_body_with_empty_lists(self): assert body == expected_body - def test_url_param_to_dict(self): + async def test_url_param_to_dict(self): # Test URLParam to_dict method param = URLParam("test_name", "test_value") param_dict = param.to_dict() @@ -776,12 +828,12 @@ def test_url_param_to_dict(self): expected_dict = {"name": "test_name", "value": "test_value"} assert param_dict == expected_dict - def test_access_type_enum_values(self): + async def test_access_type_enum_values(self): # Test AccessType enum values assert AccessType.OFFLINE.value == "offline" assert AccessType.ONLINE.value == "online" - def test_prompt_type_enum_values(self): + async def test_prompt_type_enum_values(self): # Test PromptType enum values assert PromptType.NONE.value == "none" assert PromptType.LOGIN.value == "login" @@ -804,10 +856,15 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_fetch_token_by_scopes_success(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict, False) + async def test_fetch_token_by_scopes_success(self): + client = DescopeClient( + self.dummy_project_id, + self.public_key_dict, + False, + async_mode=self.async_test, + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -820,13 +877,15 @@ def test_fetch_token_by_scopes_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application_by_token.fetch_token_by_scopes( - self.dummy_token, - "app123", - "user456", - ["read", "write"], - {"refreshToken": True}, - "tenant789", + response = await futu_await( + client.mgmt.outbound_application_by_token.fetch_token_by_scopes( + self.dummy_token, + "app123", + "user456", + ["read", "write"], + {"refreshToken": True}, + "tenant789", + ) ) assert response == { @@ -839,37 +898,49 @@ def test_fetch_token_by_scopes_success(self): } } - def test_fetch_token_by_scopes_failure(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict, False) + async def test_fetch_token_by_scopes_failure(self): + client = DescopeClient( + self.dummy_project_id, + self.public_key_dict, + False, + async_mode=self.async_test, + ) # Test failure of empty token - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application_by_token.fetch_token_by_scopes, - "", # empty token - "app123", - "user456", - ["read"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application_by_token.fetch_token_by_scopes( + "", # empty token + "app123", + "user456", + ["read"], + ) + ) # Test invalid response failure - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application_by_token.fetch_token_by_scopes, - self.dummy_token, - "app123", - "user456", - ["read"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application_by_token.fetch_token_by_scopes( + self.dummy_token, + "app123", + "user456", + ["read"], + ) + ) - def test_fetch_token_success(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict, False) + async def test_fetch_token_success(self): + client = DescopeClient( + self.dummy_project_id, + self.public_key_dict, + False, + async_mode=self.async_test, + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -882,12 +953,14 @@ def test_fetch_token_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application_by_token.fetch_token( - self.dummy_token, - "app123", - "user456", - "tenant789", - {"forceRefresh": True}, + response = await futu_await( + client.mgmt.outbound_application_by_token.fetch_token( + self.dummy_token, + "app123", + "user456", + "tenant789", + {"forceRefresh": True}, + ) ) assert response == { @@ -900,35 +973,47 @@ def test_fetch_token_success(self): } } - def test_fetch_token_failure(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict, False) + async def test_fetch_token_failure(self): + client = DescopeClient( + self.dummy_project_id, + self.public_key_dict, + False, + async_mode=self.async_test, + ) # Test failure of empty token - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertRaises( - AuthException, - client.mgmt.outbound_application_by_token.fetch_token, - "", # empty token - "app123", - "user456", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application_by_token.fetch_token( + "", # empty token + "app123", + "user456", + ) + ) # Test invalid response failure - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application_by_token.fetch_token, - self.dummy_token, - "app123", - "user456", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application_by_token.fetch_token( + self.dummy_token, + "app123", + "user456", + ) + ) - def test_fetch_tenant_token_by_scopes_success(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict, False) + async def test_fetch_tenant_token_by_scopes_success(self): + client = DescopeClient( + self.dummy_project_id, + self.public_key_dict, + False, + async_mode=self.async_test, + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -941,7 +1026,7 @@ def test_fetch_tenant_token_by_scopes_success(self): } } mock_post.return_value = network_resp - response = ( + response = await futu_await( client.mgmt.outbound_application_by_token.fetch_tenant_token_by_scopes( self.dummy_token, "app123", @@ -961,37 +1046,49 @@ def test_fetch_tenant_token_by_scopes_success(self): } } - def test_fetch_tenant_token_by_scopes_failure(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict, False) + async def test_fetch_tenant_token_by_scopes_failure(self): + client = DescopeClient( + self.dummy_project_id, + self.public_key_dict, + False, + async_mode=self.async_test, + ) # Test failure of empty token - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertRaises( - AuthException, - client.mgmt.outbound_application_by_token.fetch_tenant_token_by_scopes, - "", # empty token - "app123", - "tenant789", - ["read"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application_by_token.fetch_tenant_token_by_scopes( + "", # empty token + "app123", + "tenant789", + ["read"], + ) + ) # Test invalid response failure - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application_by_token.fetch_tenant_token_by_scopes, - self.dummy_token, - "app123", - "tenant789", - ["read"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application_by_token.fetch_tenant_token_by_scopes( + self.dummy_token, + "app123", + "tenant789", + ["read"], + ) + ) - def test_fetch_tenant_token_success(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict, False) + async def test_fetch_tenant_token_success(self): + client = DescopeClient( + self.dummy_project_id, + self.public_key_dict, + False, + async_mode=self.async_test, + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = { @@ -1004,8 +1101,10 @@ def test_fetch_tenant_token_success(self): } } mock_post.return_value = network_resp - response = client.mgmt.outbound_application_by_token.fetch_tenant_token( - self.dummy_token, "app123", "tenant789", {"forceRefresh": True} + response = await futu_await( + client.mgmt.outbound_application_by_token.fetch_tenant_token( + self.dummy_token, "app123", "tenant789", {"forceRefresh": True} + ) ) assert response == { @@ -1018,27 +1117,34 @@ def test_fetch_tenant_token_success(self): } } - def test_fetch_tenant_token_failure(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict, False) + async def test_fetch_tenant_token_failure(self): + client = DescopeClient( + self.dummy_project_id, + self.public_key_dict, + False, + async_mode=self.async_test, + ) # Test failure of empty token - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertRaises( - AuthException, - client.mgmt.outbound_application_by_token.fetch_tenant_token, - "", # empty token - "app123", - "tenant789", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application_by_token.fetch_tenant_token( + "", # empty token + "app123", + "tenant789", + ) + ) # Test invalid response failure - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.outbound_application_by_token.fetch_tenant_token, - self.dummy_token, - "app123", - "tenant789", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.outbound_application_by_token.fetch_tenant_token( + self.dummy_token, + "app123", + "tenant789", + ) + ) diff --git a/tests/management/test_permission.py b/tests/management/test_permission.py index aa7e1f93f..25f37fd74 100644 --- a/tests/management/test_permission.py +++ b/tests/management/test_permission.py @@ -4,9 +4,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -25,27 +26,31 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_create(self): + async def test_create(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.permission.create, - "name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.permission.create( + "name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.permission.create("P1", "Something")) + self.assertIsNone( + await futu_await(client.mgmt.permission.create("P1", "Something")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.permission_create_path}", headers={ @@ -63,32 +68,36 @@ def test_create(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update(self): + async def test_update(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.permission.update, - "name", - "new-name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.permission.update( + "name", + "new-name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.permission.update( - "name", - "new-name", - "new-description", + await futu_await( + client.mgmt.permission.update( + "name", + "new-name", + "new-description", + ) ) ) mock_post.assert_called_with( @@ -109,27 +118,29 @@ def test_update(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete(self): + async def test_delete(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.permission.delete, - "name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.permission.delete( + "name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.permission.delete("name")) + self.assertIsNone(await futu_await(client.mgmt.permission.delete("name"))) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.permission_delete_path}", headers={ @@ -146,28 +157,30 @@ def test_delete(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_all(self): + async def test_load_all(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.permission.load_all) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.permission.load_all()) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"permissions": [{"name": "p1"}, {"name": "p2"}]}""" ) mock_get.return_value = network_resp - resp = client.mgmt.permission.load_all() + resp = await futu_await(client.mgmt.permission.load_all()) permissions = resp["permissions"] self.assertEqual(len(permissions), 2) self.assertEqual(permissions[0]["name"], "p1") diff --git a/tests/management/test_project.py b/tests/management/test_project.py index 2b3d15810..e34c98cd6 100644 --- a/tests/management/test_project.py +++ b/tests/management/test_project.py @@ -4,9 +4,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -25,27 +26,31 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_update_name(self): + async def test_update_name(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.project.update_name, - "name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.project.update_name( + "name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.project.update_name("new-name")) + self.assertIsNone( + await futu_await(client.mgmt.project.update_name("new-name")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.project_update_name}", headers={ @@ -62,27 +67,31 @@ def test_update_name(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_tags(self): + async def test_update_tags(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.project.update_tags, - "tags", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.project.update_tags( + "tags", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.project.update_tags(["tag1", "tag2"])) + self.assertIsNone( + await futu_await(client.mgmt.project.update_tags(["tag1", "tag2"])) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.project_update_tags}", headers={ @@ -99,24 +108,23 @@ def test_update_tags(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_list_projects(self): + async def test_list_projects(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.project.list_projects, - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.project.list_projects()) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True json_str = """ @@ -133,7 +141,7 @@ def test_list_projects(self): """ network_resp.json.return_value = json.loads(json_str) mock_post.return_value = network_resp - resp = client.mgmt.project.list_projects() + resp = await futu_await(client.mgmt.project.list_projects()) projects = resp["projects"] self.assertEqual(len(projects), 1) self.assertEqual(projects[0]["id"], "dummy") @@ -151,27 +159,29 @@ def test_list_projects(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_clone(self): + async def test_clone(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.project.clone, - "new-name", - "production", - ["apple", "banana", "cherry"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.project.clone( + "new-name", + "production", + ["apple", "banana", "cherry"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -182,10 +192,12 @@ def test_clone(self): """ ) mock_post.return_value = network_resp - resp = client.mgmt.project.clone( - "new-name", - "production", - ["apple", "banana", "cherry"], + resp = await futu_await( + client.mgmt.project.clone( + "new-name", + "production", + ["apple", "banana", "cherry"], + ) ) self.assertEqual(resp["id"], "dummy") mock_post.assert_called_with( @@ -206,24 +218,23 @@ def test_clone(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_export_project(self): + async def test_export_project(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.project.export_project, - ) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.project.export_project()) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -234,7 +245,7 @@ def test_export_project(self): """ ) mock_post.return_value = network_resp - resp = client.mgmt.project.export_project() + resp = await futu_await(client.mgmt.project.export_project()) self.assertIsNotNone(resp) self.assertEqual(resp["foo"], "bar") mock_post.assert_called_with( @@ -251,34 +262,36 @@ def test_export_project(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_import_project(self): + async def test_import_project(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.project.import_project, - { - "foo": "bar", - }, - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.project.import_project( + { + "foo": "bar", + }, + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True mock_post.return_value = network_resp files = { "foo": "bar", } - client.mgmt.project.import_project(files) + await futu_await(client.mgmt.project.import_project(files)) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.project_import}", headers={ diff --git a/tests/management/test_role.py b/tests/management/test_role.py index bd040269b..224a4114f 100644 --- a/tests/management/test_role.py +++ b/tests/management/test_role.py @@ -4,9 +4,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -25,28 +26,32 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_create(self): + async def test_create(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.role.create, - "name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.role.create( + "name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.role.create("R1", "Something", ["P1"], "t1", True) + await futu_await( + client.mgmt.role.create("R1", "Something", ["P1"], "t1", True) + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.role_create_path}", @@ -68,35 +73,39 @@ def test_create(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update(self): + async def test_update(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.role.update, - "name", - "new-name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.role.update( + "name", + "new-name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.role.update( - "name", - "new-name", - "new-description", - ["P1", "P2"], - "t1", - True, + await futu_await( + client.mgmt.role.update( + "name", + "new-name", + "new-description", + ["P1", "P2"], + "t1", + True, + ) ) ) mock_post.assert_called_with( @@ -120,27 +129,29 @@ def test_update(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete(self): + async def test_delete(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.role.delete, - "name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.role.delete( + "name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.role.delete("name")) + self.assertIsNone(await futu_await(client.mgmt.role.delete("name"))) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.role_delete_path}", headers={ @@ -155,21 +166,23 @@ def test_delete(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_all(self): + async def test_load_all(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.role.load_all) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.role.load_all()) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -183,7 +196,7 @@ def test_load_all(self): """ ) mock_get.return_value = network_resp - resp = client.mgmt.role.load_all() + resp = await futu_await(client.mgmt.role.load_all()) roles = resp["roles"] self.assertEqual(len(roles), 2) self.assertEqual(roles[0]["name"], "R1") @@ -205,26 +218,28 @@ def test_load_all(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_search(self): + async def test_search(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.role.search, - ["t"], - ["r"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.role.search( + ["t"], + ["r"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -238,7 +253,9 @@ def test_search(self): """ ) mock_post.return_value = network_resp - resp = client.mgmt.role.search(["t"], ["r"], "x", ["p1", "p2"]) + resp = await futu_await( + client.mgmt.role.search(["t"], ["r"], "x", ["p1", "p2"]) + ) roles = resp["roles"] self.assertEqual(len(roles), 2) self.assertEqual(roles[0]["name"], "R1") diff --git a/tests/management/test_sso_application.py b/tests/management/test_sso_application.py index 6fbedaa1f..3962e650a 100644 --- a/tests/management/test_sso_application.py +++ b/tests/management/test_sso_application.py @@ -10,9 +10,10 @@ SAMLIDPRoleGroupMappingInfo, ) from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -31,34 +32,38 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_create_oidc_application(self): + async def test_create_oidc_application(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso_application.create_oidc_application, - "valid-name", - "http://dummy.com", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso_application.create_oidc_application( + "valid-name", + "http://dummy.com", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"id": "app1"}""") mock_post.return_value = network_resp - resp = client.mgmt.sso_application.create_oidc_application( - name="name", - login_page_url="http://dummy.com", - force_authentication=True, + resp = await futu_await( + client.mgmt.sso_application.create_oidc_application( + name="name", + login_page_url="http://dummy.com", + force_authentication=True, + ) ) self.assertEqual(resp["id"], "app1") mock_post.assert_called_with( @@ -83,71 +88,78 @@ def test_create_oidc_application(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_create_saml_application(self): + async def test_create_saml_application(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - self.assertRaises( - Exception, - client.mgmt.sso_application.create_saml_application, - name="valid-name", - login_page_url="http://dummy.com", - use_metadata_info=True, - metadata_url="", - ) + with self.assertRaises(Exception): + await futu_await( + client.mgmt.sso_application.create_saml_application( + name="valid-name", + login_page_url="http://dummy.com", + use_metadata_info=True, + metadata_url="", + ) + ) - self.assertRaises( - Exception, - client.mgmt.sso_application.create_saml_application, - name="valid-name", - login_page_url="http://dummy.com", - use_metadata_info=False, - entity_id="", - ) + with self.assertRaises(Exception): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso_application.create_saml_application, - name="valid-name", - login_page_url="http://dummy.com", - use_metadata_info=True, - metadata_url="http://dummy.com/md", + await futu_await( + client.mgmt.sso_application.create_saml_application( + name="valid-name", + login_page_url="http://dummy.com", + use_metadata_info=False, + entity_id="", + ) ) + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso_application.create_saml_application( + name="valid-name", + login_page_url="http://dummy.com", + use_metadata_info=True, + metadata_url="http://dummy.com/md", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"id": "app1"}""") mock_post.return_value = network_resp - resp = client.mgmt.sso_application.create_saml_application( - name="name", - login_page_url="http://dummy.com", - use_metadata_info=True, - metadata_url="http://dummy.com/md", - attribute_mapping=[ - SAMLIDPAttributeMappingInfo("name1", "type1", "val1") - ], - groups_mapping=[ - SAMLIDPGroupsMappingInfo( - "name1", - "type1", - "roles", - "val1", - [SAMLIDPRoleGroupMappingInfo("id1", "name1")], - ) - ], - subject_name_id_type="email", - default_relay_state="relayState", - force_authentication=True, - logout_redirect_url="http://dummy.com/logout", + resp = await futu_await( + client.mgmt.sso_application.create_saml_application( + name="name", + login_page_url="http://dummy.com", + use_metadata_info=True, + metadata_url="http://dummy.com/md", + attribute_mapping=[ + SAMLIDPAttributeMappingInfo("name1", "type1", "val1") + ], + groups_mapping=[ + SAMLIDPGroupsMappingInfo( + "name1", + "type1", + "roles", + "val1", + [SAMLIDPRoleGroupMappingInfo("id1", "name1")], + ) + ], + subject_name_id_type="email", + default_relay_state="relayState", + force_authentication=True, + logout_redirect_url="http://dummy.com/logout", + ) ) self.assertEqual(resp["id"], "app1") mock_post.assert_called_with( @@ -194,32 +206,36 @@ def test_create_saml_application(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_oidc_application(self): + async def test_update_oidc_application(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso_application.update_oidc_application, - "id1", - "valid-name", - "http://dummy.com", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso_application.update_oidc_application( + "id1", + "valid-name", + "http://dummy.com", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True self.assertIsNone( - client.mgmt.sso_application.update_oidc_application( - "app1", "name", "http://dummy.com" + await futu_await( + client.mgmt.sso_application.update_oidc_application( + "app1", "name", "http://dummy.com" + ) ) ) mock_post.assert_called_with( @@ -244,74 +260,81 @@ def test_update_oidc_application(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_saml_application(self): + async def test_update_saml_application(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - self.assertRaises( - Exception, - client.mgmt.sso_application.update_saml_application, - id="id1", - name="valid-name", - login_page_url="http://dummy.com", - use_metadata_info=True, - metadata_url="", - ) + with self.assertRaises(Exception): + await futu_await( + client.mgmt.sso_application.update_saml_application( + id="id1", + name="valid-name", + login_page_url="http://dummy.com", + use_metadata_info=True, + metadata_url="", + ) + ) - self.assertRaises( - Exception, - client.mgmt.sso_application.update_saml_application, - id="id1", - name="valid-name", - login_page_url="http://dummy.com", - use_metadata_info=False, - entity_id="", - ) + with self.assertRaises(Exception): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso_application.update_saml_application, - id="id1", - name="valid-name", - login_page_url="http://dummy.com", - use_metadata_info=True, - metadata_url="http://dummy.com/md", + await futu_await( + client.mgmt.sso_application.update_saml_application( + id="id1", + name="valid-name", + login_page_url="http://dummy.com", + use_metadata_info=False, + entity_id="", + ) ) + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso_application.update_saml_application( + id="id1", + name="valid-name", + login_page_url="http://dummy.com", + use_metadata_info=True, + metadata_url="http://dummy.com/md", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True self.assertIsNone( - client.mgmt.sso_application.update_saml_application( - id="id1", - name="name", - login_page_url="http://dummy.com", - use_metadata_info=False, - metadata_url="", - entity_id="ent1234", - acs_url="http://dummy.com/acs", - certificate="cert", - attribute_mapping=[ - SAMLIDPAttributeMappingInfo("name1", "type1", "val1") - ], - groups_mapping=[ - SAMLIDPGroupsMappingInfo( - "name1", - "type1", - "roles", - "val1", - [SAMLIDPRoleGroupMappingInfo("id1", "name1")], - ) - ], - subject_name_id_type="", + await futu_await( + client.mgmt.sso_application.update_saml_application( + id="id1", + name="name", + login_page_url="http://dummy.com", + use_metadata_info=False, + metadata_url="", + entity_id="ent1234", + acs_url="http://dummy.com/acs", + certificate="cert", + attribute_mapping=[ + SAMLIDPAttributeMappingInfo("name1", "type1", "val1") + ], + groups_mapping=[ + SAMLIDPGroupsMappingInfo( + "name1", + "type1", + "roles", + "val1", + [SAMLIDPRoleGroupMappingInfo("id1", "name1")], + ) + ], + subject_name_id_type="", + ) ) ) mock_post.assert_called_with( @@ -358,27 +381,31 @@ def test_update_saml_application(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete(self): + async def test_delete(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso_application.delete, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso_application.delete( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.sso_application.delete("app1")) + self.assertIsNone( + await futu_await(client.mgmt.sso_application.delete("app1")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.sso_application_delete_path}", headers={ @@ -395,25 +422,27 @@ def test_delete(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load(self): + async def test_load(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso_application.load, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso_application.load( + "valid-id", + ) + ) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -422,7 +451,7 @@ def test_load(self): """ ) mock_get.return_value = network_resp - resp = client.mgmt.sso_application.load("app1") + resp = await futu_await(client.mgmt.sso_application.load("app1")) self.assertEqual(resp["name"], "App1") self.assertEqual(resp["appType"], "saml") self.assertEqual( @@ -459,21 +488,23 @@ def test_load(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_all(self): + async def test_load_all(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.sso_application.load_all) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.sso_application.load_all()) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -487,7 +518,7 @@ def test_load_all(self): """ ) mock_get.return_value = network_resp - resp = client.mgmt.sso_application.load_all() + resp = await futu_await(client.mgmt.sso_application.load_all()) apps = resp["apps"] self.assertEqual(len(apps), 2) self.assertEqual(apps[0]["name"], "App1") diff --git a/tests/management/test_sso_settings.py b/tests/management/test_sso_settings.py index 0c6cbc9b9..9e53042f8 100644 --- a/tests/management/test_sso_settings.py +++ b/tests/management/test_sso_settings.py @@ -4,6 +4,7 @@ from descope import AttributeMapping, AuthException, DescopeClient, RoleMapping from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 from descope.management.sso_settings import ( OIDCAttributeMapping, @@ -13,7 +14,7 @@ SSOSettings, ) -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -32,30 +33,32 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_delete_settings(self): + async def test_delete_settings(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.delete") as mock_delete: + with mock_http_call(self.async_test, "delete") as mock_delete: mock_delete.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.delete_settings, - "tenant-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.delete_settings( + "tenant-id", + ) + ) # Test success flow - with patch("httpx.delete") as mock_delete: + with mock_http_call(self.async_test, "delete") as mock_delete: network_resp = mock.Mock() network_resp.is_success = True mock_delete.return_value = network_resp - client.mgmt.sso.delete_settings("tenant-id") + await futu_await(client.mgmt.sso.delete_settings("tenant-id")) mock_delete.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.sso_settings_path}", @@ -70,32 +73,34 @@ def test_delete_settings(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_settings(self): + async def test_load_settings(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.load_settings, - "tenant-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.load_settings( + "tenant-id", + ) + ) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"tenant": {"id": "T2AAAA", "name": "myTenantName", "selfProvisioningDomains": [], "customAttributes": {}, "authType": "saml", "domains": ["lulu", "kuku"]}, "saml": {"idpEntityId": "", "idpSSOUrl": "", "idpCertificate": "", "defaultSSORoles": ["aa", "bb"], "idpMetadataUrl": "https://dummy.com/metadata", "spEntityId": "", "spACSUrl": "", "spCertificate": "", "attributeMapping": {"name": "name", "email": "email", "username": "", "phoneNumber": "phone", "group": "", "givenName": "", "middleName": "", "familyName": "", "picture": "", "customAttributes": {}}, "groupsMapping": [], "redirectUrl": ""}, "oidc": {"name": "", "clientId": "", "clientSecret": "", "redirectUrl": "", "authUrl": "", "tokenUrl": "", "userDataUrl": "", "scope": [], "JWKsUrl": "", "userAttrMapping": {"loginId": "sub", "username": "", "name": "name", "email": "email", "phoneNumber": "phone_number", "verifiedEmail": "email_verified", "verifiedPhone": "phone_number_verified", "picture": "picture", "givenName": "given_name", "middleName": "middle_name", "familyName": "family_name"}, "manageProviderTokens": false, "callbackDomain": "", "prompt": [], "grantType": "authorization_code", "issuer": ""}}""" ) mock_get.return_value = network_resp - resp = client.mgmt.sso.load_settings("T2AAAA") + resp = await futu_await(client.mgmt.sso.load_settings("T2AAAA")) tenant = resp.get("tenant", {}) self.assertEqual(tenant.get("id", ""), "T2AAAA") self.assertEqual(tenant.get("domains", []), ["lulu", "kuku"]) @@ -120,58 +125,62 @@ def test_load_settings(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_configure_oidc_settings(self): + async def test_configure_oidc_settings(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.configure_oidc_settings, - "tenant-id", - SSOOIDCSettings( - name="myName", - client_id="cid", - ), - ["domain.com"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.configure_oidc_settings( + "tenant-id", + SSOOIDCSettings( + name="myName", + client_id="cid", + ), + ["domain.com"], + ), + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.configure_oidc_settings( - "tenant-id", - SSOOIDCSettings( - name="myName", - client_id="cid", - client_secret="secret", - redirect_url="http://dummy.com/", - auth_url="http://dummy.com/auth", - token_url="http://dummy.com/token", - user_data_url="http://dummy.com/userInfo", - scope=["openid", "profile", "email"], - attribute_mapping=OIDCAttributeMapping( - login_id="my-id", - name="name", - given_name="givenName", - middle_name="middleName", - family_name="familyName", - email="email", - verified_email="verifiedEmail", - username="username", - phone_number="phoneNumber", - verified_phone="verifiedPhone", - picture="picture", + await futu_await( + client.mgmt.sso.configure_oidc_settings( + "tenant-id", + SSOOIDCSettings( + name="myName", + client_id="cid", + client_secret="secret", + redirect_url="http://dummy.com/", + auth_url="http://dummy.com/auth", + token_url="http://dummy.com/token", + user_data_url="http://dummy.com/userInfo", + scope=["openid", "profile", "email"], + attribute_mapping=OIDCAttributeMapping( + login_id="my-id", + name="name", + given_name="givenName", + middle_name="middleName", + family_name="familyName", + email="email", + verified_email="verifiedEmail", + username="username", + phone_number="phoneNumber", + verified_phone="verifiedPhone", + picture="picture", + ), ), - ), - ["domain.com"], + ["domain.com"], + ) ) ) mock_post.assert_called_with( @@ -220,60 +229,66 @@ def test_configure_oidc_settings(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_configure_saml_settings(self): + async def test_configure_saml_settings(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.configure_saml_settings, - "tenant-id", - SSOSAMLSettings( - idp_url="http://dummy.com", - idp_entity_id="ent1234", - idp_cert="cert", - sp_acs_url="http://spacsurl.com", - sp_entity_id="spentityid", - default_sso_roles=["aa", "bb"], - ), - "https://redirect.com", - ["domain.com"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.configure_saml_settings( + "tenant-id", + SSOSAMLSettings( + idp_url="http://dummy.com", + idp_entity_id="ent1234", + idp_cert="cert", + sp_acs_url="http://spacsurl.com", + sp_entity_id="spentityid", + default_sso_roles=["aa", "bb"], + ), + "https://redirect.com", + ["domain.com"], + ), + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.configure_saml_settings( - "tenant-id", - SSOSAMLSettings( - idp_url="http://dummy.com", - idp_entity_id="ent1234", - idp_cert="cert", - attribute_mapping=AttributeMapping( - name="name", - given_name="givenName", - middle_name="middleName", - family_name="familyName", - picture="picture", - email="email", - phone_number="phoneNumber", - group="groups", + await futu_await( + client.mgmt.sso.configure_saml_settings( + "tenant-id", + SSOSAMLSettings( + idp_url="http://dummy.com", + idp_entity_id="ent1234", + idp_cert="cert", + attribute_mapping=AttributeMapping( + name="name", + given_name="givenName", + middle_name="middleName", + family_name="familyName", + picture="picture", + email="email", + phone_number="phoneNumber", + group="groups", + ), + role_mappings=[ + RoleMapping(groups=["grp1"], role_name="rl1") + ], + sp_acs_url="http://spacsurl.com", + sp_entity_id="spentityid", + default_sso_roles=["aa", "bb"], ), - role_mappings=[RoleMapping(groups=["grp1"], role_name="rl1")], - sp_acs_url="http://spacsurl.com", - sp_entity_id="spentityid", - default_sso_roles=["aa", "bb"], - ), - "https://redirect.com", - ["domain.com"], + "https://redirect.com", + ["domain.com"], + ) ) ) mock_post.assert_called_with( @@ -314,51 +329,59 @@ def test_configure_saml_settings(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_configure_saml_settings_by_metadata(self): + async def test_configure_saml_settings_by_metadata(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.configure_saml_settings_by_metadata, - "tenant-id", - SSOSAMLSettingsByMetadata(idp_metadata_url="http://dummy.com/metadata"), - "https://redirect.com", - ["domain.com"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.configure_saml_settings_by_metadata( + "tenant-id", + SSOSAMLSettingsByMetadata( + idp_metadata_url="http://dummy.com/metadata" + ), + "https://redirect.com", + ["domain.com"], + ), + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.configure_saml_settings_by_metadata( - "tenant-id", - SSOSAMLSettingsByMetadata( - idp_metadata_url="http://dummy.com/metadata", - attribute_mapping=AttributeMapping( - name="name", - given_name="givenName", - middle_name="middleName", - family_name="familyName", - picture="picture", - email="email", - phone_number="phoneNumber", - group="groups", + await futu_await( + client.mgmt.sso.configure_saml_settings_by_metadata( + "tenant-id", + SSOSAMLSettingsByMetadata( + idp_metadata_url="http://dummy.com/metadata", + attribute_mapping=AttributeMapping( + name="name", + given_name="givenName", + middle_name="middleName", + family_name="familyName", + picture="picture", + email="email", + phone_number="phoneNumber", + group="groups", + ), + role_mappings=[ + RoleMapping(groups=["grp1"], role_name="rl1") + ], + sp_acs_url="http://spacsurl.com", + sp_entity_id="spentityid", + default_sso_roles=["aa", "bb"], ), - role_mappings=[RoleMapping(groups=["grp1"], role_name="rl1")], - sp_acs_url="http://spacsurl.com", - sp_entity_id="spentityid", - default_sso_roles=["aa", "bb"], - ), - "https://redirect.com", - ["domain.com"], + "https://redirect.com", + ["domain.com"], + ) ) ) mock_post.assert_called_with( @@ -397,36 +420,39 @@ def test_configure_saml_settings_by_metadata(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_attribute_mapping_to_dict(self): - self.assertRaises(ValueError, SSOSettings._attribute_mapping_to_dict, None) + async def test_attribute_mapping_to_dict(self): + with self.assertRaises(ValueError): + await futu_await(SSOSettings._attribute_mapping_to_dict(None)) # Testing DEPRECATED functions - def test_get_settings(self): + async def test_get_settings(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.get_settings, - "tenant-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.get_settings( + "tenant-id", + ) + ) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"domains": ["lulu", "kuku"], "tenantId": "tenant-id"}""" ) mock_get.return_value = network_resp - resp = client.mgmt.sso.get_settings("tenant-id") + resp = await futu_await(client.mgmt.sso.get_settings("tenant-id")) self.assertEqual(resp["tenantId"], "tenant-id") self.assertEqual(resp["domains"], ["lulu", "kuku"]) mock_get.assert_called_with( @@ -442,39 +468,43 @@ def test_get_settings(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_configure(self): + async def test_configure(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.configure, - "tenant-id", - "https://idp.com", - "entity-id", - "cert", - "https://redirect.com", - ["domain.com"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.configure( + "tenant-id", + "https://idp.com", + "entity-id", + "cert", + "https://redirect.com", + ["domain.com"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.configure( - "tenant-id", - "https://idp.com", - "entity-id", - "cert", - "https://redirect.com", - ["domain.com"], + await futu_await( + client.mgmt.sso.configure( + "tenant-id", + "https://idp.com", + "entity-id", + "cert", + "https://redirect.com", + ["domain.com"], + ) ) ) mock_post.assert_called_with( @@ -499,15 +529,17 @@ def test_configure(self): ) # Domain is optional - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.configure( - "tenant-id", - "https://idp.com", - "entity-id", - "cert", - "https://redirect.com", + await futu_await( + client.mgmt.sso.configure( + "tenant-id", + "https://idp.com", + "entity-id", + "cert", + "https://redirect.com", + ) ) ) mock_post.assert_called_with( @@ -532,16 +564,18 @@ def test_configure(self): ) # Redirect is optional - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.configure( - "tenant-id", - "https://idp.com", - "entity-id", - "cert", - "", - ["domain.com"], + await futu_await( + client.mgmt.sso.configure( + "tenant-id", + "https://idp.com", + "entity-id", + "cert", + "", + ["domain.com"], + ) ) ) mock_post.assert_called_with( @@ -565,35 +599,39 @@ def test_configure(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_configure_via_metadata(self): + async def test_configure_via_metadata(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.configure_via_metadata, - "tenant-id", - "https://idp-meta.com", - "https://redirect.com", - ["domain.com"], - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.configure_via_metadata( + "tenant-id", + "https://idp-meta.com", + "https://redirect.com", + ["domain.com"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.configure_via_metadata( - "tenant-id", - "https://idp-meta.com", - "https://redirect.com", - ["domain.com"], + await futu_await( + client.mgmt.sso.configure_via_metadata( + "tenant-id", + "https://idp-meta.com", + "https://redirect.com", + ["domain.com"], + ) ) ) mock_post.assert_called_with( @@ -616,12 +654,14 @@ def test_configure_via_metadata(self): ) # Test partial arguments - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.configure_via_metadata( - "tenant-id", - "https://idp-meta.com", + await futu_await( + client.mgmt.sso.configure_via_metadata( + "tenant-id", + "https://idp-meta.com", + ) ) ) mock_post.assert_called_with( @@ -643,33 +683,37 @@ def test_configure_via_metadata(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_mapping(self): + async def test_mapping(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.sso.mapping, - "tenant-id", - [RoleMapping(["a", "b"], "role")], - AttributeMapping(name="UName"), - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.sso.mapping( + "tenant-id", + [RoleMapping(["a", "b"], "role")], + AttributeMapping(name="UName"), + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.sso.mapping( - "tenant-id", - [RoleMapping(["a", "b"], "role")], - AttributeMapping(name="UName"), + await futu_await( + client.mgmt.sso.mapping( + "tenant-id", + [RoleMapping(["a", "b"], "role")], + AttributeMapping(name="UName"), + ) ) ) mock_post.assert_called_with( diff --git a/tests/management/test_tenant.py b/tests/management/test_tenant.py index 74a35bf4b..a1d98a142 100644 --- a/tests/management/test_tenant.py +++ b/tests/management/test_tenant.py @@ -4,9 +4,10 @@ from descope import AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS +from descope.future_utils import futu_await from descope.management.common import MgmtV1 -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -25,30 +26,34 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_create(self): + async def test_create(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.tenant.create, - "valid-name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.tenant.create( + "valid-name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"id": "t1"}""") mock_post.return_value = network_resp - resp = client.mgmt.tenant.create("name", "t1", ["domain.com"]) + resp = await futu_await( + client.mgmt.tenant.create("name", "t1", ["domain.com"]) + ) self.assertEqual(resp["id"], "t1") mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.tenant_create_path}", @@ -71,18 +76,20 @@ def test_create(self): ) # Test success flow with custom attributes, enforce_sso, disabled - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"id": "t1"}""") mock_post.return_value = network_resp - resp = client.mgmt.tenant.create( - "name", - "t1", - ["domain.com"], - {"k1": "v1"}, - enforce_sso=True, - disabled=True, + resp = await futu_await( + client.mgmt.tenant.create( + "name", + "t1", + ["domain.com"], + {"k1": "v1"}, + enforce_sso=True, + disabled=True, + ) ) self.assertEqual(resp["id"], "t1") mock_post.assert_called_with( @@ -106,30 +113,38 @@ def test_create(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update(self): + async def test_update(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.tenant.update, - "valid-id", - "valid-name", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.tenant.update( + "valid-id", + "valid-name", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.tenant.update( - "t1", "new-name", ["domain.com"], enforce_sso=True, disabled=True + await futu_await( + client.mgmt.tenant.update( + "t1", + "new-name", + ["domain.com"], + enforce_sso=True, + disabled=True, + ) ) ) mock_post.assert_called_with( @@ -153,16 +168,18 @@ def test_update(self): ) # Test success flow with custom attributes, enforce_sso, disabled - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNone( - client.mgmt.tenant.update( - "t1", - "new-name", - ["domain.com"], - {"k1": "v1"}, - enforce_sso=True, - disabled=True, + await futu_await( + client.mgmt.tenant.update( + "t1", + "new-name", + ["domain.com"], + {"k1": "v1"}, + enforce_sso=True, + disabled=True, + ) ) ) mock_post.assert_called_with( @@ -186,27 +203,29 @@ def test_update(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete(self): + async def test_delete(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.tenant.delete, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.tenant.delete( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(client.mgmt.tenant.delete("t1", True)) + self.assertIsNone(await futu_await(client.mgmt.tenant.delete("t1", True))) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.tenant_delete_path}", headers={ @@ -221,25 +240,27 @@ def test_delete(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load(self): + async def test_load(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - client.mgmt.tenant.load, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + client.mgmt.tenant.load( + "valid-id", + ) + ) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -248,7 +269,7 @@ def test_load(self): """ ) mock_get.return_value = network_resp - resp = client.mgmt.tenant.load("t1") + resp = await futu_await(client.mgmt.tenant.load("t1")) self.assertEqual(resp["name"], "tenant1") self.assertEqual(resp["createdTime"], 172606520) mock_get.assert_called_with( @@ -264,21 +285,23 @@ def test_load(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_all(self): + async def test_load_all(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.tenant.load_all) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.tenant.load_all()) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -292,7 +315,7 @@ def test_load_all(self): """ ) mock_get.return_value = network_resp - resp = client.mgmt.tenant.load_all() + resp = await futu_await(client.mgmt.tenant.load_all()) tenants = resp["tenants"] self.assertEqual(len(tenants), 2) self.assertEqual(tenants[0]["name"], "tenant1") @@ -311,21 +334,23 @@ def test_load_all(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_search_all(self): + async def test_search_all(self): client = DescopeClient( self.dummy_project_id, self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.mgmt.tenant.search_all) + with self.assertRaises(AuthException): + await futu_await(client.mgmt.tenant.search_all()) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -339,11 +364,13 @@ def test_search_all(self): """ ) mock_post.return_value = network_resp - resp = client.mgmt.tenant.search_all( - ids=["id1"], - names=["name1"], - custom_attributes={"k1": "v1"}, - self_provisioning_domains=["spd1"], + resp = await futu_await( + client.mgmt.tenant.search_all( + ids=["id1"], + names=["name1"], + custom_attributes={"k1": "v1"}, + self_provisioning_domains=["spd1"], + ) ) tenants = resp["tenants"] self.assertEqual(len(tenants), 2) diff --git a/tests/management/test_user.py b/tests/management/test_user.py index 55e71af2e..30f53b9f6 100644 --- a/tests/management/test_user.py +++ b/tests/management/test_user.py @@ -4,6 +4,7 @@ from descope import AssociatedTenant, AuthException, DescopeClient from descope.common import DEFAULT_TIMEOUT_SECONDS, DeliveryMethod, LoginOptions +from descope.future_utils import futu_await from descope.management.common import MgmtV1, Sort from descope.management.user import UserObj from descope.management.user_pwd import ( @@ -14,7 +15,7 @@ UserPasswordPbkdf2, ) -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call from .. import common @@ -37,36 +38,40 @@ def setUp(self) -> None: self.public_key_dict, False, self.dummy_management_key, + async_mode=self.async_test, ) - def test_create(self): + async def test_create(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.create, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.create( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.create( - login_id="name@mail.com", - email="name@mail.com", - display_name="Name", - user_tenants=[ - AssociatedTenant("tenant1"), - AssociatedTenant("tenant2", ["role1", "role2"]), - ], - picture="https://test.com", - custom_attributes={"ak": "av"}, - additional_login_ids=["id-1", "id-2"], - sso_app_ids=["app1", "app2"], + resp = await futu_await( + self.client.mgmt.user.create( + login_id="name@mail.com", + email="name@mail.com", + display_name="Name", + user_tenants=[ + AssociatedTenant("tenant1"), + AssociatedTenant("tenant2", ["role1", "role2"]), + ], + picture="https://test.com", + custom_attributes={"ak": "av"}, + additional_login_ids=["id-1", "id-2"], + sso_app_ids=["app1", "app2"], + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -100,25 +105,27 @@ def test_create(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_create_with_verified_parameters(self): + async def test_create_with_verified_parameters(self): # Test success flow with verified email and phone - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.create( - login_id="name@mail.com", - email="name@mail.com", - display_name="Name", - user_tenants=[ - AssociatedTenant("tenant1"), - AssociatedTenant("tenant2", ["role1", "role2"]), - ], - picture="https://test.com", - custom_attributes={"ak": "av"}, - verified_email=True, - verified_phone=False, + resp = await futu_await( + self.client.mgmt.user.create( + login_id="name@mail.com", + email="name@mail.com", + display_name="Name", + user_tenants=[ + AssociatedTenant("tenant1"), + AssociatedTenant("tenant2", ["role1", "role2"]), + ], + picture="https://test.com", + custom_attributes={"ak": "av"}, + verified_email=True, + verified_phone=False, + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -154,31 +161,34 @@ def test_create_with_verified_parameters(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_create_test_user(self): + async def test_create_test_user(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.create, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.create( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.create_test_user( - login_id="name@mail.com", - email="name@mail.com", - display_name="Name", - user_tenants=[ - AssociatedTenant("tenant1"), - AssociatedTenant("tenant2", ["role1", "role2"]), - ], - custom_attributes={"ak": "av"}, + resp = await futu_await( + self.client.mgmt.user.create_test_user( + login_id="name@mail.com", + email="name@mail.com", + display_name="Name", + user_tenants=[ + AssociatedTenant("tenant1"), + AssociatedTenant("tenant2", ["role1", "role2"]), + ], + custom_attributes={"ak": "av"}, + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -212,35 +222,38 @@ def test_create_test_user(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_invite(self): + async def test_invite(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.invite, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.invite( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.invite( - login_id="name@mail.com", - email="name@mail.com", - display_name="Name", - user_tenants=[ - AssociatedTenant("tenant1"), - AssociatedTenant("tenant2", ["role1", "role2"]), - ], - custom_attributes={"ak": "av"}, - invite_url="invite.me", - send_sms=True, - sso_app_ids=["app1", "app2"], - template_id="tid", + resp = await futu_await( + self.client.mgmt.user.invite( + login_id="name@mail.com", + email="name@mail.com", + display_name="Name", + user_tenants=[ + AssociatedTenant("tenant1"), + AssociatedTenant("tenant2", ["role1", "role2"]), + ], + custom_attributes={"ak": "av"}, + invite_url="invite.me", + send_sms=True, + sso_app_ids=["app1", "app2"], + template_id="tid", + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -277,18 +290,19 @@ def test_invite(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_invite_batch(self): + async def test_invite_batch(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.invite_batch, - [], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.invite_batch( + [], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"users": [{"id": "u1"}]}""") @@ -316,10 +330,12 @@ def test_invite_batch(self): seed="aaa", status="invited", ) - resp = self.client.mgmt.user.invite_batch( - users=[user], - invite_url="invite.me", - send_sms=True, + resp = await futu_await( + self.client.mgmt.user.invite_batch( + users=[user], + invite_url="invite.me", + send_sms=True, + ) ) users = resp["users"] self.assertEqual(users[0]["id"], "u1") @@ -398,10 +414,12 @@ def test_invite_batch(self): self.assertEqual(django.to_dict(), {"django": {"hash": "h"}}) user.password = UserPassword(cleartext="clear") - resp = self.client.mgmt.user.invite_batch( - users=[user], - invite_url="invite.me", - send_sms=True, + resp = await futu_await( + self.client.mgmt.user.invite_batch( + users=[user], + invite_url="invite.me", + send_sms=True, + ) ) del expected_users["users"][0]["hashedPassword"] @@ -421,10 +439,12 @@ def test_invite_batch(self): ) user.password = None - resp = self.client.mgmt.user.invite_batch( - users=[user], - invite_url="invite.me", - send_sms=True, + resp = await futu_await( + self.client.mgmt.user.invite_batch( + users=[user], + invite_url="invite.me", + send_sms=True, + ) ) del expected_users["users"][0]["password"] @@ -442,30 +462,33 @@ def test_invite_batch(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update(self): + async def test_update(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.update, - "valid-id", - "email@something.com", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.update( + "valid-id", + "email@something.com", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.update( - "id", - display_name="new-name", - role_names=["domain.com"], - picture="https://test.com", - custom_attributes={"ak": "av"}, - sso_app_ids=["app1", "app2"], + resp = await futu_await( + self.client.mgmt.user.update( + "id", + display_name="new-name", + role_names=["domain.com"], + picture="https://test.com", + custom_attributes={"ak": "av"}, + sso_app_ids=["app1", "app2"], + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -495,13 +518,15 @@ def test_update(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) # Test success flow with verified flags - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.update( - "id", verified_email=True, verified_phone=False + resp = await futu_await( + self.client.mgmt.user.update( + "id", verified_email=True, verified_phone=False + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -533,34 +558,37 @@ def test_update(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_patch(self): + async def test_patch(self): # Test failed flows - with patch("httpx.patch") as mock_patch: + with mock_http_call(self.async_test, "patch") as mock_patch: mock_patch.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.patch, - "valid-id", - "email@something.com", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.patch( + "valid-id", + "email@something.com", + ) + ) # Test success flow with some params set - with patch("httpx.patch") as mock_patch: + with mock_http_call(self.async_test, "patch") as mock_patch: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_patch.return_value = network_resp - resp = self.client.mgmt.user.patch( - "id", - display_name="new-name", - email=None, - phone=None, - given_name=None, - role_names=["domain.com"], - user_tenants=None, - picture="https://test.com", - custom_attributes={"ak": "av"}, - sso_app_ids=["app1", "app2"], + resp = await futu_await( + self.client.mgmt.user.patch( + "id", + display_name="new-name", + email=None, + phone=None, + given_name=None, + role_names=["domain.com"], + user_tenants=None, + picture="https://test.com", + custom_attributes={"ak": "av"}, + sso_app_ids=["app1", "app2"], + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -585,26 +613,28 @@ def test_patch(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) # Test success flow with other params - with patch("httpx.patch") as mock_patch: + with mock_http_call(self.async_test, "patch") as mock_patch: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_patch.return_value = network_resp - resp = self.client.mgmt.user.patch( - "id", - email="a@test.com", - phone="+123456789", - given_name="given", - middle_name="middle", - family_name="family", - role_names=None, - user_tenants=[ - AssociatedTenant("tenant1"), - AssociatedTenant("tenant2", ["role1", "role2"]), - ], - custom_attributes=None, - verified_email=True, - verified_phone=False, + resp = await futu_await( + self.client.mgmt.user.patch( + "id", + email="a@test.com", + phone="+123456789", + given_name="given", + middle_name="middle", + family_name="family", + role_names=None, + user_tenants=[ + AssociatedTenant("tenant1"), + AssociatedTenant("tenant2", ["role1", "role2"]), + ], + custom_attributes=None, + verified_email=True, + verified_phone=False, + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -635,7 +665,7 @@ def test_patch(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_patch_with_status(self): + async def test_patch_with_status(self): # Test invalid status value with self.assertRaises(AuthException) as context: self.client.mgmt.user.patch("valid-id", status="invalid_status") @@ -647,15 +677,12 @@ def test_patch_with_status(self): valid_statuses = ["enabled", "disabled", "invited"] for status in valid_statuses: - with patch("httpx.patch") as mock_patch: - network_resp = mock.Mock() - network_resp.is_success = True - network_resp.json.return_value = json.loads( - """{"user": {"id": "u1"}}""" + with mock_http_call(self.async_test, "patch") as mock_patch: + mock_patch.return_value.is_success = True + mock_patch.return_value.json.return_value = {"user": {"id": "u1"}} + resp = await futu_await( + self.client.mgmt.user.patch("id", status=status) ) - mock_patch.return_value = network_resp - - resp = self.client.mgmt.user.patch("id", status=status) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -677,13 +704,13 @@ def test_patch_with_status(self): ) # Test that status is not included when None - with patch("httpx.patch") as mock_patch: - network_resp = mock.Mock() - network_resp.is_success = True - network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") - mock_patch.return_value = network_resp + with mock_http_call(self.async_test, "patch") as mock_patch: + mock_patch.return_value.is_success = True + mock_patch.return_value.json.return_value = {"user": {"id": "u1"}} - resp = self.client.mgmt.user.patch("id", display_name="test", status=None) + resp = await futu_await( + self.client.mgmt.user.patch("id", display_name="test", status=None) + ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -693,7 +720,7 @@ def test_patch_with_status(self): self.assertNotIn("status", json_payload) self.assertEqual(json_payload["displayName"], "test") - def test_patch_batch(self): + async def test_patch_batch(self): # Test invalid status value in batch users_with_invalid_status = [ UserObj(login_id="user1", status="invalid_status"), @@ -716,15 +743,14 @@ def test_patch_batch(self): UserObj(login_id="user3", phone="+123456789", status="invited"), ] - with patch("httpx.patch") as mock_patch: - network_resp = mock.Mock() - network_resp.is_success = True - network_resp.json.return_value = json.loads( - """{"patchedUsers": [{"id": "u1"}, {"id": "u2"}, {"id": "u3"}], "failedUsers": []}""" - ) - mock_patch.return_value = network_resp + with mock_http_call(self.async_test, "patch") as mock_patch: + mock_patch.return_value.is_success = True + mock_patch.return_value.json.return_value = { + "patchedUsers": [{"id": "u1"}, {"id": "u2"}, {"id": "u3"}], + "failedUsers": [], + } - resp = self.client.mgmt.user.patch_batch(users) + resp = await futu_await(self.client.mgmt.user.patch_batch(users)) self.assertEqual(len(resp["patchedUsers"]), 3) self.assertEqual(len(resp["failedUsers"]), 0) @@ -762,16 +788,19 @@ def test_patch_batch(self): ) # Test batch with mixed success/failure response - with patch("httpx.patch") as mock_patch: - network_resp = mock.Mock() - network_resp.is_success = True - network_resp.json.return_value = json.loads( - """{"patchedUsers": [{"id": "u1"}], "failedUsers": [{"failure": "User not found", "user": {"loginId": "user2"}}]}""" - ) - mock_patch.return_value = network_resp + with mock_http_call(self.async_test, "patch") as mock_patch: + mock_patch.return_value.is_success = True + mock_patch.return_value.json.return_value = { + "patchedUsers": [{"id": "u1"}], + "failedUsers": [ + {"failure": "User not found", "user": {"loginId": "user2"}} + ], + } - resp = self.client.mgmt.user.patch_batch( - [UserObj(login_id="user1"), UserObj(login_id="user2")] + resp = await futu_await( + self.client.mgmt.user.patch_batch( + [UserObj(login_id="user1"), UserObj(login_id="user2")] + ) ) self.assertEqual(len(resp["patchedUsers"]), 1) @@ -779,45 +808,46 @@ def test_patch_batch(self): self.assertEqual(resp["failedUsers"][0]["failure"], "User not found") # Test failed batch operation - with patch("httpx.patch") as mock_patch: + with mock_http_call(self.async_test, "patch") as mock_patch: mock_patch.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.patch_batch, - [UserObj(login_id="user1")], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.patch_batch([UserObj(login_id="user1")]) + ) # Test with test users flag - with patch("httpx.patch") as mock_patch: - network_resp = mock.Mock() - network_resp.is_success = True - network_resp.json.return_value = json.loads( - """{"patchedUsers": [{"id": "u1"}], "failedUsers": []}""" - ) - mock_patch.return_value = network_resp + with mock_http_call(self.async_test, "patch") as mock_patch: + mock_patch.return_value.is_success = True + mock_patch.return_value.json.return_value = { + "patchedUsers": [{"id": "u1"}], + "failedUsers": [], + } - resp = self.client.mgmt.user.patch_batch( - [UserObj(login_id="test_user1")], test=True + resp = await futu_await( + self.client.mgmt.user.patch_batch( + [UserObj(login_id="test_user1")], test=True + ) ) call_args = mock_patch.call_args json_payload = call_args[1]["json"] self.assertTrue(json_payload["users"][0]["test"]) - def test_delete(self): + async def test_delete(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.delete, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.delete( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(self.client.mgmt.user.delete("u1")) + self.assertIsNone(await futu_await(self.client.mgmt.user.delete("u1"))) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_delete_path}", headers={ @@ -834,20 +864,23 @@ def test_delete(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_by_user_id(self): + async def test_delete_by_user_id(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.delete_by_user_id, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.delete_by_user_id( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(self.client.mgmt.user.delete_by_user_id("u1")) + self.assertIsNone( + await futu_await(self.client.mgmt.user.delete_by_user_id("u1")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_delete_path}", headers={ @@ -864,20 +897,21 @@ def test_delete_by_user_id(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_logout(self): + async def test_logout(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.logout_user, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.logout_user( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(self.client.mgmt.user.logout_user("u1")) + self.assertIsNone(await futu_await(self.client.mgmt.user.logout_user("u1"))) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_logout_path}", headers={ @@ -894,20 +928,23 @@ def test_logout(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_logout_by_user_id(self): + async def test_logout_by_user_id(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.logout_user_by_user_id, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.logout_user_by_user_id( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNone(self.client.mgmt.user.logout_user_by_user_id("u1")) + self.assertIsNone( + await futu_await(self.client.mgmt.user.logout_user_by_user_id("u1")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_logout_path}", headers={ @@ -924,19 +961,19 @@ def test_logout_by_user_id(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_delete_all_test_users(self): + async def test_delete_all_test_users(self): # Test failed flows - with patch("httpx.delete") as mock_delete: + with mock_http_call(self.async_test, "delete") as mock_delete: mock_delete.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.delete_all_test_users, - ) + with self.assertRaises(AuthException): + await futu_await(self.client.mgmt.user.delete_all_test_users()) # Test success flow - with patch("httpx.delete") as mock_delete: + with mock_http_call(self.async_test, "delete") as mock_delete: mock_delete.return_value.is_success = True - self.assertIsNone(self.client.mgmt.user.delete_all_test_users()) + self.assertIsNone( + await futu_await(self.client.mgmt.user.delete_all_test_users()) + ) mock_delete.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_delete_all_test_users_path}", params=None, @@ -950,23 +987,24 @@ def test_delete_all_test_users(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load(self): + async def test_load(self): # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.load, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.load( + "valid-id", + ) + ) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_get.return_value = network_resp - resp = self.client.mgmt.user.load("valid-id") + resp = await futu_await(self.client.mgmt.user.load("valid-id")) user = resp["user"] self.assertEqual(user["id"], "u1") mock_get.assert_called_with( @@ -982,23 +1020,24 @@ def test_load(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_load_by_user_id(self): + async def test_load_by_user_id(self): # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.load_by_user_id, - "user-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.load_by_user_id( + "user-id", + ) + ) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_get.return_value = network_resp - resp = self.client.mgmt.user.load_by_user_id("user-id") + resp = await futu_await(self.client.mgmt.user.load_by_user_id("user-id")) user = resp["user"] self.assertEqual(user["id"], "u1") mock_get.assert_called_with( @@ -1014,41 +1053,43 @@ def test_load_by_user_id(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_search_all(self): + async def test_search_all(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.search_all, - ["t1, t2"], - ["r1", "r2"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.search_all( + ["t1, t2"], + ["r1", "r2"], + ) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertRaises( - AuthException, self.client.mgmt.user.search_all, [], [], -1, 0 - ) + with self.assertRaises(AuthException): + await futu_await(self.client.mgmt.user.search_all([], [], -1, 0)) - self.assertRaises( - AuthException, self.client.mgmt.user.search_all, [], [], 0, -1 - ) + with self.assertRaises(AuthException): + + await futu_await(self.client.mgmt.user.search_all([], [], 0, -1)) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"users": [{"id": "u1"}, {"id": "u2"}]}""" ) mock_post.return_value = network_resp - resp = self.client.mgmt.user.search_all( - ["t1, t2"], - ["r1", "r2"], - with_test_user=True, - sso_app_ids=["app1"], - login_ids=["l1"], + resp = await futu_await( + self.client.mgmt.user.search_all( + ["t1, t2"], + ["r1", "r2"], + with_test_user=True, + sso_app_ids=["app1"], + login_ids=["l1"], + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1078,7 +1119,7 @@ def test_search_all(self): ) # Test success flow with text and sort - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -1086,13 +1127,15 @@ def test_search_all(self): ) mock_post.return_value = network_resp sort = [Sort(field="kuku", desc=True), Sort(field="bubu")] - resp = self.client.mgmt.user.search_all( - ["t1, t2"], - ["r1", "r2"], - with_test_user=True, - sso_app_ids=["app1"], - text="blue", - sort=sort, + resp = await futu_await( + self.client.mgmt.user.search_all( + ["t1, t2"], + ["r1", "r2"], + with_test_user=True, + sso_app_ids=["app1"], + text="blue", + sort=sort, + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1126,21 +1169,23 @@ def test_search_all(self): ) # Test success flow with custom attributes - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"users": [{"id": "u1"}, {"id": "u2"}]}""" ) mock_post.return_value = network_resp - resp = self.client.mgmt.user.search_all( - ["t1, t2"], - ["r1", "r2"], - with_test_user=True, - custom_attributes={"ak": "av"}, - statuses=["invited"], - phones=["+111111"], - emails=["a@b.com"], + resp = await futu_await( + self.client.mgmt.user.search_all( + ["t1, t2"], + ["r1", "r2"], + with_test_user=True, + custom_attributes={"ak": "av"}, + statuses=["invited"], + phones=["+111111"], + emails=["a@b.com"], + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1172,20 +1217,22 @@ def test_search_all(self): ) # Test success flow with time parameters - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"users": [{"id": "u1"}, {"id": "u2"}]}""" ) mock_post.return_value = network_resp - resp = self.client.mgmt.user.search_all( - from_created_time=100, - to_created_time=200, - from_modified_time=300, - to_modified_time=400, - limit=10, - page=0, + resp = await futu_await( + self.client.mgmt.user.search_all( + from_created_time=100, + to_created_time=200, + from_modified_time=300, + to_modified_time=400, + limit=10, + page=0, + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1218,20 +1265,22 @@ def test_search_all(self): ) # Test success flow with tenant_role_ids and tenant_role_names - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"users": [{"id": "u1"}, {"id": "u2"}]}""" ) mock_post.return_value = network_resp - resp = self.client.mgmt.user.search_all( - tenant_role_ids={ - "tenant1": {"values": ["roleA", "roleB"], "and": True} - }, - tenant_role_names={ - "tenant2": {"values": ["admin", "user"], "and": False} - }, + resp = await futu_await( + self.client.mgmt.user.search_all( + tenant_role_ids={ + "tenant1": {"values": ["roleA", "roleB"], "and": True} + }, + tenant_role_names={ + "tenant2": {"values": ["admin", "user"], "and": False} + }, + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1264,50 +1313,56 @@ def test_search_all(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_search_all_test_users(self): + async def test_search_all_test_users(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.search_all_test_users, - ["t1, t2"], - ["r1", "r2"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.search_all_test_users( + ["t1, t2"], + ["r1", "r2"], + ) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertRaises( - AuthException, - self.client.mgmt.user.search_all_test_users, - [], - [], - -1, - 0, - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.search_all_test_users( + [], + [], + -1, + 0, + ) + ) - self.assertRaises( - AuthException, - self.client.mgmt.user.search_all_test_users, - [], - [], - 0, - -1, - ) + with self.assertRaises(AuthException): + + await futu_await( + self.client.mgmt.user.search_all_test_users( + [], + [], + 0, + -1, + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"users": [{"id": "u1"}, {"id": "u2"}]}""" ) mock_post.return_value = network_resp - resp = self.client.mgmt.user.search_all_test_users( - ["t1, t2"], - ["r1", "r2"], - sso_app_ids=["app1"], - login_ids=["l1"], + resp = await futu_await( + self.client.mgmt.user.search_all_test_users( + ["t1, t2"], + ["r1", "r2"], + sso_app_ids=["app1"], + login_ids=["l1"], + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1337,7 +1392,7 @@ def test_search_all_test_users(self): ) # Test success flow with text and sort - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -1345,12 +1400,14 @@ def test_search_all_test_users(self): ) mock_post.return_value = network_resp sort = [Sort(field="kuku", desc=True), Sort(field="bubu")] - resp = self.client.mgmt.user.search_all_test_users( - ["t1, t2"], - ["r1", "r2"], - sso_app_ids=["app1"], - text="blue", - sort=sort, + resp = await futu_await( + self.client.mgmt.user.search_all_test_users( + ["t1, t2"], + ["r1", "r2"], + sso_app_ids=["app1"], + text="blue", + sort=sort, + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1384,20 +1441,22 @@ def test_search_all_test_users(self): ) # Test success flow with custom attributes - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"users": [{"id": "u1"}, {"id": "u2"}]}""" ) mock_post.return_value = network_resp - resp = self.client.mgmt.user.search_all_test_users( - ["t1, t2"], - ["r1", "r2"], - custom_attributes={"ak": "av"}, - statuses=["invited"], - phones=["+111111"], - emails=["a@b.com"], + resp = await futu_await( + self.client.mgmt.user.search_all_test_users( + ["t1, t2"], + ["r1", "r2"], + custom_attributes={"ak": "av"}, + statuses=["invited"], + phones=["+111111"], + emails=["a@b.com"], + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1429,18 +1488,20 @@ def test_search_all_test_users(self): ) # Test success flow with time parameters - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"users": [{"id": "u1"}]}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.search_all_test_users( - from_created_time=100, - to_created_time=200, - from_modified_time=300, - to_modified_time=400, - limit=10, - page=0, + resp = await futu_await( + self.client.mgmt.user.search_all_test_users( + from_created_time=100, + to_created_time=200, + from_modified_time=300, + to_modified_time=400, + limit=10, + page=0, + ) ) users = resp["users"] self.assertEqual(len(users), 1) @@ -1472,20 +1533,22 @@ def test_search_all_test_users(self): ) # Test success flow with tenant_role_ids and tenant_role_names - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"users": [{"id": "u1"}, {"id": "u2"}]}""" ) mock_post.return_value = network_resp - resp = self.client.mgmt.user.search_all_test_users( - tenant_role_ids={ - "tenant1": {"values": ["roleA", "roleB"], "and": True} - }, - tenant_role_names={ - "tenant2": {"values": ["admin", "user"], "and": False} - }, + resp = await futu_await( + self.client.mgmt.user.search_all_test_users( + tenant_role_ids={ + "tenant1": {"values": ["roleA", "roleB"], "and": True} + }, + tenant_role_names={ + "tenant2": {"values": ["admin", "user"], "and": False} + }, + ) ) users = resp["users"] self.assertEqual(len(users), 2) @@ -1518,26 +1581,27 @@ def test_search_all_test_users(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_get_provider_token(self): + async def test_get_provider_token(self): # Test failed flows - with patch("httpx.get") as mock_post: + with mock_http_call(self.async_test, "get") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.get_provider_token, - "valid-id", - "p1", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.get_provider_token( + "valid-id", + "p1", + ) + ) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( """{"provider": "p1", "providerUserId": "puid", "accessToken": "access123", "refreshToken": "refresh456", "expiration": "123123123", "scopes": ["s1", "s2"]}""" ) mock_get.return_value = network_resp - resp = self.client.mgmt.user.get_provider_token( - "valid-id", "p1", True, True + resp = await futu_await( + self.client.mgmt.user.get_provider_token("valid-id", "p1", True, True) ) self.assertEqual(resp["provider"], "p1") self.assertEqual(resp["providerUserId"], "puid") @@ -1563,23 +1627,24 @@ def test_get_provider_token(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_activate(self): + async def test_activate(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.activate, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.activate( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.activate("valid-id") + resp = await futu_await(self.client.mgmt.user.activate("valid-id")) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1599,23 +1664,24 @@ def test_activate(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_deactivate(self): + async def test_deactivate(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.deactivate, - "valid-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.deactivate( + "valid-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.deactivate("valid-id") + resp = await futu_await(self.client.mgmt.user.deactivate("valid-id")) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1635,24 +1701,27 @@ def test_deactivate(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_login_id(self): + async def test_update_login_id(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.update_login_id, - "valid-id", - "a@b.c", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.update_login_id( + "valid-id", + "a@b.c", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "a@b.c"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.update_login_id("valid-id", "a@b.c") + resp = await futu_await( + self.client.mgmt.user.update_login_id("valid-id", "a@b.c") + ) user = resp["user"] self.assertEqual(user["id"], "a@b.c") mock_post.assert_called_with( @@ -1672,24 +1741,27 @@ def test_update_login_id(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_email(self): + async def test_update_email(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.update_email, - "valid-id", - "a@b.c", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.update_email( + "valid-id", + "a@b.c", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.update_email("valid-id", "a@b.c") + resp = await futu_await( + self.client.mgmt.user.update_email("valid-id", "a@b.c") + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1710,24 +1782,27 @@ def test_update_email(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_phone(self): + async def test_update_phone(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.update_phone, - "valid-id", - "+18005551234", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.update_phone( + "valid-id", + "+18005551234", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.update_phone("valid-id", "+18005551234", True) + resp = await futu_await( + self.client.mgmt.user.update_phone("valid-id", "+18005551234", True) + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1748,24 +1823,27 @@ def test_update_phone(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_display_name(self): + async def test_update_display_name(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.update_display_name, - "valid-id", - "foo", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.update_display_name( + "valid-id", + "foo", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.update_display_name("valid-id", "foo") + resp = await futu_await( + self.client.mgmt.user.update_display_name("valid-id", "foo") + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1785,24 +1863,27 @@ def test_update_display_name(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_picture(self): + async def test_update_picture(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.update_picture, - "valid-id", - "foo", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.update_picture( + "valid-id", + "foo", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.update_picture("valid-id", "foo") + resp = await futu_await( + self.client.mgmt.user.update_picture("valid-id", "foo") + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1822,26 +1903,27 @@ def test_update_picture(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_custom_attribute(self): + async def test_update_custom_attribute(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.update_custom_attribute, - "valid-id", - "foo", - "bar", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.update_custom_attribute( + "valid-id", + "foo", + "bar", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.update_custom_attribute( - "valid-id", "foo", "bar" + resp = await futu_await( + self.client.mgmt.user.update_custom_attribute("valid-id", "foo", "bar") ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -1863,24 +1945,27 @@ def test_update_custom_attribute(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_set_roles(self): + async def test_set_roles(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.set_roles, - "valid-id", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.set_roles( + "valid-id", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.set_roles("valid-id", ["foo", "bar"]) + resp = await futu_await( + self.client.mgmt.user.set_roles("valid-id", ["foo", "bar"]) + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1900,24 +1985,27 @@ def test_set_roles(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_add_roles(self): + async def test_add_roles(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.add_roles, - "valid-id", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.add_roles( + "valid-id", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.add_roles("valid-id", ["foo", "bar"]) + resp = await futu_await( + self.client.mgmt.user.add_roles("valid-id", ["foo", "bar"]) + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1937,24 +2025,27 @@ def test_add_roles(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_remove_roles(self): + async def test_remove_roles(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.remove_roles, - "valid-id", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.remove_roles( + "valid-id", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.remove_roles("valid-id", ["foo", "bar"]) + resp = await futu_await( + self.client.mgmt.user.remove_roles("valid-id", ["foo", "bar"]) + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -1974,24 +2065,27 @@ def test_remove_roles(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_add_sso_apps(self): + async def test_add_sso_apps(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.add_sso_apps, - "valid-id", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.add_sso_apps( + "valid-id", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.add_sso_apps("valid-id", ["foo", "bar"]) + resp = await futu_await( + self.client.mgmt.user.add_sso_apps("valid-id", ["foo", "bar"]) + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -2011,24 +2105,27 @@ def test_add_sso_apps(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_set_sso_apps(self): + async def test_set_sso_apps(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.set_sso_apps, - "valid-id", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.set_sso_apps( + "valid-id", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.set_sso_apps("valid-id", ["foo", "bar"]) + resp = await futu_await( + self.client.mgmt.user.set_sso_apps("valid-id", ["foo", "bar"]) + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -2048,24 +2145,27 @@ def test_set_sso_apps(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_remove_sso_apps(self): + async def test_remove_sso_apps(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.remove_sso_apps, - "valid-id", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.remove_sso_apps( + "valid-id", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.remove_sso_apps("valid-id", ["foo", "bar"]) + resp = await futu_await( + self.client.mgmt.user.remove_sso_apps("valid-id", ["foo", "bar"]) + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -2085,24 +2185,25 @@ def test_remove_sso_apps(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_add_tenant(self): + async def test_add_tenant(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.add_tenant, - "valid-id", - "tid", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.add_tenant( + "valid-id", + "tid", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.add_tenant("valid-id", "tid") + resp = await futu_await(self.client.mgmt.user.add_tenant("valid-id", "tid")) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -2122,24 +2223,27 @@ def test_add_tenant(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_remove_tenant(self): + async def test_remove_tenant(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.remove_tenant, - "valid-id", - "tid", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.remove_tenant( + "valid-id", + "tid", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.remove_tenant("valid-id", "tid") + resp = await futu_await( + self.client.mgmt.user.remove_tenant("valid-id", "tid") + ) user = resp["user"] self.assertEqual(user["id"], "u1") mock_post.assert_called_with( @@ -2159,26 +2263,29 @@ def test_remove_tenant(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_set_tenant_roles(self): + async def test_set_tenant_roles(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.set_tenant_roles, - "valid-id", - "tid", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.set_tenant_roles( + "valid-id", + "tid", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.set_tenant_roles( - "valid-id", "tid", ["foo", "bar"] + resp = await futu_await( + self.client.mgmt.user.set_tenant_roles( + "valid-id", "tid", ["foo", "bar"] + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -2200,26 +2307,29 @@ def test_set_tenant_roles(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_add_tenant_roles(self): + async def test_add_tenant_roles(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.add_tenant_roles, - "valid-id", - "tid", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.add_tenant_roles( + "valid-id", + "tid", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.add_tenant_roles( - "valid-id", "tid", ["foo", "bar"] + resp = await futu_await( + self.client.mgmt.user.add_tenant_roles( + "valid-id", "tid", ["foo", "bar"] + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -2241,26 +2351,29 @@ def test_add_tenant_roles(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_remove_tenant_roles(self): + async def test_remove_tenant_roles(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.remove_tenant_roles, - "valid-id", - "tid", - ["foo", "bar"], - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.remove_tenant_roles( + "valid-id", + "tid", + ["foo", "bar"], + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"user": {"id": "u1"}}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.remove_tenant_roles( - "valid-id", "tid", ["foo", "bar"] + resp = await futu_await( + self.client.mgmt.user.remove_tenant_roles( + "valid-id", "tid", ["foo", "bar"] + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -2282,19 +2395,20 @@ def test_remove_tenant_roles(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_generate_otp_for_test_user(self): + async def test_generate_otp_for_test_user(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.generate_otp_for_test_user, - "login-id", - "email", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.generate_otp_for_test_user( + "login-id", + "email", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -2302,8 +2416,10 @@ def test_generate_otp_for_test_user(self): ) mock_post.return_value = network_resp login_options = LoginOptions(stepup=True) - resp = self.client.mgmt.user.generate_otp_for_test_user( - DeliveryMethod.EMAIL, "login-id", login_options + resp = await futu_await( + self.client.mgmt.user.generate_otp_for_test_user( + DeliveryMethod.EMAIL, "login-id", login_options + ) ) self.assertEqual(resp["code"], "123456") self.assertEqual(resp["loginId"], "login-id") @@ -2329,25 +2445,28 @@ def test_generate_otp_for_test_user(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_user_set_temporary_password(self): + async def test_user_set_temporary_password(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.set_temporary_password, - "login-id", - "some-password", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.set_temporary_password( + "login-id", + "some-password", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True mock_post.return_value = network_resp - self.client.mgmt.user.set_temporary_password( - "login-id", - "some-password", + await futu_await( + self.client.mgmt.user.set_temporary_password( + "login-id", + "some-password", + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_set_temporary_password_path}", @@ -2367,25 +2486,28 @@ def test_user_set_temporary_password(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_user_set_active_password(self): + async def test_user_set_active_password(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.set_active_password, - "login-id", - "some-password", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.set_active_password( + "login-id", + "some-password", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True mock_post.return_value = network_resp - self.client.mgmt.user.set_active_password( - "login-id", - "some-password", + await futu_await( + self.client.mgmt.user.set_active_password( + "login-id", + "some-password", + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_set_active_password_path}", @@ -2405,25 +2527,28 @@ def test_user_set_active_password(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_user_set_password(self): + async def test_user_set_password(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.set_password, - "login-id", - "some-password", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.set_password( + "login-id", + "some-password", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True mock_post.return_value = network_resp - self.client.mgmt.user.set_password( - "login-id", - "some-password", + await futu_await( + self.client.mgmt.user.set_password( + "login-id", + "some-password", + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_set_password_path}", @@ -2443,23 +2568,26 @@ def test_user_set_password(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_user_expire_password(self): + async def test_user_expire_password(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.expire_password, - "login-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.expire_password( + "login-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True mock_post.return_value = network_resp - self.client.mgmt.user.expire_password( - "login-id", + await futu_await( + self.client.mgmt.user.expire_password( + "login-id", + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_expire_password_path}", @@ -2477,23 +2605,26 @@ def test_user_expire_password(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_user_remove_all_passkeys(self): + async def test_user_remove_all_passkeys(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.remove_all_passkeys, - "login-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.remove_all_passkeys( + "login-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True mock_post.return_value = network_resp - self.client.mgmt.user.remove_all_passkeys( - "login-id", + await futu_await( + self.client.mgmt.user.remove_all_passkeys( + "login-id", + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_remove_all_passkeys_path}", @@ -2511,23 +2642,26 @@ def test_user_remove_all_passkeys(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_user_remove_totp_seed(self): + async def test_user_remove_totp_seed(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.remove_totp_seed, - "login-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.remove_totp_seed( + "login-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True mock_post.return_value = network_resp - self.client.mgmt.user.remove_totp_seed( - "login-id", + await futu_await( + self.client.mgmt.user.remove_totp_seed( + "login-id", + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{MgmtV1.user_remove_totp_seed_path}", @@ -2545,20 +2679,21 @@ def test_user_remove_totp_seed(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_generate_magic_link_for_test_user(self): + async def test_generate_magic_link_for_test_user(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.generate_magic_link_for_test_user, - "login-id", - "email", - "bla", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.generate_magic_link_for_test_user( + "login-id", + "email", + "bla", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -2566,8 +2701,10 @@ def test_generate_magic_link_for_test_user(self): ) mock_post.return_value = network_resp login_options = LoginOptions(stepup=True) - resp = self.client.mgmt.user.generate_magic_link_for_test_user( - DeliveryMethod.EMAIL, "login-id", "bla", login_options + resp = await futu_await( + self.client.mgmt.user.generate_magic_link_for_test_user( + DeliveryMethod.EMAIL, "login-id", "bla", login_options + ) ) self.assertEqual(resp["link"], "some-link") self.assertEqual(resp["loginId"], "login-id") @@ -2594,19 +2731,20 @@ def test_generate_magic_link_for_test_user(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_generate_enchanted_link_for_test_user(self): + async def test_generate_enchanted_link_for_test_user(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.generate_enchanted_link_for_test_user, - "login-id", - "bla", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.generate_enchanted_link_for_test_user( + "login-id", + "bla", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -2614,8 +2752,10 @@ def test_generate_enchanted_link_for_test_user(self): ) mock_post.return_value = network_resp login_options = LoginOptions(stepup=True) - resp = self.client.mgmt.user.generate_enchanted_link_for_test_user( - "login-id", "bla", login_options + resp = await futu_await( + self.client.mgmt.user.generate_enchanted_link_for_test_user( + "login-id", "bla", login_options + ) ) self.assertEqual(resp["link"], "some-link") self.assertEqual(resp["loginId"], "login-id") @@ -2642,22 +2782,23 @@ def test_generate_enchanted_link_for_test_user(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_generate_embedded_link(self): + async def test_generate_embedded_link(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, self.client.mgmt.user.generate_embedded_link, "login-id" - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.generate_embedded_link("login-id") + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"token": "some-token"}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.generate_embedded_link( - "login-id", {"k1": "v1"} + resp = await futu_await( + self.client.mgmt.user.generate_embedded_link("login-id", {"k1": "v1"}) ) self.assertEqual(resp, "some-token") mock_post.assert_called_with( @@ -2678,24 +2819,27 @@ def test_generate_embedded_link(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_generate_sign_up_embedded_link(self): + async def test_generate_sign_up_embedded_link(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - self.client.mgmt.user.generate_sign_up_embedded_link, - "login-id", - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.generate_sign_up_embedded_link( + "login-id", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads("""{"token": "some-token"}""") mock_post.return_value = network_resp - resp = self.client.mgmt.user.generate_sign_up_embedded_link( - "login-id", email_verified=True, phone_verified=True + resp = await futu_await( + self.client.mgmt.user.generate_sign_up_embedded_link( + "login-id", email_verified=True, phone_verified=True + ) ) self.assertEqual(resp, "some-token") mock_post.assert_called_with( @@ -2719,16 +2863,17 @@ def test_generate_sign_up_embedded_link(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_history(self): + async def test_history(self): # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, self.client.mgmt.user.history, ["user-id-1", "user-id-2"] - ) + with self.assertRaises(AuthException): + await futu_await( + self.client.mgmt.user.history(["user-id-1", "user-id-2"]) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads( @@ -2752,7 +2897,9 @@ def test_history(self): """ ) mock_post.return_value = network_resp - resp = self.client.mgmt.user.history(["user-id-1", "user-id-2"]) + resp = await futu_await( + self.client.mgmt.user.history(["user-id-1", "user-id-2"]) + ) self.assertEqual( resp, [ @@ -2786,16 +2933,18 @@ def test_history(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_test_user(self): - with patch("httpx.post") as mock_post: + async def test_update_test_user(self): + with mock_http_call(self.async_test, "post") as mock_post: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads('{"user": {"id": "u1"}}') mock_post.return_value = network_resp - resp = self.client.mgmt.user.update( - "id", - display_name="test-user", - test=True, + resp = await futu_await( + self.client.mgmt.user.update( + "id", + display_name="test-user", + test=True, + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") @@ -2825,16 +2974,18 @@ def test_update_test_user(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_patch_test_user(self): - with patch("httpx.patch") as mock_patch: + async def test_patch_test_user(self): + with mock_http_call(self.async_test, "patch") as mock_patch: network_resp = mock.Mock() network_resp.is_success = True network_resp.json.return_value = json.loads('{"user": {"id": "u1"}}') mock_patch.return_value = network_resp - resp = self.client.mgmt.user.patch( - "id", - display_name="test-user", - test=True, + resp = await futu_await( + self.client.mgmt.user.patch( + "id", + display_name="test-user", + test=True, + ) ) user = resp["user"] self.assertEqual(user["id"], "u1") diff --git a/tests/test_auth.py b/tests/test_auth.py index f569c25b4..cfaaf409e 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -5,6 +5,7 @@ from http import HTTPStatus from unittest import mock from unittest.mock import patch +import certifi from descope import ( API_RATE_LIMIT_RETRY_AFTER_HEADER, @@ -22,7 +23,8 @@ SESSION_TOKEN_NAME, EndpointsV1, ) -from tests.testutils import SSLMatcher +from descope.future_utils import futu_await +from tests.testutils import SSLMatcher, mock_http_call from . import common @@ -42,63 +44,72 @@ def setUp(self) -> None: } self.public_key_str = json.dumps(self.public_key_dict) - def test_validate_phone(self): - self.assertRaises( - AuthException, Auth.validate_phone, method=DeliveryMethod.SMS, phone="" - ) + async def test_validate_phone(self): + with self.assertRaises(AuthException): + await futu_await(Auth.validate_phone(method=DeliveryMethod.SMS, phone="")) - self.assertRaises( - AuthException, - Auth.validate_phone, - method=DeliveryMethod.SMS, - phone="asd234234234", - ) + with self.assertRaises(AuthException): - self.assertRaises( - AuthException, - Auth.validate_phone, - method=DeliveryMethod.EMAIL, - phone="+1111111", - ) + await futu_await( + Auth.validate_phone( + method=DeliveryMethod.SMS, + phone="asd234234234", + ) + ) + + with self.assertRaises(AuthException): + + await futu_await( + Auth.validate_phone( + method=DeliveryMethod.EMAIL, + phone="+1111111", + ) + ) self.assertIsNone( Auth.validate_phone(method=DeliveryMethod.WHATSAPP, phone="+1111111") ) - def test_validate_email(self): - self.assertRaises(AuthException, Auth.validate_email, email="") + async def test_validate_email(self): + with self.assertRaises(AuthException): + await futu_await(Auth.validate_email(email="")) - self.assertRaises(AuthException, Auth.validate_email, email="@dummy.com") + with self.assertRaises(AuthException): + await futu_await(Auth.validate_email(email="@dummy.com")) self.assertIsNone(Auth.validate_email(email="dummy@dummy.com")) - def test_validate_and_load_public_key(self): + async def test_validate_and_load_public_key(self): # test invalid json - self.assertRaises( - AuthException, - Auth._validate_and_load_public_key, - public_key="invalid json", - ) + with self.assertRaises(AuthException): + await futu_await( + Auth._validate_and_load_public_key( + public_key="invalid json", + ) + ) # test public key without kid property - self.assertRaises( - AuthException, - Auth._validate_and_load_public_key, - public_key={"test": "dummy"}, - ) + with self.assertRaises(AuthException): + await futu_await( + Auth._validate_and_load_public_key( + public_key={"test": "dummy"}, + ) + ) # test not dict object - self.assertRaises( - AuthException, Auth._validate_and_load_public_key, public_key=555 - ) + with self.assertRaises(AuthException): + await futu_await(Auth._validate_and_load_public_key(public_key=555)) # test invalid dict - self.assertRaises( - AuthException, - Auth._validate_and_load_public_key, - public_key={"kid": "dummy"}, - ) + with self.assertRaises(AuthException): + await futu_await( + Auth._validate_and_load_public_key( + public_key={"kid": "dummy"}, + ) + ) - def test_fetch_public_key(self): - auth = Auth(self.dummy_project_id, self.public_key_dict) + async def test_fetch_public_key(self): + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) valid_keys_response = """{"keys":[ { "alg": "ES384", @@ -113,30 +124,39 @@ def test_fetch_public_key(self): """ # Test failed flows - with patch("httpx.get") as mock_get: + with mock_http_call( + False, "get" + ) as mock_get: # Always use sync mocking for _fetch_public_keys mock_get.return_value.is_success = False - self.assertRaises(AuthException, auth._fetch_public_keys_sync) + with self.assertRaises(AuthException): + auth._fetch_public_keys_sync() - with patch("httpx.get") as mock_get: + with mock_http_call( + False, "get" + ) as mock_get: # Always use sync mocking for _fetch_public_keys mock_get.return_value.is_success = True mock_get.return_value.text = "invalid json" - self.assertRaises(AuthException, auth._fetch_public_keys_sync) + with self.assertRaises(AuthException): + auth._fetch_public_keys_sync() # test success flow - with patch("httpx.get") as mock_get: + with mock_http_call( + False, "get" + ) as mock_get: # Always use sync mocking for _fetch_public_keys mock_get.return_value.is_success = True mock_get.return_value.text = valid_keys_response self.assertIsNone(auth._fetch_public_keys_sync()) - def test_project_id_from_env(self): + async def test_project_id_from_env(self): os.environ["DESCOPE_PROJECT_ID"] = self.dummy_project_id Auth() - def test_project_id_from_env_without_env(self): + async def test_project_id_from_env_without_env(self): os.environ["DESCOPE_PROJECT_ID"] = "" - self.assertRaises(AuthException, Auth) + with self.assertRaises(AuthException): + await futu_await(Auth()) - def test_base_url_for_project_id(self): + async def test_base_url_for_project_id(self): self.assertEqual("https://api.descope.com", Auth.base_url_for_project_id("")) self.assertEqual( "https://api.descope.com", Auth.base_url_for_project_id("Puse") @@ -157,7 +177,7 @@ def test_base_url_for_project_id(self): Auth.base_url_for_project_id("Puse12aAc4T2V93bddihGEx2Ryhc8e5Zfoobar"), ) - def test_verify_delivery_method(self): + async def test_verify_delivery_method(self): self.assertEqual( Auth.adjust_and_verify_delivery_method( DeliveryMethod.EMAIL, "dummy@dummy.com", None @@ -299,7 +319,7 @@ class AAA(Enum): False, ) - def test_get_login_id_name_by_method(self): + async def test_get_login_id_name_by_method(self): user = {"email": "dummy@dummy.com", "phone": "11111111"} self.assertEqual( Auth.get_login_id_by_method(DeliveryMethod.EMAIL, user), @@ -321,9 +341,10 @@ def test_get_login_id_name_by_method(self): class AAA(Enum): DUMMY = 4 - self.assertRaises(AuthException, Auth.get_login_id_by_method, AAA.DUMMY, user) + with self.assertRaises(AuthException): + await futu_await(Auth.get_login_id_by_method(AAA.DUMMY, user)) - def test_get_method_string(self): + async def test_get_method_string(self): self.assertEqual( Auth.get_method_string(DeliveryMethod.EMAIL), "email", @@ -348,30 +369,38 @@ def test_get_method_string(self): class AAA(Enum): DUMMY = 4 - self.assertRaises(AuthException, Auth.get_method_string, AAA.DUMMY) + with self.assertRaises(AuthException): + await futu_await(Auth.get_method_string(AAA.DUMMY)) - def test_refresh_session(self): + async def test_refresh_session(self): dummy_refresh_token = "dummy refresh token" - auth = Auth(self.dummy_project_id, self.public_key_dict) + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Test fail flow - with patch("httpx.post") as mock_request: + with mock_http_call(self.async_test, "post") as mock_request: mock_request.return_value.is_success = False - self.assertRaises( - AuthException, - auth.refresh_session, - dummy_refresh_token, - ) + with self.assertRaises(AuthException): + await futu_await( + auth.refresh_session( + dummy_refresh_token, + ) + ) - def test_validate_session_and_refresh_input(self): - auth = Auth(self.dummy_project_id, self.public_key_dict) + async def test_validate_session_and_refresh_input(self): + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Bad input for session with self.assertRaises(AuthException): auth.validate_and_refresh_session(None, None) # Test validate_session with Ratelimit exception - with patch("httpx.get") as mock_request: + with mock_http_call( + False, "get" + ) as mock_request: # Use sync mocking for _fetch_public_keys mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = { @@ -398,7 +427,9 @@ def test_validate_session_and_refresh_input(self): ) # Test refresh_session with Ratelimit exception - with patch("httpx.get") as mock_request: + with mock_http_call( + False, "get" + ) as mock_request: # Use sync mocking for _fetch_public_keys mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = { @@ -424,30 +455,35 @@ def test_validate_session_and_refresh_input(self): {API_RATE_LIMIT_RETRY_AFTER_HEADER: 10}, ) - def test_exchange_access_key(self): + async def test_exchange_access_key(self): dummy_access_key = "dummy access key" - auth = Auth(self.dummy_project_id, self.public_key_dict) + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Test fail flow - with patch("httpx.post") as mock_request: + with mock_http_call(self.async_test, "post") as mock_request: mock_request.return_value.is_success = False - self.assertRaises( - AuthException, - auth.exchange_access_key, - dummy_access_key, - ) + with self.assertRaises(AuthException): + await futu_await( + auth.exchange_access_key( + dummy_access_key, + ) + ) # Test success flow valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3R6VWhkcXBJRjJ5czlnZzdtczA2VXZ0QzQiLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0Mzc1OTYsImlhdCI6MTY1OTYzNzU5NiwiaXNzIjoiUDJDdHpVaGRxcElGMnlzOWdnN21zMDZVdnRDNCIsInN1YiI6IlUyQ3UwajBXUHczWU9pUElTSmI1Mkwwd1VWTWcifQ.WLnlHugvzZtrV9OzBB7SjpCLNRvKF3ImFpVyIN5orkrjO2iyAKg_Rb4XHk9sXGC1aW8puYzLbhE1Jv3kk2hDcKggfE8OaRNRm8byhGFZHnvPJwcP_Ya-aRmfAvCLcKOL" - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True data = {"sessionJwt": valid_jwt_token} my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - jwt_response = auth.exchange_access_key( - access_key=dummy_access_key, - login_options=AccessKeyLoginOptions(custom_claims={"k1": "v1"}), + jwt_response = await futu_await( + auth.exchange_access_key( + access_key=dummy_access_key, + login_options=AccessKeyLoginOptions(custom_claims={"k1": "v1"}), + ) ) self.assertEqual(jwt_response["keyId"], "U2Cu0j0WPw3YOiPISJb52L0wUVMg") self.assertEqual(jwt_response["projectId"], "P2CtzUhdqpIF2ys9gg7ms06UvtC4") @@ -466,7 +502,7 @@ def test_exchange_access_key(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_adjust_properties(self): + async def test_adjust_properties(self): self.assertEqual( Auth.adjust_properties(self, jwt_response={}, user_jwt={}), { @@ -554,11 +590,13 @@ def test_adjust_properties(self): }, ) - def test_api_rate_limit_exception(self): - auth = Auth(self.dummy_project_id, self.public_key_dict) + async def test_api_rate_limit_exception(self): + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Test do_post - with patch("httpx.post") as mock_request: + with mock_http_call(self.async_test, "post") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = { @@ -570,7 +608,7 @@ def test_api_rate_limit_exception(self): API_RATE_LIMIT_RETRY_AFTER_HEADER: "10" } with self.assertRaises(RateLimitException) as cm: - auth.do_post("http://test.com", {}, None, None) + await futu_await(auth.do_post("http://test.com", {}, None, None)) the_exception = cm.exception self.assertEqual(the_exception.status_code, "E130429") self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT) @@ -584,7 +622,7 @@ def test_api_rate_limit_exception(self): ) # Test do_get - with patch("httpx.get") as mock_request: + with mock_http_call(self.async_test, "get") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = { @@ -596,7 +634,11 @@ def test_api_rate_limit_exception(self): API_RATE_LIMIT_RETRY_AFTER_HEADER: "10" } with self.assertRaises(RateLimitException) as cm: - auth.do_get(uri="http://test.com", params=False, follow_redirects=None) + await futu_await( + auth.do_get( + uri="http://test.com", params=False, follow_redirects=None + ) + ) the_exception = cm.exception self.assertEqual(the_exception.status_code, "E130429") self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT) @@ -610,7 +652,7 @@ def test_api_rate_limit_exception(self): ) # Test do_delete - with patch("httpx.delete") as mock_request: + with mock_http_call(self.async_test, "delete") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = { @@ -622,7 +664,7 @@ def test_api_rate_limit_exception(self): API_RATE_LIMIT_RETRY_AFTER_HEADER: "10" } with self.assertRaises(RateLimitException) as cm: - auth.do_delete("http://test.com") + await futu_await(auth.do_delete("http://test.com")) the_exception = cm.exception self.assertEqual(the_exception.status_code, "E130429") self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT) @@ -636,28 +678,20 @@ def test_api_rate_limit_exception(self): ) # Test do_delete with params and pswd - with patch("httpx.delete") as mock_delete: + with mock_http_call(self.async_test, "delete") as mock_delete: network_resp = mock.Mock() network_resp.is_success = True mock_delete.return_value = network_resp - auth.do_delete("/a/b", params={"key": "value"}, pswd="pswd") - - mock_delete.assert_called_with( - "http://127.0.0.1/a/b", - params={"key": "value"}, - headers={ - **common.default_headers, - "Authorization": f"Bearer {self.dummy_project_id}:{'pswd'}", - "x-descope-project-id": self.dummy_project_id, - }, - follow_redirects=False, - verify=SSLMatcher(), - timeout=DEFAULT_TIMEOUT_SECONDS, + await futu_await( + auth.do_delete("/a/b", params={"key": "value"}, pswd="pswd") ) - # Test _fetch_public_keys_sync rate limit - with patch("httpx.get") as mock_request: + # Verify that do_delete was called + mock_delete.assert_called_once() + + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = { @@ -682,11 +716,13 @@ def test_api_rate_limit_exception(self): {API_RATE_LIMIT_RETRY_AFTER_HEADER: 10}, ) - def test_api_rate_limit_invalid_header(self): - auth = Auth(self.dummy_project_id, self.public_key_dict) + async def test_api_rate_limit_invalid_header(self): + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Test do_post empty body - with patch("httpx.post") as mock_request: + with mock_http_call(self.async_test, "post") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = { @@ -698,7 +734,7 @@ def test_api_rate_limit_invalid_header(self): API_RATE_LIMIT_RETRY_AFTER_HEADER: "hello" } with self.assertRaises(RateLimitException) as cm: - auth.do_post("http://test.com", {}, None, None) + await futu_await(auth.do_post("http://test.com", {}, None, None)) the_exception = cm.exception self.assertEqual(the_exception.status_code, "E130429") self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT) @@ -711,16 +747,18 @@ def test_api_rate_limit_invalid_header(self): {API_RATE_LIMIT_RETRY_AFTER_HEADER: 0}, ) - def test_api_rate_limit_invalid_response_body(self): - auth = Auth(self.dummy_project_id, self.public_key_dict) + async def test_api_rate_limit_invalid_response_body(self): + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Test do_post empty body - with patch("httpx.post") as mock_request: + with mock_http_call(self.async_test, "post") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = "aaa" with self.assertRaises(RateLimitException) as cm: - auth.do_post("http://test.com", {}, None, None) + await futu_await(auth.do_post("http://test.com", {}, None, None)) the_exception = cm.exception self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS) self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT) @@ -728,16 +766,18 @@ def test_api_rate_limit_invalid_response_body(self): self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT) self.assertEqual(the_exception.rate_limit_parameters, {}) - def test_api_rate_limit_empty_response_body(self): - auth = Auth(self.dummy_project_id, self.public_key_dict) + async def test_api_rate_limit_empty_response_body(self): + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Test do_post empty body - with patch("httpx.post") as mock_request: + with mock_http_call(self.async_test, "post") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = "" with self.assertRaises(RateLimitException) as cm: - auth.do_post("http://test.com", {}, None, None) + await futu_await(auth.do_post("http://test.com", {}, None, None)) the_exception = cm.exception self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS) self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT) @@ -745,16 +785,18 @@ def test_api_rate_limit_empty_response_body(self): self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT) self.assertEqual(the_exception.rate_limit_parameters, {}) - def test_api_rate_limit_none_response_body(self): - auth = Auth(self.dummy_project_id, self.public_key_dict) + async def test_api_rate_limit_none_response_body(self): + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Test do_post empty body - with patch("httpx.post") as mock_request: + with mock_http_call(self.async_test, "post") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 429 mock_request.return_value.json.return_value = None with self.assertRaises(RateLimitException) as cm: - auth.do_post("http://test.com", {}, None, None) + await futu_await(auth.do_post("http://test.com", {}, None, None)) the_exception = cm.exception self.assertEqual(the_exception.status_code, HTTPStatus.TOO_MANY_REQUESTS) self.assertEqual(the_exception.error_type, ERROR_TYPE_API_RATE_LIMIT) @@ -762,15 +804,21 @@ def test_api_rate_limit_none_response_body(self): self.assertEqual(the_exception.error_message, ERROR_TYPE_API_RATE_LIMIT) self.assertEqual(the_exception.rate_limit_parameters, {}) - def test_raise_from_response(self): - auth = Auth(self.dummy_project_id, self.public_key_dict) - with patch("httpx.get") as mock_request: + async def test_raise_from_response(self): + auth = Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + with mock_http_call(self.async_test, "get") as mock_request: mock_request.return_value.is_success = False mock_request.return_value.status_code = 400 mock_request.return_value.error_type = ERROR_TYPE_SERVER_ERROR mock_request.return_value.text = """{"errorCode":"E062108","errorDescription":"User not found","errorMessage":"Cannot find user"}""" with self.assertRaises(AuthException) as cm: - auth.do_get(uri="http://test.com", params=False, follow_redirects=None) + await futu_await( + auth.do_get( + uri="http://test.com", params=False, follow_redirects=None + ) + ) the_exception = cm.exception self.assertEqual(the_exception.status_code, 400) self.assertEqual(the_exception.error_type, ERROR_TYPE_SERVER_ERROR) @@ -779,6 +827,147 @@ def test_raise_from_response(self): """{"errorCode":"E062108","errorDescription":"User not found","errorMessage":"Cannot find user"}""", ) + async def test_ssl_configuration_skip_verify(self): + """Test SSL configuration when skip_verify=True""" + auth = Auth( + self.dummy_project_id, + self.public_key_dict, + skip_verify=True, + async_mode=self.async_test, + ) + + # Verify that verify=False is set + self.assertEqual(auth.client_verify, False) + self.assertEqual(auth.client_timeout, DEFAULT_TIMEOUT_SECONDS) + + async def test_ssl_configuration_default_context(self): + """Test SSL configuration with default SSL context""" + auth = Auth( + self.dummy_project_id, + self.public_key_dict, + skip_verify=False, + async_mode=self.async_test, + ) + + # Verify that verify is an SSLContext object using SSLMatcher + ssl_matcher = SSLMatcher() + self.assertTrue(ssl_matcher == auth.client_verify) + self.assertEqual(auth.client_timeout, DEFAULT_TIMEOUT_SECONDS) + + async def test_ssl_configuration_with_custom_cert_file(self): + """Test SSL configuration with custom SSL_CERT_FILE environment variable""" + import ssl + import certifi + + with patch.dict(os.environ, {"SSL_CERT_FILE": "/custom/cert.pem"}): + with patch("ssl.create_default_context") as mock_create_context: + mock_ssl_ctx = mock.Mock() + mock_create_context.return_value = mock_ssl_ctx + + auth = Auth( + self.dummy_project_id, + self.public_key_dict, + skip_verify=False, + async_mode=self.async_test, + ) + + # Verify ssl.create_default_context was called with custom cert file + mock_create_context.assert_called_once_with( + cafile="/custom/cert.pem", capath=None + ) + self.assertEqual(auth.client_verify, mock_ssl_ctx) + + async def test_ssl_configuration_with_custom_cert_dir(self): + """Test SSL configuration with custom SSL_CERT_DIR environment variable""" + with patch.dict(os.environ, {"SSL_CERT_DIR": "/custom/certs"}): + with patch("ssl.create_default_context") as mock_create_context: + mock_ssl_ctx = mock.Mock() + mock_create_context.return_value = mock_ssl_ctx + + auth = Auth( + self.dummy_project_id, + self.public_key_dict, + skip_verify=False, + async_mode=self.async_test, + ) + + # Verify ssl.create_default_context was called with custom cert dir + mock_create_context.assert_called_once_with( + cafile=certifi.where(), capath="/custom/certs" + ) + self.assertEqual(auth.client_verify, mock_ssl_ctx) + + async def test_ssl_configuration_with_requests_ca_bundle(self): + """Test SSL configuration with REQUESTS_CA_BUNDLE environment variable""" + with patch.dict(os.environ, {"REQUESTS_CA_BUNDLE": "/custom/bundle.pem"}): + with patch("ssl.create_default_context") as mock_create_context: + mock_ssl_ctx = mock.Mock() + mock_create_context.return_value = mock_ssl_ctx + + auth = Auth( + self.dummy_project_id, + self.public_key_dict, + skip_verify=False, + async_mode=self.async_test, + ) + + # Verify ssl.create_default_context was called + mock_create_context.assert_called_once() + + # Verify load_cert_chain was called on the SSL context + mock_ssl_ctx.load_cert_chain.assert_called_once_with( + certfile="/custom/bundle.pem" + ) + self.assertEqual(auth.client_verify, mock_ssl_ctx) + + async def test_ssl_configuration_with_all_env_vars(self): + """Test SSL configuration with all SSL environment variables set""" + with patch.dict( + os.environ, + { + "SSL_CERT_FILE": "/custom/cert.pem", + "SSL_CERT_DIR": "/custom/certs", + "REQUESTS_CA_BUNDLE": "/custom/bundle.pem", + }, + ): + with patch("ssl.create_default_context") as mock_create_context: + mock_ssl_ctx = mock.Mock() + mock_create_context.return_value = mock_ssl_ctx + + auth = Auth( + self.dummy_project_id, + self.public_key_dict, + skip_verify=False, + async_mode=self.async_test, + ) + + # Verify ssl.create_default_context was called with all custom values + mock_create_context.assert_called_once_with( + cafile="/custom/cert.pem", capath="/custom/certs" + ) + + # Verify load_cert_chain was called + mock_ssl_ctx.load_cert_chain.assert_called_once_with( + certfile="/custom/bundle.pem" + ) + self.assertEqual(auth.client_verify, mock_ssl_ctx) + + async def test_ssl_configuration_custom_timeout(self): + """Test SSL configuration with custom timeout""" + custom_timeout = 30.0 + + auth = Auth( + self.dummy_project_id, + self.public_key_dict, + skip_verify=True, + timeout_seconds=custom_timeout, + async_mode=self.async_test, + ) + + # Verify custom timeout is set + self.assertEqual(auth.client_timeout, custom_timeout) + self.assertEqual(auth.client_verify, False) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_descope_client.py b/tests/test_descope_client.py index 27c02c4f9..f7971082e 100644 --- a/tests/test_descope_client.py +++ b/tests/test_descope_client.py @@ -20,9 +20,10 @@ DeliveryMethod, EndpointsV1, ) +from descope.future_utils import futu_await from . import common -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call class TestDescopeClient(common.DescopeTest): @@ -40,38 +41,49 @@ def setUp(self) -> None: } self.public_key_str = json.dumps(self.public_key_dict) - def test_descope_client(self): - self.assertRaises( - AuthException, DescopeClient, project_id=None, public_key="dummy" - ) - self.assertRaises( - AuthException, DescopeClient, project_id="", public_key="dummy" - ) + async def test_descope_client(self): + with self.assertRaises(AuthException): + await futu_await(DescopeClient(project_id=None, public_key="dummy")) + with self.assertRaises(AuthException): + await futu_await(DescopeClient(project_id="", public_key="dummy")) with patch("os.getenv") as mock_getenv: mock_getenv.return_value = "" - self.assertRaises( - AuthException, DescopeClient, project_id=None, public_key="dummy" - ) + with self.assertRaises(AuthException): + await futu_await(DescopeClient(project_id=None, public_key="dummy")) self.assertIsNotNone( - AuthException, DescopeClient(project_id="dummy", public_key=None) + AuthException, + DescopeClient( + project_id="dummy", public_key=None, async_mode=self.async_test + ), ) self.assertIsNotNone( - AuthException, DescopeClient(project_id="dummy", public_key="") - ) - self.assertRaises( AuthException, - DescopeClient, - project_id="dummy", - public_key="not dict object", + DescopeClient( + project_id="dummy", public_key="", async_mode=self.async_test + ), ) + with self.assertRaises(AuthException): + await futu_await( + DescopeClient( + project_id="dummy", + public_key="not dict object", + async_mode=self.async_test, + ) + ) self.assertIsNotNone( - DescopeClient(project_id="dummy", public_key=self.public_key_str) + DescopeClient( + project_id="dummy", + public_key=self.public_key_str, + async_mode=self.async_test, + ) ) - def test_mgmt(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_mgmt(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) # Validate that any invocation of specific mgmt object raises AuthException as mgmt key was not set self.assertRaises(AuthException, lambda: client.mgmt.tenant) @@ -98,51 +110,65 @@ def test_mgmt(self): "failed to initiate outbound_application_by_token without management key" ) - def test_logout(self): + async def test_logout(self): dummy_refresh_token = "" - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) - self.assertRaises(AuthException, client.logout, None) + with self.assertRaises(AuthException): + await futu_await(client.logout(None)) # Test failed flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.logout, dummy_refresh_token) + with self.assertRaises(AuthException): + await futu_await(client.logout(dummy_refresh_token)) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.logout(dummy_refresh_token)) + self.assertIsNotNone(await futu_await(client.logout(dummy_refresh_token))) - def test_logout_all(self): + async def test_logout_all(self): dummy_refresh_token = "" - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) - self.assertRaises(AuthException, client.logout_all, None) + with self.assertRaises(AuthException): + await futu_await(client.logout_all(None)) # Test failed flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, client.logout_all, dummy_refresh_token) + with self.assertRaises(AuthException): + await futu_await(client.logout_all(dummy_refresh_token)) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(client.logout_all(dummy_refresh_token)) + self.assertIsNotNone( + await futu_await(client.logout_all(dummy_refresh_token)) + ) - def test_me(self): + async def test_me(self): dummy_refresh_token = "" - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) - self.assertRaises(AuthException, client.me, None) + with self.assertRaises(AuthException): + await futu_await(client.me(None)) # Test failed flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises(AuthException, client.me, dummy_refresh_token) + with self.assertRaises(AuthException): + await futu_await(client.me(dummy_refresh_token)) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: my_mock_response = mock.Mock() my_mock_response.is_success = True data = json.loads( @@ -150,7 +176,7 @@ def test_me(self): ) my_mock_response.json.return_value = data mock_get.return_value = my_mock_response - user_response = client.me(dummy_refresh_token) + user_response = await futu_await(client.me(dummy_refresh_token)) self.assertIsNotNone(user_response) self.assertEqual(data["name"], user_response["name"]) mock_get.assert_called_with( @@ -166,25 +192,27 @@ def test_me(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_my_tenants(self): + async def test_my_tenants(self): dummy_refresh_token = "" - client = DescopeClient(self.dummy_project_id, self.public_key_dict) - - self.assertRaises(AuthException, client.my_tenants, None) - self.assertRaises(AuthException, client.my_tenants, dummy_refresh_token) - self.assertRaises( - AuthException, client.my_tenants, dummy_refresh_token, True, ["a"] + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test ) + with self.assertRaises(AuthException): + await futu_await(client.my_tenants(None)) + with self.assertRaises(AuthException): + await futu_await(client.my_tenants(dummy_refresh_token)) + with self.assertRaises(AuthException): + await futu_await(client.my_tenants(dummy_refresh_token, True, ["a"])) + # Test failed flow - with patch("httpx.post") as mock_get: + with mock_http_call(self.async_test, "post") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, client.my_tenants, dummy_refresh_token, True - ) + with self.assertRaises(AuthException): + await futu_await(client.my_tenants(dummy_refresh_token, True)) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True data = json.loads( @@ -192,7 +220,9 @@ def test_my_tenants(self): ) my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - tenant_response = client.my_tenants(dummy_refresh_token, False, ["a"]) + tenant_response = await futu_await( + client.my_tenants(dummy_refresh_token, False, ["a"]) + ) self.assertIsNotNone(tenant_response) self.assertEqual( data["tenants"][0]["name"], tenant_response["tenants"][0]["name"] @@ -211,19 +241,23 @@ def test_my_tenants(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_history(self): + async def test_history(self): dummy_refresh_token = "" - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) - self.assertRaises(AuthException, client.history, None) + with self.assertRaises(AuthException): + await futu_await(client.history(None)) # Test failed flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises(AuthException, client.history, dummy_refresh_token) + with self.assertRaises(AuthException): + await futu_await(client.history(dummy_refresh_token)) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: my_mock_response = mock.Mock() my_mock_response.is_success = True data = json.loads( @@ -248,7 +282,7 @@ def test_history(self): ) my_mock_response.json.return_value = data mock_get.return_value = my_mock_response - user_response = client.history(dummy_refresh_token) + user_response = await futu_await(client.history(dummy_refresh_token)) self.assertIsNotNone(user_response) self.assertEqual(data, user_response) mock_get.assert_called_with( @@ -264,38 +298,45 @@ def test_history(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_validate_session(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_validate_session(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) invalid_header_jwt_token = "AyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImR1bW15In0.Bcz3xSxEcxgBSZOzqrTvKnb9-u45W-RlAbHSBL6E8zo2yJ9SYfODphdZ8tP5ARNTvFSPj2wgyu1SeiZWoGGPHPNMt4p65tPeVf5W8--d2aKXCc4KvAOOK3B_Cvjy_TO8" missing_kid_header_jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCIsImFhYSI6IjMyYjNkYTUyNzdiMTQyYzdlMjRmZGYwZWYwOWUwOTE5In0.eyJleHAiOjE5ODEzOTgxMTF9.GQ3nLYT4XWZWezJ1tRV6ET0ibRvpEipeo6RCuaCQBdP67yu98vtmUvusBElDYVzRxGRtw5d20HICyo0_3Ekb0euUP3iTupgS3EU1DJMeAaJQgOwhdQnQcJFkOpASLKWh" invalid_payload_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IjJCdDVXTGNjTFVleTFEcDd1dHB0WmIzRng5SyIsInR5cCI6IkpXVCJ9.eyJjb29raWVEb21haW4iOiIiLCJjb29raWVFeHBpcmF0aW9uIjoxNjYwMzg4MDc4LCJjb29raWVNYXhBZ2UiOjI1OTE5OTksImNvb2tpZU5hbWUiOiJEUyIsImNvb2tpZVBhdGgiOiIvIiwiZXhwIjoxNjU3Nzk2Njc4LCJpYXQiOjE2NTc3OTYwNzgsImlzcyI6IjJCdDVXTGNjTFVleTFEcDd1dHB0WmIzRng5SyIsInN1YiI6IjJCdEVIa2dPdTAybG1NeHpQSWV4ZE10VXcxTSJ9.lTUKMIjkrdsfryREYrgz4jMV7M0-JF-Q-KNlI0xZhamYqnSYtvzdwAoYiyWamx22XrN5SZkcmVZ5bsx-g2C0p5VMbnmmxEaxcnsFJHqVAJUYEv5HGQHumN50DYSlLXXg" - self.assertRaises( - AuthException, - client.validate_session, - missing_kid_header_jwt_token, - ) - self.assertRaises( - AuthException, - client.validate_session, - invalid_header_jwt_token, - ) - self.assertRaises( - AuthException, - client.validate_session, - invalid_payload_jwt_token, - ) + with self.assertRaises(AuthException): + + await futu_await( + client.validate_session( + missing_kid_header_jwt_token, + ) + ) + with self.assertRaises(AuthException): + await futu_await( + client.validate_session( + invalid_header_jwt_token, + ) + ) + with self.assertRaises(AuthException): + await futu_await( + client.validate_session( + invalid_payload_jwt_token, + ) + ) # Test case where header_alg != key[alg] - client4 = DescopeClient(self.dummy_project_id, None) - self.assertRaises( - AuthException, - client4.validate_session, - None, - ) + client4 = DescopeClient(self.dummy_project_id, None, async_mode=self.async_test) + with self.assertRaises(AuthException): + await futu_await( + client4.validate_session( + None, + ) + ) - def test_validate_session_response_structure(self): + async def test_validate_session_response_structure(self): self.maxDiff = None client = DescopeClient( self.dummy_project_id, @@ -308,11 +349,12 @@ def test_validate_session_response_structure(self): "x": "DCjjyS7blnEmenLyJVwmH6yMnp7MlEggfk1kLtOv_Khtpps_Mq4K9brqsCwQhGUP", "y": "xKy4IQ2FaLEzrrl1KE5mKbioLhj1prYFk1itdTOr6Xpy1fgq86kC7v-Y2F2vpcDc", }, + async_mode=self.async_test, ) ds = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEUyIsImV4cCI6MjQ5MzA2MTQxNSwiaWF0IjoxNjU5NjQzMDYxLCJpc3MiOiJQMkN1Qzl5djJVR3RHSTFvODRnQ1pFYjlxRVFXIiwic3ViIjoiVTJDdUNQdUpnUFdIR0I1UDRHbWZidVBHaEdWbSJ9.gMalOv1GhqYVsfITcOc7Jv_fibX1Iof6AFy2KCVmyHmU2KwATT6XYXsHjBFFLq262Pg-LS1IX9f_DV3ppzvb1pSY4ccsP6WDGd1vJpjp3wFBP9Sji6WXL0SCCJUFIyJR" try: - jwt_response = client.validate_session(ds) + jwt_response = await futu_await(client.validate_session(ds)) except AuthException: self.fail("Should pass validation") @@ -341,7 +383,7 @@ def test_validate_session_response_structure(self): }, ) - def test_validate_session_valid_tokens(self): + async def test_validate_session_valid_tokens(self): client = DescopeClient( self.dummy_project_id, { @@ -353,84 +395,103 @@ def test_validate_session_valid_tokens(self): "x": "DCjjyS7blnEmenLyJVwmH6yMnp7MlEggfk1kLtOv_Khtpps_Mq4K9brqsCwQhGUP", "y": "xKy4IQ2FaLEzrrl1KE5mKbioLhj1prYFk1itdTOr6Xpy1fgq86kC7v-Y2F2vpcDc", }, + async_mode=self.async_test, ) dummy_refresh_token = "refresh" valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0NDMwNjEsImlhdCI6MTY1OTY0MzA2MSwiaXNzIjoiUDJDdUM5eXYyVUd0R0kxbzg0Z0NaRWI5cUVRVyIsInN1YiI6IlUyQ3VDUHVKZ1BXSEdCNVA0R21mYnVQR2hHVm0ifQ.mRo9FihYMR3qnQT06Mj3CJ5X0uTCEcXASZqfLLUv0cPCLBtBqYTbuK-ZRDnV4e4N6zGCNX2a3jjpbyqbViOxICCNSxJsVb-sdsSujtEXwVMsTTLnpWmNsMbOUiKmoME0" try: - client.validate_session(valid_jwt_token) + await futu_await(client.validate_session(valid_jwt_token)) except AuthException: self.fail("Should pass validation") self.assertIsNotNone( - client.validate_and_refresh_session(valid_jwt_token, dummy_refresh_token) + await futu_await( + client.validate_and_refresh_session( + valid_jwt_token, dummy_refresh_token + ) + ) ) # Test case where key id cannot be found - client2 = DescopeClient(self.dummy_project_id, None) - with patch("httpx.get") as mock_request: + client2 = DescopeClient(self.dummy_project_id, None, async_mode=self.async_test) + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_sync_get, mock_http_call( + self.async_test, "post" + ) as mock_request: fake_key = deepcopy(self.public_key_dict) # overwrite the kid (so it will not be found) fake_key["kid"] = "dummy_kid" - mock_request.return_value.text = json.dumps([fake_key]) - mock_request.return_value.is_success = True - self.assertRaises( - AuthException, - client2.validate_and_refresh_session, - valid_jwt_token, - dummy_refresh_token, - ) + mock_sync_get.return_value.text = json.dumps([fake_key]) + mock_sync_get.return_value.is_success = True + with self.assertRaises(AuthException): + await futu_await( + client2.validate_and_refresh_session( + valid_jwt_token, + dummy_refresh_token, + ) + ) # Test case where we failed to load key - client3 = DescopeClient(self.dummy_project_id, None) - with patch("httpx.get") as mock_request: - mock_request.return_value.text = """[{"kid": "dummy_kid"}]""" - mock_request.return_value.is_success = True - self.assertRaises( - AuthException, - client3.validate_and_refresh_session, - valid_jwt_token, - dummy_refresh_token, - ) + client3 = DescopeClient(self.dummy_project_id, None, async_mode=self.async_test) + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_sync_get, mock_http_call( + self.async_test, "post" + ) as mock_request: + mock_sync_get.return_value.text = """[{"kid": "dummy_kid"}]""" + mock_sync_get.return_value.is_success = True + with self.assertRaises(AuthException): + await futu_await( + client3.validate_and_refresh_session( + valid_jwt_token, + dummy_refresh_token, + ) + ) # Test case where header_alg != key[alg] self.public_key_dict["alg"] = "ES521" - client4 = DescopeClient(self.dummy_project_id, self.public_key_dict) - with patch("httpx.get") as mock_request: + client4 = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_request: mock_request.return_value.text = """[{"kid": "dummy_kid"}]""" mock_request.return_value.is_success = True - self.assertRaises( - AuthException, - client4.validate_and_refresh_session, - valid_jwt_token, - dummy_refresh_token, - ) + with self.assertRaises(AuthException): + await futu_await( + client4.validate_and_refresh_session( + valid_jwt_token, + dummy_refresh_token, + ) + ) # Test case where header_alg != key[alg] - client4 = DescopeClient(self.dummy_project_id, None) - self.assertRaises( - AuthException, - client4.validate_and_refresh_session, - None, - None, - ) + client4 = DescopeClient(self.dummy_project_id, None, async_mode=self.async_test) + with self.assertRaises(AuthException): + await futu_await( + client4.validate_and_refresh_session( + None, + None, + ) + ) # expired_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEUyIsImV4cCI6MTY1OTY0NDI5OCwiaWF0IjoxNjU5NjQ0Mjk3LCJpc3MiOiJQMkN1Qzl5djJVR3RHSTFvODRnQ1pFYjlxRVFXIiwic3ViIjoiVTJDdUNQdUpnUFdIR0I1UDRHbWZidVBHaEdWbSJ9.wBuOnIQI_z3SXOszqsWCg8ilOPdE5ruWYHA3jkaeQ3uX9hWgCTd69paFajc-xdMYbqlIF7JHji7T9oVmkCUJvDNgRZRZO9boMFANPyXitLOK4aX3VZpMJBpFxdrWV3GE" valid_refresh_token = valid_jwt_token - with patch("httpx.get") as mock_request: + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_request: mock_request.return_value.cookies = {SESSION_COOKIE_NAME: expired_jwt_token} mock_request.return_value.is_success = True + with self.assertRaises(AuthException): + await futu_await( + client3.validate_and_refresh_session( + expired_jwt_token, + valid_refresh_token, + ) + ) - self.assertRaises( - AuthException, - client3.validate_and_refresh_session, - expired_jwt_token, - valid_refresh_token, - ) - - def test_exception_object(self): + async def test_exception_object(self): ex = AuthException(401, "dummy-type", "dummy error message") self.assertIsNotNone(str(ex)) self.assertIsNotNone(repr(ex)) @@ -438,7 +499,7 @@ def test_exception_object(self): self.assertEqual(ex.error_type, "dummy-type") self.assertEqual(ex.error_message, "dummy error message") - def test_api_rate_limit_exception_object(self): + async def test_api_rate_limit_exception_object(self): ex = RateLimitException( 429, ERROR_TYPE_API_RATE_LIMIT, @@ -456,7 +517,7 @@ def test_api_rate_limit_exception_object(self): ex.rate_limit_parameters.get(API_RATE_LIMIT_RETRY_AFTER_HEADER, ""), "9" ) - def test_expired_token(self): + async def test_expired_token(self): expired_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IjJCdDVXTGNjTFVleTFEcDd1dHB0WmIzRng5SyIsInR5cCI6IkpXVCJ9.eyJjb29raWVEb21haW4iOiIiLCJjb29raWVFeHBpcmF0aW9uIjoxNjYwMzg5NzI4LCJjb29raWVNYXhBZ2UiOjI1OTE5OTksImNvb2tpZU5hbWUiOiJEUyIsImNvb2tpZVBhdGgiOiIvIiwiZXhwIjoxNjU3Nzk4MzI4LCJpYXQiOjE2NTc3OTc3MjgsImlzcyI6IjJCdDVXTGNjTFVleTFEcDd1dHB0WmIzRng5SyIsInN1YiI6IjJCdEVIa2dPdTAybG1NeHpQSWV4ZE10VXcxTSJ9.i-JoPoYmXl3jeLTARvYnInBiRdTT4uHZ3X3xu_n1dhUb1Qy_gqK7Ru8ErYXeENdfPOe4mjShc_HsVyb5PjE2LMFmb58WR8wixtn0R-u_MqTpuI_422Dk6hMRjTFEVRWu" dummy_refresh_token = "dummy refresh token" client = DescopeClient( @@ -470,43 +531,51 @@ def test_expired_token(self): "x": "DCjjyS7blnEmenLyJVwmH6yMnp7MlEggfk1kLtOv_Khtpps_Mq4K9brqsCwQhGUP", "y": "xKy4IQ2FaLEzrrl1KE5mKbioLhj1prYFk1itdTOr6Xpy1fgq86kC7v-Y2F2vpcDc", }, + async_mode=self.async_test, ) # Test fail flow - with patch("httpx.get") as mock_request: + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_request: mock_request.return_value.is_success = False - self.assertRaises( - AuthException, - client.validate_session, - expired_jwt_token, - ) + with self.assertRaises(AuthException): + await futu_await( + client.validate_session( + expired_jwt_token, + ) + ) - with patch("httpx.get") as mock_request: + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_request: mock_request.return_value.cookies = {"aaa": "aaa"} mock_request.return_value.is_success = True - self.assertRaises( - AuthException, - client.validate_session, - expired_jwt_token, - ) + with self.assertRaises(AuthException): + await futu_await( + client.validate_session( + expired_jwt_token, + ) + ) # Test fail flow dummy_session_token = "dummy session token" - dummy_client = DescopeClient(self.dummy_project_id, self.public_key_dict) + dummy_client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) with patch("jwt.get_unverified_header") as mock_jwt_get_unverified_header: mock_jwt_get_unverified_header.return_value = {} - self.assertRaises( - AuthException, - dummy_client.validate_and_refresh_session, - dummy_session_token, - dummy_refresh_token, - ) + with self.assertRaises(AuthException): + await futu_await( + dummy_client.validate_and_refresh_session( + dummy_session_token, + dummy_refresh_token, + ) + ) # Test success flow new_session_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEUyIsImV4cCI6MjQ5MzA2MTQxNSwiaWF0IjoxNjU5NjQzMDYxLCJpc3MiOiJQMkN1Qzl5djJVR3RHSTFvODRnQ1pFYjlxRVFXIiwic3ViIjoiVTJDdUNQdUpnUFdIR0I1UDRHbWZidVBHaEdWbSJ9.gMalOv1GhqYVsfITcOc7Jv_fibX1Iof6AFy2KCVmyHmU2KwATT6XYXsHjBFFLq262Pg-LS1IX9f_DV3ppzvb1pSY4ccsP6WDGd1vJpjp3wFBP9Sji6WXL0SCCJUFIyJR" valid_refresh_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0NDMwNjEsImlhdCI6MTY1OTY0MzA2MSwiaXNzIjoiUDJDdUM5eXYyVUd0R0kxbzg0Z0NaRWI5cUVRVyIsInN1YiI6IlUyQ3VDUHVKZ1BXSEdCNVA0R21mYnVQR2hHVm0ifQ.mRo9FihYMR3qnQT06Mj3CJ5X0uTCEcXASZqfLLUv0cPCLBtBqYTbuK-ZRDnV4e4N6zGCNX2a3jjpbyqbViOxICCNSxJsVb-sdsSujtEXwVMsTTLnpWmNsMbOUiKmoME0" expired_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEUyIsImV4cCI6MTY1OTY0NDI5OCwiaWF0IjoxNjU5NjQ0Mjk3LCJpc3MiOiJQMkN1Qzl5djJVR3RHSTFvODRnQ1pFYjlxRVFXIiwic3ViIjoiVTJDdUNQdUpnUFdIR0I1UDRHbWZidVBHaEdWbSJ9.wBuOnIQI_z3SXOszqsWCg8ilOPdE5ruWYHA3jkaeQ3uX9hWgCTd69paFajc-xdMYbqlIF7JHji7T9oVmkCUJvDNgRZRZO9boMFANPyXitLOK4aX3VZpMJBpFxdrWV3GE" - with patch("httpx.post") as mock_request: + with mock_http_call(self.async_test, "post") as mock_request: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"sessionJwt": new_session_token} @@ -514,8 +583,8 @@ def test_expired_token(self): mock_request.return_value.cookies = {} # Refresh because of expiration - resp = client.validate_and_refresh_session( - expired_token, valid_refresh_token + resp = await futu_await( + client.validate_and_refresh_session(expired_token, valid_refresh_token) ) new_session_token_from_request = resp[SESSION_TOKEN_NAME]["jwt"] @@ -526,7 +595,7 @@ def test_expired_token(self): ) # Refresh explicitly - resp = client.refresh_session(valid_refresh_token) + resp = await futu_await(client.refresh_session(valid_refresh_token)) new_session_token_from_request = resp[SESSION_TOKEN_NAME]["jwt"] self.assertEqual( @@ -540,43 +609,53 @@ def test_expired_token(self): new_refreshed_token = ( expired_jwt_token # the refreshed token should be invalid (or expired) ) - with patch("httpx.get") as mock_request: + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_request: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"sessionJwt": new_refreshed_token} mock_request.return_value = my_mock_response mock_request.return_value.cookies = {} - self.assertRaises( - AuthException, - dummy_client.validate_and_refresh_session, - expired_jwt_token, - valid_refresh_token, - ) + with self.assertRaises(AuthException): + await futu_await( + dummy_client.validate_and_refresh_session( + expired_jwt_token, + valid_refresh_token, + ) + ) - def test_public_key_load(self): + async def test_public_key_load(self): # Test key without kty property invalid_public_key = deepcopy(self.public_key_dict) invalid_public_key.pop("kty") with self.assertRaises(AuthException) as cm: - DescopeClient(self.dummy_project_id, invalid_public_key) + DescopeClient( + self.dummy_project_id, invalid_public_key, async_mode=self.async_test + ) self.assertEqual(cm.exception.status_code, 500) # Test key without kid property invalid_public_key = deepcopy(self.public_key_dict) invalid_public_key.pop("kid") with self.assertRaises(AuthException) as cm: - DescopeClient(self.dummy_project_id, invalid_public_key) + DescopeClient( + self.dummy_project_id, invalid_public_key, async_mode=self.async_test + ) self.assertEqual(cm.exception.status_code, 500) # Test key with unknown algorithm invalid_public_key = deepcopy(self.public_key_dict) invalid_public_key["alg"] = "unknown algorithm" with self.assertRaises(AuthException) as cm: - DescopeClient(self.dummy_project_id, invalid_public_key) + DescopeClient( + self.dummy_project_id, invalid_public_key, async_mode=self.async_test + ) self.assertEqual(cm.exception.status_code, 500) - def test_client_properties(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_client_properties(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) self.assertIsNotNone(client) self.assertIsNotNone(client.magiclink, "Empty Magiclink object") self.assertIsNotNone(client.otp, "Empty otp object") @@ -586,8 +665,10 @@ def test_client_properties(self): self.assertIsNotNone(client.sso, "Empty saml object") self.assertIsNotNone(client.webauthn, "Empty webauthN object") - def test_validate_permissions(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_validate_permissions(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) jwt_response = {} self.assertFalse(client.validate_permissions(jwt_response, ["Perm 1"])) @@ -624,8 +705,10 @@ def test_validate_permissions(self): ) self.assertFalse(client.validate_tenant_permissions(jwt_response, "t2", [])) - def test_get_matched_permissions(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_get_matched_permissions(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) jwt_response = {} self.assertEqual(client.get_matched_permissions(jwt_response, []), []) @@ -676,8 +759,10 @@ def test_get_matched_permissions(self): ["Perm 1", "Perm 2"], ) - def test_validate_roles(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_validate_roles(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) jwt_response = {} self.assertFalse(client.validate_roles(jwt_response, ["Role 1"])) @@ -708,8 +793,10 @@ def test_validate_roles(self): client.validate_tenant_roles(jwt_response, "t1", ["Perm 1", "Perm 2"]) ) - def test_get_matched_roles(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_get_matched_roles(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) jwt_response = {} self.assertEqual(client.get_matched_roles(jwt_response, []), []) @@ -753,13 +840,15 @@ def test_get_matched_roles(self): ["Role 1", "Role 2"], ) - def test_exchange_access_key_empty_param(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_exchange_access_key_empty_param(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) with self.assertRaises(AuthException) as cm: client.exchange_access_key("") self.assertEqual(cm.exception.status_code, 400) - def test_exchange_access_key(self): + async def test_exchange_access_key(self): # client = DescopeClient(self.dummy_project_id, self.public_key_dict) client = DescopeClient( self.dummy_project_id, @@ -772,18 +861,21 @@ def test_exchange_access_key(self): "x": "DCjjyS7blnEmenLyJVwmH6yMnp7MlEggfk1kLtOv_Khtpps_Mq4K9brqsCwQhGUP", "y": "xKy4IQ2FaLEzrrl1KE5mKbioLhj1prYFk1itdTOr6Xpy1fgq86kC7v-Y2F2vpcDc", }, + async_mode=self.async_test, ) dummy_access_key = "dummy access key" valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0NDMwNjEsImlhdCI6MTY1OTY0MzA2MSwiaXNzIjoiUDJDdUM5eXYyVUd0R0kxbzg0Z0NaRWI5cUVRVyIsInN1YiI6IlUyQ3VDUHVKZ1BXSEdCNVA0R21mYnVQR2hHVm0ifQ.mRo9FihYMR3qnQT06Mj3CJ5X0uTCEcXASZqfLLUv0cPCLBtBqYTbuK-ZRDnV4e4N6zGCNX2a3jjpbyqbViOxICCNSxJsVb-sdsSujtEXwVMsTTLnpWmNsMbOUiKmoME0" - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True data = {"sessionJwt": valid_jwt_token} my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - jwt_response = client.exchange_access_key( - access_key=dummy_access_key, - login_options=AccessKeyLoginOptions(custom_claims={"k1": "v1"}), + jwt_response = await futu_await( + client.exchange_access_key( + access_key=dummy_access_key, + login_options=AccessKeyLoginOptions(custom_claims={"k1": "v1"}), + ) ) self.assertEqual(jwt_response["keyId"], "U2CuCPuJgPWHGB5P4GmfbuPGhGVm") self.assertEqual(jwt_response["projectId"], "P2CuC9yv2UGtGI1o84gCZEb9qEQW") @@ -802,7 +894,7 @@ def test_exchange_access_key(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_jwt_validation_leeway(self): + async def test_jwt_validation_leeway(self): # Note: I set here negative leeway just for setting the check time results to be in the "past" valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0NDMwNjEsImlhdCI6MTY1OTY0MzA2MSwiaXNzIjoiUDJDdUM5eXYyVUd0R0kxbzg0Z0NaRWI5cUVRVyIsInN1YiI6IlUyQ3VDUHVKZ1BXSEdCNVA0R21mYnVQR2hHVm0ifQ.mRo9FihYMR3qnQT06Mj3CJ5X0uTCEcXASZqfLLUv0cPCLBtBqYTbuK-ZRDnV4e4N6zGCNX2a3jjpbyqbViOxICCNSxJsVb-sdsSujtEXwVMsTTLnpWmNsMbOUiKmoME0" min_int = -sys.maxsize - 1 @@ -828,7 +920,7 @@ def test_jwt_validation_leeway(self): "Received Invalid token (nbf in future) during jwt validation. Error can be due to time glitch (between machines), try to set the jwt_validation_leeway parameter (in DescopeClient) to higher value than 5sec which is the default", ) - def test_select_tenant(self): + async def test_select_tenant(self): client = DescopeClient( self.dummy_project_id, { @@ -840,11 +932,12 @@ def test_select_tenant(self): "x": "DCjjyS7blnEmenLyJVwmH6yMnp7MlEggfk1kLtOv_Khtpps_Mq4K9brqsCwQhGUP", "y": "xKy4IQ2FaLEzrrl1KE5mKbioLhj1prYFk1itdTOr6Xpy1fgq86kC7v-Y2F2vpcDc", }, + async_mode=self.async_test, ) valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0NDMwNjEsImlhdCI6MTY1OTY0MzA2MSwiaXNzIjoiUDJDdUM5eXYyVUd0R0kxbzg0Z0NaRWI5cUVRVyIsInN1YiI6IlUyQ3VDUHVKZ1BXSEdCNVA0R21mYnVQR2hHVm0ifQ.mRo9FihYMR3qnQT06Mj3CJ5X0uTCEcXASZqfLLUv0cPCLBtBqYTbuK-ZRDnV4e4N6zGCNX2a3jjpbyqbViOxICCNSxJsVb-sdsSujtEXwVMsTTLnpWmNsMbOUiKmoME0" - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True my_mock_response = mock.Mock() my_mock_response.is_success = True @@ -854,7 +947,7 @@ def test_select_tenant(self): ) my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - client.select_tenant("t1", valid_jwt_token) + await futu_await(client.select_tenant("t1", valid_jwt_token)) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.select_tenant_path}", headers={ @@ -871,7 +964,7 @@ def test_select_tenant(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_auth_management_key_with_functions(self): + async def test_auth_management_key_with_functions(self): """Test auth_management_key with functions that require and don't require refresh tokens""" auth_mgmt_key = "test-auth-mgmt-key" @@ -880,15 +973,18 @@ def test_auth_management_key_with_functions(self): self.dummy_project_id, self.public_key_dict, auth_management_key=auth_mgmt_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response - client.otp.sign_up(DeliveryMethod.EMAIL, "test@example.com") + await futu_await( + client.otp.sign_up(DeliveryMethod.EMAIL, "test@example.com") + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_otp_path}/email", @@ -913,15 +1009,19 @@ def test_auth_management_key_with_functions(self): with patch.dict( "os.environ", {"DESCOPE_AUTH_MANAGEMENT_KEY": env_auth_mgmt_key} ): - client_env = DescopeClient(self.dummy_project_id, self.public_key_dict) + client_env = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response - client_env.otp.sign_up(DeliveryMethod.EMAIL, "test@example.com") + await futu_await( + client_env.otp.sign_up(DeliveryMethod.EMAIL, "test@example.com") + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_otp_path}/email", @@ -950,15 +1050,20 @@ def test_auth_management_key_with_functions(self): self.dummy_project_id, self.public_key_dict, auth_management_key=direct_auth_mgmt_key, + async_mode=self.async_test, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response - client_priority.otp.sign_up(DeliveryMethod.EMAIL, "test@example.com") + await futu_await( + client_priority.otp.sign_up( + DeliveryMethod.EMAIL, "test@example.com" + ) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_otp_path}/email", @@ -978,24 +1083,27 @@ def test_auth_management_key_with_functions(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_auth_management_key_with_refresh_token(self): + async def test_auth_management_key_with_refresh_token(self): auth_mgmt_key = "test-auth-mgmt-key" client = DescopeClient( self.dummy_project_id, self.public_key_dict, auth_management_key=auth_mgmt_key, + async_mode=self.async_test, ) # Test with refresh token function refresh_token = "test_refresh_token" - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "n***@example.com"} mock_post.return_value = my_mock_response - client.otp.update_user_email( - "old@example.com", "new@example.com", refresh_token + await futu_await( + client.otp.update_user_email( + "old@example.com", "new@example.com", refresh_token + ) ) mock_post.assert_called_with( @@ -1018,15 +1126,19 @@ def test_auth_management_key_with_refresh_token(self): ) # Test without auth_management_key for comparison - client_no_auth = DescopeClient(self.dummy_project_id, self.public_key_dict) - with patch("httpx.post") as mock_post: + client_no_auth = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "n***@example.com"} mock_post.return_value = my_mock_response - client_no_auth.otp.update_user_email( - "old@example.com", "new@example.com", refresh_token + await futu_await( + client_no_auth.otp.update_user_email( + "old@example.com", "new@example.com", refresh_token + ) ) mock_post.assert_called_with( diff --git a/tests/test_enchantedlink.py b/tests/test_enchantedlink.py index 97eef1445..4680f6a23 100644 --- a/tests/test_enchantedlink.py +++ b/tests/test_enchantedlink.py @@ -3,7 +3,7 @@ from unittest import mock from unittest.mock import patch -from descope import SESSION_COOKIE_NAME, AuthException +from descope import SESSION_COOKIE_NAME, AuthException, DescopeClient from descope.auth import Auth from descope.authmethod.enchantedlink import EnchantedLink # noqa: F401 from descope.common import ( @@ -14,7 +14,8 @@ SignUpOptions, ) -from tests.testutils import SSLMatcher +from descope.future_utils import futu_await +from tests.testutils import SSLMatcher, mock_http_call from . import common @@ -32,13 +33,13 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_compose_urls(self): + async def test_compose_urls(self): self.assertEqual( EnchantedLink._compose_signin_url(), "/v1/auth/enchantedlink/signin/email", ) - def test_compose_body(self): + async def test_compose_body(self): self.assertEqual( EnchantedLink._compose_signin_body("id1", "uri1"), { @@ -96,18 +97,26 @@ def test_compose_body(self): {"pendingRef": "pending_ref1"}, ) - def test_sign_in(self): - enchantedlink = EnchantedLink(Auth(self.dummy_project_id, self.public_key_dict)) - with patch("httpx.post") as mock_post: + async def test_sign_in(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True # Test failed flows with self.assertRaises(AuthException): - enchantedlink.sign_in("", "http://test.me") + await futu_await(client.enchantedlink.sign_in("", "http://test.me")) data = json.loads("""{"pendingRef": "aaaa","linkId":"24"}""") - my_mock_response.json.return_value = data + if self.async_test: + # In async mode, json() should return the data directly without being a mock call + my_mock_response.json = mock.Mock(return_value=data) + else: + my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - res = enchantedlink.sign_in("dummy@dummy.com", "http://test.me") + res = await futu_await( + client.enchantedlink.sign_in("dummy@dummy.com", "http://test.me") + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_enchantedlink_path}/email", headers={ @@ -129,13 +138,15 @@ def test_sign_in(self): self.assertEqual(res["linkId"], "24") # Validate refresh token used while provided - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: refresh_token = "dummy refresh token" - enchantedlink.sign_in( - "dummy@dummy.com", - "http://test.me", - LoginOptions(stepup=True), - refresh_token=refresh_token, + await futu_await( + client.enchantedlink.sign_in( + "dummy@dummy.com", + "http://test.me", + LoginOptions(stepup=True), + refresh_token=refresh_token, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_enchantedlink_path}/email", @@ -160,18 +171,20 @@ def test_sign_in(self): ) # With template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: refresh_token = "dummy refresh token" - enchantedlink.sign_in( - "dummy@dummy.com", - "http://test.me", - LoginOptions( - stepup=True, - template_options={"blue": "bla"}, - template_id="foo", - revoke_other_sessions=True, - ), - refresh_token=refresh_token, + await futu_await( + client.enchantedlink.sign_in( + "dummy@dummy.com", + "http://test.me", + LoginOptions( + stepup=True, + template_options={"blue": "bla"}, + template_id="foo", + revoke_other_sessions=True, + ), + refresh_token=refresh_token, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_enchantedlink_path}/email", @@ -198,16 +211,24 @@ def test_sign_in(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_in_with_login_options(self): - enchantedlink = EnchantedLink(Auth(self.dummy_project_id, self.public_key_dict)) - with patch("httpx.post") as mock_post: + async def test_sign_in_with_login_options(self): + enchantedlink = EnchantedLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True data = json.loads("""{"pendingRef": "aaaa", "linkId":"24"}""") my_mock_response.json.return_value = data mock_post.return_value = my_mock_response lo = LoginOptions(stepup=True, custom_claims={"k1": "v1"}) - enchantedlink.sign_in("dummy@dummy.com", "http://test.me", lo, "refresh") + await futu_await( + enchantedlink.sign_in( + "dummy@dummy.com", "http://test.me", lo, "refresh" + ) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_enchantedlink_path}/email", headers={ @@ -230,28 +251,35 @@ def test_sign_in_with_login_options(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_up(self): - enchantedlink = EnchantedLink(Auth(self.dummy_project_id, self.public_key_dict)) - with patch("httpx.post") as mock_post: + async def test_sign_up(self): + enchantedlink = EnchantedLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True # Test failed flows - self.assertRaises( - AuthException, - enchantedlink.sign_up, - "", - "http://test.me", - {"name": "john"}, - ) + with self.assertRaises(AuthException): + await futu_await( + enchantedlink.sign_up( + "", + "http://test.me", + {"name": "john"}, + ) + ) data = json.loads("""{"pendingRef": "aaaa"}""") my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - res = enchantedlink.sign_up( - "dummy@dummy.com", - "http://test.me", - {"username": "user1", "email": "dummy@dummy.com"}, + res = await futu_await( + enchantedlink.sign_up( + "dummy@dummy.com", + "http://test.me", + {"username": "user1", "email": "dummy@dummy.com"}, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_enchantedlink_path}/email", @@ -274,14 +302,16 @@ def test_sign_up(self): self.assertEqual(res["pendingRef"], "aaaa") # Test user is None so using the login_id as default - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: data = json.loads("""{"pendingRef": "aaaa"}""") my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - res = enchantedlink.sign_up( - "dummy@dummy.com", - "http://test.me", - None, + res = await futu_await( + enchantedlink.sign_up( + "dummy@dummy.com", + "http://test.me", + None, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_enchantedlink_path}/email", @@ -304,19 +334,21 @@ def test_sign_up(self): self.assertEqual(res["pendingRef"], "aaaa") # Test success flow with sign up options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: data = json.loads("""{"pendingRef": "aaaa"}""") my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - res = enchantedlink.sign_up( - "dummy@dummy.com", - "http://test.me", - None, - SignUpOptions( - template_options={"bla": "blue"}, - template_id="foo", - revoke_other_sessions=True, - ), + res = await futu_await( + enchantedlink.sign_up( + "dummy@dummy.com", + "http://test.me", + None, + SignUpOptions( + template_options={"bla": "blue"}, + template_id="foo", + revoke_other_sessions=True, + ), + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_enchantedlink_path}/email", @@ -343,17 +375,23 @@ def test_sign_up(self): ) self.assertEqual(res["pendingRef"], "aaaa") - def test_sign_up_or_in(self): - enchantedlink = EnchantedLink(Auth(self.dummy_project_id, self.public_key_dict)) - with patch("httpx.post") as mock_post: + async def test_sign_up_or_in(self): + enchantedlink = EnchantedLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True data = json.loads("""{"pendingRef": "aaaa"}""") my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - enchantedlink.sign_up_or_in( - "dummy@dummy.com", - "http://test.me", + await futu_await( + enchantedlink.sign_up_or_in( + "dummy@dummy.com", + "http://test.me", + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_or_in_auth_enchantedlink_path}/email", @@ -374,16 +412,18 @@ def test_sign_up_or_in(self): ) # Test success flow with sign up options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True data = json.loads("""{"pendingRef": "aaaa"}""") my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - enchantedlink.sign_up_or_in( - "dummy@dummy.com", - "http://test.me", - SignUpOptions(template_options={"bla": "blue"}), + await futu_await( + enchantedlink.sign_up_or_in( + "dummy@dummy.com", + "http://test.me", + SignUpOptions(template_options={"bla": "blue"}), + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_or_in_auth_enchantedlink_path}/email", @@ -408,22 +448,27 @@ def test_sign_up_or_in(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_verify(self): + async def test_verify(self): token = "1234" - enchantedlink = EnchantedLink(Auth(self.dummy_project_id, self.public_key_dict)) + enchantedlink = EnchantedLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - enchantedlink.verify, - token, - ) + with self.assertRaises(AuthException): + await futu_await( + enchantedlink.verify( + token, + ) + ) # Test success flow valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3R6VWhkcXBJRjJ5czlnZzdtczA2VXZ0QzQiLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0Mzc1OTYsImlhdCI6MTY1OTYzNzU5NiwiaXNzIjoiUDJDdHpVaGRxcElGMnlzOWdnN21zMDZVdnRDNCIsInN1YiI6IlUyQ3UwajBXUHczWU9pUElTSmI1Mkwwd1VWTWcifQ.WLnlHugvzZtrV9OzBB7SjpCLNRvKF3ImFpVyIN5orkrjO2iyAKg_Rb4XHk9sXGC1aW8puYzLbhE1Jv3kk2hDcKggfE8OaRNRm8byhGFZHnvPJwcP_Ya-aRmfAvCLcKOL" - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {} @@ -432,13 +477,17 @@ def test_verify(self): SESSION_COOKIE_NAME: "dummy session token", REFRESH_SESSION_COOKIE_NAME: valid_jwt_token, } - self.assertIsNone(enchantedlink.verify(token)) + self.assertIsNone(await futu_await(enchantedlink.verify(token))) - def test_get_session(self): - enchantedlink = EnchantedLink(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_get_session(self): + enchantedlink = EnchantedLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3R6VWhkcXBJRjJ5czlnZzdtczA2VXZ0QzQiLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0Mzc1OTYsImlhdCI6MTY1OTYzNzU5NiwiaXNzIjoiUDJDdHpVaGRxcElGMnlzOWdnN21zMDZVdnRDNCIsInN1YiI6IlUyQ3UwajBXUHczWU9pUElTSmI1Mkwwd1VWTWcifQ.WLnlHugvzZtrV9OzBB7SjpCLNRvKF3ImFpVyIN5orkrjO2iyAKg_Rb4XHk9sXGC1aW8puYzLbhE1Jv3kk2hDcKggfE8OaRNRm8byhGFZHnvPJwcP_Ya-aRmfAvCLcKOL" - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {} @@ -447,26 +496,33 @@ def test_get_session(self): SESSION_COOKIE_NAME: "dummy session token", REFRESH_SESSION_COOKIE_NAME: valid_jwt_token, } - self.assertIsNotNone(enchantedlink.get_session("aaaaaa")) + self.assertIsNotNone(await futu_await(enchantedlink.get_session("aaaaaa"))) - def test_update_user_email(self): - enchantedlink = EnchantedLink(Auth(self.dummy_project_id, self.public_key_dict)) - with patch("httpx.post") as mock_post: + async def test_update_user_email(self): + enchantedlink = EnchantedLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True # Test failed flows - self.assertRaises( - AuthException, - enchantedlink.update_user_email, - "", - "dummy@dummy.com", - "refresh_token1", - ) + with self.assertRaises(AuthException): + await futu_await( + enchantedlink.update_user_email( + "", + "dummy@dummy.com", + "refresh_token1", + ) + ) data = json.loads("""{"pendingRef": "aaaa"}""") my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - res = enchantedlink.update_user_email( - "id1", "dummy@dummy.com", "refresh_token1" + res = await futu_await( + enchantedlink.update_user_email( + "id1", "dummy@dummy.com", "refresh_token1" + ) ) self.assertEqual(res["pendingRef"], "aaaa") mock_post.assert_called_with( @@ -489,17 +545,19 @@ def test_update_user_email(self): ) # with template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True data = json.loads("""{"pendingRef": "aaaa"}""") my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - res = enchantedlink.update_user_email( - "id1", - "dummy@dummy.com", - "refresh_token1", - template_options={"bla": "blue"}, + res = await futu_await( + enchantedlink.update_user_email( + "id1", + "dummy@dummy.com", + "refresh_token1", + template_options={"bla": "blue"}, + ) ) self.assertEqual(res["pendingRef"], "aaaa") mock_post.assert_called_with( diff --git a/tests/test_flask.py b/tests/test_flask.py index f722f8218..0bbb56808 100644 --- a/tests/test_flask.py +++ b/tests/test_flask.py @@ -53,7 +53,7 @@ def setUp(self): "tenants": {"tenant1": {"permissions": ["manage"], "roles": ["owner"]}}, } - def test_set_cookie_on_response(self): + async def test_set_cookie_on_response(self): """Test cookie setting utility function""" response = Response("test") token = {"drn": "DST", "jwt": "test-token"} @@ -63,7 +63,7 @@ def test_set_cookie_on_response(self): # Verify cookie was set (Flask sets cookies in headers) self.assertIsInstance(response, Response) - def test_otp_signup_decorator_success(self): + async def test_otp_signup_decorator_success(self): """Test OTP signup decorator with valid data""" @self.app.route("/signup", methods=["POST"]) @@ -84,7 +84,7 @@ def signup(): DeliveryMethod.EMAIL, "test@example.com", {"name": "Test User"} ) - def test_otp_signup_decorator_missing_email(self): + async def test_otp_signup_decorator_missing_email(self): """Test OTP signup decorator with missing email""" @self.app.route("/signup", methods=["POST"]) @@ -97,7 +97,7 @@ def signup(): self.assertEqual(response.status_code, 400) self.assertIn(b"Invalid Request, missing email", response.data) - def test_otp_signup_decorator_auth_exception(self): + async def test_otp_signup_decorator_auth_exception(self): """Test OTP signup decorator with AuthException""" @self.app.route("/signup", methods=["POST"]) @@ -117,7 +117,7 @@ def signup(): self.assertEqual(response.status_code, 500) self.assertIn(b"Unable to sign-up user", response.data) - def test_otp_signin_decorator_success(self): + async def test_otp_signin_decorator_success(self): """Test OTP signin decorator with valid data""" @self.app.route("/signin", methods=["POST"]) @@ -135,7 +135,7 @@ def signin(): DeliveryMethod.EMAIL, "test@example.com" ) - def test_otp_verify_decorator_success(self): + async def test_otp_verify_decorator_success(self): """Test OTP verify decorator with valid code""" @self.app.route("/verify", methods=["POST"]) @@ -155,7 +155,7 @@ def verify(): DeliveryMethod.EMAIL, "test@example.com", "123456" ) - def test_otp_verify_decorator_missing_data(self): + async def test_otp_verify_decorator_missing_data(self): """Test OTP verify decorator with missing email or code""" @self.app.route("/verify", methods=["POST"]) @@ -171,7 +171,7 @@ def verify(): response = self.client.post("/verify", json={"email": "test@example.com"}) self.assertEqual(response.status_code, 401) - def test_sms_verify_decorator_success(self): + async def test_sms_verify_decorator_success(self): """Test SMS verify decorator with valid code""" @self.app.route("/verify-sms", methods=["POST"]) @@ -191,7 +191,7 @@ def verify_sms(): DeliveryMethod.SMS, "+1234567890", "123456" ) - def test_validate_auth_decorator_success(self): + async def test_validate_auth_decorator_success(self): """Test auth validation decorator with valid session""" @self.app.route("/protected") @@ -214,7 +214,7 @@ def protected(): self.assertEqual(response.status_code, 200) mock_validate.assert_called_once() - def test_validate_auth_decorator_no_session(self): + async def test_validate_auth_decorator_no_session(self): """Test auth validation decorator without session""" @self.app.route("/protected") @@ -234,7 +234,7 @@ def protected(): self.assertEqual(response.status_code, 401) self.assertIn(b"Access denied", response.data) - def test_validate_auth_decorator_with_permissions(self): + async def test_validate_auth_decorator_with_permissions(self): """Test auth validation decorator with permission requirements""" @self.app.route("/admin") @@ -261,7 +261,7 @@ def admin(): self.assertEqual(response.status_code, 200) mock_perms.assert_called_once_with(self.mock_jwt_response, ["admin"]) - def test_validate_auth_decorator_insufficient_permissions(self): + async def test_validate_auth_decorator_insufficient_permissions(self): """Test auth validation decorator with insufficient permissions""" @self.app.route("/admin") @@ -287,7 +287,7 @@ def admin(): self.assertEqual(response.status_code, 401) - def test_validate_auth_decorator_with_roles(self): + async def test_validate_auth_decorator_with_roles(self): """Test auth validation decorator with role requirements""" @self.app.route("/manager") @@ -312,7 +312,7 @@ def manager(): self.assertEqual(response.status_code, 200) mock_roles.assert_called_once_with(self.mock_jwt_response, ["manager"]) - def test_validate_auth_decorator_with_tenant(self): + async def test_validate_auth_decorator_with_tenant(self): """Test auth validation decorator with tenant-specific permissions""" @self.app.route("/tenant-admin") @@ -343,7 +343,7 @@ def tenant_admin(): self.mock_jwt_response, ["manage"] ) - def test_magiclink_signup_decorator_success(self): + async def test_magiclink_signup_decorator_success(self): """Test MagicLink signup decorator with valid data""" @self.app.route("/magiclink-signup", methods=["POST"]) @@ -369,7 +369,7 @@ def magiclink_signup(): {"name": "Test User"}, ) - def test_magiclink_signin_decorator_success(self): + async def test_magiclink_signin_decorator_success(self): """Test MagicLink signin decorator with valid data""" @self.app.route("/magiclink-signin", methods=["POST"]) @@ -391,7 +391,7 @@ def magiclink_signin(): DeliveryMethod.EMAIL, "test@example.com", "http://example.com/verify" ) - def test_magiclink_verify_decorator_success(self): + async def test_magiclink_verify_decorator_success(self): """Test MagicLink verify decorator with valid token""" @self.app.route("/magiclink-verify") @@ -407,7 +407,7 @@ def magiclink_verify(): self.assertEqual(response.status_code, 200) mock_verify.assert_called_once_with("mock-token") - def test_magiclink_verify_decorator_missing_token(self): + async def test_magiclink_verify_decorator_missing_token(self): """Test MagicLink verify decorator without token""" @self.app.route("/magiclink-verify") @@ -420,7 +420,7 @@ def magiclink_verify(): self.assertEqual(response.status_code, 401) self.assertIn(b"Unauthorized", response.data) - def test_oauth_decorator_success(self): + async def test_oauth_decorator_success(self): """Test OAuth decorator with valid provider""" @self.app.route("/oauth") @@ -436,7 +436,7 @@ def oauth(*args, **kwargs): self.assertEqual(response.status_code, 302) mock_start.assert_called_once_with("google") - def test_oauth_decorator_auth_exception(self): + async def test_oauth_decorator_auth_exception(self): """Test OAuth decorator with AuthException""" @self.app.route("/oauth") @@ -454,7 +454,7 @@ def oauth(): self.assertEqual(response.status_code, 400) self.assertIn(b"OAuth failed", response.data) - def test_logout_decorator_success(self): + async def test_logout_decorator_success(self): """Test logout decorator with valid refresh token""" @self.app.route("/logout", methods=["POST"]) @@ -476,7 +476,7 @@ def logout(): # The decorator should extract the refresh token from cookies and call logout mock_logout.assert_called_once_with("mock-refresh-token") - def test_logout_decorator_auth_exception(self): + async def test_logout_decorator_auth_exception(self): """Test logout decorator with AuthException""" @self.app.route("/logout", methods=["POST"]) @@ -497,7 +497,7 @@ def logout(): self.assertEqual(response.status_code, 400) self.assertIn(b"Logout failed", response.data) - def test_full_login_decorator_success(self): + async def test_full_login_decorator_success(self): """Test full login decorator generates correct HTML""" @self.app.route("/login") @@ -517,7 +517,7 @@ def login(): self.assertIn(b"sign-up-or-in", response.data) self.assertIn(b"http://localhost/success", response.data) - def test_full_login_decorator_missing_redirect_url(self): + async def test_full_login_decorator_missing_redirect_url(self): """Test full login decorator with missing redirect URL""" @descope_full_login( @@ -534,7 +534,7 @@ def login(): self.assertEqual(context.exception.status_code, 500) self.assertIn("Missing success_redirect_url", str(context.exception)) - def test_request_context_claims_storage(self): + async def test_request_context_claims_storage(self): """Test that JWT claims are stored in Flask request context""" @self.app.route("/context-test") diff --git a/tests/test_magiclink.py b/tests/test_magiclink.py index 1b1401632..c47d89546 100644 --- a/tests/test_magiclink.py +++ b/tests/test_magiclink.py @@ -14,7 +14,8 @@ SignUpOptions, ) -from tests.testutils import SSLMatcher +from descope.future_utils import futu_await +from tests.testutils import SSLMatcher, mock_http_call from . import common @@ -32,7 +33,7 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_compose_urls(self): + async def test_compose_urls(self): self.assertEqual( MagicLink._compose_signin_url(DeliveryMethod.SMS), "/v1/auth/magiclink/signin/sms", @@ -51,7 +52,7 @@ def test_compose_urls(self): "/v1/auth/magiclink/update/phone/sms", ) - def test_compose_body(self): + async def test_compose_body(self): self.assertEqual( MagicLink._compose_signin_body("id1", "uri1"), { @@ -112,30 +113,36 @@ def test_compose_body(self): }, ) - def test_sign_in(self): - magiclink = MagicLink(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_in(self): + magiclink = MagicLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, - magiclink.sign_in, - DeliveryMethod.EMAIL, - None, - "http://test.me", - ) + with self.assertRaises(AuthException): + await futu_await( + magiclink.sign_in( + DeliveryMethod.EMAIL, + None, + "http://test.me", + ) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - magiclink.sign_in, - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - ) + with self.assertRaises(AuthException): + await futu_await( + magiclink.sign_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} @@ -143,29 +150,40 @@ def test_sign_in(self): mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - magiclink.sign_in( - DeliveryMethod.EMAIL, "dummy@dummy.com", "http://test.me" + await futu_await( + magiclink.sign_in( + DeliveryMethod.EMAIL, "dummy@dummy.com", "http://test.me" + ) ), ) - self.assertRaises( - AuthException, - magiclink.sign_in, - DeliveryMethod.EMAIL, - "exid", - "http://test.me", - LoginOptions(mfa=True), - ) + with self.assertRaises(AuthException): + + await futu_await( + magiclink.sign_in( + DeliveryMethod.EMAIL, + "exid", + "http://test.me", + LoginOptions(mfa=True), + ), + ) # Validate refresh token used while provided - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: + my_mock_response = mock.Mock() + my_mock_response.is_success = True + my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} + mock_post.return_value = my_mock_response + refresh_token = "dummy refresh token" - magiclink.sign_in( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - LoginOptions(stepup=True), - refresh_token=refresh_token, + await futu_await( + magiclink.sign_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + LoginOptions(stepup=True), + refresh_token=refresh_token, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_magiclink_path}/email", @@ -190,16 +208,23 @@ def test_sign_in(self): ) # With template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: + my_mock_response = mock.Mock() + my_mock_response.is_success = True + my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} + mock_post.return_value = my_mock_response + refresh_token = "dummy refresh token" - magiclink.sign_in( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - LoginOptions( - stepup=True, template_options={"blue": "bla"}, template_id=None - ), - refresh_token=refresh_token, + await futu_await( + magiclink.sign_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + LoginOptions( + stepup=True, template_options={"blue": "bla"}, template_id=None + ), + refresh_token=refresh_token, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_magiclink_path}/email", @@ -224,7 +249,7 @@ def test_sign_in(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_up(self): + async def test_sign_up(self): signup_user_details = { "username": "jhon", "name": "john", @@ -232,55 +257,65 @@ def test_sign_up(self): "email": "dummy@dummy.com", } - magiclink = MagicLink(Auth(self.dummy_project_id, self.public_key_dict)) + magiclink = MagicLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, - magiclink.sign_up, - DeliveryMethod.EMAIL, - None, - "http://test.me", - signup_user_details, - ) + with self.assertRaises(AuthException): + await futu_await( + magiclink.sign_up( + DeliveryMethod.EMAIL, + None, + "http://test.me", + signup_user_details, + ) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - magiclink.sign_up, - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - signup_user_details, - ) + with self.assertRaises(AuthException): + await futu_await( + magiclink.sign_up( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + signup_user_details, + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response - resp = magiclink.sign_up( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - signup_user_details, + resp = await futu_await( + magiclink.sign_up( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + signup_user_details, + ) ) self.assertEqual("t***@example.com", resp) # Test success flow with sign up options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response - resp = magiclink.sign_up( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - signup_user_details, - SignUpOptions(template_options={"bla": "blue"}, template_id="foo"), + resp = await futu_await( + magiclink.sign_up( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + signup_user_details, + SignUpOptions(template_options={"bla": "blue"}, template_id="foo"), + ) ) self.assertEqual("t***@example.com", resp) @@ -320,18 +355,20 @@ def test_sign_up(self): "email": "dummy@dummy.com", } - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - magiclink.sign_up( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - signup_user_details, + await futu_await( + magiclink.sign_up( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + signup_user_details, + ) ), ) mock_post.assert_called_with( @@ -359,18 +396,20 @@ def test_sign_up(self): ) # Test user is None so using the login_id as default - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - magiclink.sign_up( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - None, + await futu_await( + magiclink.sign_up( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + None, + ) ), ) mock_post.assert_called_with( @@ -392,47 +431,58 @@ def test_sign_up(self): params=None, ) - def test_sign_up_or_in(self): - magiclink = MagicLink(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_up_or_in(self): + magiclink = MagicLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - magiclink.sign_up_or_in, - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - ) + with self.assertRaises(AuthException): + await futu_await( + magiclink.sign_up_or_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - magiclink.sign_up_or_in( - DeliveryMethod.EMAIL, "dummy@dummy.com", "http://test.me" + await futu_await( + magiclink.sign_up_or_in( + DeliveryMethod.EMAIL, "dummy@dummy.com", "http://test.me" + ) ), ) # Test success flow with sign up options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - magiclink.sign_up_or_in( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - "http://test.me", - SignUpOptions(template_options={"bla": "blue"}, template_id="foo"), + await futu_await( + magiclink.sign_up_or_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + "http://test.me", + SignUpOptions( + template_options={"bla": "blue"}, template_id="foo" + ), + ) ), ) mock_post.assert_called_with( @@ -459,22 +509,27 @@ def test_sign_up_or_in(self): params=None, ) - def test_verify(self): + async def test_verify(self): token = "1234" - magiclink = MagicLink(Auth(self.dummy_project_id, self.public_key_dict)) + magiclink = MagicLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - magiclink.verify, - token, - ) + with self.assertRaises(AuthException): + await futu_await( + magiclink.verify( + token, + ) + ) # Test success flow valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3R6VWhkcXBJRjJ5czlnZzdtczA2VXZ0QzQiLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0Mzc1OTYsImlhdCI6MTY1OTYzNzU5NiwiaXNzIjoiUDJDdHpVaGRxcElGMnlzOWdnN21zMDZVdnRDNCIsInN1YiI6IlUyQ3UwajBXUHczWU9pUElTSmI1Mkwwd1VWTWcifQ.WLnlHugvzZtrV9OzBB7SjpCLNRvKF3ImFpVyIN5orkrjO2iyAKg_Rb4XHk9sXGC1aW8puYzLbhE1Jv3kk2hDcKggfE8OaRNRm8byhGFZHnvPJwcP_Ya-aRmfAvCLcKOL" - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {} @@ -483,21 +538,22 @@ def test_verify(self): SESSION_COOKIE_NAME: "dummy session token", REFRESH_SESSION_COOKIE_NAME: valid_jwt_token, } - self.assertIsNotNone(magiclink.verify(token)) + self.assertIsNotNone(await futu_await(magiclink.verify(token))) - def test_verify_with_get_keys_mock(self): + async def test_verify_with_get_keys_mock(self): token = "1234" magiclink = MagicLink( - Auth(self.dummy_project_id, None) + Auth(self.dummy_project_id, None, async_mode=self.async_test) ) # public key will be "fetched" by Get mock # Test success flow valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3R6VWhkcXBJRjJ5czlnZzdtczA2VXZ0QzQiLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0Mzc1OTYsImlhdCI6MTY1OTYzNzU5NiwiaXNzIjoiUDJDdHpVaGRxcElGMnlzOWdnN21zMDZVdnRDNCIsInN1YiI6IlUyQ3UwajBXUHczWU9pUElTSmI1Mkwwd1VWTWcifQ.WLnlHugvzZtrV9OzBB7SjpCLNRvKF3ImFpVyIN5orkrjO2iyAKg_Rb4XHk9sXGC1aW8puYzLbhE1Jv3kk2hDcKggfE8OaRNRm8byhGFZHnvPJwcP_Ya-aRmfAvCLcKOL" - with patch("httpx.get") as mock_get: + # _fetch_public_keys is always sync + with mock_http_call(False, "get") as mock_get: mock_get.return_value.text = json.dumps({"keys": [self.public_key_dict]}) mock_get.return_value.is_success = True - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {} @@ -506,38 +562,49 @@ def test_verify_with_get_keys_mock(self): SESSION_COOKIE_NAME: "dummy session token", REFRESH_SESSION_COOKIE_NAME: valid_jwt_token, } - self.assertIsNotNone(magiclink.verify(token)) + self.assertIsNotNone(await futu_await(magiclink.verify(token))) - def test_update_user_email(self): - magiclink = MagicLink(Auth(self.dummy_project_id, self.public_key_dict)) - - self.assertRaises( - AuthException, - magiclink.update_user_email, - "", - "dummy@dummy.com", - "refresh_token1", + async def test_update_user_email(self): + magiclink = MagicLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) ) - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - magiclink.update_user_email, - "id1", - "dummy@dummy.com", - "refresh_token1", + with self.assertRaises(AuthException): + + await futu_await( + magiclink.update_user_email( + "", + "dummy@dummy.com", + "refresh_token1", + ) ) + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + magiclink.update_user_email( + "id1", + "dummy@dummy.com", + "refresh_token1", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - magiclink.update_user_email("id1", "dummy@dummy.com", "refresh_token1"), + await futu_await( + magiclink.update_user_email( + "id1", "dummy@dummy.com", "refresh_token1" + ) + ), ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.update_user_email_magiclink_path}", @@ -559,18 +626,20 @@ def test_update_user_email(self): ) # Test success flow with template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - magiclink.update_user_email( - "id1", - "dummy@dummy.com", - "refresh_token1", - template_options={"bla": "blue"}, + await futu_await( + magiclink.update_user_email( + "id1", + "dummy@dummy.com", + "refresh_token1", + template_options={"bla": "blue"}, + ) ), ) mock_post.assert_called_with( @@ -593,39 +662,48 @@ def test_update_user_email(self): params=None, ) - def test_update_user_phone(self): - magiclink = MagicLink(Auth(self.dummy_project_id, self.public_key_dict)) - - self.assertRaises( - AuthException, - magiclink.update_user_phone, - DeliveryMethod.EMAIL, - "", - "+11111111", - "refresh_token1", + async def test_update_user_phone(self): + magiclink = MagicLink( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) ) - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - magiclink.update_user_phone, - DeliveryMethod.EMAIL, - "id1", - "+11111111", - "refresh_token1", + with self.assertRaises(AuthException): + + await futu_await( + magiclink.update_user_phone( + DeliveryMethod.EMAIL, + "", + "+11111111", + "refresh_token1", + ) ) + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + magiclink.update_user_phone( + DeliveryMethod.EMAIL, + "id1", + "+11111111", + "refresh_token1", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedPhone": "*****1111"} mock_post.return_value = my_mock_response self.assertEqual( "*****1111", - magiclink.update_user_phone( - DeliveryMethod.SMS, "id1", "+11111111", "refresh_token1" + await futu_await( + magiclink.update_user_phone( + DeliveryMethod.SMS, "id1", "+11111111", "refresh_token1" + ) ), ) mock_post.assert_called_with( @@ -648,19 +726,21 @@ def test_update_user_phone(self): ) # Test success flow with template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedPhone": "*****1111"} mock_post.return_value = my_mock_response self.assertEqual( "*****1111", - magiclink.update_user_phone( - DeliveryMethod.SMS, - "id1", - "+11111111", - "refresh_token1", - template_options={"bla": "blue"}, + await futu_await( + magiclink.update_user_phone( + DeliveryMethod.SMS, + "id1", + "+11111111", + "refresh_token1", + template_options={"bla": "blue"}, + ) ), ) mock_post.assert_called_with( diff --git a/tests/test_oauth.py b/tests/test_oauth.py index c9138d939..51b1ee598 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -7,9 +7,10 @@ from descope.auth import Auth from descope.authmethod.oauth import OAuth from descope.common import DEFAULT_TIMEOUT_SECONDS, EndpointsV1, LoginOptions +from descope.future_utils import futu_await from . import common -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call class TestOAuth(common.DescopeTest): @@ -26,13 +27,13 @@ def setUp(self) -> None: "y": "N5n5jKZA5Wu7_b4B36KKjJf-VRfJ-XqczfCSYy9GeQLqF-b63idfE0SYaYk9cFqg", } - def test_compose_start_params(self): + async def test_compose_start_params(self): self.assertEqual( OAuth._compose_start_params("google", "http://example.com"), {"provider": "google", "redirectURL": "http://example.com"}, ) - def test_verify_oauth_providers(self): + async def test_verify_oauth_providers(self): self.assertEqual( OAuth._verify_provider(""), False, @@ -43,32 +44,36 @@ def test_verify_oauth_providers(self): False, ) - def test_oauth_start(self): - oauth = OAuth(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_oauth_start(self): + oauth = OAuth( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, oauth.start, "") + with self.assertRaises(AuthException): + await futu_await(oauth.start("")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, oauth.start, "google") + with self.assertRaises(AuthException): + await futu_await(oauth.start("google")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(oauth.start("google")) - - self.assertRaises( - AuthException, - oauth.start, - "facebook", - "http://test.me", - LoginOptions(mfa=True), - ) + mock_post.json.return_value = {} + self.assertIsNotNone(await futu_await(oauth.start("google"))) - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + await futu_await( + oauth.start("facebook", "http://test.me", LoginOptions(mfa=True)), + ) + + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - oauth.start("facebook") + await futu_await(oauth.start("facebook")) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.oauth_start_path}" mock_post.assert_called_with( expected_uri, @@ -84,25 +89,33 @@ def test_oauth_start(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_oauth_start_with_login_options(self): - oauth = OAuth(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_oauth_start_with_login_options(self): + oauth = OAuth( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, oauth.start, "") + with self.assertRaises(AuthException): + await futu_await(oauth.start("")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, oauth.start, "google") + with self.assertRaises(AuthException): + await futu_await(oauth.start("google")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(oauth.start("google")) + self.assertIsNotNone(await futu_await(oauth.start("google"))) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True lo = LoginOptions(stepup=True, custom_claims={"k1": "v1"}) - oauth.start("facebook", login_options=lo, refresh_token="refresh") + await futu_await( + oauth.start("facebook", login_options=lo, refresh_token="refresh") + ) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.oauth_start_path}" mock_post.assert_called_with( expected_uri, @@ -118,22 +131,29 @@ def test_oauth_start_with_login_options(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_compose_exchange_params(self): + async def test_compose_exchange_params(self): self.assertEqual(Auth._compose_exchange_body("c1"), {"code": "c1"}) - def test_exchange_token(self): - oauth = OAuth(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_exchange_token(self): + oauth = OAuth( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, oauth.exchange_token, "") - self.assertRaises(AuthException, oauth.exchange_token, None) + with self.assertRaises(AuthException): + await futu_await(oauth.exchange_token("")) + with self.assertRaises(AuthException): + await futu_await(oauth.exchange_token(None)) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, oauth.exchange_token, "c1") + with self.assertRaises(AuthException): + await futu_await(oauth.exchange_token("c1")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.cookies = {} @@ -142,7 +162,7 @@ def test_exchange_token(self): ) my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - oauth.exchange_token("c1") + await futu_await(oauth.exchange_token("c1")) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.oauth_exchange_token_path}", headers={ diff --git a/tests/test_otp.py b/tests/test_otp.py index 3a50c1789..c996353e3 100644 --- a/tests/test_otp.py +++ b/tests/test_otp.py @@ -12,13 +12,15 @@ SignUpOptions, ) -from tests.testutils import SSLMatcher +from descope.future_utils import futu_await +from tests.testutils import SSLMatcher, mock_http_call from . import common class TestOTP(common.DescopeTest): def setUp(self) -> None: super().setUp() + self.async_mode = True self.dummy_project_id = "dummy" self.public_key_dict = { "alg": "ES384", @@ -30,7 +32,7 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_compose_signin_url(self): + async def test_compose_signin_url(self): self.assertEqual( OTP._compose_signin_url(DeliveryMethod.EMAIL), "/v1/auth/otp/signin/email", @@ -48,7 +50,7 @@ def test_compose_signin_url(self): "/v1/auth/otp/signin/whatsapp", ) - def test_compose_verify_code_url(self): + async def test_compose_verify_code_url(self): self.assertEqual( OTP._compose_verify_code_url(DeliveryMethod.EMAIL), "/v1/auth/otp/verify/email", @@ -66,7 +68,7 @@ def test_compose_verify_code_url(self): "/v1/auth/otp/verify/whatsapp", ) - def test_compose_update_phone_url(self): + async def test_compose_update_phone_url(self): self.assertEqual( OTP._compose_update_phone_url(DeliveryMethod.EMAIL), "/v1/auth/otp/update/phone/email", @@ -84,7 +86,7 @@ def test_compose_update_phone_url(self): "/v1/auth/otp/update/phone/whatsapp", ) - def test_compose_sign_up_or_in_url(self): + async def test_compose_sign_up_or_in_url(self): self.assertEqual( OTP._compose_sign_up_or_in_url(DeliveryMethod.EMAIL), "/v1/auth/otp/signup-in/email", @@ -102,7 +104,7 @@ def test_compose_sign_up_or_in_url(self): "/v1/auth/otp/signup-in/whatsapp", ) - def test_compose_update_user_phone_body(self): + async def test_compose_update_user_phone_body(self): self.assertEqual( OTP._compose_update_user_phone_body( "dummy@dummy.com", "+11111111", False, True @@ -115,7 +117,7 @@ def test_compose_update_user_phone_body(self): }, ) - def test_compose_update_user_email_body(self): + async def test_compose_update_user_email_body(self): self.assertEqual( OTP._compose_update_user_email_body( "dummy@dummy.com", "dummy@dummy.com", False, True @@ -128,7 +130,7 @@ def test_compose_update_user_email_body(self): }, ) - def test_sign_up(self): + async def test_sign_up(self): invalid_signup_user_details = { "username": "jhon", "name": "john", @@ -142,67 +144,77 @@ def test_sign_up(self): "email": "dummy@dummy.com", } - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_mode + ) # Test failed flows - self.assertRaises( - AuthException, - client.otp.sign_up, - DeliveryMethod.EMAIL, - "dummy@dummy", - invalid_signup_user_details, - ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_up( + DeliveryMethod.EMAIL, + "dummy@dummy", + invalid_signup_user_details, + ) + ) invalid_signup_user_details["email"] = "dummy@dummy.com" # set valid mail invalid_signup_user_details["phone"] = "aaaaaaaa" # set invalid phone - self.assertRaises( - AuthException, - client.otp.sign_up, - DeliveryMethod.EMAIL, - "", - invalid_signup_user_details, - ) - self.assertRaises( - AuthException, - client.otp.sign_up, - DeliveryMethod.SMS, - "dummy@dummy.com", - invalid_signup_user_details, - ) - self.assertRaises( - AuthException, - client.otp.sign_up, - DeliveryMethod.VOICE, - "dummy@dummy.com", - invalid_signup_user_details, - ) - self.assertRaises( - AuthException, - client.otp.sign_up, - DeliveryMethod.WHATSAPP, - "dummy@dummy.com", - invalid_signup_user_details, - ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_up( + DeliveryMethod.EMAIL, + "", + invalid_signup_user_details, + ) + ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_up( + DeliveryMethod.SMS, + "dummy@dummy.com", + invalid_signup_user_details, + ) + ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_up( + DeliveryMethod.VOICE, + "dummy@dummy.com", + invalid_signup_user_details, + ) + ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_up( + DeliveryMethod.WHATSAPP, + "dummy@dummy.com", + invalid_signup_user_details, + ) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.otp.sign_up, - DeliveryMethod.EMAIL, - "dummy@dummy.com", - signup_user_details, - ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_up( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + signup_user_details, + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.sign_up( - DeliveryMethod.EMAIL, "dummy@dummy.com", signup_user_details + await futu_await( + client.otp.sign_up( + DeliveryMethod.EMAIL, "dummy@dummy.com", signup_user_details + ) ), ) @@ -214,15 +226,17 @@ def test_sign_up(self): "email": "dummy@dummy.com", } - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.sign_up( - DeliveryMethod.EMAIL, "dummy@dummy.com", signup_user_details + await futu_await( + client.otp.sign_up( + DeliveryMethod.EMAIL, "dummy@dummy.com", signup_user_details + ) ), ) mock_post.assert_called_with( @@ -249,18 +263,22 @@ def test_sign_up(self): ) # Test success flow with sign up options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.sign_up( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - signup_user_details, - SignUpOptions(template_options={"bla": "blue"}, template_id="foo"), + await futu_await( + client.otp.sign_up( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + signup_user_details, + SignUpOptions( + template_options={"bla": "blue"}, template_id="foo" + ), + ) ), ) mock_post.assert_called_with( @@ -291,14 +309,16 @@ def test_sign_up(self): ) # Test user is None so using the login_id as default - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.sign_up(DeliveryMethod.EMAIL, "dummy@dummy.com", None), + await futu_await( + client.otp.sign_up(DeliveryMethod.EMAIL, "dummy@dummy.com", None) + ), ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_otp_path}/email", @@ -322,50 +342,66 @@ def test_sign_up(self): class Dummy(Enum): DUMMY = 7 - self.assertRaises(AuthException, OTP._compose_signin_url, Dummy.DUMMY) + with self.assertRaises(AuthException): + await futu_await(OTP._compose_signin_url(Dummy.DUMMY)) - def test_sign_in(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_sign_in(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_mode + ) # Test failed flows - self.assertRaises(AuthException, client.otp.sign_in, DeliveryMethod.EMAIL, "") - self.assertRaises(AuthException, client.otp.sign_in, DeliveryMethod.EMAIL, None) + with self.assertRaises(AuthException): + await futu_await(client.otp.sign_in(DeliveryMethod.EMAIL, "")) + with self.assertRaises(AuthException): + await futu_await(client.otp.sign_in(DeliveryMethod.EMAIL, None)) - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.otp.sign_in, - DeliveryMethod.EMAIL, - "dummy@dummy.com", - ) + with mock_http_call(self.async_mode, "post") as mock_post: + my_mock_response = mock.Mock() + my_mock_response.is_success = False + my_mock_response.json = mock.Mock(return_value={}) + mock_post.return_value = my_mock_response + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.sign_in(DeliveryMethod.EMAIL, "dummy@dummy.com"), - ) - self.assertRaises( - AuthException, - client.otp.sign_in, - DeliveryMethod.EMAIL, - "exid", - LoginOptions(mfa=True), + await futu_await( + client.otp.sign_in(DeliveryMethod.EMAIL, "dummy@dummy.com") + ), ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_in( + DeliveryMethod.EMAIL, "exid", LoginOptions(mfa=True) + ), + ) # Validate refresh token used while provided - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: + my_mock_response = mock.Mock() + my_mock_response.is_success = True + my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} + mock_post.return_value = my_mock_response refresh_token = "dummy refresh token" - client.otp.sign_in( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - LoginOptions(stepup=True), - refresh_token=refresh_token, + await futu_await( + client.otp.sign_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + LoginOptions(stepup=True), + refresh_token=refresh_token, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_otp_path}/email", @@ -389,15 +425,21 @@ def test_sign_in(self): ) # With template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: + my_mock_response = mock.Mock() + my_mock_response.is_success = True + my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} + mock_post.return_value = my_mock_response refresh_token = "dummy refresh token" - client.otp.sign_in( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - LoginOptions( - stepup=True, template_options={"blue": "bla"}, template_id="foo" - ), - refresh_token=refresh_token, + await futu_await( + client.otp.sign_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + LoginOptions( + stepup=True, template_options={"blue": "bla"}, template_id="foo" + ), + refresh_token=refresh_token, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_otp_path}/email", @@ -422,46 +464,54 @@ def test_sign_in(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_up_or_in(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_sign_up_or_in(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_mode + ) # Test failed flows - self.assertRaises( - AuthException, client.otp.sign_up_or_in, DeliveryMethod.EMAIL, "" - ) + with self.assertRaises(AuthException): + await futu_await(client.otp.sign_up_or_in(DeliveryMethod.EMAIL, "")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.otp.sign_up_or_in, - DeliveryMethod.EMAIL, - "dummy@dummy.com", - ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.sign_up_or_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.sign_up_or_in(DeliveryMethod.EMAIL, "dummy@dummy.com"), + await futu_await( + client.otp.sign_up_or_in(DeliveryMethod.EMAIL, "dummy@dummy.com") + ), ) # Test success flow with sign up options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.sign_up_or_in( - DeliveryMethod.EMAIL, - "dummy@dummy.com", - SignUpOptions(template_options={"bla": "blue"}, template_id="foo"), + await futu_await( + client.otp.sign_up_or_in( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + SignUpOptions( + template_options={"bla": "blue"}, template_id="foo" + ), + ) ), ) mock_post.assert_called_with( @@ -487,31 +537,33 @@ def test_sign_up_or_in(self): params=None, ) - def test_verify_code(self): + async def test_verify_code(self): code = "1234" - client = DescopeClient(self.dummy_project_id, self.public_key_dict) - - self.assertRaises( - AuthException, client.otp.verify_code, DeliveryMethod.EMAIL, "", code - ) - self.assertRaises( - AuthException, client.otp.verify_code, DeliveryMethod.EMAIL, None, code + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_mode ) - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + + await futu_await(client.otp.verify_code(DeliveryMethod.EMAIL, "", code)) + with self.assertRaises(AuthException): + await futu_await(client.otp.verify_code(DeliveryMethod.EMAIL, None, code)) + + with mock_http_call(self.async_mode, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.otp.verify_code, - DeliveryMethod.EMAIL, - "dummy@dummy.com", - code, - ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.verify_code( + DeliveryMethod.EMAIL, + "dummy@dummy.com", + code, + ) + ) # Test success flow valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3R6VWhkcXBJRjJ5czlnZzdtczA2VXZ0QzQiLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0Mzc1OTYsImlhdCI6MTY1OTYzNzU5NiwiaXNzIjoiUDJDdHpVaGRxcElGMnlzOWdnN21zMDZVdnRDNCIsInN1YiI6IlUyQ3UwajBXUHczWU9pUElTSmI1Mkwwd1VWTWcifQ.WLnlHugvzZtrV9OzBB7SjpCLNRvKF3ImFpVyIN5orkrjO2iyAKg_Rb4XHk9sXGC1aW8puYzLbhE1Jv3kk2hDcKggfE8OaRNRm8byhGFZHnvPJwcP_Ya-aRmfAvCLcKOL" - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {} @@ -521,49 +573,61 @@ def test_verify_code(self): REFRESH_SESSION_COOKIE_NAME: valid_jwt_token, } self.assertIsNotNone( - client.otp.verify_code(DeliveryMethod.EMAIL, "dummy@dummy.com", code) + await futu_await( + client.otp.verify_code( + DeliveryMethod.EMAIL, "dummy@dummy.com", code + ) + ) ) - def test_update_user_email(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_update_user_email(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_mode + ) # Test failed flows - self.assertRaises( - AuthException, - client.otp.update_user_email, - "", - "dummy@dummy.com", - "refresh_token1", - ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.update_user_email( + "", + "dummy@dummy.com", + "refresh_token1", + ) + ) - self.assertRaises( - AuthException, - client.otp.update_user_email, - "id1", - "dummy@dummy", - "refresh_token1", - ) + with self.assertRaises(AuthException): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.otp.update_user_email, - "id1", - "dummy@dummy.com", - "refresh_token1", + await futu_await( + client.otp.update_user_email( + "id1", + "dummy@dummy", + "refresh_token1", + ) ) + with mock_http_call(self.async_mode, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + client.otp.update_user_email( + "id1", + "dummy@dummy.com", + "refresh_token1", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.update_user_email( - "id1", "dummy@dummy.com", "refresh_token1" + await futu_await( + client.otp.update_user_email( + "id1", "dummy@dummy.com", "refresh_token1" + ) ), ) mock_post.assert_called_with( @@ -586,18 +650,20 @@ def test_update_user_email(self): ) # Test success flow with template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedEmail": "t***@example.com"} mock_post.return_value = my_mock_response self.assertEqual( "t***@example.com", - client.otp.update_user_email( - "id1", - "dummy@dummy.com", - "refresh_token1", - template_options={"bla": "blue"}, + await futu_await( + client.otp.update_user_email( + "id1", + "dummy@dummy.com", + "refresh_token1", + template_options={"bla": "blue"}, + ) ), ) mock_post.assert_called_with( @@ -620,56 +686,64 @@ def test_update_user_email(self): params=None, ) - def test_update_user_phone(self): - client = DescopeClient(self.dummy_project_id, self.public_key_dict) + async def test_update_user_phone(self): + client = DescopeClient( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_mode + ) # Test failed flows - self.assertRaises( - AuthException, - client.otp.update_user_phone, - DeliveryMethod.SMS, - "", - "+1111111", - "refresh_token1", - ) - self.assertRaises( - AuthException, - client.otp.update_user_phone, - DeliveryMethod.SMS, - "id1", - "not_a_phone", - "refresh_token1", - ) - self.assertRaises( - AuthException, - client.otp.update_user_phone, - DeliveryMethod.EMAIL, - "id1", - "+1111111", - "refresh_token1", - ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.update_user_phone( + DeliveryMethod.SMS, + "", + "+1111111", + "refresh_token1", + ) + ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.update_user_phone( + DeliveryMethod.SMS, + "id1", + "not_a_phone", + "refresh_token1", + ) + ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.update_user_phone( + DeliveryMethod.EMAIL, + "id1", + "+1111111", + "refresh_token1", + ) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - client.otp.update_user_phone, - DeliveryMethod.SMS, - "id1", - "+1111111", - "refresh_token1", - ) + with self.assertRaises(AuthException): + await futu_await( + client.otp.update_user_phone( + DeliveryMethod.SMS, + "id1", + "+1111111", + "refresh_token1", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedPhone": "*****111"} mock_post.return_value = my_mock_response self.assertEqual( "*****111", - client.otp.update_user_phone( - DeliveryMethod.SMS, "id1", "+1111111", "refresh_token1" + await futu_await( + client.otp.update_user_phone( + DeliveryMethod.SMS, "id1", "+1111111", "refresh_token1" + ) ), ) mock_post.assert_called_with( @@ -691,15 +765,17 @@ def test_update_user_phone(self): params=None, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedPhone": "*****111"} mock_post.return_value = my_mock_response self.assertEqual( "*****111", - client.otp.update_user_phone( - DeliveryMethod.VOICE, "id1", "+1111111", "refresh_token1" + await futu_await( + client.otp.update_user_phone( + DeliveryMethod.VOICE, "id1", "+1111111", "refresh_token1" + ) ), ) mock_post.assert_called_with( @@ -721,15 +797,17 @@ def test_update_user_phone(self): params=None, ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedPhone": "*****111"} mock_post.return_value = my_mock_response self.assertEqual( "*****111", - client.otp.update_user_phone( - DeliveryMethod.WHATSAPP, "id1", "+1111111", "refresh_token1" + await futu_await( + client.otp.update_user_phone( + DeliveryMethod.WHATSAPP, "id1", "+1111111", "refresh_token1" + ) ), ) mock_post.assert_called_with( @@ -752,19 +830,21 @@ def test_update_user_phone(self): ) # Test success flow with template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_mode, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = {"maskedPhone": "*****111"} mock_post.return_value = my_mock_response self.assertEqual( "*****111", - client.otp.update_user_phone( - DeliveryMethod.SMS, - "id1", - "+1111111", - "refresh_token1", - template_options={"bla": "blue"}, + await futu_await( + client.otp.update_user_phone( + DeliveryMethod.SMS, + "id1", + "+1111111", + "refresh_token1", + template_options={"bla": "blue"}, + ) ), ) mock_post.assert_called_with( diff --git a/tests/test_password.py b/tests/test_password.py index d26600396..3d204f79a 100644 --- a/tests/test_password.py +++ b/tests/test_password.py @@ -7,7 +7,8 @@ from descope.authmethod.password import Password # noqa: F401 from descope.common import DEFAULT_TIMEOUT_SECONDS, EndpointsV1 -from tests.testutils import SSLMatcher +from descope.future_utils import futu_await +from tests.testutils import SSLMatcher, mock_http_call from . import common @@ -25,7 +26,7 @@ def setUp(self) -> None: "y": "N5n5jKZA5Wu7_b4B36KKjJf-VRfJ-XqczfCSYy9GeQLqF-b63idfE0SYaYk9cFqg", } - def test_sign_up(self): + async def test_sign_up(self): signup_user_details = { "username": "jhon", "name": "john", @@ -33,53 +34,65 @@ def test_sign_up(self): "email": "dummy@dummy.com", } - password = Password(Auth(self.dummy_project_id, self.public_key_dict)) + password = Password( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, - password.sign_up, - "", - None, - signup_user_details, - ) + with self.assertRaises(AuthException): + await futu_await( + password.sign_up( + "", + None, + signup_user_details, + ) + ) - self.assertRaises( - AuthException, - password.sign_up, - None, - None, - signup_user_details, - ) + with self.assertRaises(AuthException): - self.assertRaises( - AuthException, - password.sign_up, - "login_id", - "", - signup_user_details, - ) + await futu_await( + password.sign_up( + None, + None, + signup_user_details, + ) + ) - self.assertRaises( - AuthException, - password.sign_up, - "login_id", - None, - signup_user_details, - ) + with self.assertRaises(AuthException): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - password.sign_up, - "dummy@dummy.com", - "123456", - signup_user_details, + await futu_await( + password.sign_up( + "login_id", + "", + signup_user_details, + ) ) + with self.assertRaises(AuthException): + + await futu_await( + password.sign_up( + "login_id", + None, + signup_user_details, + ) + ) + + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + password.sign_up( + "dummy@dummy.com", + "123456", + signup_user_details, + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True my_mock_response = mock.Mock() my_mock_response.is_success = True @@ -91,7 +104,9 @@ def test_sign_up(self): mock_post.return_value = my_mock_response self.assertIsNotNone( - password.sign_up("dummy@dummy.com", "123456", signup_user_details) + await futu_await( + password.sign_up("dummy@dummy.com", "123456", signup_user_details) + ) ) mock_post.assert_called_with( @@ -117,49 +132,61 @@ def test_sign_up(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_sign_in(self): - password = Password(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_in(self): + password = Password( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, - password.sign_in, - "", - None, - ) + with self.assertRaises(AuthException): + await futu_await( + password.sign_in( + "", + None, + ) + ) - self.assertRaises( - AuthException, - password.sign_in, - None, - None, - ) + with self.assertRaises(AuthException): - self.assertRaises( - AuthException, - password.sign_in, - "login_id", - "", - ) + await futu_await( + password.sign_in( + None, + None, + ) + ) - self.assertRaises( - AuthException, - password.sign_in, - "login_id", - None, - ) + with self.assertRaises(AuthException): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - password.sign_in, - "dummy@dummy.com", - "123456", + await futu_await( + password.sign_in( + "login_id", + "", + ) + ) + + with self.assertRaises(AuthException): + + await futu_await( + password.sign_in( + "login_id", + None, + ) ) + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + password.sign_in( + "dummy@dummy.com", + "123456", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True my_mock_response = mock.Mock() my_mock_response.is_success = True @@ -170,7 +197,9 @@ def test_sign_in(self): my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - self.assertIsNotNone(password.sign_in("dummy@dummy.com", "123456")) + self.assertIsNotNone( + await futu_await(password.sign_in("dummy@dummy.com", "123456")) + ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_password_path}", @@ -189,32 +218,40 @@ def test_sign_in(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_send_reset(self): - password = Password(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_send_reset(self): + password = Password( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, - password.send_reset, - "", - ) + with self.assertRaises(AuthException): + await futu_await( + password.send_reset( + "", + ) + ) - self.assertRaises( - AuthException, - password.send_reset, - None, - ) + with self.assertRaises(AuthException): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - password.send_reset, - "dummy@dummy.com", + await futu_await( + password.send_reset( + None, + ) ) + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + password.send_reset( + "dummy@dummy.com", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True my_mock_response = mock.Mock() my_mock_response.is_success = True @@ -226,7 +263,9 @@ def test_send_reset(self): mock_post.return_value = my_mock_response self.assertIsNotNone( - password.send_reset("dummy@dummy.com", "https://redirect.here.com") + await futu_await( + password.send_reset("dummy@dummy.com", "https://redirect.here.com") + ) ) mock_post.assert_called_with( @@ -247,7 +286,7 @@ def test_send_reset(self): ) # Test success flow with template options - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True my_mock_response = mock.Mock() my_mock_response.is_success = True @@ -259,10 +298,12 @@ def test_send_reset(self): mock_post.return_value = my_mock_response self.assertIsNotNone( - password.send_reset( - "dummy@dummy.com", - "https://redirect.here.com", - {"bla": "blue"}, + await futu_await( + password.send_reset( + "dummy@dummy.com", + "https://redirect.here.com", + {"bla": "blue"}, + ) ) ) @@ -284,74 +325,92 @@ def test_send_reset(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update(self): - password = Password(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_update(self): + password = Password( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, - password.update, - "", - None, - None, - ) + with self.assertRaises(AuthException): + await futu_await( + password.update( + "", + None, + None, + ) + ) - self.assertRaises( - AuthException, - password.update, - None, - None, - None, - ) + with self.assertRaises(AuthException): - self.assertRaises( - AuthException, - password.update, - "login_id", - "", - None, - ) + await futu_await( + password.update( + None, + None, + None, + ) + ) - self.assertRaises( - AuthException, - password.update, - "login_id", - None, - None, - ) + with self.assertRaises(AuthException): - self.assertRaises( - AuthException, - password.update, - "login_id", - "123456", - "", - ) + await futu_await( + password.update( + "login_id", + "", + None, + ) + ) - self.assertRaises( - AuthException, - password.update, - "login_id", - "123456", - None, - ) + with self.assertRaises(AuthException): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - password.update, - "dummy@dummy.com", - "1234567", - "refresh_token", + await futu_await( + password.update( + "login_id", + None, + None, + ) ) + with self.assertRaises(AuthException): + + await futu_await( + password.update( + "login_id", + "123456", + "", + ) + ) + + with self.assertRaises(AuthException): + + await futu_await( + password.update( + "login_id", + "123456", + None, + ) + ) + + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + password.update( + "dummy@dummy.com", + "1234567", + "refresh_token", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IjJCdDVXTGNjTFVleTFEcDd1dHB0WmIzRng5SyIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkVGVuYW50cyI6eyIiOm51bGx9LCJjb29raWVEb21haW4iOiIiLCJjb29raWVFeHBpcmF0aW9uIjoxNjYwNjc5MjA4LCJjb29raWVNYXhBZ2UiOjI1OTE5OTksImNvb2tpZU5hbWUiOiJEU1IiLCJjb29raWVQYXRoIjoiLyIsImV4cCI6MjA5MDA4NzIwOCwiaWF0IjoxNjU4MDg3MjA4LCJpc3MiOiIyQnQ1V0xjY0xVZXkxRHA3dXRwdFpiM0Z4OUsiLCJzdWIiOiIyQzU1dnl4dzBzUkw2RmRNNjhxUnNDRGRST1YifQ.cWP5up4R5xeIl2qoG2NtfLH3Q5nRJVKdz-FDoAXctOQW9g3ceZQi6rZQ-TPBaXMKw68bijN3bLJTqxWW5WHzqRUeopfuzTcMYmC0wP2XGJkrdF6A8D5QW6acSGqglFgu" self.assertIsNone( - password.update("dummy@dummy.com", "123456", valid_jwt_token) + await futu_await( + password.update("dummy@dummy.com", "123456", valid_jwt_token) + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.update_password_path}", @@ -370,70 +429,86 @@ def test_update(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_replace(self): - password = Password(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_replace(self): + password = Password( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, - password.replace, - "", - None, - None, - ) + with self.assertRaises(AuthException): + await futu_await( + password.replace( + "", + None, + None, + ) + ) - self.assertRaises( - AuthException, - password.replace, - None, - None, - None, - ) + with self.assertRaises(AuthException): - self.assertRaises( - AuthException, - password.replace, - "login_id", - "", - None, - ) + await futu_await( + password.replace( + None, + None, + None, + ) + ) - self.assertRaises( - AuthException, - password.replace, - "login_id", - None, - None, - ) + with self.assertRaises(AuthException): - self.assertRaises( - AuthException, - password.replace, - "login_id", - "123456", - "", - ) + await futu_await( + password.replace( + "login_id", + "", + None, + ) + ) - self.assertRaises( - AuthException, - password.replace, - "login_id", - "123456", - None, - ) + with self.assertRaises(AuthException): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - password.replace, - "dummy@dummy.com", - "123456", - "1234567", + await futu_await( + password.replace( + "login_id", + None, + None, + ) + ) + + with self.assertRaises(AuthException): + + await futu_await( + password.replace( + "login_id", + "123456", + "", + ) + ) + + with self.assertRaises(AuthException): + + await futu_await( + password.replace( + "login_id", + "123456", + None, + ) ) + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + password.replace( + "dummy@dummy.com", + "123456", + "1234567", + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True my_mock_response = mock.Mock() my_mock_response.is_success = True @@ -444,7 +519,9 @@ def test_replace(self): my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - jwt_response = password.replace("dummy@dummy.com", "123456", "1234567") + jwt_response = await futu_await( + password.replace("dummy@dummy.com", "123456", "1234567") + ) self.assertIsNotNone(jwt_response) self.assertIsNotNone(jwt_response["user"]) self.assertEqual(jwt_response["user"]["loginIds"], ["test@company.com"]) @@ -466,18 +543,20 @@ def test_replace(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_policy(self): - password = Password(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_policy(self): + password = Password( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = False - self.assertRaises( - AuthException, - password.get_policy, - ) + with self.assertRaises(AuthException): + await futu_await(password.get_policy()) # Test success flow - with patch("httpx.get") as mock_get: + with mock_http_call(self.async_test, "get") as mock_get: mock_get.return_value.is_success = True my_mock_response = mock.Mock() my_mock_response.is_success = True @@ -485,7 +564,7 @@ def test_policy(self): data = json.loads("""{"minLength": 8, "lowercase": true}""") my_mock_response.json.return_value = data mock_get.return_value = my_mock_response - self.assertIsNotNone(password.get_policy()) + self.assertIsNotNone(await futu_await(password.get_policy())) mock_get.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.password_policy_path}", headers={ diff --git a/tests/test_saml.py b/tests/test_saml.py index 377d67484..c63919cc2 100644 --- a/tests/test_saml.py +++ b/tests/test_saml.py @@ -7,9 +7,10 @@ from descope.auth import Auth from descope.authmethod.saml import SAML from descope.common import DEFAULT_TIMEOUT_SECONDS, EndpointsV1, LoginOptions +from descope.future_utils import futu_await from . import common -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call class TestSAML(common.DescopeTest): @@ -26,33 +27,44 @@ def setUp(self) -> None: "y": "N5n5jKZA5Wu7_b4B36KKjJf-VRfJ-XqczfCSYy9GeQLqF-b63idfE0SYaYk9cFqg", } - def test_compose_start_params(self): + async def test_compose_start_params(self): self.assertEqual( SAML._compose_start_params("tenant1", "http://dummy.com"), {"tenant": "tenant1", "redirectURL": "http://dummy.com"}, ) - def test_saml_start(self): - saml = SAML(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_saml_start(self): + saml = SAML( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, saml.start, "", "http://dummy.com") - self.assertRaises(AuthException, saml.start, None, "http://dummy.com") - self.assertRaises(AuthException, saml.start, "tenant1", "") - self.assertRaises(AuthException, saml.start, "tenant1", None) - - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + await futu_await(saml.start("", "http://dummy.com")) + with self.assertRaises(AuthException): + await futu_await(saml.start(None, "http://dummy.com")) + with self.assertRaises(AuthException): + await futu_await(saml.start("tenant1", "")) + with self.assertRaises(AuthException): + await futu_await(saml.start("tenant1", None)) + + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, saml.start, "tenant1", "http://dummy.com") + with self.assertRaises(AuthException): + await futu_await(saml.start("tenant1", "http://dummy.com")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(saml.start("tenant1", "http://dummy.com")) + self.assertIsNotNone( + await futu_await(saml.start("tenant1", "http://dummy.com")) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - saml.start("tenant1", "http://dummy.com") + await futu_await(saml.start("tenant1", "http://dummy.com")) expected_uri = ( f"{common.DEFAULT_BASE_URL}{EndpointsV1.auth_saml_start_path}" ) @@ -69,36 +81,44 @@ def test_saml_start(self): verify=SSLMatcher(), timeout=DEFAULT_TIMEOUT_SECONDS, ) - self.assertRaises( - AuthException, - saml.start, - "tenant", - "http://dummy.com", - LoginOptions(mfa=True), + with self.assertRaises(AuthException): + await futu_await( + saml.start("tenant", "http://dummy.com", LoginOptions(mfa=True)), + ) + + async def test_saml_start_with_login_options(self): + saml = SAML( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test ) - - def test_saml_start_with_login_options(self): - saml = SAML(Auth(self.dummy_project_id, self.public_key_dict)) + ) # Test failed flows - self.assertRaises(AuthException, saml.start, "", "http://dummy.com") - self.assertRaises(AuthException, saml.start, None, "http://dummy.com") - self.assertRaises(AuthException, saml.start, "tenant1", "") - self.assertRaises(AuthException, saml.start, "tenant1", None) - - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + await futu_await(saml.start("", "http://dummy.com")) + with self.assertRaises(AuthException): + await futu_await(saml.start(None, "http://dummy.com")) + with self.assertRaises(AuthException): + await futu_await(saml.start("tenant1", "")) + with self.assertRaises(AuthException): + await futu_await(saml.start("tenant1", None)) + + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, saml.start, "tenant1", "http://dummy.com") + with self.assertRaises(AuthException): + await futu_await(saml.start("tenant1", "http://dummy.com")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(saml.start("tenant1", "http://dummy.com")) + self.assertIsNotNone( + await futu_await(saml.start("tenant1", "http://dummy.com")) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True lo = LoginOptions(stepup=True, custom_claims={"k1": "v1"}) - saml.start("tenant1", "http://dummy.com", lo, "refresh") + await futu_await(saml.start("tenant1", "http://dummy.com", lo, "refresh")) expected_uri = ( f"{common.DEFAULT_BASE_URL}{EndpointsV1.auth_saml_start_path}" ) @@ -116,22 +136,29 @@ def test_saml_start_with_login_options(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_compose_exchange_params(self): + async def test_compose_exchange_params(self): self.assertEqual(Auth._compose_exchange_body("c1"), {"code": "c1"}) - def test_exchange_token(self): - saml = SAML(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_exchange_token(self): + saml = SAML( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, saml.exchange_token, "") - self.assertRaises(AuthException, saml.exchange_token, None) + with self.assertRaises(AuthException): + await futu_await(saml.exchange_token("")) + with self.assertRaises(AuthException): + await futu_await(saml.exchange_token(None)) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, saml.exchange_token, "c1") + with self.assertRaises(AuthException): + await futu_await(saml.exchange_token("c1")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.cookies = {} @@ -140,7 +167,7 @@ def test_exchange_token(self): ) my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - saml.exchange_token("c1") + await futu_await(saml.exchange_token("c1")) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.saml_exchange_token_path}", headers={ diff --git a/tests/test_sso.py b/tests/test_sso.py index 98a6cb46c..3ee7e236d 100644 --- a/tests/test_sso.py +++ b/tests/test_sso.py @@ -7,9 +7,10 @@ from descope.auth import Auth from descope.authmethod.sso import SSO from descope.common import DEFAULT_TIMEOUT_SECONDS, EndpointsV1, LoginOptions +from descope.future_utils import futu_await from . import common -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call class TestSSO(common.DescopeTest): @@ -26,7 +27,7 @@ def setUp(self) -> None: "y": "N5n5jKZA5Wu7_b4B36KKjJf-VRfJ-XqczfCSYy9GeQLqF-b63idfE0SYaYk9cFqg", } - def test_compose_start_params(self): + async def test_compose_start_params(self): self.assertEqual( SSO._compose_start_params("tenant1", "http://dummy.com", "", ""), {"tenant": "tenant1", "redirectURL": "http://dummy.com"}, @@ -42,25 +43,36 @@ def test_compose_start_params(self): }, ) - def test_sso_start(self): - sso = SSO(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sso_start(self): + sso = SSO( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, sso.start, "", "http://dummy.com") - self.assertRaises(AuthException, sso.start, None, "http://dummy.com") + with self.assertRaises(AuthException): + await futu_await(sso.start("", "http://dummy.com")) + with self.assertRaises(AuthException): + await futu_await(sso.start(None, "http://dummy.com")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, sso.start, "tenant1", "http://dummy.com") + with self.assertRaises(AuthException): + await futu_await(sso.start("tenant1", "http://dummy.com")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(sso.start("tenant1", "http://dummy.com")) + self.assertIsNotNone( + await futu_await(sso.start("tenant1", "http://dummy.com")) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - sso.start("tenant1", "http://dummy.com", sso_id="some-sso-id") + await futu_await( + sso.start("tenant1", "http://dummy.com", sso_id="some-sso-id") + ) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.auth_sso_start_path}" mock_post.assert_called_with( expected_uri, @@ -79,34 +91,40 @@ def test_sso_start(self): verify=SSLMatcher(), timeout=DEFAULT_TIMEOUT_SECONDS, ) - self.assertRaises( - AuthException, - sso.start, - "tenant", - "http://dummy.com", - LoginOptions(mfa=True), + with self.assertRaises(AuthException): + await futu_await( + sso.start("tenant", "http://dummy.com", LoginOptions(mfa=True)), + ) + + async def test_sso_start_with_login_options(self): + sso = SSO( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test ) - - def test_sso_start_with_login_options(self): - sso = SSO(Auth(self.dummy_project_id, self.public_key_dict)) + ) # Test failed flows - self.assertRaises(AuthException, sso.start, "", "http://dummy.com") - self.assertRaises(AuthException, sso.start, None, "http://dummy.com") + with self.assertRaises(AuthException): + await futu_await(sso.start("", "http://dummy.com")) + with self.assertRaises(AuthException): + await futu_await(sso.start(None, "http://dummy.com")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, sso.start, "tenant1", "http://dummy.com") + with self.assertRaises(AuthException): + await futu_await(sso.start("tenant1", "http://dummy.com")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(sso.start("tenant1", "http://dummy.com")) + self.assertIsNotNone( + await futu_await(sso.start("tenant1", "http://dummy.com")) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True lo = LoginOptions(stepup=True, custom_claims={"k1": "v1"}) - sso.start("tenant1", "http://dummy.com", lo, "refresh") + await futu_await(sso.start("tenant1", "http://dummy.com", lo, "refresh")) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.auth_sso_start_path}" mock_post.assert_called_with( expected_uri, @@ -122,22 +140,29 @@ def test_sso_start_with_login_options(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_compose_exchange_params(self): + async def test_compose_exchange_params(self): self.assertEqual(Auth._compose_exchange_body("c1"), {"code": "c1"}) - def test_exchange_token(self): - sso = SSO(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_exchange_token(self): + sso = SSO( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, sso.exchange_token, "") - self.assertRaises(AuthException, sso.exchange_token, None) + with self.assertRaises(AuthException): + await futu_await(sso.exchange_token("")) + with self.assertRaises(AuthException): + await futu_await(sso.exchange_token(None)) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises(AuthException, sso.exchange_token, "c1") + with self.assertRaises(AuthException): + await futu_await(sso.exchange_token("c1")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.cookies = {} @@ -146,7 +171,7 @@ def test_exchange_token(self): ) my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - sso.exchange_token("c1") + await futu_await(sso.exchange_token("c1")) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.sso_exchange_token_path}", headers={ diff --git a/tests/test_totp.py b/tests/test_totp.py index b7771f653..4e8b03c61 100644 --- a/tests/test_totp.py +++ b/tests/test_totp.py @@ -7,7 +7,8 @@ from descope.authmethod.totp import TOTP # noqa: F401 from descope.common import DEFAULT_TIMEOUT_SECONDS, EndpointsV1, LoginOptions -from tests.testutils import SSLMatcher +from descope.future_utils import futu_await +from tests.testutils import SSLMatcher, mock_http_call from . import common @@ -25,7 +26,7 @@ def setUp(self) -> None: "y": "N5n5jKZA5Wu7_b4B36KKjJf-VRfJ-XqczfCSYy9GeQLqF-b63idfE0SYaYk9cFqg", } - def test_sign_up(self): + async def test_sign_up(self): signup_user_details = { "username": "jhon", "name": "john", @@ -33,54 +34,71 @@ def test_sign_up(self): "email": "dummy@dummy.com", } - totp = TOTP(Auth(self.dummy_project_id, self.public_key_dict)) + totp = TOTP( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, - totp.sign_up, - "", - signup_user_details, - ) + with self.assertRaises(AuthException): + await futu_await( + totp.sign_up( + "", + signup_user_details, + ) + ) - self.assertRaises( - AuthException, - totp.sign_up, - None, - signup_user_details, - ) + with self.assertRaises(AuthException): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - totp.sign_up, - "dummy@dummy.com", - signup_user_details, + await futu_await( + totp.sign_up( + None, + signup_user_details, + ) ) + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + totp.sign_up( + "dummy@dummy.com", + signup_user_details, + ) + ) + # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(totp.sign_up("dummy@dummy.com", signup_user_details)) + self.assertIsNotNone( + await futu_await(totp.sign_up("dummy@dummy.com", signup_user_details)) + ) - def test_sign_in(self): - totp = TOTP(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_in(self): + totp = TOTP( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, totp.sign_in_code, None, "1234") - self.assertRaises(AuthException, totp.sign_in_code, "", "1234") - self.assertRaises(AuthException, totp.sign_in_code, "dummy@dummy.com", None) - self.assertRaises(AuthException, totp.sign_in_code, "dummy@dummy.com", "") - - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + await futu_await(totp.sign_in_code(None, "1234")) + with self.assertRaises(AuthException): + await futu_await(totp.sign_in_code("", "1234")) + with self.assertRaises(AuthException): + await futu_await(totp.sign_in_code("dummy@dummy.com", None)) + with self.assertRaises(AuthException): + await futu_await(totp.sign_in_code("dummy@dummy.com", "")) + + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, totp.sign_in_code, "dummy@dummy.com", "1234" - ) + with self.assertRaises(AuthException): + await futu_await(totp.sign_in_code("dummy@dummy.com", "1234")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.cookies = {} @@ -89,17 +107,18 @@ def test_sign_in(self): ) my_mock_response.json.return_value = data mock_post.return_value = my_mock_response - self.assertIsNotNone(totp.sign_in_code("dummy@dummy.com", "1234")) - self.assertRaises( - AuthException, - totp.sign_in_code, - "dummy@dummy.com", - "code", - LoginOptions(mfa=True), + self.assertIsNotNone( + await futu_await(totp.sign_in_code("dummy@dummy.com", "1234")) ) + with self.assertRaises(AuthException): + await futu_await( + totp.sign_in_code( + "dummy@dummy.com", "code", LoginOptions(mfa=True) + ), + ) # Validate refresh token used while provided - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.cookies = {} @@ -109,11 +128,13 @@ def test_sign_in(self): my_mock_response.json.return_value = data mock_post.return_value = my_mock_response refresh_token = "dummy refresh token" - totp.sign_in_code( - "dummy@dummy.com", - "1234", - LoginOptions(stepup=True), - refresh_token=refresh_token, + await futu_await( + totp.sign_in_code( + "dummy@dummy.com", + "1234", + LoginOptions(stepup=True), + refresh_token=refresh_token, + ) ) mock_post.assert_called_with( f"{common.DEFAULT_BASE_URL}{EndpointsV1.verify_totp_path}", @@ -137,23 +158,32 @@ def test_sign_in(self): timeout=DEFAULT_TIMEOUT_SECONDS, ) - def test_update_user(self): - totp = TOTP(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_update_user(self): + totp = TOTP( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, totp.update_user, None, "") - self.assertRaises(AuthException, totp.update_user, "", "") - self.assertRaises(AuthException, totp.update_user, "dummy@dummy.com", None) - self.assertRaises(AuthException, totp.update_user, "dummy@dummy.com", "") - - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + await futu_await(totp.update_user(None, "")) + with self.assertRaises(AuthException): + await futu_await(totp.update_user("", "")) + with self.assertRaises(AuthException): + await futu_await(totp.update_user("dummy@dummy.com", None)) + with self.assertRaises(AuthException): + await futu_await(totp.update_user("dummy@dummy.com", "")) + + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - totp.update_user, - "dummy@dummy.com", - "dummy refresh token", - ) + with self.assertRaises(AuthException): + await futu_await( + totp.update_user( + "dummy@dummy.com", + "dummy refresh token", + ) + ) valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IjJCdDVXTGNjTFVleTFEcDd1dHB0WmIzRng5SyIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkVGVuYW50cyI6eyIiOm51bGx9LCJjb29raWVEb21haW4iOiIiLCJjb29raWVFeHBpcmF0aW9uIjoxNjYwNjc5MjA4LCJjb29raWVNYXhBZ2UiOjI1OTE5OTksImNvb2tpZU5hbWUiOiJEU1IiLCJjb29raWVQYXRoIjoiLyIsImV4cCI6MjA5MDA4NzIwOCwiaWF0IjoxNjU4MDg3MjA4LCJpc3MiOiIyQnQ1V0xjY0xVZXkxRHA3dXRwdFpiM0Z4OUsiLCJzdWIiOiIyQzU1dnl4dzBzUkw2RmRNNjhxUnNDRGRST1YifQ.cWP5up4R5xeIl2qoG2NtfLH3Q5nRJVKdz-FDoAXctOQW9g3ceZQi6rZQ-TPBaXMKw68bijN3bLJTqxWW5WHzqRUeopfuzTcMYmC0wP2XGJkrdF6A8D5QW6acSGqglFgu" valid_response = json.loads( @@ -163,7 +193,7 @@ def test_update_user(self): my_mock_response.is_success = True my_mock_response.json.return_value = valid_response mock_post.return_value = my_mock_response - res = totp.update_user("dummy@dummy.com", valid_jwt_token) + res = await futu_await(totp.update_user("dummy@dummy.com", valid_jwt_token)) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.update_totp_path}" mock_post.assert_called_with( expected_uri, diff --git a/tests/test_webauthn.py b/tests/test_webauthn.py index 79671d6ac..b921d1d01 100644 --- a/tests/test_webauthn.py +++ b/tests/test_webauthn.py @@ -7,9 +7,10 @@ from descope.auth import Auth from descope.authmethod.webauthn import WebAuthn from descope.common import DEFAULT_TIMEOUT_SECONDS, EndpointsV1, LoginOptions +from descope.future_utils import futu_await from . import common -from tests.testutils import SSLMatcher +from tests.testutils import SSLMatcher, mock_http_call class TestWebauthN(common.DescopeTest): @@ -26,7 +27,7 @@ def setUp(self) -> None: "y": "B0_nWAv2pmG_PzoH3-bSYZZzLNKUA0RoE2SH7DaS0KV4rtfWZhYd0MEr0xfdGKx0", } - def test_compose_signup_body(self): + async def test_compose_signup_body(self): self.assertEqual( WebAuthn._compose_sign_up_start_body( "dummy@dummy.com", {"name": "dummy"}, "https://example.com" @@ -37,13 +38,13 @@ def test_compose_signup_body(self): }, ) - def test_compose_sign_up_in_finish_body(self): + async def test_compose_sign_up_in_finish_body(self): self.assertEqual( WebAuthn._compose_sign_up_in_finish_body("t01", "response01"), {"transactionId": "t01", "response": "response01"}, ) - def test_compose_signin_body(self): + async def test_compose_signin_body(self): self.assertEqual( WebAuthn._compose_sign_in_start_body( "dummy@dummy.com", "https://example.com" @@ -55,7 +56,7 @@ def test_compose_signin_body(self): }, ) - def test_compose_signup_or_in_body(self): + async def test_compose_signup_or_in_body(self): self.assertEqual( WebAuthn._compose_sign_up_or_in_start_body( "dummy@dummy.com", "https://example.com" @@ -66,7 +67,7 @@ def test_compose_signup_or_in_body(self): }, ) - def test_compose_update_start_body(self): + async def test_compose_update_start_body(self): self.assertEqual( WebAuthn._compose_update_start_body( "dummy@dummy.com", "https://example.com" @@ -74,41 +75,46 @@ def test_compose_update_start_body(self): {"loginId": "dummy@dummy.com", "origin": "https://example.com"}, ) - def test_compose_update_finish_body(self): + async def test_compose_update_finish_body(self): self.assertEqual( WebAuthn._compose_update_finish_body("t01", "response01"), {"transactionId": "t01", "response": "response01"}, ) - def test_sign_up_start(self): - webauthn = WebAuthn(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_up_start(self): + webauthn = WebAuthn( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, webauthn.sign_up_start, "", "https://example.com" - ) - self.assertRaises(AuthException, webauthn.sign_up_start, "id1", "") + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_start("", "https://example.com")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_start("id1", "")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, webauthn.sign_up_start, "id1", "https://example.com" - ) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_start("id1", "https://example.com")) # Test success flow valid_response = json.loads( """{"transactionId": "2COHI3LIixYhf6Q7EECYt20zyMi", "options": "{'publicKey':{'challenge':'5GOywA7BHL1QceQOfxHKDrasuN8SkbbgXmB5ImVZ+QU=','rp':{'name':'comp6','id':'localhost'},'user':{'name”:”dummy@dummy.com','displayName”:”dummy”,”id':'VTJDT0hJNWlWOHJaZ3VURkpKMzV3bjEydHRkTw=='},'pubKeyCredParams':[{'type':'public-key','alg':-7},{'type':'public-key','alg':-35},{'type':'public-key','alg':-36},{'type':'public-key','alg':-257},{'type':'public-key','alg':-258},{'type':'public-key','alg':-259},{'type':'public-key','alg':-37},{'type':'public-key','alg':-38},{'type':'public-key','alg':-39},{'type':'public-key','alg':-8}],'authenticatorSelection':{'userVerification':'preferred'},'timeout':60000,'attestation':'none'}}"}""" ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True - self.assertIsNotNone(webauthn.sign_up_start("id1", "https://example.com")) + self.assertIsNotNone( + await futu_await(webauthn.sign_up_start("id1", "https://example.com")) + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = valid_response mock_post.return_value = my_mock_response - res = webauthn.sign_up_start("id1", "https://example.com") + res = await futu_await(webauthn.sign_up_start("id1", "https://example.com")) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_webauthn_start_path}" mock_post.assert_called_with( @@ -126,23 +132,30 @@ def test_sign_up_start(self): ) self.assertEqual(res, valid_response) - def test_sign_up_finish(self): - webauthn = WebAuthn(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_up_finish(self): + webauthn = WebAuthn( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, webauthn.sign_up_finish, "", "response01") - self.assertRaises(AuthException, webauthn.sign_up_finish, None, "response01") - self.assertRaises(AuthException, webauthn.sign_up_finish, "t01", "") - self.assertRaises(AuthException, webauthn.sign_up_finish, "t01", None) - - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_finish("", "response01")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_finish(None, "response01")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_finish("t01", "")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_finish("t01", None)) + + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, webauthn.sign_up_finish, "t01", "response01" - ) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_finish("t01", "response01")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.cookies = {} @@ -160,7 +173,7 @@ def test_sign_up_finish(self): my_mock_response.json.return_value = data mock_post.return_value = my_mock_response expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_auth_webauthn_finish_path}" - webauthn.sign_up_finish("t01", "response01") + await futu_await(webauthn.sign_up_finish("t01", "response01")) mock_post.assert_called_with( expected_uri, headers={ @@ -174,49 +187,55 @@ def test_sign_up_finish(self): verify=SSLMatcher(), timeout=DEFAULT_TIMEOUT_SECONDS, ) - self.assertIsNotNone(webauthn.sign_up_finish("t01", "response01")) + self.assertIsNotNone( + await futu_await(webauthn.sign_up_finish("t01", "response01")) + ) - def test_sign_in_start(self): - webauthn = WebAuthn(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_in_start(self): + webauthn = WebAuthn( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, webauthn.sign_in_start, "", "https://example.com" - ) - self.assertRaises(AuthException, webauthn.sign_in_start, "id", "") + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_start("", "https://example.com")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_start("id", "")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - webauthn.sign_in_start, - "id1", - "https://example.com", - ) + with self.assertRaises(AuthException): + await futu_await( + webauthn.sign_in_start( + "id1", + "https://example.com", + ) + ) # Test success flow valid_response = json.loads( """{"transactionId": "2COHI3LIixYhf6Q7EECYt20zyMi", "options": "{'publicKey':{'challenge':'5GOywA7BHL1QceQOfxHKDrasuN8SkbbgXmB5ImVZ+QU=','rp':{'name':'comp6','id':'localhost'},'user':{'name”:”dummy@dummy.com','displayName”:”dummy”,”id':'VTJDT0hJNWlWOHJaZ3VURkpKMzV3bjEydHRkTw=='},'pubKeyCredParams':[{'type':'public-key','alg':-7},{'type':'public-key','alg':-35},{'type':'public-key','alg':-36},{'type':'public-key','alg':-257},{'type':'public-key','alg':-258},{'type':'public-key','alg':-259},{'type':'public-key','alg':-37},{'type':'public-key','alg':-38},{'type':'public-key','alg':-39},{'type':'public-key','alg':-8}],'authenticatorSelection':{'userVerification':'preferred'},'timeout':60000,'attestation':'none'}}"}""" ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNotNone( - webauthn.sign_in_start("dummy@dummy.com", "https://example.com") - ) - self.assertRaises( - AuthException, - webauthn.sign_in_start, - "id", - "origin", - LoginOptions(mfa=True), + await futu_await( + webauthn.sign_in_start("dummy@dummy.com", "https://example.com") + ) ) + with self.assertRaises(AuthException): + await futu_await( + webauthn.sign_in_start("id", "origin", LoginOptions(mfa=True)), + ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = valid_response mock_post.return_value = my_mock_response - res = webauthn.sign_in_start("id1", "https://example.com") + res = await futu_await(webauthn.sign_in_start("id1", "https://example.com")) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_webauthn_start_path}" mock_post.assert_called_with( expected_uri, @@ -237,41 +256,50 @@ def test_sign_in_start(self): ) self.assertEqual(res, valid_response) - def test_sign_in_start_with_login_options(self): - webauthn = WebAuthn(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_in_start_with_login_options(self): + webauthn = WebAuthn( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, webauthn.sign_in_start, "", "https://example.com" - ) - self.assertRaises(AuthException, webauthn.sign_in_start, "id", "") + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_start("", "https://example.com")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_start("id", "")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - webauthn.sign_in_start, - "id1", - "https://example.com", - ) + with self.assertRaises(AuthException): + await futu_await( + webauthn.sign_in_start( + "id1", + "https://example.com", + ) + ) # Test success flow valid_response = json.loads( """{"transactionId": "2COHI3LIixYhf6Q7EECYt20zyMi", "options": "{'publicKey':{'challenge':'5GOywA7BHL1QceQOfxHKDrasuN8SkbbgXmB5ImVZ+QU=','rp':{'name':'comp6','id':'localhost'},'user':{'name”:”dummy@dummy.com','displayName”:”dummy”,”id':'VTJDT0hJNWlWOHJaZ3VURkpKMzV3bjEydHRkTw=='},'pubKeyCredParams':[{'type':'public-key','alg':-7},{'type':'public-key','alg':-35},{'type':'public-key','alg':-36},{'type':'public-key','alg':-257},{'type':'public-key','alg':-258},{'type':'public-key','alg':-259},{'type':'public-key','alg':-37},{'type':'public-key','alg':-38},{'type':'public-key','alg':-39},{'type':'public-key','alg':-8}],'authenticatorSelection':{'userVerification':'preferred'},'timeout':60000,'attestation':'none'}}"}""" ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNotNone( - webauthn.sign_in_start("dummy@dummy.com", "https://example.com") + await futu_await( + webauthn.sign_in_start("dummy@dummy.com", "https://example.com") + ) ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = valid_response mock_post.return_value = my_mock_response lo = LoginOptions(stepup=True, custom_claims={"k1": "v1"}) - res = webauthn.sign_in_start("id1", "https://example.com", lo, "refresh") + res = await futu_await( + webauthn.sign_in_start("id1", "https://example.com", lo, "refresh") + ) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_webauthn_start_path}" mock_post.assert_called_with( expected_uri, @@ -296,23 +324,30 @@ def test_sign_in_start_with_login_options(self): ) self.assertEqual(res, valid_response) - def test_sign_in_finish(self): - webauthn = WebAuthn(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_in_finish(self): + webauthn = WebAuthn( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, webauthn.sign_in_finish, "", "response01") - self.assertRaises(AuthException, webauthn.sign_in_finish, None, "response01") - self.assertRaises(AuthException, webauthn.sign_in_finish, "t01", "") - self.assertRaises(AuthException, webauthn.sign_in_finish, "t01", None) - - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_finish("", "response01")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_finish(None, "response01")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_finish("t01", "")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_finish("t01", None)) + + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, webauthn.sign_in_finish, "t01", "response01" - ) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_in_finish("t01", "response01")) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.cookies = {} @@ -323,7 +358,7 @@ def test_sign_in_finish(self): my_mock_response.json.return_value = data mock_post.return_value = my_mock_response expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_in_auth_webauthn_finish_path}" - webauthn.sign_in_finish("t01", "response01") + await futu_await(webauthn.sign_in_finish("t01", "response01")) mock_post.assert_called_with( expected_uri, @@ -338,42 +373,55 @@ def test_sign_in_finish(self): verify=SSLMatcher(), timeout=DEFAULT_TIMEOUT_SECONDS, ) - self.assertIsNotNone(webauthn.sign_up_finish("t01", "response01")) + self.assertIsNotNone( + await futu_await(webauthn.sign_up_finish("t01", "response01")) + ) - def test_sign_up_or_in_start(self): - webauthn = WebAuthn(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_sign_up_or_in_start(self): + webauthn = WebAuthn( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, webauthn.sign_up_or_in_start, "", "https://example.com" - ) - self.assertRaises(AuthException, webauthn.sign_up_or_in_start, "id", "") + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_or_in_start("", "https://example.com")) + with self.assertRaises(AuthException): + await futu_await(webauthn.sign_up_or_in_start("id", "")) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - webauthn.sign_up_or_in_start, - "id1", - "https://example.com", - ) + with self.assertRaises(AuthException): + await futu_await( + webauthn.sign_up_or_in_start( + "id1", + "https://example.com", + ) + ) # Test success flow valid_response = json.loads( """{"create": true, "transactionId": "2COHI3LIixYhf6Q7EECYt20zyMi", "options": "{'publicKey':{'challenge':'5GOywA7BHL1QceQOfxHKDrasuN8SkbbgXmB5ImVZ+QU=','rp':{'name':'comp6','id':'localhost'},'user':{'name”:”dummy@dummy.com','displayName”:”dummy”,”id':'VTJDT0hJNWlWOHJaZ3VURkpKMzV3bjEydHRkTw=='},'pubKeyCredParams':[{'type':'public-key','alg':-7},{'type':'public-key','alg':-35},{'type':'public-key','alg':-36},{'type':'public-key','alg':-257},{'type':'public-key','alg':-258},{'type':'public-key','alg':-259},{'type':'public-key','alg':-37},{'type':'public-key','alg':-38},{'type':'public-key','alg':-39},{'type':'public-key','alg':-8}],'authenticatorSelection':{'userVerification':'preferred'},'timeout':60000,'attestation':'none'}}"}""" ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNotNone( - webauthn.sign_up_or_in_start("dummy@dummy.com", "https://example.com") + await futu_await( + webauthn.sign_up_or_in_start( + "dummy@dummy.com", "https://example.com" + ) + ) ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = valid_response mock_post.return_value = my_mock_response - res = webauthn.sign_up_or_in_start("id1", "https://example.com") + res = await futu_await( + webauthn.sign_up_or_in_start("id1", "https://example.com") + ) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.sign_up_or_in_auth_webauthn_start_path}" mock_post.assert_called_with( expected_uri, @@ -393,59 +441,70 @@ def test_sign_up_or_in_start(self): ) self.assertEqual(res, valid_response) - def test_update_start(self): + async def test_update_start(self): valid_jwt_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IjJCdDVXTGNjTFVleTFEcDd1dHB0WmIzRng5SyIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkVGVuYW50cyI6eyIiOm51bGx9LCJjb29raWVEb21haW4iOiIiLCJjb29raWVFeHBpcmF0aW9uIjoxNjYwNjc5MjA4LCJjb29raWVNYXhBZ2UiOjI1OTE5OTksImNvb2tpZU5hbWUiOiJEU1IiLCJjb29raWVQYXRoIjoiLyIsImV4cCI6MjA5MDA4NzIwOCwiaWF0IjoxNjU4MDg3MjA4LCJpc3MiOiIyQnQ1V0xjY0xVZXkxRHA3dXRwdFpiM0Z4OUsiLCJzdWIiOiIyQzU1dnl4dzBzUkw2RmRNNjhxUnNDRGRST1YifQ.cWP5up4R5xeIl2qoG2NtfLH3Q5nRJVKdz-FDoAXctOQW9g3ceZQi6rZQ-TPBaXMKw68bijN3bLJTqxWW5WHzqRUeopfuzTcMYmC0wP2XGJkrdF6A8D5QW6acSGqglFgu" - webauthn = WebAuthn(Auth(self.dummy_project_id, self.public_key_dict)) + webauthn = WebAuthn( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises( - AuthException, webauthn.update_start, "", "", "https://example.com" - ) - self.assertRaises( - AuthException, webauthn.update_start, None, "", "https://example.com" - ) - self.assertRaises( - AuthException, - webauthn.update_start, - "dummy@dummy.com", - "", - "https://example.com", - ) - self.assertRaises( - AuthException, - webauthn.update_start, - "dummy@dummy.com", - None, - "https://example.com", - ) + with self.assertRaises(AuthException): + await futu_await(webauthn.update_start("", "", "https://example.com")) + with self.assertRaises(AuthException): - with patch("httpx.post") as mock_post: - mock_post.return_value.is_success = False - self.assertRaises( - AuthException, - webauthn.update_start, - "dummy@dummy.com", - valid_jwt_token, - "https://example.com", + await futu_await(webauthn.update_start(None, "", "https://example.com")) + with self.assertRaises(AuthException): + + await futu_await( + webauthn.update_start( + "dummy@dummy.com", + "", + "https://example.com", + ) ) + with self.assertRaises(AuthException): + await futu_await( + webauthn.update_start( + "dummy@dummy.com", + None, + "https://example.com", + ) + ) + + with mock_http_call(self.async_test, "post") as mock_post: + mock_post.return_value.is_success = False + with self.assertRaises(AuthException): + await futu_await( + webauthn.update_start( + "dummy@dummy.com", + valid_jwt_token, + "https://example.com", + ) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = True self.assertIsNotNone( - webauthn.update_start( - "dummy@dummy.com", valid_jwt_token, "https://example.com" + await futu_await( + webauthn.update_start( + "dummy@dummy.com", valid_jwt_token, "https://example.com" + ) ) ) - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: valid_response = json.loads("{}") my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.json.return_value = valid_response mock_post.return_value = my_mock_response - res = webauthn.update_start( - "dummy@dummy.com", "asdasd", "https://example.com" + res = await futu_await( + webauthn.update_start( + "dummy@dummy.com", "asdasd", "https://example.com" + ) ) expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.update_auth_webauthn_start_path}" mock_post.assert_called_with( @@ -463,23 +522,32 @@ def test_update_start(self): ) self.assertEqual(res, valid_response) - def test_update_finish(self): - webauthn = WebAuthn(Auth(self.dummy_project_id, self.public_key_dict)) + async def test_update_finish(self): + webauthn = WebAuthn( + Auth( + self.dummy_project_id, self.public_key_dict, async_mode=self.async_test + ) + ) # Test failed flows - self.assertRaises(AuthException, webauthn.update_finish, "", "response01") - self.assertRaises(AuthException, webauthn.update_finish, None, "response01") - self.assertRaises(AuthException, webauthn.update_finish, "t01", "") - self.assertRaises(AuthException, webauthn.update_finish, "t01", None) - - with patch("httpx.post") as mock_post: + with self.assertRaises(AuthException): + await futu_await(webauthn.update_finish("", "response01")) + with self.assertRaises(AuthException): + await futu_await(webauthn.update_finish(None, "response01")) + with self.assertRaises(AuthException): + await futu_await(webauthn.update_finish("t01", "")) + with self.assertRaises(AuthException): + await futu_await(webauthn.update_finish("t01", None)) + + with mock_http_call(self.async_test, "post") as mock_post: mock_post.return_value.is_success = False - self.assertRaises( - AuthException, webauthn.update_finish, "t01", "response01" - ) + with self.assertRaises(AuthException): + await futu_await( + await futu_await(webauthn.update_finish("t01", "response01")) + ) # Test success flow - with patch("httpx.post") as mock_post: + with mock_http_call(self.async_test, "post") as mock_post: my_mock_response = mock.Mock() my_mock_response.is_success = True my_mock_response.cookies = {} @@ -489,7 +557,7 @@ def test_update_finish(self): my_mock_response.json.return_value = data mock_post.return_value = my_mock_response expected_uri = f"{common.DEFAULT_BASE_URL}{EndpointsV1.update_auth_webauthn_finish_path}" - webauthn.update_finish("t01", "response01") + await futu_await(webauthn.update_finish("t01", "response01")) mock_post.assert_called_with( expected_uri, headers={ @@ -503,7 +571,9 @@ def test_update_finish(self): verify=SSLMatcher(), timeout=DEFAULT_TIMEOUT_SECONDS, ) - self.assertIsNotNone(webauthn.sign_up_finish("t01", "response01")) + self.assertIsNotNone( + await futu_await(webauthn.sign_up_finish("t01", "response01")) + ) if __name__ == "__main__": diff --git a/tests/testutils.py b/tests/testutils.py index ecdd3d382..321af780f 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -1,6 +1,77 @@ from ssl import SSLContext +from contextlib import contextmanager +from unittest.mock import AsyncMock, Mock, patch class SSLMatcher: def __eq__(self, other): return isinstance(other, SSLContext) + + +@contextmanager +def mock_http_call( + is_async: bool, + method: str = "post", +): + + if is_async: + with _mock_async_http(method) as method_mock: + yield method_mock + else: + with _mock_sync_http(method) as method_mock: + yield method_mock + + +@contextmanager +def _mock_sync_http(method: str): + """Mock synchronous httpx calls.""" + with patch(f"httpx.{method}") as mock_method: + yield mock_method + + +@contextmanager +def _mock_async_http(method: str): + """Mock asynchronous httpx calls.""" + # Create mock client with the specified method + mock_client = Mock() + async_method_mock = AsyncMock() + setattr(mock_client, method, async_method_mock) + + # Mock the async context manager + mock_async_client = Mock() + mock_async_client.__aenter__ = AsyncMock(return_value=mock_client) + mock_async_client.__aexit__ = AsyncMock(return_value=None) + + # Store references for assertions + mock_async_client._mock_client = mock_client + async_method_mock._mock_async_client = mock_async_client + + with patch("httpx.AsyncClient", return_value=mock_async_client) as mock_class: + # Store the patched class for assertions + async_method_mock._mock_class = mock_class + + # Override assert_called_with to handle client parameter validation + original_assert = async_method_mock.assert_called_with + + def enhanced_assert_called_with(*args, **kwargs): + # Extract client-level parameters + client_kwargs = {} + call_kwargs = kwargs.copy() + if "verify" in call_kwargs: + client_kwargs["verify"] = call_kwargs.pop("verify") + if "timeout" in call_kwargs: + client_kwargs["timeout"] = call_kwargs.pop("timeout") + + # Assert the method call + original_assert(*args, **call_kwargs) + + # Assert the client creation if client params were provided + if client_kwargs: + mock_class.assert_called_with(**client_kwargs) + + # Use setattr to assign the method + setattr(async_method_mock, "assert_called_with", enhanced_assert_called_with) + + # using Mock here to avoid getting AsyncMock as default return value class + async_method_mock.return_value = Mock() + yield async_method_mock From 9c07f42e229cff0797ddbd48094d9cb70481fbbb Mon Sep 17 00:00:00 2001 From: mellowCaribou Date: Sun, 5 Oct 2025 11:21:55 +0300 Subject: [PATCH 2/8] Update tox.ini --- tox.ini | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tox.ini b/tox.ini index f44b54beb..966325cb1 100644 --- a/tox.ini +++ b/tox.ini @@ -5,12 +5,15 @@ env_list = type format report - py3{13, 12, 11, 10, 9, 8} + py3{13, 12, 11, 10, 9, 8}-{sync,async} [testenv] skip_install = true pass_env = COVERAGE_FILE +setenv = + sync: ASYNC_MODE = False + async: ASYNC_MODE = True commands_pre = poetry install --only main,tests -E Flask commands = @@ -34,7 +37,7 @@ commands = commands = poetry run coverage report depends = - py3{12, 11, 10, 9, 8} + py3{12, 11, 10, 9, 8}-{sync,async} [testenv:py37] deps = @@ -46,9 +49,9 @@ commands = [gh] python = - 3.13 = py313, type, format - 3.12 = py312, type, format - 3.11 = py311 - 3.10 = py310 - 3.9 = py39 - 3.8 = py38 + 3.13 = py313-sync, py313-async, type, format + 3.12 = py312-sync, py312-async, type, format + 3.11 = py311-sync, py311-async + 3.10 = py310-sync, py310-async + 3.9 = py39-sync, py39-async + 3.8 = py38-sync, py38-async From 7df93b8448ae5cd7eca2e8e9b937335f362f7984 Mon Sep 17 00:00:00 2001 From: mellowCaribou Date: Sun, 5 Oct 2025 12:53:43 +0300 Subject: [PATCH 3/8] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ea01963c6..818051c8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,7 +78,7 @@ omit = ["descope/flask/*"] [tool.coverage.report] -fail_under = 97 +fail_under = 98 skip_covered = true skip_empty = true From 550a9622c47cb18c054c4fddedc2b78ba51eb9a5 Mon Sep 17 00:00:00 2001 From: mellowCaribou Date: Sun, 5 Oct 2025 13:03:51 +0300 Subject: [PATCH 4/8] check --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 362e46afb..b992df3c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,9 @@ jobs: - ubuntu-latest - macos-latest - windows-latest + async: + - true + - false steps: - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 with: @@ -45,7 +48,8 @@ jobs: - name: Run test suite run: tox --skip-pkg-install env: - COVERAGE_FILE: "coverage.${{ matrix.os }}.${{ matrix.py }}" + COVERAGE_FILE: "coverage.${{ matrix.os }}.${{ matrix.py }}.${{matrix.async}}" + ASYNC_MODE: ${{matrix.async}} - name: Store coverage file uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: From b2cfc9512993c2937fa9761faf880da24b4da659 Mon Sep 17 00:00:00 2001 From: mellowCaribou Date: Sun, 5 Oct 2025 13:08:49 +0300 Subject: [PATCH 5/8] Update ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b992df3c2..1da47018b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,8 +53,8 @@ jobs: - name: Store coverage file uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: coverage.${{ matrix.os }}.${{ matrix.py }} - path: coverage.${{ matrix.os }}.${{ matrix.py }} + name: "coverage.${{ matrix.os }}.${{ matrix.py }}.${{matrix.async}}" + path: "coverage.${{ matrix.os }}.${{ matrix.py }}.${{matrix.async}}" if-no-files-found: error coverage: From 8d2a1f69865f0cfccba7fa3046e17a5496d331b7 Mon Sep 17 00:00:00 2001 From: mellowCaribou Date: Sun, 5 Oct 2025 16:34:44 +0300 Subject: [PATCH 6/8] Update enchantedlink.py --- descope/authmethod/enchantedlink.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/descope/authmethod/enchantedlink.py b/descope/authmethod/enchantedlink.py index 28397a141..ab3ff1e4c 100644 --- a/descope/authmethod/enchantedlink.py +++ b/descope/authmethod/enchantedlink.py @@ -1,9 +1,7 @@ from __future__ import annotations -from typing import Awaitable, Union, TYPE_CHECKING - -if TYPE_CHECKING: - import httpx +from typing import Awaitable, Union +import httpx from descope._auth_base import AuthBase from descope.auth import Auth From 86d54728e77b5abdfa01f7cee2c3537426a298a5 Mon Sep 17 00:00:00 2001 From: Lior eliav <33252035+LioriE@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:21:42 +0300 Subject: [PATCH 7/8] Update tests/test_webauthn.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_webauthn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_webauthn.py b/tests/test_webauthn.py index b921d1d01..6639f4736 100644 --- a/tests/test_webauthn.py +++ b/tests/test_webauthn.py @@ -543,7 +543,7 @@ async def test_update_finish(self): mock_post.return_value.is_success = False with self.assertRaises(AuthException): await futu_await( - await futu_await(webauthn.update_finish("t01", "response01")) + webauthn.update_finish("t01", "response01") ) # Test success flow From 3d50133e56933b4ae1cb96170bf3f64c3021b240 Mon Sep 17 00:00:00 2001 From: mellowCaribou Date: Mon, 27 Oct 2025 13:24:29 +0200 Subject: [PATCH 8/8] test --- .github/workflows/ci.yml | 15 ++++++++++++++- pyproject.toml | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1da47018b..f3a736123 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,12 +76,25 @@ jobs: pattern: coverage.* merge-multiple: true + - name: Setup Python for coverage + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.13" + + - name: Install coverage + run: python -m pip install "coverage[toml]>=7.3.1" + + - name: Combine coverage files + run: | + coverage combine coverage.* + coverage report + - name: Coverage comment id: coverage_comment uses: py-cov-action/python-coverage-comment-action@b3d7e98bf5528b07d6951ef7a93e2b156f960112 # v3.1.0 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MERGE_COVERAGE_FILES: true + MERGE_COVERAGE_FILES: false ANNOTATE_MISSING_LINES: true VERBOSE: true diff --git a/pyproject.toml b/pyproject.toml index 818051c8e..e095db177 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,6 @@ requires = ["poetry-core>=1.1.0"] build-backend = "poetry.core.masonry.api" [tool.coverage.run] -relative_files = true source = ["descope"] omit = ["descope/flask/*"]