mirror of
https://github.com/langgenius/dify.git
synced 2026-05-23 18:38:26 +08:00
140 lines
4.6 KiB
Python
140 lines
4.6 KiB
Python
"""
|
|
Regression tests for `libs.helper.TokenManager`.
|
|
|
|
`TokenManager` is the storage primitive shared by multiple auth flows, so it
|
|
must preserve every metadata field written by the caller. Business-specific
|
|
validation now happens at the callsite boundary (for example,
|
|
`AccountService.get_change_email_data`), not inside `TokenManager`.
|
|
"""
|
|
|
|
import json
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
import libs.helper as helper_module
|
|
from libs.helper import TokenManager
|
|
|
|
|
|
def _build_fake_redis(storage: dict[str, str]):
|
|
def store_value(key: str, _ttl: int, value: str) -> bool:
|
|
storage[key] = value
|
|
return True
|
|
|
|
def load_value(key: str) -> str | None:
|
|
return storage.get(key)
|
|
|
|
return SimpleNamespace(
|
|
setex=store_value,
|
|
get=load_value,
|
|
delete=lambda *_args, **_kwargs: None,
|
|
)
|
|
|
|
|
|
def test_token_manager_roundtrip_preserves_untyped_metadata_keys(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""`TokenManager` must round-trip arbitrary metadata keys without silently
|
|
dropping fields such as `phase`, `email_change_phase`, or future auth
|
|
payload extensions.
|
|
"""
|
|
|
|
storage: dict[str, str] = {}
|
|
monkeypatch.setattr(helper_module, "redis_client", _build_fake_redis(storage))
|
|
|
|
token = TokenManager.generate_token(
|
|
email="user@example.com",
|
|
token_type="change_email",
|
|
additional_data={
|
|
"code": "654321",
|
|
"old_email": "old@example.com",
|
|
"phase": "legacy-phase",
|
|
"email_change_phase": "old_email",
|
|
"custom_marker": "preserve-me",
|
|
},
|
|
)
|
|
|
|
data = TokenManager.get_token_data(token, "change_email")
|
|
|
|
assert data is not None
|
|
assert data.get("phase") == "legacy-phase"
|
|
assert data.get("email_change_phase") == "old_email"
|
|
assert data.get("custom_marker") == "preserve-me"
|
|
|
|
|
|
def test_token_manager_roundtrip_uses_explicit_email_with_account(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""When both `account` and `email` are supplied, the token should bind the
|
|
stable `account_id` from the account and the target email from the explicit
|
|
email argument.
|
|
"""
|
|
|
|
storage: dict[str, str] = {}
|
|
monkeypatch.setattr(helper_module, "redis_client", _build_fake_redis(storage))
|
|
|
|
account = SimpleNamespace(id="acc-1", email="old@example.com")
|
|
|
|
token = TokenManager.generate_token(
|
|
account=account,
|
|
email="new@example.com",
|
|
token_type="change_email",
|
|
additional_data={
|
|
"code": "654321",
|
|
"old_email": "old@example.com",
|
|
"email_change_phase": "new_email",
|
|
},
|
|
)
|
|
|
|
data = TokenManager.get_token_data(token, "change_email")
|
|
|
|
assert data is not None
|
|
assert data.get("account_id") == "acc-1"
|
|
assert data.get("email") == "new@example.com"
|
|
assert data.get("old_email") == "old@example.com"
|
|
assert data.get("email_change_phase") == "new_email"
|
|
|
|
|
|
def test_token_manager_roundtrip_still_validates_declared_fields(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Unknown fields should be preserved, but declared baseline fields should
|
|
still be validated by `_token_data_adapter`.
|
|
"""
|
|
|
|
storage = {
|
|
"change_email:token:token-123": json.dumps(
|
|
{
|
|
"token_type": "change_email",
|
|
"account_id": "acc-1",
|
|
"email": ["not-a-string"],
|
|
"code": "654321",
|
|
"old_email": "old@example.com",
|
|
"email_change_phase": "old_email",
|
|
}
|
|
)
|
|
}
|
|
monkeypatch.setattr(helper_module, "redis_client", _build_fake_redis(storage))
|
|
|
|
with pytest.raises(ValidationError):
|
|
TokenManager.get_token_data("token-123", "change_email")
|
|
|
|
|
|
def test_token_manager_roundtrip_validates_email_change_phase_as_string(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""`email_change_phase` is part of the shared baseline schema, so obviously
|
|
malformed discriminator values should fail before the change-email-specific
|
|
union parsing at the callsite boundary.
|
|
"""
|
|
|
|
storage = {
|
|
"change_email:token:token-456": json.dumps(
|
|
{
|
|
"token_type": "change_email",
|
|
"account_id": "acc-1",
|
|
"email": "new@example.com",
|
|
"code": "654321",
|
|
"old_email": "old@example.com",
|
|
"email_change_phase": ["not-a-string"],
|
|
}
|
|
)
|
|
}
|
|
monkeypatch.setattr(helper_module, "redis_client", _build_fake_redis(storage))
|
|
|
|
with pytest.raises(ValidationError):
|
|
TokenManager.get_token_data("token-456", "change_email")
|