mirror of
https://github.com/langgenius/dify.git
synced 2026-06-13 04:01:12 +08:00
120 lines
4.2 KiB
Python
120 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from collections.abc import Callable, Iterator
|
|
from contextlib import contextmanager
|
|
from typing import Literal
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from faker import Faker
|
|
from flask import Flask
|
|
from sqlalchemy.orm import Session
|
|
|
|
from controllers.openapi.auth.data import AuthData
|
|
from libs.oauth_bearer import AuthContext, Scope, SubjectType, TokenType, reset_auth_ctx, set_auth_ctx
|
|
from models import Account, Tenant
|
|
from services.account_service import AccountService, TenantService
|
|
from tests.test_containers_integration_tests.helpers import generate_valid_password
|
|
|
|
|
|
@pytest.fixture
|
|
def app(flask_app_with_containers: Flask) -> Flask:
|
|
return flask_app_with_containers
|
|
|
|
|
|
@pytest.fixture
|
|
def make_account(db_session_with_containers: Session) -> Callable[..., Account]:
|
|
"""Factory that registers a real Account and gives it an owner workspace.
|
|
|
|
System feature gates are stubbed (registration / workspace creation
|
|
allowed) exactly like the AppDslService integration tests, so this stays a
|
|
pure account+tenant setup helper.
|
|
"""
|
|
|
|
# Depend on db_session_with_containers so the app context / DB session is
|
|
# active for the real AccountService/TenantService calls below.
|
|
assert db_session_with_containers is not None
|
|
|
|
def _make(*, with_owner_tenant: bool = True) -> Account:
|
|
fake = Faker()
|
|
with patch("services.account_service.FeatureService") as mock_feature_service:
|
|
mock_feature_service.get_system_features.return_value.is_allow_register = True
|
|
account = AccountService.create_account(
|
|
email=fake.email(),
|
|
name=fake.name(),
|
|
interface_language="en-US",
|
|
password=generate_valid_password(fake),
|
|
)
|
|
if with_owner_tenant:
|
|
TenantService.create_owner_tenant_if_not_exist(account, name=fake.company())
|
|
return account
|
|
|
|
return _make
|
|
|
|
|
|
def add_tenant_for_account(account: Account, *, role: str = "normal", name: str = "Second WS") -> Tenant:
|
|
"""Create an additional tenant and join ``account`` to it (real service calls)."""
|
|
with patch("services.account_service.FeatureService") as mock_feature_service:
|
|
mock_feature_service.get_system_features.return_value.is_allow_create_workspace = True
|
|
tenant = TenantService.create_tenant(name=name)
|
|
TenantService.create_tenant_member(tenant, account, role=role)
|
|
return tenant
|
|
|
|
|
|
def auth_for(
|
|
account: Account,
|
|
*,
|
|
app_model: object | None = None,
|
|
token_id: uuid.UUID | None = None,
|
|
caller_kind: Literal["account", "end_user"] | None = None,
|
|
) -> AuthData:
|
|
"""Build an AuthData for ``account`` (and optionally an app context).
|
|
|
|
``token_id`` is needed by the self-revoke endpoint, and ``caller_kind`` by
|
|
any handler calling ``require_app_context`` (e.g. file upload / task stop).
|
|
"""
|
|
return AuthData(
|
|
token_type=TokenType.OAUTH_ACCOUNT,
|
|
account_id=uuid.UUID(str(account.id)),
|
|
token_hash="integration-test",
|
|
token_id=token_id,
|
|
scopes=frozenset({Scope.FULL}),
|
|
caller=account,
|
|
caller_kind=caller_kind,
|
|
app=app_model, # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
@contextmanager
|
|
def account_auth_context(
|
|
account: Account,
|
|
*,
|
|
token_id: uuid.UUID,
|
|
client_id: str = "integration-cli",
|
|
) -> Iterator[AuthContext]:
|
|
"""Publish an account ``AuthContext`` for handlers that read ``get_auth_ctx()``.
|
|
|
|
The auth pipeline normally sets this ContextVar; the integration suite
|
|
bypasses the pipeline via ``inspect.unwrap``, so endpoints that resolve the
|
|
caller through ``get_auth_ctx()`` (the ``/account/sessions*`` family) need it
|
|
set explicitly. Resets on exit so the worker thread can't leak identity.
|
|
"""
|
|
ctx = AuthContext(
|
|
subject_type=SubjectType.ACCOUNT,
|
|
subject_email=account.email,
|
|
subject_issuer=None,
|
|
account_id=uuid.UUID(str(account.id)),
|
|
client_id=client_id,
|
|
scopes=frozenset({Scope.FULL}),
|
|
token_id=token_id,
|
|
token_type=TokenType.OAUTH_ACCOUNT,
|
|
expires_at=None,
|
|
token_hash="integration-test",
|
|
)
|
|
reset_token = set_auth_ctx(ctx)
|
|
try:
|
|
yield ctx
|
|
finally:
|
|
reset_auth_ctx(reset_token)
|