Skip to content

feat(tests): add e2e test suite#1564

Open
LioriE wants to merge 32 commits into
mainfrom
feat/e2e-tests
Open

feat(tests): add e2e test suite#1564
LioriE wants to merge 32 commits into
mainfrom
feat/e2e-tests

Conversation

@LioriE

@LioriE LioriE commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

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 (the invoke() + attribute-delegation wrapper) into a standalone module. Both the unit-test UnifiedClient in tests/conftest.py and the new e2e fixture inherit from it, eliminating duplication.

E2E fixture (tests/e2e/conftest.py)
A pytest-parametrized descope_client fixture (params: ["sync", "async"]) that wraps DescopeClient / DescopeClientAsync against a real backend. Tests auto-skip when DESCOPE_PROJECT_ID / DESCOPE_MANAGEMENT_KEY are absent — normal mocked CI runs are unaffected.

Test coverage

  • Auth: test_access_keys, test_magiclink, test_otp, test_password
  • Management: test_descoper, test_flow, test_jwt, test_mgmtkey, test_project, test_sso, test_user

Each test runs twice (sync + async) via the parametrized fixture. All tests use pytest.mark.e2e.

CI job
Adds an e2e job to .github/workflows/ci.yml that runs pytest tests/e2e -m e2e with project secrets injected. Added to the all-checks-passed gate.

Dependency
python-dotenv added as a dev dependency to support .env files for local runs.

LioriE and others added 18 commits June 7, 2026 16:02
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.
- 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
@shuni-bot-dev

shuni-bot-dev Bot commented Jun 11, 2026

Copy link
Copy Markdown

🐕 Review complete — View session on Shuni Portal 🐾

@shuni-bot-dev shuni-bot-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐕 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-dotenv hard-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!

Comment thread tests/e2e/conftest.py Outdated
Comment thread .github/workflows/ci.yml Outdated
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

Coverage report

The coverage rate went from 98.31% to 98.12% ⬇️

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.
LioriE and others added 2 commits June 15, 2026 11:40
@LioriE LioriE changed the base branch from feat/poc-async-base-and-totp to feat/async-descope-client June 17, 2026 11:29
LioriE added 8 commits June 17, 2026 15:22
…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
@omercnet

Copy link
Copy Markdown
Member

@shuni review like Linus

@shuni-bot-dev

shuni-bot-dev Bot commented Jun 21, 2026

Copy link
Copy Markdown

🐕 Review complete — View session on Shuni Portal 🐾

@shuni-bot-dev shuni-bot-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐕 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.py aborts the whole pytest session on missing env vars, breaking the README's documented pytest tests -m "not e2e" skip command
  • 1 🟢 LOW: skip_debounce workflow_dispatch input can never take effect

See inline comments. Needs a quick bath! Woof!

Comment thread tests/e2e/conftest.py
Comment on lines +35 to +40
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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.

Comment thread .github/workflows/ci.yml
github.event_name == 'pull_request' &&
github.actor != 'renovate[bot]' &&
!contains(github.event.pull_request.labels.*.name, 'quick-e2e') &&
inputs.skip_debounce != 'true'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.

LioriE added 2 commits June 22, 2026 12:22
…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.
Base automatically changed from feat/async-descope-client to main June 23, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants