""" Auth-flow integration tests for gn-auth. Require live credentials set up by the CI test-session lifecycle (create-test-users / create-test-oauth2-client). Run with: pytest -m "gn_auth and auth_flow" Environment variables: GN_TEST_EMAIL admin test-user email GN_TEST_PASSWORD admin test-user password GN_TEST_BASIC_EMAIL unprivileged test-user email GN_TEST_BASIC_PASSWORD unprivileged test-user password GN_OAUTH2_CLIENT_ID OAuth2 client UUID GN_OAUTH2_CLIENT_SECRET OAuth2 client secret """ import pytest pytestmark = [pytest.mark.gn_auth, pytest.mark.auth_flow] # --------------------------------------------------------------------------- # POST /auth/token — password grant with valid credentials # --------------------------------------------------------------------------- class TestTokenGrant: """Password grant with valid admin credentials issues a usable token.""" def _post_token(self, gn_auth_url, http, oauth2_credentials, scope="profile group resource"): email, password, client_id, client_secret = oauth2_credentials return http.post( f"{gn_auth_url}/auth/token", json={ "grant_type": "password", "username": email, "password": password, "scope": scope, "client_id": client_id, "client_secret": client_secret, }, timeout=30, ) def test_valid_credentials_return_200( self, gn_auth_url, http, oauth2_credentials): resp = self._post_token(gn_auth_url, http, oauth2_credentials) assert resp.status_code == 200, ( f"Expected 200 from token endpoint, got {resp.status_code}: {resp.text}" ) def test_response_contains_access_token( self, gn_auth_url, http, oauth2_credentials): resp = self._post_token(gn_auth_url, http, oauth2_credentials) data = resp.json() assert "access_token" in data, ( f"Token response missing 'access_token': {data}" ) def test_token_type_is_bearer( self, gn_auth_url, http, oauth2_credentials): resp = self._post_token(gn_auth_url, http, oauth2_credentials) token_type = resp.json().get("token_type", "") assert token_type.lower() == "bearer", ( f"Expected token_type 'bearer', got: {token_type!r}" ) def test_granted_scope_covers_requested( self, gn_auth_url, http, oauth2_credentials): requested = {"profile", "group", "resource"} resp = self._post_token(gn_auth_url, http, oauth2_credentials) data = resp.json() assert "scope" in data, f"Token response missing 'scope': {data}" granted = set(data["scope"].split()) assert requested <= granted, ( f"Requested scopes {requested} not all in granted scopes {granted}" )