Files
gatehouse-api/tests/conftest.py
T

376 lines
11 KiB
Python
Raw Normal View History

2026-01-08 01:00:26 +10:30
"""Pytest configuration and fixtures."""
import pytest
2026-01-20 15:54:00 +10:30
from unittest.mock import Mock, patch
from datetime import datetime, timedelta, timezone
2026-01-15 03:40:29 +10:30
from gatehouse_app import create_app
from gatehouse_app.extensions import db as _db
2026-01-20 15:54:00 +10:30
from gatehouse_app.models import User, Organization, OrganizationMember, AuthenticationMethod
2026-01-15 03:40:29 +10:30
from gatehouse_app.services.auth_service import AuthService
2026-01-20 15:54:00 +10:30
from gatehouse_app.utils.constants import OrganizationRole, AuthMethodType
from gatehouse_app.services.external_auth_service import ExternalProviderConfig, OAuthState
2026-01-08 01:00:26 +10:30
@pytest.fixture(scope="session")
def app():
"""Create application for testing."""
app = create_app("testing")
return app
@pytest.fixture(scope="function")
def db(app):
"""Create database for testing."""
with app.app_context():
_db.create_all()
yield _db
_db.session.remove()
_db.drop_all()
@pytest.fixture(scope="function")
def client(app, db):
"""Create test client."""
return app.test_client()
@pytest.fixture(scope="function")
def test_user(db):
"""Create a test user."""
email = "test@example.com"
password = "TestPassword123!"
full_name = "Test User"
user = AuthService.register_user(
email=email,
password=password,
full_name=full_name,
)
# Store password for testing
user._test_password = password
return user
@pytest.fixture(scope="function")
def test_organization(db, test_user):
"""Create a test organization."""
2026-01-15 03:40:29 +10:30
from gatehouse_app.services.organization_service import OrganizationService
2026-01-08 01:00:26 +10:30
org = OrganizationService.create_organization(
name="Test Organization",
slug="test-org",
owner_user_id=test_user.id,
description="A test organization",
)
return org
@pytest.fixture(scope="function")
def authenticated_client(client, test_user):
"""Create authenticated test client."""
# Login
response = client.post(
"/api/v1/auth/login",
json={
"email": test_user.email,
"password": test_user._test_password,
},
)
assert response.status_code == 200
return client
@pytest.fixture(scope="function")
def second_test_user(db):
"""Create a second test user."""
email = "second@example.com"
password = "TestPassword123!"
full_name = "Second User"
user = AuthService.register_user(
email=email,
password=password,
full_name=full_name,
)
user._test_password = password
return user
2026-01-20 15:54:00 +10:30
# =============================================================================
# External Auth Testing Fixtures
# =============================================================================
@pytest.fixture(scope="function")
def google_provider_config(db, test_organization):
"""Create a Google OAuth provider configuration."""
config = ExternalProviderConfig(
organization_id=test_organization.id,
provider_type=AuthMethodType.GOOGLE.value,
client_id="test-google-client-id",
client_secret_encrypted="encrypted-google-secret",
auth_url="https://accounts.google.com/o/oauth2/v2/auth",
token_url="https://oauth2.googleapis.com/token",
userinfo_url="https://www.googleapis.com/oauth2/v3/userinfo",
scopes=["openid", "profile", "email"],
redirect_uris=[
"http://localhost:3000/callback",
"http://localhost:5173/callback",
"https://myapp.example.com/callback",
],
is_active=True,
)
config.save()
return config
@pytest.fixture(scope="function")
def github_provider_config(db, test_organization):
"""Create a GitHub OAuth provider configuration."""
config = ExternalProviderConfig(
organization_id=test_organization.id,
provider_type=AuthMethodType.GITHUB.value,
client_id="test-github-client-id",
client_secret_encrypted="encrypted-github-secret",
auth_url="https://github.com/login/oauth/authorize",
token_url="https://github.com/login/oauth/access_token",
userinfo_url="https://api.github.com/user",
scopes=["read:user", "user:email"],
redirect_uris=["http://localhost:3000/callback"],
is_active=True,
)
config.save()
return config
@pytest.fixture(scope="function")
def microsoft_provider_config(db, test_organization):
"""Create a Microsoft OAuth provider configuration."""
config = ExternalProviderConfig(
organization_id=test_organization.id,
provider_type=AuthMethodType.MICROSOFT.value,
client_id="test-microsoft-client-id",
client_secret_encrypted="encrypted-microsoft-secret",
auth_url="https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
token_url="https://login.microsoftonline.com/common/oauth2/v2.0/token",
userinfo_url="https://graph.microsoft.com/oidc/userinfo",
scopes=["openid", "profile", "email", "User.Read"],
redirect_uris=["http://localhost:3000/callback"],
is_active=True,
)
config.save()
return config
@pytest.fixture(scope="function")
def user_with_google_link(db, test_user):
"""Create a test user with a linked Google account."""
auth_method = AuthenticationMethod(
user_id=test_user.id,
method_type=AuthMethodType.GOOGLE,
provider_user_id="google-123456789",
provider_data={
"email": test_user.email,
"name": "Test User",
"picture": "https://example.com/avatar.jpg",
},
verified=True,
is_primary=False,
)
auth_method.save()
return test_user
@pytest.fixture(scope="function")
def user_with_multiple_providers(db, test_user):
"""Create a test user with multiple linked external accounts."""
# Google account
google_method = AuthenticationMethod(
user_id=test_user.id,
method_type=AuthMethodType.GOOGLE,
provider_user_id="google-123",
provider_data={
"email": test_user.email,
"name": "Test User",
},
verified=True,
)
google_method.save()
# GitHub account
github_method = AuthenticationMethod(
user_id=test_user.id,
method_type=AuthMethodType.GITHUB,
provider_user_id="github-456",
provider_data={
"email": "user@github.com",
"name": "Test User",
},
verified=True,
)
github_method.save()
return test_user
@pytest.fixture
def mock_google_oauth_token_response():
"""Mock Google OAuth token response."""
return {
"access_token": "ya29.mock-access-token",
"refresh_token": "1//mock-refresh-token",
"id_token": "eyJ.mock-id-token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile email",
}
@pytest.fixture
def mock_google_oauth_user_info():
"""Mock Google OAuth user info response."""
return {
"sub": "google-123456789",
"name": "Test User",
"given_name": "Test",
"family_name": "User",
"picture": "https://example.com/avatar.jpg",
"email": "testuser@gmail.com",
"email_verified": True,
}
@pytest.fixture
def mock_github_oauth_token_response():
"""Mock GitHub OAuth token response."""
return {
"access_token": "gho_mock-access-token",
"token_type": "bearer",
"scope": "read:user,user:email",
}
@pytest.fixture
def mock_github_oauth_user_info():
"""Mock GitHub OAuth user info response."""
return {
"id": 123456789,
"login": "testuser",
"name": "Test User",
"email": "testuser@github.com",
"avatar_url": "https://example.com/avatar.jpg",
"type": "User",
}
@pytest.fixture
def oauth_login_state(db, test_organization):
"""Create an OAuth state for login flow."""
state = OAuthState.create_state(
flow_type="login",
provider_type=AuthMethodType.GOOGLE,
organization_id=test_organization.id,
redirect_uri="http://localhost:3000/callback",
nonce="mock-nonce",
code_verifier="mock-code-verifier",
code_challenge="mock-code-challenge",
lifetime_seconds=600,
)
return state
@pytest.fixture
def oauth_register_state(db, test_organization):
"""Create an OAuth state for register flow."""
state = OAuthState.create_state(
flow_type="register",
provider_type=AuthMethodType.GOOGLE,
organization_id=test_organization.id,
redirect_uri="http://localhost:3000/callback",
lifetime_seconds=600,
)
return state
@pytest.fixture
def oauth_link_state(db, test_user, test_organization):
"""Create an OAuth state for link flow."""
state = OAuthState.create_state(
flow_type="link",
provider_type=AuthMethodType.GOOGLE,
user_id=test_user.id,
organization_id=test_organization.id,
redirect_uri="http://localhost:3000/callback",
lifetime_seconds=600,
)
return state
@pytest.fixture
def expired_oauth_state(db, test_organization):
"""Create an expired OAuth state."""
state = OAuthState.create_state(
flow_type="login",
provider_type=AuthMethodType.GOOGLE,
organization_id=test_organization.id,
redirect_uri="http://localhost:3000/callback",
lifetime_seconds=-1, # Already expired
)
return state
@pytest.fixture
def used_oauth_state(db, test_organization):
"""Create a used OAuth state."""
state = OAuthState.create_state(
flow_type="login",
provider_type=AuthMethodType.GOOGLE,
organization_id=test_organization.id,
redirect_uri="http://localhost:3000/callback",
lifetime_seconds=600,
)
state.mark_used()
return state
@pytest.fixture
def mock_oauth_flow_mocks():
"""Common mocks for OAuth flow tests."""
with patch.object(
ExternalProviderConfig, 'get_client_secret', return_value='mock-secret'
) as mock_get_secret, patch(
'requests.post'
) as mock_post, patch(
'requests.get'
) as mock_get:
# Mock token exchange response
mock_post.return_value.json.return_value = {
"access_token": "mock-access-token",
"refresh_token": "mock-refresh-token",
"id_token": "mock-id-token",
"expires_in": 3600,
}
mock_post.return_value.raise_for_status = Mock()
# Mock user info response
mock_get.return_value.json.return_value = {
"sub": "google-123",
"email": "testuser@gmail.com",
"email_verified": True,
"name": "Test User",
"picture": "https://example.com/avatar.jpg",
}
mock_get.return_value.raise_for_status = Mock()
yield {
'get_secret': mock_get_secret,
'post': mock_post,
'get': mock_get,
}