From 62609affbfa2afbac933297c8ca3969450ba7032 Mon Sep 17 00:00:00 2001 From: Sean Keever <33592180+swkeever@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:33:23 -0400 Subject: [PATCH] feat(auth): expose current session --- README.md | 4 ++++ features/contract/auth.feature | 9 +++++++++ features/steps/sdk_contract_steps.py | 6 ++++++ src/volcano_sdk/auth.py | 9 +++++++++ tests/unit/test_contract_bindings.py | 3 ++- tests/unit/test_state.py | 27 ++++++++++++++++++++++++++- 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3fc86884..e814a0b4 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ client = VolcanoClient( ) session = client.auth.sign_in(email="user@example.com", password="secret") +current_session = client.auth.get_session() +assert current_session == session rows = client.database("main").from_("items").select("*").eq("slug", "a").execute() @@ -30,6 +32,8 @@ lease = client.locks.acquire("build", ttl=30) client.locks.release("build", lease) ``` +`get_session()` reads immutable local state. It does not refresh or validate the token. + Realtime is async. Channels wrap `centrifuge-python`; the underlying client and subscription objects are not part of the public API. diff --git a/features/contract/auth.feature b/features/contract/auth.feature index c24af106..6b671406 100644 --- a/features/contract/auth.feature +++ b/features/contract/auth.feature @@ -7,3 +7,12 @@ Feature: SDK authentication contract Then the SDK operation succeeds And the current session belongs to the contract user And the current session exposes access and refresh tokens + + @auth @SDK-AUTH-002 + Scenario: The auth facade returns the current session + Given the confirmed contract user + When the client signs in with the contract user's credentials + And the client reads the current session + Then the SDK operation succeeds + And the current session belongs to the contract user + And the current session exposes access and refresh tokens diff --git a/features/steps/sdk_contract_steps.py b/features/steps/sdk_contract_steps.py index 60d99ef4..f03d8128 100644 --- a/features/steps/sdk_contract_steps.py +++ b/features/steps/sdk_contract_steps.py @@ -32,6 +32,12 @@ def sign_in(context: Any) -> None: ) +@when("the client reads the current session") +def read_current_session(context: Any) -> None: + world = _world(context) + world.record(world.client.auth.get_session) + + @then("the SDK operation succeeds") def operation_succeeds(context: Any) -> None: outcome = _world(context).last_outcome diff --git a/src/volcano_sdk/auth.py b/src/volcano_sdk/auth.py index c4dcaa7e..ce815589 100644 --- a/src/volcano_sdk/auth.py +++ b/src/volcano_sdk/auth.py @@ -13,6 +13,11 @@ class AuthContext(Protocol): _transport: Transport + @property + def current_session(self) -> Session | None: + """Return the locally held session, if one exists.""" + ... + def _anon_token(self) -> str: ... def _set_session(self, session: Session) -> None: ... @@ -25,6 +30,10 @@ def __init__(self, client: AuthContext) -> None: """Create an authentication facade backed by a client.""" self._client = client + def get_session(self) -> Session | None: + """Return the immutable locally held session without validating it.""" + return self._client.current_session + def sign_in(self, *, email: str, password: str) -> Session: """Sign in a user and store the returned session.""" response = invoke( diff --git a/tests/unit/test_contract_bindings.py b/tests/unit/test_contract_bindings.py index 7493fb77..ff5a9891 100644 --- a/tests/unit/test_contract_bindings.py +++ b/tests/unit/test_contract_bindings.py @@ -15,7 +15,7 @@ ROOT = Path(__file__).parents[2] FEATURE_SHA256 = { - "auth.feature": "6e6bcc6244bbdb9b1c141a3f0d8f1256d2be0057429457084a094ed98cbcbd07", + "auth.feature": "70289856dcca9854464ae92659363f3395b2fe79d92ebb8374973f29f5c1994d", "database.feature": ( "4685b29357a621068b25984ff0de29cd4c504eebe5cfb597f0b999e29878a668" ), @@ -66,6 +66,7 @@ def test_every_contract_phrase_is_bound_verbatim() -> None: "the SDK operation succeeds", "the client acquires and releases the contract lock", 'the client selects the contract table where "slug" equals the fixture slug', + "the client reads the current session", "the client signs in with the contract user's credentials", "the client uploads and downloads the contract object", "the confirmed contract user", diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py index 5e4f12ef..41eb2578 100644 --- a/tests/unit/test_state.py +++ b/tests/unit/test_state.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from typing import Any -from volcano_sdk import VolcanoClient +from volcano_sdk import Session, VolcanoClient @dataclass(frozen=True) @@ -107,3 +107,28 @@ def test_each_request_reads_the_current_credentials() -> None: ("acquire", "service-1"), ("release", "service-2"), ] + + +def test_auth_facade_reads_an_empty_session_without_transport() -> None: + transport = StateTransport() + client = VolcanoClient(anon_key="anon", _transport=transport) + + assert client.auth.get_session() is None + assert transport.authorizations == [] + + +def test_auth_facade_reads_established_immutable_session_without_transport() -> None: + transport = StateTransport() + client = VolcanoClient(anon_key="anon", _transport=transport) + established = client.auth.sign_in(email="user@example.com", password="secret") + calls_after_sign_in = list(transport.authorizations) + + current = client.auth.get_session() + + assert current is established + assert current == Session( + access_token="access-1", + refresh_token="refresh-access-1", + user_id="user-123", + ) + assert transport.authorizations == calls_after_sign_in