Skip to content

Commit 25c20d3

Browse files
committed
test(auth): cover non-string email claims and email-less stubs
Sourcery review follow-ups: a JWT whose "email" claim is an int or list must parse to email=None without erroring, and an email-only allowlist must gracefully deny a user object lacking the email attribute entirely — pinning _email_of's getattr fallback.
1 parent 2f31d4d commit 25c20d3

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

tests/unit/services/test_feature_flag_service.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ async def test_no_email_attribute_falls_back_to_user_id(self):
170170
# _user() with no email omits the attribute; getattr returns None.
171171
assert await service.is_enabled("test_flag", _user(USER_A)) is True
172172

173+
@pytest.mark.asyncio
174+
async def test_email_only_allowlist_denies_stub_without_email_attribute(self):
175+
# Pins _email_of's getattr fallback: a user object with NO email
176+
# attribute at all (not CurrentUser with email=None) against an
177+
# email-ONLY allowlist → graceful deny, no AttributeError.
178+
flag = _flag(
179+
rollout_type=RolloutType.ALLOWLIST,
180+
allowlist_emails=["alice@example.com"],
181+
)
182+
service, _, _ = make_service(flag=flag)
183+
stub = _user(USER_A) # SimpleNamespace; email attribute omitted
184+
assert not hasattr(stub, "email")
185+
assert await service.is_enabled("test_flag", stub) is False
186+
173187
@pytest.mark.asyncio
174188
async def test_current_user_email_field_flows_through(self):
175189
"""CurrentUser.email (JWT "email" claim / UserDoc email) satisfies

tests/unit/test_auth_deps.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,21 @@ async def test_jwt_blank_email_claim_yields_none_email(self):
235235
assert result is not None
236236
assert result.email is None
237237

238+
@pytest.mark.asyncio
239+
@pytest.mark.parametrize("bad_email", [42, ["alice@example.com"]])
240+
async def test_jwt_non_string_email_claim_yields_none_email(self, bad_email):
241+
# A token whose "email" claim is not a string (int, list, …) must
242+
# still authenticate — the claim parses to None, never an error.
243+
token = make_jwt_token(email=bad_email)
244+
req = make_request(auth_header=f"Bearer {token}")
245+
246+
with patch("dependencies.auth.get_settings", return_value=make_settings()):
247+
result = await get_current_user(req, db=MagicMock())
248+
249+
assert result is not None
250+
assert result.user_id == USER_OID
251+
assert result.email is None
252+
238253
@pytest.mark.asyncio
239254
async def test_token_factory_round_trip_populates_email(self):
240255
# Mint with the real TokenFactory → resolve via get_current_user:

0 commit comments

Comments
 (0)