mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 06:47:13 +00:00
104 lines
3.6 KiB
Python
104 lines
3.6 KiB
Python
"""
|
|
Unit tests for Passkey WebAuthn service operations in UserService.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import pytest
|
|
|
|
from langbot.pkg.api.http.service.user import UserService
|
|
from langbot.pkg.entity.persistence.user import AccountStatus, User
|
|
|
|
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
class TestPasskeyChallengeLifecycle:
|
|
async def test_challenge_issuance_and_consumption(self):
|
|
service = UserService(SimpleNamespace())
|
|
token, challenge_bytes = await service.issue_passkey_challenge(
|
|
purpose='register',
|
|
rp_id='localhost',
|
|
origin='http://localhost:3000',
|
|
account_uuid='acc-123',
|
|
user_email='user@example.com',
|
|
)
|
|
|
|
assert len(token) > 20
|
|
assert len(challenge_bytes) == 32
|
|
|
|
data = await service.consume_passkey_challenge(token, 'register')
|
|
assert data.challenge == challenge_bytes
|
|
assert data.rp_id == 'localhost'
|
|
assert data.origin == 'http://localhost:3000'
|
|
assert data.account_uuid == 'acc-123'
|
|
assert data.user_email == 'user@example.com'
|
|
|
|
# Replay should fail
|
|
with pytest.raises(ValueError, match='Invalid or expired passkey challenge'):
|
|
await service.consume_passkey_challenge(token, 'register')
|
|
|
|
async def test_challenge_purpose_mismatch_fails(self):
|
|
service = UserService(SimpleNamespace())
|
|
token, _ = await service.issue_passkey_challenge(
|
|
purpose='register',
|
|
rp_id='localhost',
|
|
origin='http://localhost:3000',
|
|
)
|
|
|
|
with pytest.raises(ValueError, match='Passkey challenge purpose mismatch'):
|
|
await service.consume_passkey_challenge(token, 'auth')
|
|
|
|
async def test_challenge_expiration(self):
|
|
service = UserService(SimpleNamespace())
|
|
token, _ = await service.issue_passkey_challenge(
|
|
purpose='auth',
|
|
rp_id='localhost',
|
|
origin='http://localhost:3000',
|
|
ttl_seconds=0,
|
|
)
|
|
|
|
with pytest.raises(ValueError, match='Invalid or expired passkey challenge'):
|
|
await service.consume_passkey_challenge(token, 'auth')
|
|
|
|
|
|
class TestPasskeyOptionsGeneration:
|
|
async def test_generate_registration_options(self):
|
|
service = UserService(SimpleNamespace())
|
|
mock_user = Mock(spec=User)
|
|
mock_user.uuid = 'acc-test-uuid'
|
|
mock_user.user = 'test@example.com'
|
|
mock_user.status = AccountStatus.ACTIVE.value
|
|
service.get_user_by_uuid = AsyncMock(return_value=mock_user)
|
|
service.get_user_passkeys = AsyncMock(return_value=[])
|
|
|
|
options, token = await service.generate_passkey_registration_options(
|
|
account_uuid='acc-test-uuid',
|
|
rp_id='localhost',
|
|
origin='http://localhost:3000',
|
|
rp_name='LangBot Test',
|
|
)
|
|
|
|
assert isinstance(options, dict)
|
|
assert options['rp']['name'] == 'LangBot Test'
|
|
assert options['rp']['id'] == 'localhost'
|
|
assert options['user']['name'] == 'test@example.com'
|
|
assert 'challenge' in options
|
|
assert len(token) > 0
|
|
|
|
async def test_generate_authentication_options_discoverable(self):
|
|
service = UserService(SimpleNamespace())
|
|
|
|
options, token = await service.generate_passkey_authentication_options(
|
|
rp_id='localhost',
|
|
origin='http://localhost:3000',
|
|
)
|
|
|
|
assert isinstance(options, dict)
|
|
assert options['rpId'] == 'localhost'
|
|
assert 'challenge' in options
|
|
assert len(token) > 0
|