feat(tests): add e2e test suite#1564
Conversation
Move PUBLIC_KEY_DICT, VALID_REFRESH_TOKEN, VALID_SESSION_TOKEN, and EXPIRED_SESSION_TOKEN from individual test files into testutils.py. Move assert_http_called into conftest.py. Replace old test files with the unified sync/async parity versions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace ~45 three-line dash-banner comment blocks across 8 files with nothing — class names, method names, and docstrings already describe what those banners labelled. The only banner-style block kept is the SOCKS-proxy workaround doc in conftest.py, which is real documentation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For each method (OAuth, SSO, SAML, OTP, MagicLink, EnchantedLink, Password, WebAuthn): extract an I/O-free _<name>_base.py with shared static helpers, refactor the sync class to inherit it, add a parallel async class using AsyncAuthBase, and rewrite the test file to the parametrized client_factory harness so every test runs under both sync and async. Wires all new async classes into DescopeClientAsync.
…hon-sdk into feat/poc-async-base-and-totp
- Add missing `from datetime import datetime` to audit.py / audit_async.py - Rename camelCase params in tenant_async.py (JITDisabled→jit_disabled) and user_async.py (withRefreshToken/forceRefresh→snake_case); `from __future__ import annotations` caused ruff N803 to fire only on the async files - Fix N806 variable names in _user_base.py (usersBody→users_body, uBody→u_body) - Wire JWTBase into jwt.py / jwt_async.py; UserBase._validate_search_pagination into user.py / user_async.py; complete sso_settings_async.py dedup - Remove unused imports (Any from fga_async, url_params_to_dict from outbound_application)
- Remove stale pytest.skip from test_mgmt (MGMTAsync raises AuthException without key, same as sync) - Add per-method one-liner docstrings to all 8 async auth classes to match TOTPAsync pattern - Fix response param shadowing in webauthn sign_up_finish/sign_in_finish (sync + async) - Add gitleaks fingerprints for test-fixture JWTs in test_descope_client_parity.py
|
🐕 Review complete — View session on Shuni Portal 🐾 |
There was a problem hiding this comment.
🐕 Shuni's Review
Adds a parametrized (sync+async) e2e suite that runs the SDK against a real Descope backend, plus a CI e2e job. Clean, well-organized port — good bones!
Sniffed out 2 issues:
- 2 🟡 MEDIUM: dev-only
python-dotenvhard-imported in an always-collected conftest; e2e added to the required merge gate while running against prod.
No crash bugs found. See inline comments. Woof!
Coverage reportThe coverage rate went from None of the new lines are part of the tested code. Therefore, there is no coverage data about them. |
…natures Mypy flagged arg-type errors because the callers pass Optional[str] (validated by guard methods that raise on None) while the base method signatures declared str. The implementations already handle None via truthiness checks; widen the parameter types to match the calling convention.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ient # Conflicts: # descope/management/sso_settings.py # uv.lock
…dshake - Extract AuthBase from Auth; AuthAsync inherits and runs all I/O async (JWKS fetch via asyncio.Lock-guarded ensure_keys, no module-level httpx in the constructor or hot path). - License handshake on the async client now runs lazily on the first mgmt request via HTTPClientAsync._pre_request_hook, so users who skip aopen()/async-with no longer silently lose the x-descope-license header. - Authmethods share validation/parsing through _authmethod_base; sync vs async only differ in the actual I/O call site.
- _auth_base.py: comment the audience-sniff except (intentional fallthrough).
- auth_async.py: comment + `continue` in JWKS key-loading skip-on-error loop.
- auth.py / tests/test_auth.py: drop the `import jwt` test-patch shim and
re-target the 6 `patch("descope.auth.jwt.*")` sites to the truthful
`descope._auth_base.jwt.*` location where the symbol is actually used.
- tests/test_descope_client.py: rewrite three previously [async]-skipped
tests to use the existing UnifiedClient mock helpers (mock_post,
mock_mgmt_post) and `invoke()`, with mode-aware assert_called_with for
the verify=/timeout= per-call args that only sync HTTPClient sends.
Merge with main brought in `actor_id` on Tenant.generate_sso_configuration_link plus a new unittest-style test using the pre-refactor harness. - TenantAsync: add the same `actor_id` parameter so sync/async stay in parity. - test_generate_sso_configuration_link_with_actor: rewrite to use the parametrized client_factory + mock_mgmt_post + invoke pattern, matching the other tests in this file.
Coverage was at 97.4% under the 98% gate. Added: - AuthAsync: validate_and_refresh_session (3 paths), refresh_session empty token, exchange_token (empty + happy path), _ensure_keys_for_jwt_response exception fall-throughs, _fetch_public_keys skip-on-invalid-entry. - common.py: LoginOptions optional fields, SignUpOptions construction and signup_options_to_dict (empty + full). Local coverage: 98.16%.
…feat/e2e-tests # Conflicts: # pyproject.toml # uv.lock
…ient # Conflicts: # descope/authmethod/otp.py # tests/test_otp.py
|
@shuni review like Linus |
|
🐕 Review complete — View session on Shuni Portal 🐾 |
There was a problem hiding this comment.
🐕 Shuni's Review
Adds a parametrized sync/async e2e suite, a shared UnifiedClientBase, and an e2e CI job. Solid bones — clean fixtures and good cleanup discipline.
Sniffed out 2 issues:
- 1 🟠 HIGH: e2e
conftest.pyaborts the whole pytest session on missing env vars, breaking the README's documentedpytest tests -m "not e2e"skip command - 1 🟢 LOW:
skip_debounceworkflow_dispatch input can never take effect
See inline comments. Needs a quick bath! Woof!
| if not os.environ.get("DESCOPE_PROJECT_ID") or not os.environ.get("DESCOPE_MANAGEMENT_KEY"): | ||
| print( | ||
| "ERROR: DESCOPE_PROJECT_ID and DESCOPE_MANAGEMENT_KEY must be set to run e2e tests", | ||
| file=sys.stderr, | ||
| ) | ||
| pytest.exit("Missing required e2e environment variables", returncode=1) |
There was a problem hiding this comment.
🟠 HIGH: This module-level guard runs at collection time for the whole tests/e2e tree, so pytest.exit(...) aborts the entire session — not just e2e — whenever the env vars are absent.
Marker filtering happens after collection, so the README's documented skip command uv run pytest tests -m "not e2e" (and any local pytest tests) will hard-fail with returncode 1 instead of running the unit suite. CI's coverage job only survives because it uses --ignore=tests/e2e, not -m.
Move the env check into the descope_client fixture (raise/pytest.fail there) so it still fails loudly when an e2e test actually runs, but doesn't break collection of unrelated tests.
| github.event_name == 'pull_request' && | ||
| github.actor != 'renovate[bot]' && | ||
| !contains(github.event.pull_request.labels.*.name, 'quick-e2e') && | ||
| inputs.skip_debounce != 'true' |
There was a problem hiding this comment.
🟢 LOW: This skip_debounce check is dead. The step's first condition requires github.event_name == 'pull_request', but the skip_debounce input only exists for workflow_dispatch events. On a PR run inputs.skip_debounce is always empty, so != 'true' is always true; on a dispatch run the step is skipped entirely. The input (and the comment promising it skips the debounce) never has any effect.
…h helper Restore main's DRY pattern: oauth/saml/sso exchange_token (sync + async) now delegate to Auth.exchange_token / AuthAsync.exchange_token instead of inlining the HTTP call. This reconnects the previously-orphaned helpers, restores the audience pass-through (was hard-coded None), and removes 6 byte-identical copies. Also drop the now-dead _validate_exchange_code / _compose_exchange_body statics from the oauth/saml/sso base classes. No public API or behavior change.
Closes descope/etc#16390
What
Adds an end-to-end test suite that runs the SDK against a real Descope backend, covering both the sync and async clients in a single parametrized pass.
Key changes
New shared base (
tests/_unified.py)Extracts
UnifiedClientBase(theinvoke()+ attribute-delegation wrapper) into a standalone module. Both the unit-testUnifiedClientintests/conftest.pyand the new e2e fixture inherit from it, eliminating duplication.E2E fixture (
tests/e2e/conftest.py)A
pytest-parametrizeddescope_clientfixture (params:["sync", "async"]) that wrapsDescopeClient/DescopeClientAsyncagainst a real backend. Tests auto-skip whenDESCOPE_PROJECT_ID/DESCOPE_MANAGEMENT_KEYare absent — normal mocked CI runs are unaffected.Test coverage
test_access_keys,test_magiclink,test_otp,test_passwordtest_descoper,test_flow,test_jwt,test_mgmtkey,test_project,test_sso,test_userEach test runs twice (sync + async) via the parametrized fixture. All tests use
pytest.mark.e2e.CI job
Adds an
e2ejob to.github/workflows/ci.ymlthat runspytest tests/e2e -m e2ewith project secrets injected. Added to theall-checks-passedgate.Dependency
python-dotenvadded as a dev dependency to support.envfiles for local runs.