You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_flask_utils.py 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import contextvars
  2. import threading
  3. from typing import Optional
  4. import pytest
  5. from flask import Flask
  6. from flask_login import LoginManager, UserMixin, current_user, login_user
  7. from libs.flask_utils import preserve_flask_contexts
  8. class User(UserMixin):
  9. """Simple User class for testing."""
  10. def __init__(self, id: str):
  11. self.id = id
  12. def get_id(self) -> str:
  13. return self.id
  14. @pytest.fixture
  15. def login_app(app: Flask) -> Flask:
  16. """Set up a Flask app with flask-login."""
  17. # Set a secret key for the app
  18. app.config["SECRET_KEY"] = "test-secret-key"
  19. login_manager = LoginManager()
  20. login_manager.init_app(app)
  21. @login_manager.user_loader
  22. def load_user(user_id: str) -> Optional[User]:
  23. if user_id == "test_user":
  24. return User("test_user")
  25. return None
  26. return app
  27. @pytest.fixture
  28. def test_user() -> User:
  29. """Create a test user."""
  30. return User("test_user")
  31. def test_current_user_not_accessible_across_threads(login_app: Flask, test_user: User):
  32. """
  33. Test that current_user is not accessible in a different thread without preserve_flask_contexts.
  34. This test demonstrates that without the preserve_flask_contexts, we cannot access
  35. current_user in a different thread, even with app_context.
  36. """
  37. # Log in the user in the main thread
  38. with login_app.test_request_context():
  39. login_user(test_user)
  40. assert current_user.is_authenticated
  41. assert current_user.id == "test_user"
  42. # Store the result of the thread execution
  43. result = {"user_accessible": True, "error": None}
  44. # Define a function to run in a separate thread
  45. def check_user_in_thread():
  46. try:
  47. # Try to access current_user in a different thread with app_context
  48. with login_app.app_context():
  49. # This should fail because current_user is not accessible across threads
  50. # without preserve_flask_contexts
  51. result["user_accessible"] = current_user.is_authenticated
  52. except Exception as e:
  53. result["error"] = str(e) # type: ignore
  54. # Run the function in a separate thread
  55. thread = threading.Thread(target=check_user_in_thread)
  56. thread.start()
  57. thread.join()
  58. # Verify that we got an error or current_user is not authenticated
  59. assert result["error"] is not None or (result["user_accessible"] is not None and not result["user_accessible"])
  60. def test_current_user_accessible_with_preserve_flask_contexts(login_app: Flask, test_user: User):
  61. """
  62. Test that current_user is accessible in a different thread with preserve_flask_contexts.
  63. This test demonstrates that with the preserve_flask_contexts, we can access
  64. current_user in a different thread.
  65. """
  66. # Log in the user in the main thread
  67. with login_app.test_request_context():
  68. login_user(test_user)
  69. assert current_user.is_authenticated
  70. assert current_user.id == "test_user"
  71. # Save the context variables
  72. context_vars = contextvars.copy_context()
  73. # Store the result of the thread execution
  74. result = {"user_accessible": False, "user_id": None, "error": None}
  75. # Define a function to run in a separate thread
  76. def check_user_in_thread_with_manager():
  77. try:
  78. # Use preserve_flask_contexts to access current_user in a different thread
  79. with preserve_flask_contexts(login_app, context_vars):
  80. from flask_login import current_user
  81. if current_user:
  82. result["user_accessible"] = True
  83. result["user_id"] = current_user.id
  84. else:
  85. result["user_accessible"] = False
  86. except Exception as e:
  87. result["error"] = str(e) # type: ignore
  88. # Run the function in a separate thread
  89. thread = threading.Thread(target=check_user_in_thread_with_manager)
  90. thread.start()
  91. thread.join()
  92. # Verify that current_user is accessible and has the correct ID
  93. assert result["error"] is None
  94. assert result["user_accessible"] is True
  95. assert result["user_id"] == "test_user"