mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
feat: initialize user timezone and language from browser (#36170)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
+112
-1
@@ -143,7 +143,118 @@ class TestEmailRegisterResetApi:
|
||||
response = EmailRegisterResetApi().post()
|
||||
|
||||
assert response == {"result": "success", "data": {"access_token": "a", "refresh_token": "r"}}
|
||||
mock_create_account.assert_called_once_with("invitee@example.com", "ValidPass123!")
|
||||
mock_create_account.assert_called_once_with(
|
||||
email="invitee@example.com",
|
||||
password="ValidPass123!",
|
||||
timezone=None,
|
||||
language=None,
|
||||
)
|
||||
mock_reset_login_rate.assert_called_once_with("invitee@example.com")
|
||||
mock_revoke_token.assert_called_once_with("token-123")
|
||||
mock_extract_ip.assert_called_once()
|
||||
|
||||
@patch("controllers.console.auth.email_register.AccountService.reset_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.email_register.AccountService.login")
|
||||
@patch("controllers.console.auth.email_register.EmailRegisterResetApi._create_new_account")
|
||||
@patch("controllers.console.auth.email_register.AccountService.get_account_by_email_with_case_fallback")
|
||||
@patch("controllers.console.auth.email_register.AccountService.revoke_email_register_token")
|
||||
@patch("controllers.console.auth.email_register.AccountService.get_email_register_data")
|
||||
@patch("controllers.console.auth.email_register.extract_remote_ip", return_value="127.0.0.1")
|
||||
def test_reset_passes_timezone_to_new_account(
|
||||
self,
|
||||
mock_extract_ip,
|
||||
mock_get_data,
|
||||
mock_revoke_token,
|
||||
mock_get_account,
|
||||
mock_create_account,
|
||||
mock_login,
|
||||
mock_reset_login_rate,
|
||||
app: Flask,
|
||||
):
|
||||
mock_get_data.return_value = {"phase": "register", "email": "Invitee@Example.com"}
|
||||
mock_create_account.return_value = MagicMock()
|
||||
token_pair = MagicMock()
|
||||
token_pair.model_dump.return_value = {"access_token": "a", "refresh_token": "r"}
|
||||
mock_login.return_value = token_pair
|
||||
mock_get_account.return_value = None
|
||||
|
||||
feature_flags = SimpleNamespace(enable_email_password_login=True, is_allow_register=True)
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config", SimpleNamespace(EDITION="CLOUD")),
|
||||
patch("controllers.console.wraps.FeatureService.get_system_features", return_value=feature_flags),
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/email-register",
|
||||
method="POST",
|
||||
json={
|
||||
"token": "token-123",
|
||||
"new_password": "ValidPass123!",
|
||||
"password_confirm": "ValidPass123!",
|
||||
"timezone": "Asia/Shanghai",
|
||||
},
|
||||
):
|
||||
response = EmailRegisterResetApi().post()
|
||||
|
||||
assert response == {"result": "success", "data": {"access_token": "a", "refresh_token": "r"}}
|
||||
mock_create_account.assert_called_once_with(
|
||||
email="invitee@example.com",
|
||||
password="ValidPass123!",
|
||||
timezone="Asia/Shanghai",
|
||||
language=None,
|
||||
)
|
||||
mock_reset_login_rate.assert_called_once_with("invitee@example.com")
|
||||
mock_revoke_token.assert_called_once_with("token-123")
|
||||
mock_extract_ip.assert_called_once()
|
||||
|
||||
@patch("controllers.console.auth.email_register.AccountService.reset_login_error_rate_limit")
|
||||
@patch("controllers.console.auth.email_register.AccountService.login")
|
||||
@patch("controllers.console.auth.email_register.EmailRegisterResetApi._create_new_account")
|
||||
@patch("controllers.console.auth.email_register.AccountService.get_account_by_email_with_case_fallback")
|
||||
@patch("controllers.console.auth.email_register.AccountService.revoke_email_register_token")
|
||||
@patch("controllers.console.auth.email_register.AccountService.get_email_register_data")
|
||||
@patch("controllers.console.auth.email_register.extract_remote_ip", return_value="127.0.0.1")
|
||||
def test_reset_passes_language_to_new_account(
|
||||
self,
|
||||
mock_extract_ip,
|
||||
mock_get_data,
|
||||
mock_revoke_token,
|
||||
mock_get_account,
|
||||
mock_create_account,
|
||||
mock_login,
|
||||
mock_reset_login_rate,
|
||||
app: Flask,
|
||||
):
|
||||
mock_get_data.return_value = {"phase": "register", "email": "Invitee@Example.com"}
|
||||
mock_create_account.return_value = MagicMock()
|
||||
token_pair = MagicMock()
|
||||
token_pair.model_dump.return_value = {"access_token": "a", "refresh_token": "r"}
|
||||
mock_login.return_value = token_pair
|
||||
mock_get_account.return_value = None
|
||||
|
||||
feature_flags = SimpleNamespace(enable_email_password_login=True, is_allow_register=True)
|
||||
with (
|
||||
patch("controllers.console.wraps.dify_config", SimpleNamespace(EDITION="CLOUD")),
|
||||
patch("controllers.console.wraps.FeatureService.get_system_features", return_value=feature_flags),
|
||||
):
|
||||
with app.test_request_context(
|
||||
"/email-register",
|
||||
method="POST",
|
||||
json={
|
||||
"token": "token-123",
|
||||
"new_password": "ValidPass123!",
|
||||
"password_confirm": "ValidPass123!",
|
||||
"language": "zh-Hans",
|
||||
},
|
||||
):
|
||||
response = EmailRegisterResetApi().post()
|
||||
|
||||
assert response == {"result": "success", "data": {"access_token": "a", "refresh_token": "r"}}
|
||||
mock_create_account.assert_called_once_with(
|
||||
email="invitee@example.com",
|
||||
password="ValidPass123!",
|
||||
timezone=None,
|
||||
language="zh-Hans",
|
||||
)
|
||||
mock_reset_login_rate.assert_called_once_with("invitee@example.com")
|
||||
mock_revoke_token.assert_called_once_with("token-123")
|
||||
mock_extract_ip.assert_called_once()
|
||||
|
||||
+128
-5
@@ -14,7 +14,7 @@ from controllers.console.auth.oauth import (
|
||||
_get_account_by_openid_or_email,
|
||||
get_oauth_providers,
|
||||
)
|
||||
from libs.oauth import OAuthUserInfo
|
||||
from libs.oauth import OAuthUserInfo, encode_oauth_state
|
||||
from models.account import AccountStatus
|
||||
from services.account_service import AccountService
|
||||
from services.errors.account import AccountRegisterError
|
||||
@@ -101,7 +101,55 @@ class TestOAuthLogin:
|
||||
with app.test_request_context(f"/auth/oauth/github?{query_string}"):
|
||||
resource.get("github")
|
||||
|
||||
mock_oauth_provider.get_authorization_url.assert_called_once_with(invite_token=expected_token)
|
||||
mock_oauth_provider.get_authorization_url.assert_called_once_with(
|
||||
invite_token=expected_token,
|
||||
timezone=None,
|
||||
language=None,
|
||||
)
|
||||
mock_redirect.assert_called_once_with("https://github.com/login/oauth/authorize?...")
|
||||
|
||||
@patch("controllers.console.auth.oauth.get_oauth_providers")
|
||||
@patch("controllers.console.auth.oauth.redirect")
|
||||
def test_should_pass_timezone_to_oauth_state(
|
||||
self,
|
||||
mock_redirect,
|
||||
mock_get_providers,
|
||||
resource,
|
||||
app: Flask,
|
||||
mock_oauth_provider,
|
||||
):
|
||||
mock_get_providers.return_value = {"github": mock_oauth_provider, "google": None}
|
||||
|
||||
with app.test_request_context("/auth/oauth/github?timezone=Asia/Shanghai"):
|
||||
resource.get("github")
|
||||
|
||||
mock_oauth_provider.get_authorization_url.assert_called_once_with(
|
||||
invite_token=None,
|
||||
timezone="Asia/Shanghai",
|
||||
language=None,
|
||||
)
|
||||
mock_redirect.assert_called_once_with("https://github.com/login/oauth/authorize?...")
|
||||
|
||||
@patch("controllers.console.auth.oauth.get_oauth_providers")
|
||||
@patch("controllers.console.auth.oauth.redirect")
|
||||
def test_should_pass_language_to_oauth_state(
|
||||
self,
|
||||
mock_redirect,
|
||||
mock_get_providers,
|
||||
resource,
|
||||
app: Flask,
|
||||
mock_oauth_provider,
|
||||
):
|
||||
mock_get_providers.return_value = {"github": mock_oauth_provider, "google": None}
|
||||
|
||||
with app.test_request_context("/auth/oauth/github?language=zh-Hans"):
|
||||
resource.get("github")
|
||||
|
||||
mock_oauth_provider.get_authorization_url.assert_called_once_with(
|
||||
invite_token=None,
|
||||
timezone=None,
|
||||
language="zh-Hans",
|
||||
)
|
||||
mock_redirect.assert_called_once_with("https://github.com/login/oauth/authorize?...")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -229,7 +277,8 @@ class TestOAuthCallback:
|
||||
mock_register_service.is_valid_invite_token.return_value = True
|
||||
mock_register_service.get_invitation_by_token.return_value = {"email": "user@example.com"}
|
||||
|
||||
with app.test_request_context("/auth/oauth/github/callback?code=test_code&state=invite123"):
|
||||
state = encode_oauth_state(invite_token="invite123", timezone="Asia/Shanghai")
|
||||
with app.test_request_context(f"/auth/oauth/github/callback?code=test_code&state={state}"):
|
||||
resource.get("github")
|
||||
|
||||
mock_register_service.get_invitation_by_token.assert_called_once_with(token="invite123")
|
||||
@@ -488,7 +537,13 @@ class TestAccountGeneration:
|
||||
|
||||
if should_create:
|
||||
mock_register_service.register.assert_called_once_with(
|
||||
email="test@example.com", name="Test User", password=None, open_id="123", provider="github"
|
||||
email="test@example.com",
|
||||
name="Test User",
|
||||
password=None,
|
||||
open_id="123",
|
||||
provider="github",
|
||||
language="en-US",
|
||||
timezone=None,
|
||||
)
|
||||
else:
|
||||
mock_register_service.register.assert_not_called()
|
||||
@@ -515,7 +570,75 @@ class TestAccountGeneration:
|
||||
_generate_account("github", user_info)
|
||||
|
||||
mock_register_service.register.assert_called_once_with(
|
||||
email="upper@example.com", name="Test User", password=None, open_id="123", provider="github"
|
||||
email="upper@example.com",
|
||||
name="Test User",
|
||||
password=None,
|
||||
open_id="123",
|
||||
provider="github",
|
||||
language="en-US",
|
||||
timezone=None,
|
||||
)
|
||||
|
||||
@patch("controllers.console.auth.oauth._get_account_by_openid_or_email", return_value=None)
|
||||
@patch("controllers.console.auth.oauth.FeatureService")
|
||||
@patch("controllers.console.auth.oauth.RegisterService")
|
||||
@patch("controllers.console.auth.oauth.AccountService")
|
||||
@patch("controllers.console.auth.oauth.TenantService")
|
||||
def test_should_register_with_browser_timezone(
|
||||
self,
|
||||
mock_tenant_service,
|
||||
mock_account_service,
|
||||
mock_register_service,
|
||||
mock_feature_service,
|
||||
mock_get_account,
|
||||
app: Flask,
|
||||
user_info,
|
||||
):
|
||||
mock_feature_service.get_system_features.return_value.is_allow_register = True
|
||||
mock_register_service.register.return_value = MagicMock()
|
||||
|
||||
with app.test_request_context(headers={"Accept-Language": "zh-Hans,zh;q=0.9"}):
|
||||
_generate_account("github", user_info, timezone="Asia/Shanghai")
|
||||
|
||||
mock_register_service.register.assert_called_once_with(
|
||||
email="test@example.com",
|
||||
name="Test User",
|
||||
password=None,
|
||||
open_id="123",
|
||||
provider="github",
|
||||
language="zh-Hans",
|
||||
timezone="Asia/Shanghai",
|
||||
)
|
||||
|
||||
@patch("controllers.console.auth.oauth._get_account_by_openid_or_email", return_value=None)
|
||||
@patch("controllers.console.auth.oauth.FeatureService")
|
||||
@patch("controllers.console.auth.oauth.RegisterService")
|
||||
@patch("controllers.console.auth.oauth.AccountService")
|
||||
@patch("controllers.console.auth.oauth.TenantService")
|
||||
def test_should_register_with_state_language(
|
||||
self,
|
||||
mock_tenant_service,
|
||||
mock_account_service,
|
||||
mock_register_service,
|
||||
mock_feature_service,
|
||||
mock_get_account,
|
||||
app: Flask,
|
||||
user_info,
|
||||
):
|
||||
mock_feature_service.get_system_features.return_value.is_allow_register = True
|
||||
mock_register_service.register.return_value = MagicMock()
|
||||
|
||||
with app.test_request_context(headers={"Accept-Language": "en-US,en;q=0.9"}):
|
||||
_generate_account("github", user_info, language="zh-Hans")
|
||||
|
||||
mock_register_service.register.assert_called_once_with(
|
||||
email="test@example.com",
|
||||
name="Test User",
|
||||
password=None,
|
||||
open_id="123",
|
||||
provider="github",
|
||||
language="zh-Hans",
|
||||
timezone=None,
|
||||
)
|
||||
|
||||
@patch("controllers.console.auth.oauth._get_account_by_openid_or_email")
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from controllers.console.auth.email_register import EmailRegisterResetApi, EmailRegisterResetPayload
|
||||
|
||||
|
||||
@patch("controllers.console.auth.email_register.AccountService.create_account_and_tenant")
|
||||
def test_create_new_account_uses_requested_language(mock_create_account):
|
||||
account = MagicMock()
|
||||
mock_create_account.return_value = account
|
||||
|
||||
result = EmailRegisterResetApi()._create_new_account(
|
||||
"invitee@example.com",
|
||||
"ValidPass123!",
|
||||
timezone="Asia/Shanghai",
|
||||
language="zh-Hans",
|
||||
)
|
||||
|
||||
assert result is account
|
||||
mock_create_account.assert_called_once_with(
|
||||
email="invitee@example.com",
|
||||
name="invitee@example.com",
|
||||
password="ValidPass123!",
|
||||
interface_language="zh-Hans",
|
||||
timezone="Asia/Shanghai",
|
||||
)
|
||||
|
||||
|
||||
def test_reset_payload_rejects_invalid_timezone():
|
||||
with pytest.raises(ValidationError):
|
||||
EmailRegisterResetPayload.model_validate(
|
||||
{
|
||||
"token": "token-123",
|
||||
"new_password": "ValidPass123!",
|
||||
"password_confirm": "ValidPass123!",
|
||||
"timezone": "",
|
||||
}
|
||||
)
|
||||
@@ -13,9 +13,10 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from pydantic import ValidationError
|
||||
|
||||
from controllers.console.auth.error import EmailCodeError, InvalidEmailError, InvalidTokenError
|
||||
from controllers.console.auth.login import EmailCodeLoginApi, EmailCodeLoginSendEmailApi
|
||||
from controllers.console.auth.login import EmailCodeLoginApi, EmailCodeLoginPayload, EmailCodeLoginSendEmailApi
|
||||
from controllers.console.error import (
|
||||
AccountInFreezeError,
|
||||
AccountNotFound,
|
||||
@@ -31,6 +32,18 @@ def encode_code(code: str) -> str:
|
||||
return base64.b64encode(code.encode("utf-8")).decode()
|
||||
|
||||
|
||||
def test_email_code_login_payload_rejects_invalid_timezone():
|
||||
with pytest.raises(ValidationError):
|
||||
EmailCodeLoginPayload.model_validate(
|
||||
{
|
||||
"email": "newuser@example.com",
|
||||
"code": "123456",
|
||||
"token": "token-123",
|
||||
"timezone": "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TestEmailCodeLoginSendEmailApi:
|
||||
"""Test cases for sending email verification codes."""
|
||||
|
||||
@@ -342,6 +355,7 @@ class TestEmailCodeLoginApi:
|
||||
"code": encode_code("123456"),
|
||||
"token": "valid_token",
|
||||
"language": "en-US",
|
||||
"timezone": "Asia/Shanghai",
|
||||
},
|
||||
):
|
||||
api = EmailCodeLoginApi()
|
||||
@@ -349,7 +363,12 @@ class TestEmailCodeLoginApi:
|
||||
|
||||
# Assert
|
||||
assert response.json["result"] == "success"
|
||||
mock_create_account.assert_called_once()
|
||||
mock_create_account.assert_called_once_with(
|
||||
email="newuser@example.com",
|
||||
name="newuser@example.com",
|
||||
interface_language="en-US",
|
||||
timezone="Asia/Shanghai",
|
||||
)
|
||||
|
||||
@patch("controllers.console.wraps.db")
|
||||
@patch("controllers.console.auth.login.AccountService.get_email_code_login_data")
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from controllers.console.auth.oauth import OAuthLogin, _generate_account
|
||||
from libs.oauth import OAuthUserInfo
|
||||
from services.errors.account import AccountRegisterError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app() -> Flask:
|
||||
app = Flask(__name__)
|
||||
app.config["TESTING"] = True
|
||||
return app
|
||||
|
||||
|
||||
@patch("controllers.console.auth.oauth.redirect")
|
||||
@patch("controllers.console.auth.oauth.get_oauth_providers")
|
||||
def test_oauth_login_passes_language_and_timezone_to_authorization_url(
|
||||
mock_get_oauth_providers,
|
||||
mock_redirect,
|
||||
app: Flask,
|
||||
):
|
||||
oauth_provider = MagicMock()
|
||||
oauth_provider.get_authorization_url.return_value = "https://github.com/login/oauth/authorize?state=..."
|
||||
mock_get_oauth_providers.return_value = {"github": oauth_provider}
|
||||
|
||||
with app.test_request_context("/oauth/login/github?language=zh-Hans&timezone=Asia/Shanghai"):
|
||||
OAuthLogin().get("github")
|
||||
|
||||
oauth_provider.get_authorization_url.assert_called_once_with(
|
||||
invite_token=None,
|
||||
timezone="Asia/Shanghai",
|
||||
language="zh-Hans",
|
||||
)
|
||||
mock_redirect.assert_called_once_with("https://github.com/login/oauth/authorize?state=...")
|
||||
|
||||
|
||||
@patch("controllers.console.auth.oauth.AccountService.link_account_integrate")
|
||||
@patch("controllers.console.auth.oauth.RegisterService")
|
||||
@patch("controllers.console.auth.oauth.FeatureService")
|
||||
@patch("controllers.console.auth.oauth._get_account_by_openid_or_email", return_value=None)
|
||||
def test_generate_account_registers_with_browser_timezone(
|
||||
mock_get_account,
|
||||
mock_feature_service,
|
||||
mock_register_service,
|
||||
mock_link_account,
|
||||
app: Flask,
|
||||
):
|
||||
account = MagicMock()
|
||||
mock_register_service.register.return_value = account
|
||||
mock_feature_service.get_system_features.return_value.is_allow_register = True
|
||||
user_info = OAuthUserInfo(id="github-123", name="Test User", email="User@Example.com")
|
||||
|
||||
with app.test_request_context(headers={"Accept-Language": "zh-Hans,zh;q=0.9"}):
|
||||
result, oauth_new_user = _generate_account("github", user_info, timezone="Asia/Shanghai")
|
||||
|
||||
assert result is account
|
||||
assert oauth_new_user is True
|
||||
mock_register_service.register.assert_called_once_with(
|
||||
email="user@example.com",
|
||||
name="Test User",
|
||||
password=None,
|
||||
open_id="github-123",
|
||||
provider="github",
|
||||
language="zh-Hans",
|
||||
timezone="Asia/Shanghai",
|
||||
)
|
||||
mock_link_account.assert_called_once_with("github", "github-123", account)
|
||||
|
||||
|
||||
@patch("controllers.console.auth.oauth.AccountService.link_account_integrate")
|
||||
@patch("controllers.console.auth.oauth.RegisterService")
|
||||
@patch("controllers.console.auth.oauth.FeatureService")
|
||||
@patch("controllers.console.auth.oauth._get_account_by_openid_or_email", return_value=None)
|
||||
def test_generate_account_prefers_state_language_over_accept_language(
|
||||
mock_get_account,
|
||||
mock_feature_service,
|
||||
mock_register_service,
|
||||
mock_link_account,
|
||||
app: Flask,
|
||||
):
|
||||
account = MagicMock()
|
||||
mock_register_service.register.return_value = account
|
||||
mock_feature_service.get_system_features.return_value.is_allow_register = True
|
||||
user_info = OAuthUserInfo(id="github-123", name="Test User", email="User@Example.com")
|
||||
|
||||
with app.test_request_context(headers={"Accept-Language": "en-US,en;q=0.9"}):
|
||||
_generate_account("github", user_info, language="zh-Hans")
|
||||
|
||||
mock_register_service.register.assert_called_once_with(
|
||||
email="user@example.com",
|
||||
name="Test User",
|
||||
password=None,
|
||||
open_id="github-123",
|
||||
provider="github",
|
||||
language="zh-Hans",
|
||||
timezone=None,
|
||||
)
|
||||
mock_link_account.assert_called_once_with("github", "github-123", account)
|
||||
|
||||
|
||||
@patch("controllers.console.auth.oauth.dify_config")
|
||||
@patch("controllers.console.auth.oauth.RegisterService")
|
||||
@patch("controllers.console.auth.oauth.FeatureService")
|
||||
@patch("controllers.console.auth.oauth._get_account_by_openid_or_email", return_value=None)
|
||||
def test_generate_account_rejects_new_user_when_registration_disabled(
|
||||
mock_get_account,
|
||||
mock_feature_service,
|
||||
mock_register_service,
|
||||
mock_config,
|
||||
app: Flask,
|
||||
):
|
||||
mock_feature_service.get_system_features.return_value.is_allow_register = False
|
||||
mock_config.BILLING_ENABLED = False
|
||||
user_info = OAuthUserInfo(id="github-123", name="Test User", email="user@example.com")
|
||||
|
||||
with app.test_request_context(headers={"Accept-Language": "en-US,en;q=0.9"}):
|
||||
with pytest.raises(AccountRegisterError):
|
||||
_generate_account("github", user_info)
|
||||
|
||||
mock_register_service.register.assert_not_called()
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from libs.oauth import OAuth
|
||||
from libs.oauth import OAuth, decode_oauth_state, encode_oauth_state
|
||||
|
||||
|
||||
def test_oauth_base_methods_raise_not_implemented():
|
||||
@@ -17,3 +17,17 @@ def test_oauth_base_methods_raise_not_implemented():
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
oauth._transform_user_info({})
|
||||
|
||||
|
||||
def test_oauth_state_round_trips_invite_token_timezone_and_language():
|
||||
state = encode_oauth_state(invite_token="invite-123", timezone="Asia/Shanghai", language="zh-Hans")
|
||||
|
||||
assert decode_oauth_state(state) == {
|
||||
"invite_token": "invite-123",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"language": "zh-Hans",
|
||||
}
|
||||
|
||||
|
||||
def test_oauth_state_returns_empty_payload_for_invalid_state():
|
||||
assert decode_oauth_state("invalid-state") == {}
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from libs.oauth import GitHubOAuth, GoogleOAuth, OAuthUserInfo
|
||||
from libs.oauth import GitHubOAuth, GoogleOAuth, OAuthUserInfo, decode_oauth_state
|
||||
|
||||
|
||||
class BaseOAuthTest:
|
||||
@@ -37,15 +37,25 @@ class TestGitHubOAuth(BaseOAuthTest):
|
||||
return GitHubOAuth(oauth_config["client_id"], oauth_config["client_secret"], oauth_config["redirect_uri"])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("invite_token", "expected_state"),
|
||||
("invite_token", "timezone", "language", "expected_state"),
|
||||
[
|
||||
(None, None),
|
||||
("test_invite_token", "test_invite_token"),
|
||||
("", None),
|
||||
(None, None, None, None),
|
||||
("test_invite_token", None, None, {"invite_token": "test_invite_token"}),
|
||||
("", None, None, None),
|
||||
(None, "Asia/Shanghai", None, {"timezone": "Asia/Shanghai"}),
|
||||
(None, None, "zh-Hans", {"language": "zh-Hans"}),
|
||||
(
|
||||
"test_invite_token",
|
||||
"Asia/Shanghai",
|
||||
"zh-Hans",
|
||||
{"invite_token": "test_invite_token", "timezone": "Asia/Shanghai", "language": "zh-Hans"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_should_generate_authorization_url_correctly(self, oauth, oauth_config, invite_token, expected_state):
|
||||
url = oauth.get_authorization_url(invite_token)
|
||||
def test_should_generate_authorization_url_correctly(
|
||||
self, oauth, oauth_config, invite_token, timezone, language, expected_state
|
||||
):
|
||||
url = oauth.get_authorization_url(invite_token, timezone=timezone, language=language)
|
||||
parsed, params = self.parse_auth_url(url)
|
||||
|
||||
assert parsed.scheme == "https"
|
||||
@@ -56,7 +66,7 @@ class TestGitHubOAuth(BaseOAuthTest):
|
||||
assert params["scope"][0] == "user:email"
|
||||
|
||||
if expected_state:
|
||||
assert params["state"][0] == expected_state
|
||||
assert decode_oauth_state(params["state"][0]) == expected_state
|
||||
else:
|
||||
assert "state" not in params
|
||||
|
||||
@@ -208,15 +218,25 @@ class TestGoogleOAuth(BaseOAuthTest):
|
||||
return GoogleOAuth(oauth_config["client_id"], oauth_config["client_secret"], oauth_config["redirect_uri"])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("invite_token", "expected_state"),
|
||||
("invite_token", "timezone", "language", "expected_state"),
|
||||
[
|
||||
(None, None),
|
||||
("test_invite_token", "test_invite_token"),
|
||||
("", None),
|
||||
(None, None, None, None),
|
||||
("test_invite_token", None, None, {"invite_token": "test_invite_token"}),
|
||||
("", None, None, None),
|
||||
(None, "Asia/Shanghai", None, {"timezone": "Asia/Shanghai"}),
|
||||
(None, None, "zh-Hans", {"language": "zh-Hans"}),
|
||||
(
|
||||
"test_invite_token",
|
||||
"Asia/Shanghai",
|
||||
"zh-Hans",
|
||||
{"invite_token": "test_invite_token", "timezone": "Asia/Shanghai", "language": "zh-Hans"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_should_generate_authorization_url_correctly(self, oauth, oauth_config, invite_token, expected_state):
|
||||
url = oauth.get_authorization_url(invite_token)
|
||||
def test_should_generate_authorization_url_correctly(
|
||||
self, oauth, oauth_config, invite_token, timezone, language, expected_state
|
||||
):
|
||||
url = oauth.get_authorization_url(invite_token, timezone=timezone, language=language)
|
||||
parsed, params = self.parse_auth_url(url)
|
||||
|
||||
assert parsed.scheme == "https"
|
||||
@@ -228,7 +248,7 @@ class TestGoogleOAuth(BaseOAuthTest):
|
||||
assert params["scope"][0] == "openid email"
|
||||
|
||||
if expected_state:
|
||||
assert params["state"][0] == expected_state
|
||||
assert decode_oauth_state(params["state"][0]) == expected_state
|
||||
else:
|
||||
assert "state" not in params
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ class TestAccountService:
|
||||
assert result.interface_theme == "light"
|
||||
assert result.password is not None
|
||||
assert result.password_salt is not None
|
||||
assert result.timezone is not None
|
||||
assert result.timezone == "America/New_York"
|
||||
|
||||
# Verify database operations
|
||||
mock_db_dependencies["db"].session.add.assert_called_once()
|
||||
@@ -271,7 +271,28 @@ class TestAccountService:
|
||||
assert added_account.interface_theme == "light"
|
||||
assert added_account.password is not None
|
||||
assert added_account.password_salt is not None
|
||||
assert added_account.timezone is not None
|
||||
assert added_account.timezone == "America/New_York"
|
||||
self._assert_database_operations_called(mock_db_dependencies["db"])
|
||||
|
||||
def test_create_account_uses_explicit_timezone(
|
||||
self, mock_db_dependencies, mock_password_dependencies, mock_external_service_dependencies
|
||||
):
|
||||
"""Test account creation prefers explicit browser timezone."""
|
||||
mock_external_service_dependencies["feature_service"].get_system_features.return_value.is_allow_register = True
|
||||
mock_external_service_dependencies["billing_service"].is_email_in_freeze.return_value = False
|
||||
mock_password_dependencies["hash_password"].return_value = b"hashed_password"
|
||||
|
||||
result = AccountService.create_account(
|
||||
email="test@example.com",
|
||||
name="Test User",
|
||||
interface_language="en-US",
|
||||
password="password123",
|
||||
timezone="Asia/Shanghai",
|
||||
)
|
||||
|
||||
assert result.timezone == "Asia/Shanghai"
|
||||
added_account = mock_db_dependencies["db"].session.add.call_args[0][0]
|
||||
assert added_account.timezone == "Asia/Shanghai"
|
||||
self._assert_database_operations_called(mock_db_dependencies["db"])
|
||||
|
||||
def test_create_account_registration_disabled(self, mock_external_service_dependencies):
|
||||
@@ -1221,6 +1242,7 @@ class TestRegisterService:
|
||||
interface_language="en-US",
|
||||
password="password123",
|
||||
is_setup=False,
|
||||
timezone=None,
|
||||
)
|
||||
mock_create_tenant.assert_called_once_with("Test User's Workspace")
|
||||
mock_create_member.assert_called_once_with(mock_tenant, mock_account, role="owner")
|
||||
|
||||
Reference in New Issue
Block a user