mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 14:57:15 +00:00
135 lines
5.0 KiB
Python
135 lines
5.0 KiB
Python
"""
|
|
Integration smoke tests for Passkey API endpoints.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import pytest
|
|
|
|
pytest_plugins = ['tests.integration.api.test_smoke']
|
|
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
class TestPasskeyPublicEndpoints:
|
|
@pytest.mark.asyncio
|
|
async def test_auth_options_endpoint(self, quart_test_client, fake_api_app):
|
|
fake_api_app.user_service.generate_passkey_authentication_options = AsyncMock(
|
|
return_value=({'challenge': 'test_chal', 'rpId': 'localhost'}, 'token_123')
|
|
)
|
|
|
|
response = await quart_test_client.post(
|
|
'/api/v1/user/passkey/auth/options',
|
|
json={'origin': 'http://localhost:3000'},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = await response.get_json()
|
|
assert data['code'] == 0
|
|
assert data['data']['challenge_token'] == 'token_123'
|
|
assert data['data']['options']['rpId'] == 'localhost'
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_auth_verify_missing_payload(self, quart_test_client, fake_api_app):
|
|
response = await quart_test_client.post(
|
|
'/api/v1/user/passkey/auth/verify',
|
|
json={},
|
|
)
|
|
assert response.status_code == 200
|
|
data = await response.get_json()
|
|
assert data['code'] != 0
|
|
assert 'Missing challenge_token or credential' in data['msg']
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_auth_verify_success(self, quart_test_client, fake_api_app):
|
|
fake_api_app.user_service.verify_passkey_authentication = AsyncMock(
|
|
return_value=('jwt_token_abc', Mock(user='user@example.com'))
|
|
)
|
|
|
|
response = await quart_test_client.post(
|
|
'/api/v1/user/passkey/auth/verify',
|
|
json={'challenge_token': 'token_123', 'credential': {'id': 'cred_id'}},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = await response.get_json()
|
|
assert data['code'] == 0
|
|
assert data['data']['token'] == 'jwt_token_abc'
|
|
assert data['data']['user'] == 'user@example.com'
|
|
|
|
|
|
class TestPasskeyProtectedEndpoints:
|
|
@pytest.mark.asyncio
|
|
async def test_register_options_requires_auth(self, quart_test_client):
|
|
response = await quart_test_client.post('/api/v1/user/passkey/register/options', json={})
|
|
assert response.status_code == 401
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_passkeys_list_requires_auth(self, quart_test_client):
|
|
response = await quart_test_client.get('/api/v1/user/passkeys')
|
|
assert response.status_code == 401
|
|
|
|
|
|
class TestPasskeyReverseProxyScenarios:
|
|
@pytest.mark.asyncio
|
|
async def test_auth_options_respects_custom_origin_body_behind_proxy(self, quart_test_client, fake_api_app):
|
|
fake_api_app.user_service.generate_passkey_authentication_options = AsyncMock(
|
|
return_value=({'challenge': 'test_chal', 'rpId': 'proxy.company.com'}, 'token_proxy')
|
|
)
|
|
|
|
response = await quart_test_client.post(
|
|
'/api/v1/user/passkey/auth/options',
|
|
json={'origin': 'https://proxy.company.com:8443'},
|
|
headers={'Host': '127.0.0.1:5300'},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = await response.get_json()
|
|
assert data['code'] == 0
|
|
fake_api_app.user_service.generate_passkey_authentication_options.assert_awaited_once_with(
|
|
rp_id='proxy.company.com',
|
|
origin='https://proxy.company.com:8443',
|
|
email=None,
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_auth_options_falls_back_to_origin_header(self, quart_test_client, fake_api_app):
|
|
fake_api_app.user_service.generate_passkey_authentication_options = AsyncMock(
|
|
return_value=({'challenge': 'test_chal', 'rpId': 'bot.example.com'}, 'token_header')
|
|
)
|
|
|
|
response = await quart_test_client.post(
|
|
'/api/v1/user/passkey/auth/options',
|
|
json={},
|
|
headers={'Origin': 'https://bot.example.com'},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
fake_api_app.user_service.generate_passkey_authentication_options.assert_awaited_once_with(
|
|
rp_id='bot.example.com',
|
|
origin='https://bot.example.com',
|
|
email=None,
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_auth_options_falls_back_to_referer_header(self, quart_test_client, fake_api_app):
|
|
fake_api_app.user_service.generate_passkey_authentication_options = AsyncMock(
|
|
return_value=({'challenge': 'test_chal', 'rpId': 'bot.example.com'}, 'token_referer')
|
|
)
|
|
|
|
response = await quart_test_client.post(
|
|
'/api/v1/user/passkey/auth/options',
|
|
json={},
|
|
headers={'Referer': 'https://bot.example.com:9000/login'},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
fake_api_app.user_service.generate_passkey_authentication_options.assert_awaited_once_with(
|
|
rp_id='bot.example.com',
|
|
origin='https://bot.example.com:9000',
|
|
email=None,
|
|
)
|