auth google + jwt
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
106
tests/test_auth_routes.py
Normal file
106
tests/test_auth_routes.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""Tests for authentication routes."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from app import create_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
"""Create a test client for the Flask application."""
|
||||
app = create_app()
|
||||
app.config["TESTING"] = True
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
|
||||
class TestAuthRoutes:
|
||||
"""Test cases for authentication routes."""
|
||||
|
||||
@patch("app.routes.auth.auth_service.get_login_url")
|
||||
def test_login_route(self, mock_get_login_url: Mock, client) -> None:
|
||||
"""Test the login route."""
|
||||
mock_get_login_url.return_value = "https://accounts.google.com/oauth/authorize?..."
|
||||
|
||||
response = client.get("/api/auth/login")
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
assert "login_url" in data
|
||||
assert data["login_url"] == "https://accounts.google.com/oauth/authorize?..."
|
||||
|
||||
def test_callback_route_no_code(self, client) -> None:
|
||||
"""Test callback route without authorization code."""
|
||||
response = client.get("/api/auth/callback")
|
||||
assert response.status_code == 400
|
||||
data = response.get_json()
|
||||
assert data["error"] == "Authorization code not found"
|
||||
|
||||
@patch("app.routes.auth.auth_service.handle_callback")
|
||||
def test_callback_route_success(self, mock_handle_callback: Mock, client) -> None:
|
||||
"""Test successful callback route."""
|
||||
user_data = {
|
||||
"id": "123",
|
||||
"email": "test@example.com",
|
||||
"name": "Test User"
|
||||
}
|
||||
mock_response = Mock()
|
||||
mock_response.get_json.return_value = {
|
||||
"message": "Login successful",
|
||||
"user": user_data
|
||||
}
|
||||
mock_handle_callback.return_value = (user_data, mock_response)
|
||||
|
||||
with patch("app.routes.auth.client.get") as mock_get:
|
||||
mock_get.return_value = mock_response
|
||||
response = client.get("/api/auth/callback?code=test_code")
|
||||
# Since we're returning the mock response directly, we need to verify differently
|
||||
mock_handle_callback.assert_called_once()
|
||||
|
||||
@patch("app.routes.auth.auth_service.handle_callback")
|
||||
def test_callback_route_error(self, mock_handle_callback: Mock, client) -> None:
|
||||
"""Test callback route with error."""
|
||||
mock_handle_callback.side_effect = Exception("OAuth error")
|
||||
|
||||
response = client.get("/api/auth/callback?code=test_code")
|
||||
assert response.status_code == 400
|
||||
data = response.get_json()
|
||||
assert data["error"] == "OAuth error"
|
||||
|
||||
@patch("app.routes.auth.auth_service.logout")
|
||||
def test_logout_route(self, mock_logout: Mock, client) -> None:
|
||||
"""Test logout route."""
|
||||
mock_response = Mock()
|
||||
mock_response.get_json.return_value = {"message": "Logged out successfully"}
|
||||
mock_logout.return_value = mock_response
|
||||
|
||||
with patch("app.routes.auth.client.get") as mock_get:
|
||||
mock_get.return_value = mock_response
|
||||
response = client.get("/api/auth/logout")
|
||||
mock_logout.assert_called_once()
|
||||
|
||||
@patch("app.routes.auth.auth_service.get_current_user")
|
||||
def test_me_route_authenticated(self, mock_get_current_user: Mock, client) -> None:
|
||||
"""Test /me route when authenticated."""
|
||||
user_data = {
|
||||
"id": "123",
|
||||
"email": "test@example.com",
|
||||
"name": "Test User"
|
||||
}
|
||||
mock_get_current_user.return_value = user_data
|
||||
|
||||
response = client.get("/api/auth/me")
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
assert data["user"] == user_data
|
||||
|
||||
@patch("app.routes.auth.auth_service.get_current_user")
|
||||
def test_me_route_not_authenticated(self, mock_get_current_user: Mock, client) -> None:
|
||||
"""Test /me route when not authenticated."""
|
||||
mock_get_current_user.return_value = None
|
||||
|
||||
response = client.get("/api/auth/me")
|
||||
assert response.status_code == 401
|
||||
data = response.get_json()
|
||||
assert data["error"] == "Not authenticated"
|
||||
81
tests/test_auth_service.py
Normal file
81
tests/test_auth_service.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""Tests for AuthService."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from app.services.auth_service import AuthService
|
||||
|
||||
|
||||
class TestAuthService:
|
||||
"""Test cases for AuthService."""
|
||||
|
||||
def test_init_without_app(self) -> None:
|
||||
"""Test initializing AuthService without Flask app."""
|
||||
auth_service = AuthService()
|
||||
assert auth_service.oauth is not None
|
||||
assert auth_service.google is None
|
||||
assert auth_service.token_service is not None
|
||||
|
||||
@patch("app.services.auth_service.os.getenv")
|
||||
def test_init_app(self, mock_getenv: Mock) -> None:
|
||||
"""Test initializing AuthService with Flask app."""
|
||||
mock_getenv.side_effect = lambda key: {
|
||||
"GOOGLE_CLIENT_ID": "test_client_id",
|
||||
"GOOGLE_CLIENT_SECRET": "test_client_secret"
|
||||
}.get(key)
|
||||
|
||||
mock_app = Mock()
|
||||
auth_service = AuthService()
|
||||
auth_service.init_app(mock_app)
|
||||
|
||||
auth_service.oauth.init_app.assert_called_once_with(mock_app)
|
||||
|
||||
@patch("app.services.auth_service.request")
|
||||
def test_get_current_user_no_token(self, mock_request: Mock) -> None:
|
||||
"""Test getting current user when no token exists."""
|
||||
mock_request.cookies.get.return_value = None
|
||||
auth_service = AuthService()
|
||||
|
||||
user = auth_service.get_current_user()
|
||||
assert user is None
|
||||
|
||||
@patch("app.services.auth_service.request")
|
||||
def test_get_current_user_with_token(self, mock_request: Mock) -> None:
|
||||
"""Test getting current user when valid token exists."""
|
||||
mock_request.cookies.get.return_value = "valid.access.token"
|
||||
auth_service = AuthService()
|
||||
|
||||
user_data = {"id": "123", "email": "test@example.com", "name": "Test User"}
|
||||
with patch.object(auth_service.token_service, 'get_user_from_access_token', return_value=user_data):
|
||||
user = auth_service.get_current_user()
|
||||
assert user == user_data
|
||||
|
||||
@patch("app.services.auth_service.request")
|
||||
def test_is_authenticated_false(self, mock_request: Mock) -> None:
|
||||
"""Test authentication check when not authenticated."""
|
||||
mock_request.cookies.get.return_value = None
|
||||
auth_service = AuthService()
|
||||
|
||||
assert not auth_service.is_authenticated()
|
||||
|
||||
@patch("app.services.auth_service.request")
|
||||
def test_is_authenticated_true(self, mock_request: Mock) -> None:
|
||||
"""Test authentication check when authenticated."""
|
||||
mock_request.cookies.get.return_value = "valid.access.token"
|
||||
auth_service = AuthService()
|
||||
user_data = {"id": "123", "email": "test@example.com", "name": "Test User"}
|
||||
|
||||
with patch.object(auth_service.token_service, 'get_user_from_access_token', return_value=user_data):
|
||||
assert auth_service.is_authenticated()
|
||||
|
||||
@patch("app.services.auth_service.make_response")
|
||||
def test_logout(self, mock_make_response: Mock) -> None:
|
||||
"""Test logout functionality."""
|
||||
mock_response = Mock()
|
||||
mock_make_response.return_value = mock_response
|
||||
|
||||
auth_service = AuthService()
|
||||
result = auth_service.logout()
|
||||
|
||||
assert result == mock_response
|
||||
mock_response.set_cookie.assert_any_call("access_token", "", expires=0)
|
||||
mock_response.set_cookie.assert_any_call("refresh_token", "", expires=0)
|
||||
22
tests/test_greeting_service.py
Normal file
22
tests/test_greeting_service.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Tests for GreetingService."""
|
||||
|
||||
from app.services.greeting_service import GreetingService
|
||||
|
||||
|
||||
class TestGreetingService:
|
||||
"""Test cases for GreetingService."""
|
||||
|
||||
def test_get_greeting_without_name(self) -> None:
|
||||
"""Test getting greeting without providing a name."""
|
||||
result = GreetingService.get_greeting()
|
||||
assert result == {"message": "Hello from backend!"}
|
||||
|
||||
def test_get_greeting_with_name(self) -> None:
|
||||
"""Test getting greeting with a name."""
|
||||
result = GreetingService.get_greeting("Alice")
|
||||
assert result == {"message": "Hello, Alice!"}
|
||||
|
||||
def test_get_greeting_with_empty_string(self) -> None:
|
||||
"""Test getting greeting with empty string name."""
|
||||
result = GreetingService.get_greeting("")
|
||||
assert result == {"message": "Hello from backend!"}
|
||||
49
tests/test_routes.py
Normal file
49
tests/test_routes.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""Tests for routes."""
|
||||
|
||||
import pytest
|
||||
|
||||
from app import create_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
"""Create a test client for the Flask application."""
|
||||
app = create_app()
|
||||
app.config["TESTING"] = True
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
|
||||
class TestMainRoutes:
|
||||
"""Test cases for main routes."""
|
||||
|
||||
def test_index_route(self, client) -> None:
|
||||
"""Test the index route."""
|
||||
response = client.get("/api/")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json() == {"message": "Hello from backend!"}
|
||||
|
||||
def test_hello_route_without_name(self, client) -> None:
|
||||
"""Test hello route without name parameter."""
|
||||
response = client.get("/api/hello")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json() == {"message": "Hello from backend!"}
|
||||
|
||||
def test_hello_route_with_name(self, client) -> None:
|
||||
"""Test hello route with name parameter."""
|
||||
response = client.get("/api/hello/Alice")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json() == {"message": "Hello, Alice!"}
|
||||
|
||||
def test_health_route(self, client) -> None:
|
||||
"""Test health check route."""
|
||||
response = client.get("/api/health")
|
||||
assert response.status_code == 200
|
||||
assert response.get_json() == {"status": "ok"}
|
||||
|
||||
def test_protected_route_without_auth(self, client) -> None:
|
||||
"""Test protected route without authentication."""
|
||||
response = client.get("/api/protected")
|
||||
assert response.status_code == 401
|
||||
data = response.get_json()
|
||||
assert data["error"] == "Authentication required"
|
||||
141
tests/test_token_service.py
Normal file
141
tests/test_token_service.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""Tests for TokenService."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import patch
|
||||
|
||||
import jwt
|
||||
import pytest
|
||||
|
||||
from app.services.token_service import TokenService
|
||||
|
||||
|
||||
class TestTokenService:
|
||||
"""Test cases for TokenService."""
|
||||
|
||||
def test_init(self) -> None:
|
||||
"""Test TokenService initialization."""
|
||||
token_service = TokenService()
|
||||
assert token_service.algorithm == "HS256"
|
||||
assert token_service.access_token_expire_minutes == 15
|
||||
assert token_service.refresh_token_expire_days == 7
|
||||
|
||||
def test_generate_access_token(self) -> None:
|
||||
"""Test access token generation."""
|
||||
token_service = TokenService()
|
||||
user_data = {
|
||||
"id": "123",
|
||||
"email": "test@example.com",
|
||||
"name": "Test User"
|
||||
}
|
||||
|
||||
token = token_service.generate_access_token(user_data)
|
||||
assert isinstance(token, str)
|
||||
|
||||
# Verify token content
|
||||
payload = jwt.decode(token, token_service.secret_key, algorithms=[token_service.algorithm])
|
||||
assert payload["user_id"] == "123"
|
||||
assert payload["email"] == "test@example.com"
|
||||
assert payload["name"] == "Test User"
|
||||
assert payload["type"] == "access"
|
||||
|
||||
def test_generate_refresh_token(self) -> None:
|
||||
"""Test refresh token generation."""
|
||||
token_service = TokenService()
|
||||
user_data = {
|
||||
"id": "123",
|
||||
"email": "test@example.com",
|
||||
"name": "Test User"
|
||||
}
|
||||
|
||||
token = token_service.generate_refresh_token(user_data)
|
||||
assert isinstance(token, str)
|
||||
|
||||
# Verify token content
|
||||
payload = jwt.decode(token, token_service.secret_key, algorithms=[token_service.algorithm])
|
||||
assert payload["user_id"] == "123"
|
||||
assert payload["type"] == "refresh"
|
||||
|
||||
def test_verify_valid_token(self) -> None:
|
||||
"""Test verifying a valid token."""
|
||||
token_service = TokenService()
|
||||
user_data = {"id": "123", "email": "test@example.com", "name": "Test User"}
|
||||
|
||||
token = token_service.generate_access_token(user_data)
|
||||
payload = token_service.verify_token(token)
|
||||
|
||||
assert payload is not None
|
||||
assert payload["user_id"] == "123"
|
||||
assert payload["type"] == "access"
|
||||
|
||||
def test_verify_invalid_token(self) -> None:
|
||||
"""Test verifying an invalid token."""
|
||||
token_service = TokenService()
|
||||
|
||||
payload = token_service.verify_token("invalid.token.here")
|
||||
assert payload is None
|
||||
|
||||
@patch("app.services.token_service.datetime")
|
||||
def test_verify_expired_token(self, mock_datetime) -> None:
|
||||
"""Test verifying an expired token."""
|
||||
# Set up mock to return a past time for token generation
|
||||
past_time = datetime(2020, 1, 1, tzinfo=timezone.utc)
|
||||
mock_datetime.now.return_value = past_time
|
||||
mock_datetime.UTC = timezone.utc
|
||||
|
||||
token_service = TokenService()
|
||||
user_data = {"id": "123", "email": "test@example.com", "name": "Test User"}
|
||||
|
||||
token = token_service.generate_access_token(user_data)
|
||||
|
||||
# Reset mock to current time for verification
|
||||
mock_datetime.now.return_value = datetime.now(timezone.utc)
|
||||
|
||||
payload = token_service.verify_token(token)
|
||||
assert payload is None
|
||||
|
||||
def test_is_access_token(self) -> None:
|
||||
"""Test access token type checking."""
|
||||
token_service = TokenService()
|
||||
|
||||
access_payload = {"type": "access", "user_id": "123"}
|
||||
refresh_payload = {"type": "refresh", "user_id": "123"}
|
||||
|
||||
assert token_service.is_access_token(access_payload)
|
||||
assert not token_service.is_access_token(refresh_payload)
|
||||
|
||||
def test_is_refresh_token(self) -> None:
|
||||
"""Test refresh token type checking."""
|
||||
token_service = TokenService()
|
||||
|
||||
access_payload = {"type": "access", "user_id": "123"}
|
||||
refresh_payload = {"type": "refresh", "user_id": "123"}
|
||||
|
||||
assert token_service.is_refresh_token(refresh_payload)
|
||||
assert not token_service.is_refresh_token(access_payload)
|
||||
|
||||
def test_get_user_from_access_token_valid(self) -> None:
|
||||
"""Test extracting user from valid access token."""
|
||||
token_service = TokenService()
|
||||
user_data = {"id": "123", "email": "test@example.com", "name": "Test User"}
|
||||
|
||||
token = token_service.generate_access_token(user_data)
|
||||
extracted_user = token_service.get_user_from_access_token(token)
|
||||
|
||||
assert extracted_user == user_data
|
||||
|
||||
def test_get_user_from_access_token_refresh_token(self) -> None:
|
||||
"""Test extracting user from refresh token (should fail)."""
|
||||
token_service = TokenService()
|
||||
user_data = {"id": "123", "email": "test@example.com", "name": "Test User"}
|
||||
|
||||
token = token_service.generate_refresh_token(user_data)
|
||||
extracted_user = token_service.get_user_from_access_token(token)
|
||||
|
||||
assert extracted_user is None
|
||||
|
||||
def test_get_user_from_access_token_invalid(self) -> None:
|
||||
"""Test extracting user from invalid token."""
|
||||
token_service = TokenService()
|
||||
|
||||
extracted_user = token_service.get_user_from_access_token("invalid.token")
|
||||
assert extracted_user is None
|
||||
Reference in New Issue
Block a user